Главная » Программирование для UNIX » Доступ к файлам: vis, версия 3

0

Две первые версии программы vis  читали только стандартный ввод и записывали выходные данные только в стандартный вывод, при  этом и ввод  и вывод  были  унаследованы от  оболочки. Пришло время сделать  следующий шаг – изменить vis так, чтобы она могла обратиться к файлу по его имени, тогда

$ vis file1 file2 …

просматривала бы  указанные файлы, а  не  устройство стандартного ввода. Если  же  имена  файлов не  указаны, то vis  должна поступать, как раньше, то есть читать свой стандартный ввод.

Вопрос  в том,  как прочитать файлы, то есть как связать имена файлов с операторами ввода, которые и осуществляют считывание данных.

Есть  несколько простых правил. Прежде чем файл можно будет  прочитать или  записать в  него  данные, его  надо  открыть при  помощи стандартной библиотечной функции  fopen.  Функция fopen  получает имя  файла (например, temp или /etc/passwd), выполняет некоторые служебные действия и согласования с ядром, после чего возвращает внут реннее имя, которое и будет использоваться при дальнейших операциях с файлом.

Это внутреннее имя  на  самом  деле  является  указателем (указатель файла) на  структуру,  содержащую информацию о файле, такую как местоположение буфера, позиция текущего символа в буфере, читается файл или записывается и т. д. Одно из описаний, полученных благодаря  включению <stdio.h>,  относится к структуре под  названием FILE. Объявление указателя файла выглядит следующим образом:

FILE *fp;

Такая запись означает, что  fp представляет  собой  указатель на  FILE. Функция fopen возвращает указатель на FILE; таково объявление типа  для fopen в <stdio.h>.

Вызов  fopen в программе выглядит так:

char  *name, *mode;

fp  = fopen(name,  mode);

Первый аргумент функции – это имя файла, символьная строка. Второй аргумент (тоже символьная строка) указывает, как именно предполагается использовать файл; разрешены такие режимы, как чтение ("r"), запись ("w") и добавление ("a").

Если  файл, открываемый для  записи или добавления, не существует, то он создается (если возможно). Если  для записи открывается существующий файл, то его старое содержимое стирается. Попытка прочитать несуществующий  файл приводит к ошибке –  так  же, как и  попытка

прочитать или записать файл, для которого у пользователя нет прав доступа. В случае  ошибки функция fopen возвращает  недействительное значение указателя NULL (обычно определяется как (char *)0 в <stdio.h>).

Файл открыт, теперь надо как-то прочитать или записать его. Сущест вует несколько способов сделать это, простейшим является использование getc и putc. Функция getc извлекает из файла следующий символ:

c = getc(fp)

в переменную c помещается следующий символ из файла, на который указывает fp; когда достигнут конец файла, возвращается EOF. Функция putc устроена аналогично:

putc(c, fp)

помещает символ c в файл fp и возвращает c. В случае ошибки getc  и

putc возвращают EOF.

Когда  запускается программа, три  файла уже  открыты и на  них  существуют указатели. Это файлы стандартного ввода, стандартного вывода и стандартного вывода ошибок; соответствующие указатели называются stdin,  stdout и  stderr. Эти  указатели файлов  объявлены в

<stdio.h>;  они  могут   использоваться  везде,  где  могут   существовать объекты типа  FILE *. Это константы, а не переменные, поэтому присваивать им значения нельзя.

Функция    getchar()    эквивалентна     getc(stdin),     а     putchar(c) –

putc(c,stdout).

В действительности все четыре «функции», упомянутые выше, определены в <stdio.h>  как  макросы, они  выполняются быстрее,  потому  что не вызываются для  каждого символа отдельно (как функция). Некоторые другие определения из <stdio.h> приведены в табл. 6.3.

Таблица 6.3. Некоторые описания <stdio.h>

Определение       Смысл

stdin                       стандартный ввод

stdout                     стандартный вывод

stderr                     стандартный вывод ошибок

EOF                           конец файла; обычно  –1

NULL                         недействительный указатель; обычно  0

FILE                        используется для объявления указателей файлов

BUFSIZ                         обычный размер буфера ввода-вывода (часто  512 или 1024)

getc(fp)               возвращает один символ из потока fp getchar()             getc(stdin)

putc(c,fp)              помещает символ c в поток fp

Таблица 6.3 (продолжение)

Определение

Смысл

putchar(c) feof(fp) ferror(fp) fileno(fp)

putc(c, stdout)

не ноль, если в потоке fp  достигнут конец файла не ноль, если в потоке fp  обнаружена ошибка файловый дескриптор потока fp, см. главу 7

Сделаем еще  несколько предварительных  замечаний и  приступим к третьей версии vis. Если  в командной строке есть  аргументы, то они обрабатываются по порядку. Если аргументы не заданы, обрабатывается стандартный ввод.

