© Иван Рощин, Москва
| ZXNet | : 500:95/462.53 |
| : bestview@mtu-net.ru | |
| WWW | : http://www.ivr.da.ru |
(«Радиомир. Ваш
компьютер» 5/2004)
Исправленная и дополненная версия.
Дата последнего редактирования: 2.11.2004.
Предлагаю вашему вниманию программу, с помощью которой можно разрезать заданный файл на части указанной длины, или получить часть файла указанной длины, начиная с указанного смещения, или пропустить первые X байтов файла, а остаток разрезать на части размером Y байтов, или разделить файл на части в X, Y, Z байтов и остаток Как вы увидите дальше, программа позволяет разрезать файл на части и более сложным образом.
(Замечу, что на самом деле «разрезание» не совсем точный термин, так как программа не изменяет исходный файл, а создаёт новые файлы части исходного.)
При запуске программы надо указать в командной строке имя исходного файла, имя получаемых файлов (без расширения оно будет сформировано автоматически) и команды, определяющие, как именно файл будет разбит на части.
Пример:
file_cut.exe 1.txt aaa 0x735-,92163,*
Исходный файл предполагается находящимся в текущем каталоге, там же будут сформированы и выходные файлы. Расширение первого сформированного файла будет «001», второго «002», и так далее (если получится более 999 частей, будут записаны только первые 999).
Теперь о командах, определяющих, как именно файл будет разбит на части. Может быть указана одна или несколько команд; если их несколько, они разделяются запятыми. Всего можно использовать четыре различных типа команд:
| n | | записать часть длиной n байтов, начиная с текущей позиции в исходном файле (если до конца исходного файла осталось менее n байтов, размер записанной части будет равен оставшейся части файла); |
| n | | пропустить n байтов с текущей позиции в исходном файле (если до конца исходного файла осталось менее n байтов, будут пропущены все оставшиеся байты); |
| n* | | записывать части длиной n байтов, начиная с текущей позиции в исходном файле, пока не будет достигнут конец файла (таким образом, длина последней записанной части может оказаться меньше n); |
| * | | записать часть, начиная с текущей позиции в исходном файле и до конца файла. Эта команда, очевидно, может быть только последней. |
Во всех командах n число от 1 до 0x7FFFFFFF (2147483647), записанное в десятичном или в шестнадцатеричном виде (при записи в шестнадцатеричном виде число должно предваряться символами «0x», регистр символов в записи числа не имеет значения).
Программа может обработать не более 64 команд (определяется значением
константы MAX_COMM в исходном тексте программы), но столько команд вы всё
равно не введёте
Команды выполняются в порядке их записи. Перед выполнением первой команды
текущая позиция в исходном файле это начало файла. Работа
программы заканчивается, если все команды выполнены, или если достигнут конец
исходного файла, или если уже записано 999 частей файла, или если произошла
Всего лишь четыре типа команд дают возможность достаточно разнообразной обработки файлов. Ниже приведены несколько примеров.
500
500-,*
0x1000-,0x300
100,200,300,*
10000*
0x400-,123,0x100-,*
Также замечу, что не очень сложно доработать программу, добавив ещё
/* =============================
File: "file_cut.c"
Compiler: Turbo C 2.0
============================= */
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include <limits.h>
void save_part (char *nameonly, long begin_part, long len_part);
int work_com (char* s);
int get_val (char **s, long *v);
#define MAX_COMM 64 /* Максимальное количество команд. */
int n_comm=0; /* Количество команд. */
int comm_type [MAX_COMM]; /* Тип команды (0-3). */
long comm_val [MAX_COMM]; /* Число в команде (если есть). */
int n_part=1; /* Номер записываемой части. */
long len_src, point_src=0; /* Длина исходного файла и положение указателя. */
FILE *f_src, *f_dst;
void main (int argc, char *argv[])
{
printf ("\nFile_Cut by Ivan Roshin, 28.03.2004.\n");
if (argc!=4)
{printf ("Syntax: file_cut.exe source-file destiny-file(without extension) commands\n"); exit(0);}
/* Разбираем заданные команды. */
if (work_com(argv[3])==1) {printf ("Incorrect commands!\n"); exit(0);}
/* Проверяем, чтобы имя формируемых частей было не длиннее 8 символов. */
if (strlen(argv[2])>8) {printf ("Error in destiny filename!\n"); exit(0);}
f_src=fopen(argv[1],"rb"); /* Открываем исходный файл. */
if (f_src==NULL) {printf ("Can't open source file!\n"); exit(0);}
len_src=filelength(fileno(f_src)); /* Определяем длину исходного файла. */
if (len_src==-1) {printf ("Can't define source length!\n"); exit(0);}
/* Выполнение указанных команд (если длина исходного файла больше 0). */
if (len_src>0)
{
int i;
for (i=0; i<n_comm; i++)
{
switch (comm_type[i])
{
case 0: /* Запись части файла заданной длины. */
if (comm_val[i]>(len_src-point_src)) comm_val[i]=len_src-point_src;
save_part (argv[2], point_src, comm_val[i]);
point_src+=comm_val[i]; n_part++;
break;
case 1: /* Пропуск части файла заданной длины. */
if (comm_val[i]>(len_src-point_src)) comm_val[i]=len_src-point_src;
point_src+=comm_val[i];
break;
case 2: /* Запись частей заданной длины, пока не кончится файл или общее
количество записанных частей не достигнет 999. */
do
{
if (comm_val[i]>(len_src-point_src)) comm_val[i]=len_src-point_src;
save_part (argv[2], point_src, comm_val[i]);
point_src+=comm_val[i]; n_part++;
} while ((point_src<len_src)&&(n_part<=999));
break;
case 3: /* Запись остатка файла. */
save_part (argv[2], point_src, len_src-point_src);
point_src=len_src; n_part++;
}
/* Если файл уже обработан или номер следующей записываемой части больше
максимального (999) - заканчиваем выполнение команд. */
if ((point_src==len_src)||(n_part>999)) break;
}
}
if (fclose(f_src)!=0) printf ("Can't close source file!");
printf("\n");
}
/* ==========================================================================
Функция save_part - запись файла-части.
Вход: nameonly - имя записываемого файла (без расширения),
n_part - номер записываемой части (по нему формируется расширение),
begin_part - смещение части от начала исходного файла,
len_part - длина части.
========================================================================= */
void save_part (char *nameonly, long begin_part, long len_part)
{
long i;
char dst_ext[4];
char dst_name[12];
/* Формируем имя записываемого файла с расширением. */
itoa (n_part,dst_ext,10);
strcpy (dst_name, nameonly);
strcat (dst_name, ".");
for (i=strlen(dst_ext);i<3;i++) strcat (dst_name,"0");
strcat (dst_name, dst_ext);
/* Записываем. */
printf ("Creating file: %s ",dst_name);
f_dst=fopen(dst_name,"wb");
if (f_dst==NULL) {printf ("Can't open destiny file!\n"); exit(0);}
fseek (f_src, begin_part, SEEK_SET);
for (i=0;i<len_part;i++)
{
int c;
c=fgetc(f_src); if (c==EOF) {printf ("Can't read from source file!\n"); exit(0);}
if (fputc(c,f_dst)==EOF) {printf ("Can't write to destiny file!\n"); exit(0);}
}
if (fclose(f_dst)!=0) {printf ("Can't close destiny file!\n"); exit(0);}
printf ("OK\n");
}
/* ==========================================================================
Функция work_com - разбор заданных команд.
Вход: s - строка с командами.
Выход: 0 - OK, 1 - ошибка в строке с командами.
========================================================================= */
int work_com (char *s)
{
m1: /* Главный цикл. */
/* Строка с командами закончилась? */
if (*s==0) {if (n_comm==0) return (1); else return (0);}
/* В массиве ещё есть место для команд? */
if (n_comm==MAX_COMM) return (1);
/* Команда началась с числа? */
if ((*s>='0')&&(*s<='9'))
{
if (get_val(&s,&comm_val[n_comm])==1) return (1); /* Ошибка! */
/* Проверяем, имеется ли за числом символ "-" или "*", и в зависимости
от этого определяем тип команды. */
if (*s=='-') {comm_type[n_comm]=1; s++; n_comm++; goto end_comm;}
if (*s=='*') {comm_type[n_comm]=2; s++; n_comm++; goto end_comm;}
comm_type[n_comm]=0; n_comm++; goto end_comm;
}
/* Команда "*"? */
if (*s=='*') {comm_type[n_comm]=3; s++; n_comm++; goto end_comm;}
/* Команда не распознана. */
return (1); /* Ошибка! */
/* Текущая команда закончилась. Проверяем: теперь должен быть либо конец
строки (код 0), либо запятая и далее ненулевой символ (начало следующей
команды). */
end_comm:
if (*s==0) goto m1;
if ((*s==',')&&(*(s+1)!=0)) {s++; goto m1;}
return (1); /* Ошибка! */
}
/* ==========================================================================
Функция get_val - преобразование строки с записью числа в это число.
Число может быть представлено в десятичном или шестнадцатеричном виде
и должно находиться в диапазоне 0<x<=LONG_MAX.
Вход: s - адрес указателя на строку, v - адрес, куда поместить число.
Выход: если число задано корректно, функция возвращает 0, число помещено
по адресу v, а указатель на строку указывает на первый символ после числа.
Если число задано некорректно, функция возвращает 1.
========================================================================= */
int get_val (char **s, long *v)
{
int d; /* Очередная цифра числа. */
/* Если первый символ строки - '1'-'9', то началось десятичное число. */
if ((**s>='1')&&(**s<='9'))
{
/* Началось десятичное число, обрабатываем. */
*v=**s-'0';
loop_dec:
(*s)++;
if ((**s>='0')&&(**s<='9')) /* Продолжение числа (цифра 0-9)? */
{
d=**s-'0';
/* Добавляем цифру к числу (и следим за переполнением). */
if (*v>(LONG_MAX/10)) return (1); /* Ошибка! */ else *v*=10;
if ((LONG_MAX-*v)<d) return (1); /* Ошибка! */ else *v+=d;
goto loop_dec;
}
return(0); /* OK! */
}
/* Если первый символ строки - '0', то началось шестнадцатеричное число
(десятичное число не может начинаться с нуля). */
if (**s=='0')
{
(*s)++; if ((**s!='x')&&(**s!='X')) return (1); /* Ошибка! */
*v=0;
loop_hex:
(*s)++;
if (((**s>='0')&&(**s<='9'))||((**s>='a')&&(**s<='f'))||((**s>='A')&&(**s<='F')))
{
/* Получаем значение очередной цифры числа. */
if ((**s>='0')&&(**s<='9')) d=**s-'0';
if ((**s>='a')&&(**s<='f')) d=**s-'a'+10;
if ((**s>='A')&&(**s<='F')) d=**s-'A'+10;
/* Добавляем цифру к числу (и следим за переполнением). */
if (*v>(LONG_MAX/16)) return (1); /* Ошибка! */ else *v*=16;
if ((LONG_MAX-*v)<d) return (1); /* Ошибка! */ else *v+=d;
goto loop_hex;
}
/* Число кончилось. Теперь, если *v=0, то либо число равно 0, либо в числе
не было ни одной цифры. И то и другое недопустимо. */
if (*v==0) return (1); /* Ошибка! */ else return (0); /* OK! */
}
/* Число в строке не обнаружено. */
return (1); /* Ошибка! */
}
Если кто не знает, как выполнять обратную задачу
склеивать файлы из частей, напоминаю, что это можно сделать с
помощью досовской команды «copy». В командной строке надо указать
параметр «/b» (двоичное склеивание), имена склеиваемых файлов в
нужном порядке, соединённые знаком «+», и имя
Пример:
copy /b 1.txt+2.txt+3.txt all.txt
Обращаю внимание, что получаемый файл не должен совпадать с
Описанная в этой статье программа не может обрабатывать файлы, длина которых больше или равна 2 ГБ (2147483648 байтов). Дело в том, что используемая в программе библиотечная функция для определения длины файла (filelength) возвращает результат типа long, а максимальное положительное значение этого типа в Turbo C 2.0 равно 0x7FFFFFFF (2147483647), т.е. 2 ГБ 1. Также значение типа long требует в качестве входного параметра функция fseek, используемая в программе для перехода к нужной позиции в обрабатываемом файле.