Программирование в стандарте POSIX

         

История создания и текущий статус стандарта POSIX


Обеспечение мобильности (переносимости, портабельности) программного обеспечения (ПО) - задача исключительной важности и сложности; в наше время это обстоятельство едва ли нуждается в пространных обоснованиях. Один из общепринятых способов повышения мобильности ПО - стандартизация окружения приложений: предоставляемых программных интерфейсов, утилит и т.п. На уровне системных сервисов подобное окружение описывает стандарт POSIX (Portable Operating System Interface - мобильный интерфейс операционной системы); название предложено известным специалистом, основателем Фонда свободного программного обеспечения Ричардом Столмэном.

Мы будем рассматривать наиболее современную из доступных версий стандарта POSIX, в редакции 2003 г., которую можно назвать "стандартом втройне", а именно: стандартом IEEE Std 1003.1, Техническим стандартом Open Group и (см. [6]), что для нас важнее всего, международным стандартом ISO/IEC 9945 (см. [1], [2], [3], [4]).

История создания этой версии такова. В начале 1998 г. представители трех организаций - Комитета по стандартам мобильных приложений Института инженеров по электротехнике и электронике, Open Group и рабочей группы 15 подкомитета 22 совместного технического комитета 1 (JTC1/SC22/WG15) Международной организации по стандартизации - начали консультации по вопросу слияния и развития курируемых ими стандартов интерфейсов к системным сервисам: IEEE Std 1003.1, IEEE Std 1003.2, Базовых спецификаций от Open Group, ISO/IEC 9945-1, ISO/IEC 9945-2. В сентябре того же года в городе Остин, штат Техас, в офисе корпорации IBM состоялось организационное заседание группы, сформированной для достижения поставленной цели (см. http://www.opengroup.org/austin).

Основополагающим документом для пересмотренного стандарта, первый проект которого был представлен в июле 1999 года, стали Базовые спецификации от Open Group, поскольку они включали положения стандартов IEEE и ISO/IEC. В 2001 году, по завершении подготовительной работы, стандарт содержал следующие четыре части:


основные определения (термины, концепции и интерфейсы, общие для всех частей);описание прикладного программного C-интерфейса к системным сервисам;описание интерфейса к системным сервисам на уровне командного языка и служебных программ;детальное разъяснение положений стандарта, обоснование принятых решений.

Далее в ISO, IEEE и Open Group с большей или меньшей скоростью (в 2001-2002 гг.) прошло формальное утверждение нового стандарта POSIX. Тем временем накапливались относительно мелкие исправления, учтенные в редакции 2003-го года.

С развитием стандарта расширялась и трактовка термина "POSIX". Первоначально он относился к документу IEEE Std 1003.1-1988, описывавшему прикладной программный интерфейс ОС класса Unix. После стандартизации интерфейса на уровне командного языка и служебных программ более правильно понимать под словом "POSIX" стандарт в целом, обозначая перечисленные выше части 2 и 3 через POSIX.1 и POSIX.2 в соответствии с нумерацией документов IEEE и ISO/IEC.


Мобильность POSIX-совместимых приложений


Мобильность приложений, соответствующих стандарту POSIX, принципиально достижима благодаря двум основным факторам. Во-первых - это наличие огромного числа стандартизованных системных сервисов, а во-вторых - возможность динамического выяснения характеристик целевой платформы и подстройки под них приложения. (Естественно, мы имеем в виду мобильность в рамках, регламентируемых стандартом.)

Приложения, соответствующие стандарту POSIX, могут быть одно- и многопроцессными, с возможностью динамической адаптации конфигурации к свойствам целевой платформы. Стандартизованы средства порождения и завершения процессов, смены их программ, опроса и/или изменения разнообразных характеристик. Процессы можно приостанавливать и активизировать в заданное время. Механизм сигналов позволяет извещать о событиях и завершать процессы внешним образом. Для их группирования предусмотрены средства управления заданиями. Приложения снабжены регуляторами для управления планированием и приоритетами процессов. Широк спектр средств межпроцессного взаимодействия (очереди сообщений, разделяемая память, семафоры) и управления памятью. Наконец, в пределах процесса можно организовать несколько потоков управления.

Необходимая степень детерминизма выполнения достигается благодаря средствам поддержки реального времени (к ним относятся управление дисциплиной выделение процессоров, сигналы реального времени, удержание страниц в оперативной памяти, таймеры высокого разрешения и т.д.).

Функции для работы с файлами удовлетворяют потребности приложений в чтении и записи долговременных данных, защите таких данных от несанкционированного доступа. Механизм блокировки фрагментов файлов позволяет обеспечить атомарность транзакций. Асинхронный ввод/вывод дает возможность совмещать операции обмена, оптимизируя тем самым приложения. С помощью множества служебных программ можно относительно легко организовать сложную обработку данных.

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

Стандартизованный командный язык shell - адекватное средство для написания небольших мобильных процедур и их быстрой интерактивной отладки. Выделим механизм конвейеров, позволяющий объединять команды в цепочки с фильтрацией промежуточных результатов. Служебные программы образуют развитую среду выполнения для shell-процедур. За счет фонового режима можно организовать одновременное выполнение нескольких программ и взаимодействие с ними посредством обычного терминала без многооконных возможностей (впрочем, окна, несомненно, не помешали бы).

POSIX стандартизует интерфейс командной строки. В принципе, он достаточен, в меру удобен и, что важно, создает минимум проблем с точки зрения мобильности. Вероятно, в будущих версиях стандарта будет регламентирован графический интерфейс, но, безусловно, это чревато дополнительными сложностями для разработчиков мобильных приложений.

Языково-культурная среда - одно из важнейших понятий стандарта POSIX с точки зрения мобильности. Приложения способны определять нужную им среду и адаптироваться к потребностям пользователей.

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

Стандартом POSIX предусмотрены базовые средства поддержки разработки (в первую очередь - для языка C), что, конечно, не снижает потребности в специализированных, развитых системах, когда речь идет о работе с действительно большими программными проектами.

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

Проблема мобильности приложений чрезвычайно сложна, и было бы преувеличением утверждать, что стандарт POSIX-2001 решает ее полностью.Во-первых, за его рамками остаются такие важнейшие вопросы, как графика, многооконный интерфейс и целый ряд других. Во-вторых, в регламентируемых областях присутствуют "белые пятна" неспецифицированного поведения реализаций. Тем не менее, подчеркнем это еще раз, следование стандарту POSIX - обязательный элемент современной дисциплины разработки прикладных систем.


Основные идеи стандарта POSIX


Стандарт POSIX описывает множество базовых, системных сервисов, необходимых для функционирования прикладных программ. Доступ к ним предоставляется посредством интерфейса, специфицированного для языка C, командного языка и общеупотребительных служебных программ.

У каждого интерфейса есть две стороны: вызывающая и вызываемая. Стандарт POSIX ориентирован в первую очередь на вызывающую. Его цель - сделать приложения мобильными на уровне исходного языка. Это значит, в частности, что при переносе C-программ на другую операционную платформу потребуется перекомпиляция. О мобильности выполнимых программ и/или объектных файлов речь не идет.

Стандарт POSIX отнюдь не ограничен рамками Unix-среды. Существуют операционные системы (ОС) "независимого происхождения" (например, системы реального времени), предоставляющие необходимые сервисы и тем самым поддерживающие выполнение POSIX-совместимых приложений. Можно утверждать, что следование стандарту POSIX облегчает перенос приложений практически на любую сколько-нибудь распространенную операционную платформу. Дополнительные усилия по повышению мобильности, прилагаемые на этапе разработки, безусловно, окупятся.

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

POSIX нейтрален по отношению к системной архитектуре и разрядности процессора. Это очень важный аспект мобильности приложений.

Ориентация на международный стандарт языка C определила не только стиль описания функций, но и, до некоторой степени, направление развития спецификаций POSIX в плане синхронизации обоих стандартов. Как известно в утвержденной в 1999 г. редакции спецификаций языка C (см. [5]) узаконен комплексный тип данных, что вызвало соответствующее пополнение POSIX-функций.


В стандарте POSIX проведено разделение на обязательные и дополнительные функции, причем обязательное ядро сделано по возможности компактным. Разумеется, особое внимание уделяется способам реализации стандартизуемых функций как в "классической" Unix-среде, так и на других операционных платформах, в сетевых и распределенных конфигурациях.

Разработчики новой версии стандарта POSIX очень бережно отнеслись и к его предыстории, и к предыстории Unix-систем, и, главное, к приложениям, удовлетворявшим более ранним версиям стандарта. Существующие интерфейсы старались сохранять; в процессе развития соблюдался принцип обратной совместимости; новые интерфейсы добавлялись так, чтобы они не конфликтовали со старыми. Полностью избежать внесения изменений в приложения не удалось по вполне понятным причинам: потребовалось устранить противоречия между разными исходными спецификациями, а также отказаться от поддержки "традиционного" варианта языка C и перейти на его международный стандарт.


Основные понятия операционных систем, соответствующих стандарту POSIX


Мы рассмотрим следующие основные понятия операционных систем, соответствующих стандарту POSIX:

пользователь;файл;процесс;терминал;хост;узел сети;время;языково-культурная среда.

Это первичные понятия. Их нельзя строго определить, но можно пояснить с помощью других понятий и отношений. Для каждого из выделенных понятий будут описаны присущие им атрибуты и применимые к ним операции.

В тексте стандарта POSIX содержатся следующие пояснения основных понятий вместе со ссылками на атрибуты и операции.

У пользователя есть имя и числовой идентификатор.Файл - объект, допускающий чтение и/или запись и имеющий такие атрибуты, как права доступа и тип. К числу последних относятся обычный файл, символьный и блочный специальные файлы, канал, символьная ссылка, сокет и каталог. Реализация может поддерживать и другие типы файлов.Процесс - адресное пространство вместе с выполняемыми в нем потоками управления, а также системными ресурсами, которые этим потокам требуются.Терминал (или терминальное устройство) - символьный специальный файл, подчиняющийся спецификациям общего терминального интерфейса.Сеть - совокупность взаимосвязанных хостов.Языково-культурная среда - часть пользовательского окружения, зависящая от языковых и культурных соглашений.

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

Для написания программ, оперирующих с сущностями POSIX-совместимых систем, применяются командный интерпретатор (язык shell) и/или компилируемый язык C. В первом случае приложение может пользоваться служебными программами (утилитами), во втором - функциями. Функциональный интерфейс операционных систем естественно считать первичным, поскольку большинство служебных программ предназначены, по сути, для вызова той или иной функции. По этой причине далее мы будем рассматривать преимущественно уровень функций.

Основными операциями, применимыми к объектам ОС, являются чтение, запись и выполнение.


Механизм прав доступа позволяет избирательно разрешать и запрещать осуществление подобных операций. Ранее в стандарте фигурировало понятие суперпользователя, не подверженного контролю прав доступа. В POSIX-2001 выбрана более гибкая формулировка - "имеющий соответствующие привилегии", что отражает прогресс в реализации ОС с расщеплением суперпользовательских возможностей.

В POSIX-совместимых ОС определены объекты, которые можно назвать вспомогательными; они помогают организовать взаимодействие между основными сущностями. Особенно широк спектр средств межпроцессного взаимодействия.

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

Как правило, с процессом ассоциированы по крайней мере три файла - стандартный ввод, стандартный вывод, стандартный протокол. Обычно стандартный ввод назначается на клавиатуру терминала, а стандартный вывод и стандартный протокол - на экран. Со стандартного ввода читаются команды и (иногда) исходные данные для них. На стандартный вывод поступают результаты выполнения команд. В стандартный протокол помещаются диагностические сообщения.

К операционным системам могут предъявляться качественные требования, например, требование поддержки реального времени: способность обеспечить необходимый сервис в течение заданного отрезка времени.


Основные понятия стандарта POSIX


Стандарт POSIX в редакции 2003-го года - весьма обширный, многогранный документ, где подробно рассматриваются следующие категории системных компонентов:

средства разработки;сетевые средства;средства реального времени;потоки управления;математические интерфейсы;пакетные сервисы;заголовочные файлы;унаследованные интерфейсы.

Именно такой (на верхнем уровне, далеко не полный) репертуар должна предоставлять операционная система для работы приложения.

Важнейшим является понятие соответствия стандарту POSIX. Мы уже отмечали, что всякий интерфейс располагает двумя сторонами: вызывающей и вызываемой. Две стороны есть и у POSIX-соответствия: соответствие реализации (операционной системы) и приложения.

Реализация (операционная система), соответствующая стандарту POSIX, должна поддерживать все обязательные служебные программы, функции, заголовочные файлы с обеспечением специфицированного в стандарте поведения. Константа _POSIX_VERSION имеет значение 200112L.

ОС может предоставлять возможности, помеченные в стандарте в качестве дополнительных, а также содержать нестандартные функции. Если утверждается, что поддерживается некоторое расширение, это должно производиться непротиворечивым образом, для всех необходимых частей и так, как описано в стандарте.

В заголовочном файле <unistd.h> следует определить константы, соответствующие поддерживаемым необязательным возможностям (например, константа _POSIX2_C_DEV обслуживает средства разработки на языке C). Анализируя эти константы во время компиляции, приложение выяснит возможности используемой ОС и подстроится под них. Аналогичные действия на этапе выполнения могут быть выполнены с помощью функции sysconf() и/или служебной программы getconf.

Для минимизации размеров ОС и приложений стандартом POSIX предусмотрена весьма мелкая гранулярность необязательных возможностей (всего их сорок). С другой стороны, проведено объединение взаимосвязанных необязательных возможностей в группы, что во многих случаях избавляет от анализа большого числа опций.
Группы эти таковы:

шифрование;средства реального времени;продвинутые средства реального времени;потоки реального времени;продвинутые потоки реального времени;трассировка;ПОТОКИ;унаследованные возможности.

Например, в группу "средства реального времени" (_XOPEN_REALTIME) входят возможности четырнадцати видов, в том числе планирование на основе приоритетов, асинхронный ввод/вывод, семафоры, таймеры и т.п.

Версия ОС Linux, на которой готовился текст данного курса, выдавала следующие значения некоторых конфигурационных констант (см. листинг 1.1).

$ getconf _POSIX_VERSION 199506 $ getconf POSIX2_C_DEV 1 $ getconf _XOPEN_REALTIME 1 $ getconf _POSIX_TRACE undefined

Листинг 1.1. Результат применения утилиты getconf к одной из версий ОС Linux. (html, txt)

Это значит, что поддерживается устаревшая версия стандарта POSIX, среди прочих присутствуют средства разработки и возможности реального времени; средства трассировки отсутствуют.

В документации на ОС должны быть отражены вопросы соответствия стандарту POSIX, описаны поддерживаемые дополнительные и нестандартные возможности.

Для приложений понятие соответствия стандарту POSIX богаче нюансами. Предусмотрено строгое соответствие, главный отличительный признак которого - ограничение круга используемых возможностей рамками стандарта. Рассматривается и соответствие с применением расширений; в этом случае документация на приложение должна содержать описание требуемых нестандартных возможностей. Желательно, чтобы используемые расширения POSIX-возможностей описывались международными и/или национальными стандартами.

(Отметим, что для реализации понятие строгого POSIX-соответствия бессмысленно хотя бы по той причине, что не бывает операционных систем без средств администрирования, а они не описываются данным стандартом.)

Профилем будем называть набор опций, описывающих необязательные возможности. Соответствие профилю означает соответствие стандарту POSIX и поддержку заданных возможностей. Разумным образом выбранные профили позволяют учитывать потребности представительных классов пользователей и/или приложений.



Допускается существование "подпрофилей", описывающих подмножества стандартных возможностей. Реализация, соответствующая подпрофилю, может функционировать на аппаратных платформах с ограниченными ресурсами и/или обслуживать нужды специфических приложений.

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

Еще один близкий термин, "поведение, зависящее от реализации", дополнительно означает, что поведение реализации необходимо документировать.

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

Более ограниченный смысл придан термину "унаследованный": он описывает устаревшие необязательные возможности, которых, разумеется, следует избегать в новых приложениях.


к одной из версий ОС


$ getconf _POSIX_VERSION 199506 $ getconf POSIX2_C_DEV 1 $ getconf _XOPEN_REALTIME 1 $ getconf _POSIX_TRACE undefined
Листинг 1.1. Результат применения утилиты getconf к одной из версий ОС Linux.
Закрыть окно




#if defined(_REENTRANT) || (_POSIX_C_SOURCE - 0 >= 199506L) # define LIBXML_THREAD_ENABLED #endif
Листинг 1.2. Пример использования макроса проверки возможностей _POSIX_C_SOURCE.
Закрыть окно



Среда компиляции POSIX-совместимых приложений


Как правило (хотя это и не всегда осознается), разработка приложений ведется в кросс-режиме, то есть платформа разработки (эквивалентный термин - инструментальная платформа) не совпадает с платформой выполнения (называемой также целевой платформой). На инструментальной платформе создается среда компиляции приложений, так что результат компиляции может быть перенесен для последующего выполнения на целевую платформу.

Важнейшая часть среды компиляции - заголовочные (или включаемые) файлы, содержащие прототипы функций, определения символических констант, макросов, типов данных, структур и т.п. Для каждой описанной в стандарте POSIX функции определено, какие заголовочные файлы должны быть включены использующим ее приложением (обычно требуется один файл).

Выше было указано, что посредством символических констант, определенных в заголовочном файле <unistd.h>, операционная система предоставляет приложению информацию о поддерживаемых возможностях. Стандартом POSIX предусмотрен симметричный механизм, называемый механизмом макросов проверки возможностей, он позволяет приложениям объявлять о своем желании получить доступ к определенным прототипам и именам.

Основным макросом проверки возможностей является _POSIX_C_SOURCE. Среди требований к приложениям, строго соответствующим стандарту POSIX, фигурирует необходимость определения символической константы _POSIX_C_SOURCE со значением 200112L до включения каких-либо заголовочных файлов. Таким образом POSIX-совместимое приложение заявляет, что ему нужны POSIX-имена. Близкую по смыслу роль играет макрос _XOPEN_SOURCE (со значением 600).

Примером использования макроса _POSIX_C_SOURCE во включаемых файлах ОС Linux может служить фрагмент, приведенный на листинге 1.2.

#if defined(_REENTRANT) || (_POSIX_C_SOURCE - 0 >= 199506L) #define LIBXML_THREAD_ENABLED #endif

Листинг 1.2. Пример использования макроса проверки возможностей _POSIX_C_SOURCE. (html, txt)

Стандартом POSIX предусмотрены некоторые меры для решения важной и трудной проблемы (вызванной в первую очередь необъектным характером языка C), заключающейся в отсутствии пересечений по именам между приложением и операционной системой.
Префиксы posix_, POSIX_ и _POSIX_ зарезервированы для нужд стандарта.

С подчеркивания, за которым следует еще одно подчеркивание или заглавная латинская буква, могут начинаться только системные (но не прикладные) имена. Для включаемых файлов описаны префиксы используемых в них имен. Например, для операций управления файлами, фигурирующих в <fcntl.h>, в качестве префиксов задействованы F_, O_, S_. У средств межпроцессного взаимодействия, описанных в файле <sys/ipc.h>, префиксом служит IPC_. К сожалению, заголовочных файлов много, а какая-то общая дисциплина именования отсутствует вследствие исторических причин. Так, для манипулирования характеристиками терминалов в файле <termios.h> определено множество разнообразных имен: EXTB, VDSUSP, DEFECHO, FLUSHO и т.п. Еще имеется четыреста семнадцать имен типа _Exit, abort, abs, acos и т.д., которые могут участвовать в редактировании внешних связей прикладной программы. В результате, прикладной программист может случайно "перебить" системный макрос, внешнюю переменную или функцию, поэтому целесообразно задействовать все диагностические средства среды компиляции и тщательно изучать выдаваемые ими сообщения.


Генерация маршрутных имен файлов


После всех подстановок, прежде чем команда начнет выполняться, в каждом составляющем ее поле осуществляется поиск символов *, ?, и [. Если находится хотя бы один из них, то это поле рассматривается как шаблон имен файлов и заменяется именами файлов, удовлетворяющими данному шаблону. Имена подставляются в алфавитном порядке. Если ни одно имя файла не удовлетворяет шаблону, поле остается неизменным. Символ . в начале имени файла или непосредственно после /, так же как и сам символ /, должны быть заданы в шаблоне явно. Трактовка символов *, ? и [:

* - сопоставляется с произвольной цепочкой символов, в том числе с пустой.? - сопоставляется с произвольным символом.[...] - сопоставляется с любым из перечисленных в скобках символов. Пара символов, разделенных знаком -, обозначает отрезок алфавита, включающий сами указанные символы. Если сразу вслед за [ идет !, шаблону удовлетворяет любой символ, не перечисленный в скобках.

Рассмотрим несколько примеров. Чтобы подсчитать суммарное число строк во всех C-файлах текущего каталога, достаточно выполнить команду

wc -l *.c

В качестве второго примера расширим приведенный выше фрагмент действий при загрузке системы (см. листинг 2.22):

for f in /etc/rc$runlevel.d/S* do if [ -s ${f} ] then /bin/sh ${f} start fi done

Листинг 2.22. Пример сочетания управляющих конструкций с генерацией имен файлов. (html, txt)

В цикле будут в алфавитном порядке запускаться все непустые файлы из каталога, соответствующего заданному уровню выполнения (обычно это /etc/rc3.d, поскольку runlevel имеет значение 3, но в данном случае это не суть важно), имена которых начинаются на S.

Как пример использования шаблона имен файлов приведем команду

rm -f .*.[Bb]?

Она удалит из текущего каталога все файлы, имена которых начинаются с точки, сразу после второй точки стоит одна из букв - B или b, а затем произвольный символ.

Читателю предлагается рассмотреть еще один способ применения шаблона имен файлов и самостоятельно ответить на вопрос, удалит ли команда


rm -f *.[Bb][AaUu]

файл с именем .bu.

Наконец, рассмотрим более сложный пример. Пусть требуется так переименовать фортрановские файлы текущего каталога, чтобы окончание .f заменилось на .for. Служебная программа

basename цепочка_символов [суффикс]

убирает из цепочки_символов любой оканчивающийся на / префикс и суффикс (если он есть) и выдает результат на стандартный вывод. Применим ее для решения сформулированной задачи (см. листинг 2.23).

for f in *.f do mv $f `basename $f .f`.for done

Листинг 2.23. Пример подстановки результатов команды как части слова. (html, txt)

Особенностью приведенного примера является использование подстановки результатов команды как части слова (в данном случае - как части нового имени файла).

Еще одним видом генерации маршрутных имен файлов можно было бы считать обработку символа тильда '~', однако это действие открывает, а не завершает подстановки в командной строке. Префикс слов, от тильды до ближайшего слэша (или конца слова, если слэш отсутствует), заменяется на имя домашнего каталога пользователя, входное имя которого задано префиксом. Если префикс пустой, вместо него подставляется значение переменной окружения HOME. В присваиваниях тильда распознается и обрабатывается не только в начале слова, но и после знаков равенства и двоеточия, что позволяет, в частности, естественным образом обращаться со значением переменной окружения PATH.

Так, при подстановке результатом работы команды echo ~ может быть /home/galat.


Экранирование


Под экранированием имеется в виду защита символов от интерпретации со стороны языка shell. Следующие символы shell трактует по-особому; не будучи экранированными, они завершают предшествующее им слово:

| & ; < > ( ) $ ` \ " ' пробел перевод_строки

Не являясь разделителями слов, особую роль играют для shell и символы

* ? [ ] # ~ = %

Каждый из перечисленных выше символов может быть экранирован, т. е. представлять самого себя, если перед ним стоит \ либо он расположен между одиночными или двойными кавычками ('' или "").

Экранирование с помощью двойных кавычек имеет некоторые особенности. Во-первых, оно не отменяет особой трактовки символов $ и `. Иными словами, внутри двойных кавычек обычным образом выполняется подстановка значений переменных, результатов выполнения команд и результатов вычисления арифметических выражений, только результат подстановки расценивается как одно слово. Во-вторых, особая трактовка символа \ отменяется внутри двойных кавычек лишь частично: символ \ сохраняет экранирующую роль, если после него следуют:

$ ` " \ перевод_строки

Приведем несколько примеров использования двойных кавычек для экранирования. В первом команда

echo "$(echo *)"

выдаст список файлов текущего каталога, а во втором результатом выполнения строки

echo "$(echo "*")"

станет звездочка. Если же аргумент "$(echo *)" предложить усовершенствованной процедуре three_args, то число фактических аргументов окажется равным единице, а список файлов текущего каталога станет значением первого аргумента.

Рассмотрим третий пример. Пусть нужно выдать информацию о всех файлах текущего каталога, которые модифицировались последний раз 3 октября. Конвейер

ls -al | grep "Oct 3"

сделает то, что нужно (служебная программа grep выполняет сопоставление заданного шаблона со строками заданных файлов или стандартного ввода). Кавычки использованы для экранирования двух пробелов в разыскиваемой цепочке символов. Без экранирования пробелы превращаются в разделители аргументов, и команда grep попыталась бы найти текст Oct в файле с цифровым именем 3.

Еще один пример. Запись

$(($i\*5))

синтаксически неверна, поскольку в арифметических выражениях shell рассматривает звездочку как операцию умножения, а не как элемент шаблона имен файлов; соответственно, она не нуждается в экранировании, и символ \ становится лишним, нарушая синтаксис выражения. Таким образом, лишнее экранирование может быть вредным.



Конвейеры и примеры их использования


Конвейер - одна из самых красивых конструкций ОС Unix, вошедшая, разумеется, и в стандарт POSIX. Идея его проста, но на редкость продуктивна. С помощью конвейеров удается комбинировать возможности, предоставляемые разными командами, получая по существу новое качество.

Например, утилита ls не подсчитывает число файлов в каталоге, а лишь выдает информацию о них. С другой стороны, служебная программа wc способна подсчитать число строк в файле, но не имеет отношения к распечатке содержимого каталогов. Если же построить конвейер из двух упомянутых команд, количество файлов в каталоге легко вычисляется. Например, результатом работы конвейера (см. листинг 2.1) на нашей установке ОС Linux будет число 92 (утилита wc, вызванная без аргументов, обрабатывает файл стандартного ввода, который в данном случае является результатом работы команды ls). Значит, в каталоге /bin 91 файл, если считать и элементы, соответствующие текущему и вышележащему каталогам (первая строка выдачи ls содержит суммарное число блоков, занятых файлами каталога).

ls -al /bin | wc -l

Листинг 2.1. Пример конвейера. (html, txt)

Еще один пример. Пусть необходима информация о файлах текущего каталога, модифицированных в октябре. К цели ведет конвейер, показанный в листинге 2.2:

ls -al | grep "Oct "

Листинг 2.2. Еще один пример конвейера. (html, txt)

Служебная программа grep играет здесь роль фильтра, который пропускает для вывода только часть строк, выдаваемых ls.

Можно выстроить и трехступенчатый конвейер, если требуется подсчитать число файлов, модифицированных в октябре (см. листинг 2.3):

ls -al | grep "Oct " | wc -l

Листинг 2.3. Пример трехступенчатого конвейера. (html, txt)

Здесь утилиту grep с еще большим правом можно назвать фильтром.

Приведем еще один пример конвейера, полезного, когда нужна подробная информация о большом каталоге (см. листинг 2.4):

ls -Rl /dev | more

Листинг 2.4. Конвейер для поэкранного просмотра результатов. (html, txt)

Связующее звено между последовательными компонентами конвейера называется каналом. Иными словами, для интерпретации конвейера shell создает временный файл типа "канал", с одного конца в него заносят данные, а с другого - читают. С помощью служебной программы tee можно организовать ответвление канала, т. е. помещать информацию не только на стандартный вывод, но и в указанные файлы:

tee файл ...

Например, если нужно не только подсчитать число файлов из текущего каталога, модифицированных в октябре, но и поместить информацию о них в файл для последующего анализа, следует построить четырехступенчатый конвейер (см. листинг 2.5):

ls -al | grep "Oct" | tee /tmp/tmpinf | wc -l

Листинг 2.5. Четырехступенчатый конвейер. (html, txt)

В результате его выполнения на экране появится сообщение о количестве нужных файлов, а в файле /tmp/tmpinf - информация о них.



Окружение процессов


Окружение - это набор пар (имя, значение), который передается выполняемой программе так же, как и обычный список аргументов. Иными словами, порождаемые процессы наследуют окружение процесса-предка. Компонентами окружения являются, помимо прочих, следующие переменные и их значения:

HOME

Подразумеваемый аргумент утилиты смены текущего каталога cd - домашний каталог пользователя.

IFS

Цепочка символов, где перечислены разделители полей; обычно включает пробел, табуляцию и перевод строки.

PATH

Список имен каталогов для поиска команд. В дальнейшем подобные списки называются списками поиска. Элементы списка разделяются двоеточием. Пустой элемент означает текущий каталог.

PS1

Основное приглашение интерактивного языка shell (по умолчанию "$").

TERM

Тип пользовательского терминала.

TZ

Информация о часовом поясе.

Для отсылки информации об окружении на стандартный вывод следует воспользоваться командой

env

Поясним подробнее смысл некоторых переменных окружения.

Прежде чем выполнить команду, shell ищет файл с соответствующим именем в последовательности каталогов, являющейся значением переменной PATH. Назовем подобную последовательность списком поиска. Если, например, значение $PATH суть

/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin

то нужный файл будет сначала разыскиваться в каталоге /usr/local/bin, затем в /bin и т.д. Как только файл отыщется, поиск прекратится. Это важно, если в разных каталогах есть одноименные выполнимые файлы.

Значение $IFS влияет не только на разбор команд, но и на чтение строк данных с помощью команды read (см. далее). Кроме того, первый символ из значения $IFS вставляется между фактическими аргументами при выполнении подстановки $*.

Переменная TERM хранит тип терминала пользователя. Интерактивные утилиты (редакторы или другие программы с экранным интерфейсом, например talk) с помощью значения $TERM настраиваются на конкретный тип терминала.

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

Для изменения окружения мало присвоить новое значение соответствующей переменной. Дело в том, что по умолчанию переменные считаются локальными по отношению к shell-процедуре, т. е. присваивание изменит локальную переменную, но не затронет одноименную переменную окружения. Таким образом, в окружение новых процессов (порожденных, например, для выполнения последующих команд данной shell-процедуры) войдет переменная со старым значением.

С помощью конструкции

export имя[=слово]

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

Следующая строка иллюстрирует типичное применение команды export:

export PATH=/local/bin:$PATH

Переменная PATH изменилась, и новое значение экспортировано в окружение.

Команда

export -p

выдает на стандартный вывод имена и значения всех экспортированных переменных. Эту выдачу можно использовать для сохранения и последующего восстановления (быть может, с некоторыми модификациями) окружения. Ниже приведен фрагмент возможного результата работы команды export -p (см. листинг 2.17).

export HISTSIZE="1000" export HOME="/home/galat" export LANG="C" export LESSCHARSET="koi8-r" export LOGNAME="galat" export MAIL="/var/spool/mail/galat" export TTY="/dev/ttyS4" export USER="galat"

Листинг 2.17. Возможные результаты выполнения команды export -p. (html, txt)

Мы видим, что выдача устроена так, чтобы вновь быть поданной на вход командного интерпретатора.


Основные понятия языка shell


В дальнейшем изложении слово shell будет употребляться в двух смыслах - как имя языка программирования и как название командного интерпретатора.

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

Свойства shell как интерактивного командного интерпретатора, непосредственно взаимодействующего с пользователем, хранение и использование истории сеанса, возможности редактирования командной строки и т.п., на ваш взгляд, менее актуальны, поскольку современный пользовательский интерфейс строится на иной основе.

Выделим основные понятия языка shell на лексическом уровне.

Под пробелом в дальнейшем понимается не только собственно пробел, но также и символ табуляции.

Слово - это лексема, отличная от знака операции.

Имя - последовательность букв, цифр, символов подчеркивания, начинающаяся с буквы или подчеркивания.

Параметр - имя, цифра или любой из символов *, @, #, ?, -, $, !.

Комментарий - лексема, начинающаяся с символа #, а также вся последующая часть строки.

На синтаксическом уровне различаются несколько видов команд.

Простая команда - последовательность полей с разделителями (обычно пробелами) между ними. Первое поле определяет имя команды, которая будет выполняться; оставшиеся поля, за исключением присваиваемых параметрам и перенаправления ввода/вывода (см. далее), передаются команде в качестве аргументов. Имя команды передается как аргумент 0.

Значение простой команды - ее код завершения.

Команда - это либо простая команда, либо одна из управляющих конструкций (см. далее). Кодом завершения команды является код завершения последней выполненной простой команды.

Конвейер - последовательность команд, разделенных знаком |. Стандартный вывод всех команд, кроме последней, направляется на стандартный ввод следующей команды конвейера.
Каждая команда выполняется как самостоятельный процесс; shell ожидает завершения последней команды, код завершения которой становится кодом завершения конвейера. Формально будем считать простую команду частным случаем конвейера.

Список - последовательность из одного или нескольких разделенных символами ;, &, && или || конвейеров, она может заканчиваться символами ; или &. Из четырех указанных операций ; и & имеют равные приоритеты, меньшие, чем у && и ||. Приоритеты последних также равны между собой. Символ ; означает, что конвейеры будут выполняться последовательно, а & - параллельно (т. е. shell не ожидает завершения конвейера). Операция && (||) означает, что список, следующий за ней, будет выполняться лишь в том случае, если код завершения предыдущего конвейера нулевой (ненулевой). В списке в качестве разделителя конвейеров вместо символа ; можно использовать символ перевода строки.

Командная строка - строка текста на языке shell.

Shell-процедура - файл, содержащий программу на языке shell.

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

Далее при описании синтаксиса конструкций языка shell и способа вызова служебных программ будут использоваться следующие соглашения:

в квадратные скобки заключаются необязательные части конструкций;многоточие означает повторение предыдущей части конструкции произвольное число раз.


Переменные и аргументы shell-процедур


Переменные обозначаются именами. Значения могут присваиваться им привычным способом, то есть посредством команд вида:

имя=значение [имя=значение] ...

Все значения в языке shell трактуются как текстовые. Подчеркнем, что, в соответствии с этими требованиями, конструкция

имя=значение

должна представлять собой одно слово - в ней не может быть пробелов.

Обычно в языках программирования ясно из контекста, где подразумевается имя переменной, а где значение. Так, в левой части оператора присваивания обычно используется имя, в правой - значение. В shell все не так. Переход от имени переменной к значению помечается посредством явной операции $. Если в команде встречается конструкция

$имя

то вместо нее интерпретатор shell подставляет значение переменной с указанным именем. Допускается и запись

${имя}

с тем же смыслом, если нужно отделить имя от последующего текста.

Рассмотрим пример. После выполнения команд (см. листинг 2.6) - утилита echo (эхо) выдает на стандартный вывод свои аргументы - на экране появится результат вывода значений переменных (см. листинг 2.7).

a=value_of_variable b=1+2 echo a = $a echo b = $b

Листинг 2.6. Присваивание и извлечение значение переменных. (html, txt)

a = value_of_variable b = 1+2

Листинг 2.7. Результат вывода значений переменных. (html, txt)

Значения формальных аргументов shell-процедур обозначаются как

$цифра

$0 - это имя интерпретируемой shell-процедуры. Если заданных при вызове команды фактических аргументов меньше, чем 9, "лишние" формальные аргументы получают пустые значения. О том, как добраться до фактических аргументов с номерами большими, чем 9, будет сказано ниже (см. управляющую конструкцию for и команду shift).

В качестве примера рассмотрим shell-процедуру, которая выдает на стандартный вывод свое имя и значения трех первых аргументов (см. листинг 2.8).

echo Имя команды: $0 echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3

Листинг 2.8.
Пример shell-процедуры. (html, txt)

Пусть приведенный текст помещен в файл с именем three_args. Тогда после выполнения команды (см. листинг 2.9) на экране появится ее результат (см. листинг 2.10).

three_args arg1 . - arg4

Листинг 2.9. Пример вызова shell-процедуры с аргументами. (html, txt)

Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -

Листинг 2.10. Результат выполнения shell-процедуры. (html, txt)

Поскольку в тексте shell-процедуры упомянуты только первые 3 аргумента, значения аргументов с большими номерами (даже если они заданы, как в приведенном примере) не влияют на ее работу.

Команда

three_args arg1 --

выдаст другой результат (см. листинг 2.11):

Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: -- Значение третьего аргумента:

Листинг 2.11. Еще один результат выполнения shell-процедуры. (html, txt)

Значение третьего формального аргумента пусто.

Прежде чем начнется выполнение командной строки, т. е. будет вызвана заданная в ней команда, строку обрабатывает shell, выполняющий в ней некоторые подстановки. С одной из них - значения переменной вместо конструкции $имя - мы уже познакомились. Язык shell содержит ряд аналогичных конструкций. Рассмотрим их.

Там, где в командной строке встречается запись вида

${имя:-слово}

вместо нее подставляется значение переменной с указанным именем, если это значение непусто, в противном случае подставляется слово. Например, при наличии в shell-процедуре присваивания

initdir=${1:-/}

переменная initdir получит значение первого аргумента, если оно непусто. Если же процедуру вызвали без аргументов, значением initdir станет символ /. Подобные конструкции - удобный способ использования подразумеваемых значений.

Запись вида

${имя:=слово}

влечет присваивание нового значения (слово) переменной с заданным именем, только если ее старое значение не установлено или пусто (удобный способ присваивания переменным подразумеваемых значений).


Вместо всей конструкции в командную строку подставляется итоговое (непустое старое или присвоенное новое) значение переменной.

Конструкция

${имя:?слово}

предназначена для выдачи в стандартный протокол сообщения об ошибке, если значение переменной с заданным именем не установлено или пусто, после чего неинтерактивный shell завершает работу с ненулевым кодом возврата. Непустое значение подставляется в командную строку, и интерпретация командного файла продолжается обычным образом.

Вместо конструкции

${имя:+слово}

подставляется слово, если значение переменной с заданным именем непусто; в противном случае не подставляется ничего. Такой способ удобен для использования альтернативных значений.

Если в приведенных выше конструкциях опустить двоеточие, будет отменена проверка непустоты значения (останется лишь проверка того, установлено ли значение переменной с заданным именем. В остальном смысл конструкций остается прежним.

Shell содержит базовые средства для обработки цепочек символов. Так, вместо конструкции

${#имя}

подставляется число символов в значении переменной с заданным именем.

Предоставляется четыре конструкции для сопоставления с образцом:

${имя%слово} ${имя%%слово} ${имя#слово} ${имя##слово}

Во всех случаях слово рассматривается как образец (шаблон, см. далее); после сопоставления с ним подставляется значение переменной с заданным именем, из которого удален минимальный (максимальный) сопоставленный суффикс (префикс).

Приведем несколько примеров. Для их понимания достаточно знать, что шаблон * сопоставляется с произвольной цепочкой символов, в том числе с пустой.

После присваивания переменной

HOME=/home/galat

команда

echo ${#HOME}

выдаст 11.

Заменить в имени файла суффикс .f на .for можно с помощью команд, показанных в листинге 2.12:

f=file.f mv $f ${f%.f}.for

Листинг 2.12. Пример сопоставления с образцом. (html, txt)

Выдать первый элемент маршрутного имени файла (см. далее) можно с помощью команд, показанных в листинге 2.13:

f=маршрутное_имя echo ${f%%/*}

Листинг 2.13. Второй пример сопоставления с образцом. (html, txt)

Последний элемент выдается командой, приведенной в листинге 2.14:

echo ${f##*/}

Листинг 2.14. Третий пример сопоставления с образцом. (html, txt)


Перенаправление ввода/вывода


Командный интерпретатор shell ассоциирует с каждым открытым файлом так называемый дескриптор. Дескрипторы нумеруются десятичными целыми числами, начиная с нуля. Верхняя граница зависит от реализации, но, согласно стандарту POSIX, должны поддерживаться по крайней мере десять одновременно открытых файлов (с номерами дескрипторов от 0 до 9 включительно).

Дескрипторы с номерами 0, 1 и 2 имеют специальный смысл. Они соответствуют стандартному вводу, стандартному выводу и стандартному протоколу.

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

Перенаправление ввода:

<слово

Использовать файл слово для стандартного ввода (дескриптор файла 0).

Перенаправление вывода с перезаписью:

>слово >|слово

Использовать файл слово для стандартного вывода (дескриптор файла 1). Имеющийся файл опустошается, а если файла нет, он создается. (При использовании первой разновидности перенаправления вывода в некоторых случаях попытка перезаписи существующего файла является ошибкой; см. далее описание опции -C команды set.)

Перенаправление вывода с дозаписью:

>>слово

Использовать файл слово для стандартного вывода. Если файл существует, то выводимая информация добавляется в конец (сначала производится поиск конца файла); в противном случае файл создается. Если любой из этих конструкций предшествует цифра, она определяет дескриптор (вместо подразумеваемых дескрипторов 0 или 1), который будет ассоциирован с файлом, указанным в конструкции. Например, строка

... 2>protocol

перенаправляет стандартный протокол (дескриптор 2) в файл по имени protocol.

Ввод и вывод могут перенаправляться не только в файл с заданным именем, но и в открытый файл с заданным дескриптором.
Для этого в описанных выше конструкциях в качестве слова следует употребить связку &номер_дескриптора:

<&номер_дескриптора >&номер_дескриптора

Например, чтобы перенаправить стандартный протокол туда же, куда назначен стандартный вывод, употребить конструкцию

... 2>&1

Если в качестве номера_дескриптора указан минус ('-'), соответствующий файл (стандартный ввод, вывод или явно заданный своим дескриптором файл) закрывается.

Shell позволяет открыть файл одновременно на чтение и запись при помощи конструкции

<>слово

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

Приведем два примера. Пусть нужно измерить время выполнения некоторой команды, направив ее результаты в файл cmd.res, а данные о времени - в файл cmd.time. К цели ведет строка, приведенная в листинге 2.24:

time команда >cmd.res 2>cmd.time

Листинг 2.24. Пример перенаправления стандартного вывода и стандартного протокола. (html, txt)

Второй пример. Рассмотрим цикл, описанный в листинге 2.25:

i=0 while [ $i -lt 40 ] do > lost+found/g$i i=$(($i+1)) done rm lost+found/*

Листинг 2.25. Пример перенаправления стандартного вывода пустой команды. (html, txt)

С его помощью создается 40 файлов в каталоге lost+found, которые затем удаляются. Отметим, что перенаправляется стандартный вывод пустой команды, а в результате создается пустой файл.

(Поясним смысл приведенного фрагмента. При проверке и коррекции файловой системы утилитой fsck в каталог /lost+found помещаются непустые файлы, на которые нет ссылок. Сложность в том, что пока утилита fsck работает, ни один файл не должен расширяться, т. е. в каталоге /lost+found должны быть заранее заготовленные пустые места.)

Shell дает возможность размещать исходные данные в виде вставки в интерпретируемый командный файл, для чего употребляются многострочные конструкции:

<<слово вставка слово

и

<<-слово вставка слово

Их можно считать разновидностями перенаправления ввода.


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

В качестве примера использования вставок приведем файл ОС Linux /etc/tripwire/twinstall.sh, который содержит следующие фрагменты (см. листинг 2.26 и листинг 2.27):

cat << END_OF_TEXT A clear-text version of the Tripwire configuration file $TXT_CFG has been preserved for your inspection. It is recommended that you delete this file manually after you have examined it. END_OF_TEXT

Листинг 2.26. Пример использования вставки. (html, txt)

cat <<eof1; cat <<eof2 Вставка 1 eof1 Вставка 2 eof2

Листинг 2.27. Пример использования двух последовательных вставок. (html, txt)

В результате выполнения второго фрагмента будут выданы две строки:

Вставка 1 Вставка 2


Подстановка результатов выполнения команд


Если в командной строке встретилась цепочка символов, заключенная в обратные кавычки (`), она интерпретируется как команда, стандартный вывод которой подставляется вместо упомянутой конструкции. Говорят, что в этом случае производится подстановка результатов выполнения команды, а сами обратные кавычки называют символами подстановки.

Эквивалентной синтаксической формой для подстановки результата выполнения команды является конструкция вида

$(команда)

Для выполнения заданной команды порождается процесс, в рамках которого работает еще один экземпляр командного интерпретатора.

Опишем несколько употребительных способов использования подстановки результатов. Пусть файл filelist содержит список имен других файлов и над совокупностью последних требуется проделать некоторое действие. Если в командную строку поместить конструкцию

... `cat filelist` ...

то это аналогично явному указанию в командной строке на все файлы из списка. (Утилита cat выдает на стандартный вывод содержимое файлов, заданных в качестве аргументов.) Пример подстановки результатов выполнения команды (выдача информации о файлах):

ls -l `cat filelist`

Нет нужды в том, чтобы каждая служебная программа умела читать свои аргументы из файла: имеются универсальный механизм подстановки результатов и команда cat.

Подстановка результатов выполнения команд может сочетаться с другими видами подстановок, например, значения вместо имени переменной. Так, после обработки конструкции

${x:-$(ls)}

команда ls выполнится только тогда, когда значение x не установлено или пусто; в противном случае выполнится команда $x, и ее результат займет место в командной строке.

В языке shell все значения считаются текстовыми. Значит, для выполнения операций с числами нужны особые средства. Вместо конструкции вида

$((выражение))

shell подставит результат вычисления этого выражения, что (с некоторой натяжкой) можно рассматривать как частный случай подстановки результатов выполнения команд. Например, после обработки строки

i=$(($i+1))

значение i (если оно было числом) увеличится на единицу.

Стандарт POSIX обязывает поддерживать арифметику длинных целых со знаком; константы (десятичные, восьмеричные, шестнадцатеричные) должны удовлетворять требованиям языка C.



Правила формирования и средства разбора командных строк


К теме командного языка и его интерпретатора логически примыкают правила формирования и средства разбора командных строк.

В общем случае командная строка состоит из имени служебной программы (утилиты), опций, аргументов этих опций и, наконец, операндов команды.

Разработчики служебных программ должны руководствоваться следующими правилами.

Имя утилиты состоит не менее чем из двух и не более чем из девяти малых латинских букв и/или цифр.Имя опции - это один буквенно-цифровой символ. Опциям предшествует знак минус. После одного минуса могут располагаться несколько опций без аргументов.Опции отделены от своих аргументов.У опций нет необязательных аргументов.Если у опции несколько аргументов, они представляются одним словом и отделяются друг от друга запятыми или экранированными пробелами.Все опции располагаются в командной строке перед операндами.Специальный элемент командной строки --, который не является ни опцией, ни операндом, обозначает конец опций. Все последующие слова трактуются как операнды, даже если они начинаются со знака минус.Порядок разных опций в командной строке не имеет значения. Если повторяется одна опция с аргументами, последние должны интерпретироваться в порядке, указанном в командной строке.Порядок интерпретации операндов может зависеть от утилиты.Если операнд задает читаемый или записываемый файл, то знак минус на его месте используется только для обозначения стандартного ввода (или стандартного вывода, если из контекста ясно, что специфицируется выходной файл).

Стандартным средством разбора опций и их аргументов, заданных в командной строке, является служебная программа getopts (чаще всего реализуемая как обычная встроенная команда shell):

getopts цепочка_имен_опций переменная [аргумент ...]

Как правило, shell-процедура, запущенная разбираемой командной строкой, вызывает утилиту getopts многократно (в цикле). При каждом таком вызове getopts помещает имя очередной выделенной ею опции в качестве значения заданной переменной. Место продолжения разбора (индекс в командной строке) запоминается в shell-переменной OPTIND, начальным значением которой служит единица.


Если у опции должен быть аргумент (что обозначается двоеточием после имени опции в цепочке имен опций), getopts выделяет его из командной строки и помещает в shell-переменную OPTARG.

Заданную при вызове getopts переменную, а также OPTIND и OPTARG следует использовать в качестве локальных, они не должны экспортироваться в окружение.

Если при вызове getopts указаны аргументы, разбираются они, а не элементы упомянутой выше командной строки.

Когда в командной строке обнаруживается опция, отсутствующая в цепочке имен опций, значением заданной переменной становится знак вопроса. Если первый символ цепочки имен опций - двоеточие, то в OPTARG помещается обнаруженный символ, иначе в стандартный протокол выдается диагностическое сообщение. С точки зрения shell-процедуры, вызвавшей getopts, это должно считаться ошибкой; с точки зрения getopts - нет.

По достижении конца опций утилита getopts возвращает положительный код завершения, а в OPTIND помещается номер первого элемента командной строки, не являющегося опцией.

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

В качестве примера использования служебной программы getopts рассмотрим фрагмент shell-процедуры cmd, где производится разбор заданных при ее вызове опций (см. листинг 2.31).

Листинг 2.31. Пример использования служебной программы getopts. (html, txt)

Если вызвать shell-процедуру cmd любым из способов, показанных в листинге 2.32, будет выдан результат, приведенный в листинге 2.33.

cmd -a -o f1.o,f2.o file1 - cmd -ba -o f1.o,f2.o -- file1 - cmd -o f1.o,f2.o -b -a file1 -

Листинг 2.32. Возможные варианты вызова shell-процедуры, использующей служебную программу getopts. (html, txt)

Заданный флаг: a Аргумент опции o: f1.o,f2.o Остаток командной строки: file1 -

Листинг 2.33. Возможный результат работы shell-процедуры, использующей служебную программу getopts. (html, txt)



© 2003-2007 INTUIT.ru. Все права защищены.

Листинг 2.


ls -al /bin | wc -l
Листинг 2. 1. Пример конвейера.
Закрыть окно




ls -al | grep "Oct "
Листинг 2.2. Еще один пример конвейера.
Закрыть окно




ls -al | grep "Oct " | wc -l
Листинг 2.3. Пример трехступенчатого конвейера.
Закрыть окно




ls -Rl /dev | more
Листинг 2.4. Конвейер для поэкранного просмотра результатов.
Закрыть окно




ls -al | grep "Oct" | tee /tmp/tmpinf | wc -l
Листинг 2. 5. Четырехступенчатый конвейер.
Закрыть окно




a=value_of_variable b=1+2 echo a = $a echo b = $b
Листинг 2.6. Присваивание и извлечение значение переменных.
Закрыть окно




a = value_of_variable b = 1+2
Листинг 2.7. Результат вывода значений переменных.
Закрыть окно




echo Имя команды: $ 0 echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3
Листинг 2.8. Пример shell-процедуры.
Закрыть окно




three_args arg1 . - arg4
Листинг 2.9. Пример вызова shell-процедуры с аргументами.
Закрыть окно




Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -
Листинг 2.10. Результат выполнения shell-процедуры.
Закрыть окно




Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: -- Значение третьего аргумента:
Листинг 2.11. Еще один результат выполнения shell-процедуры.
Закрыть окно




f=file. f mv $f ${f%.f}.for
Листинг 2.12. Пример сопоставления с образцом.
Закрыть окно




f=маршрутное_имя echo ${f%%/*}
Листинг 2.13. Второй пример сопоставления с образцом.
Закрыть окно




echo ${f##*/}
Листинг 2.14. Третий пример сопоставления с образцом.
Закрыть окно




echo Идентификатор текущего процесса: $$ echo Имя команды: $ 0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3
Листинг 2.15. Усовершенствованная shell-процедура three_args.
Закрыть окно




Идентификатор текущего процесса: 3882 Имя команды: three_args Число фактических аргументов: 4 Совокупность всех аргументов: arg1 . - arg4 Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -
Листинг 2.16. Результат вызова усовершенствованной процедуры three_args.
Закрыть окно




export HISTSIZE="1000" export HOME="/home/galat" export LANG="C" export LESSCHARSET="koi8-r" export LOGNAME="galat" export MAIL="/var/spool/mail/galat" export TTY="/dev/ttyS4" export USER="galat"
Листинг 2.17. Возможные результаты выполнения команды export -p.
Закрыть окно




echo Идентификатор текущего процесса: $$ echo Имя команды: $ 0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ i=1 for arg do echo Значение аргумента номер ${i}: $arg i=$(($i+1)) done
Листинг 2.18. Еще одно усовершенствование shell-процедуры three_args.
Закрыть окно




if [ -s ${f} ] then /bin/sh ${f} start fi
Листинг 2.19. Пример условного оператора.
Закрыть окно




case "$1" in start) start ;; stop) stop ;; reload | restart) restart ;; condrestart) if [ -f /var/lock/subsys/atd ] then restart fi ;; *) echo $"Usage: $0 {start | stop | restart | condrestart}" exit 1 esac
Листинг 2.20. Пример оператора выбора.
Закрыть окно