/*  vis:  сделать  видимыми  непечатаемые символы  (версия  3)  */

#include <stdio.h>

#include <ctype.h>

int strip = 0;            /*  1 =>  удалить специальные   символы */ char  *progname;         /*program  name  for error message */

main(argc,  argv) int  argc;

char  *argv[];

{

int i; FILE  *fp;

progname = argv[0];

while  (argc >  1  &&  argv[1][0]  == ‘–’) { switch  (argv[1][1])  {

case  ‘s':   /*  –s:  удалить странные символы  */

strip = 1; break;

default:

fprintf(stderr, "%s:  unknown  arg  %s\n",  argv[0], argv[1]); exit(1);

} argc––; argv++;

}

if (argc  == 1) vis(stdin);

else

for  (i = 1;  i < argc; i++)

if ((fp=fopen(argv[i],  "r")) == NULL)  {

fprintf(stderr, "%s: can’t open  %s\n",  progname,  argv[i]); exit(1);

} else { vis(fp); fclose(fp);

} exit(0);

}

В данной программе подразумевается выполнение соглашения о том, что первыми обрабатываются необязательные аргументы.

После  того как обработаны все необязательные параметры, argc и argv корректируются так, что оставшаяся часть  программы не зависит от наличия таких аргументов. Несмотря на то что vis  распознает только одиночный параметр, программа написана как цикл для того,  чтобы показать, как  можно организовать обработку  аргументов. В главе 1 упоминалось, что UNIX-программы обрабатывают необязательные аргументы. Еще  одной  причиной для  этого, помимо склонности к анар хии, служит то, что разбор любой комбинации параметров выполняется  исключительно  просто. В  некоторых системах есть  функция ge– topt(3), с ее помощью пытались как-то упорядочить ситуацию; стоит  изучить ее, прежде чем браться за написание своей собственной.

Функция vis выводит один файл:

vis(fp) /*  сделать  символы  видимыми в  FILE *fp  */ FILE  *fp;

{

int c;

while  ((c  =  getc(fp))  !=  EOF) if  (isascii(c)  &&

(isprint(c) ||  c==’\n’ || c==’\t’  ||  c==’  ‘)) putchar(c);

else  if  (!strip) printf("\\%03o",  c);

}

Функция fprintf практически идентична printf, отличие лишь в том,  что в аргументе – файловом указателе – указывается файл для записи.

Функция fclose  обрывает связь между  указателем файла и внешним именем,  установленную fopen,  и указатель  освобождается  для  нового файла. Поскольку количество файлов, которые система может открывать  одновременно (около  20), ограничено, лучше освобождать файлы, которые больше не понадобятся. Обычно  вывод, осуществляемый ка кой-то из стандартных библиотечных функций типа  printf, putc и т. д., буферизуется таким образом, что запись выполняется большими порциями, что увеличивает производительность. (Исключением является вывод на терминал, обычно осуществляемый по мере поступления или, по крайней мере, по вводу  символа новой  строки (построчно).) Вызов  fclose для файла вывода закрывает любой буферизованный вывод. Когда программа вызывает exit или  возвращается из main, происходит автоматическое обращение к fclose для каждого открытого файла.

Так  же как stdin и stdout, программе присваивается stderr. Выходные данные, записанные на  stderr, появятся на  терминале пользователя,

даже если  стандартный вывод  будет  перенаправлен.  Программа vis  выводит диагностические сообщения на stderr, а не на stdout, поэтому если  по какой-то причине  невозможно получить доступ  к одному  из файлов, сообщение об этом  появится на  терминале, а  не  исчезнет в конвейере или файле вывода. (Стандартный вывод  ошибок был изобретен  в некоторой степени именно  потому, что  ошибки начали исче зать  в конвейерах.)

Условие выхода из  программы выбрано,  можно  сказать,  случайным образом. Это происходит, если  vis  не может открыть файл со входными данными; такой выбор  выглядит разумным для  программы, чаще всего использующейся интерактивно и получающей один  файл ввода. Однако если хотите, можете выбрать и другую конструкцию.

Упражнение 6.5.  Напишите программу  printable,  которая выводила бы  имя   каждого  файла-аргумента,  содержащего только  печатаемые символы; если же в файле встречается какой-то непечатаемый символ, его имя  не выводится. Такая программа полезна в следующих случаях:

$ pr  `printable *`  | lpr

Добавьте параметр –v, чтобы  изменить смысл проверки на обратный, как в grep.  Как  программе следует поступать, если  имена  файлов не указаны? Какой код должна возвращать printable? ~

Источник: Керниган Б., Пайк Р., UNIX. Программное окружение. – Пер. с англ. – СПб: Символ-Плюс, 2003. – 416 с., ил.

По теме:

  • Комментарии