Опубликован: 15.06.2004 | Уровень: специалист | Доступ: платный
Лекция 5:

Файловый ввод/вывод

Чтение и запись данных

Чтение данных из файла выполняют функции 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().

Функция 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().

Отметим, что использование функции бинарного ввода   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().

Следующая программа (см. листинг 5.10) переправляет недлинные сообщения с управляющего терминала процесса (ему соответствует специальный файл /dev/tty ) на стандартный вывод до тех пор, пока не будет введен символ конца файла.

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define C_TERM "/dev/tty"

int main (void) {
	char buf [BUFSIZ];
	int fd;
	ssize_t line_len;
	/* Открытие на чтение специального файла, */
	/* ассоциированного с управляющим терминалом */
	if ((fd = open (C_TERM, O_RDONLY)) < 0) {
		perror ("OPEN");
		return (-1);
	}
	/* Ввод с терминала */
	printf ("Вводите строки\n");
	while ((line_len = read (fd, buf, BUFSIZ - 1))
		> 0) {
		buf [line_len] = '\0';
		printf ("Вы ввели: %s", buf);
	}
	if (line_len == -1) {
		perror ("READ");
		close (fd);
		return (-1);
	}
	return (close (fd));
}
Листинг 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(), нуждаются в пояснениях. При записи размер файла может увеличиться. Если файл открыт на добавление, запись производится в его конец.

Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?