ндексный узел,
паспорт), что и исходный файл. Это просто альтернативное имя файла, учитываемое
в поле di_nlink в I-node.
____________________
|- BSD - семейство UNIX-ов из University of California, Berkley. Berkley Software
Distribution.
А. Богатырев, 1992-95 - 145 - Си в UNIX
symbolic link
- создается вызовом symlink. Это отдельный самостоятельный файл, с собственным
I-node. Правда, коды доступа к этому файлу не играют никакой роли; значимы
только коды доступа указуемого файла.
4.7. Напишите программу, которая находит в файле символ @ и выдает файл с этого
места дважды. Указание: для запоминания позиции в файле используйте вызов lseek() -
позиционирование указателя чтения/записи:
long offset, lseek();
...
/* Узнать текущую позицию чтения/записи:
* сдвиг на 0 от текущей позиции. lseek вернет новую
* позицию указателя (в байтах от начала файла). */
offset = lseek(fd, 0L, 1); /* ftell(fp) */
А для возврата в эту точку:
lseek(fd, offset, 0); /* fseek(fp, offset, 0) */
По поводу lseek надо помнить такие вещи:
- lseek(fd, offset, whence) устанавливает указатель чтения/записи на расстояние
offset байт
при whence:
0 от начала файла RWptr = offset;
1 от текущей позиции RWptr += offset;
2 от конца файла RWptr = длина_файла + offset;
Эти значения whence можно обозначать именами:
#include <stdio.h>
0 это SEEK_SET
1 это SEEK_CUR
2 это SEEK_END
- Установка указателя чтения/записи - это виртуальная операция, т.е. реального
подвода магнитных головок и вообще обращения к диску она не вызывает. Реальное
движение головок к нужному месту диска произойдет только при операциях
чтения/записи read()/write(). Поэтому lseek() - дешевая операция.
- lseek() возвращает новую позицию указателя чтения/записи RWptr относительно
начала файла (long смещение в байтах). Помните, что если вы используете это зна-
чение, то вы должны предварительно описать lseek как функцию, возвращающую длин-
ное целое: long lseek();
- Аргумент offset должен иметь тип long (не ошибитесь!).
- Если поставить указатель за конец файла (это допустимо!), то операция записи
write() сначала заполнит байтом '\0' все пространство от конца файла до позиции
указателя; операция read() при попытке чтения из-за конца файла вернет "прочи-
тано 0 байт". Попытка поставить указатель перед началом файла вызовет ошибку.
- Вызов lseek() неприменим к pipe и FIFO-файлам, поэтому попытка сдвинуться на 0
байт выдаст ошибку:
/* это стандартная функция */
int isapipe(int fd){
extern errno;
return (lseek(fd, 0L, SEEK_CUR) < 0 && errno == ESPIPE);
}
выдает "истину", если fd - дескриптор "трубы"(pipe).
А. Богатырев, 1992-95 - 146 - Си в UNIX
4.8. Каков будет эффект следующей программы?
int fd = creat("aFile", 0644); /* creat создает файл
открытый на запись, с доступом rw-r--r-- */
write(fd, "begin", 5 );
lseek(fd, 1024L * 1000, 0);
write(fd, "end", 3 );
close(fd);
Напомним, что при записи в файл, его длина автоматически увеличивается, когда мы
записываем информацию за прежним концом файла. Это вызывает отведение места на диске
для хранения новых данных (порциями, называемыми блоками - размером от 1/2 до 8 Кб в
разных версиях). Таким образом, размер файла ограничен только наличием свободных
блоков на диске.
В нашем примере получится файл длиной 1024003 байта. Будет ли он занимать на
диске 1001 блок (по 1 Кб)?
В системе UNIX - нет! Вот кое-что про механику выделения блоков:
- Блоки располагаются на диске не обязательно подряд - у каждого файла есть специ-
альным образом организованная таблица адресов его блоков.
- Последний блок файла может быть занят не целиком (если длина файла не кратна
размеру блока), тем не менее число блоков у файла всегда целое (кроме семейства
BSD, где блок может делиться на фрагменты, принадлежащие разным файлам). Опера-
ционная система в каждый момент времени знает длину файла с точностью до одного
байта и не позволяет нам "заглядывать" в остаток блока, пока при своем "росте"
файл не займет эти байты.
- Блок на диске физически выделяется лишь после операции записи в этот блок.
В нашем примере: при создании файла его размер 0, и ему выделено 0 блоков. При
первой записи файлу будет выделен один блок (логический блок номер 0 для файла) и в
его начало запишется "begin". Длина файла станет равна 5 (остаток блока - 1019 байт
- не используется и файлу логически не принадлежит!). Затем lseek поставит указатель
записи далеко за конец файла и write запишет в 1000-ый блок слово "end". 1000-ый блок
будет выделен на диске. В этот момент у файла "возникнут" и все промежуточные блоки
1..999. Однако они будут только "числиться за файлом", но на диске отведены не будут
(в таблице блоков файла это обозначается адресом 0)! При чтении из них будут
читаться байты '\0'. Это так называемая "дырка" в файле. Файл имеет размер 1024003
байта, но на диске занимает всего 2 блока (на самом деле чуть больше, т.к. часть
таблицы блоков файла тоже находится в специальных блоках файла). Блок из "дырки"
станет реальным, если в него что-нибудь записать.
Будьте готовы к тому, что "размер файла" (который, кстати, можно узнать систем-
ным вызовом stat) - это в UNIX не то же самое, что "место, занимаемое файлом на
диске".
4.9. Найдите ошибки:
FILE *fp;
...
fp = open( "файл", "r" ); /* открыть */
close(fp); /* закрыть */
Ответ: используется системный вызов open() вместо функции fopen(); а также close
вместо fclose, а их форматы (и результат) различаются! Следует четко различать две
существующие в Си модели обмена с файлами: через системные вызовы: open, creat,
close, read, write, lseek; и через библиотеку буферизованного обмена stdio: fopen,
fclose, fread, fwrite, fseek, getchar, putchar, printf, и.т.д. В первой из них обра-
щение к файлу происходит по целому fd - дескриптору файла, а во втором - по указателю
FILE *fp - указателю на файл. Это параллельные механизмы (по своим возможностям),
хотя второй является просто надстройкой над первым. Тем не менее, лучше их не смеши-
вать.
А. Богатырев, 1992-95 - 147 - Си в UNIX
4.10. Доступ к диску (чтение/запись) гораздо (на несколько порядков) медленнее, чем
доступ к данным в оперативной памяти. Кроме того, если мы читаем или записываем файл
при помощи системных вызовов маленькими порциями (по 1-10 символов)
char c;
while( read(0, &c, 1)) ... ; /* 0 - стандартный ввод */
то мы проигрываем еще в одном: каждый системный вызов - это обращение к ядру операци-
онной системы. При каждом таком обращении происходит довольно большая дополнительная
работа (смотри главу "Взаимодействие с UNIX"). При этом накладные расходы на такое
посимвольное чтение файла могут значительно превысить полезную работу.
Еще одной проблемой является то, что системные вызовы работают с файлом как с
неструктурированным массивом байт; тогда как человеку часто удобнее представлять, что
файл поделен на строки, содержащие читабельный текст, состоящий лишь из обычных
печатных символов (текстовый файл).
Для решения этих двух проблем была построена специальная библиотека функций,
названная stdio - "стандартная библиотека ввода/вывода" (standard input/output
library). Она является частью библиотеки /lib/libc.a и представляет собой надстройку
над системными вызовами (т.к. в конце концов все ее функции время от времени обраща-
ются к системе, но гораздо реже, чем если использовать сисвызовы непосредственно).
Небезызвестная директива #include <stdio.h> включает в нашу программу файл с объявле-
нием форматов данных и констант, используемых этой библиотекой.
Библиотеку stdio можно назвать библиотекой буферизованного обмена, а также биб-
лиотекой работы с текстовыми файлами (т.е. имеющими разделение на строки), поскольку
для оптимизации обменов с диском (для уменьшения числа обращений к нему и тем самым
сокращения числа системных вызовов) эта библиотека вводит буферизацию, а также пре-
доставляет несколько функций для работы со строчно-организованными файлами.
Связь с файлом в этой модели обмена осуществляется уже не при помощи целого
числа - дескриптора файла (file descriptor), а при помощи адреса "связной" структуры
FILE. Указатель на такую структуру условно называют указателем на файл (file
pointer)|-. Структура FILE содержит в себе:
- дескриптор fd файла для обращения к системным вызовам;
- указатель на буфер, размещенный в памяти программы;
- указатель на текущее место в буфере, откуда надо выдать или куда записать оче-
редной символ; этот указатель продвигается при каждом вызове getc или putc;
- счетчик оставшихся в буфере символов (при чтении) или свободного места (при
записи);
- режимы открытия файла (чтение/запись/чтение+запись) и текущее состояние файла.
Одно из состояний - при чтении файла был достигнут его конец|=;
- способ буферизации;
Предусмотрено несколько стандартных структур FILE, указатели на которые называются
stdin, stdout и stderr и связаны с дескрипторами 0, 1, 2 соответственно (стандартный
ввод, стандартный вывод, стандартный вывод ошибок). Напомним, что эти каналы открыты
неявно (автоматически) и, если не перенаправлены, связаны с вводом с клавиатуры и
выводом на терминал.
Буфер в оперативной памяти нашей программы создается (функцией malloc) при отк-
рытии файла при помощи функции fopen(). После открытия файла все операции обмена с
файлом происходят не по 1 байту, а большими порциями размером с буфер - обычно по 512
байт (константа BUFSIZ).
При чтении символа
int c; FILE *fp = ... ;
c = getc(fp);
____________________
|- Это не та "связующая" структура file в ядре, про которую шла речь выше, а ЕЩЕ
одна - в памяти самой программы.
|= Проверить это состояние позволяет макрос feof(fp); он истинен, если конец был
достигнут, ложен - если еще нет.
А. Богатырев, 1992-95 - 148 - Си в UNIX
в буфер считывается read-ом из файла порция информации, и getc выдает ее первый байт.
При последующих вызовах getc выдаются следующие байты из буфера, а обращений к диску
уже не происходит! Лишь когда буфер будет исчерпан - произойдет очередное чтение с
диска. Таким образом, информация читается из файла с опережением, заранее наполняя
буфер; а по требованию выдается уже из буфера. Если мы читаем 1024 байта из файла
при помощи getc(), то мы 1024 раза вызываем эту функцию, но всего 2 раза системный
вызов read - для чтения двух порций информации из файла, каждая - по 512 байт.
При записи
char c; FILE *fp = ... ;
putc(c, fp);
выводимые символы накапливаются в буфере. Только когда в нем окажется большая порция
информации, она за одно обращение write записывается на диск. Буфер записи "выталки-
вается" в файл в таких случаях:
- буфер заполнен (содержит BUFSIZ символов).
- при закрытии файла (fclose или exit |-|-).
- при вызове функции fflush (см. ниже).
- в специальном режиме - после помещения в буфер символа '\n' (см. ниже).
- в некоторых версиях - перед любой операцией чтения из канала stdin (например,
при вызове gets), при условии, что stdout буферизован построчно (режим _IOLBF,
смотри ниже), что по-умолчанию так и есть.
Приведем упрощенную схему, поясняющую взаимоотношения основных функций и макросов из
stdio (кто кого вызывает). Далее s означает строку, c - символ, fp - указатель на
структуру FILE |=|=. Функции, работающие со строками, в цикле вызывают посимвольные
операции. Обратите внимание, что в конце концов все функции обращаются к системным
вызовам read и write, осуществляющим ввод/вывод низкого уровня.
Системные вызовы далее обозначены жирно, макросы - курсивом.
Открыть файл, создать буфер:
#include <stdio.h>
FILE *fp = fopen(char *name, char *rwmode);
| вызывает
V
int fd = open (char *name, int irwmode);
Если открываем на запись и файл не существует (fd < 0),
то создать файл вызовом:
fd = creat(char *name, int accessmode);
fd будет открыт для записи в файл.
По умолчанию fopen() использует для creat коды доступа accessmode равные 0666 (rw-
rw-rw-).
____________________
|-|- При выполнении вызова завершения программы exit(); все открытые файлы автомати-
чески закрываются.
|=|= Обозначения fd для дескрипторов и fp для указателей на файл прижились и их сле-
дует придерживаться. Если переменная должна иметь более мнемоничное имя - следует
писать так: fp_output, fd_input (а не просто fin, fout).
А. Богатырев, 1992-95 - 149 - Си в UNIX
Соответствие аргументов fopen и open:
rwmode irwmode
-------------------------
"r" O_RDONLY
"w" O_WRONLY|O_CREAT |O_TRUNC
"r+" O_RDWR
"w+" O_RDWR |O_CREAT |O_TRUNC
"a" O_WRONLY|O_CREAT |O_APPEND
"a+" O_RDWR |O_CREAT |O_APPEND
Для r, r+ файл уже должен существовать, в остальных случаях файл создается, если его
не было.
Если fopen() не смог открыть (или создать) файл, он возвращает значение NULL:
if((fp = fopen(name, rwmode)) == NULL){ ...неудача... }
Итак, схема:
printf(fmt,...)--->--,----fprintf(fp,fmt,...)->--*
fp=stdout |
fputs(s,fp)--------->--|
puts(s)----------->-------putchar(c)-----,---->--|
fp=stdout |
fwrite(array,size,count,fp)->--|
|
Ядро ОС putc(c,fp)
------------------* |
|файловая---<--write(fd,s,len)------------<----БУФЕР
|система---->---read(fd,s,len)-* _flsbuf(c,fp)
| | ! |
|системные буфера ! |
| | ! V ungetc(c,fp)
|драйвер устр-ва ! | |
|(диск, терминал) ! | _filbuf(fp) |
| | ! *--------->-----БУФЕР<-*
|устройство ! |
------------------* c=getc(fp)
|
rdcount=fread(array,size,count,fp)--<--|
gets(s)-------<---------c=getchar()------,----<--|
fp=stdout |
|
fgets(sbuf,buflen,fp)-<--|
scanf(fmt,.../*ук-ли*/)--<-,--fscanf(fp,fmt,...)-*
fp=stdin
Закрыть файл, освободить память выделенную под буфер:
fclose(fp) ---> close(fd);
И чуть в стороне - функция позиционирования:
fseek(fp,long_off,whence) ---> lseek(fd,long_off,whence);
Функции _flsbuf и _filbuf - внутренние для stdio, они как раз сбрасывают буфер в файл
либо читают новый буфер из файла.
По указателю fp можно узнать дескриптор файла:
int fd = fileno(fp);
Это макроопределение просто выдает поле из структуры FILE. Обратно, если мы открыли
А. Богатырев, 1992-95 - 150 - Си в UNIX
файл open-ом, мы можем ввести буферизацию этого канала:
int fd = open(name, O_RDONLY); /* или creat() */
...
FILE *fp = fdopen(fd, "r");
(здесь надо вновь указать КАК мы открываем файл, что должно соответствовать режиму
открытия open-ом). Теперь можно работать с файлом через fp, а не fd.
В приложении имеется текст, содержащий упрощенную реализацию главных функций из
библиотеки stdio.
4.11. Функция ungetc(c,fp) "возвращает" прочитанный байт в файл. На самом деле байт
возвращается в буфер, поэтому эта операция неприменима к небуферизованным каналам.
Возврат соответствует сдвигу указателя чтения из буфера (который увеличивается при
getc()) на 1 позицию назад. Вернуть можно только один символ подряд (т.е. перед сле-
дующим ungetc-ом должен быть хоть один getc), поскольку в противном случае можно
сдвинуть указатель за начало буфера и, записывая туда символ c, разрушить память
программы.
while((c = getchar()) != '+' );
/* Прочли '+' */ ungetc(c ,stdin);
/* А можно заменить этот символ на другой! */
c = getchar(); /* снова прочтет '+' */
4.12. Очень часто делают ошибку в функции fputc, путая порядок ее аргументов. Так
ничего не стоит написать:
FILE *fp = ......;
fputc( fp, '\n' );
Запомните навсегда!
int fputc( int c, FILE *fp );
указатель файла идет вторым! Существует также макроопределение
putc( c, fp );
Оно ведет себя как и функция fputc, но не может быть передано в качестве аргумента в
функцию:
#include <stdio.h>
putNtimes( fp, c, n, f )
FILE *fp; int c; int n; int (*f)();
{ while( n > 0 ){ (*f)( c, fp ); n--; }}
возможен вызов
putNtimes( fp, 'a', 3, fputc );
но недопустимо
putNtimes( fp, 'a', 3, putc );
Тем не менее всегда, где возможно, следует пользоваться макросом - он работает быст-
рее. Аналогично, есть функция fgetc(fp) и макрос getc(fp).
Отметим еще, что putchar и getchar это тоже всего лишь макросы
#define putchar(c) putc((c), stdout)
#define getchar() getc(stdin)
А. Богатырев, 1992-95 - 151 - Си в UNIX
4.13. Известная вам функция printf также является частью библиотеки stdio. Она вхо-
дит в семейство функций:
FILE *fp; char bf[256];
fprintf(fp, fmt, ... );
printf( fmt, ... );
sprintf(bf, fmt, ... );
Первая из функций форматирует свои аргументы в соответствии с форматом, заданным
строкой fmt (она содержит форматы в виде %-ов) и записывает строку-результат посим-
вольно (вызывая putc) в файл fp. Вторая - это всего-навсего fprintf с каналом fp
равным stdout. Третяя выдает сформатированную строку не в файл, а записывает ее в
массив bf. В конце строки sprintf добавляет нулевой байт '\0' - признак конца.
Для чтения данных по формату используются функции семейства
fscanf(fp, fmt, /* адреса арг-тов */...);
scanf( fmt, ... );
sscanf(bf, fmt, ... );
Функции fprintf и fscanf являются наиболее мощным средством работы с текстовыми фай-
лами (содержащими изображение данных в виде печатных символов).
4.14. Текстовые файлы (имеющие строчную организацию) хранятся на диске как линейные
массивы байт. Для разделения строк в них используется символ '\n'. Так, например,
текст
стр1
стрк2
кнц
хранится как массив
с т р 1 \n с т р к 2 \n к н ц длина=14 байт
!
указатель чтения/записи (read/write pointer RWptr)
(расстояние в байтах от начала файла)
При выводе на экран дисплея символ \n преобразуется драйвером терминалов в последова-
тельность \r\n, которая возвращает курсор в начало строки ('\r') и опускает курсор на
строку вниз ('\n'), то есть курсор переходит в начало следующей строки.
В MS DOS строки в файле на диске разделяются двумя символами \r\n и при выводе
на экран никаких преобразований не делается|-. Зато библиотечные функции языка Си
преобразуют эту последовательность при чтении из файла в \n, а при записи в файл
превращают \n в \r\n, поскольку в Си считается, что строки разделяются только \n. Для
работы с файлом без таких преобразований, его надо открывать как "бинарный":
FILE *fp = fopen( имя, "rb" ); /* b - binary */
int fd = open ( имя, O_RDONLY | O_BINARY );
____________________
|- Управляющие символы имеют следующие значения:
'\n' - '\012' (10) line feed
'\r' - '\015' (13) carriage return
'\t' - '\011' (9) tab
'\b' - '\010' (8) backspace
'\f' - '\014' (12) form feed
'\a' - '\007' (7) audio bell (alert)
'\0' - 0. null byte
А. Богатырев, 1992-95 - 152 - Си в UNIX
Все нетекстовые файлы в MS DOS надо открывать именно так, иначе могут произойти раз-
ные неприятности. Например, если мы программой копируем нетекстовый файл в текстовом
режиме, то одиночный символ \n будет считан в программу как \n, но записан в новый
файл как пара \r\n. Поэтому новый файл будет отличаться от оригинала (что для файлов
с данными и программ совершенно недопустимо!).
Задание: напишите программу подсчета строк и символов в файле. Указание: надо
подсчитать число символов '\n' в файле и учесть, что последняя строка файла может не
иметь этого символа на конце. Поэтому если последний символ файла (тот, который вы
прочитаете самым последним) не есть '\n', то добавьте к счетчику строк 1.
4.15. Напишите программу подсчета количества вхождений каждого из символов алфавита
в файл и печатающую результат в виде таблицы в 4 колонки. (Указание: заведите массив
из 256 счетчиков. Для больших файлов счетчики должны быть типа long).
4.16. Почему вводимый при помощи функций getchar() и getc(fp) символ должен описы-
ваться типом int а не char?
Ответ: функция getchar() сообщает о конце файла тем, что возвращает значение EOF
(end of file), равное целому числу (-1). Это НЕ символ кодировки ASCII, поскольку
getchar() может прочесть из файла любой символ кодировки (кодировка содержит символы
с кодами 0...255), а специальный признак не должен совпадать ни с одним из хранимых в
файле символов. Поэтому для его хранения требуется больше одного байта (нужен хотя
бы еще 1 бит). Проверка на конец файла в программе обычно выглядит так:
...
while((ch = getchar()) != EOF ){
putchar(ch);
...
}
- Пусть ch имеет тип unsigned char. Тогда ch всегда лежит в интервале 0...255 и
НИКОГДА не будет равно (-1). Даже если getchar() вернет такое значение, оно
будет приведено к типу unsigned char обрубанием и станет равным 255. При срав-
нении с целым (-1) оно расширится в int добавлением нулей слева и станет равно
255. Таким образом, наша программа никогда не завершится, т.к. вместо признака
конца файла она будет читать символ с кодом 255 (255 != -1).
- Пусть ch имеет тип signed char. Тогда перед сравнением с целым числом EOF байт
ch будет приведен к типу signed int при помощи расширения знакового бита (7-
ого). Если getchar вернет значение (-1), то оно будет сначала в присваивании
значения байту ch обрублено до типа char: 255; но в сравнении с EOF значение 255
будет приведено к типу int и получится (-1). Таким образом, истинный конец файла
будет обнаружен. Но теперь, если из файла будет прочитан настоящий символ с
кодом 255, он будет приведен в сравнении к целому значению (-1) и будет также
воспринят как конец файла. Таким образом, если в нашем файле окажется символ с
кодом 255, то программа воспримет его как фальшивый конец файла и оставит весь
остаток файла необработанным (а в нетекстовых файлах такие символы - не ред-
кость).
- Пусть ch имеет тип int или unsigned int (больше 8 бит). Тогда все корректно.
Отметим, что в UNIX признак конца файла в самом файле физически НЕ ХРАНИТСЯ. Система
в любой момент времени знает длину файла с точностью до одного байта; признак EOF
вырабатывается стандартными функциями тогда, когда обнаруживается, что указатель чте-
ния достиг конца файла (то есть позиция чтения стала равной длине файла - последний
байт уже прочитан).
В MS DOS же в текстовых файлах признак конца (EOF) хранится явно и обозначается
символом CTRL/Z. Поэтому, если программным путем записать куда-нибудь в середину
файла символ CTRL/Z, то некоторые программы перестанут "видеть" остаток файла после
этого символа!
Наконец отметим, что разные функции при достижении конца файла выдают разные
значения: scanf, fscanf, fgetc, getc, getchar выдают EOF, read - выдает 0, а gets,
fgets - NULL.
А. Богатырев, 1992-95 - 153 - Си в UNIX
4.17. Напишите программу, которая запрашивает ваше имя и приветствует вас. Для ввода
имени используйте стандартные библиотечные функции
gets(s);
fgets(s,slen,fp);
В чем разница?
Ответ: функция gets() читает строку (завершающуюся '\n') из канала fp==stdin.
Она не контролирует длину буфера, в которую считывается строка, поэтому если строка
окажется слишком длинной - ваша программа повредит свою память (и аварийно завер-
шится). Единственный возможный совет - делайте буфер достаточно большим (очень туман-
ное понятие!), чтобы вместить максимально возможную (длинную) строку.
Функция fgets() контролирует длину строки: если строка на входе окажется длин-
нее, чем slen символов, то остаток строки не будет прочитан в буфер s, а будет остав-
лен "на потом". Следующий вызов fgets прочитает этот сохраненный остаток. Кроме того
fgets, в отличие от gets, не обрубает символ '\n' на конце строки, что доставляет нам
дополнительные хлопоты по его уничтожению, поскольку в Си "нормальные" строки завер-
шаются просто '\0', а не "\n\0".
char buffer[512]; FILE *fp = ... ; int len;
...
while(fgets(buffer, sizeof buffer, fp)){
if((len = strlen(buffer)) && buffer[len-1] == '\n')
/* @ */ buffer[--len] = '\0';
printf("%s\n", buffer);
}
Здесь len - длина строки. Если бы мы выбросили оператор, помеченный '@', то printf
печатал бы текст через строку, поскольку выдавал бы код '\n' дважды - из строки
buffer и из формата "%s\n".
Если в файле больше нет строк (файл дочитан до конца), то функции gets и fgets
возвращают значение NULL. Обратите внимание, что NULL, а не EOF. Пока файл не дочи-
тан, эти функции возвращают свой первый аргумент - адрес буфера, в который была запи-
сана очередная строка файла.
Фрагмент для обрубания символа перевода строки может выглядеть еще так:
#include <stdio.h>
#include <string.h>
char buffer[512]; FILE *fp = ... ;
...
while(fgets(buffer, sizeof buffer, fp) != NULL){
char *sptr;
if(sptr = strchr(buffer, '\n'))
*sptr = '\0';
printf("%s\n", buffer);
}
4.18. В чем отличие puts(s); и fputs(s,fp); ?
Ответ: puts выдает строку s в канал stdout. При этом puts выдает сначала строку
s, а затем - дополнительно - символ перевода строки '\n'. Функция же fputs символ
перевода строки не добавляет. Упрощенно:
fputs(s, fp) char *s; FILE *fp;
{ while(*s) putc(*s++, fp); }
puts(s) char *s;
{ fputs(s, stdout); putchar('\n'); }
А. Богатырев, 1992-95 - 154 - Си в UNIX
4.19. Найдите ошибки в программе:
#include <stdio.h>
main() {
int fp;
int i;
char str[20];
fp = fopen("файл");
fgets(stdin, str, sizeof str);
for( i = 0; i < 40; i++ );
fputs(fp, "Текст, выводимый в файл:%s",str );
fclose("файл");
}
Мораль: надо быть внимательнее к формату вызова и смыслу библиотечных функций.
4.20. Напишите программу, которая распечатывает самую длинную строку из файла ввода
и ее длину.
4.21. Напишите программу, которая выдает n-ую строку файла. Номер строки и имя
файла задаются как аргументы main().
4.22. Напишите программу
slice -сКакой +сколько файл
которая выдает сколько строк файла файл, начиная со строки номер сКакой (нумерация
строк с единицы).
#include <stdio.h>
#include <ctype.h>
long line, count, nline, ncount; /* нули */
char buf[512];
void main(int argc, char **argv){
char c; FILE *fp;
argc--; argv++;
/* Разбор ключей */
while((c = **argv) == '-' || c == '+'){
long atol(), val; char *s = &(*argv)[1];
if( isdigit(*s)){
val = atol(s);
if(c == '-') nline = val;
else ncount = val;
} else fprintf(stderr,"Неизвестный ключ %s\n", s-1);
argc--; ++argv;
}
if( !*argv ) fp = stdin;
else if((fp = fopen(*argv, "r")) == NULL){
fprintf(stderr, "Не могу читать %s\n", *argv);
exit(1);
}
for(line=1, count=0; fgets(buf, sizeof buf, fp); line++){
if(line >= nline){
fputs(buf, stdout); count++;
}
if(ncount && count == ncount)
break;
}
А. Богатырев, 1992-95 - 155 - Си в UNIX
fclose(fp); /* это не обязательно писать явно */
}
/* End_Of_File */
4.23. Составьте программу, которая распечатывает последние n строк файла ввода.
4.24. Напишите программу, которая делит входной файл на файлы по n строк в каждом.
4.25. Напишите программу, которая читает 2 файла и печатает их вперемежку: одна
строка из первого файла, другая - из второго. Придумайте, как поступить, если файлы
содержат разное число строк.
4.26. Напишите программу сравнения двух файлов, которая будет печатать первую из
различающихся строк и позицию символа, в котором они различаются.
4.27. Напишите программу для интерактивной работы с файлом. Сначала у вас запраши-
вается имя файла, а затем вам выдается меню:
1. Записать текст в файл.
2. Дописать текст к концу файла.
3. Просмотреть файл.
4. Удалить файл.
5. Закончить работу.
Текст вводится в файл построчно с клавиатуры. Конец ввода - EOF (т.е. CTRL/D), либо
одиночный символ '.' в начале строки. Выдавайте число введенных строк.
Просмотр файла должен вестись постранично: после выдачи очередной порции строк
выдавайте подсказку
--more-- _
(курсор остается в той же строке и обозначен подчерком) и ожидайте нажатия клавиши.
Ответ 'q' завершает просмотр. Если файл, который вы хотите просмотреть, не сущест-
вует - выдавайте сообщение об ошибке.
После выполнения действия программа вновь запрашивает имя файла. Если вы отве-
тите вводом пустой строки (сразу нажмете <ENTER>, то должно использоваться имя файла,
введенное на предыдущем шаге. Имя файла, предлагаемое по умолчанию, принято писать в
запросе в [] скобках.
Введите имя файла [oldfile.txt]: _
Когда вы научитесь работать с экраном дисплея (см. главу "Экранные библиотеки"),
перепишите меню и выдачу сообщений с использованием позиционирования курсора в задан-
ное место экрана и с выделением текста инверсией. Для выбора имени файла предложите
меню: отсортированный список имен всех файлов текущего каталога (по поводу получения
списка файлов см. главу про взаимодействие с UNIX). Просто для распечатки текущего
каталога на экране можно также использовать вызов
system("ls -x");
а для считывания каталога в программу|-
FILE *fp = popen("ls *.c", "r");
... fgets(...,fp); ... // в цикле, пока не EOF
pclose(fp);
(в этом примере читаются только имена .c файлов).
4.28. Напишите программу удаления n-ой строки из файла; вставки строки после m-ой.
К сожалению, это возможно только путем переписывания всего файла в другое место (без
ненужной строки) и последующего его переименования.
А. Богатырев, 1992-95 - 156 - Си в UNIX
4.29. Составьте программу перекодировки текста, набитого в кодировке КОИ-8, в аль-
тернативную кодировку и наоборот. Для этого следует составить таблицу перекодировки
из 256 символов: c_new=TABLE[c_old]; Для решения обратной задачи используйте стан-
дартную функцию strchr(). Программа читает один файл и создает новый.
4.30. Напишите программу, делящую большой файл на куски заданного размера (не в
строках, а в килобайтах). Эта программа может применяться для записи слишком боль-
шого файла на дискеты (файл режется на части и записывается на несколько дискет).
#include <fcntl.h>
#include <stdio.h>
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define KB 1024 /* килобайт */
#define PORTION (20L* KB) /* < 32768 */
long ONEFILESIZE = (300L* KB);
extern char *strrchr(char *, char);
extern long atol (char *);
extern errno; /* системный код ошибки */
char buf[PORTION]; /* буфер для копирования */
void main (int ac, char *av[]) {
char name[128], *s, *prog = av[0];
int cnt=0, done=0, fdin, fdout;
/* M_UNIX автоматически определяется
* компилятором в UNIX */
#ifndef M_UNIX /* т.е. MS DOS */
extern int _fmode; _fmode = O_BINARY;
/* Задает режим открытия и создания ВСЕХ файлов */
#endif
if(av[1] && *av[1] == '-'){ /* размер одного куска */
ONEFILESIZE = atol(av[1]+1) * KB; av++; ac--;
}
if (ac < 2){
fprintf(stderr, "Usage: %s [-size] file\n", prog);
exit(1);
}
if ((fdin = open (av[1], O_RDONLY)) < 0) {
fprintf (stderr, "Cannot read %s\n", av[1]); exit (2);
}
if ((s = strrchr (av[1], '.'))!= NULL) *s = '\0';
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 <fcntl.h>
#include <stdio.h>
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 <stdio.h>
...
freopen( "имя_файла", "r", stdin );
способ 2 (системные вызовы)
#include <fcntl.h>
int fd;
...
fd = open( "имя_файла", O_RDONLY );
dup2 ( fd, 0 ); /* 0 - стандартный ввод */
close( fd ); /* fd больше не нужен - закрыть
его, чтоб не занимал место в таблице */
А. Богатырев, 1992-95 - 158 - Си в UNIX
способ 3 (системные вызовы)
#include <fcntl.h>
int fd;
...
fd = open( "имя_файла", O_RDONLY )