echo $# $1 f ( ) { echo $# $1 } f a b f b echo $# $1
Листинг 2.21. Пример определения и вызова функции.
Закрыть окно




for f in /etc/rc$runlevel.d/S* do if [ -s ${f} ] then /bin/sh ${f} start fi done
Листинг 2.22. Пример сочетания управляющих конструкций с генерацией имен файлов.
Закрыть окно




for f in *. f do mv $f `basename $f .f`.for done
Листинг 2.23. Пример подстановки результатов команды как части слова.
Закрыть окно




time команда >cmd.res 2>cmd.time
Листинг 2.24. Пример перенаправления стандартного вывода и стандартного протокола.
Закрыть окно




i= 0 while [ $i -lt 40 ] do > lost+found/g$i i=$(($i+1)) done rm lost+found/*
Листинг 2.25. Пример перенаправления стандартного вывода пустой команды.
Закрыть окно




cat << END_OF_TEXT A clear- text version of the Tripwire configuration file $TXT_CFG has been preserved for your inspection. It is recommended that you delete this file manually after you have examined it. END_OF_TEXT
Листинг 2.26. Пример использования вставки.
Закрыть окно




cat <<eof1; cat <<eof2 Вставка 1 eof1 Вставка 2 eof2
Листинг 2.27. Пример использования двух последовательных вставок.
Закрыть окно




while [ "$1" ] do [ -f $1 ] && echo $ 1 shift done
Листинг 2.28. Пример использования специальной встроенной команды shift.
Закрыть окно




argv1="$1" set `/sbin/runlevel` runlevel=$2 previous=$1
Листинг 2.29. Пример использования специальной встроенной команды set. (административная утилита runlevel выдает предыдущий и текущий уровни выполнения системы).
Закрыть окно




#include <stdlib.h> int system (const char *command); #include <stdio.h> FILE *popen (const char *command, const char *mode);
Листинг 2.30. Описание функций system() и popen().
Закрыть окно




while getopts :abo: c do case $c in a | b) FLAG=$c;; o) OARG=$OPTARG;; ?) printf "Использование: %s: [-a | -b] [-o выходной_файл] [аргумент ...]\n" $0 exit 1;; esac done shift $(($OPTIND - 1)) printf "Заданный флаг: %s\n" $FLAG printf "Аргумент опции o: %s\n" $OARG printf "Остаток командной строки: %s\n" "$*"
Листинг 2.31. Пример использования служебной программы getopts.
Закрыть окно




cmd -a -o f1.o,f2.o file1 - cmd -ba -o f1.o,f2.o -- file1 - cmd -o f1.o,f2.o -b -a file1 -
Листинг 2.32. Возможные варианты вызова shell-процедуры, использующей служебную программу getopts.
Закрыть окно




Заданный флаг: a Аргумент опции o: f1.o,f2. o Остаток командной строки: file1 -
Листинг 2.33. Возможный результат работы shell-процедуры, использующей служебную программу getopts.
Закрыть окно




#include <unistd.h> int getopt (int argc, char *const argv[], const char *optstring); extern char *optarg; extern int optind, opterr, optopt;
Листинг 2.34. Описание функции getopt() и ассоциированных с ней внешних переменных.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа разбирает опции вызвавшей ее командной строки */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> int main (int argc, char *argv []) { int c; /* Имя анализируемой опции */ int aflg = 0; /* Признак того, что задана опция a */ int bflg = 0; /* Признак того, что задана опция b */ int errflg = 0; /* Флаг наличия ошибки в командной строке */ int flg = '?'; /* Флаг (a или b), заданный в командной строке */ char *ofile = NULL; /* Указатель на аргумент опции o */ /* Подавим стандартную диагностику */ /* независимо от первого символа */ /* цепочки имен опций */ opterr = 0; while ((c = getopt (argc, argv, ":abo:")) != -1) { switch (c) { case 'a': aflg++; flg = c; if (bflg) { fprintf (stderr, "Опции a и b несовместимы\n"); errflg++; } break; case 'b': bflg++; flg = c; if (aflg) { fprintf (stderr, "Опции a и b несовместимы\n"); errflg++; } break; case 'o': ofile = optarg; break; case ':': fprintf (stderr, "Отсутствует аргумент опции -%c\n", optopt); errflg++; break; case '?': fprintf (stderr, "Недопустимая опция -%c\n", optopt); errflg++; break; } } if (errflg) { (void) fprintf (stderr, "Использование: %s: [-a | -b] [-o выходной_файл] " "[аргумент ...]\n", argv [0]); return (1); } printf ("Заданный флаг: %c\n", flg); printf ("Аргумент опции o: %s\n", ofile); printf ("Остаток командной строки:"); for (; optind < argc; optind++) { printf (" %s", argv [optind]); } printf ("\n"); return 0; }
Листинг 2.35. Пример использования функции getopt().
Закрыть окно



