do { unsigned long sent;
sprintf (name, "%s.%d", av[1], ++cnt);
if ((fdout = creat (name, 0644)) < 0) {
fprintf (stderr, "Cannot create %s\n", name); exit (3);
}
sent = 0L; /* сколько байт переслано */
for(;;){ unsigned isRead, /* прочитано read-ом */
need = min(ONEFILESIZE - sent, PORTION);
if( need == 0 ) break;
sent += (isRead = read (fdin, buf, need));
errno = 0;
if (write (fdout, buf, isRead) != isRead &&
errno){ perror("write"); exit(4);
} else if (isRead < need){ done++; break; }
}
if(close (fdout) < 0){
perror("Мало места на диске"); exit(5);
}
printf("%s\t%lu байт\n", name, sent);
} while( !done ); exit(0);
}
А. Богатырев, 1992-95 - 157 - Си в UNIX
4.31. Напишите обратную программу, которая склеивает несколько файлов в один. Это
аналог команды cat с единственным отличием: результат выдается не в стандартный
вывод, а в файл, указанный в строке аргументов последним. Для выдачи в стандартный
вывод следует указать имя "-".
#include
#include
void main (int ac, char **av){
int i, err = 0; FILE *fpin, *fpout;
if (ac < 3) {
fprintf(stderr,"Usage: %s from... to\n", av[0]);
exit(1);
}
fpout = strcmp(av[ac-1], "-") ? /* отлично от "-" */
fopen (av[ac-1], "wb") : stdout;
for (i = 1; i < ac-1; i++) {
register int c;
fprintf (stderr, "%s\n", av[i]);
if ((fpin = fopen (av[i], "rb")) == NULL) {
fprintf (stderr, "Cannot read %s\n", av[i]);
err++; continue;
}
while ((c = getc (fpin)) != EOF)
putc (c, fpout);
fclose (fpin);
}
fclose (fpout); exit (err);
}
Обе эти программы могут без изменений транслироваться и в MS DOS и в UNIX. UNIX
просто игнорирует букву b в открытии файла "rb", "wb". При работе с read мы могли бы
открывать файл как
#ifdef M_UNIX
# define O_BINARY 0
#endif
int fdin = open( av[1], O_RDONLY | O_BINARY);
4.32. Каким образом стандартный ввод переключить на ввод из заданного файла, а стан-
дартный вывод - в файл? Как проверить, существует ли файл; пуст ли он? Как надо
открывать файл для дописывания информации в конец существующего файла? Как надо отк-
рывать файл, чтобы попеременно записывать и читать тот же файл? Указание: см. fopen,
freopen, dup2, stat. Ответ про перенаправления ввода:
способ 1 (библиотечные функции)
#include
...
freopen( "имя_файла", "r", stdin );
способ 2 (системные вызовы)
#include
int fd;
...
fd = open( "имя_файла", O_RDONLY );
dup2 ( fd, 0 ); /* 0 - стандартный ввод */
close( fd ); /* fd больше не нужен - закрыть
его, чтоб не занимал место в таблице */
А. Богатырев, 1992-95 - 158 - Си в UNIX
способ 3 (системные вызовы)
#include
int fd;
...
fd = open( "имя_файла", O_RDONLY );
close (0); /* 0 - стандартный ввод */
fcntl (fd, F_DUPFD, 0 ); /* 0 - стандартный ввод */
close (fd);
Это перенаправление ввода соответствует конструкции
$ a.out < имя_файла
написанной на командном языке СиШелл. Для перенаправления вывода замените 0 на 1,
stdin на stdout, open на creat, "r" на "w".
Рассмотрим механику работы вызова dup2 [*]:
new = open("файл1",...); dup2(new, old); close(new);
таблица открытых
файлов процесса
...## ##
new----##---> файл1 new---##---> файл1
## ##
old----##---> файл2 old---## файл2
## ##
0:до вызова 1:разрыв связи old с файл2
dup2() (закрытие канала old, если он был открыт)
## ##
new----##--*--> файл1 new ## *----> файл1
## | ## |
old----##--* old--##--*
## ##
2:установка old на файл1 3:после оператора close(new);
на этом dup2 завершен. дескриптор new закрыт.
Здесь файл1 и файл2 - связующие структуры "открытый файл" в ядре, о которых рассказы-
валось выше (в них содержатся указатели чтения/записи). После вызова dup2 дескрипторы
new и old ссылаются на общую такую структуру и поэтому имеют один и тот же R/W-
указатель. Это означает, что в программе new и old являются синонимами и могут
использоваться даже вперемежку:
dup2(new, old);
write(new, "a", 1);
write(old, "b", 1);
write(new, "c", 1);
запишет в файл1 строку "abc". Программа
____________________
[*] Функция
int system(char *команда);
выполняет команду, записанную в строке команда, вызывая для этого интерпретатор ко-
манд
/bin/sh -c "команда"
А. Богатырев, 1992-95 - 159 - Си в UNIX
int fd;
printf( "Hi there\n");
fd = creat( "newout", 0640 );
dup2(fd, 1); close(fd);
printf( "Hey, You!\n");
выдаст первое сообщение на терминал, а второе - в файл newout, поскольку printf
выдает данные в канал stdout, связанный с дескриптором 1.
4.33. Напишите программу, которая будет выдавать подряд в стандартный вывод все
файлы, чьи имена указаны в аргументах командной строки. Используйте argc для органи-
зации цикла. Добавьте сквозную нумерацию строк и печать номера строки.
4.34. Напишите программу, распечатывающую первую директиву препроцессора, встретив-
шуюся в файле ввода.
#include
char buf[512], word[] = "#";
main(){ char *s; int len = strlen(word);
while((s=fgets(buf, sizeof buf, stdin)) &&
strncmp(s, word, len));
fputs(s? s: "Не найдено.\n", stdout);
}
4.35. Напишите программу, которая переключает свой стандартный вывод в новый файл
имяФайла каждый раз, когда во входном потоке встречается строка вида
>>>>>>имяФайла
Ответ:
#include
char line[512];
main(){ FILE *fp = fopen("00", "w");
while(gets(line) != NULL)
if( !strncmp(line, ">>>", 3)){
if( freopen(line+3, "a", fp) == NULL){
fprintf(stderr, "Can't write to '%s'\n", line+3);
fp = fopen("00", "a");
}
} else fprintf(fp, "%s\n", line);
}
4.36. Библиотека буферизованного обмена stdio содержит функции, подобные некоторым
системным вызовам. Вот функции - аналоги read и write:
Стандартная функция fread из библиотеки стандартных функций Си предназначена для
чтения нетекстовой (как правило) информации из файла:
____________________
и возвращает код ответа этой программы. Функция popen (pipe open) также запускает
интерпретатор команд, при этом перенаправив его стандартный вывод в трубу (pipe).
Другой конец этой трубы можно читать через канал fp, т.е. можно прочесть в свою прог-
рамму выдачу запущенной команды.
____________________
[*] dup2 читается как "dup to", в английском жаргоне принято обозначать предлог "to"
цифрой 2, поскольку слова "to" и "two" произносятся одинаково: "ту". "From me 2
You". Также 4 читается как "for".
А. Богатырев, 1992-95 - 160 - Си в UNIX
int fread(addr, size, count, fp)
register char *addr; unsigned size, count; FILE *fp;
{ register c; unsigned ndone=0, sz;
if(size)
for( ; ndone < count ; ndone++){
sz = size;
do{ if((c = getc(fp)) >= 0 )
*addr++ = c;
else return ndone;
}while( --sz );
}
return ndone;
}
Заметьте, что count - это не количество БАЙТ (как в read), а количество ШТУК размером
size байт. Функция выдает число целиком прочитанных ею ШТУК. Существует аналогичная
функция fwrite для записи в файл. Пример:
#include
#define MAXPTS 200
#define N 127
char filename[] = "pts.dat";
struct point { int x,y; } pts[MAXPTS], pp= { -1, -2};
main(){
int n, i;
FILE *fp = fopen(filename, "w");
for(i=0; i < N; i++) /* генерация точек */
pts[i].x = i, pts[i].y = i * i;
/* запись массива из N точек в файл */
fwrite((char *)pts, sizeof(struct point), N, fp);
fwrite((char *)&pp, sizeof pp, 1, fp);
fp = freopen(filename, "r", fp);
/* или fclose(fp); fp=fopen(filename, "r"); */
/* чтение точек из файла в массив */
n = fread(pts, sizeof pts[0], MAXPTS, fp);
for(i=0; i < n; i++)
printf("Точка #%d(%d,%d)\n",i,pts[i].x,pts[i].y);
}
Файлы, созданные fwrite, не переносимы на машины другого типа, поскольку в них хра-
нится не текст, а двоичные данные в формате, используемом данным процессором. Такой
файл не может быть понят человеком - он не содержит изображений данных в виде текста,
а содержит "сырые" байты. Поэтому чаще пользуются функциями работы с текстовыми фай-
лами: fprintf, fscanf, fputs, fgets. Данные, хранимые в виде текста, имеют еще одно
преимущество помимо переносимости: их легко при нужде подправить текстовым редакто-
ром. Зато они занимают больше места!
Аналогом системного вызова lseek служит функция fseek:
fseek(fp, offset, whence);
Она полностью аналогична lseek, за исключением возвращаемого ею значения. Она НЕ
возвращает новую позицию указателя чтения/записи! Чтобы узнать эту позицию применя-
ется специальная функция
long ftell(fp);
Она вносит поправку на положение указателя в буфере канала fp. fseek сбрасывает флаг
"был достигнут конец файла", который проверяется макросом feof(fp);
А. Богатырев, 1992-95 - 161 - Си в UNIX
4.37. Найдите ошибку в программе (программа распечатывает корневой каталог в "ста-
ром" формате каталогов - с фиксированной длиной имен):
#include
#include
#include
main(){
FILE *fp;
struct direct d;
char buf[DIRSIZ+1]; buf[DIRSIZ] = '\0';
fp = fopen( '/', "r" );
while( fread( &d, sizeof d, 1, fp) == 1 ){
if( !d.d_ino ) continue; /* файл стерт */
strncpy( buf, d.d_name, DIRSIZ);
printf( "%s\n", buf );
}
fclose(fp);
}
Указание: смотри в fopen(). Внимательнее к строкам и символам! '/' и "/" - это
совершенно разные вещи (хотя синтаксической ошибки нет!).
Переделайте эту программу, чтобы название каталога поступало из аргументов main
(а если название не задано - используйте текущий каталог ".").
4.38. Функциями
fputs( строка, fp);
printf( формат, ...);
fprintf(fp, формат, ...);
невозможно вывести строку формат, содержащую в середине байт '\0', поскольку он слу-
жит для них признаком конца строки. Однако такой байт может понадобиться в файле,
если мы формируем некоторые нетекстовые данные, например управляющую последователь-
ность переключения шрифтов для принтера. Как быть? Есть много вариантов решения.
Пусть мы хотим выдать в канал fp последовательность из 4х байт "\033e\0\5". Мы можем
сделать это посимвольно:
putc('\033',fp); putc('e', fp);
putc('\000',fp); putc('\005',fp);