Чтение и запись данных
Чтение данных из файла выполняют функции read() и fread() (см. пример 5.7).
#include <unistd.h> ssize_t read (int fd, void *buf, size_t nbyte); #include <stdio.h> size_t fread (void *restrict buf, size_t size, size_t nitems, FILE *restrict stream);
Листинг 5.7. Описание функций read() и fread(). (html, txt)
Функция read() пытается прочитать nbyte байт из файла, ассоциированного с дескриптором fd, и поместить их в буфер buf.
Для файлов, допускающих позиционирование, read() выполняет чтение, начиная со значения индикатора текущей позиции, ассоциированного с дескриптором fd. После завершения операции этот индикатор увеличивается на количество прочитанных байт. Для устройств, не поддерживающих позиционирования (таких, например, как терминал), значение упомянутого индикатора не определено, а чтение выполняется с текущей позиции устройства.
При успешном завершении read() возвращает количество байт, реально прочитанных и помещенных в буфер; это значение может оказаться меньше значения аргумента nbyte, если до конца файла оставалось меньше, чем nbyte байт. Например, если текущая позиция совпадала с концом файла, результат будет равен 0. В случае ошибки возвращается -1.
Функция буферизованного ввода/вывода fread() во многом аналогична read(), но число читаемых байт задается как произведение размера одного элемента (аргумент size) на число элементов (аргумент nitems), а результатом служит количество успешно прочитанных элементов. В стандарте оговаривается, что элементы читаются побайтно.
Число элементов, успешно прочитанных функцией fread(), может быть меньше затребованного, только если достигнут конец файла или произошла ошибка чтения. В таком случае fread() устанавливает для потока индикатор ошибки или конца файла, проверить которые позволяют функции feof() и ferror(), соответственно (см. пример 5.8), возвращая при установленном индикаторе ненулевой результат.
#include <stdio.h> int feof (FILE *stream); #include <stdio.h> int ferror (FILE *stream);
Листинг 5.8. Описание функций feof() и ferror(). (html, txt)
Отметим, что использование функции бинарного ввода fread() ограничивает мобильность приложений, так как результат зависит от размера элементов и порядка байт, поддерживаемого процессором.
Обратим также внимание на некоторые нюансы синхронного и асинхронного ввода с помощью функции read(). При попытке чтения из пустого канала, не открытого кем-либо на запись, результат равен 0 (как признак конца файла). Если пустой канал открыт кем-либо на запись, при установленном флаге O_NONBLOCK возвращается -1 (как признак ошибки EAGAIN); при отсутствии флага O_NONBLOCK процесс (поток управления) блокируется до появления данных в канале. Аналогичным образом устроен ввод из файлов других типов, поддерживающих чтение в асинхронном режиме.
Содержимое символьных ссылок приходится читать особым образом (хотя бы потому, что обычно функция open() раскрывает их, т. е. открывает указуемый файл). Для этого служит функция readlink() (см. пример 5.9). Она помещает содержимое ссылки с именем link_name в буфер buf длины buf_size (если буфер мал, остаток содержимого отбрасывается). Результат равен числу помещенных в буфер байт или -1 в случае неудачи.
#include <unistd.h> ssize_t readlink (const char *restrict link_name, char *restrict buf, size_t buf_size);
Листинг 5.9. Описание функции readlink(). (html, txt)
Следующая программа (см. пример 5.10) переправляет недлинные сообщения с управляющего терминала процесса (ему соответствует специальный файл /dev/tty) на стандартный вывод до тех пор, пока не будет введен символ конца файла.
Листинг 5.10. Пример чтения из файла. (html, txt)
В качестве примера мобильного использования функции fread(), а также функций feof() и ferror(), рассмотрим программу, подсчитывающую число символов, слов и строк в файле – аргументе командной строки (см. пример 5.11).
Листинг 5.11. Программа, подсчитывающая число строк, слов и символов в файле. (html, txt)
Читателю предлагается убрать из цикла проверку feof (fp) и оценить, как изменится обработка интерактивного стандартного ввода.
Для иллюстрации использования функции readlink() напишем программу, выдающую на стандартный вывод содержимое символьных ссылок, имена которых заданы в командной строке (см. пример 5.12).
Листинг 5.12. Пример программы, читающей содержимое символьных ссылок. (html, txt)
Запись данных в файл выполняют функции write() и fwrite() (см. пример 5.13).
#include <unistd.h> ssize_t write (int fildes, const void *buf, size_t nbyte); #include <stdio.h> size_t fwrite (const void *restrict buf, size_t size, size_t nitems, FILE *restrict stream);
Листинг 5.13. Описание функций write() и fwrite(). (html, txt)
Вероятно, лишь несколько моментов, связанных с функциями write() и fwrite(), нуждаются в пояснениях. При записи размер файла может увеличиться. Если файл открыт на добавление, запись производится в его конец.
Листинг 5.10. Пример чтения из файла.
В качестве примера мобильного использования функции fread(), а также функций feof() и ferror(), рассмотрим программу, подсчитывающую число символов, слов и строк в файле – аргументе командной строки (см. пример 5.11).
/* * * * * * * * * * * * * * * * * * * * * */ /* Подсчет символов, слов и строк в файле */ /* * * * * * * * * * * * * * * * * * * * * */ #include <stdio.h> int main (int argc, char *argv[]) { long nwords = 0; /* Счетчик слов */ long nlines = 0; /* Счетчик строк */ long nchars = 0; /* Счетчик символов */ FILE *fp = stdin; /* Если файл не задан, */ /* читается стандартный ввод */ unsigned char buf [BUFSIZ]; /* Буфер для */ /* чтения файла */ unsigned char *p1; /* Указатель на */ /* обрабатываемую часть буфера */ size_t nbytes = 0; /* Количество прочитанных,*/ /* но не обработанных байт */ register int c; /* Обрабатываемый символ */ int inword = 0; /* Признак - находимся ли мы */ /* внутри слова */ if (argc > 2) { fprintf (stderr, "Использование: %s [файл]\n", argv [0]); return (2); } if (argc > 1 && (fp = fopen (argv [1], "r")) == NULL) { perror ("OPEN"); fprintf (stderr, "%s: Не могу открыть файл %s\n", argv [0], argv[1]); return (2); } p1 = buf; for (;;) { /* Посимвольная обработка файла */ if (nbytes == 0) { /* Нужно прочитать новую порцию */ if (feof (fp)) { /* При предыдущем чтении дошли до конца файла */ break; } nbytes = fread (p1 = buf, (size_t) 1, sizeof (buf), fp); if (ferror (fp)) { perror ("READ"); fprintf (stderr, "%s: Ошибка чтения из файла %s:", argv [0], argc == 1 ? "стандартного ввода" : argv [1]); break; } if (nbytes == 0) { /* В файле не оставалось непрочитанных символов */ break; } nchars += nbytes; } c = *p1++; /* Обработка одного символа */ nbytes--; if (c > ' ') { if (!inword) { nwords++; inword = 1; } continue; } if (c == '\n') { nlines++; } else if (c != ' ' && c != '\t') { continue; } inword = 0; } if (argc > 1) { fclose (fp); } printf ("Файл %s: строк: %ld, слов: %ld, символов: %ld\n", argc == 1 ? "стандартного ввода" : argv [1], nlines, nwords, nchars); return (0); }
Листинг 5.11. Программа, подсчитывающая число строк, слов и символов в файле.
Читателю предлагается убрать из цикла проверку feof (fp) и оценить, как изменится обработка интерактивного стандартного ввода.
Для иллюстрации использования функции readlink() напишем программу, выдающую на стандартный вывод содержимое символьных ссылок, имена которых заданы в командной строке (см. пример 5.12).
#include <unistd.h> #include <stdio.h> /* Программа выдает на стандартный вывод */ /* содержимое символьных ссылок - */ /* аргументов командной строки */ int main (int argc, char *argv[]) { char buf [BUFSIZ]; ssize_t link_len; int err = 0; int i; for (i = 1; i < argc; i++) { if ((link_len = readlink (argv [i], buf, sizeof (buf) - 1)) < 0) { perror ("READLINK"); fprintf (stderr, "%s: Не удалось прочитать содержимое символьной ссылки %s\n", argv [0], argv [i]); err = -1; continue; } buf [link_len] = '\0'; printf ("Содержимое символьной ссылки %s -> %s\n", argv [i], buf); } return (err); }
Листинг 5.12. Пример программы, читающей содержимое символьных ссылок.
Запись данных в файл выполняют функции write() и fwrite() (см. пример 5.13).
#include <unistd.h> ssize_t write (int fildes, const void *buf, size_t nbyte); #include <stdio.h> size_t fwrite (const void *restrict buf, size_t size, size_t nitems, FILE *restrict stream);
Листинг 5.13. Описание функций write() и fwrite().
Вероятно, лишь несколько моментов, связанных с функциями write() и fwrite(), нуждаются в пояснениях. При записи размер файла может увеличиться. Если файл открыт на добавление, запись производится в его конец.
При записи в канал, если флаг O_NONBLOCK не установлен, процесс (поток управления) может быть отложен, но после нормального завершения функция write() вернет nbyte. При установленном флаге O_NONBLOCK поведение зависит от значения nbyte и наличия свободного места в канале. Если nbyte не превосходит константы PIPE_BUF, запишется все или ничего (в последнем случае результат будет равен -1).
При попытке записать порцию данных большего размера запишется сколько можно или ничего.
Приведем несколько примеров. Следующая программа (см. пример 5.14) выводит приветствие на управляющий терминал.
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #define C_TERM "/dev/tty" char msg [] = "HELLO !!!\n"; int main (void) { int fd; /* Открытие на запись специального файла, ассоциированного с управляющим терминалом */ if ((fd = open (C_TERM, O_WRONLY)) < 0) { perror ("OPEN"); return (-1); } /* Вывод на терминал */ if (write (fd, msg, sizeof (msg)) != (ssize_t) sizeof (msg)) { perror ("WRITE"); return (1); } return (close (fd)); }
Листинг 5.14. Пример программы, использующей функцию write().
Программа prnmyself (см. пример 5.15) выводит свой собственный исходный текст. При этом применяется следующий прием: данные фиксированными порциями читаются из файла и выводятся на терминал; процесс повторяется до тех пор, пока число реально прочитанных байт совпадает с указанным (до обнаружения конца файла).
#include <unistd.h> #include <stdio.h> #include <fcntl.h>
#define SOURCE_FILE "prnmyself.c" #define C_TERM "/dev/tty"
int main (void) { unsigned char buf [BUFSIZ]; int fdr, fdw; /* Дескрипторы для чтения и записи */ ssize_t nb; if (((fdr = open (SOURCE_FILE, O_RDONLY)) < 0) || ((fdw = open (C_TERM, O_WRONLY)) < 0)) { perror ("OPEN " SOURCE_FILE " or " C_TERM); return (1); } do { if ((nb = read (fdr, buf, BUFSIZ)) < 0) { perror ("READ"); break; } if (write (fdw, buf, nb) != nb) { perror ("WRITE"); break; } } while (nb == BUFSIZ); (void) close (fdw); (void) close (fdr); return (0); }
Листинг 5.15. Пример программы, использующей функции read() и write().
Для буферизованного ввода/вывода байт служат функции fgetc() и fputc(), строки рекомендуется вводить, вызывая функцию fgets(), а выводить с помощью функций fputs() и puts() (см.
пример 5.16).
#include <stdio.h> int fgetc (FILE *stream); #include <stdio.h> int fputc (int c, FILE *stream); #include <stdio.h> char *fgets (char * restrict s, int n, FILE *restrict stream); #include <stdio.h> int fputs (const char *restrict s, FILE *restrict stream); #include <stdio.h> int puts (const char *s);
Листинг 5.16. Описание функций fgetc(), fputc(), fgets(), fputs(), puts().
Описание аналогичных функций для широких символов приведено в пример 5.17.
#include <stdio.h> #include <wchar.h> wint_t fgetwc (FILE *stream); #include <stdio.h> #include <wchar.h> wint_t fputwc (wchar_t wc, FILE *stream); #include <stdio.h> #include <wchar.h> wchar_t *fgetws (wchar_t *restrict ws, int n, FILE *restrict stream); #include <stdio.h> #include <wchar.h> int fputws (const wchar_t *restrict ws, FILE *restrict stream);
Листинг 5.17. Описание функций fgetwc(), fputwc(), fgetws(), fputws().
Функция fgetc() пытается прочитать из заданного потока один байт, преобразовать его из типа unsigned char в int и вернуть в качестве результата. В случае ошибки или при достижении конца файла возвращается константа EOF.
Функция fgets() читает из заданного потока и помещает в буфер с адресом s (n - 1) байт или строку, включая символ перевода строки, или байты, оставшиеся до конца файла, если длина строки или число байт до конца меньше (n - 1). После прочитанных добавляется нулевой байт.
При нормальном завершении fgets() возвращает s. В случае ошибки или при достижении конца файла возвращается пустой указатель.
Функция fputc() помещает в поток значение c, предварительно преобразовав его к типу unsigned char. Результатом служит c или EOF.
Функция fputs() выводит в поток цепочку символов (без завершающего нулевого байта) и возвращает неотрицательное целое число или EOF. Функция puts() делает то же для потока stdout, завершая вывод переводом строки.
Функции для работы с потоками широкой ориентации устроены аналогично с точностью до замены int на wint_t, char на wchar_t и EOF на WEOF.
Программа, показанная в пример 5.18, иллюстрирует использование функций fgets() и fputs(). Читателю предлагается сравнить ее с пример 5.10.
#include <stdio.h> #include <limits.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод */ int main (void) { char line [LINE_MAX]; fputs ("Вводите строки\n", stdout); while (fgets (line, sizeof (line), stdin) != NULL) { if ((fputs ("Вы ввели: ", stdout) == EOF) || (fputs (line, stdout) == EOF)) { break; } } return (ferror (stdin) || ferror (stdout)); }
Листинг 5.18. Пример использования функций fgets() и fputs().
Использование функций fgetc() и fputc() иллюстрируется программой, написанной С.В. Самборским (см. пример 5.19). Она выполняет раскодировку файлов формата base64, применяемого, например, в электронной почте.
#include <stdio.h> #include <assert.h> #include <string.h> #include <endian.h>
FILE *input=NULL, *output=NULL; const char str [] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; unsigned int Prm [256]; /* Таблица перекодировки */ const int WHITE = 100, ERR = 101, END = 102; static void usage (char argv0 []) { fprintf (stderr,"Программа раскодирует файлы формата base64\n"); fprintf (stderr,"Использование:\n%s входной_файл выходной_файл\n", argv0); fprintf (stderr,"Файл должен начинаться с первого символа в кодировке base64.\n"); } int main (int argc, char *argv []) { int n; union { unsigned long l; char c[4]; } a; { int i; for (i = 0; i < 256; i++) Prm [i] = ERR; Prm [' '] = WHITE; Prm ['\t'] = WHITE; Prm ['\n'] = WHITE; Prm ['\r'] = WHITE; Prm ['='] = END; for (i = 0; i < 64; i++) Prm [(int) str [i]] = i; } if (argc != 3) { usage (argv [0]); return (1); } assert (NULL != (input = fopen (argv [1], "r"))); assert (NULL != (output = fopen (argv [2], "w"))); for (a.l = 0, n = 0; ; ) { /* Цикл обработки входного файла */ int c, b, shift; assert (EOF != (c = fgetc (input))); b = Prm [c]; if (WHITE == b) continue; if (END == b) break; if (ERR == b) { fprintf (stderr,"Символ номер %d: %d не входит в кодировку base64\n", n, c); return (1); } n++; assert (b < 64); shift = 6 * (4 - n % 4); if (shift != 24) b = b << shift; a.l += b; if (0 == n % 4) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); fputc (a.c[0], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output); #else #error "Unknown endian" #endif a.l = 0; } } { /* Обработка остатка входного файла */ int tl = (((n - 1) % 4) * 6 + 7) / 8; if (tl == 3) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); fputc (a.c[0], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output); #else #error "Unknown endian" #endif } if (tl == 2) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); #else #error "Unknown endian" #endif } if (tl == 1) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); #else #error "Unknown endian" #endif } } fclose (input); fclose (output); return (0); }
Листинг 5.19. Пример использования функций fgetc() и fputc().
Приведенный пример показывает, что написание мобильных программ даже для сравнительно простых задач требует заметных усилий. В данном случае пришлось воспользоваться нестандартной возможностью – включаемым файлом <endian.h>, содержащим препроцессорные константы, которые описывают порядок байт в машинном слове.
Отметим также стиль обработки ошибочных ситуаций, основанный на применении макроса assert. Для авторов программ такой стиль, безусловно, упрощает жизнь, исходный текст получается более компактным, однако для пользователей сообщение вида
decode64: decode64.c:39: main: Assertion `((void *)0) != (input = fopen (argv [1], "r"))' failed.
может оказаться менее информативным, чем выдача функции perror().