Служебные переменные языка shell


Значения некоторых переменных устанавливаются самим языком shell. Перечислим эти переменные и опишем их предназначение.

#

Количество фактических аргументов (десятичное).

-

Флаги (однобуквенные опции), указанные при запуске shell или установленные посредством команды set (см. далее).

?

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

$

Идентификатор процесса, в рамках которого выполняется shell.

!

Идентификатор последнего асинхронно запущенного процесса.

*, @

Совокупность всех фактических аргументов (начиная с $1), разделенных пробелами.

Напомним: чтобы получить значения этих переменных, перед ними нужно поставить знак $.

Значения $@ и $* имеют некоторые тонкие различия, на которых мы, однако, останавливаться не будем.

Несколько усложним процедуру three_args, чтобы продемонстрировать только что описанные возможности (см. листинг 2.15).

echo Идентификатор текущего процесса: $$ echo Имя команды: $0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3

Листинг 2.15. Усовершенствованная shell-процедура three_args. (html, txt)

Теперь, вызывая усовершенствованную процедуру three_args, выполним командную строку

three_args arg1 . - arg4

Тогда на экране появится примерно следующее (см. листинг 2.16):

Идентификатор текущего процесса: 3882 Имя команды: three_args Число фактических аргументов: 4 Совокупность всех аргументов: arg1 . - arg4 Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -

Листинг 2.16. Результат вызова усовершенствованной процедуры three_args. (html, txt)



Управляющие конструкции


Среди прочих язык shell содержит следующие управляющие конструкции.

Оператор цикла for:

for имя [in слово ...] do список done

При каждой итерации переменная имя принимает следующее значение из набора

in слово ...

Если конструкция in слово ... опущена, то список выполняется для каждого формального аргумента.

Условный оператор:

if список_1 then список_2 [elif список_3 then список_4] ... [else список_5] fi

Выполняется список_1. Если код его завершения 0, то выполняется список_2, в противном случае - список_3, и если код его завершения 0, то выполняется список_4 и т.д. Если же коды завершения всех списков, использованных в качестве условий, оказались ненулевыми, выполняется else-часть (список_5). Если else-часть отсутствует и ни одна then-часть не выполнялась, возвращается нулевой код завершения.

Оператор цикла while (until):

while список_1 do список_2 done

Пока код завершения последней команды списка_1 есть 0, выполняются команды списка_2. При замене служебного слова while на until условие продолжения цикла меняется на противоположное. Если команды из списка_2 не выполнялись вообще, код завершения устанавливается равным нулю.

Оператор выбора:

case слово in [шаблон [| шаблон] ...) список ;;] ... esac

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

Определение функции:

имя () открывающая_скобка список закрывающая_скобка

Определяется функция с заданным именем. Список является ее телом, которое окружают круглые или фигурные скобки. Для вызова функции используется обычный синтаксис команды:

имя [аргумент ...]

Если тело функции заключено в фигурные скобки, она выполняется в рамках текущего процесса; в случае круглых скобок порождается отдельный процесс. На время выполнения функции аргументы $1, $2, ..., а также служебная переменная # получают новые значения, определенные аргументами команды вызова. Затем восстанавливаются старые значения.

Приведем примеры использования управляющих конструкций. Сначала усовершенствуем процедуру three_args, чтобы она выдавала значения всех, а не только первых трех аргументов (см. листинг 2.18):

echo Идентификатор текущего процесса: $$ echo Имя команды: $0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ i=1 for arg do echo Значение аргумента номер ${i}: $arg i=$(($i+1)) done

Листинг 2.18. Еще одно усовершенствование shell-процедуры three_args. (html, txt)


!Унарная операция отрицания
-aЛогическое И.
-oЛогическое ИЛИ.
Обратим внимание на то, что квадратные скобки, обрамляющие условие, и каждый компонент условия должны быть выделены пробелами.

Приведем пример использования управляющей конструкции if. В процессе загрузки практически любой разновидности ОС Unix выполняются строки следующего или близкого вида (см. листинг 2.19):

if [ -s ${f} ] then /bin/sh ${f} start fi

Листинг 2.19. Пример условного оператора.

Если файл, имя которого является значением переменной f, существует и имеет ненулевой размер, он выполняется с аргументом start.

В качестве примера употребления конструкции case приведем еще один фрагмент, типичный для процесса загрузки системы (см. листинг 2.20):

case "$1" in start) start ;; stop) stop ;; reload | restart) restart ;; condrestart) if [ -f /var/lock/subsys/atd ] then restart fi ;; *) echo $"Usage: $0 {start | stop | restart | condrestart}" exit 1 esac

Листинг 2.20. Пример оператора выбора.

Известные значения первого аргумента распознаются, в ответ на все прочие (шаблон *) сообщается, как пользоваться данной shell-процедурой.

Следующий пример иллюстрирует определение и вызов функции (см. листинг 2.21).

echo $# $1 f ( ) { echo $# $1 } f a b f b echo $# $1

Листинг 2.21. Пример определения и вызова функции.

Если данный текст хранится в файле g, то по окончании выполнения команды (вызов shell-процедуры, содержащей функцию)

g c d e

будет выдан следующий результат:

3 c 2 a 1 b 3 c


Встроенные команды


Мы уже отмечали, что подавляющее большинство команд разыскивается в списке поиска и выполняется языком shell в рамках порожденных процессов. Однако для некоторых команд - либо по соображениям эффективности, либо потому, что требуемую функциональность трудно достичь в рамках отдельного процесса - целесообразно сделать исключение и реализовать их как встроенные, их выполняет сам shell. Очевидным примером встроенной команды служит export - пометить переменную как глобальную, входящую в окружение процесса проще внутренними методами.

Встроенные команды подразделяются на обычные и специальные. С точки зрения приложений первые ничем (кроме, быть может, эффективности обработки) не отличаются от невстроенных утилит. В частности, они могут фигурировать в качестве программы процесса. Специальные встроенные команды этим свойством обладать не обязаны. Кроме того, у них есть еще два отличия:

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

Перечислим некоторые из специальных встроенных команд.

. файл

Shell читает и выполняет команды из файла, затем возобновляется чтение со стандартного ввода; при поиске файла используется значение переменной PATH. Иными словами, специальная команда . позволяет выполнить любую команду в рамках текущего процесса.

eval [аргумент ...]

Выполнить команду, заданную аргументами eval.

exec [аргумент ...]

Сменить программу процесса: в рамках текущего процесса команда, заданная аргументами exec, заменяет shell. В качестве аргументов могут указываться спецификации ввода/вывода, и, если нет никаких других аргументов, будет лишь перенаправлен ввод/вывод текущего процесса shell.

exit [код_завершения]

Завершить выполнение shell с указанным кодом. При отсутствии аргумента код завершения определяется последней выполненной командой. Чтение символа конца файла также приводит к завершению shell.


shift [n]

Формальные аргументы, начиная с (n+1)-го, переименовываются в $1 и т.д. По умолчанию n=1.

К числу обычных встроенных команд относятся:

cd [каталог]

Сделать текущим заданный каталог. Если каталог не указан, используется значение переменной окружения HOME.

pwd

Выводит имя текущего каталога.

read [переменная ...]

Со стандартного ввода читается одна строка и делится на поля; первое поле присваивается первой переменной, второе - второй и т.д., причем последовательность всех оставшихся полей присваивается последней переменной. Исходная строка имеет продолжение, если в конце ее стоит последовательность \перевод_строки. Символы, отличные от перевода строки, также могут быть экранированы с помощью \, который удаляется перед присваиванием полей. Возвращается нулевой код завершения, если только не встретился конец файла.

Некоторые пояснения. С помощью команды exec осуществляется смена программы текущего процесса. Например, если пользователь привык работать в командном интерпретаторе bash, он может перейти туда по команде

exec bash

При этом обычный shell бесследно исчезнет.

Аргументы специальной встроенной команды eval обрабатываются дважды: когда исходную строку интерпретирует shell и когда работает сама eval. Так, после выполнения строк

a=1 b=a eval c='$'$b echo $c

на стандартный вывод будет выдана единица, поскольку аргументом eval будет оператор присваивания c=$a.

Если файл data содержит строку

1 2 3 4 5

то после выполнения специальной встроенной команды read

read a b c echo $c $b $a

на стандартный вывод будет выдан следующий результат:

3 4 5 2 1

Текст "3 4 5" (остаток строки) стал значением переменной c.

Команду shift используют при последовательной обработке аргументов shell-процедур. Например, если требуется выдать аргументы, которые являются именами существующих обычных файлов, употребляют цикл вида (см. листинг 2.28):

while [ "$1" ] do [ -f $1 ] && echo $1 shift done

Листинг 2.28. Пример использования специальной встроенной команды shift. (html, txt)



Важную роль играет еще одна специальная встроенная команда - set. Она используется для изменения режима работы shell и для присваивания новых значений формальным аргументам. Сразу же отметим, что наивное присваивание

цифра=слово

shell трактует как запуск программы с именем цифра=слово, т. е. оно не позволяет изменить значение формального аргумента.

Команда set записывается в виде

set [опция ...] [аргумент ...]

Из ее опций упомянем следующие:

-a

Экспортировать в окружение все переменные, которым производится присваивание.

-C

Защищать существующие файлы от перезаписи при перенаправлении вывода посредством конструкции ">" (см. выше раздел "Перенаправление ввода/вывода").

+o

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

-x

Устанавливает режим трассировки после выполнения всех подстановок в командных строках, но до выполнения они будут выдаваться в стандартный протокол. Опция +x выключает трассировку. Стандарт не специфицирует, будет ли трассироваться команда set +x.

Если в команде set заданы аргументы, они станут новыми значениями $1, $2 и т.д. Типичный пример использования команды set содержится в одном из файлов ОС Linux (/etc/rc.d/rc) выполняемых при загрузке системы (см. листинг 2.29):

argv1="$1" set `/sbin/runlevel` runlevel=$2 previous=$1

Листинг 2.29. Пример использования специальной встроенной команды set. (административная утилита runlevel выдает предыдущий и текущий уровни выполнения системы). (html, txt)

Возможностью, не обязательной с точки зрения стандарта POSIX-2001 (отнесенной к расширению "Мобильность пользователей", UP), но весьма полезной на практике, является механизм синонимов для имен простых команд. Синонимы задаются с помощью обычной встроенной команды alias:

alias [имя[=синоним] ...]

Когда shell выделяет в командной строке слово, расположенное вместо имени простой команды, он проверяет, не является ли оно именем синонима, и при положительном ответе заменяет его значением, заданным командой alias и представляющим собой цепочку символов.

Например, если хочется, чтобы команда ls по умолчанию выдавала информацию о файлах, имена которых начинаются с точки, можно воспользоваться спецификацией для определения синонима:

alias ls="ls -a"

Если подставленный синоним оканчивается пробелом, то анализируется следующее слово командной строки; если и оно оказывается именем синонима, подстановки продолжаются. Например, если синоним для ls задать в виде строки с завершающим пробелом

alias ls="ls -a "

и дополнительно ввести еще одно определение синонима

alias l="-Rl"

то команда, использующая синоним с завершающим пробелом,

ls l

будет раскрыта в строку

ls -a -Rl

и выдаст в длинном формате информацию о всех файлах текущего каталога и его подкаталогов.

Команда alias без аргументов выдает список имеющихся синонимов.


Вызов командного интерпретатора shell


Вызов командного интерпретатора shell осуществляется командой

sh [опция...] [командный_файл [аргумент ...]]

или

sh -c [опция...] командная_цепочка [имя_команды [аргумент ...]]

или

sh -s [опция...] [аргумент ...]

В первом случае интерпретируется заданный командный файл (или содержимое стандартного ввода, если файл не указан), во втором - цепочка символов, в третьем команды читаются со стандартного ввода.

Большинство опций команд sh и set (см. выше) совпадают. Им может предшествовать не только знак минус, но и плюс, что означает инвертирование их смысла. Из специфических опций команды sh выделим -i, предписывающую считать shell интерактивным. Shell будет интерактивным и тогда, когда команды читаются со стандартного ввода, направленного, как и стандартный протокол, на терминал.

Заданные в командной строке аргументы становятся значениями фактических аргументов $1, $2 и т.д. Если при наличии опции -c задано имя_команды, то в результате интерпретации командной цепочки оно становится значением $0.

Пример. Команда

sh -c 'echo $0 $1 $2' a b c

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

a b c

Читателю предлагается самостоятельно определить, что выдаст на стандартный вывод похожая команда

sh -c "echo $0 $1 $2" a b c

(вместо одиночных кавычек для экранирования пробелов использованы двойные), и объяснить полученный результат.

Командный интерпретатор можно вызвать и из программы на языке C, воспользовавшись функциями system() или popen() (см. листинг 2.30):

#include <stdlib.h> int system (const char *command); #include <stdio.h> FILE *popen (const char *command, const char *mode);

Листинг 2.30. Описание функций system() и popen(). (html, txt)

Аргумент command язык shell трактует как командную цепочку в вызове

sh -c command

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

code = system ("cd /usr/bin; ./ls > /tmp/lst");

Функция popen(), как и system(), вызывает выполнение указанной команды. Отличие в том, что при использовании функции popen() создается канал между вызвавшей ее программой и командой. Аргумент mode определяет, что программа будет делать с этим каналом: читать ("r") или писать ("w").



Данные, ассоциированные с пользователем


Операционная система, соответствующая стандарту POSIX, должна поддерживать базу данных пользователей, в которой о каждом из них хранится по крайней мере следующая информация:

имя пользователя;числовой идентификатор пользователя;числовой идентификатор начальной группы;начальный рабочий каталог;начальная программа пользователя.

Поясним смысл перечисленных элементов данных.

Каждый зарегистрированный пользователь ОС имеет имя, которое он указывает для целей идентификации при входе в систему. После проведения идентификации и, как правило, аутентификации пользователя, с ним ассоциируются (неотрицательные) числовые идентификаторы пользователя и начальной группы. В отличие от имен, ОС оперирует ими во всех случаях, кроме первоначальной идентификации. Затем запускается начальная программа пользователя (например, командный интерпретатор shell) с указанным начальным рабочим каталогом.

Поля начального рабочего каталога и начальной программы пользователя могут быть пустыми; в таком случае их трактовка зависит от реализации. Обычно в системе определена подразумеваемая начальная программа, в качестве которой обычно используется /bin/sh.

Пользователи объединяются в группы; каждый пользователь является членом хотя бы одной из них. Для групп также существует база данных, ее записи содержат по крайней мере следующие поля:

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

В базе данных пользователей указывается идентификатор начальной группы, в нее пользователь попадает сразу после входа в систему. В процессе работы возможен переход в другую группу (см. далее описание утилиты newgrp), однако на эти переходы наложены ограничения в виде списка возможных членов группы.

С объектно-ориентированной точки зрения можно считать, что класс "пользователь" предоставляет один метод - начальную программу. Его можно применить для программирования определенных услуг. Например, если нужно дать возможность любому человеку (не обязательно зарегистрированному пользователю), оказавшемуся рядом со свободным терминалом, узнать текущие дату и время, заводят пользователя date с начальной программой /bin/date.


Опросить ассоциированные с пользователем данные (точнее, идентификаторы и имена пользователя и начальной группы, а также всех групп, членами которых ему разрешено быть, - так называемых дополнительных групп) позволяет служебная программа

id [имя_пользователя]

Если имя_пользователя опущено, выдается информация о текущем пользователе и его текущей группе.

Пример использования служебной программы id. Команда

id root

может выдать на стандартный вывод следующий результат:

uid=0(root) gid=0(root) groups=0(root), 1(bin),2(daemon),3(sys),4(adm),6(disk), 10(wheel)

Числовой идентификатор 0 характеризует суперпользователя, который в большинстве Unix-систем не подвержен контролю прав доступа.

Входное имя текущего пользователя можно узнать также с помощью утилиты

logname

и функции getlogin():

#include <unistd.h> char *getlogin (void);

Отметим, что одному идентификатору пользователя может соответствовать несколько записей в базе данных пользователей, различающихся входными именами и, возможно, другими атрибутами; logname и getlogin() выдают имя, под которым начат текущий сеанс работы с ОС.

Над базой данных пользователей определены операции поиска по идентификатору или имени пользователя, реализуемые, соответственно, функциями getpwuid() и getpwnam() (см. пример 3.1):

#include <pwd.h> struct passwd *getpwuid (uid_t uid); #include <pwd.h> struct passwd *getpwnam (const char *name);

Листинг 3.1. Описание функций getpwuid() и getpwnam(). (html, txt)

По стандарту структура passwd должна содержать по крайней мере следующие поля, соответствующие описанным выше обязательным элементам базы данных пользователей:

char *pw_name; /* Имя пользователя */

uid_t pw_uid; /* Числовой идентификатор пользователя */ gid_t pw_gid; /* Числовой идентификатор начальной группы */ char *pw_dir; /* Начальный рабочий каталог */ char *pw_shell; /* Начальная программа пользователя */

Типы uid_t и gid_t определяются в заголовочном файле <sys/types.h>.

Приведем пример выдачи информации о текущем пользователе и пользователе root с идентификатором 0 (см.


пример 3.2).

Листинг 3.2. Пример работы с базой данных пользователей. (html, txt)

По окончании работы этой программы может быть получен следующий результат (см. пример 3.3):

Листинг 3.3. Возможный результат работы с базой данных пользователей. (html, txt)

Аналогичные функции имеются для поиска в базе данных групп - getgrgid() и getgrnam() (см. пример 3.4):

#include <grp.h> struct group *getgrgid (gid_t gid); #include <grp.h> struct group *getgrnam (const char *name);

Листинг 3.4. Описание функций getgrgid() и getgrnam(). (html, txt)

Структура group обязана содержать поля

char *gr_name;/* Имя группы */ gid_t gr_gid; /* Числовой идентификатор группы */ char **gr_mem; /* Указатель на ограниченный пустым указателем массив символьных указателей на имена пользователей, которым разрешено становиться членами данной группы */

Применение функции getgrgid() отражено в пример 3.5.

Листинг 3.5. Пример работы с базой данных групп. (html, txt)

Приведенная в качестве примера программа может привести к результату, показанному в пример 3.6:

Пользователи, включенные в группу с идентификатором 1: root bin daemon

Листинг 3.6. Возможный результат работы с базой данных групп. (html, txt)

Для смены текущей группы пользователя предназначена служебная программа newgrp (стандарт POSIX-2001 относит ее к числу необязательных, входящих в расширение "Мобильность пользователей", UP):

newgrp [-l] [группа]

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

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

Если пользователь не входит в список возможных членов новой группы, при переходе в нее может запрашиваться пароль, однако в стандарте POSIX-2001 этот аспект считается зависящим от реализации.Более того, отмечается, что в базе данных групп нет удобных способов задания паролей, поэтому их использование в приложениях не приветствуется, а со временем пароли групп могут стать ненужными.



Листинг 3.4. Описание функций getgrgid() и getgrnam().

Структура group обязана содержать поля

char *gr_name;/* Имя группы */ gid_t gr_gid; /* Числовой идентификатор группы */ char **gr_mem; /* Указатель на ограниченный пустым указателем массив символьных указателей на имена пользователей, которым разрешено становиться членами данной группы */

Применение функции getgrgid() отражено в пример 3.5.

#include <sys/types.h> #include <grp.h> #include <stdio.h> /* Печать списка пользователей, включенных в группу с заданным идентификатором */ static int print_gr_mem (const gid_t gid) { struct group *grp; /* Данные о группе */ char **c_gr_mem; /* Текущий указатель на имя члена группы */ char *c_gr_mem_name; /* Текущее имя члена группы */ if ((grp = getgrgid (gid)) == NULL) { fprintf (stderr, "\nНе удалось найти информацию о группе с идентификатором %d\n", gid); return 1; } printf ("\nПользователи, включенные в группу с идентификатором %d:\n", gid); c_gr_mem = grp->gr_mem; while ((c_gr_mem_name = *c_gr_mem++) != NULL) { printf(" %-8.8s", c_gr_mem_name); } printf ("\n"); return 0; } int main (void) { return print_gr_mem (1); }

Листинг 3.5. Пример работы с базой данных групп.

Приведенная в качестве примера программа может привести к результату, показанному в пример 3.6:

Пользователи, включенные в группу с идентификатором 1: root bin daemon

Листинг 3.6. Возможный результат работы с базой данных групп.

Для смены текущей группы пользователя предназначена служебная программа newgrp (стандарт POSIX-2001 относит ее к числу необязательных, входящих в расширение "Мобильность пользователей", UP):

newgrp [-l] [группа]

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

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

Если пользователь не входит в список возможных членов новой группы, при переходе в нее может запрашиваться пароль, однако в стандарте POSIX-2001 этот аспект считается зависящим от реализации. Более того, отмечается, что в базе данных групп нет удобных способов задания паролей, поэтому их использование в приложениях не приветствуется, а со временем пароли групп могут стать ненужными.


struct passwd


#include <pwd.h> struct passwd *getpwuid (uid_t uid); #include <pwd.h> struct passwd *getpwnam (const char *name);
Листинг 3.1. Описание функций getpwuid() и getpwnam().
Закрыть окно




#include <unistd.h> #include <sys/types.h> #include <pwd.h> #include <stdio.h> /* Печать элемента базы данных пользователей */ static void print_pwent (const struct passwd *pw) { printf ("Имя пользователя: %s\n", pw->pw_name); printf ("Идентификатор пользователя: %d\n", pw->pw_uid); printf ("Идентификатор группы: %d\n", pw->pw_gid); printf ("Начальный каталог: %s\n", pw->pw_dir); printf ("Начальная программа: %s\n", pw->pw_shell); } int main (void) { char *lgnm; /* Имя текущего пользователя */ struct passwd *pw; /* Данные о текущем пользователе */ /* Поиск и печать информации о текущем пользователе */ if ((lgnm = getlogin ()) == NULL || (pw = getpwnam (lgnm)) == NULL) { fprintf (stderr, "\nНе удалось найти информацию о текущем пользователе\n"); return 1; } printf ("\nИнформация о текущем пользователе\n"); print_pwent (pw); /* То же для пользователя root */ if ((pw = getpwuid ((uid_t) 0)) == NULL) { fprintf (stderr, "\nНе удалось найти информацию о пользователе root\n"); return 1; } printf ("\nИнформация о пользователе root\n"); print_pwent (pw); return 0; }
Листинг 3.2. Пример работы с базой данных пользователей.
Закрыть окно




Информация о текущем пользователе Имя пользователя: galat Идентификатор пользователя: 108 Идентификатор группы: 3 Начальный каталог: /home/galat Начальная программа: /bin/sh Информация о пользователе root Имя пользователя: root Идентификатор пользователя: 0 Идентификатор группы: 0 Начальный каталог: /root Начальная программа: /bin/bash
Листинг 3.3. Возможный результат работы с базой данных пользователей.
Закрыть окно




#include <grp.h> struct group *getgrgid (gid_t gid); #include <grp.h> struct group *getgrnam (const char *name);
Листинг 3.4. Описание функций getgrgid() и getgrnam().
Закрыть окно




#include <sys/types.h> #include <grp.h> #include <stdio.h> /* Печать списка пользователей, включенных в группу с заданным идентификатором */ static int print_gr_mem (const gid_t gid) { struct group *grp; /* Данные о группе */ char **c_gr_mem; /* Текущий указатель на имя члена группы */ char *c_gr_mem_name; /* Текущее имя члена группы */ if ((grp = getgrgid (gid)) == NULL) { fprintf (stderr, "\nНе удалось найти информацию о группе с идентификатором %d\n", gid); return 1; } printf ("\nПользователи, включенные в группу с идентификатором %d:\n", gid); c_gr_mem = grp->gr_mem; while ((c_gr_mem_name = *c_gr_mem++) != NULL) { printf(" %-8.8s", c_gr_mem_name); } printf ("\n"); return 0; } int main (void) { return print_gr_mem (1); }
Листинг 3.5. Пример работы с базой данных групп.
Закрыть окно




Пользователи, включенные в группу с идентификатором 1: root bin daemon
Листинг 3.6. Возможный результат работы с базой данных групп.
Закрыть окно




who | while read a b c do write $a $b << ! $1 ! done
Листинг 3.7. Пример использования утилит who и write.
Закрыть окно




address=... . . . mailx $address << ! . . . текст письма . . . !
Листинг 3.8. Пример использования вставки для формирования письма.
Закрыть окно



Служебные программы, обслуживающие взаимодействие пользователей


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

Чтобы узнать, какие пользователи активны и за какими терминалами они работают, можно воспользоваться служебной программой

who

(заметим, что стандарт POSIX-2001 трактует ее как необязательную, входящую в расширение "Мобильность пользователей").

Выдача утилиты who может выглядеть, например, так (правый столбец означает время входа в систему):

galat ttyS4 Aug 22 12:41 kost ttyS6 Aug 22 10:09

К той же дополнительной категории, что и who, принадлежат утилиты write, talk и mesg (а также описанная выше служебная программа newgrp).

После установления соединения утилита

write имя_пользователя [терминал]

позволяет построчно пересылать стандартный ввод отправителя на терминал пользователя-получателя. Аргументы имя_пользователя и терминал задаются в том виде, как их выводит служебная программа who. Необязательный аргумент [терминал] нужен в тех случаях, когда пользователь-получатель вошел в систему с нескольких терминалов.

Утилиту

talk имя_пользователя [терминал]

можно рассматривать как более современный аналог write, поскольку она имеет экранный интерфейс и поддерживает двустороннее взаимодействие активных пользователей.

С помощью служебной программы

mesg [y|n]

пользователь может разрешить или запретить установление соединений со своим терминалом. При вызове без аргументов mesg выдает текущий статус терминала.

Приведем пример употребления описанных служебных программ. Рассылку сообщения-аргумента всем активным пользователям можно реализовать посредством shell-процедуры (см. пример 3.7).

who | while read a b c do write $a $b << ! $1 ! done

Листинг 3.7. Пример использования утилит who и write. (html, txt)

Обратим внимание на использование во вставке значения аргумента shell-процедуры.

Базовым средством обеспечения почтового взаимодействия между пользователями, согласно стандарту POSIX-2001, является служебная программа mailx. Она позволяет и отправлять, и получать письма.
В первом случае ее следует вызывать командной строкой

mailx [-s тема] адрес ...

во втором -

mailx -e

или

mailx [опция ...]

или

mailx -f [опция ...] [почтовый_ящик]

В процессе отправки текст письма читается со стандартного ввода. При получении всеми указанными выше способами, кроме последнего, проверяется системный почтовый ящик, выделенный текущему пользователю, а при наличии опции -f в качестве почтового ящика используется явно заданный файл. Опция -e предписывает только проверить почтовый ящик и, если он не пуст, вернуть код успеха.

В режиме получения можно отправлять письма и управлять содержимым почтового ящика (для чего утилита mailx предоставляет весьма богатый набор команд), однако поддержку этого режима стандарт POSIX-2001 относит к числу необязательных возможностей.

При отправке писем shell-процедурами часто пользуются вставками (см. пример 3.8):

address=... . . . mailx $address << ! . . . текст письма . . . !

Листинг 3.8. Пример использования вставки для формирования письма. (html, txt)

Разумеется, интерактивные пользователи редко применяют утилиту mailx напрямую; для работы с почтой практически во всех ОС имеются средства с более дружественным интерфейсом.


Изменение атрибутов файлов и текущей позиции в файловой иерархии


Для смены текущего каталога (т. е. начальной точки маршрутов, не начинающихся символом /) служат упоминавшаяся ранее обычная встроенная команда языка shell

cd [-L | -P] [целевой_каталог] cd -

и функция chdir():

#include <unistd.h> int chdir (const char *path);

Команда cd без аргументов осуществляет переход в домашний каталог пользователя, заданный переменной окружения HOME. Если аргументом является минус, выполняются действия, показанные в пример 4.22: осуществляется переход в каталог, ранее бывший текущим, и в случае успеха выводится его абсолютное маршрутное имя.

cd "$OLDPWD" && pwd

Листинг 4.22. Действия, выполняемые по команде cd -. (html, txt)

Алгоритм работы команды cd, как ни странно, довольно сложен и содержит целый ряд тонкостей. Сначала, если целевой каталог задан относительным маршрутным именем, выполняется преобразование к абсолютному формату. При этом:

если имя начинается с точки или точки-точки, перед ним подставляется значение переменной окружения $PWD и /;в других случаях вместо $PWD подставляются элементы списка, являющегося значением переменной окружения $CDPATH и устроенного аналогично $PATH; процесс продолжается до тех пор, пока не получится существующий каталог; в случае необходимости в последнюю очередь используется значение $PWD.

Затем выполняется раскрытие символьных ссылок и устранение имен "точка" и "точка-точка" ("точка" уничтожается вместе со следующим за ней символом /, "точка-точка" - вместе с предыдущим компонентом, отличным от "точки-точки", и символом / между ними). Опции команды cd влияют на порядок выполняемых действий. По опции -P сначала раскрываются символьные ссылки. Это значит, что "точка-точка" трактуется как физический надкаталог (каталог, вышележащий по отношению к указуемому файлу). При наличии (подразумеваемой) опции -L порядок действий обратный; в результате "точка-точка" обозначает логический надкаталог (каталог, вышележащий по отношению к символьной ссылке, а не к указуемому файлу).


Наконец, выполняется переход по результирующему маршруту.

В случае успешной смены текущего каталога соответственно изменяются значения переменных окружения $OLDPWD (текущий каталог непосредственно перед выполнением команды cd) и $PWD (текущий каталог после выполнения команды cd).

Рассмотрим пример выполнения команды cd с разными опциями (см. пример 4.23).

Возможный результат показан в пример 4.24.

ls -dl /usr/tmp /var/tmp cd /usr/tmp pwd pwd -P cd .. pwd cd - cd -P .. pwd

Пример 4.23. Пример выполнения команды cd с разными опциями (html, txt)

Пример 4.24. результат выполнения команд cd с разными опциями (html, txt)

Можно видеть, что /usr/tmp является символьной ссылкой на каталог   /var/tmp. При варьировании опций команды pwd   каталог   /usr/tmp по-разному отображается в качестве текущего (напомним, опция -P команды pwd вызывает раскрытие символьных ссылок). По-разному срабатывает и команда cd с целевым каталогом "точка-точка", опцией -P и без таковой. В результате видно различие между физическим и логическим надкаталогами символьной ссылки.

Для изменения атрибутов файлов служат утилиты

chown [-R] [-H | -L | -P ] владелец[:группа] файл ...

(смена владельца и, быть может, владеющей группы файла) и

chmod [-R] изменение_режима файл ...

(модификация режима файла), а также аналогичные им по назначению и именам функции (см. пример 4.25).

#include <unistd.h> int chown (const char *path, uid_t owner, gid_t group); #include <unistd.h> int fchown (int fildes, uid_t owner, gid_t group); #include <sys/stat.h> int chmod (const char *path, mode_t mode); #include <sys/stat.h> int fchmod (int fildes, mode_t mode);

Листинг 4.25. Описание функций chown(), fchown(), chmod() и fchmod(). (html, txt)

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

Изменить владельца может только нынешний владелец файла или пользователь, "имеющий соответствующие привилегии" (см. выше раздел "Основные понятия и идеи стандарта POSIX"); некоторые реализации предоставляют подобное право только привилегированным пользователям. Обычно приходится изменять владельца и/или группу после переноса файлов с другого компьютера с иным соответствием числовых идентификаторов и имен.

Опции -R, -H и -L имеют в целом тот же смысл, что и для утилиты ls (см. выше): первая предписывает рекурсивный обход встретившихся подкаталогов, две другие - выборочное или полное раскрытие символьных ссылок, указывающих на каталоги. Опция -P означает, что изменения относятся к самим символьным ссылкам.

Если при использовании функций chown() и fchown() меняется лишь владелец, то аргумент group задается равным (gid_t) (-1); при смене только группы идентификатор владельца следует задать как (uid_t) (-1).



Изменить владельца может только нынешний владелец файла или пользователь, "имеющий соответствующие привилегии" (см. выше раздел "Основные понятия и идеи стандарта POSIX"); некоторые реализации предоставляют подобное право только привилегированным пользователям. Обычно приходится изменять владельца и/или группу после переноса файлов с другого компьютера с иным соответствием числовых идентификаторов и имен.

Опции -R, -H и -L имеют в целом тот же смысл, что и для утилиты ls (см. выше): первая предписывает рекурсивный обход встретившихся подкаталогов, две другие - выборочное или полное раскрытие символьных ссылок, указывающих на каталоги. Опция -P означает, что изменения относятся к самим символьным ссылкам.

Если при использовании функций chown() и fchown() меняется лишь владелец, то аргумент group задается равным (gid_t) (-1); при смене только группы идентификатор владельца следует задать как (uid_t) (-1).

Задавая аргумент изменение_режима служебной программы chmod   владельца файла обозначают буквой u, владеющую группу - буквой g, прочих пользователей - o. Добавлению прав соответствует знак +, их удаление помечается знаком -. Знак = обозначает буквальное задание прав (для указанной категории пользователей устанавливается указанный режим доступа). После букв, определяющих категорию пользователей, и знака операции следуют сами добавляемые (удаляемые, устанавливаемые) режимы доступа - обычно r, w и/или x; можно указать несколько подобных связок, разделяя их запятыми (без пробелов).

Пусть, например, все могут читать и изменять файл myfile. Чтобы отнять право на запись у владеющей группы и прочих пользователей и одновременно добавить себе право на выполнение, владельцу следует воспользоваться командой, показанной в пример 4.26.

chmod go-w,u+x myfile

Листинг 4.26. Пример использования служебной программы chmod.

Как указывалось выше, для удобства программирования аналогичных действий на языке C в заголовочном файле   <sys/stat.h> определены константы, соответствующие битам режима файлов.


( Любопытны правила формирования старших цифр года, если они опущены. Когда младшие цифры лежат в диапазоне от 69 до 99, подразумевается 19; в противном случае - 20. Несомненно, в будущих версиях стандарта данное соглашение изменится.)

Если файл-аргумент не существует, он создается по умолчанию служебной программой touch. Опция -c запрещает делать это.

Приведем пример совместного использования служебных программ touch, chown и chmod. В процессе загрузки ОС Linux выполняются действия по инициализации файлов с информацией о пользовательских сеансах, подобные тем, что приведены в пример 4.28.

> /var/run/utmp touch /var/log/wtmp chown :utmp /var/run/utmp /var/log/wtmp chmod ug=rw,o=r /var/run/utmp /var/log/wtmp

Листинг 4.28. использования совместного утилит touch, chown и chmod.

Здесь полезны обе возможности touch - и модификация атрибутов существующих файлов, и создание новых.

Часто touch применяют при работе с файлами-замками (см. пример 4.29).

start () { echo -n "Starting cupsd: " daemon cupsd RETVAL=$? echo [ $RETVAL = 0 ] && touch /var/lock/subsys/cups return $RETVAL }

Листинг 4.29. Пример использования утилиты touch для работы с файлами-замками при загрузке ОС Linux.


Обход и обработка файловых иерархий


Для обхода файловой иерархии и систематической обработки ее элементов служит утилита find:

find [-H | -L] файл ... [выражение]

Она рекурсивно обходит каждую из иерархий с корнями в заданных файлах (разумеется, обычно в этом качестве указываются каталоги), отыскивая файлы, которые удовлетворяют логическому выражению, построенному с помощью описанных ниже средств. Опции -H и -L стандартным образом влияют на трактовку символьных ссылок. Если указуемый файл не существует, find оперирует с самой ссылкой.

Перечислим элементарные логические выражения и правила их вычисления. (Далее n обозначает целое десятичное число; вместо него могут указываться также комбинации +n и -n, что означает, соответственно, "больше, чем n" и "меньше, чем n".)

-name шаблон_файлов

Истина, если имя текущего файла удовлетворяет шаблону_файлов. Символы шаблона, имеющие для shell специальный смысл, должны быть экранированы.

-type тип_файла

Истина, если файл имеет заданный тип: b, c, d, f, p или s - является, соответственно, блочным или символьным специальным файлом, каталогом, обычным файлом, каналом или сокетом.

-size n[c]

Истина, если файл занимает n блоков по 512 байт. Когда указана буква c, размер файла задается в символах. Напомним, что с помощью комбинаций +n и -n можно проверять размер (и три указанные ниже характеристики) не только на равенство, но и на неравенство.

-atime n

Истина, если последний доступ к файлу производился n дней назад (в данном контексте день - это промежуток времени в 86400 секунд).

-mtime n

Истина, если файл последний раз модифицировался n дней назад.

-ctime n

Истина, если атрибуты файла последний раз изменялись n дней назад.

-perm [-]режим

Истина, если режим файла соответствует заданному. При наличии знака минус соответствие понимается как включение (все заданные биты должны присутствовать в режиме доступа к файлу); если минус отсутствует, требуется точное совпадение. Режим задается аналогично утилите chmod.

-links n

Истина, если на файл имеется n жестких ссылок.


-user имя_пользователя

Истина, если владельцем файлаявляется заданный пользователь. Когда в качестве имени задано десятичное число и пользователя с таким именем нет, число трактуется как идентификатор пользователя.

-nouser

Истина, если идентификатор владельца файла отсутствует в базе данных пользователей.

-group владеющая_группа

Истина, если файлом владеет заданная группа.

-nogroup

Истина, если идентификатор владеющей группы файла отсутствует в базе данных групп.

-depth

Всегда истина; задает дисциплину обхода иерархии вглубь: сначала обрабатываются все элементы каталога, потом - сам каталог (по умолчанию find в первую очередь обрабатывает каталог и лишь после этого - его элементы).

-xdev

Всегда истина; предписывает не спускаться в каталоги, имеющие другой идентификатор устройства (st_dev, см. выше описание структуры stat).

-prune

Всегда истина; предписывает не обрабатывать текущий файл, если он является каталогом.

-exec команда

Истина, если после выполнения команды возвращается нулевой код завершения. запись команды должна заканчиваться экранированной точкой с запятой. Аргумент команды, заданный в виде пары фигурных скобок {}, заменяется маршрутным именем обрабатываемого файла.

-ok команда

Эквивалентно -exec за исключением того, что перед выполнением команды запрашивается подтверждение (в виде сгенерированной командной строки со знаком вопроса в конце), и она выполняется только при ответе y.

-print

Всегда истина; вызывает выдачу маршрутного имени обрабатываемого файла на стандартный вывод. Если в командной строке find не задано выражение, то подразумевается -print. Если выражение не содержит ни -exec, ни -ok, ни -print, вместо него используется конструкция( выражение ) -print

-newer файл

Истина, если текущий файл был модифицирован позднее указанного файла

( выражение )

Истина, если истинно заключенное в скобки выражение (скобки должны быть экранированы от интерпретации языком shell).

Элементарные логические выражения могут комбинироваться с помощью следующих операций (в порядке уменьшения приоритета):

унарная операция отрицания, обозначается !.

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

логическое ИЛИ, обозначается -o. Если значением первого операнда оказалась истина, второй не вычисляется.


Основные понятия.


В трактовке стандарта POSIX понятие файла охватывает все, что может содержать, потреблять и/или поставлять информацию. В объектно-ориентированной терминологии файл должен предоставлять методы для чтения и/или записи и иметь такие атрибуты, как тип, имя и режим.

В стандарте зафиксированы следующие типы файлов:

обычный файл;

каталог;

канал;

символьный специальный файл;

блочный специальный файл;

символьная ссылка;

сокет.

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

Каталог состоит из элементов (ссылок), ассоциирующих имена с файлами. Одинаковые имена не могут фигурировать в разных элементах одного каталога, но разные элементы могут ссылаться на один и тот же файл.

Канал можно представлять себе в виде транспортера, с одной стороны которого находится поставщик (процесс, пишущий в канал), а с другой - потребитель (процесс, читающий из канала). Данные читаются в том же порядке, в каком производилась запись, т. е. с точки зрения структур данных канал - это очередь.

Специальные файлы соответствуют аппаратным компонентам компьютера. Обычно при использовании символьных специальных файлов остаются видимыми аппаратные характеристики соответствующих устройств, а при доступе к устройствам посредством блочных специальных файлов аппаратные характеристики, как правило, остаются скрытыми. Типичный пример устройства, которому соответствует символьный специальный файл, - терминал.

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

Сокет - это оконечная точка межпроцессных коммуникаций.

Файлы вместе со служебной информацией, хранящейся в объектах, которые называются описателями файлов, объединяются в иерархическую структуру (направленный граф), именуемую файловой системой.
Все неконцевые вершины графа (т. е. вершины, откуда выходит хотя бы одна дуга) являются каталогами; все концевые имеют другие типы.

Согласно стандарту POSIX-2001, для каждого процесса определен корневой каталог с именем / - вершина графа, из которой осуществляется доступ к прочим файлам данной файловой системы. "Абсолютного", единого для всех корня может и не существовать.

В пределах файловой системы каждый файл имеет уникальный идентификатор (порядковый номер - он же номер описателя файла).

Имена, ассоциируемые с файлами (точнее, с их порядковыми номерами) посредством каталогов, называются простыми. С формальной точки зрения они могут включать любые символы, кроме слэша (/), однако для повышения мобильности приложений стандарт рекомендует ограничиться латинскими буквами, цифрами, а также символами ., _, - (точка, подчеркивание, минус), причем минус не должен быть первым символом. В соответствующей стандарту операционной системе сохраняется различие между большими и малыми буквами.

(При использовании в именах файловнекоторых других символов довольно часто возникают проблемы. Укажем лишь на часть из них.

Символы национальных алфавитов создают трудности с отображением имен. Символы, имеющие специальный смысл для командного интерпретатора, могут вызвать неожиданные и нежелательные модификации имен. Символ : (двоеточие) в имени каталога способен нарушить нормальную интерпретацию значения переменной окружения PATH. Минус в качестве первого символа придает имени файла вид комбинации опций, нарушая процесс разбора аргументов утилит. И т.д, и т.п.)

В каждом каталоге должны присутствовать имена . (точка) и .. (по стандарту читается как точка-точка), которые трактуются как ссылки на текущий и вышележащий каталоги. Для корневого каталога имя .. может также ссылаться на корень.

Под маршрутным именем понимается цепочка символов, идентифицирующая файл и состоящая из нескольких (в том числе из нуля) простых имен файлов, разделенных символами / и называемых компонентами маршрута.

Получение информации о файлах и файловых системах


Чтобы узнать абсолютное маршрутное имя   текущего каталога, приложение может воспользоваться упоминавшейся ранее обычной встроенной командой языка shell

pwd [-L | -P]

#include <unistd.h> char *getcwd (char *buf, size_t size);

Листинг 4.1. Описание функции getcwd(). (html, txt)

Команда pwd с (подразумеваемой) опцией -L извлекает имя текущего каталога из переменной окружения PWD, если это возможно. Посредством опции -P выполняется раскрытие символьных ссылок: в выдаваемом на стандартный вывод абсолютном маршрутном имени вместо символьных ссылок подставляется их содержимое.

Функция getcwd() помещает абсолютное маршрутное имя   текущего каталога (без символьных ссылок) в массив buf длины size, который и возвращается в качестве результата (при ошибке результат равен NULL).

Приведем пример программы, использующей функцию getcwd() (см. пример 4.2).

Листинг 4.2. Пример использования функции getcwd(). (html, txt)

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

Для выдачи на стандартный вывод информации о файлах всех типов служит утилита

ls [опция ...] [файл ...]

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

Опции управляют порядком и степенью подробности выдаваемой информации о файлах. Если опции не заданы, выводятся только имена файлов. Если не заданы файлы, выдается информация о файлах   текущего каталога. Опция -l предписывает выводить подробную информацию. Например, при использовании служебной программы ls по команде

ls -l /

может быть выдано следующее (см. пример 4.3):

Пример 4.3. Возможный результат использования служебной программы ls (html, txt)

Число в первой строке есть суммарный размер (в блоках по 512 байт) всех файлов   каталога, информация о которых выдана.
Далее следуют строки с информацией об отдельных файлах. Первый символ в этих строках задает тип файла:

d - каталог;b - блочный специальный файл;c - символьный специальный файл;l - символьная ссылка;p - канал;- (минус) - обычный файл.

Отметим, что стандартом POSIX-2001 не предусмотрено обозначение для сокетов, указано только, что реализации могут вводить свои типы файлов и обозначения для них.

Девять последующих символов отражают режим доступа к файлу: первые три символа - права доступа его владельца, следующие три - владеющей группы, последние три - права доступа прочих пользователей. Наличие буквы r (чтение), w (запись) или x (выполнение) означает, что соответствующее право имеется; знак минус свидетельствует об отсутствии права. Например, файл   /usr является каталогом, куда может писать только пользователь root, а читать и искать информацию - все остальные.

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

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

Наконец, следуют дата и время последнего изменения и имя файла. По умолчанию имена файлов сортируются в алфавитном порядке.

Опишем еще несколько употребительных опций служебной программы ls.

КлючОписание
-aВывести список всех файлов (обычно не выводятся данные о файлах, имена которых начинаются с точки).
-CВыдавать имена файлов в несколько столбцов (с сортировкой по столбцам). Отметим, что если в командной строке встречаются пары взаимоисключающих опций (например, -lC), то действует та, что указана последней.
-dТрактовать каталоги наравне с файлами других типов. Часто используется с опцией -l для получения сведений о состоянии каталога.
-FВыводить символ / после имен каталогов, * - после выполнимых файлов, | - после каналов, @ - после символьных ссылок.
-iВыдавать порядковый номер файла в файловой системе (см. выше).
-R

Рекурсивно обойти встретившиеся подкаталоги.
-rИзменить порядок сортировки файлов на обратный.
-tИспользовать в качестве первичного ключа сортировки время последнего изменения (сначала идут самые свежие файлы); имя служит вторичным ключом. Обычно символьные ссылки трактуются утилитой ls наравне с файлами других типов, только на месте имени выводится комбинацияимя_файла-ссылки -> содержимое_файла-ссылки. Для получения информации о файле, на который указывает символьная ссылка, следует воспользоваться одной из следующих опций.
-LЕсли символьная ссылка является аргументом утилиты ls или встретилась в процессе обхода файловой иерархии, выдавать информацию об указуемом файле, а не о ссылке. На месте имени выводится имя файла-ссылки (а не указуемого файла).
-HАналогична -L, но воздействует только на символьные ссылки, заданные в командной строке и указывающие на каталог.


Наличие буквы r (чтение), w (запись) или x (выполнение) означает, что соответствующее право имеется; знак минус свидетельствует об отсутствии права. Например, файл   /usr является каталогом, куда может писать только пользователь root, а читать и искать информацию - все остальные.

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

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

Наконец, следуют дата и время последнего изменения и имя файла. По умолчанию имена файлов сортируются в алфавитном порядке.

Опишем еще несколько употребительных опций служебной программы ls.

КлючОписание
-aВывести список всех файлов (обычно не выводятся данные о файлах, имена которых начинаются с точки).
-CВыдавать имена файлов в несколько столбцов (с сортировкой по столбцам). Отметим, что если в командной строке встречаются пары взаимоисключающих опций (например, -lC), то действует та, что указана последней.
-dТрактовать каталоги наравне с файлами других типов. Часто используется с опцией -l для получения сведений о состоянии каталога.
-FВыводить символ / после имен каталогов, * - после выполнимых файлов, | - после каналов, @ - после символьных ссылок.
-iВыдавать порядковый номер файла в файловой системе (см. выше).
-R

Рекурсивно обойти встретившиеся подкаталоги.
-rИзменить порядок сортировки файлов на обратный.
-tИспользовать в качестве первичного ключа сортировки время последнего изменения (сначала идут самые свежие файлы); имя служит вторичным ключом. Обычно символьные ссылки трактуются утилитой ls наравне с файлами других типов, только на месте имени выводится комбинацияимя_файла-ссылки -> содержимое_файла-ссылки. Для получения информации о файле, на который указывает символьная ссылка, следует воспользоваться одной из следующих опций.
-LЕсли символьная ссылка является аргументом утилиты ls или встретилась в процессе обхода файловой иерархии, выдавать информацию об указуемом файле, а не о ссылке. На месте имени выводится имя файла-ссылки (а не указуемого файла).
-HАналогична -L, но воздействует только на символьные ссылки, заданные в командной строке и указывающие на каталог.
<


4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 ex 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 rvi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 rview 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 vi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 view

Листинг 4.8. Возможный результат использования служебной программы ls при выдаче информации о символьных ссылках и наличии опции -L.

Данные о специальных файлах (см. пример 4.9) могут выглядеть так, как показано в пример 4.10.

ls -l /dev/ttyp[0-3] /dev/fd[2-5]

Листинг 4.9. Пример использования служебной программы ls для выдачи информации о специальных файлах.

brw-rw---- 1 root floppy 2, 2 Apr 1 2002 /dev/fd2 brw-rw---- 1 root floppy 2, 3 Apr 1 2002 /dev/fd3 brw-rw---- 1 root floppy 2, 128 Apr 1 2002 /dev/fd4 brw-rw---- 1 root floppy 2, 129 Apr 1 2002 /dev/fd5 crw-rw-rw- 1 root root 3, 0 Sep 2 17:21 /dev/ttyp0 crw-rw-rw- 1 root tty 3, 1 Apr 1 2002 /dev/ttyp1 crw-rw-rw- 1 root tty 3, 2 Apr 1 2002 /dev/ttyp2 crw-rw-rw- 1 root tty 3, 3 Apr 1 2002 /dev/ttyp3

Листинг 4.10. Возможный результат использования служебной программы ls при выдаче информации о специальных файлах.

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

Сведения о файлах-каналах могут выглядеть так, как показано в пример 4.11.

total 0 prw--w---- 1 root root 0 Sep 2 10:42 xdmctl prw--w---- 1 shmyrev root 0 Sep 2 10:42 xdmctl-:0

Листинг 4.11. Возможный результат использования служебной программы ls при выдаче информации о каналах.

В пример 4.12 приведен пример вывода информации о сокетах.

srwx------ 1 shmyrev root 0 Sep 2 10:42 /dev/gpmctl srw-rw-rw- 1 root root 0 Sep 2 10:42 /dev/log

Листинг 4.12. Возможный результат использования служебной программы ls при выдаче информации о сокетах.

Опция -t позволяет увидеть сначала те файлы, которые изменялись позже других. Например, командная строка служебной программы ls



Функция stat() предоставляет информацию о поименованном файле: аргумент path указывает на маршрутное имя файла. Чтобы получить эти сведения, достаточно иметь право на поиск для всех компонентов маршрутного префикса. Функция fstat() сообщает данные об открытом файле, задаваемом дескриптором файла   fildes. Функция lstat() эквивалентна stat() за одним исключением: если аргумент path задает символьную ссылку, lstat() возвращает информацию о ней, а stat() - о файле, на который эта ссылка указывает.

В случае нормального завершения результат функций семейства stat() равен 0.

Аргумент buf является указателем на структуру типа stat, в которую помещается информация о файле. Согласно стандарту POSIX-2001, в ней содержатся по крайней мере следующие поля:

dev_t st_dev; /* Идентификатор устройства, содержащего файл */ ino_t st_ino; /* Порядковый номер файла в файловой системе */ mode_t st_mode; /* Режим файла nlink_t st_nlink; /* Число жестких ссылок на файл */ uid_t st_uid; /* Идентификатор владельца файла */ gid_t st_gid; /* Идентификатор владеющей группы */ off_t st_size; /* Для обычных файлов и символьных ссылок - размер в байтах */ /* Для файлов других типов значение этого поля неспецифицировано */ time_t st_atime; /* Время последнего доступа */ time_t st_mtime; /* Время последнего изменения файла */ time_t st_ctime; /* Время последнего изменения статуса файла */

Некоторые пояснения. Комбинация значений (st_dev, st_ino) должна однозначно определять файл в пределах объединенной (в том числе сетевой) файловой системы. Статус файла меняется, когда модифицируются его атрибуты (например, режим), а не содержимое.

В файле   <sys/stat.h> определена не только структура stat, но и константы, полезные для работы с битами режима. Так, S_IFMT выделяет тип файла, S_IFREG обозначает обычные файлы, S_IRWXU - биты режима доступа владельцаи т.д.

Приведем пример использования функций stat() и lstat() (см. пример 4.15).

/* Программа выдает информацию о файлах - аргументах командной строки */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h>



Описание функций fstatvfs() и statvfs().

Утилита df выдает на стандартный вывод информацию об имеющемся пространстве и, возможно, некоторые другие данные. Если файлы-аргументы не указаны, поступают сообщения обо всех компонентах объединенной файловой системы.

По умолчанию пространство измеряется в 512-байтных блоках; опция -k предписывает использовать блоки размером в килобайт. Подразумеваемый формат вывода не специфицирован, но должны становиться известными по крайней мере имя файловой системы и объем свободного пространства. Если задана опция -P, употребляется так называемый мобильный формат вывода с заголовком

Filesystem 512-blocks Used Available Capacity Mounted on

и следующей построчной информацией о каждой файловой системе:

имя файловой системы;общее пространство;

занятое пространство;

свободное пространство;

процент занятости;корень файловой системы (точка монтирования в объединенной файловой системе).

Возможный результат выполнения служебной программы df с опциями -kP приведен в пример 4.18.

Filesystem 1024-blocks Used Available Capacity Mounted on /dev/sda3 75822432 43456504 28514348 61% / /dev/sda1 46636 21129 23099 48% /boot none 257212 0 257212 0% /dev/shm /dev/fd0 1424 392 1032 28% /a

Листинг 4.18. Возможный результат выполнения командной строки df -kP.

Пара функций statvfs() и fstatvfs() по интерфейсу аналогична функциям stat() и fstat(), только в выходную структуру (типа fstatvfs) помещается информация не о файлах, а о файловых системах, содержащих эти файлы. Согласно стандарту POSIX-2001, в структуре statvfs должны присутствовать по крайней мере следующие поля:

unsigned long f_bsize; /* Размер блока файловой системы */ unsigned long f_frsize; /* Базовый размер блока файловой системы */ fsblkcnt_t f_blocks; /* Общее число блоков базового размера в файловой системе */ fsblkcnt_t f_bfree; /* Общее число свободных блоков */ fsblkcnt_t f_bavail; /* Число свободных блоков, доступных непривилегированным процессам */ fsfilcnt_t f_files; /* Общее число описателей файлов */ fsfilcnt_t f_ffree; /* Общее число свободных описателей файлов */ fsfilcnt_t f_favail; /* Число описателей файлов, доступных непривилегированным процессам */ unsigned long f_fsid; /* Идентификатор файловой системы */ unsigned long f_flag; /* Битная шкала флагов */ unsigned long f_namemax; /* Максимальная длина имени файла */



statvfs-информация о файловой системе, содержащей файл /a: Размер блока файловой системы: 512 Базовый размер блока файловой системы: 512 Общее число блоков базового размера в файловой системе: 2847 Общее число свободных блоков: 1960 Число свободных блоков, доступных непривилегированным процессам: 1960 Общее число описателей файлов: 0 Общее число свободных описателей файлов: 0 Число описателей файлов, доступных непривилегированным процессам: 0 Идентификатор файловой системы: 0 Битная шкала флагов: f Максимальная длина имени файла: 260

Листинг 4.20. Возможный результат работы программы, использующей функцию statvfs().

Интерпретация полученных результатов предоставляется читателю.

Служебная программа

du [опция ...] [файл ...]

выдает информацию о суммарном объеме пространства (измеряемого аналогично df), занятого иерархиями файлов с указанными в командной строке корнями, предваряя ее аналогичными сведениями для каждого из подкаталогов, входящих в иерархии. При отсутствии аргументов выводятся сведения о текущем каталоге. Файлы, на которые есть несколько жестких ссылок, учитываются только один раз. Символьные ссылки трактуются по сути так же, как и в служебной программе ls, включая смысл опций -H и -L.

Выделим несколько других опций.

КлючОписание
-aВ дополнение к подразумеваемому выводу сообщать размеры файлов, входящих в обрабатываемые иерархии и не являющихся каталогами.
-sВместо подразумеваемого вывода информировать только о суммарном объеме занятого пространства для каждой заданной в командной строке иерархии.
Пример. В ответ на команду

du -k /usr/local/man /bin/vi /bin/view

может быть выдано следующее (см. пример 4.21):

1428 /usr/local/man/man1 12 /usr/local/man/man5 64 /usr/local/man/man7 36 /usr/local/man/man8 1544 /usr/local/man 384 /bin/vi 0 /bin/view

Листинг 4.21. Возможный результат использования утилиты du.

Таким образом, иерархия файлов с корнем в /usr/local/man занимает около 1.5 Мб (почти все приходится на долю файлов подкаталога man1), обычный файл   /bin/vi - 384 Кб (читателю предлагается сопоставить эту величину и приведенный выше размер данного файла в байтах), символьная ссылка лишнего места не занимает.


Описание функции


#include <unistd.h> char *getcwd (char *buf, size_t size);
Листинг 4.1. Описание функции getcwd().
Закрыть окно




#include <stdlib.h> #include <unistd.h> #include <stdio.h> int main (void) { size_t size; char *buf; char *apath; /* Выясним, каким должен быть размер буфера */ /* для абсолютного маршрутного имени текущего каталога */ size = (size_t) pathconf (".", _PC_PATH_MAX); if ((buf = (char *) malloc (size)) == NULL) { fprintf (stderr, "\nНе удалось выделить буфер размера %d\n", size); return 1; } if ((apath = getcwd (buf, size)) == NULL) { fprintf (stderr, "\nНе удалось определить абсолютное маршрутное имя текущего каталога\n"); return 1; } printf ("\nАбсолютное маршрутное имя текущего каталога: %s\n", apath); return 0; }
Листинг 4.2. Пример использования функции getcwd().
Закрыть окно




total 338 drwxrwxr-x 2 root root 4096 Jul 6 13:31 a drwxr-xr-x 2 root root 4096 Jul 8 13:32 bin drwxr-xr-x 4 root root 1024 Jul 6 15:06 boot drwxrwxr-x 2 root root 4096 Jul 6 13:31 cdrom drwxr-xr-x 18 root root 86016 Sep 2 10:42 dev drwxr-xr-x 6 root root 4096 Feb 23 2003 dss drwxr-xr-x 60 root root 4096 Sep 2 10:42 etc drwxr-xr-x 20 root root 4096 Jul 15 18:00 home drwxr-xr-x 2 root root 4096 Jun 12 2001 initrd drwxr-xr-x 8 root root 4096 Aug 8 13:16 lib drwx------ 2 root root 16384 Jul 6 14:15 lost+found drwxr-xr-x 2 root root 4096 Apr 1 2002 misc drwxr-xr-x 5 root root 4096 Jul 6 11:24 mnt drwxr-xr-x 7 root root 4096 Jul 23 13:09 opt dr-xr-xr-x 84 root root 0 Sep 2 2003 proc drwxr-x--- 5 root root 4096 Sep 1 17:10 root drwxr-xr-x 2 root root 4096 Jul 11 17:38 sbin drwxrwxrwt 23 root root 8192 Sep 2 12:43 tmp drwxr-xr-x 14 root root 4096 Jul 15 12:50 usr drwxr-xr-x 16 root root 4096 Jul 9 16:22 var
Пример 4.3. Возможный результат использования служебной программы ls
Закрыть окно




cd /usr/share/zoneinfo/posix ls -il UTC Universal Zulu
Листинг 4.4. Еще один пример использования служебной программы ls.
Закрыть окно




2506763 -rw-r--r-- 6 root root 56 Apr 15 2002 UTC 2506763 -rw-r--r-- 6 root root 56 Apr 15 2002 Universal 2506763 -rw-r--r-- 6 root root 56 Apr 15 2002 Zulu
Листинг 4.5. Возможный результат использования служебной программы ls.
Закрыть окно




cd /bin ls - il ex rvi rview vi view
Листинг 4.6. Применение служебной программы ls для выдачи информации о символьных ссылках.
Закрыть окно




4849722 lrwxrwxrwx 1 root root 2 Jul 6 14:17 ex -> vi 4849723 lrwxrwxrwx 1 root root 2 Jul 6 14:17 rvi -> vi 4849724 lrwxrwxrwx 1 root root 2 Jul 6 14:17 rview -> vi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 vi 4849726 lrwxrwxrwx 1 root root 2 Jul 6 14:17 view -> vi
Листинг 4.7. Возможный результат использования служебной программы ls при выдаче информации о символьных ссылках.
Закрыть окно




4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 ex 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 rvi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 rview 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 vi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 view
Листинг 4.8. Возможный результат использования служебной программы ls при выдаче информации о символьных ссылках и наличии опции -L.
Закрыть окно




ls -l /dev/ttyp[0-3] /dev/fd[2-5]
Листинг 4.9. Пример использования служебной программы ls для выдачи информации о специальных файлах.
Закрыть окно




brw-rw---- 1 root floppy 2, 2 Apr 1 2002 /dev/fd2 brw-rw---- 1 root floppy 2, 3 Apr 1 2002 /dev/fd3 brw-rw---- 1 root floppy 2, 128 Apr 1 2002 /dev/fd4 brw-rw---- 1 root floppy 2, 129 Apr 1 2002 /dev/fd5 crw-rw-rw- 1 root root 3, 0 Sep 2 17:21 /dev/ttyp0 crw-rw-rw- 1 root tty 3, 1 Apr 1 2002 /dev/ttyp1 crw-rw-rw- 1 root tty 3, 2 Apr 1 2002 /dev/ttyp2 crw-rw-rw- 1 root tty 3, 3 Apr 1 2002 /dev/ttyp3
Листинг 4.10. Возможный результат использования служебной программы ls при выдаче информации о специальных файлах.
Закрыть окно




total 0 prw--w---- 1 root root 0 Sep 2 10:42 xdmctl prw--w---- 1 shmyrev root 0 Sep 2 10:42 xdmctl-:0
Листинг 4.11. Возможный результат использования служебной программы ls при выдаче информации о каналах.
Закрыть окно




srwx------ 1 shmyrev root 0 Sep 2 10:42 /dev/gpmctl srw-rw-rw- 1 root root 0 Sep 2 10:42 /dev/log
Листинг 4.12. Возможный результат использования служебной программы ls при выдаче информации о сокетах.
Закрыть окно




total 338 drwxrwxrwt 23 root root 8192 Sep 2 16:26 tmp dr-xr-xr-x 143 root root 0 Sep 2 14:42 proc drwxr-xr-x 60 root root 4096 Sep 2 10:42 etc drwxr-xr-x 18 root root 86016 Sep 2 10:42 dev drwxr-x--- 5 root root 4096 Sep 1 17:10 root drwxr-xr-x 8 root root 4096 Aug 8 13:16 lib drwxr-xr-x 7 root root 4096 Jul 23 13:09 opt drwxr-xr-x 20 root root 4096 Jul 15 18:00 home drwxr-xr-x 14 root root 4096 Jul 15 12:50 usr drwxr-xr-x 2 root root 4096 Jul 11 17:38 sbin drwxr-xr-x 16 root root 4096 Jul 9 16:22 var drwxr-xr-x 2 root root 4096 Jul 8 13:32 bin drwxr-xr-x 4 root root 1024 Jul 6 15:06 boot drwx------ 2 root root 16384 Jul 6 14:15 lost+found drwxrwxr-x 2 root root 4096 Jul 6 13:31 cdrom drwxrwxr-x 2 root root 4096 Jul 6 13:31 a drwxr-xr-x 5 root root 4096 Jul 6 11:24 mnt drwxr-xr-x 6 root root 4096 Feb 23 2003 dss drwxr-xr-x 2 root root 4096 Apr 1 2002 misc drwxr-xr-x 2 root root 4096 Jun 12 2001 initrd
Листинг 4.13. Возможный результат использования служебной программы ls с сортировкой файлов по времени последнего изменения.
Закрыть окно




#include <sys/stat.h> int stat (const char * restrict path, struct stat *restrict buf); #include <sys/stat.h> int fstat (int fildes, struct stat *buf); #include <sys/stat.h> int lstat (const char *restrict path, struct stat *restrict buf);
Листинг 4.14. Описание функций семейства stat().
Закрыть окно




/* Программа выдает информацию о файлах - аргументах командной строки */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h>
/* Функция возвращает односимвольное обозначение типа файла */ /* Для неизвестного типа возвращается 'u' */ static char my_filetype (const mode_t mode) { switch (mode &S_IFMT) { case S_IFDIR: return ('d'); case S_IFBLK: return ('b'); case S_IFCHR: return ('c'); case S_IFLNK: return ('l'); case S_IFIFO: return ('p'); case S_IFREG: return ('-'); case S_IFSOCK: return ('s'); default: return ('u'); } } int main (int argc, char *argv[]) { struct stat buf; int i; for (i = 1; i < argc; i++) { if (stat (argv [i], &buf)) { fprintf (stderr, "\nstat: не удалось получить информацию о файле %s\n", argv [i]); return (-1); }
printf ("\nstat-информация о файле %s:\n", argv [i]); printf ("Тип: %c\n", my_filetype (buf.st_mode)); printf ("Размер: %ld\n", buf.st_size);
if (lstat (argv [i], &buf)) { fprintf (stderr, "\nlstat: не удалось получить информацию о файле %s\n", argv [i]); return (-1); }
printf ("\nlstat-информация о файле %s:\n", argv [i]); printf ("Тип: %c\n", my_filetype (buf.st_mode)); printf ("Размер: %ld\n", buf.st_size); }
return 0; }
Листинг 4.15. Пример использования функций stat() и lstat().
Закрыть окно




stat-информация о файле /bin/view: Тип: - Размер: 386120 lstat-информация о файле /bin/view: Тип: l Размер: 2 stat-информация о файле /bin/vi: Тип: - Размер: 386120 lstat-информация о файле /bin/vi: Тип: - Размер: 386120
Листинг 4.16. Возможный результат работы программы, использующей функции stat() и lstat().
Закрыть окно




#include <sys/statvfs.h> int fstatvfs ( int fildes, struct statvfs *buf); int statvfs (const char *restrict path, struct statvfs *restrict buf);
Листинг 4.17. Описание функций fstatvfs() и statvfs().
Закрыть окно




Filesystem 1024- blocks Used Available Capacity Mounted on /dev/sda3 75822432 43456504 28514348 61% / /dev/sda1 46636 21129 23099 48% /boot none 257212 0 257212 0% /dev/shm /dev/fd0 1424 392 1032 28% /a
Листинг 4.18. Возможный результат выполнения командной строки df -kP.
Закрыть окно




#include <sys/statvfs.h> #include <stdio.h>
int main (int argc, char *argv[]) { struct statvfs buf; int i;
for (i = 1; i < argc; i++) { if (statvfs (argv [i], &buf)) { fprintf (stderr, "\nstatvfs: не удалось получить информацию о файловой системе, содержащей файл %s\n", argv [i]); return (-1); }
printf ("\nstatvfs-информация о файловой системе, содержащей файл %s:\n", argv [i]); printf ("Размер блока файловой системы: %ld\n", buf.f_bsize); printf ("Базовый размер блока файловой системы: %ld\n", buf.f_frsize); printf ("Общее число блоков базового размера в файловой системе: %ld\n", buf.f_blocks); printf ("Общее число свободных блоков: %ld\n", buf.f_bfree); printf ("Число свободных блоков, доступных непривилегированным процессам: %ld\n", buf.f_bavail); printf ("Общее число описателей файлов: %ld\n", buf.f_files); printf ("Общее число свободных описателей файлов: %ld\n", buf.f_ffree); printf ("Число описателей файлов, доступных непривилегированным процессам: %ld\n", buf.f_favail); printf ("Идентификатор файловой системы: %ld\n", buf.f_fsid); printf ("Битная шкала флагов: %lx\n", buf.f_flag); printf ("Максимальная длина имени файла: %ld\n", buf.f_namemax); }
return 0; }
Листинг 4.19. Пример использования функции statvfs().
Закрыть окно




statvfs-информация о файловой системе, содержащей файл /: Размер блока файловой системы: 4096 Базовый размер блока файловой системы: 4096 Общее число блоков базового размера в файловой системе: 18955608 Общее число свободных блоков: 7990010 Число свободных блоков, доступных непривилегированным процессам: 7027115 Общее число описателей файлов: 9633792 Общее число свободных описателей файлов: 8259049 Число описателей файлов, доступных непривилегированным процессам: 8259049 Идентификатор файловой системы: 0 Битная шкала флагов: 0 Максимальная длина имени файла: 255
statvfs-информация о файловой системе, содержащей файл /a: Размер блока файловой системы: 512 Базовый размер блока файловой системы: 512 Общее число блоков базового размера в файловой системе: 2847 Общее число свободных блоков: 1960 Число свободных блоков, доступных непривилегированным процессам: 1960 Общее число описателей файлов: 0 Общее число свободных описателей файлов: 0 Число описателей файлов, доступных непривилегированным процессам: 0 Идентификатор файловой системы: 0 Битная шкала флагов: f Максимальная длина имени файла: 260
Листинг 4.20. Возможный результат работы программы, использующей функцию statvfs().
Закрыть окно




1428 /usr/local/man/man1 12 /usr/local/man/man5 64 /usr/local/man/man7 36 /usr/local/man/man8 1544 /usr/local/man 384 /bin/vi 0 /bin/view
Листинг 4.21. Возможный результат использования утилиты du.
Закрыть окно




cd "$OLDPWD" && pwd
Листинг 4.22. Действия, выполняемые по команде cd -.
Закрыть окно




ls -dl /usr/tmp /var/tmp cd /usr/ tmp pwd pwd -P cd .. pwd cd - cd -P .. pwd
Пример 4.23. Пример выполнения команды cd с разными опциями
Закрыть окно




lrwxrwxrwx 1 root root 10 Jul 6 14:16 /usr/tmp -> ../var/tmp drwxrwxrwt 5 root root 4096 Sep 4 18:05 /var/tmp /usr/tmp /var/tmp /usr /usr/tmp /var
Пример 4.24. результат выполнения команд cd с разными опциями
Закрыть окно




#include <unistd.h> int chown (const char *path, uid_t owner, gid_t group); #include <unistd.h> int fchown ( int fildes, uid_t owner, gid_t group); #include <sys/stat.h> int chmod (const char *path, mode_t mode); #include <sys/stat.h> int fchmod (int fildes, mode_t mode);
Листинг 4.25. Описание функций chown(), fchown(), chmod() и fchmod().
Закрыть окно




chmod go-w,u+ x myfile
Листинг 4.26. Пример использования служебной программы chmod.
Закрыть окно




#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> /* Программа добавляет права на выполнение для файлов - аргументов командной строки */ int main (int argc, char *argv[]) { struct stat buf; int i;
for (i = 1; i < argc; i++) { if (stat (argv [i], &buf)) { fprintf (stderr, "\nstat: не удалось получить информацию о файле %s\n", argv [i]); return (-1); }
if (chmod (argv [i], buf.st_mode | S_IXUSR | S_IXGRP | S_IXOTH)) { fprintf (stderr, "\nchmod: не удалось изменить режим доступа к файлу %s\n", argv [i]); return (-1); } }
return 0; }
Листинг 4.27. использования функций stat() и chmod().
Закрыть окно




> /var/run/utmp touch /var/log/wtmp chown :utmp /var/run/utmp /var/log/wtmp chmod ug=rw,o=r /var/run/utmp /var/log/wtmp
Листинг 4.28. использования совместного утилит touch, chown и chmod.
Закрыть окно




start () { echo -n " Starting cupsd: " daemon cupsd RETVAL=$? echo [ $RETVAL = 0 ] && touch /var/lock/subsys/cups return $RETVAL }
Листинг 4.29. Пример использования утилиты touch для работы с файлами-замками при загрузке ОС Linux.
Закрыть окно




#include <fcntl.h> int creat (const char *path, mode_t mode);
Листинг 4.30. Описание функции creat().
Закрыть окно




#include <stdio.h> void perror (const char *s);
Листинг 4.31. Описание функции perror().
Закрыть окно




#include <fcntl.h> #include <errno.h> #include <stdio.h> #include <limits.h> /* Программа пытается создавать в текущем */ /* каталоге файлы с именами g1, g2, ..., */ /* пока эти попытки не закончатся неудачей */ int main (void) { int n = 0; char name [PATH_MAX]; do sprintf (name, "g%d", ++n); while (creat (name, (mode_t) 0) >= 0); perror ("CREAT failed"); fprintf (stderr, "errno = %d\n", errno); fprintf (stderr, "Неудача на файле номер %d\n", n); return 0; }
Листинг 4.32. Пример программы, использующей функции creat() и perror().
Закрыть окно




CREAT failed: Too many open files errno = 24 Неудача на файле номер 1022
Листинг 4.33. Возможный результат первого выполнения программы, использующей функции creat() и perror().
Закрыть окно




CREAT failed: Permission denied errno = 22 Неудача на файле номер 1
Листинг 4.34. Результат повторного выполнения программы, использующей функции creat() и perror().
Закрыть окно




df = creat ("/tmp/sample", S_IRWXU | S_IRWXG | S_IRWXO);
Листинг 4.35. Пример вызова функции creat().
Закрыть окно




mkdir -p work/tmp/save
Листинг 4.36. Пример использования служебной программы mkdir для создания цепочки каталогов.
Закрыть окно




#include <sys/stat.h> int mkfifo (const char *path, mode_t mode);
Листинг 4.37. Описание функции mkfifo().
Закрыть окно




#include <unistd.h> int link (const char *source_path, const char *target_path); #include <unistd.h> int symlink (const char *link_contents, const char *link_name);
Листинг 4.38. Описание функций link() и symlink().
Закрыть окно




if [ -n "$mver" ]; then ln -sf /lib/modules/$mver\ /lib/modules/default fi
Листинг 4.39. Использование утилиты ln для формирования содержимого символьной ссылки
Закрыть окно




#include <unistd.h> int unlink (const char *path); #include <unistd.h> int rmdir (const char *path); #include <stdio.h> int remove (const char *path);
Листинг 4.40. Описание функций unlink(), rmdir() и remove().
Закрыть окно




rm -p a/b
Листинг 4.41. Пример команды удаления цепочки каталогов.
Закрыть окно




#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <sys/stat.h> /* Программа выполняет обработку */ /* с осторожным замещением */ /* основного файла рабочим */ #define MAIN_FILE "/home/galat/garb/temp/mfile" #define OLD_FILE "/home/galat/garb/temp/ofile" #define WORK_FILE "/home/galat/garb/temp/wfile" int main (void) { /* Необходимые описания */ /* . . . */ int work_success = 1;
/* Выполнение операций над рабочим файлом */ /* . . . */
/* В случае неудачи выдадим диагностическое сообщение и удалим рабочий файл */ if (! work_success) { fprintf (stderr, "\nНеудачное завершение операций над рабочим файлом %s\n", WORK_FILE); unlink (WORK_FILE); return (-1); }
/* Установим режим доступа к рабочему файлу, */ /* подходящие для последующего использования */ /* Пусть, например, это будет доступ для всех */ /* только на чтение */ chmod (WORK_FILE, S_IRUSR | S_IRGRP | S_IROTH);
/* Удалим ранее сохраненную старую версию основного файла */ unlink (OLD_FILE);
/* Сохраним текущую версию основного файла */ if (link (MAIN_FILE, OLD_FILE)) { perror ("Не удалось сохранить текущую версию основного файла"); return (-1); }
/* Удалим текущую версию основного файла */ unlink (MAIN_FILE);
/* Сделаем рабочий файл основным */ if (link (WORK_FILE, MAIN_FILE)) { perror ("Не удалось сделать рабочий файл основным"); /* Восстановим основной файл */ link (OLD_FILE, MAIN_FILE); return (-1); }
/* Удалим рабочий файл */ unlink (WORK_FILE);
return 0; }
Листинг 4.42. Пример программы, использующей функции link() и unlink().
Закрыть окно




.: d1/ x y ./d1:
Листинг 4.43. Состояние текущего каталога перед перемещением файлов.
Закрыть окно




ls -RF mv x y d1 mv d1 d2 ls -RF
Листинг 4.44. Использование утилиты mv для перемещения файлов и файловых иерархий.
Закрыть окно




.: d2/ ./d2: x y
Листинг 4.45. Состояние текущего каталога после перемещения файлов.
Закрыть окно




cp -R d2 d1 cp -R d2 d1 ls -RF
Листинг 4.46. Применение утилиты cp для копирования файловых иерархий.
Закрыть окно




.: d1/ d2/ ./d1: d2/ x y ./d1/d2: x y ./d2: x y
Листинг 4.47. Результат использования утилиты cp для копирования файловых иерархий.
Закрыть окно




#include <stdio.h> int rename (const char *old_path, const char *new_path);
Листинг 4.48. Описание функции rename().
Закрыть окно




#include <stdlib.h> #include <stdio.h> # define OLD_DIR "d1/d2" #define NEW_DIR "d2" int main (void) { system ("rm -f " NEW_DIR "/*"); if (rename (OLD_DIR, NEW_DIR)) { perror ("RENAME failed"); return (-1); } system ("ls -RF"); return 0; }
Листинг 4.49. Пример использования функции rename() для переименования каталогов.
Закрыть окно




.: d1/ d2/ ./d1: x y ./d2: x y
Листинг 4.50. Состояние текущего каталога после переименования каталогов с помощью функции rename().
Закрыть окно




find . \( -size 0c -o -name \*.o -a -atime +30 \) -ok rm {} \;
Листинг 4.51. Еще один пример использования утилиты find.
Закрыть окно




find /mnt \( -nouser -o -nogroup \) -exec chown nobody:nobody {} \;
Листинг 4.52. Пример выявления и обработки файлов с неизвестными владельцем или владеющей группой.
Закрыть окно




find . - name skip -prune -o -print find . -print -name skip -prune
Листинг 4.53. Пример использования элементарного выражения -prune.
Закрыть окно




pax -w . mkdir new_dir pax -rw old_dir new_dir
Листинг 4.54. Пример использования служебной программы pax.
Закрыть окно



Создание, удаление, копирование и перемещение файлов


Стандарт POSIX-2001 не требует наличия утилиты для создания   обычных файлов. Они появляются по мере необходимости как побочный продукт многочисленных служебных программ (например, утилиты копирования), поэтому сама постановка задачи - создать файл "просто так" - является отчасти надуманной. В то же время, если новый файл все-таки нужен, полезно иметь в виду возможность перенаправления вывода пустой команды, которая имеется в языке shell.

При программировании на языке C для создания   обычных файлов можно воспользоваться функцией creat() (см. пример 4.30).

#include <fcntl.h> int creat (const char *path, mode_t mode);

Листинг 4.30. Описание функции creat(). (html, txt)

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

Если файл, который пытаются создать при помощи creat(), уже существует, он опустошается (размер становится равным 0), а режим доступа и владелец не изменяются.

В случае неудачи результат creat() равен -1, а внешней переменной errno присваивается код ошибки, позволяющий определить причину ее (ошибки) возникновения. Переменная errno, а также мнемоники для кодов ошибок определены в заголовочном файле   <errno.h>. Для формирования системного сообщения об ошибке можно воспользоваться функцией perror() (см. пример 4.31), которая, опираясь на значение errno, помещает в стандартный протокол описание последней ошибки.

#include <stdio.h> void perror (const char *s);

Листинг 4.31. Описание функции perror(). (html, txt)

Например, при первом выполнении программы, приведенной в пример 4.32, в стандартный протокол может быть выдан соответствующий результат (см. пример 4.33).

#include <fcntl.h> #include <errno.h> #include <stdio.h> #include <limits.h> /* Программа пытается создавать в текущем */ /* каталоге файлы с именами g1, g2, ..., */ /* пока эти попытки не закончатся неудачей */ int main (void) { int n = 0; char name [PATH_MAX]; do sprintf (name, "g%d", ++n); while (creat (name, (mode_t) 0) >= 0); perror ("CREAT failed"); fprintf (stderr, "errno = %d\n", errno); fprintf (stderr, "Неудача на файле номер %d\n", n); return 0; }


Листинг 4.32. Пример программы, использующей функции creat() и perror(). (html, txt)

CREAT failed: Too many open files errno = 24 Неудача на файле номер 1022

Листинг 4.33. Возможный результат первого выполнения программы, использующей функции creat() и perror(). (html, txt)

Результат второго запуска той же программы показан в пример 4.34.

CREAT failed: Permission denied errno = 22 Неудача на файле номер 1

Листинг 4.34. Результат повторного выполнения программы, использующей функции creat() и perror(). (html, txt)

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

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

Рассмотренный в примере стиль уведомления о неудачном завершении общий для большинства функций. Неудача определяется возвращением результата, невозможного в другом случае (почти всегда это -1 или пустой указатель NULL); код ошибки заносится в переменную errno. Разумеется, в реальных программах errno не выводят, а анализируют. В данном случае уместно было бы сравнивать errno с константами EACCES, EINVAL, EMFILE и т.д. (см. <errno.h>).

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

df = creat ("/tmp/sample", S_IRWXU | S_IRWXG | S_IRWXO);

Листинг 4.35. Пример вызова функции creat(). (html, txt)

Для создания (пустых) каталогов служит утилита

mkdir [-p] [-m режим_доступа] каталог ...



Листинг 4.32. Пример программы, использующей функции creat() и perror().

CREAT failed: Too many open files errno = 24 Неудача на файле номер 1022

Листинг 4.33. Возможный результат первого выполнения программы, использующей функции creat() и perror().

Результат второго запуска той же программы показан в пример 4.34.

CREAT failed: Permission denied errno = 22 Неудача на файле номер 1

Листинг 4.34. Результат повторного выполнения программы, использующей функции creat() и perror().

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

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

Рассмотренный в примере стиль уведомления о неудачном завершении общий для большинства функций. Неудача определяется возвращением результата, невозможного в другом случае (почти всегда это -1 или пустой указатель NULL); код ошибки заносится в переменную errno. Разумеется, в реальных программах errno не выводят, а анализируют. В данном случае уместно было бы сравнивать errno с константами EACCES, EINVAL, EMFILE и т.д. (см. <errno.h>).

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

df = creat ("/tmp/sample", S_IRWXU | S_IRWXG | S_IRWXO);

Листинг 4.35. Пример вызова функции creat().

Для создания (пустых) каталогов служит утилита

mkdir [-p] [-m режим_доступа] каталог ...

и функция mkdir()

#include <sys/stat.h> int mkdir (const char *path, mode_t mode);



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

Функция link() аналогична служебной программе ln в первой форме, без опций. Ее нормальным результатом служит 0; в случае ошибки возвращается -1. Важно отметить, что с точки зрения файловой системы образование нового элемента каталога и увеличение счетчика жестких ссылок на исходный файл осуществляется функцией link() как неделимое действие.

Утилита ln с опцией -s и функция symlink() создают новые символьные ссылки. Исходные файлы не обязаны существовать; соответствующие аргументы трактуются как цепочки символов и задают содержимое ссылок.

Опция -f позволяет замещать существующие элементы каталогов новыми (по умолчанию задание существующего файла в качестве целевого считается ошибкой).

Примером смены содержимого символьной ссылки посредством служебной программы ln с опциями -s и -f может служить фрагмент действий при загрузке ОС Linux, приведенный в пример 4.39.

if [ -n "$mver" ]; then ln -sf /lib/modules/$mver\ /lib/modules/default fi

Листинг 4.39. Использование утилиты ln для формирования содержимого символьной ссылки

Для удаления файлов служат утилиты

rm [-fiRr] файл ...

и

rmdir [-p] файл ...

а также функции unlink(), rmdir() и remove() (см. пример 4.40).

#include <unistd.h> int unlink (const char *path); #include <unistd.h> int rmdir (const char *path); #include <stdio.h> int remove (const char *path);

Листинг 4.40. Описание функций unlink(), rmdir() и remove().

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



В пример 4. 42 приведен пример программы, которая с помощью функций link() и unlink() осуществляет ответственную обработку файлов с сохранением копии текущей версии.

#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <sys/stat.h> /* Программа выполняет обработку */ /* с осторожным замещением */ /* основного файла рабочим */ #define MAIN_FILE "/home/galat/garb/temp/mfile" #define OLD_FILE "/home/galat/garb/temp/ofile" #define WORK_FILE "/home/galat/garb/temp/wfile" int main (void) { /* Необходимые описания */ /* . . . */ int work_success = 1;

/* Выполнение операций над рабочим файлом */ /* . . . */

/* В случае неудачи выдадим диагностическое сообщение и удалим рабочий файл */ if (! work_success) { fprintf (stderr, "\nНеудачное завершение операций над рабочим файлом %s\n", WORK_FILE); unlink (WORK_FILE); return (-1); }

/* Установим режим доступа к рабочему файлу, */ /* подходящие для последующего использования */ /* Пусть, например, это будет доступ для всех */ /* только на чтение */ chmod (WORK_FILE, S_IRUSR | S_IRGRP | S_IROTH);

/* Удалим ранее сохраненную старую версию основного файла */ unlink (OLD_FILE);

/* Сохраним текущую версию основного файла */ if (link (MAIN_FILE, OLD_FILE)) { perror ("Не удалось сохранить текущую версию основного файла"); return (-1); }

/* Удалим текущую версию основного файла */ unlink (MAIN_FILE);

/* Сделаем рабочий файл основным */ if (link (WORK_FILE, MAIN_FILE)) { perror ("Не удалось сделать рабочий файл основным"); /* Восстановим основной файл */ link (OLD_FILE, MAIN_FILE); return (-1); }

/* Удалим рабочий файл */ unlink (WORK_FILE);

return 0; }

Листинг 4.42. Пример программы, использующей функции link() и unlink().

Выше мы отмечали, что, как правило, файлы создаются по мере необходимости при выполнении определенных операций. Одной из таких операций является копирование файлов, выполняемое служебной программой cp:

cp [-fip] исходный_файл целевой_файл cp [-fip] исходный_файл ...


На самом деле утилита mv, как правило, применяется для переименования файлов, и тогда ее работа сводится к созданию новых элементов каталогов и уничтожению старых, а реальное копирование может потребоваться только при перемещении файлов между файловыми системами (например, со съемного носителя на постоянный).

По опции -i   запрашивается подтверждение перед замещением существующего файла, опция -f влечет отсутствие подобных запросов (по поводу логики запроса подтверждений см. выше описание команды rm).

Приведем пример употребления утилиты mv. Пусть текущий каталог содержит только файлы   x и y и пустой каталог   d1 (см. пример 4.43). Тогда после выполнения команд, показанных в пример 4.44, будет создан каталог   d2, где окажутся файлы   x и y, а каталог   d1 исчезнет (см. пример 4.45).

.: d1/ x y ./d1:

Листинг 4.43. Состояние текущего каталога перед перемещением файлов.

ls -RF mv x y d1 mv d1 d2 ls -RF

Листинг 4.44. Использование утилиты mv для перемещения файлов и файловых иерархий.

.: d2/ ./d2: x y

Листинг 4.45. Состояние текущего каталога после перемещения файлов.

Продолжим этот пример двумя одинаковыми командами копирования (см. пример 4.46).

cp -R d2 d1 cp -R d2 d1 ls -RF

Листинг 4.46. Применение утилиты cp для копирования файловых иерархий.

Первая команда скопирует иерархию с корнем d2 во вновь созданный каталог   d1, вторая - под d1 (с сохранением имени d2 для корня копии). Результат показан в пример 4.47.

.: d1/ d2/ ./d1: d2/ x y ./d1/d2: x y ./d2: x y

Листинг 4.47. Результат использования утилиты cp для копирования файловых иерархий.

Для перемещения (переименования) одного файла служит функция rename() из репертуара C99 (см. пример 4.48).

#include <stdio.h> int rename (const char *old_path, const char *new_path);

Листинг 4.48. Описание функции rename().

Функция rename() обладает частью функциональности служебной программы mv. Она не перемещает файловых иерархий и не раскрывает символьных ссылок, если их имена заданы в качестве аргументов.

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


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


Основные понятия


В стандарте POSIX-2001 предусмотрены две основные группы функций, обслуживающие операции ввода/вывода:

функции нижнего уровня, использующие упоминавшиеся ранее целочисленные файловые дескрипторы (эти функции и ассоциирован- ные объекты определены в заголовочном файле <fcntl.h>);функции более высокого уровня, осуществляющие буферизованный ввод/вывод с применением потоков (см. <stdio.h>).

Применение потоков не расширяет базовых возможностей ввода/вывода, поскольку в конечном счете используются функции нижнего уровня, но в отличие от них позволяет приложениям получить дополнительный сервис в виде управляемой буферизации и форматного ввода/вывода.

Согласно стандарту POSIX-2001, поток – это объект, служащий для доступа к файлам как к упорядоченной последовательности символов.

Поток представляется структурой типа FILE, с которой ассоциирован соответствующий дескриптор открытого файла. Несколько дескрипторов и/или потоков могут ссылаться на одно описание открытого файла.

Существенны не только содержимое, но и адрес объекта типа FILE; копия подобного объекта не обязательно является корректным представлением потока.

И файловые дескрипторы, и потоки формируются в результате выполнения функций открытия файлов, которые должны предшествовать операциям ввода/вывода. Имеется, однако, три предопределенных потока: стандартный ввод, стандартный вывод и стандартный протокол, открываемые окружением времени выполнения еще перед началом работы C-программ. Для обращения к ним служат указатели на объекты типа FILE с именами, соответственно, stdin, stdout и stderr.

При открытии файлов указывается вид последующих операций ввода/вывода: чтение, запись, модификация (чтение и запись), добавление (запись в конец). Вид операций должен быть согласован с правами доступа к файлу; в противном случае открытие закончится неудачей.

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


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

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

Стандартом C99 [5] предусмотрены байтные и широкие символы. Соответственно, в стандарте POSIX-2001 введено понятие ориентации потока, которая может быть широкой или байтной. Задает ориентацию первая после открытия файла   ввода/вывода операция. Если вначале применяется функция ввода/вывода широких символов, поток получает широкую ориентацию; в противном случае – байтную. Сменить ориентацию можно только повторным открытием файла; применяемые к потоку функции ввода/вывода должны соответствовать его ориентации.

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

Стандарт POSIX-2001 предусматривает как синхронный, так и асинхронный ввод/вывод.

Операция асинхронного ввода/вывода сама по себе не способна привести к приостановке процесса (потока управления) и лишить его возможности использовать процессор. Это означает, что процесс и асинхронная операция ввода/вывода могут выполняться параллельно.

При синхронном вводе/выводе процесс (поток управления) приостанавливается до завершения запрошенной операции обмена данными.

С одним файлом могут параллельно работать несколько процессов, выполняющихся на разных хостах (узлах сети) и использующих разные буфера. Для корректного обслуживания подобной ситуации в стандарте POSIX-2001 определено (в качестве необязательной возможности) понятие синхронизированного ввода/вывода – так называют механизм, повышающий детерминированность и устойчивость средств обмена данными, поэтому приложение может быть уверено, что данные, которыми оно манипулирует, физически присутствуют на устройствах вторичной (массовой, стабильной) памяти и наряду с файлами находятся в целостном состоянии.



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

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

Стандартом C99 [5] предусмотрены байтные и широкие символы. Соответственно, в стандарте POSIX-2001 введено понятие ориентации потока, которая может быть широкой или байтной. Задает ориентацию первая после открытия файла   ввода/вывода операция. Если вначале применяется функция ввода/вывода широких символов, поток получает широкую ориентацию; в противном случае – байтную. Сменить ориентацию можно только повторным открытием файла; применяемые к потоку функции ввода/вывода должны соответствовать его ориентации.

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

Стандарт POSIX-2001 предусматривает как синхронный, так и асинхронный ввод/вывод.

Операция асинхронного ввода/вывода сама по себе не способна привести к приостановке процесса (потока управления) и лишить его возможности использовать процессор. Это означает, что процесс и асинхронная операция ввода/вывода могут выполняться параллельно.

При синхронном вводе/выводе процесс (поток управления) приостанавливается до завершения запрошенной операции обмена данными.

С одним файлом могут параллельно работать несколько процессов, выполняющихся на разных хостах (узлах сети) и использующих разные буфера. Для корректного обслуживания подобной ситуации в стандарте POSIX-2001 определено (в качестве необязательной возможности) понятие синхронизированного ввода/вывода – так называют механизм, повышающий детерминированность и устойчивость средств обмена данными, поэтому приложение может быть уверено, что данные, которыми оно манипулирует, физически присутствуют на устройствах вторичной (массовой, стабильной) памяти и наряду с файлами находятся в целостном состоянии.


Открытие и закрытие файлов


Как уже указывалось, открытие файла должно предшествовать операциям ввода/вывода, поскольку оно возвращает дескриптор файла или поток, которые используют подобные операции. Для открытия файлов и формирования новых описаний открытых файлов, файловых дескрипторов и потоков служат функции нижнего уровня open() и pipe() (см. пример 5.1), а также функции буферизованного ввода/вывода, показанные в пример 5.2.

#include <fcntl.h> int open (const char *path, int oflag, ...); #include <unistd.h> int pipe (int fildes [2]);

Листинг 5.1. Описание функций open() и pipe(). (html, txt)

#include <stdio.h> FILE *fopen (const char *restrict path, const char *restrict mode); #include <stdio.h> FILE *fdopen (int fildes, const char *mode); #include <stdio.h> FILE *freopen (const char *restrict path, const char *restrict mode, FILE *restrict stream);

Листинг 5.2. Описание функций fopen(), fdopen(), freopen(). (html, txt)

Функция open() открывает файл с заданным маршрутным именем (первый аргумент, path), создавая для него описание – новое и, следовательно, не разделяемое с другими процессами. Возвращаемый в качестве результата файловый дескриптор является минимальным из числа не используемых в данный момент текущим процессом (при неудаче возвращается -1).

Второй аргумент, oflag, устанавливает флаги статуса файла и определяет допустимые виды операций ввода/вывода. Его значение формируется как побитное ИЛИ перечисленных ниже флагов. Из первых трех флагов должен быть задан ровно один.

O_RDONLY

Открыть файл только на чтение.

O_WRONLY

Открыть файл только на запись.

O_RDWR

Открыть файл на чтение и запись.

Следующие флаги могут комбинироваться произвольным образом.

O_APPEND

Перед каждой записью устанавливать индикатор текущей позиции на конец файла.

O_CREAT

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

O_EXCL

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

O_TRUNC

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

Отметим, что рассмотренная ранее функция creat (path, mode) по определению эквивалентна вызову open (path, O_WRONLY | O_CREAT | O_TRUNC, mode).

Следующий флаг относится к асинхронному вводу/выводу.

O_NONBLOCK

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

При открытии специального файла вызов open() завершается только после того, как устройство оказывается в состоянии готовности. Флаг O_NONBLOCK отменяет эту задержку, однако последующее поведе- ние устройства зависит от реализации.

Следующая группа флагов обслуживает синхронизированный ввод/вывод.

O_DSYNC

Операции записи с возвращаемым файловым дескриптором должны завершаться с обеспечением целостности данных.

O_SYNC

Операции записи должны завершаться с обеспечением целостности файла.

O_RSYNC

Операции чтения должны завершаться на уровне целостности, заданном флагами O_DSYNC или O_SYNC.

Смысл последнего из стандартизованных флагов будет пояснен при рассмотрении процессов.

O_NOCTTY

Если открывается специальный файл, соответствующий терминалу, последний не должен становиться управляющим терминалом процесса. Для создания и открытия канала предназначена функция pipe().В массиве fildes она возвращает сразу два дескриптора: fildes [0]служит для чтения, fildes [1] – для записи. Данные читаются в том же порядке, в каком были записаны.

При успешном завершении pipe() возвращает 0, при неудаче – -1.



В массиве fildes она возвращает сразу два дескриптора: fildes [0]служит для чтения, fildes [1] – для записи. Данные читаются в том же порядке, в каком были записаны.

При успешном завершении pipe() возвращает 0, при неудаче – -1.

Функция fopen() из группы буферизованного ввода/вывода по сути аналогична open(), только вместо файлового дескриптора в качестве результата возвращается указатель на объект, служащий для управления сформированным потоком (в случае неудачи результат равен пустому указателю NULL).

Второй аргумент, mode, определяющий допустимые виды операций ввода/вывода, задается как цепочка символов. Он может принимать следующие значения.

"r"

Открыть файл на чтение.

"w"

Опустошить или создать файл, открыв его на запись.

"a"

Открыть или создать файл на запись в конец.

"r+"

Открыть файл на изменение (чтение и запись).

"w+"

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

"a+"

Открыть или создать файл на изменение с записью в конец.

(Стандарт языка C позволяет приписывать к перечисленным цепочкам символ 'b', который, впрочем, ни на что не влияет.)

Если открытый файл не соответствует интерактивному устройству, ассоциированный с ним поток полностью буферизуется.

Функция fdopen() формирует поток, ассоциированный с дескриптором ранее открытого файла.

Второй аргумент, mode, может принимать те же значения, что и для fopen(), но их трактовка по понятным причинам отличается: существующие файлы не опустошаются, а новые не создаются.

Функция freopen() предназначена для ассоциирования существующего потока (третий аргумент, stream) с заданным файлом (первый аргумент, path) и разрешенными видами операций ввода/вывода (второй аргумент, mode).

В первую очередь freopen() пытается вытолкнуть буфера потока stream и закрыть ассоциированный с ним файл. Неудача данного действия ни на что не влияет.

Затем, аналогично fopen(), открывается заданный файл, только без формирования нового потока; результатом служит stream (лишенный, правда, ориентации) или NULL (в случае неудачи).



Если в качестве первого аргумента функции freopen() задан пустой указатель, делается попытка изменить виды операций, разрешенные для потока   stream. (Формально можно считать, что первым аргументом freopen() служит имя файла, ассоциированного с этим потоком.) От реализации зависит, разрешены ли подобные действия вообще и при каких условиях они завершаются успешно.

Для закрытия файлов (точнее, файловых дескрипторов или потоков) предназначены функции close() и fclose() (см. пример 5.3).

#include <unistd.h> int close (int fildes); #include <stdio.h> int fclose (FILE *stream);

Листинг 5.3. Описание функций close() и fclose().

Функция close() освобождает файловый дескриптор fildes, который становится доступным для последующего использования при открытии файлов.

Когда закрывается последний дескриптор, ссылающийся на описание открытого файла, оно освобождается.

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

Когда закрывается последний дескриптор, ассоциированный с каналом, все оставшиеся непрочитанными данные теряются.

Функция close() возвращает 0 в случае успешного завершения и -1 при неудаче.

Функция fclose() по сути аналогична, только она освобождает поток, выталкивая при этом буфера. Признаком успешного завершения также служит 0, признаком неудачи – константа EOF.

Приведем примеры использования описанных функций. Сочетание флагов O_CREAT и O_EXCL функции open() позволяет организовать проверку и создание файлов-замков, для которых важен факт существования в одном экземпляре, а не содержимое (см. пример 5.4).

#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <errno.h> #define LOCK_FILE "my_lock" /* Функция пытается создать файл-замок */ /* Результат равен 0 в случае успеха, */ /* 1, если файл уже существует, */ /* -1 в случае прочих ошибок */ static int gate (const char *lock_name) { int fd; if ((fd = open (lock_name, O_WRONLY | O_CREAT | O_EXCL, (mode_t) 0)) < 0) { if (errno == EEXIST) { return (1); } return (-1); } return (close (fd)); } int main (void) { int res; if ((res = gate (LOCK_FILE)) > 0) { perror ("Ошибка при создании файла-замка " LOCK_FILE); } else if (res == 1) { fprintf (stderr, "Файл-замок " LOCK_FILE " уже существует\n"); } return (res); }



Листинг 5.4. Пример программы, использующей функции open() и close().

Читателю предлагается выполнить приведенную программу дважды.

Следующая программа иллюстрирует перенаправление стандартного вывода в файл (см. пример 5.5). Ее тоже полезно выполнить дважды и затем самостоятельно осмыслить результаты.

#include <stdio.h> #define LOGFILE "my_logfile" int main (void) { FILE *fp; printf ("До перенаправления стандартного вывода в файл " LOGFILE "\n"); if ((fp = freopen (LOGFILE, "a", stdout)) == NULL) { perror ("Не удалось перенаправить стандартный вывод в файл " LOGFILE); return (-1); } printf ("После перенаправления стандартного вывода в файл " LOGFILE "\n"); if (fclose (fp) == EOF) { perror ("Не удалось закрыть файл " LOGFILE); return (-1); } printf ("После закрытия файла " LOGFILE "\n"); return (0); }

Листинг 5.5. Перенаправление стандартного вывода с помощью функции freopen().

Весьма полезной с практической точки зрения является функция создания и открытия временных файлов tmpfile() (см. пример 5.6).

#include <stdio.h> FILE *tmpfile (void);

Листинг 5.6. Описание функции tmpfile().

Временный файл открывается на изменение (w+) и автоматически удаляется после закрытия всех ссылок на него.

Использование функции tmpfile() предпочтительнее генерации «временного» имени с помощью функции tmpnam() и последующего создания файла с этим именем, поскольку в промежутке какой-либо другой процесс может создать одноименный файл.


int open


#include <fcntl.h> int open (const char *path, int oflag, ...); #include <unistd.h> int pipe (int fildes [2]);
Листинг 5.1. Описание функций open() и pipe().
Закрыть окно




#include <stdio.h> FILE *fopen (const char * restrict path, const char *restrict mode); #include <stdio.h> FILE *fdopen (int fildes, const char *mode); #include <stdio.h> FILE *freopen (const char *restrict path, const char *restrict mode, FILE *restrict stream);
Листинг 5.2. Описание функций fopen(), fdopen(), freopen().
Закрыть окно




#include <unistd.h> int close (int fildes); #include <stdio.h> int fclose (FILE *stream);
Листинг 5.3. Описание функций close() и fclose().
Закрыть окно




#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <errno.h> #define LOCK_FILE "my_lock" /* Функция пытается создать файл-замок */ /* Результат равен 0 в случае успеха, */ /* 1, если файл уже существует, */ /* -1 в случае прочих ошибок */ static int gate (const char *lock_name) { int fd; if ((fd = open (lock_name, O_WRONLY | O_CREAT | O_EXCL, (mode_t) 0)) < 0) { if (errno == EEXIST) { return (1); } return (-1); } return (close (fd)); } int main (void) { int res; if ((res = gate (LOCK_FILE)) > 0) { perror ("Ошибка при создании файла-замка " LOCK_FILE); } else if (res == 1) { fprintf (stderr, "Файл-замок " LOCK_FILE " уже существует\n"); } return (res); }
Листинг 5.4. Пример программы, использующей функции open() и close().
Закрыть окно




#include <stdio.h> #define LOGFILE "my_logfile" int main (void) { FILE *fp; printf ("До перенаправления стандартного вывода в файл " LOGFILE "\n"); if ((fp = freopen (LOGFILE, "a", stdout)) == NULL) { perror (" Не удалось перенаправить стандартный вывод в файл " LOGFILE); return (-1); } printf ("После перенаправления стандартного вывода в файл " LOGFILE "\n"); if (fclose (fp) == EOF) { perror ("Не удалось закрыть файл " LOGFILE); return (-1); } printf ("После закрытия файла " LOGFILE "\n"); return (0); }
Листинг 5.5. Перенаправление стандартного вывода с помощью функции freopen().
Закрыть окно




#include <stdio.h> FILE *tmpfile (void);
Листинг 5.6. Описание функции tmpfile().
Закрыть окно




#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().
Закрыть окно




#include <stdio.h> int feof (FILE *stream); #include <stdio.h> int ferror (FILE *stream);
Листинг 5.8. Описание функций feof() и ferror().
Закрыть окно




#include <unistd.h> ssize_t readlink (const char * restrict link_name, char *restrict buf, size_t buf_size);
Листинг 5.9. Описание функции readlink().
Закрыть окно




#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. Пример чтения из файла.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * */ /* Подсчет символов, слов и строк в файле */ /* * * * * * * * * * * * * * * * * * * * * */ #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. Программа, подсчитывающая число строк, слов и символов в файле.
Закрыть окно




#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. Пример программы, читающей содержимое символьных ссылок.
Закрыть окно




#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().
Закрыть окно




#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().
Закрыть окно




#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().
Закрыть окно




#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().
Закрыть окно




#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().
Закрыть окно




#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().
Закрыть окно




#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().
Закрыть окно




#include <unistd.h> off_t lseek ( int fildes, off_t offset, int whence); #include <stdio.h> int fseek (FILE *stream, long offset, int whence); long ftell (FILE *stream); off_t ftello (FILE *stream); int fgetpos (FILE *restrict stream, fpos_t *restrict pos); int fsetpos (FILE *stream, const fpos_t *pos); void rewind (FILE *stream);
Листинг 5.20. Описание функций lseek(), fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind().
Закрыть окно




(void) lseek (fildes, (off_t) 0, SEEK_SET); (void) lseek (fildes, (off_t) 0, SEEK_END); (void) lseek (fildes, inc, SEEK_CUR);
Листинг 5.21. Примеры вызова функции lseek().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * */ /* Набор функций для занесения текстов в файл, */ /* который составляется из двух частей: */ /* таблицы длин и смещений текстов от начала */ /* файла собственно текстов */ /* */ /* В начало файла помещается специальный */ /* элемент таблицы с магическим числом и общим */ /* количеством текстов */ /* * * * * * * * * * * * * * * * * * * * * * * */ #include <stdio.h> #include <string.h> /* Магическое число файла с текстами */ #define G_TXT_MAGIC 0x1993 /* Элемент таблицы длин и смещений */ typedef struct { unsigned int l_txt; /* Длина текста (без */ /* (нулевого байта) */ unsigned long off_txt; /* Смещение текста от */ /* начала файла */ } txt_table_elem; static FILE *fp = NULL;/* Указатель на поток */ /* файла с текстами */ static unsigned long max_n_txt; /* Общее число */ /* текстов в файле */ /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция для инициализации набора. */ /* n_txts - максимальное число добавляемых текстов */ /* * * * * * * * * * * * * * * * * * * * * * * */ int g_init_add_txt (const int argc, char *argv [], const unsigned long n_txts) { char *path; /* Имя файла, куда нужно поместить тексты */ int magic; /* Магическое число файла с текстами */ txt_table_elem tte; unsigned int i; if (argc != 2) { fprintf (stderr, "Использование: %s файл_для_текстов\n", argv [0]); return (-1); } path = argv [1]; /* Аккуратно откроем файл с текстами */ /* Если он уже есть и в нем правильное */ /* магическое число, */ /* будем добавлять тексты. */ /* В противном случае создадим и */ /* инициализируем файл */ if (((fp = fopen (path, "r+")) != NULL) && (fread (&magic, sizeof (unsigned int), 1, fp) == 1) && (magic == G_TXT_MAGIC)) { /* Перед нами - наш файл */ /* Проверим, не превышает ли заказанная */ /* верхняя граница существующую */ if (fread (&max_n_txt, sizeof (unsigned long), 1, fp) != 1) { fprintf (stderr, "Не удается прочитать информацию из файла %s\n", path); return (-1); } if (n_txts > max_n_txt) { fprintf (stderr, "***** Новая верхняя граница числа сообщений %lu больше существующей %lu\n", n_txts, max_n_txt); } } else { /* Файла нет или он не наш */ (void) fclose (fp); if ((fp = fopen (path, "w+")) == NULL) { fprintf (stderr, "Не удается открыть файл %s\n", path); return (-1); } tte.l_txt = magic = G_TXT_MAGIC; tte.off_txt = max_n_txt = n_txts; if (fwrite (&tte, sizeof (txt_table_elem), 1, fp) != 1) { fprintf (stderr, "Не удается записать информацию в файл %s\n", path); return (-1); } /* Пропишем нулями индексную таблицу */ /* Заодно конец файла окажется в будущем */ /* начале текстов */ tte.l_txt = 0; tte.off_txt = 0; for (i = 0; i < max_n_txt; i++) { if (fwrite (&tte, sizeof (txt_table_elem), 1, fp) != 1) { fprintf (stderr, "Не удается записать информацию в файл %s\n", path); return (-1); } } } /* if - существует ли файл с текстами */ return 0; } /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция для добавления одного текста */ /* * * * * * * * * * * * * * * * * * * * * * * */ int g_add_txt (const unsigned long n_t, const char *txt) { unsigned int l; /* Длина текста txt */ txt_table_elem tte; if (n_t >= max_n_txt) { fprintf (stderr, "Номер текста: %lu должен быть меньше: %lu\n", n_t, max_n_txt); return (-1); } l = strlen (txt); tte.l_txt = l; if (fseek (fp, 0L, SEEK_END)) { fprintf (stderr, "Ошибка позиционирования при добавлении текста номер %lu\n", n_t); return (-1); } tte.off_txt = ftell (fp); if (fseek (fp, (n_t + 1) * sizeof (txt_table_elem), SEEK_SET)) { fprintf (stderr, "Ошибка позиционирования при добавлении текста номер %lu\n", n_t); return (-1); } if (fwrite (&tte, sizeof (tte), 1, fp) != 1) { fprintf (stderr, "Ошибка записи при добавлении текста номер %lu\n", n_t); return (-1); } if (fseek (fp, tte.off_txt, SEEK_SET)) { fprintf (stderr, "Ошибка позиционирования при добавлении текста номер %lu\n", n_t); return (-1); } if (fwrite (txt, sizeof (char), l, fp) != l) { fprintf (stderr, "Ошибка записи при добавлении текста номер %lu\n", n_t); return (-1); } return 0; } /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция для завершения добавления текстов */ /* * * * * * * * * * * * * * * * * * * * * * * */ int g_term_add_txt () { return (fclose (fp)); } /* * * * * * * * * * * * * * * * * * * * * * * */ /* Главная программа, вызывающая определенные */ /* выше функции */ /* * * * * * * * * * * * * * * * * * * * * * * */ #define MAX_TXTS 10240
int main (int argc, char *argv[]) { if (g_init_add_txt (argc, argv, MAX_TXTS) || g_add_txt (0, "Reference to section number %d in %s\n") || g_add_txt (1, "Data .init section in %s\n")) { (void) g_term_add_txt (); return (-1); } return (g_term_add_txt ()); }
Листинг 5.22. Пример использования функций буферизованного ввода/вывода.
Закрыть окно




#include <fcntl.h> int fcntl ( int fildes, int cmd, ...);
Листинг 5.23. Описание функции fcntl().
Закрыть окно




#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <assert.h> #define LOGFILE "my_logfile"
int main (void) { int fd; int flags; assert ((fd = open (LOGFILE, O_WRONLY | O_CREAT | O_APPEND, S_IRWXU)) > 2); printf ("До перенаправления стандартного вывода в файл " LOGFILE "\n"); close (1); assert (fcntl (fd, F_DUPFD, 1) == 1); close (fd); printf ("После перенаправления стандартного вывода в файл " LOGFILE "\n"); /* Добавим флаг обеспечения целостности файла */ /* при записи */ assert ((flags = fcntl (1, F_GETFL, 0)) != -1); assert (fcntl (1, F_SETFL, flags | O_SYNC) != -1); fprintf (stderr, "До перенаправления стандартного протокола на стандартный вывод\n"); close (2); assert (fcntl (1, F_DUPFD, 2) == 2); fprintf (stderr, "После перенаправления стандартного протокола на стандартный вывод\n"); close (1); close (2); return (0); }
Листинг 5.24. Пример перенаправления стандартного вывода в файл, а стандартного протокола – на стандартный вывод.
Закрыть окно




#include <fcntl.h> struct flock lck; . . . lck.l_type = F_RDLCK; /* Блокировка на чтение */ /* всего файла */ lck.l_whence = SEEK_SET; lck.l_start = 0; lck.l_len = 0;
Листинг 5.25. Примеры заполнения структуры flock.
Закрыть окно




if (fcntl (fd, F_SETLK, &lck) != -1) ... if (fcntl (fd, F_SETLKW, &lck) != -1) ...
Листинг 5.26. Примеры вызова функции 2 для установки блокировок.
Закрыть окно




#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <assert.h>
#define LOCKFILE "my_lockfile"
/* Программа устанавливает несколько блокировок */ /* на файл LOCKFILE */ int main (void) { int fd; struct flock lck; assert ((fd = open (LOCKFILE, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)) != -1); /* Установим блокировку на запись на весь файл */ lck.l_type = F_WRLCK; lck.l_whence = SEEK_SET; lck.l_start = (off_t) 0; lck.l_len = (off_t) 0; if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-1"); close (fd); return (-1); } /* Сделаем размер файла ненулевым */ if (lseek (fd, (off_t) 1024, SEEK_SET) == -1) { perror ("LSEEK"); close (fd); return (-1); } if (write (fd, &lck, sizeof (lck)) != sizeof (lck)) { perror ("WRITE"); close (fd); return (-1); } /* Снимем блокировку в середине файла */ lck.l_type = F_UNLCK; lck.l_whence = SEEK_SET; lck.l_start = (off_t) 512; lck.l_len = (off_t) sizeof (lck); if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-2"); close (fd); return (-1); } /* Установим блокировку на чтение в конце файла */ lck.l_type = F_RDLCK; lck.l_whence = SEEK_END; lck.l_start = (off_t) -sizeof (lck); lck.l_len = (off_t) sizeof (lck); if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-2"); close (fd); return (-1); } sleep (10); return (close (fd)); }
Листинг 5.27. Пример программы set_locks, устанавливающей блокировки файла.
Закрыть окно




#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <assert.h>
#define LOCKFILE "my_lockfile"
/* Программа выявляет блокировки, установленные */ /* на файл LOCKFILE */ int main (void) { int fd; struct flock lck; assert ((fd = open (LOCKFILE, O_WRONLY)) != -1); (void) printf ("ид-р проц. тип начало длина\n"); /* Начнем с попытки установить блокировку на */ /* весь файл */ lck.l_whence = SEEK_SET; lck.l_start = 0; lck.l_len = 0; do { lck.l_type = F_WRLCK; (void) fcntl (fd, F_GETLK, &lck); if (lck.l_type != F_UNLCK) { (void) printf ("%9d %3c %7ld %5ld\n", lck.l_pid, (lck.l_type == F_WRLCK) ? 'W' : 'R', lck.l_start, lck.l_len); /* Если эта блокировка покрывает остаток файла, */ /* нет нужды выявлять другие блокировки */ if (lck.l_len == 0) break; /* Иначе поищем новую блокировку после найденной */ lck.l_start += lck.l_len; } while (lck.l_type != F_UNLCK); return (close (fd)); }
Листинг 5.28. Пример программы test_locks, выявляющей блокировки файла.
Закрыть окно




ид- р проц. тип начало длина 31174 W 0 512 31174 W 528 496 31174 R 1024 16 31174 W 1040 0
Листинг 5.29. Возможный результат выполнения командной строки set_locks &amp; test_locks.
Закрыть окно




#include <stdio.h> void setbuf (FILE *restrict stream, char *restrict buf); #include <stdio.h> int setvbuf (FILE *restrict stream, char * restrict buf, int type, size_t size); #include <stdio.h> int fflush (FILE *stream);
Листинг 5.30. Описание функций setbuf(), setvbuf() и fflush().
Закрыть окно




char name [LINE_MAX]; (void) printf (" Введите Ваше имя: "); (void) fflush (stdout); (void) fgets (name, sizeof (name), stdin);
Листинг 5.31. Пример использования функции fflush().
Закрыть окно



Управляющие операции с файлами и ассоциированными данными


К числу управляющих операций с файлами мы отнесем прежде всего позиционирование. Индикатор текущей позиции может быть опрошен или передвинут при помощи функции нижнего уровня lseek(), а также функций буферизованного ввода/вывода   fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind() (см. пример 5.20).

#include <unistd.h> off_t lseek (int fildes, off_t offset, int whence); #include <stdio.h> int fseek (FILE *stream, long offset, int whence); long ftell (FILE *stream); off_t ftello (FILE *stream); int fgetpos (FILE *restrict stream, fpos_t *restrict pos); int fsetpos (FILE *stream, const fpos_t *pos); void rewind (FILE *stream);

Листинг 5.20. Описание функций lseek(), fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind(). (html, txt)

Функция lseek() устанавливает индикатор текущей позиции следующим образом. Сначала, в зависимости от значения третьего аргумента, whence, выбирается точка отсчета: 0, если это значение равно SEEK_SET, текущая позиция для SEEK_CUR и размер файла для SEEK_END. Затем к точке отсчета прибавляется смещение offset (второй аргумент).

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

Результатом функции lseek() служит новое значение индикатора текущей позиции, отсчитанное в байтах от начала файла. В случае ошибки возвращается (off_t) (-1), а текущая позиция остается прежней.

Функция fseek() по сути аналогична, только в случае нормального завершения возвращается 0. Кроме того, если поток имеет широкую ориентацию, значение аргумента whence должно равняться SEEK_SET, а значение offset – нулю или результату вызова функции ftell() для того же потока.

Функция ftell() возвращает значение индикатора текущей позиции для заданного потока (в случае ошибки – (long) (-1)). Функция ftello() эквивалентна ftell() с точностью до типа результата; в новых приложениях рекомендуется использовать ftello(), так как эта функция применима к большим файлам.


Функции fgetpos() и fsetpos() являются парными. Первая из них заполняет структуру, на которую указывает аргумент pos, а вторая применяет ее для установки индикатора текущей позиции. Нормальным результатом служит 0.

Функция rewind(), если пренебречь некоторыми тонкостями, сводится к вызову

(void) fseek (stream, 0L, SEEK_SET).

Приведем несколько примеров. В пример 5.21 показана установка индикатора текущей позиции в начало и конец файла, а также его (индикатора) приращение.

(void) lseek (fildes, (off_t) 0, SEEK_SET); (void) lseek (fildes, (off_t) 0, SEEK_END); (void) lseek (fildes, inc, SEEK_CUR);

Листинг 5.21. Примеры вызова функции lseek(). (html, txt)

Применение функций буферизованного ввода/вывода иллюстрируется программой, показанной в пример 5.22. Отметим, что она не является мобильной относительно смены порядка байт, поддерживаемого процессором.

Листинг 5.22. Пример использования функций буферизованного ввода/вывода. (html, txt)

Функция fcntl() предназначена для выполнения разнообразных управляющих операций над открытым файлом (см. пример 5.23).

#include <fcntl.h> int fcntl (int fildes, int cmd, ...);

Листинг 5.23. Описание функции fcntl(). (html, txt)

Аргумент fildes задает дескриптор открытого файла, cmd – управляющую команду, дополнительные данные для которой могут быть переданы в качестве третьего (необязательного) аргумента arg (обычно имеющего тип int). В случае успешного завершения возвращаемое значение естественным образом зависит от команды; при неудаче всегда возвращается -1.

Допустимые команды (значения аргумента cmd) определены в файле <fcntl.h>. Перечислим сначала наиболее употребительные из них.

F_DUPFD

Дублирование дескриптора открытого файла: вернуть минимальный среди бывших незанятыми файловый дескриптор (не меньший, чем arg) и ассоциировать его с тем же описанием открытого файла, что и fildes.

F_GETFL

Вернуть флаги статуса и режим доступа к файлу (см. выше описание функции open()). Для выделения режима доступа из возвращаемого значения предоставляется маска O_ACCMODE.



© 2003-2007 INTUIT.ru. Все права защищены.