В четвертом издании популярного руководства даны основы программирования в операционной системе Linux. Рассмотрены: использование библиотек C/C++ и стан­дартных средств разработки, организация системных вызовов, файловый ввод/вывод, взаимодействие процессов, программирование средствами командной оболочки, создание графических пользовательских интерфейсов с помощью инструментальных средств GTK+ или Qt, применение сокетов и др. Описана компиляция программ, их компоновка c библиотеками и работа с терминальным вводом/выводом. Даны приемы написания приложений в средах GNOME® и KDE®, хранения данных с использованием СУБД MySQL® и отладки программ. Книга хорошо структурирована, что делает обучение легким и быстрым. Для начинающих Linux-программистов

Нейл Мэтью

Ричард Стоунс

Основы программирования в Linux

4-е издание

Об авторах

Нейл Мэтью (Neil Matthew) интересуется компьютерами и пишет для них программы с 1974 г. Выпускник университета г. Ноттингема по специальности "Математика", Нейл по-настоящему увлекается языками программирования и любит искать новые пути решения компьютерных проблем. Им разработаны системы программирования на языках BCPL, FP (Functional programming), Lisp, Prolog и структурированном BASIC. Он даже написал эмулятор микропроцессора 6502 для выполнения в системах UNIX программ для микрокомпьютера ВВС.

Что касается опыта работы в UNIX, начиная с конца 1970 гг., Нейл испробовал все варианты, включая BSD UNIX, AT&T System V, Sun Solaris, IBM AIX, многие другие и, конечно, Linux. Он может утверждать, что занимается ОС Linux с августа 1993 г., когда обзавелся дистрибутивом из Канады Software Landing (SLS) на дискетах с версией ядра 0.99.11. Он применял компьютеры на базе Linux дома и на работе для осваивания языков С, С++, Icon, Prolog, Tcl и Java.

Все "домашние" проекты Нейла разработаны на базе Linux. Он утверждает, что Linux гораздо проще, поскольку поддерживает множество функциональных возможностей других систем, поэтому программы, предназначенные для систем BSD и System V, как правило, компилируются с небольшими изменениями или вообще без каких-либо изменений.

Сейчас Нейл работает в компании Celesio AG как архитектор ПО компании (Enterprise Architect), специализирующийся на разработке стратегии информационных технологий. У него есть профессиональный опыт технического консультирования, разработки программного обеспечения и контроля качества. Нейл также писал программы на языках С и С++ для встроенных систем реального времени.

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

Ричард Стоунс (Richard Stones) начал программировать в школе (раньше, чем он может вспомнить) на микрокомпьютере ВВС, оснащенном микропроцессором 6502, который с помощью нескольких запчастей продолжал функционировать следующие 15 лет. Он закончил университет г. Ноттингема по специальности "Электроника", но решил, что программное обеспечение увлекательнее.

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

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

Будучи отчасти лингвистом в программировании он писал программы на разных ассемблерах, на чистом, патентованном языке телекоммуникаций, названном SL-1, нескольких диалектах языка FORTRAN, языках Pascal, Perl, SQL и чуть-чуть на Python, С++ и С. (Под давлением он даже признался, что одно время считался не без оснований специалистом в Visual Basic, но старается не афишировать это временное помрачение рассудка.)

Рик живет в деревне графства Лестершир в Англии с женой Анной, детьми Дженнифер и Эндрю и кошкой. В свободное от работы время он увлекается классической музыкой, особенно старинной духовной музыкой, и фотографией и прилагает максимум усилий, чтобы найти время для игры на пианино. 

Благодарности

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

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

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

Мы хотели бы выразить признательность сотрудникам издательства Wiley, которые помогли подготовить это четвертое издание к печати. Спасибо Кэрол Лонг (Carol Long) за запуск этого процесса и улаживание проблем, связанных с контрактами, особая благодарность Cape Шлаер (Sara Shlaer) за исключительную редакторскую работу и Тимоти Борончику (Timothy Boronczyk) за отличные технические рецензии, Мы также хотим поблагодарить Дженни Ватсон (Jenny Watson) за поиск средств для оплаты неожиданно возникавших дополнительных расходов и сопровождение книги на всех административных уровнях, Биллу Бартону (Bill Barton) за обеспечение надлежащих организации и презентации и Киму Коферу (Kim Cofer) за тщательную корректуру. Мы также очень признательны Эрику Фостеру-Джонсону (Eric Foster-Johnson) за его фантастическую работу над главами 16 и 17. Мы можем сказать, что благодаря стараниям всех вас книга стала лучше.

Мы также хотели бы поблагодарить наших работодателей, компании Scientific Generics, Mobicom и Celesio за поддержку во время подготовки всех четырех изданий книги.

В заключение нам хотелось бы выразить глубокую признательность двум важнейшим инициаторам, сделавшим возможным появление книги. Во-первых, Ричарду Столлмену (Richard Stallman) за отличные средства проекта GNU и идею среды со свободно распространяемым программным обеспечением, ставшей в наши дни реальностью благодаря GNU/Linux, и во-вторых, Линусу Торвальдсу (Linus Torvalds) за начатую и продолженную им совместную разработку, которая дает нам все улучшающееся ядро системы Linux.

Предисловие

У всех программистов есть своя груда записей и черновиков. Они собирают собственные примеры текстов программ, накопившиеся за время героических погружений в многочисленные руководства или добытые из сети Usenet, в которой порой даже дураки боятся блуждать. (Другая точка зрения состоит в том, что у всех дураков свободный доступ к Usenet, и они используют ее безостановочно.) Поэтому довольно странно, что так мало книг выпущено в подобном стиле. В интерактивном мире существует множество коротких документов, касающихся конкретных проблем программирования и администрирования по существу. В рамках проекта по созданию документации Linux выпущено множество документов, посвященных самым разным темам, начиная с установки ОС Linux и Windows на одной машине и заканчивая написанием вашей виртуальной машины Java для Linux. На самом деле, загляните на Web-сайт Linux Documentation Project (проект документации Linux) по адресу http://www.tldp.org.

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

Данное издание книги было проверено и исправлено в соответствии с современным уровнем разработок в ОС Linux.

Алан Кокс (Alan Сох)

Введение

Рады предложить вам легкое в использовании руководство по разработке программ для Linux и других UNIX-подобных операционных систем.

В этой книге мы стремимся дать вам представление о разнообразных понятиях, важных для разработчика, применяющего систему Linux, так сказать, основы. Слово "основы" скорее относится к ее содержимому, чем к уровню подготовки читателя. Мы построили книгу так, чтобы, несмотря на уже приобретенный вами опыт, вы узнали больше о том, что может предложить Linux. Программирование в ОС Linux — обширная область, и мы стараемся представить достаточно сведений, касающихся различных тем, чтобы дать вам прочные "основы" для усвоения каждой из них.

Для кого эта книга?

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

Мы полагаем, что у вас есть некоторый опыт программирования на языках С и/или С++ в ОС Windows или какой-нибудь другой операционной системе, но мы старались сохранить простоту приведенных в книге примеров, чтобы для их понимания не требовалось слишком высокой квалификации в программировании на С. Все явные сопоставления методов программирования в Linux с приемами программирования на языках C/C++ отмечены в тексте книги.

Примечание

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

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

Чему посвящена книга?

У книги есть ряд задач, перечисленных далее.

□ Научить применению стандартных библиотек языка С в ОС Linux и других средств, описанных в разных стандартах Linux и UNIX.

□ Показать, как использовать большинство стандартных средств разработки Linux.

□ Дать краткий обзор способов хранения данных под управлением Linux с помощью СУБД DBM и MySQL.

□ Показать, как создавать графические интерфейсы пользователя на базе графической системы X Window System. Мы воспользуемся библиотеками GTK (основы графической среды GNOME) и Qt (основы графической среды KDE).

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

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

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

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

После этого мы обсудим управление данными. Знакомство с библиотекой базы данных dbm, к которой мы обратимся в нескольких последующих главах, — достаточное основание для переделки приложения, но на этот раз вместе с проектом. В следующей главе рассматривается хранение данных в реляционной базе данных средствами СУРБД MySQL и позже мы также повторно применим эти методы хранения данных, поэтому вы сможете сравнить разные способы управления данными. Размер новых версий приложения таков, что нам далее придется иметь дело с такими практическими задачами, как отладка, контроль исходного текста программы, распространение программного обеспечения и make-файлы.

Вы также узнаете, как могут взаимодействовать разные процессы в ОС Linux и как программы в Linux могут применять сокеты для поддержки сетевых соединений разных машин на базе протоколов TCP/IP, включая проблемы, касающиеся связи машин с процессорами разной архитектуры.

После изложения основ программирования в Linux мы обсуждаем создание программ в графическом режиме. Этому посвящены две главы, в которых сначала рассматривается комплект инструментальных средств GTK+, лежащий в основе графической среды GNOME, а затем комплект Qt, лежащий в основе графической среды KDE.

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

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

Что вам потребуется для использования книги?

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

Существуют варианты Linux для самых разных систем. Адаптируемость Linux такова, что предприимчивые люди заставляют ее работать в том или ином виде на любом оборудовании, имеющем процессор! Примеры включают системы на базе процессоров Alpha, ARM, IBM Cell, Itanium, PA-RISC, PowerPC, SPARC, SuperH и ЦП 68k, а также на базе различных процессоров класса х86 с 32- и 64-разрядными версиями.

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

Работая над книгой, мы сначала главным образом использовали системы на базе процессоров x86, хотя мало что из описанного в книге характерно только для х86. Несмотря на то, что можно успешно запускать Linux на PC 486 с 8 Мбайт RAM, для успешной работы современного дистрибутива Linux и выполнения примеров из этой книги мы советуем выбрать современную версию одного из наиболее популярных дистрибутивов Linux, например Fedora, openSUSE или Ubuntu, и проверить их аппаратные рекомендации.

Что касается требований к программному обеспечению, мы полагаем, что вы используете современную версию предпочитаемого вами дистрибутива Linux и, чтобы поддерживать систему на современном уровне и иметь самые свежие исправления найденных ошибок, применяете текущий набор обновлений, которые большинство поставщиков делают доступными интерактивно в виде автоматических обновлений. Linux и комплект инструментальных средств проекта GNU выпускаются на условиях GNU General Public License (GPL) (Общедоступной лицензии проекта GNU). Большинство других компонентов типичного дистрибутива Linux ссылаются либо на GPL, либо на одну из множества других лицензий Open Source (открытый или свободно распространяемый программный код), и это означает, что у них есть определенные характеристики, одна из которых — свобода. У них всегда есть исходный программный код, и никто не может отнять эту свободу. Дополнительную информацию о GPL см. на Web-сайте http://www.gnu.org/licenses/, а определение Open Source и разные применяемые лицензии — на Web-сайте http://www.opensource.org. В случае GNU/Linux у вас всегда будет возможность технической поддержки либо благодаря самостоятельной работе с исходным программным кодом, либо за счет найма стороннего специалиста или обращения к одному из поставщиков, предлагающих платную техническую поддержку.

Исходный программный код

Для работы с примерами книги можно ввести программный код вручную или воспользоваться сопроводительными файлами с исходным текстом примеров. Весь программный код, применяемый в книге, можно найти на Web-сайте http://www.wrox.com. Открыв главную страницу сайта, просто найдите заголовок книги (либо с помощью поля Search (Поиск), либо используя один из списков заголовков) и на странице с описанием книги щелкните кнопкой мыши ссылку Download Code для того, чтобы получить весь программный код примеров.

Примечание

Поскольку у многих книг похожие заголовки, легче всего найти нужную книгу по номеру ISBN (International Standard Book Number); ISBN этой книги (оригинальной) — 978-0-470-14762-7.

После загрузки программного кода из Интернета просто распакуйте его своей любимой программой сжатия. Вы также можете перейти на главную страницу загрузки программного кода издательства Wrox http://www.wrox.com/dynamic/books/download.aspx, для того чтобы просмотреть код к данной книге и ко всем остальным книгам издательства.

Замечание, касающееся программного кода примеров

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

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

Общедоступная лицензия проекта GNU

Исходный программный код книги сделан доступным на условиях Общедоступной лицензии проекта GNU версии 2 (GNU General Public License, version 2), опубликованной на Web-странице http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. Приведенное далее положение о разрешении и правах применяется ко всему программному коду данной книги.

This program is free software; you can redistribute it and/or modify

it under the terms of the GNU General Public License as published by

the Free Software Foundation; either version 2 of the License, or

(at your option) any later version.

(Это программа — свободно распространяемое программное обеспечение; вы можете

распространять ее и/или изменять на условиях Общедоступной лицензии GNU,

опубликованной Фондом свободного программного обеспечения;

либо версии 2 этой лицензии, либо (по вашему усмотрению) любой более свежей версии.)

This program is distributed in the hope that it will be useful,

but WITHOUT ANY WARRANTY; without even the implied warranty of

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

GNU General Public License for more details.

(Эта программа распространяется в расчете на ее полезность, но без каких-либо

гарантий, даже без подразумеваемой гарантии ТОВАРНОГО СОСТОЯНИЯ ПРИ ПРОДАЖЕ И

ПРИГОДНОСТИ ДЛЯ ИСПОЛЬЗОВАНИЯ В КОНКРЕТНЫХ ЦЕЛЯХ. Более подробную информацию

см. в Общедоступной лицензии проекта GNU.)

You should have received a copy of the GNU General Public License

along with this program; if not, write to the Free Software

Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

(Вы должны были получить копию Общедоступной лицензии GNU вместе с этой

программой; если этого не произошло, напишите в Фонд свободного программного

обеспечения по адресу Free Software Foundation, Inc., 59 Temple Place, Suite

330, Boston, MA 02111-1307 USA)

Стилевое оформление, принятое в книге

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

Примечание

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

Когда вводятся важные понятия, мы выделяем их курсивом. Символы, которые вы должны ввести, выделяются жирным моноширинным шрифтом. Элементы интерфейса выделены полужирным шрифтом. Комбинации клавиш обозначаются следующим образом: <Ctrl>+<A>

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

$ who

root tty1 Sep 10 16:12

rick tty2 Sep 10 16:10

Верхняя строка приведенного кода — это командная строка, а остальные строки отображаются в обычном стиле. Знак $ — приглашение (если для ввода команды требуется суперпользователь, приглашение обозначается знаком #); жирным шрифтом помечается текст, который вы должны ввести, и для выполнения команды следует нажать клавишу <Enter> (или <Return>). Любой последующий текст, набранный тем же шрифтом, но без выделения жирным, — это вывод обозначенной жирным шрифтом команды. В приведенном примере вы вводите команду who и видите ее вывод в двух строках, расположенных под ней.

Прототипы функций и структуры, определенные в системе Linux, приводятся жирным шрифтом, как показано далее:

#include <stdio.h>

int printf(const char *format, ...);

В программном коде наших примеров строки с выделенным фоном указывают на новый важный материал, например, так:

/* Это новый материал, и соответствующий код выглядит так. */

если код выглядит так, как показано далее (без выделения фоном), он менее важен:

/* Этот код уже встречался, и он выглядит так. */

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

/* Программный код примера */

/* Это строка завершения. */ 

Если позже в этой главе мы добавим в нее новые строки, она будет выглядеть так:

/* Программный код примера. */

/* В эти строки */

/* добавляется новый код */

/* Это строка завершения. */

И последнее, принятое в книге стилевое оформление, о котором следует упомянуть, — все примеры программного кода начинаются с заголовка "Упражнение", который помогает разделить код там, где это полезно, выделить его составные части и показать, как развивается приложение. Когда это важно, мы после программного кода включаем раздел "Как это работает" для пояснения основных мест в тексте программы, касающихся изложенной перед ним теории. Мы считаем,, что эти два приема помогают разбить наиболее громоздкие листинги на легко перевариваемые кусочки.

Ошибки

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

Для поиска страницы с ошибками, найденными в этой книге, перейдите на Web-сайт http://www.wrox.corn и найдите заголовок с помощью поля Search (Поиск) или одного из списков заголовков. Далее на странице с выходными данными книги щелкните кнопкой мыши ссылку Errata (Ошибки). Вы попадете на страницу, отображающую все ошибки, представленные на рассмотрение и опубликованные редакторами издательства Wrox. На Web-странице www.wrox.com/misc-pages/booklist.shtml можно найти полный список книг, включающий ссылки на ошибки, найденные в каждой книге.

Если вы не обнаружили "свою" ошибку на странице Errata (Ошибки), перейдите на страницу www.wrox.com/contact/techsupport.shtml и заполните форму для отправки нам найденной вами ошибки. Мы проверим присланную информацию и, если согласимся с ней, опубликуем сообщение на странице с ошибками, найденными в книге, и исправим ее в последующих изданиях книги.

Сайт p2p.wrox.com

Для обмена мнениями с авторами и такими же, как вы, читателями присоединяйтесь к форумам Р2Р (Programmer to Programmer) на Web-сайте p2p.wrox.com. Форумы — это система на основе Web-технологии, предназначенная для отправки вашего сообщения, относящегося к книгам издательства Wrox и родственным технологиям, и обмена мнениями с другими читателями и пользователями этих технологий. Форумы предлагают функцию подписки для отправки вам по электронной почте по мере поступления новых сообщений, относящихся к выбранным вами и интересующих вас темам. На этих форумах представлены авторы и редакторы Wrox и другие специалисты, работающие в области информационных технологий.

На Web-сайте http://p2p.wrox.com вы найдете ряд разных форумов, которые помогут вам не только во время чтения книги, но и в процессе разработки ваших собственных приложений. Для присоединения к форумам выполните следующие действия:

1. Перейдите на Web-сайт p2p.wrox.com и щелкните кнопкой мыши ссылку Register (Зарегистрироваться).

2. Прочтите условия пользования и щелкните мышью кнопку Agree (Принять).

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

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

Примечание

Читать сообщения на форумах вы сможете и без регистрации в Р2Р, но для того чтобы отправлять собственные сообщения, придется зарегистрироваться.

После присоединения к форуму вы можете посылать новые сообщения и отвечать на сообщения, посланные другими пользователями. Читать сообщения можно будет в любое время, находясь в Web-пространстве. Если вы хотите получать по электронной почте новые сообщения, появляющиеся на конкретном форуме, щелкните мышью пиктограмму Subscribe to this Forum (Подписаться на этот форум), расположенную рядом с именем форума в списке форумов.

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

Глава 1

Приступая к работе

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

□ UNIX, Linux и проекте GNU;

□ программах и языках программирования в ОС Linux;

□ способах поиска ресурсов разработки;

□ статических и совместно используемых библиотеках;

□ теоретических основах ОС UNIX.

Введение в UNIX, Linux и проект GNU

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

Своим успехом она обязана системам и приложениям — предшественникам: ОС UNIX и программному обеспечению GNU. В этом разделе рассматривается, как появилась ОС Linux и каковы ее корни. 

Что такое ОС UNIX?

Операционная система UNIX первоначально была разработана в компании Bell Laboratories, бывшей в то время частью телекоммуникационного гиганта, компании AT&T. Разработанная в 1970-х гг. для мини-компьютеров PDP корпорации Digital Equipment ОС UNIX стала очень популярной многопользовательской, многозадачной операционной системой для самых разных аппаратных платформ, начиная с рабочих станций PC и заканчивая многопроцессорными серверами и суперкомпьютерами.

Краткая история ОС UNIX

Строго говоря, UNIX — это торговое название, контролируемое организацией Open Group и относящееся к компьютерной операционной системе, соответствующей определенной спецификации. В этой спецификации, именуемой "The Single UNIX Specification" ("Единая спецификация UNIX"), определены имена, интерфейсы и поведение всех обязательных функций операционной системы UNIX. Данная спецификация в значительной степени представляет собой расширенный набор более ранних спецификаций, стандартов Р1003 или POSIX (Portable Operating System Interface, интерфейс переносимой операционной системы), разработанных IEEE (Institute of Electrical and Electronic Engineers, Институт инженеров по электротехнике и радиоэлектронике).

Существует много коммерческих UNIX-подобных систем, таких как AIX корпорации IBM, UX компании HP и Solaris компании Sun Microsystems. Некоторые системы, например FreeBSD и Linux, свободно распространяются. В настоящее время спецификации Open Group удовлетворяют лишь несколько операционных систем, что позволяет предлагать их на рынке с названием UNIX.

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

Идеология UNIX

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

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

□ Простота. Многие из наиболее полезных утилит UNIX очень просты и как результат малы и понятны. KISS (Keep It Small and Simple, сохраняйте программу маленькой и простой) — отличный подход, которому следует научиться. Чем больше и сложнее система, тем наверняка в ней больше сложных ошибок, и отладка превращается в тяжелую работу, которой хотелось бы избежать. 

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

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

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

□ Открытые файловые форматы. Наиболее удачные и популярные UNIX- программы применяют файлы конфигурации и файлы данных в виде обычного текста ASCII или файла на языке XML. Если в разрабатываемой вами программе можно использовать любой из этих форматов — это хороший выбор. Он позволит другим пользователям применить стандартные средства при изменении или поиске элементов конфигурации и разрабатывать новые средства для выполнения новых функций обработки файлов данных. Хорошим примером такого подхода может служить система перекрестных ссылок исходного кода ctags, записывающая сведения о местоположении символа в. виде регулярного выражения, подходящего для использования программами поиска.

□ Гибкость. Вы не можете точно предусмотреть заранее, как изобретательные пользователи будут применять вашу программу. Программируя, попытайтесь быть настолько гибким, "насколько это возможно. Старайтесь избегать любых ограничений размеров полей или числа записей. Если можно, пишите программу в расчете на применение в сети, способную выполняться одинаково хорошо при сетевом вызове и на локальной машине. Никогда не думайте, что вы знаете все о потребностях будущего пользователя.

Что такое Linux?

Как вы уже, возможно, знаете, Linux — это свободно распространяемая реализация UNIX-подобного ядра, низкоуровневой сердцевины операционной системы. Поскольку прообразом ОС Linux стала система UNIX, Linux- и UNIX-программы очень похожи. В действительности почти все программы, написанные для ОС UNIX, могут быть скомпилированы и выполнены в ОС Linux. Кроме того, некоторые коммерческие приложения, продаваемые для коммерческих версий UNIX, могут выполняться без изменения их двоичного кода в системах под управлением Linux.

ОС Linux была разработана Линусом Торвальдсом (Linus Torvalds) из Университета г. Хельсинки совместно с программистами UNIX, оказывавшими ему помощь по Интернету. Работа начиналась как хобби, а вдохновителем стала ОС Minix Энди Таненбаума (Andy Tanenbaum), маленькая UNIX-подобная система. Со временем Linux выросла, превратившись в сложную самостоятельную систему. Ее цель — отказ от патентованного кода и применение только свободно распространяемого программного кода.

В настоящее время ОС Linux существует для широкого набора компьютерных систем с разными типами процессоров, включая PC на 16- и 32-битных процессорах Intel x86 и совместимых с ними процессорах; рабочие станции и серверы на процессорах Sun SPARC, IBM PowerPC, AMD Opteron и Intel Itanium и даже некоторые карманные компьютеры PDA и игровые приставки Playstation 2 и 3 фирмы Sony. Если у устройства есть процессор, кто-то где-нибудь пытается добыть ОС Linux, выполняющуюся на этом процессоре!

Проект GNU и Фонд свободного ПО 

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

Linux-сообщество (совместно с другими людьми) поддерживает идею свободного программного обеспечения (ПО), т.е. свободного от ограничений и подчиняющегося Общедоступной лицензии проекта GNU (GNU General Public License, GPL). (GNU означает GNU's Not UNIX (GNU не UNIX).) Несмотря на то, что получение программного обеспечения может быть небесплатным, это ПО может использоваться как угодно и обычно распространяется в виде исходного программного кода.

Фонд свободного программного обеспечения (Free Software Foundation) был организован Ричардом Столлменом (Richard Stallman) — автором GNU Emacs, одного из самых известных текстовых редакторов для ОС UNIX и других систем. Столлмен — автор концепции свободного программного обеспечения и организатор проекта GNU, попытки создания операционной системы и среды разработки, совместимой с ОС UNIX, но не подверженной ограничениям, связанным с торговой маркой UNIX и предоставлением исходного программного кода. В любой момент может оказаться, что проект GNU сильно отличается от UNIX способами поддержки аппаратных средств и управления исполняемыми программами, но он будет продолжать поддерживать приложения в стиле UNIX.

Проект GNU уже снабдил программистское сообщество множеством приложений, сильно напоминающих, компоненты, входящие в системы UNIX. Все эти программы, называемые программным обеспечением GNU, распространяются в соответствии с Общедоступной лицензией GNU (GPL), копию которой можно найти на сайте http://www.gnu.org. В этой лицензии вводится понятие "авторского "лева" (copyleft)" (в противоположность авторскому праву ("copyright")). Авторское "лево" задумано как препятствие установлению каких-либо ограничений на использование свободного программного обеспечения.

Далее приведены основные примеры ПО проекта GNU, распространяемого в соответствии с лицензией GPL:

□ пакет компиляторов GCC (GNU Compiler Collection), включающий компилятор GNU С;

□ G++ — компилятор С++, включающий как часть GCC;

□ GDB — отладчик на уровне исходного кода;

□ GNU make — версия UNIX-автосборщика make;

□ Bison — генератор синтаксических анализаторов, совместимый с генератором компиляторов UNIX yacc;

□ bash — командная оболочка;

□ GNU Emacs — текстовый редактор и среда разработки.

Кроме того, было разработано и распространено на принципах свободного ПО и под контролем лицензии GPL множество других пакетов, включая электронные таблицы, средства управления программным кодом, компиляторы, интерпретаторы, интернет-средства, программы обработки графических объектов, например, графический редактор Gimp и две законченные объектно-ориентированные среды разработки: GNOME и KDE. Мы обсудим GNOME и KDE в главах 16 и 17.

Сейчас существует такое множество доступного свободного программного обеспечения, что если добавить к нему ядро Linux, можно сказать, что благодаря Linux достигнута основная цель проекта GNU — свободная UNIX-подобная система. Признавая вклад программного обеспечения проекта GNU, многие люди теперь, как правило, называют Linux-системы GNU/Linux.

Более подробную информацию о концепции свободного программного обеспечения можно получить на Web-сайте http://www.gnu.org.

Дистрибутивы Linux

Как мы уже упоминали, Linux — это только ядро. Вы можете получить исходный программный код ядра, откомпилировать его и установить на машину, а затем получить и установить много другого свободного программного обеспечения для завершения установки ОС Linux. Такие установки часто называют системами Linux, т.к. они содержат много программ помимо ядра. Большинство утилит приходит от проекта GNU Фонда свободного ПО. 

Понятно, что создание системы Linux только из исходного программного кода — трудное дело. К счастью, многие люди подготовили готовые к установке дистрибутивы (часто называемые разновидностями (flavor)), обычно загружаемые из Интернета или с CD/DVD-накопителей и содержащие не только ядро, но и множество других программных средств и утилит. Часто в их состав входит реализация X Window System — графической оболочки, общей для множества систем UNIX. Дистрибутивы обычно снабжаются программой установки и дополнительной документацией (как правило, все на компакт-дисках), чтобы помочь вам установить собственную систему Linux. К некоторым хорошо известным дистрибутивам, в особенности для семейства процессоров Intel х86, относятся дистрибутивы Red Hat Enterprise Linux и его усовершенствованный сообществом родственник Fedora, Novell SUSE Linux и свободно распространяемый вариант openSUSE, Ubuntu Linux, Slackware, Gentoo и Debian GNU/Linux. Подробную информацию о множестве других дистрибутивов можно найти на Web-сайте DistroWatch по адресу http://distrowatch.com.

Программирование в ОС Linux

Многие думают, что программирование в Linux означает применение языка программирования С. Известно, что ОС UNIX первоначально была написана на С и что большинство UNIX-приложений были написаны на языке С. Но для программистов ОС Linux, или UNIX, С — не единственно возможный вариант. Далее в книге мы назовем пару альтернатив.

Примечание

На самом деле первая версия UNIX была написана в 1969 г. на ассемблере PDP 7. Язык С был задуман Деннисом Ритчи (Dennis Ritchie) примерно в это время, и в 1973 г. он вместе с Кеном Томпсоном (Ken Tompson) по существу переписал на С все ядро UNIX, совершив настоящий подвиг в эпоху разработки системного программного обеспечения на языке ассемблера.

В системах Linux доступен широкий диапазон языков программирования, многие из них свободно распространяются и есть на компакт-дисках или в архивах на FTP- сайтах в Интернете. Далее перечислена часть языков программирования, доступных программистам Linux:

□ Ada;

□ С;

□ С++;

□ Eiffel;

□ Forth;

□ Fortran;

□ Icon;

□ Java;

□ JavaScript;

□ Lisp;

□ Modula 2;

□ Modula 3;

□ Oberon;

□ Objective С;

□ Pascal; 

□ Perl;

□ Prolog;

□ PostScript;

□ Python;

□ Ruby;

□ Smalltalk;

□ PHP;

□ Tcl/Tk;

□ Bourne Shell.

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

Linux-программы

Linux-приложения представлены файлами двух типов: исполняемыми (executable) и сценариями или пакетными файлами (script). Исполняемые файлы — это программы, которые могут непосредственно выполняться на компьютере; они соответствуют файлам ОС Windows с расширением exe. Сценарии или пакетные файлы — это наборы команд для выполнения другой программой, интерпретатором. Они соответствуют в ОС Windows файлам с расширением bat или cmd или интерпретируемым программам на языке Basic.

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

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

□ /bin — бинарные файлы (binaries), программы, применяемые для загрузки системы;

□ /usr/bin — пользовательские библиотеки, стандартные программы, доступные пользователям;

□ /usr/local/bin — локальные библиотеки, программы, относящиеся к этапу инициализации.

Если войти в систему как администратор, например с именем root, можно использовать переменную PATH, которая включает каталоги с хранящимися системными программами, такие как /sbin и /usr/sbin.

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

Примечание

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

Обратите внимание на то, что в ОС Linux, как и UNIX, для разделения отдельных элементов в переменной PATH применяется символ двоеточия (:) в отличие от символа точки с запятой, используемого в ОС MS-DOS и Windows. (ОС UNIX сделала выбор первой, поэтому спрашивайте, почему отличается Windows, а не почему в UNIX все не так!) Далее приведен пример переменной PATH:

/usr/local/bin:/bin:/usr/bin:.:/home/neil/bin:/usr/X11R6/bin

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

Запомните, в ОС Linux используется прямой слэш (/) для отделения имен каталогов в полном имени файла в отличие от обратного слэша (\), применяемого в ОС Windows. И снова ОС UNIX выбирала первой.

Текстовые редакторы

Для ввода и набора примеров программного кода, приведенных в книге, вам понадобится текстовый редактор. В типовых системах Linux есть большой выбор таких программ. У многих пользователей популярен редактор vi.

Оба автора предпочитают Emacs, поэтому мы предлагаем потратить немного времени на знакомство с основными функциями этого мощного редактора. Почти во все дистрибутивы ОС Linux Emacs включен как необязательный пакет, который можно установить. Кроме того, вы можете получить его на Web-сайте GNU по адресу http://www.gnu.org или же взять версию для графических сред разработки на Web-сайте XEmacs по адресу http://www.xemacs.org.

Для того чтобы узнать больше о редакторе Emacs, можно воспользоваться его интерактивным средством обучения. Начните с выполнения команды emacs, затем нажмите комбинацию клавиш <Ctrl>+<H> с последующим вводом символа t для доступа к этому средству. У редактора Emacs есть также полное руководство. Для получения дополнительной информации о нем в редакторе Emacs нажмите комбинацию клавиш <Ctrl>+<H> с последующим вводом символа i. В некоторых версиях Emacs может быть меню, предоставляющее доступ к средству обучения и полному руководству.

Компилятор языка С

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

Когда создавался стандарт POSIX, выяснилось, что невозможно определить стандартную команду cc, которая была бы совместима со всеми этими разработками. 

Вместо этого комитет решил создать новую стандартную команду для компилятора языка С — с89. Если эта команда представлена, она всегда использует одни и те же опции независимо от машины.

В системах Linux, которые на деле пытаются следовать стандартам, можно обнаружить, что все или некоторые из команд с89, cc и gcc ссылаются на системный компилятор языка С, обычно компилятор GNU С или gcc. В системах UNIX компилятор языка С почти всегда называется cc.

В этой книге мы используем gcc, поскольку он поставляется в дистрибутивах Linux и потому что он поддерживает для языка С синтаксис стандарта ANSI. Если когда-нибудь вы обнаружите, что в вашей системе нет gcc, мы советуем получить его и установить. Найти его вы можете по адресу http://www.gnu.org. Всюду, где мы используем в книге команду gcc, просто заменяйте ее подходящей командой вашей системы.

Упражнение 1.1. Ваша первая Linux-программа на языке C

В этом примере вы начнете разработку в ОС Linux с помощью языка С, написав, откомпилировав и выполнив свою первую Linux-программу. Ею, кстати, может стать самая известная из всех программ для начинающих — программа, выводящая сообщение "Hello World" ("Привет, мир").

1. Далее приводится текст файла hello.c:

#include <stdio.h>

#include <stdlib.h>

int main() {

 printf("Hello World\n");

 exit(0);

}

2. Теперь откомпилируйте, скомпонуйте и выполните вашу программу.

$ gcc -о hello.c $ ./hello

Hello World

Как это работает

Вы запустили компилятор GNU С (в Linux, вероятнее всего, он будет доступен и как cc), который оттранслировал исходный код на языке С в исполняемый файл, названный hello. Вы выполнили программу, и она вывела на экран приветствие. Это наипростейший из существующих примеров, но если вы смогли с помощью вашей системы добраться до этого места, то сможете откомпилировать и выполнить и остальные примеры из книги. Если же программа не сработала, убедитесь в том, что в вашей системе установлен компилятор языка С. Например, во многих дистрибутивах Linux есть установочная опция, названная Software Development (Разработка ПО) (или что-то похожее), которую следует выбрать для установки необходимых пакетов.

Поскольку это первая выполненная вами программа, самое время обратить внимание на некоторые основные положения. Программа hello, вероятно, должна быть в вашем исходном каталоге. Если в переменную PATH не включена ссылка на ваш исходный каталог, оболочка не сможет найти программу hello. Более того, если один из каталогов в переменной PATH содержит другую программу, названную hello, вместо вашей будет выполнена эта программа. То же самое произойдет, если такой каталог упомянут в переменной path раньше вашего исходного каталога. Для решения этой потенциальной проблемы можно снабдить имена программ префиксом ./ (например, ./hello). Данный префикс сообщает оболочке о необходимости выполнить программу с заданным именем, находящуюся в текущем каталоге. (Точка — это условное название текущего каталога.)

Если вы забыли опцию -o name, которая указывает компилятору, куда поместить исполняемый файл, компилятор поместит его в файл с именем a.out (что означает ассемблерный вывод). Не забудьте поискать файл с именем a.out, если вы уверены, что скомпилировали программу, а найти ее не можете! Когда ОС UNIX только появилась, пользователи, хотевшие играть в ней в игры, часто запускали их как файл с именем a.out, чтобы не быть пойманными системным администратором, и некоторые установки ОС UNIX традиционно удаляют каждый вечер все файлы с именем a.out.

Маршрутная карта системы разработки

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

Приложения

Приложения обычно хранятся в отведенных для них каталогах. Приложения, предоставляемые системой для общего использования, включая средства разработки программ, находятся в каталоге /usr/bin. Приложения, добавленные системными администраторами для конкретного хост-компьютера или локальной сети, часто хранятся в каталоге /usr/local/bin или /opt.

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

Дополнительные средства и системы программирования могут иметь собственные структуры каталогов и каталоги программ. Важнейшая среди них — графическая оболочка X Window System, которая обычно устанавливается в каталог /usr/X11 или каталог /usr/bin/X11. В дистрибутивах Linux, как правило, применяется версия X.Org Foundation графической оболочки X Window System, базирующаяся на модификации Revision 7 (X11R7). В других UNIX-подобных системах могут быть выбраны иные версии X Window System, устанавливаемые в другие каталоги, например, каталог /usr/openwin для оболочки Open Windows компании Sun в системе Solaris.

Программа системного драйвера компилятора GNU, gcc (которую вы использовали в предыдущем упражнении) обычно помещается в каталог usr/bin или usr/local/bin, но она будет запускать различные поддерживающие компиляцию приложения из других каталогов. Эти каталоги задаются во время компиляции самого компилятора и зависят от типа хост-компьютера. В системах Linux это может быть зависящий от конкретной версии подкаталог /usr/lib/gcc/. На одной из машин одного из авторов во время написания книги это был подкаталог /usr/lib/gcc/i586-suse-linux/4.1.3. В нем хранятся отдельные проходы компилятора GNU C/C++ и специфические заголовочные файлы GNU.

Заголовочные файлы

В процессе программирования на языке С и других языках вам потребуются заголовочные файлы или файлы заголовков для включения определений констант и объявлений вызовов системных и библиотечных функций. В случае языка С эти файлы почти всегда находятся в каталоге /usr/include и его подкаталогах. Заголовочные файлы, зависящие от конкретного воплощения запущенной вами ОС Linux, вы, как правило, найдете в каталогах /usr/include/sys и /usr/include/linux.

У других систем программирования тоже есть заголовочные файлы, хранящиеся в каталогах, которые автоматически находятся соответствующим компилятором. Примерами могут служить каталоги /usr/include/X11 для графической оболочки X Window System и /usr/include/c++ для языка GNU С++.

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

$ gcc -I/usr/openwin/include fred.c

заставит искать заголовочные файлы, использованные в программе fred.c, в стандартных каталогах и в каталоге /usr/openwin/include. Для получения дополнительных сведений обратитесь к руководству компилятора С (man gcc).

Искать заголовочные файлы с конкретными определениями и прототипами конкретных функций часто удобно с помощью команды grep. Предположим, вам нужно знать имя из директив #define, используемое для возврата из программы статуса завершения. Просто замените каталог на /usr/include и примените grep для поиска предполагаемой части имени следующим образом:

$ grep EXIT_ *.h

...

stdlib.h#define EXIT_FAILURE 1 /*Failing exit status. */

stdlib.h#define EXIT_SUCCESS 0 /*Successful exit status. */

...

$

В этом случае команда grep ищет в каталоге все файлы с именами, заканчивающимися на .h, со строкой EXIT_. В данном примере она нашла (среди прочих) нужное вам определение в файле stdlib.h.

Библиотечные файлы

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

Стандартные системные библиотеки обычно хранятся в каталогах /lib и /usr/lib. Компилятору языка С (или, точнее, компоновщику) необходимо сообщить, в каких библиотеках искать, поскольку по умолчанию он ищет только в стандартной библиотеке С. Это пережиток, пришедший к нам из того времени, когда компьютеры были медленными и циклы ЦПУ были дороги. Недостаточно поместить библиотеку в стандартный каталог и ждать, что компилятор найдет ее; библиотеки должны следовать очень специфическим правилам именования и быть упомянуты в командной строке.

Имя файла библиотеки всегда начинается с символов lib. Далее следует часть, указывающая на назначение библиотеки (например, с для библиотеки С или m для математической библиотеки). Последняя часть имени начинается с точки (.) и задает тип библиотеки:

□ а — для традиционных статических библиотек;

□ .so — для совместно используемых библиотек (см. далее).

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

$ gcc -о fred fred.c /usr/lib/libm.a

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

$ gcc -о fred fred.c -lm

-lm (без пробела между символами l и m) — это сокращенное обозначение (сокращенные формы очень ценятся в UNIX-кругах) библиотеки с именем libm.a, хранящейся в одном из стандартных библиотечных каталогов (в данном случае /usr/lib). Дополнительное преимущество обозначения -lm в том, что компилятор автоматически выберет совместно используемую библиотеку, если она существует.

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

$ gcc -о x11fred -L/usr/openwin/lib x11fred.c -lX11

будет компилировать и компоновать программу x11fred, используя версию библиотеки libX11, найденную в каталоге /usr/openwin/lib.

Статические библиотеки

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

Статические библиотеки, также называемые архивами, в соответствии с принятыми соглашениями имеют окончание .а. Например, lib/libc.а и /usr/lib/libX11 для библиотек С и X11 соответственно.

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

Упражнение 1.2. Статические библиотеки

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

1. Сначала создайте отдельные исходные файлы (как не удивительно, названные fred.c и bill.c) для каждой функции.

Далее приведен первый из них:

#include <stdio.h>

void fred(int arg) {

 printf("fred: you passed %d\n", arg);

}

А это второй:

#include <stdio.h>

void bill(char *arg) {

 printf("bill: you passed %s\n", arg);

}

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

$ gcc -с bill.с fred.c

$ ls *.o

bill.о fred.о

3. Теперь напишите программу, вызывающую функцию bill. Прежде всего, хорошо бы создать заголовочный файл для вашей библиотеки. В нем будут объявлены функции из вашей библиотеки, и он будет включаться во все приложения, которые захотят применить вашу библиотеку. В файлы fred.c и bill.c тоже хорошо бы включить заголовочный файл, чтобы помочь компилятору обнаружить любые ошибки.

/*

 Это файл lib.h. В кем объявлены пользовательские функции fred and bill

*/[1]

void bill(char *);

void fred(int);

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

#include <stdlib.h>

#include "lib.h"

int main() {

 bill("Hello World");

 exit(0);

}

5. Теперь можно откомпилировать и протестировать программу. Для этого задайте компилятору явно объектные файлы и попросите его откомпилировать ваш файл и связать его с ранее откомпилированным объектным модулем bill.o.

$ gcc -с program.с

$ gcc -о program program.о bill.о

$ ./program

bill: we passed Hello World

$

6. Для создания архива и включения в него ваших объектных файлов используйте программу ar. Программа называется ar, поскольку она создает архивы или коллекции отдельных файлов, помещая их все в один большой файл. Имейте в виду, что программу ar можно применять для создания архивов из файлов любого типа. (Как многие утилиты UNIX, ar — универсальное средство.)

$ ar crv libfоо.a bill.о fred.о

а - bill.о а - fred.о

7. Библиотека создана, и в нее добавлены два объектных файла. Для того чтобы успешно применять библиотеку в некоторых системах, в особенности в производных от Berkeley UNIX, требуется создать для библиотеки индекс содержимого архива или список вложенных в библиотеку функций и переменных (table of contents). Сделайте это с помощью команды ranlib. В ОС Linux при использовании программных средств разработки GNU этот шаг не является необходимым (но и не приносит вреда).

$ ranlib libfoo.a

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

$ gcc -о program program.о libfоо.а

$ ./program

bill: we passed Hello world

Можно было бы применить для доступа к библиотеке флаг -l, но т.к. она хранится не в одном из стандартных каталогов, вы должны сообщить компилятору место поиска с помощью флага -L следующим образом:

$ gcc -о program .program.о -L. -lfoo

Опция -L заставляет компилятор искать библиотеки в текущем каталоге (.). Опция -lfoo сообщает компилятору, что нужно использовать библиотеку с именем libfoo.a (или совместно используемую библиотеку libfoo.so, если она есть). Для того чтобы посмотреть, какие функции включены в объектный файл, библиотеку или исполняемую программу, можно применить команду nm. Если вы взглянете на файлы program и libfoo.a, то увидите, что библиотека содержит обе функции: fred и bill, а файл program — только функцию bill. Когда создается программа, в нее включаются из библиотеки только те функции, которые ей действительно нужны. Вставка заголовочного файла, содержащего объявления всех функций библиотеки, не вызывает включения в конечную программу целиком всей библиотеки.

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

Таблица 1.1

Элемент UNIX Windows
Объектный модуль func.o FUNC.OBJ
Статическая библиотека lib.a LIB.LIB
Программа program PROGRAM.EXE

Совместно используемые библиотеки

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

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

Совместно используемые библиотеки хранятся в тех же каталогах, что и статические, но у имен файлов совместно используемых библиотек другой суффикс. В типовой системе Linux имя совместно используемой версии стандартной библиотеки математических функций — /lib/libm.so.

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

В этом случае система предоставляет возможность многим приложениям одновременно использовать единственную копию совместно используемой библиотеки и хранить ее на диске в единственном экземпляре. Дополнительным преимуществом служит возможность обновления совместно используемой библиотеки независимо от базирующихся на ней приложений. Применяются символические ссылки из файла /lib/libm.so на текущую версию библиотеки (/lib/libm.so.N, где N — основной номер версии — 6 во время написания книги). Когда ОС Linux запускает приложение, она учитывает номер версии библиотеки, требующийся приложению, чтобы не дать ведущим новым версиям библиотеки испортить более старые приложения.

Примечание

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

В системах Linux программа (динамический загрузчик), отвечающая за загрузку совместно используемых библиотек и разрешение ссылок на функции в клиентских программах, называется ld.so и может присутствовать в системе как ld-linux.so.2, или li-lsb.so.2, или li-lsb.so.3. Дополнительные каталоги поиска совместно используемых библиотек настраиваются в файле /etc/ld.so.conf, который после внесения изменений (например, если добавляются совместно используемые библиотеки X11 при установке графической оболочки X Window System) следует обработать командой ldconfig.

Запустив утилиту ldd, вы можете увидеть, какие совместно используемые библиотеки требуются программе. Например, если вы попытаетесь выполнить утилиту для приложения из нашего примера, то получите следующие строки:

$ ldd program

  linux-gate.so.1 => (0xffffe000)

  libc.so.6 => /lib/libc.so.6 (0xb7db4000)

  /lib/ld-linux.so.2 (0xb7efc000)

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

Во многом совместно используемые библиотеки аналогичны динамически подключаемым библиотекам в ОС Windows. Библиотеки с расширением .so соответствуют файлам с расширением dll и требуются во время выполнения, а библиотеки с расширением .а аналогичны файлам с расширением lib, которые включаются в исполняемые программы.

Получение справки

Подавляющее большинство систем Linux хорошо документировано в отношении системных программных интерфейсов и стандартных утилит. Это на самом деле так, потому что со времени первых систем UNIX программистов призывали снабжать свои приложения справочными материалами или описаниями. Эти справочные страницы (man pages), которые иногда предоставлялись в печатной форме, всегда доступны и в электронном виде.

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

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

Упражнение 1.3. Справочные руководства и система info

Давайте познакомимся с документацией для компилятора GNU С (gcc).

1. Сначала посмотрим на справочное руководство.

$ man gcc

GCC(1)                GNU                GCC(1)

NAME

       gcc — GNU project С and С++ compiler

SYNOPSIS

       gcc [-с|-S|-E] [-std=standard]

           [-g] [-pg] [-Olevel]

           [-Wwarn...] [-pedantic]

           [-Idir...] [-Ldir...]

           [-Dmacro[=defn]...] [-Umacro]

           [-foption...] [-mmachine-option...]

           [-о outfile] infile...

       Only the most useful options are listed here; see below

       for the remainder. g++ accepts mostly the same options as

       gcc.

DESCRIPTION

       When you invoke GCC, it normally does preprocessing, com-

       pilation, assembly and linking. The "overall options"

       allow you to stop this process at an intermediate stage.

       For example, the -c option says not to run the linker.

       Then the output consists of object files output by the assembler.

       Other options are passed on to one stage of processing.

       Some options control the preprocessor and others the com-

       piler itself. Yet other options control the assembler and

       linker; most of these are not documented here, since we

       rarely need to use any of them.

...

Если хотите, можно прочесть об опциях, поддерживаемых транслятором. В этом случае справочное руководство очень длинное, хотя содержит лишь малую часть полной документации по компилятору GNU С (и С++).

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

2. Для получения более подробной информации о компиляторе GNU С можно попробовать применить команду info.

$ info gcc

File: gcc.info. Node: Top, Next: G++ and GCC, Up: (DIR)

Introduction

************

   This manual documents how to use the GNU compilers, as well as their

features and incompatibilities, and how to report bugs. It corresponds to

GCC version 4.1.3. The internals of the GNU compilers, including how to port

them to new targets and some information about how to write front ends for

new languages, are documented in a separate manual.

*Note Introduction: (gccint)Top.

*Menu:

* G++ and GCC:: You can compile С or С++ Applications.

* Standards:: Language standards supported by GCC,

* Invoking GCC:: Command options supported by `gcc'.

* С Implementation:: How GCC implements theISO С specification.

* С Extensions:: GNU extensions to the С language family.

* С++ Extensions:: GNU extensions to the С++ language.

* Objective-C:: GNU Objective-C runtime features.

* Compatibility:: Binary Compatibility

--zz-Info: (gcc.info.gz)Top, 39 lines --Top--------------------------

Welcome to Info version 4.8. Type ? for help, m for menu item.

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

У системы info есть собственная справка, конечно, в формате страниц info. Если нажать комбинацию клавиш <Ctrl>+<H>, можно познакомиться со справочным руководством, включающим средства обучения пользованию системой info. Программа info входит в состав многих дистрибутивов Linux и может устанавливаться в других ОС UNIX.

Резюме 

В этой вводной главе мы познакомились с программированием в ОС Linux и другими компонентами ОС Linux, общими с патентованными системами UNIX. Мы отметили огромное разнообразие систем программирования, доступных UNIX-разработчикам. Мы также представили простые программу и библиотеку, чтобы показать базовые средства языка С и сравнить их с эквивалентными средствами в ОС Windows.

Глава 2

Программирование средствами командной оболочки

Начав книгу с программирования в ОС Linux на языке С, теперь мы сделаем отступление и остановимся на написании программ в командной оболочке. Почему? ОС Linux не относится к системам, у которых интерфейс командной строки — запоздалое детище графического интерфейса. У систем UNIX, прообраза Linux, первоначально вообще не было графического интерфейса; все выполнялось из командной строки. Поэтому оболочка командной строки UNIX все время развивалась и превратилась в очень мощный инструмент. Эти свойства перекочевали и в Linux, и некоторые самые серьезные задачи вы можете выполнить наиболее легким способом именно из командной оболочки. Поскольку она так важна для ОС Linux и столь полезна для автоматизации простых задач, программирование средствами командной оболочки рассматривается прежде всего.

В этой главе мы познакомим вас с синтаксисом, структурами и командами, доступными при программировании в командной оболочке, как правило, используя интерактивные (основанные на экранах) примеры. Они помогут продемонстрировать функциональные возможности командной оболочки и собственные действия. Мы также бросим беглый взгляд на пару особенно полезных утилит режима командной строки, часто вызываемых из командной оболочки: grep и find. Рассматривая утилиту grep, мы познакомимся с основными положениями, касающимися регулярных выражений, которые появляются в утилитах ОС Linux и языках программирования, таких как Perl, Ruby и PHP. В конце главы вы узнаете, как писать настоящие сценарии, которые будут перепрограммироваться и расширяться на языке С на протяжении всей книги. В этой главе рассматриваются следующие темы:

□ что такое командная оболочка;

□ теоретические основы;

□ тонкости синтаксиса: переменные, условия и управление программой;

□ списки;

□ функции;

□ команды и их выполнение;

□ встроенные (here) документы;

□ отладка;

□ утилита grep и регулярные выражения;

□ утилита find.

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

Почему программа в командной оболочке?

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

Хотя внешне командная оболочка очень похожа на режим командной строки в ОС Windows, она гораздо мощнее и способна выполнять самостоятельно очень сложные программы. Вы можете не только выполнять команды и вызывать утилиты ОС Linux; но и разрабатывать их. Командная оболочка выполняет программы оболочки, часто называемые сценариями или скриптами, которые интерпретируются во время выполнения. Такой подход облегчает отладку, потому что вы легко можете выполнять программу построчно и не тратить время на перекомпиляцию. Но для задач, которым важно время выполнения или необходимо интенсивное использование процессора, командная оболочка оказывается неподходящей средой.

Немного теории

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

$ ls -al | more

Эта команда применяет утилиты ls и more и передает вывод списка файлов для поэкранного отображения. Каждая утилита — это отдельный блок. Зачастую вы можете применять множество мелких утилит для создания больших и сложных комплексов программ.

Например, если вы хотите напечатать контрольную копию справочного руководства оболочки bash, примените следующую команду:

man bash | col -b | lpr

Более того, благодаря автоматической обработке типов файлов пользователям этих утилит обычно не нужно знать, на каком языке данные программы написаны. Если необходимо ускорить выполнение утилиты, как правило, ее сначала моделируют в командной оболочке и затем, когда работоспособность утилиты проверена, реализуют ее на языке С или С++, Perl, Python или каком-либо другом, обеспечивающем более быстрое выполнение. В противном случае, если в командной оболочке утилита действует адекватно, вы вполне можете оставить ее в покое.

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

Примечание

Для удовлетворения вашего любопытства в вашу ОС Linux уже загружены многочисленные примеры сценариев, включая инсталляторы пакетов, .xinitrc и startx, и сценарии в каталоге /etc/rc.d, предназначенные для настройки системы в процессе загрузки.

Что такое командная оболочка?

Прежде чем переходить к обсуждению того, как программа использует оболочку, давайте рассмотрим, как функционирует оболочка и какие оболочки есть в Linux-подобных системах. Командная оболочка — это программа, которая действует как интерфейс между вами и ОС Linux, позволяя вам вводить команды, которые должна выполнить операционная система. В этом смысле она похожа на командную строку в ОС Windows, но, как уже упоминалось, командные оболочки Linux гораздо мощнее. Например, ввод и вывод можно перенаправить с помощью символов < и >, передавать данные между двумя одновременно выполняющимися программами с помощью символа |, а перехватывать вывод подпроцесса с помощью конструкции $(...). В ОС Linux вполне может сосуществовать несколько установленных командных оболочек, и разные пользователи могут выбрать ту, которая им больше нравится. На рис. 2.1 показано, как командная оболочка (на самом деле, две командные оболочки: bash и csh) и другие программы располагаются вокруг ядра Linux.

Рис. 2.1

Поскольку ОС Linux — модульная система, вы можете вставить и применять одну из множества различных стандартных командных оболочек, хотя большинство из них — потомки первоначальной оболочки Bourne. В Linux стандартная командная оболочка, всегда устанавливаемая как /bin/sh и входящая в комплект средств проекта GNU, называется bash (GNU Bourne-Again SHell). Именно ее мы будем применять, т. к. это отличная командная оболочка, всегда устанавливаемая в системах Linux, со свободно распространяемым программным кодом и переносимая почти на все варианты UNIX-систем. В данной главе используется оболочка bash версии 3, и в большинстве случаев применяются ее функциональные возможности, общие для всех командных оболочек, удовлетворяющих требованиям стандарта POSIX. Мы полагаем, что командная оболочка, установленная как /bin/sh и для вашей учетной записи, считается командной оболочкой по умолчанию. В большинстве дистрибутивов Linux программа /bin/sh, командная оболочка по умолчанию, — это ссылка на программу /bin/bash.

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

$ /bin/bash --version

GNU bash, version 3.2.9(1)-release (i686-pc-linux-gnu)

Copyright (C) 2005 Free Software Foundation, Inc.

Примечание

Для перехода на другую командную оболочку, если в вашей системе по умолчанию установлена не bash, просто выполните программу нужной вам командной оболочки (т.е. /bin/bash) для запуска новой оболочки и смены приглашения в командной строке. Если вы используете ОС UNIX, и командная оболочка bash не установлена, вы можете бесплатно загрузить ее с Web-сайта www.gnu.org. Исходный код обладает высокой степенью переносимости, и велика вероятность, что он откомпилируется в вашей версии UNIX прямо в готовую к использованию программу.

Когда создаются учетные записи пользователей ОС Linux, вы можете задать командную оболочку, которой они будут пользоваться, в момент создания учетной записи пользователя или позже, откорректировав ее параметры. На рис. 2.2 показан выбор командной оболочки для пользователя дистрибутива Fedora.

Рис. 2.2

Существует много других командных оболочек, распространяемых свободно или на коммерческой основе. В табл. 2.1 предлагается краткая сводка некоторых самых распространенных командных оболочек.

Таблица 2.1

Название командной оболочки Краткие исторические сведения
sh (Bourne) Первоначальная оболочка в ранних версиях ОС UNIX
csh, tcsh, zsh Командная оболочка C-shell (и ее производные), первоначально созданная Биллом Джойем (Bill Joy) для систем Berkeley UNIX. C-shell, возможно, третья по популярности командная оболочка после оболочек bash и Korn
ksh, pdksh Командная оболочка Korn и ее безлицензионный родственник. Написанная Дэвидом Корном (David Korn) эта оболочка применяется по умолчанию во многих коммерческих версиях UNIX
bash Основная командная оболочка ОС Linux из проекта GNU или Bourne Again SHell со свободно распространяемым программным кодом. Если в настоящий момент она не выполняется в вашей системе UNIX, вероятно, есть вариант оболочки, перенесенный на вашу систему. У bash много сходств с оболочкой Korn

За исключением оболочки C-shell и небольшого числа ее производных все перечисленные оболочки очень похожи и очень близки к оболочке, определенной в спецификациях Х/Оpen 4.2 и POSIX 1003.2. В спецификации POSIX 1003.2 задан минимум, необходимый для создания командной оболочки, а в спецификации Х/Open представлена более дружественная и мощная оболочка.

Каналы и перенаправление

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

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

Возможно, вы уже знакомы с некоторыми видами перенаправления, например, таким как:

$ ls -l > lsoutput.txt

сохраняющим вывод команды ls в файле с именем lsoutput.txt.

Однако перенаправление позволяет сделать гораздо больше, чем демонстрирует этот простой пример. В главе 3 вы узнаете больше о дескрипторах стандартных файлов, а сейчас вам нужно знать только то, что дескриптор файла 0 соответствует стандартному вводу программы, дескриптор файла 1 — стандартному выводу, а дескриптор файла 2 — стандартному потоку ошибок. Каждый из этих файлов можно перенаправлять независимо друг от друга. На самом деле можно перенаправлять и другие дескрипторы файлов, но, как правило, нет нужды перенаправлять любые другие дескрипторы, кроме стандартных: 0, 1 и 2.

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

Для дозаписи в конец файла используйте оператор >>. Например, команда

ps >> lsoutput.txt

добавит вывод команды ps в конец заданного файла.

Для перенаправления стандартного потока ошибок перед оператором > вставьте номер дескриптора файла, который хотите перенаправить. Поскольку у стандартного потока ошибок дескриптор файла 2, укажите оператор 2>. Часто бывает полезно скрывать стандартный поток ошибок, запрещая вывод его на экран.

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

Команда

$ kill -HUP 1234 >killout. txt 2>killer.txt

поместит вывод и информацию об ошибке в разные файлы.

Если вы предпочитаете собрать оба набора выводимых данных в одном файле, можно применить оператор >& для соединения двух выводных потоков. Таким образом, команда

$ kill -1 1234 >killerr.txt 2>&1

поместит свой вывод и стандартный поток ошибок в один и тот же файл. Обратите внимание на порядок следования операторов. Приведенный пример читается как "перенаправить стандартный вывод в файл killerr.txt, а затем перенаправить стандартный поток ошибок туда же, куда и стандартный вывод". Если вы нарушите порядок, перенаправление выполнится не так, как вы ожидаете.

Поскольку обнаружить результат выполнения команды kill можно с помощью кода завершения (который будет подробно обсуждаться далее в этой главе), часто вам не потребуется сохранять какой бы то ни было стандартный вывод или стандартный поток ошибок. Для того чтобы полностью отбросить любой вывод, вы можете использовать универсальную "мусорную корзину" Linux, /dev/null, следующим образом:

$ kill -l 1234 >/dev/null 2>&1

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

Также как вывод вы можете перенаправить ввод. Например,

$ more < killout.txt

Понятно, что это тривиальнейший пример для ОС Linux; команда more в системе Linux в отличие от своего эквивалента командной строки в ОС Windows с радостью принимает имена файлов в качестве параметров.

Каналы 

Вы можете соединять процессы с помощью оператора канала (|). В ОС Linux, в отличие от MS-DOS, процессы, соединенные каналами, могут выполняться одновременно и автоматически переупорядочиваться в соответствии с потоками данных между ними. Как пример, можно применить команду sort для сортировки вывода команды ps.

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

$ ps > psout.txt

$ sort psout.txt > pssort.out

Соединение процессов каналом даст более элегантное решение:

$ ps | sort > pssort.out

Поскольку вы, вероятно, захотите увидеть на экране вывод, разделенный на страницы, можно подсоединить третий процесс, more, и все это в одной командной строке:

$ ps | sort | more

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

$ ps -хо соmm | sort | uniq | grep -v sh | more

В ней берется вывод команды ps, сортируется в алфавитном порядке, из него извлекаются процессы с помощью команды uniq, применяется утилита grep -v sh для удаления процесса с именем sh и в завершение полученный список постранично выводится на экран.

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

cat mydata.txt | sort | uniq > mydata.txt

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

Командная оболочка как язык программирования

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

Интерактивные программы

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

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

$ for file in *

> do

> if grep -l POSIX $file

> then

> more $file

> fi

> done

posix

This is a file with POSIX in it - treat it well

$

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

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

Командная оболочка также обрабатывает групповые символы или метасимволы (часто называемые знаками подстановки). Вы почти наверняка знаете о применении символа * как знака подстановки, соответствующего строке символов. Но вы можете не знать о существовании односимвольного знака подстановки, ?, а конструкция [set] позволяет проверить любое количество одиночных символов, [^set] — применяет логическую операцию "НЕ" к множеству, т.е. включает все, кроме того, что вы задали. Подстановочный шаблон из фигурных скобок {} (доступен в некоторых командных оболочках, включая bash) позволяет формировать множество из произвольных строк, которое командная оболочка раскроет. Например, команда

$ ls my_{finger, toe}s

будет выводить файлы my_fingers и my_toes. Эта команда использует оболочку для проверки всех файлов в текущем каталоге. Мы вернемся к этим правилам соответствия шаблонам в конце главы, когда будем более подробно рассматривать утилиту grep и возможности регулярных выражений.

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

$ more `grep -l POSIX *`

или синонимической конструкции

$ more $(grep -l POSIX *)

В дополнение команда

$ grep -l POSIX * | more

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

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

Создание сценария

С помощью любого текстового редактора необходимо создать файл, содержащий команды. Создайте файл с именем first с таким содержимым:

#!/bin/sh

# first

# Этот файл просматривает все файлы в текущем каталоге

# для поиска строки POSIX, а затем выводит имена

# найденных файлов в стандартный вывод.

for file in *

do

 if grep -q POSIX $file

 then

  echo $file

 fi

done

exit 0

Комментарий начинается со знака # и продолжается до конца строки. Принято знак # ставить в первой символьной позиции строки. Сделав такое общее заявление, далее отметим, что первая строка #!/bin/sh — это особая форма комментария; символы #! сообщают системе о том, что следующий за ними аргумент — программа, применяемая для выполнения данного файла. В данном случае программа /bin/sh — командная оболочка, применяемая по умолчанию. 

Примечание

Обратите внимание на абсолютный путь, заданный в комментарии. Принято сохранять его длиной не более 32 символов для обратной совместимости, поскольку некоторые старые версии ОС UNIX могут использовать только такое ограниченное количество символов в комментарии #!, хотя у ОС Linux обычно нет подобного ограничения.

Поскольку сценарий по существу обрабатывается как стандартный ввод командной оболочки, он может содержать любые команды ОС Linux, на которые ссылается переменная окружения PATH.

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

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

В сценарии не используются никакие расширения и суффиксы имен файлов; ОС Linux и UNIX, как правило, редко применяют при именовании файлов расширения для указания типа файла. Вы могли бы использовать расширение sh или любое другое, командную оболочку это не волнует. У большинства предустановленных сценариев нет никакого расширения в именах файлов и лучший способ проверить, сценарий это или нет применить команду file, например, file first или file /bin/bash. Пользуйтесь любыми правилами, принятыми в вашей организации или удобными для вас.

Превращение сценария в исполняемый файл

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

$ /bin/sh first

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

$ chmod +х first 

Примечание

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

После этого вы можете выполнять файл с помощью команды

$ first

При этом может появиться сообщение об ошибке, говорящее о том, что команда не найдена. Почти наверняка причина в том, что в переменной PATH не задан текущий каталог для поиска выполняемых команд. Исправить это можно либо введя с клавиатуры в командной строке PATH=$PATH:., либо добавив данную команду в конец файла .bash_profile. Затем выйдите из системы и зарегистрируйтесь снова. В противном случае введите ./first в каталог, содержащий сценарий, чтобы задать командной оболочке полный относительный путь к файлу.

Указание пути, начинающегося с символов ./, дает еще одно преимущество: в этом случае вы случайно не сможете выполнить другую команду с тем же именем, что и у вашего файла сценария.

Примечание

Не следует вносить подобные изменения в переменную PATH для суперпользователя, как правило, с именем root. Это лазейка в системе безопасности, т.к. системного администратора, зарегистрировавшегося как root, обманным путём могут заставить запустить фиктивную версию стандартной команды. Один из авторов однажды разрешил сделать это — конечно только для того, чтобы поставить перед системным администратором вопрос о безопасности! В случае обычных учетных записей включение текущего каталога в полный путь сопряжено с очень небольшим риском, поэтому, если вам это нужно, примите за правило добавление комбинации символов ./ перед всеми командами, находящимися в локальном каталоге.

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

# ср first /usr/local/bin

# chown root /usr/local/bin/first

# chgrp root /usr/local/bin/first

# chmod 755 /usr/local/bin/first

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

Если захотите, можно применить более длинную, но более понятную форму команды chmod:

# chmod u=rwx, go=rx /usr/local/bin/first

Более подробную информацию можно найти в справочном руководстве команды chmod.

Примечание

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

Синтаксис командной оболочки

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

□ переменные: строки, числа, переменные окружения и параметры;

□ условия: булевы или логические выражения (Booleans);

□ управление выполнением программы: if, elif, for, while, until, case;

□ списки;

□ функции;

□ команды, встроенные в командную оболочку;

□ получение результата выполнения команды;

□ встроенные (here) документы.

Переменные

В командной оболочке переменные перед применением обычно не объявляются. Вместо этого вы создаете их, просто используя (например, когда присваиваете им начальное значение). По умолчанию все переменные считаются строками и хранятся как строки, даже когда им присваиваются числовые значения. Командная оболочка и некоторые утилиты преобразуют строки, содержащие числа, в числовые значения, когда нужно их обработать должным образом. Linux — система, чувствительная к регистру символов, поэтому командная оболочка считает foo и Foo двумя разными переменными, отличающимися от третьей переменной FOO.

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

Вы можете увидеть это в действии, если в командной строке будете задавать и проверять разные значения переменной salutation:

$ salutation=Hello

$ echo $salutation

Hello

$ salutation="Yes Dear"

$ echo $salutation

Yes Dear

$ salutation=7+5

$ echo $salutation

7+5

Примечание

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

Вы можете присвоить переменной пользовательский ввод с помощью команды read. Она принимает один параметр — имя переменной, в которую будут считываться данные, и затем ждет, пока пользователь введет какой-либо текст. Команда read обычно завершается после нажатия пользователем клавиши <Enter>. При чтении переменной с терминала, как правило, заключать ее значения в кавычки не требуется:

$ read salutation

Wie geht's?

$ echo $salutation

Wie geht's?

Заключение в кавычки

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

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

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

Выполним упражнение 2.1.

Упражнение 2.1. Игра с переменными

В этом упражнении показано, как кавычки влияют на вывод переменной:

#!/bin/sh

myvar="Hi there"

echo $myvar

echo "$myvar"

echo '$myvar'

echo \$myvar

echo Enter some text

read myvar

echo '$myvar' now equals $myvar

exit 0

Данный сценарий ведет себя следующим образом:

$ ./variable

Hi there

Hi there

$myvar

$myvar

Enter some text

Hello World

$myvar now equals Hello World

Как это работает

Создается переменная myvar, и ей присваивается строка Hi there. Содержимое переменной выводится на экран с помощью команды echo, демонстрирующей, как символ $ раскрывает содержимое переменной. Вы видите, что применение двойных кавычек не влияет на раскрытие содержимого переменной, а одинарные кавычки и обратный слэш влияют. Вы также применяете команду read для получения строки от пользователя.

Переменные окружения

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

Таблица 2.2

Переменная окружения Описание
$НОМЕ Исходный каталог текущего пользователя
$PATH Разделенный двоеточиями список каталогов для поиска команд
$PS1 Подсказка или приглашение командной строки. Часто знак $, но в оболочке bash можно применять и более сложные варианты. Например, строка [\u@\h \w]$ — популярный стандарт, сообщающий в подсказке пользователя имя компьютера и текущий каталог, а также знак $
$PS2 Дополнительная подсказка или приглашение, применяемое как приглашение для дополнительного ввода; обычно знак >
$IFS Разделитель полей ввода. Список символов, применяемых для разделения слов при чтении оболочкой ввода, как правило, пробел, знак табуляции и символ перехода на новую строку
$0 Имя сценария командной оболочки
$# Количество передаваемых параметров
$$ ID (идентификатор) процесса сценария оболочки, часто применяемый внутри сценария для генерации уникальных имен временных файлов; например, /tmp/tmpfile_$$

Примечание

Если вы хотите проверить с помощью команды env <команда>, как работает программа в разных окружениях, познакомьтесь с интерактивным справочным руководством к команде env. Далее в этой главе вы увидите, как задавать переменные окружения в подоболочках (subshells), применяя команду export.

Переменные-параметры

Если ваш сценарий вызывается с параметрами, создается несколько дополнительных переменных. Если параметры не передаются, переменная окружения $# все равно существует, но равна 0.

Переменные-параметры перечислены в табл. 2.3.

Таблица 2.3

Переменная-параметр Описание
$1, $2, ... Параметры, передаваемые сценарию
$* Список всех параметров в единственной переменной, разделенных первым символом из переменной окружения IFS. Если IFS корректируется, способ разделения командной строки на параметры в переменной $* изменяется
$@ Едва различимая вариация $*; не использует переменную окружения IFS, поэтому параметры не сольются, даже если переменная IFS пуста

Легче всего увидеть разницу между переменными-параметрами $* и $@, опробовав их.

$ IFS=''

$ set foo bar bam

$ echo "$@"

foo bar bam

$ echo "$*"

foobarbam

$ unset IFS

$ echo "$*"

foo bar bam

Как видите, заключенная в двойные кавычки переменная-параметр $@ представляет позиционные параметры как отдельные поля, независимо от значения переменной окружения IFS. Как правило, если вы хотите получить доступ к параметрам, лучше использовать переменную-параметр.

Помимо вывода на экран содержимого переменных с помощью команды echo, вы также можете прочитать его командой read (упражнение 2.2).

Упражнение 2.2. Манипулирование параметрами и переменными окружения

В приведенном далее сценарии показано несколько простых манипуляций переменными. После ввода сценария и записи его в файл try_var не забудьте превратить его в исполняемый файл с помощью команды chmod +х try_var.

#!/bin/sh

salutation="Hello"

echo $salutation

echo "The program $0 is now running"

echo "The second parameter was $2"

echo "The first parameter was $1"

echo "The parameter list was

echo "The user's home directory is $HOME"

echo "Please enter a new greeting"

read salutation

echo $salutation

echo "The script is now complete"

exit 0

Если вы выполните этот сценарий, то получите следующий вывод:

$ ./try_var foo bar baz

Hello

The program ./try_var is now running

The second parameter was bar

The first parameter was foo

The parameter list was foo bar baz

The user's home directory is /home/rick

Please enter a new greeting

Sire

Sire

The script is now complete $

Как это работает

Сценарий создает переменную salutation, выводит на экран ее содержимое и затем показывает, что уже сформированы и имеют соответствующие значения различные переменные-параметры и переменная окружения $НОМЕ.

Далее в этой главе мы рассмотрим более подробно подстановку параметров.

Условия

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

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

Команда test или [

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

Примечание

Эти команды в некоторых ранних оболочках UNIX вызывают внешнюю программу, но в более современных версиях их стараются делать встроенными. Мы вернемся к этому, когда будем рассматривать команды в следующем разделе.

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

Мы представим команду test на примере одного простейшего условия: проверки наличия файла. Для нее понадобится следующая команда: test -f <имя_файла>, поэтому в сценарии можно написать

if test -f fred.c

then

 ...

fi

To же самое можно записать следующим образом:

if [ -f fred.c ]

then

 ...

fi

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

Примечание

Имейте в виду, что вы должны вставлять пробелы между квадратной скобкой [ и проверяемым условием. Это легко усвоить, если запомнить, что вставить символ [ — это все равно, что написать test, а после имени команды вы всегда должны вставлять пробел.

Если вы предпочитаете помещать слово then в той же строке, что и if, нужно добавить точку с запятой для отделения команды test от then:

if [ -f fred.c ]; then

 ...

fi

Варианты условий, которые вы можете применять в команде test, делятся на три типа: строковые сравнения, числовые сравнения и проверка файловых флагов (file conditionals). Эти типы условий описаны в табл. 2.4.

Таблица 2.4

Варианты условий Результат
Сравнения строк
Строка1 = Строка2 True (истина), если строки одинаковы
Строка1 != Строка2 True (истина), если строки разные
-n Строка True (истина), если Строка не null
-z Строка True (истина), если Строка null (пустая строка)
Сравнения чисел
Выражение1 -eq Выражение2 True (истина), если выражения равны
Выражение1 -ne Выражение2 True (истина), если выражения не равны
Выражение1 -gt Выражение2 True (истина), если Выражение1 больше, чем Выражение2
Выражение1 -ge Выражение2 True (истина), если Выражение1 не меньше Выражение2
Выражение1 -lt Выражение2 True (истина), если Выражение1 меньше, чем Выражение2
Выражение1 -lе Выражение2 True (истина), если Выражение1 не больше Выражение2
! Выражение True (истина), если Выражение ложно, и наоборот
Файловый флаг
-d файл True (истина), если файл — каталог
файл True (истина), если файл существует. Исторически, опция -e не была переносима на другие платформы, поэтому обычно применяется -f
-f файл True (истина), если файл — обычный файл
-g файл True (истина), если для файла установлен бит set-group-id
-r файл True (истина), если файл доступен для чтения
-s файл True (истина), если файл ненулевого размера
-u файл True (истина), если для файла установлен бит set-user-id
-v файл True (истина), если файл доступен для записи
файл True (истина), если файл — исполняемый файл

Примечание

Вас могли заинтересовать непонятные биты set-group-id и set-user-id (также называемые set-gid и set-uid). Бит set-uid предоставляет программе права владельца, а не просто ее пользователя, бит set-gid предоставляет программе права группы. Эти биты устанавливаются командой chmod с помощью опций s и g. На файлы, содержащие сценарии, флаги set-gid и set-uid не влияют, они оказывают влияние только на исполняемые двоичные файлы.

Мы немного сами себя обогнали, но далее следует пример тестирования состояния файла /bin/bash, так что вы сможете увидеть, как это выглядит на практике.

#!/bin/sh

if [ -f /bin/bash ]

then

 echo "file /bin/bash exists"

fi

if [ -d /bin/bash ]

then

 echo "/bin/bash is a directory"

else

 echo "/bin/bash is NOT a directory"

fi

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

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

Управляющие структуры

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

Примечание

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

if

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

if условие

then

 операторы

else

 операторы

fi

Наиболее часто оператор if применяется, когда задается вопрос, и решение принимается в зависимости от ответа:

#!/bin/sh

echo "Is it morning? Please answer yes or no "

read timeofday

if [ $timeofday = "yes" ]; then

 echo "Good morning"

else

 echo "Good afternoon"

fi

exit 0

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

Is it morning? Please answer yes or no

yes

Good morning

$

В этом сценарии для проверки содержимого переменной timeofday применяется команда [. Результат оценивается оператором командной оболочки if, который затем разрешает выполнять разные строки программного кода.

Примечание

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

elif

К сожалению, с этим простым сценарием связано несколько проблем. Во-первых, он принимает в значении no (нет) любой ответ за исключением yes (да). Можно помешать этому, воспользовавшись конструкцией elif, которая позволяет добавить второе условие, проверяемое при выполнении части else оператора if (упражнение 2.3). 

Упражнение 2.3. Выполнение проверок с помощью elif

Вы можете откорректировать предыдущий сценарий так, чтобы он выводил сообщение об ошибке, если пользователь вводит что-либо отличное от yes или no. Для этого замените ветку else веткой elif и добавьте еще одно условие:

#!/bin/sh

echo "Is it morning? Please answer yes or no "

read timeofday

if [ $timeofday = "yes" ]

then

 echo "Good morning"

elif [ $timeofday = "no" ]; then

 echo "Good afternoon"

else

 echo "Sorry, $timeofday not recognized. Enter yes or no "

 exit 1

fi

exit 0

Как это работает

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

Проблема, связанная с переменными

Данный сценарий исправляет наиболее очевидный дефект, а более тонкая проблема остается незамеченной. Запустите новый вариант сценария, но вместо ответа на вопрос просто нажмите клавишу <Enter> (или на некоторых клавиатурах клавишу <Return>). Вы получите сообщение об ошибке:

[: =: unary operator expected

Что же не так? Проблема в первой ветви оператора if. Когда проверялась переменная timeofday, она состояла из пустой строки. Следовательно, ветвь оператора if выглядела следующим образом:

if [ = "yes" ]

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

if [ "$timeofday" = "yes" ]

Теперь проверка с пустой переменной будет корректной:

if [ "" = "yes" ]

Новый сценарий будет таким:

#!/bin/sh

echo "Is it morning? Please answer yes or no "

read timeofday

if [ "$timeofday" = "yes" ]

then

 echo "Good morning"

elif [ "$timeofday" = "no" ]; then

 echo "Good afternoon"

else

 echo "Sorry, $timeofday not recognized. Enter yes or no "

 exit 1

fi

exit 0

Этот вариант безопасен, даже если пользователь в ответ на вопрос просто нажмет клавишу <Enter>.

Примечание

Если вы хотите, чтобы команда echo удалила новую строку в конце, наиболее легко переносимый вариант — применить команду printf (см. разд. "printf" далее в этой главе) вместо команды echo. В некоторых командных оболочках применяется команда echo -е, но она поддерживается не всеми системами. В оболочке bash для запрета перехода на новую строку допускается команда echo -n, поэтому, если вы уверены, что вашему сценарию придется трудиться только в оболочке bash, предлагаем вам использовать следующий синтаксис:

echo -n "Is it morning? Please answer yes or no: "

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

for

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

Синтаксис этого оператора прост:

for переменная in значения

do

 операторы

done

Выполните упражнения 2.4 и 2.5.

Упражнение 2.4. Применение цикла for к фиксированным строкам

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

#!/bin/sh

for foo in bar fud 43

do

 echo $foo

done

exit 0

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

bar

fud

43

Примечание

Что произойдет, если вы измените первую строку с for foo in bar fud 43 на for foo in "bar fud 43"? Напоминаем, что вставка кавычек заставляет командную оболочку считать все, что находится между ними, единой строкой. Это один из способов сохранения пробелов в переменной.

Как это работает

В данном примере создается переменная foo и ей в каждом проходе цикла for присваиваются разные значения. Поскольку оболочка считает по умолчанию все переменные строковыми, применять строку 43 так же допустимо, как и строку fud.

Упражнение 2.5. Применение цикла for с метасимволами

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

Вы уже видели этот прием в первом примере first. В сценарии применялись средства подстановки командной оболочки — символ * для подстановки имен всех файлов из текущего каталога. Каждое из этих имен по очереди используется в качестве значения переменной $file внутри цикла for.

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

#!/bin/sh

for file in $(ls f*.sh); do

 lpr $file

done

exit 0

Как это работает

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

Командная оболочка раскрывает f*.sh, подставляя имена всех файлов, соответствующих данному шаблону.

Примечание

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

while

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

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

while  условие

do

 операторы

done

Далее приведен пример довольно слабой программы проверки паролей.

#!/bin/sh

echo "Enter password"

read trythis

while [ "$trythis" != "secret" ]; do

 echo "Sorry, try again"

 read trythis

done

exit 0

Следующие строки могут служить примером вывода данного сценария:

Enter password

password

Sorry, try again

secret

$

Ясно, что это небезопасный способ выяснения пароля, но он вполне подходит для демонстрации применения цикла while. Операторы, находящиеся между операторами do и done, выполняются бесконечное число раз до тех пор, пока условие остается истинным (true). В данном случае вы проверяете, равно ли значение переменной trythis строке secret. Цикл будет выполняться, пока $trythis не равно secret. Затем выполнение сценария продолжится с оператора, следующего сразу за оператором done.

until

У цикла until следующая синтаксическая запись:

until  условие

do

 операторы

done

Она очень похожа на синтаксическую запись цикла while, но с обратным проверяемым условием. Другими словами, цикл продолжает выполняться, пока условие не станет истинным (true).

Примечание

Как правило, если нужно выполнить цикл хотя бы один раз, применяют цикл while; если такой необходимости нет, используют цикл until.

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

#!/bin/bash

until who | grep "$1" > /dev/null

do

 sleep 60

done

# Теперь звонит колокольчик и извещает о новом пользователе

echo -е '\а'

echo "**** $1 has just logged in ****"

exit 0

Если пользователь уже зарегистрировался в системе, выполнять цикл нет необходимости. Поэтому естественно выбрать цикл until, а не цикл while.

case

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

case переменная  in

 образец [ | образец] ...) операторы;;

 образец [ | образец] ...) операторы;;

esac

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

Примечание

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

Возможность сопоставлять многочисленные образцы и затем выполнять множественные связанные с образцом операторы делают конструкцию case очень удобной для обработки пользовательского ввода. Лучше всего увидеть, как работает конструкция case на примерах. Мы будем применять ее в упражнениях 2.6–2.8, каждый раз совершенствуя сопоставление с образцами.

Примечание

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

Упражнение 2.6. Вариант 1: пользовательский ввод

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

#!/bin/sh

echo "Is it morning? Please answer yes or no "

read timeofday

case "$timeofday" in

 yes) echo "Good Morning";;

 no ) echo "Good Afternoon";;

 y  ) echo "Good Morning";;

 n  ) echo "Good Afternoon";;

 *  ) echo "Sorry, answer not recognized";;

esac

exit 0

Как это работает

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

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

Упражнение 2.7. Вариант 3: объединение образцов

Предыдущая версия конструкции case, безусловно, элегантнее варианта с множественными операторами if, но, объединив все образцы, можно создать более красивую версию.

#!/bin/sh

echo "Is it morning? Please answer yes or no "

read timeofday

case "$timeofday" in

 yes | y | Yes | YES ) echo "Good Morning";;

 n* | N*)              echo "Good Afternoon";;

 * )                   echo "Sorry, answer not recognized";;

esac

exit 0

Как это работает

Данный сценарий в операторе case использует несколько строк-образцов в каждой ветви, таким образом, case проверяет несколько разных строк для каждого возможного оператора. Этот прием делает сценарий короче и, как показывает практика, облегчает его чтение. Приведенный программный код также показывает, как можно использовать метасимвол *, несмотря на то, что он может соответствовать непредусмотренным образцам. Например, если пользователь введет строку never, она будет соответствовать образцу n*, и на экран будет выведено приветствие Good Afternoon (Добрый день), хотя такое поведение в сценарии не предусматривалось. Учтите также, что заключенный в кавычки знак подстановки * не действует.

Упражнение 2.8. Вариант 3: выполнение нескольких операторов

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

#!/bin/sh

echo "Is it -morning? Please answer yes or no"

read timeofday

case "$timeofday" in

 yes | y | Yes | YES )

  echo "Good Morning"

  echo "Up bright and early this morning"

  ;;

 [nN]*)

  echo "Good Afternoon"

  ;;

 *)

  echo "Sorry, answer not recognized"

  echo "Please answer yes or no"

  exit 1

  ;;

esac

exit 0

Как это работает

Для демонстрации другого способа определения соответствия образцу в этом программном коде изменен вариант определения соответствия для ветви no. Также видно, как в каждой ветви оператора case может выполняться несколько операторов. Следует быть внимательным и располагать в операторе самые точные образцы строк первыми, а самые общие варианты образцов последними. Это очень важно, потому что оператор case выполняется, как только найдено первое, а не наилучшее соответствие. Если вы поставите ветвь *) первой, совпадение с этим образцом будет определяться всегда, независимо от варианта введенной строки.

Примечание

Учтите, что сдвоенная точка с запятой ;; перед служебным словом esac необязательна. В отличие от программирования на языке С, в котором пропуск маркера завершения считается плохим стилем программирования, пропуск ;; не создает проблем, если последняя ветвь оператора case — это вариант, принятый по умолчанию, поскольку другие образцы уже не будут анализироваться.

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

[yY] | [Yy][Ее][Ss])

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

Списки

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

if [ -f this_file ]; then

 if [ -f that_file ]; then

  if [ -f the_other_file ]; then

   echo "All files present, and correct"

  fi

 fi

fi

Или вы хотите, чтобы хотя бы одно условие из последовательности условий было истинным.

if [ -f this_file ]; then

 foo="True"

elif [ -f that_file ]; then

 foo="True"

elif [ -f the_other_file ];

 then foo="True"

else

 foo="False"

fi

if ["$foo" = "True" ]; then

 echo "One of the files exists"

fi

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

И-cписок

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

оператор1 && оператор2 && оператор3  && ...

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

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

Выполните упражнение 2.9.

Упражнение 2.9. И-списки

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

#!/bin/sh

touch file_one

rm -f file_two

if [ -f file_one ] && echo "hello" [ -f file_two ] && echo " there"

then

 echo "in if"

else

 echo "in else"

fi

exit 0

Попробуйте выполнить сценарий, и вы получите следующий вывод:

hello

in else

Как это работает

Команды touch и rm гарантируют, что файлы в текущем каталоге находятся в известном состоянии. Далее И-список выполняет команду [ -f file one ], которая возвращает значение true, потому что вы только что убедились в наличии файла. Поскольку предыдущий оператор завершился успешно, теперь выполняется команда echo. Она тоже завершается успешно (echo всегда возвращает true). Затем выполняется третья проверка [ -f file_two ]. Она возвращает значение false, т.к. файл не существует. Поскольку последняя команда вернула false, заключительная команда echo не выполняется. В результате И-список возвращает значение false, поэтому в операторе if выполняется вариант else.

ИЛИ-список

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

оператор1 || оператор2 || оператор3 || ...

Операторы выполняются слева направо. Если очередной оператор возвращает значение false, выполняется следующий за ним оператор. Это продолжается до тех пор, пока очередной оператор не вернет значение true, после этого никакие операторы уже не выполняются.

ИЛИ-список очень похож на И-список, за исключением того, что правило для выполнения следующего оператора — выполнение предыдущего оператора со значением false.

Рассмотрим упражнение 2.10.

Упражнение 2.10. ИЛИ-списки

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

#!/bin/sh

rm -f file_one

if [ -f file_one ] || echo "hello" || echo " there" then

 echo "in if"

else

 echo "in else"

fi

exit 0

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

hello

in if

Как это работает

В первых двух строках просто задаются файлы для остальной части сценария. Первая команда списка [ -f file one ] возвращает значение false, потому что файла в каталоге нет. Далее выполняется команда echo. Вот это да — она возвращает значение true, и больше в ИЛИ-списке не выполняются никакие команды. Оператор if получает из списка значение true, поскольку одна из команд ИЛИ-списка (команда echo) вернула это значение.

Результат, возвращаемый обоими этими списками, — это результат последней выполненной команды списка.

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

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

[ -f file_one ] && команда в случае true || команда в случае false

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

Операторные блоки

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

get_confirm && {

 grep -v "$cdcatnum" $tracks_file > $temp_file

 cat $temp_file > $tracks_file

 echo

 add record_tracks

}

Функции

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

Примечание

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

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

Имя_функции() {

операторы

}

Выполните упражнения 2.11 и 2.12.

Упражнение 2.11. Простая функция

Давайте начнем с действительно простой функции.

#!/bin/sh

foo() {

 echo "Function foo is executing"

}

echo "script starting"

foo

echo "script ended"

exit 0

Выполняющийся сценарий, выведет на экран следующий текст:

script starting

Function foo is executingscript ended

Как это работает

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

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

Когда функция вызывается, позиционные параметры сценария $*, $@, $#, $1, $2 и т.д. заменяются параметрами функции. Именно так вы считываете параметры, передаваемые функции. Когда функция завершится, они восстановят свои прежние значения.

Примечание

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

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

foo() { echo JAY;}

...

result="$(foo)"

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

#!/bin/sh

sample_text="global variable"

foo() {

 local sample_text="local variable"

 echo "Function foo is executing"

 echo $sample_text

}

echo "script starting"

echo $sample_text

foo

echo "script ended"

echo $sample_text

exit 0

При отсутствии команды return, задающей возвращаемое значение, функция возвращает статус завершения последней выполненной команды,

Упражнение 2.12. Возврат значения

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

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

#!/bin/sh

yes_or_no() {

 echo "Is your name $* ? "

 while true

 do

  echo -n "Enter yes or no: "

  read x

  case "$x" in

   y | yes ) return 0;;

   n | no )  return 1;;

   * )       echo "Answer yes or no"

  esac

 done

}

2. Далее начинается основная часть программы.

echo "Original parameters are $*"

if yes_or_no "$1"

then

 echo "Hi $1, nice name"

else

 echo "Never mind"

fi

exit 0

Типичный вывод этого сценария может выглядеть следующим образом:

$ ./my_name Rick Neil

Original parameters are Rick Neil

Is your name Rick ?

Enter yes or no: yes

Hi Rick, nice name

$

Как это работает

Когда сценарий начинает выполняться, функция определена, но еще не выполняется. В операторе if сценарий вызывает функцию yes_or_no, передавая ей оставшуюся часть строки как параметры после замены $1 первым параметром исходного сценария строкой Rick. Функция использует эти параметры, в данный момент хранящиеся в позиционных параметрах $1, $2 и т.д., и возвращает значение в вызывающую программу. В зависимости от возвращенного функцией значения конструкция if выполняет один из операторов.

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

Команды

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

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

break

Используйте команду break для выхода из циклов for, while и until до того, как будет удовлетворено управляющее условие. В команде break можно задать дополнительный числовой параметр, указывающий на число циклов, из которых предполагается выход. Однако это может сильно усложнить чтение сценариев, поэтому мы не советуем вам использовать его. По умолчанию break обеспечивает выход из одного цикла.

#!/bin/sh

rm -rf fred*

echo > fred1

echo > fred2

mkdir fred3

echo > fred4

for file in fred*

do

 if [ -d "$file" ]; then

  break;

 fi

done

echo first directory starting fred was $file

m -rf fred*

exit 0

Команда :

Команда "двоеточие" — фиктивная команда. Она иногда полезна для упрощения логики в условиях, будучи псевдонимом команды true. Поскольку команда : встроенная, она выполняется быстрее, чем true, хотя ее вывод гораздо менее читабелен.

Вы можете найти эту команду в условии для циклов while. Конструкция while : выполняет бесконечный цикл вместо более общего while true.

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

: ${var:=value}

Без : командная оболочка попытается интерпретировать $var как команду.

Примечание

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

#!/bin/sh

rm -f fred

if [ -f fred ]; then

 :

else

 echo file fred did not exist

fi

exit 0

continue

Как и одноименный оператор языка С, эта команда заставляет охватывающий ее цикл for, while или until начать новый проход или следующую итерацию. При этом переменная цикла принимает следующее значение в списке.

#!/bin/sh

rm -rf fred*

echo > fred1

echo > fred2

mkdir fred3

echo > fred4

for file in fred*

do

 if [ -d "$file" ]; then

  echo "skipping directory $file"

  continue

 fi

 echo file is $file

done

rm -rf fred*

exit 0

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

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

for x in 1 2 3

do

 echo before $x

 continue 1

 echo after $x

done

У приведенного фрагмента будет следующий вывод:

before 1

before 2

before 3

Команда .

Команда "точка" (.) выполняет команду в текущей оболочке:

. ./shell_script

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

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

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

Выполните упражнение 2.13.

Упражнение 2.13. Команда точка

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

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

#!/bin/sh

version=classic

PATH=/usr/local/old_bin:/usr/bin:/bin:

.

PS1="classic> "

2. Для новых команд применяется latest_set.

#!/bin/sh

version=latest

PATH=/usr/local/new_bin:/usr/bin:/bin:

.

PS1=" latest version> "

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

$ . ./classic_set

classic> echo $version

classic

classic> . /latest_set

latest version> echo $version

latest

latest version>

Как это работает

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

echo

Несмотря на призыв группы Х/Open применять в современных командных оболочках команду printf, мы будем продолжать следовать общепринятой практике использования команды echo для вывода строки с последующим переходом на новую строку.

При этом возникает общая проблема: удаление символа перехода на новую строку. К сожалению, в разных версиях ОС UNIX реализованы разные решения. В ОС Linux общепринятый метод

echo -n "string to output"

Но вы часто будете сталкиваться и с вариантом

echo -е "string to output\c"

Второй вариант echo -е рассчитан на то, что задействована интерпретация символов escape-последовательности, начинающихся с обратного слэша, таких как \c для подавления новой строки, \t для вывода табуляции, \n для вывода символов возврата каретки. В более старых версиях bash этот режим установлен по умолчанию, а в более современных версиях интерпретация символов escape-последовательностей с обратным слэшем отключена. Подробные сведения о поведении вашего дистрибутива ищите на страницах интерактивного справочного руководства.

Примечание

Если вам нужен легко переносимый способ удаления завершающей новой строки, для избавления от нее можно воспользоваться внешней командой tr, но она будет выполняться немного медленнее. Если вашим системам UNIX нужна переносимость и нужно избавиться от завершающей новой строки, как правило, лучше придерживаться команды printf. Если ваши сценарии предназначены для работы только в ОС Linux и bash, вполне подойдет echo -n, хотя, возможно, придется начинать файл со строки #!/bin/bash для того, чтобы в явной форме показать, что вы рассчитываете на поведение в стиле bash.

eval

Команда eval позволяет вычислять аргументы. Она встроена в командную оболочку и обычно не представлена как отдельная команда. Лучше всего ее действие демонстрирует короткий пример, позаимствованный непосредственно из стандарта X/Open.

foo=10

x=foo

у='$'$х

echo $у

Будет выведено $foo. Однако код

foo=10

x=foo

eval у='$'$х

echo $у

выведет на экран 10. Таким образом, eval немного похожа на дополнительный знак $: она возвращает значение значения переменной.

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

exec

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

Например, строка

exec wall "Thanks for all the fish"

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

Второй вариант применения exec — модификация текущих дескрипторов файлов.

exec 3< afile

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

exit n

Команда exit вызывает завершение сценария с кодом завершения n. Если вы примените ее в строке подсказки или приглашения любой интерактивной командной оболочки, она приведет к вашему выходу из системы. Если разрешить сценарию завершиться без указания кода завершения, статус последней выполненной в сценарии команды используется как возвращаемое значение. Задание кода завершения считается хорошим стилем программирования.

При программировании сценариев в командной оболочке код завершения 0 — успешное завершение сценария, коды от 1 до 125 включительно — коды ошибок, которые можно использовать в сценариях. Оставшиеся значения зарезервированы в соответствии с табл. 2.5.

Таблица 2.5

Код завершения Описание
126 Файл не является исполняемым
127 Команда не найдена
128 и выше Появившийся сигнал

Многим программистам на языках С и С++ использование нуля как признака успешного завершения может показаться несколько необычным. Большое преимущество сценариев — возможность применения 125 кодов ошибок, определенных пользователем, и отсутствие необходимости в глобальной переменной для хранения кода ошибки.

Далее приведен простой пример, возвращающий код успешного завершения, если в текущем каталоге существует файл с именем .profile.

#!/bin/sh

if [ -f .profile ]; then

 exit 0

fi

exit 1

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

[ -f .profile ] && exit 0 || exit 1

export

Команда export делает переменную, называемую ее параметром, доступной в подоболочках. По умолчанию переменные, созданные в командной оболочке, не доступны в новых дочерних подоболочках, запускаемых из данной. Команда export создает из своего параметра переменную окружения, которая видна другим сценариям и программам, запускаемым из текущей программы. Говоря профессиональным языком, экспортируемые переменные формируют переменные окружения в любых дочерних процессах, порожденных командной оболочкой. Лучше всего проиллюстрировать это примером из двух сценариев: export1 и export2 (упражнение 2.14).

Упражнение 2.14. Экспорт переменных

1. Первым представим сценарий export2.

#!/bin/sh

echo "$foo"

echo "$bar"

2. Теперь сценарий export1. В конце сценария запускается export2.

#!/bin/sh

foo="The first meta-syntactic variable"

export bar="The second meta-syntactic variable"

export2

Если вы запустите их, то получите следующий результат.

$ ./export1

The second meta-syntactic variable

$

Как это работает

Сценарий export2 просто выводит значения двух переменных. В сценарии export1 задаются значения обеих переменных, но только переменная bar помечается как экспортируемая, поэтому, когда впоследствии запускается сценарий export2, значение переменной foo потеряно, а значение переменной bar экспортировано во второй сценарий. На экране появляется пустая строка, поскольку $foo ничего не содержит и вывод переменной со значением null приводит к отображению новой строки.

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

Примечание

Команды set -а или set -allexport экспортируют все переменные соответственно.

expr

Команда expr вычисляет выражение, составленное из ее аргументов. Чаще всего она применяется для подсчета простых арифметических выражений в следующем виде:

х=`expr $x + 1`

Символы `` (обратная кавычка или обратный апостроф) заставляют переменную х принять результат выполнения команды expr $х + 1. Ее можно также записать с помощью синтаксической конструкции $( ) вместо обратной кавычки, например, следующим образом:

х=$(expr $х + 1)

Команда expr обладает большими возможностями, с ее помощью можно вычислять различные выражения. Основные виды вычислений перечислены в табл. 2.6.

Таблица 2.6

Вычисление выражения Описания
Выражение1 | Выражение2 Выражение1, если Выражение1 не равно нулю, в противном случае Выражение2
Выражение1 & Выражение2 Нуль, если оба выражения равны нулю, в противном случае Выражение1
Выражение1 = Выражение2 Равенство
Выражение1 > Выражение2 Больше чем
Выражение1 >= Выражение2 Больше или равно
Выражение1 < Выражение2 Меньше чем
Выражение1 <= Выражение2 Меньше или равно
Выражение1 != Выражение2 Неравенство
Выражение1 + Выражение2 Сложение
Выражение1Выражение2 Вычитание
Выражение1 * Выражение2 Умножение
Выражение1 / Выражение2 Деление нацело
Выражение1 % Выражение2 Остаток от деления нацело

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

printf

Команда printf есть только в современных командных оболочках. Группа X/Open полагает, что ее следует применять вместо команды echo для генерации форматированного вывода, несмотря на то, что, кажется, лишь немногие следуют этому совету.

У команды следующая синтаксическая запись.

printf "строка формата" параметр1 параметр2 ...

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

В табл. 2.7 приведены поддерживаемые командой escape-последовательности.

Таблица 2.7

Escape-последовательность Описание
\" Двойная кавычка
\\ Символ обратный слэш
\a Звуковой сигнал тревоги (звонок колокольчика или прерывистый звуковой сигнал)
\b Символ Backspace (стирание слева)
\c Отбрасывание последующего вывода
\f Символ Form feed (подача бумаги)
\n Символ перехода на новую строку
\r Возврат каретки
\t Символ табуляции
\v Символ вертикальной табуляции
\ooo Один символ с восьмеричным значением ooo
\xHH Один символ с шестнадцатеричным значением HH

Спецификаторы преобразований довольно сложны, поэтому мы приведем наиболее распространенные варианты их применения. Более подробную информацию можно найти в интерактивном справочном руководстве командной оболочки bash или на страницах раздела 1 интерактивного руководства к команде printf (man 1 printf). (Если вы не найдете нужных сведений в разделе 1, попробуйте поискать в разделе 3.) Спецификатор преобразования состоит из символа %, за которым следует символ преобразования. Основные варианты преобразований перечислены в табл. 2.8.

Таблица 2.8

Символ преобразования  Описание
D Вывод десятичного числа
С Вывод символа
S Вывод строки
% Вывод знака %

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

$ printf "%s\n" hello

hello

$ printf "%s %d\t%s" "Hi There" 15 people

Hi There 15 people

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

return

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

set

Команда set задает переменные-параметры командной оболочки. Она может быть полезна при использовании полей в командах, выводящих значения, разделенные пробелами.

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

#!/bin/sh

echo the date is $(date)

set $(date)

echo The month is $2

exit 0

Программа задает список параметров для вывода команды date и затем использует позиционный параметр $2 для получения названия месяца.

Мы использовали команду date только как простой пример, демонстрирующий, как извлекать позиционные параметры. Поскольку команда date зависит от языковых параметров или локализации, в действительности мы бы извлекли название месяца командой date +%B. У команды date много других вариантов форматирования, более подробную информацию см. на страницах интерактивного справочного руководства к команде.

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

shift

Команда shift сдвигает все переменные-параметры на одну позицию назад, так что параметр $2 становится параметром $1, параметр $3$2 и т.д. Предыдущее значение параметра $1 отбрасывается, а значение параметра $0 остается неизменным. Если в вызове команды shift задан числовой параметр, параметры сдвигаются на указанное количество позиций. Остальные переменные $*, $@ и $# также изменяются в связи с новой расстановкой переменных-параметров.

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

Например, вы можете просмотреть все позиционные параметры:

#!/bin/sh

while [ "$1" != "" ]; do

 echo "$1"

 shift

done

exit 0

trap

Команда trap применяется для задания действий, предпринимаемых при получении сигналов, которые подробно будут обсуждаться далее в этой книге. Обычное действие — удалить сценарий, когда он прерван. Исторически командные оболочки всегда использовали числа для обозначения сигналов, но в современных сценариях следует применять имена, которые берутся из файла signal.h директивы #include с опущенным префиксом SIG. Для того чтобы посмотреть номера сигналов и соответствующие им имена, можно ввести в командной строке команду trap -l.

Примечание

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

С помощью команды trap передается предпринимаемое действие, за которым следует имя (имена) сигнала для перехвата:

trap команда сигнал

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

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

В табл. 2.9 перечислены самые важные, включенные в. стандарт Х/Open сигналы, которые можно отследить (со стандартными номерами в скобках). Дополнительную информацию можно найти на страницах раздела 7 интерактивного справочного руководства, посвященного сигналам (man 7 signal).

Таблица 2.9

Сигнал Описание
HUP (1) Неожиданный останов; обычно посылается, когда отключается терминал или пользователь выходит из системы
INT (2) Прерывание; обычно посылается нажатием комбинации клавиш <Ctrl>+<C>
QUIT (3) Завершение выполнения; обычно посылается нажатием комбинации клавиш <Ctrl>+<\>
ABRT (6) Аварийное завершение; обычно посылается при возникновении серьезной ошибки выполнения
ALRM (14) Аварийный сигнал; обычно посылается для обработки превышений лимита времени
TERM (15) Завершение; обычно посылается системой, когда она завершает работу

А теперь выполните упражнение 2.15.

Упражнение 2.15. Сигналы прерываний

В следующем сценарии показана простая обработка сигнала.

#!/bin/sh

trap 'rm -f /tmp/my_tmp_file_$$' INT

echo creating file /tmp/my_tmp_file_$$

date > /tmp/my_tmp_file_$$

echo "press interrupt (CTRL-C) to interrupt..."

while [ -f /tmp/my_tmp_file_$$ ] ; do

 echo File exists

 sleep 1

done

echo The file no longer exists trap INT

echo creating file /tmp/my_tmp_file_$$

date > /tmp/my_tmp_file_$$

echo "press interrupt (CTRL-C) to interrupt..."

while [ -f /tmp/my_tmp_file_$$ ]; do

 echo File exists

 sleep 1

done

echo we never get here

exit 0

Если вы выполните этот сценарий, нажимая и удерживая нажатой клавишу <Ctrl> и затем нажимая клавишу <C> (или любую другую прерывающую комбинацию клавиш) в каждом из циклов, то получите следующий вывод:

creating file /tmp/my_tmp_file_141

press interrupt (CTRL-C) to interrupt ...

File exists

File exists

File exists

File exists

The file no longer exists

creating file /tmp/my tmp_file_141

press interrupt (CTRL-C) to interrupt ...

File exists

File exists

File exists

File exists

Как это работает

Сценарий использует команду trap для организации выполнения команды rm -f /tmp/my_tmp_file_$$ при возникновении сигнала INT (прерывание). Затем сценарий выполняет цикл while до тех пор, пока существует файл. Когда пользователь нажимает комбинацию клавиш <Ctrl>+<C>, выполняется команда rm -f /tmp/my_tmp_file_$$, а затем возобновляется выполнение цикла while. Поскольку теперь файл удален, первый цикл while завершается стандартным образом.

Далее сценарий снова применяет команду trap, на этот раз для того, чтобы сообщить, что при возникновении сигнала INT никакая команда не выполняется. Затем сценарий создает заново файл и выполняет второй цикл while. Когда пользователь снова нажимает комбинацию клавиш <Ctrl>+<C>, не задана команда для выполнения, поэтому реализуется стандартное поведение: немедленное прекращение выполнения сценария. Поскольку сценарий завершается немедленно, заключительные команды echo и exit никогда не выполняются.

unset

Команда unset удаляет переменные или функции из окружения. Она не может проделать это с переменными, предназначенными только для чтения и определенными командной оболочкой, такими как IFS. Команда применяется редко.

В следующем сценарии сначала выводится строка Hello world, а во второй раз новая строка.

#!/bin/sh

foo="Hello World"

echo $foo

unset foo

echo $foo

Примечание

Написание foo= подобно, но не идентично применению команды unset в только что приведенной программе. Оператор foo= задает для переменной foo значение null, но при этом переменная foo все еще существует. Команда unset foo удаляет из окружения переменную foo.

Еще две полезные команды и регулярные выражения

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

Команда find

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

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

# find / -name test -print

/usr/bin/test

#

В зависимости от варианта установки системы на вашей машине вы можете найти и другие файлы, также названные test. Как вы, вероятно, догадываетесь, команда звучит так: "искать, начиная с каталога /, файл с именем test и затем вывести на экран имя файла". Легко, не правда ли? Безусловно.

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

В этом случае на помощь приходит первая опция. Если вы укажете опцию -mount, то сможете сообщить команде find о том, что смонтированные каталоги проверять не нужно.

# find / -mount -name test -print

/usr/bin/test

#

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

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

find [путь] [опции] [критерии] [действия]

Часть записи [путь] понятна и проста: вы можете указать абсолютный путь поиска, например, /bin, или относительный, например .. При необходимости можно задать несколько путей — например, find /var /home.

В табл. 2.10 перечислены основные опции команды.

Таблица 2.10

Опция Описание
-depth Поиск в подкаталогах перед поиском в самом каталоге
-follow Следовать по символическим ссылкам
-maxdepths N При поиске проверять не более N вложенных уровней каталога
-mount (или -xdev) Не искать в каталогах других файловых систем

Теперь о критериях. В команде find можно задать большое число критериев, и каждый из них возвращает либо true, либо false. В процессе работы команда find рассматривает по очереди каждый файл и применяет к нему все критерий в порядке их определения. Если очередной критерий возвращает значение false, команда find прекращает анализ текущего файла и переходит к следующему; если критерий возвращает значение true, команда применяет следующий критерий к текущему файлу или совершает заданное действие над ним. В табл. 2.11 перечислены самые распространенные критерии; полный список тестов, которые можно применять в команде find, вы найдете на страницах интерактивного справочного руководства.

Таблица 2.11

Критерий Описание
-atime N К файлу обращались последний раз N дней назад
-mtime N Файл последний раз изменялся N дней назад
-name шаблон Имя файла без указания пути соответствует заданному шаблону. Для гарантии того, что шаблон будет передан в команду find и не будет немедленно обработан командной оболочкой, его следует всегда заключать в кавычки
-newer другой файл Текущий файл, измененный позже, чем другой файл
-type С Файл типа C, где C может принимать определенные значения; наиболее широко используемые "d" для каталогов и "f" для обычных файлов. Остальные обозначения типов можно посмотреть на страницах интерактивного справочного руководства
-user имя пользователя Файл принадлежит пользователю с заданным именем

Вы также можете объединять критерии с помощью операторов. Как показано в табл. 2.12, у большинства из них две формы записи: короткая и более длинная форма.

Таблица 2.12

Оператор, короткая форма Оператор, длинная форма Описание
! -not Инвертирование критерия
-and Оба критерия должны быть истинны
-or Один из критериев должен быть истинным

Изменить порядок проверки критериев и выполнения операторов можно с помощью скобок. Поскольку в командной оболочке у них есть особое назначение, скобки также следует выделять с помощью обратного слэша. Кроме того, если вы применяете шаблон для имени файла, то следует использовать кавычки, чтобы оболочка не выполняла подстановку имени, а прямо передала шаблон команде find. Например, если вы хотите задать критерий "измененный позже, чем файл X, или с именем, начинающимся со знака подчеркивания", его можно записать следующим образом:

\(-newer X -о -name "_*" \)

Мы приведем пример сразу после описания "Как это работает". А сейчас выполните упражнение 2.16.

Упражнение 2.16 Применение команды find с критериями

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

$ find . -newer while2 -print

.

./elif3

./words.txt

./words2.txt

./_trap

$

Все чудесно, за исключением того, что вы нашли ненужный вам текущий каталог. Вас интересуют только обычные файлы, поэтому добавьте дополнительный критерий -type f.

$ find . -newer while2 -type f -print

./elif3

./words.txt

./words2.txt

./_trap

$

Как это работает

Как это работает? Вы определили, что команда find должна искать в текущем каталоге (.) файлы, измененные позже, чем файл while2 (-newer while2), и, если этот критерий пройден, проверять с помощью следующего критерия (-type f), обычные ли это файлы. В заключение вы применили действие, с которым уже сталкивались, -print, просто для того чтобы подтвердить, что файлы были найдены.

Теперь найдем файлы с именами, начинающимися с символа подчеркивания или измененные позже, чем файл while2, но в любом случае обычные файлы. Этот пример покажет, как объединять критерии с помощью скобок.

$ find . \( -name "_*" -or -newer while2 \) -type f -print

./elif3

./words.txt

./words2.txt

./_break

./_if

./set

./_shift

./_trap

./_unset

./ until

$

Это не слишком трудный пример, не так ли? Вы должны экранировать скобки, чтобы они не обрабатывались командной оболочкой, и заключить в кавычки символ *, чтобы он также был передан непосредственно в команду find.

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

Таблица 2.13

Действие Описание
-exec команда Выполняеткоманду. Наиболее широко используемое действие. После табл. 2.13 приведено объяснение способа передачи параметров в команду. Это действие следует завершать символьной парой \;
-ok команда Подобно действию exec, за исключением того, что перед обработкой файловкомандой выводится подсказка для получения подтверждения пользователя на обработку каждого файла. Это действие следует завершать символьной парой \;
-print Вывод на экран имени файла
-ls Применение команды ls -dils к текущему файлу

Команда в аргументах -exec и -ok принимает последующие параметры в строке как собственные, пока не встретится последовательность \; В действительности команда, в аргументах -exec и -ok выполняет встроенную команду, поэтому встроенная команда должна завершиться экранированной точкой с запятой, для того чтобы команда find могла определить, когда ей следует продолжить поиск в командной строке аргументов, предназначенных для нее самой. Магическая строка {} — параметр специального типа для команд -exec и -ok, который заменяется полным путем к текущему файлу.

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

$ find . -newer while2 -type f -exec ls -l  {} \;

-rwxr-xr-x 1 rick rick  275 Feb 8 17:07 ./elif3

-rwxr-xr-x 1 rick rick  336 Feb 8 16:52 ./words.txt

-rwxr-xr-x 1 rick rick 1274 Feb 8 16:52 ./words2.txt

-rwxr-xr-x 1 rick rick  504 Feb 8 18:43 ./_trap

$

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

Команда grep

Вторая очень полезная команда, заслуживающая рассмотрения, — это команда grep. Необычное имя, означающее общий синтаксический анализатор регулярных выражений (general regular expression parser). Вы применяете команду find для поиска файлов в вашей системе, а команду grep для поиска строк в ваших файлах. Действительно, очень часто при использовании команды find команда grep передается после аргумента -exec.

Команда grep принимает опции, шаблон соответствия и файлы для поиска:

grep [опции] шаблон [файлы]

Если имена файлов не заданы, команда анализирует стандартный ввод.

Давайте начнем с изучения основных опций команды grep. И на этот раз в табл. 2.14 приведены только самые важные из них; полный список см. на страницах интерактивного справочного руководства.

Таблица 2.14

Опция Описание
Вместо вывода на экран совпавших с шаблоном строк выводит их количество
-E Включает расширенные регулярные выражения
-h Ужимает обычное начало каждой строки вывода за счет удаления имени файла, в котором строка найдена
-i Не учитывает регистр букв
-l Перечисляет имена файлов со строками, совпадающими с шаблоном; не выводит сами найденные строки
-v Меняет шаблон соответствия для выбора вместо строк, соответствующих шаблону, несовпадающих с ним строк

Выполните упражнение 2.17.

Упражнение 2.17. Основной вариант использования команды grep

Посмотрим команду grep в действии на примерах простых шаблонов.

$ grep in words.txt

When shall we three meet again. In thunder, lightning, or in rain?

I come, Graymalkin!

$ grep -c in words.txt words2.txt

words.txt:2 words2.txt:14

$ grep -c -v in words.txt words2.txt

words.txt:9

words2.txt:16$

Как это работает

В первом примере нет опций; в нем просто ищется строка in в файле words.txt и выводятся на экран любые строки, соответствующие условию поиска. Имя файла не отображается, поскольку поиск велся в единственном файле.

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

В заключение применяется опция -v для инвертирования критерия поиска и подсчета строк, не совпадающих с шаблоном.

Регулярные выражения

Как вы убедились, основной вариант применения команды grep легко усвоить. Теперь пришло время рассмотреть основы построения регулярных выражений, которые позволят вам выполнять более сложный поиск. Как упоминалось ранее в этой главе, регулярные выражения применяются в системе Linux и многих языках программирования с открытым исходным кодом. Вы можете использовать их в редакторе vi и в скриптах на языке Perl, применяя одни и те же принципы, общие для регулярных выражений, где бы они ни появлялись.

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

Таблица 2.15

Символ Описание
^ Привязка к началу строки
$ Привязка к концу строки
. Любой одиночный символ
[] В квадратных скобках содержится диапазон символов, с любым из них возможно совпадение, например, диапазон символов a-e или инвертированный диапазон, перед которым стоит символ ^

Если вы хотите использовать любые из перечисленных символов как "обычные", поставьте перед ними символ \. Например, если нужно найти символ $, просто введите \$.

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

Таблица 2.16

Проверочный шаблон Описание
[:alnum:] Буквенно-цифровые символы
[:alpha:] Буквы
[:ascii:] Символы таблицы ASCII
[:blank:] Пробел или табуляция
[:cntrl:] Управляющие символы ASCII
[:digit:] Цифры
[:graph:] Неуправляющие и непробельные символы
[:lower:] Строчные буквы
[:print:] Печатные символы
[:punct:] Знаки пунктуации
[:space:] Пробельные символы, включая вертикальную табуляцию
[:upper:] Прописные буквы
[:xdigit:] Шестнадцатиричные цифры

Кроме того, если задана опция =E для расширенного соответствия, за регулярным выражением могут следовать и другие символы, управляющие выполнением проверки на соответствие шаблону (табл. 2.17). В команде grep перед этими символами необходимо вводить символ \.

Таблица 2.17

Опция  Описание
? Совпадение не обязательно, но возможно не более одного раза
* Совпадения может не быть, оно может быть однократным или многократным
+ Совпадение должно быть однократным или многократным
{n} Совпадение должно быть n раз
{n, } Совпадение должно быть n раз и больше
{n, m} Совпадение должно быть от n до m раз включительно

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

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

$ grep e$ words2.txt

Art thou not, fatal vision, sensible

I see thee yet, in form as palpable

Nature seems dead, and wicked dreams abuse

$

Как видите, найдены строки, заканчивающиеся буквой "е".

2. Теперь найдите трехбуквенные слова, начинающиеся с символов "Th". В данном случае вам понадобится шаблон [[:space:]] для ограничения длины слова и . для единственного дополнительного символа.

$ grep Th.[[:space:]] words 2.txt

The handle toward my hand? Come, let me clutch thee.

The curtain'd sleep; witchcraft celebrates

Thy very stones prate of my whereabout,

$

3. В заключение примените расширенный режим поиска в команде grep для обнаружения слов из строчных букв длиной ровно 10 символов. Для этого задайте диапазон совпадающих символов от а до z и 10 повторяющихся совпадений.

$ grep -Е [a-z]\{10\} words2.txt

Proceeding from the heat-oppressed brain?

And such an instrument I was to use.

The curtain'd sleep; witchcraft celebrates

hy very stones prate of my whereabout,

$

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

Выполнение команд

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

Сделать это можно с помощью синтаксической конструкции $(команда), показанной ранее в примере с командой set. Существует устаревший вариант подстановки команды `команда`, который все еще широко распространен.

Примечание

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

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

Результат выполнения конструкции $(команда) — просто вывод команды. Имейте в виду, что это не статус возврата команды, а просто строковый вывод, показанный далее.

#!/bin/sh

echo The current directory is $PWD

echo The current users are $(who)

exit 0

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

Если вы хотите поместить результат в переменную, то можете просто присвоить его обычным образом:

whoisthere=$(who)

echo Swhoisthere

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

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

Подстановки в арифметических выражениях

Мы уже использовали команду expr, которая позволяет выполнять простые арифметические операции, но она делает это очень медленно, потому что для выполнения команды expr запускается новая командная оболочка.

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

#!/bin/sh

х=0

while [ "$х" -ne 10 ]; do

 echo $х

 х=$(($x+1))

done

exit 0

Примечание

Обратите внимание на тонкое отличие приведенной подстановки от команды х=$(...). Двойные скобки применяются для подстановки значений в арифметические выражения. Вариант с одиночными скобками, показанный ранее, используется для выполнения команд и перехвата их вывода.

Подстановка значений параметров

Вы уже видели простейший вариант присваивания параметра и подстановки значения параметра:

foo=fredecho $foo

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

#!/bin/sh

for i in 1 2 do

 my_secret_process $i_tmp

done

Но в каждом проходе цикла вы получите следующее сообщение:

my_secret_process: too few arguments

В чем ошибка?

Проблема заключается в том, что командная оболочка попыталась подставить значение переменной $i_tmp, которая не существует. Оболочка не считает это ошибкой; она просто не делает никакой подстановки, поэтому в сценарий my_secret_process не передаются никакие параметры. Для обеспечения подстановки в переменную части ее значения $i необходимо i заключить в фигурные скобки следующим образом:

#!/bin/sh

for i in 1 2 do

 my_secret_process ${i}_tmp

done

В каждом проходе цикла вместо ${i} подставляется значение i и получаются реальные имена файлов. Вы подставляете значение параметра в строку.

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

Таблица 2.18

Шаблон подстановки параметра Описание
${парам:-значение по умолчанию} Если у парам нет значения, ему присваивается значение по умолчанию
${#парам} Задается длина парам
${парам%строка} От конца значения парам отбрасывается наименьшая порция, совпадающая со строкой, и возвращается остальная часть значения
${парам%%строка} От конца значения парам отбрасывается наибольшая порция, совпадающая со строкой, и возвращается остальная часть значения
${парам#строка} От начала значения парам отбрасывается наименьшая порция, совпадающая со строкой, и возвращается остальная часть значения
${парам##строка} От начала значения парам отбрасывается наибольшая порция, совпадающая со строкой, и возвращается остальная часть значения

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

В приведенном далее сценарии показано применение шаблонов при подстановках значений параметров.

#!/bin/sh

unset foo

echo ${foo:-bar}

foo=fud

echo ${foo:-bar}

foo=/usr/bin/X11/startx

echo ${foo#*/}

echo ${foo##*/}

bar=/usr/local/etc/local/networks

echo ${bar%local*}

echo ${bar%%local*}

exit 0

У этого сценария следующий вывод:

bar

fud

usr/bin/X11/startx

startx

/usr/local/etc/usr

Как это работает

Первая подстановка ${foo:-bar} дает значение bar, поскольку у foo нет значения в момент выполнения команды. Переменная foo остается неизменной, т.е. она остается незаданной.

Примечание

Подстановка ${foo:=bar} установила бы значение переменной $foo. Этот строковый шаблон устанавливает, что переменная foo существует и не равна null. Если значение переменной не равно null, оператор возвращает ее значение, в противном случае вместо этого переменной foo присваивается значение bar.

Подстановка ${foo:?bar} выведет на экран foo: bar и аварийно завершит команду, если переменной foo не существует или ее значение не определено. И наконец, ${foo:+bar} вернет bar, если foo существует и не равна null. Какое разнообразие вариантов!

Шаблон {foo#*/} задает поиск и удаление только левого символа / (символ * соответствует любой строке, в том числе и пустой). Шаблон {foo##*/} задает поиск максимальной подстроки, совпадающей с ним, и, таким образом, удаляет самый правый символ / и все предшествующие ему символы.

Шаблон ${bar%local*} определяет просмотр символов в значении параметра, начиная от крайнего правого, до первого появления подстроки local, за которой следует любое количество символов, а в случае шаблона ${bar%%local*} ищется максимально возможное количество символов, начиная от крайнего правого символа значения и заканчивая крайним левым появлением подстроки local.

Поскольку в системах UNIX и Linux многое основано на идеи фильтров, результат какой-либо операции часто должен перенаправляться вручную. Допустим, вы хотите преобразовать файлы GIF в файлы JPEG с помощью программы cjpeg:

$ cjpeg image.gif > image.jpg

Порой вам может потребоваться выполнить такого рода операцию над большим числом файлов. Как автоматизировать подобное перенаправление? Это очень просто:

#!/bin/sh

for image in *.gif

do

 cjpeg $image > {image%%gif}jpg

done

Этот сценарий, giftojpeg, создает в текущем каталоге для каждого файла формата GIF файл формата JPEG.

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

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

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

Рассмотрим упражнение 2.19.

Упражнение 2.19. Применение встроенных документов

Простейший пример просто передает входные данные команде cat.

#!/bin/sh

cat <<!FUNKY!

hello

this is a here

document

!FUNKY!

Этот пример выводит на экран следующие строки:

hello

this is a here

document

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

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

Упражнение 2.20. Ещё одно применение встроенного документа

1. Начнем с файла, названного a_text_file и содержащего следующие строки:

That is line 1

That is line 2

That is line 3That is line 4

2. Вы можете отредактировать этот файл, совместно используя встроенный документ и редактор ed:

#!/bin/sh

ed a_text_file <<!FunkyStuff!

3

d

., \$s/is/was/ w

q

!FunkyStuff!

exit 0

Если вы выполните этот сценарий, то увидите, что теперь файл содержит следующие строки:

That is line 1

That is line 2

That was line 4

Как это работает

Сценарий командной оболочки запускает редактор ed и передает ему команды, необходимые для перехода к третьей строке, удаления строки и затем замены ее содержимым текущей строки (поскольку строка 3 (line 3) была удалена, теперь текущая строка — последняя строка файла). Эти команды редактора ed берутся из строк сценария, формирующих встроенный документ, строк между маркерами !Funky Stuff!.

Примечание

Обратите внимание на знак \ внутри встроенного документа, применяемый для защиты от подстановки, выполняемой командной оболочкой. Символ \ экранирует знак $, поэтому оболочка знает, что не следует пытаться подставить вместо строки \$s/is/was/ ее значение, которого у нее конечно же нет. Оболочка просто передает текст \$ как $, который затем сможет интерпретировать редактор e

Отладка сценариев

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

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

Поскольку сценарии обрабатываются интерпретатором, нет затрат на компиляцию при корректировке и повторном выполнении сценария. Основной способ отслеживания наиболее трудно выявляемых ошибок — задание различных опций командной оболочки. Для этого вы можете применять опции командной строки после запуска командной оболочки или использовать команду set. В табл. 2.19 перечислены эти опции.

Таблица 2.19

Опция командной строки Опция команды set Описание
sh -n <сценарий> set -о noexec  set -n Только проверяет синтаксические ошибки; не выполняет команды
sh -v <сценарий> set -о verbose  set -v Выводит на экран команды перед их выполнением
sh -х <сценарий> set -о xtrace  set -x Выводит на экран команды после обработки командной строки
sh -u <сценарий> set -o nounset  set -u Выдает сообщение об ошибке при использовании неопределенной переменной

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

Для установки опции xtrace используйте следующую команду:

set -о xtrace

Для того чтобы снова отключить эту опцию, применяйте следующую команду:

set +о xtrace

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

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

trap 'echo Exiting: critical variable = $critical_variable' EXIT

По направлению к графическому режиму — утилита dialog

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

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

Примечание

В некоторых дистрибутивах команда dialog по умолчанию не устанавливается; например, в Ubuntu вам, возможно, придется добавить совместно поддерживаемые репозитарии для поиска готовой версии. В других дистрибутивах вы можете найти уже установленный альтернативный вариант, gdialog. Он очень похож, но рассчитан на пользовательский интерфейс GNOME, применяемый для отображения диалоговых окон команды. В этом случае вы получите настоящий графический интерфейс. Как правило, в любой программе, использующей команду dialog, можно заменить все вызовы этой команды на gdialog, и вы получите графическую версию вашей программы. В конце этого раздела мы покажем пример программы, использующей команду gdialog.

Общая концепция утилиты dialog проста — одна программа с множеством параметров и опций, позволяющих отображать различные типы графических окон, начиная с простых окон с кнопками типа Yes/No (Да/Нет) и заканчивая окнами ввода и даже выбором пункта меню. Утилита обычно возвращает результат, когда пользователь выполнил какой-либо ввод, и результат может быть получен или из статуса завершения, или, если вводился текст, извлечением стандартного потока ошибок.

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

dialog --msgbox "Hello World" 9 18

На экране появится графическое информационное окно, дополненное кнопкой OK (рис. 2.3).

Рис. 2.3

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

Таблица 2.20

Тип диалогового окна Опция, применяемая для создания окна этого типа Назначение окна
Окна с флажками (Check boxes) --checklist Позволяет отображать список флажков, каждый из которых можно установить или сбросить
Информационные окна (Info boxes) --infobox Простое немедленное отображение в окне, без очистки экрана, возвращаемых данных
Окна ввода (Input boxes) --inputbox Позволяет пользователю вводить в окно текст
Окна меню (Menu boxes) --menu Позволяет пользователю выбрать один пункт из списка
Окна сообщений (Message boxes) --msgbox Отображает сообщения для пользователей и снабжено кнопкой OK, которую они должны нажать для продолжения
Окна с переключателями (Radio selection boxes) --radiolist Позволяет пользователю выбрать один переключатель из списка
Текстовые окна (Text boxes) --textbox Позволяют отображать содержимое файла в окне с прокруткой
Диалоговые окна Да/Нет (Yes/No boxes) --yesno Позволяют задать вопрос, на который пользователь может ответить "Да" или "Нет"

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

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

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

Таблица 2.21

Тип диалогового окна Параметры
--checklist text height width list-height [tag text status] ...
--infobox text height width
--inputbox text height width [initial string]
--menu text height width menu-height [tag item ] ...
--msgbox text height width
--radiolist text height width list-height [tag text status] ...
--textbox filename height width
--yesno text height width

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

Выполните упражнения 2.21 и 2.22.

Упражнение 2.21. Применение утилиты dialog

Давайте сразу перейдем к красивому сложному примеру. Если вы поймете его, все остальные покажутся легкими! В этом примере вы создадите диалоговое окно со списком флажков, с заголовком Check me (Поставь галочку) и пояснительной надписью Pick Numbers (Выбери номера). Окно с флажками будет высотой 15 строк и шириной 25 символов, и каждый флажок будет занимать 3 символа по высоте. И последнее, но не по степени важности, вы перечислите отображаемые элементы вместе с принятой по умолчанию установкой или сбросом (on/off) флажка.

dialog --title "Check me" --checklist "Pick Numbers" 15 25 3 1 "one" "off" 2 "two" "on" 3 "three" "off"

Полученный результат показан на рис. 2.4.

Как это работает

В этом примере параметр --checklist указывает на то, что вы собираетесь создать диалоговое окно с флажками. Вы используете опцию --title для задания заголовка "Check me", следующий параметр — пояснительная надпись "Pick Numbers".

Далее вы переходите к указанию размеров диалогового окна. Оно будет высотой 15 строк и шириной 25 символов и 3 строки отводятся для меню. Это не самый удачный выбор размеров, но он позволит вам увидеть, как размещаются элементы.

Опции выглядят несколько мудрено, но вам следует запомнить только то, что у каждого элемента списка есть три значения:

□ номер в списке;

□ текст;

□ состояние.

Рис. 2.4

У первого элемента номер 1, отображается текст "one" (один) и выбрано состояние "off" (сброшен). Далее вы переходите ко второму элементу с номером 2, текстом "two" и состоянием "on" (установлен). Так продолжается до тех пор, пока вы не опишите все элементы списка.

Легко, не правда ли? Теперь попробуйте ввести несколько вариантов в командной строке и убедитесь, насколько эту утилиту легко применять. Для того чтобы включить этот пример в программу, вы должны иметь доступ к результатам пользовательского ввода. Это совсем просто: перенаправьте стандартный поток ошибок в текстовый ввод или проверьте переменную окружения $?, которая, как вы помните, не что иное, как код завершения предыдущей команды.

Упражнение 2.22. Более сложная программа, использующая утилиту dialog

Давайте рассмотрим простую программу questions, которая принимает к сведению пользовательские ответы.

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

#!/bin/sh

# Задайте несколько вопросов и получите ответ

dialog --title "Questionnaire" --msgbox "Welcome to my simple survey" 9 18

2. Спросите пользователя с помощью простого диалогового окна с кнопками типа Yes/No, хочет ли он продолжать. Воспользуйтесь переменной окружения $? для того, чтобы выяснить, выбрал пользователь ответ Yes (код завершения 0) или No. Если он не хочет двигаться дальше, используйте простое информационное окно, не требующее никакого пользовательского ввода для своего завершения.

dialog --title "Confirm" --yesno "Are you willing to take part?" 9 18

if [ $? != 0 ]; then

 dialog --infobox "Thank you anyway" 5 20 sleep 2

 dialog --clear exit 0

fi

3. Спросите у пользователя его имя с помощью диалогового окна ввода. Перенаправьте стандартный поток ошибок во временный файл _1.txt, который затем вы сможете обработать в переменной QNAME.

dialog --title "Questionnaire" --inputbox "Please enter your name" 9 30 2>_1.txt

Q_NAME=$(cat _1.txt)

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

dialog --menu "$Q_NAME, what music do you like best?" 15 30 4 1 "Classical" 2 "Jazz" 3 "Country" 4 "Other" 2>_1.txt

Q_MUSIC=$(cat _1.txt)

5. Номер, выбранный пользователем, будет запоминаться во временном файле _1.txt, который перехватывается переменной Q_MUSIC, поэтому вы сможете проверить результат.

if [ "$Q_MUSIC" = "1" ]; then

 dialog --title "Likes Classical" --msgbox "Good choice!" 12 25

else

 dialog --title "Doesn't like Classical" --msgbox "Shame" 12 25

fi

В заключение очистите последнее диалоговое окно и завершите программу.

sleep 2

dialog --clear

exit 0

На рис. 2.5 показан результат.

Как это работает

В данном примере вы соединяете команду dialog и простой программный код на языке командной оболочки для того, чтобы показать, как можно создавать простые программы с графическим пользовательским интерфейсом, используя только сценарий командной оболочки. Вы начинаете с обычного экрана-приветствия, а затем с помощью простого диалогового окна с кнопками типа Yes/No спрашиваете пользователя о его желании участвовать в опросе. Вы используете переменную $? для проверки ответа пользователя. Если он согласен, вы запрашиваете его имя, сохраняете его в переменной Q_NAME и выясняете с помощью диалогового окна-меню, какой музыкальный стиль он любит. Сохранив числовой вывод в переменной Q_MUSIC, вы сможете увидеть, что ответил пользователь, и отреагировать соответственно.

Рис. 2.5

Рис. 2.6

Если вы применяете графический пользовательский интерфейс (GUI) на базе графической среды GNOME и в данный момент запустили в нем сеанс работы с терминалом, на месте команды dialog можно использовать команду gdialog. У обеих команд одинаковые параметры, поэтому вы сможете воспользоваться тем же программным кодом, не считая замены запускаемой вами команды dialog командой gdialog. На рис. 2.6 показано, как выглядит этот сценарий в дистрибутиве Ubuntu, когда применяется команда gdialog.

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

Соединяем все вместе

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

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

Требования

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

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

Проектирование

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

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

Нужно ли ограничить количество дорожек на одном компакт-диске, которые можно хранить? Это ограничение кажется лишним, поэтому сразу отбросим его.

Если вы допускаете, что на компакт-диске может быть разное количество дорожек, у вас есть три варианта:

□ использовать один файл с одной строкой для "заголовочной" типовой информации и n строк для сведений о дорожках на каждом компакт-диске;

□ поместить всю информацию о каждом компакт-диске в одну строку, разрешая ей продолжаться то тех пор, пока вся информация о дорожках диска не будет сохранена;

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

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

Далее нужно решить, какие данные помещать в файлы.

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

□ номер компакт-диска в каталоге;

□ название;

□ музыкальный стиль (классика, рок, поп, джаз и т.д.);

□ композитор или исполнитель.

О дорожках вы будете хранить две характеристики:

□ номер дорожки;

□ ее название.

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

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

Таблица 2.22

Catalog Title Type Composer
CD123 Cool sax Jazz Bix
CD234 Classic violin Classical Bach
CD345 Hits99 Pop Various

Таблица 2.23

Catalog Track No. Title
CD123 1 Some jazz
CD123 2 More jazz
CD234 1 Sonata in D minor
CD345 1 Dizzy

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

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

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

get_return();

get_confirm();

set_menu_choice();

insert_title();

insert_track();

add_record_tracks();

add_records();

find_cd();

update_cd();

count_cds();

remove_records();

list_tracks().

Упражнение 2.23. Приложение для работы с коллекцией компакт-дисков

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

#!/bin/bash

# Очень простой пример сценария командной оболочки для управления

# коллекцией компакт-дисков.

# Copyright (С) 1996-2007 Wiley Publishing Inc.

# Это свободно распространяемое программное обеспечение;

# вы можете распространять эту программу и/или изменять ее

# в соответствии с положениями GNU General Public License,

# документа, опубликованного фондом Free Software Foundation;

# либо версии 2 этой лицензии или (по вашему выбору)

# любой более свежей версии.

# Эта программа распространяется в надежде на ее полезность,

# но WITHOUT ANY WARRANTY, (без каких-либо гарантий);

# даже без предполагаемой гарантии MERCHANTABILITY

# or FITNESS FOR A PARTICULAR PURPOSE (годности

# ее для продажи или применения для определенной цели).

# Более подробную информацию см. в GNU General Public License.

# Вы должны были получить копию GNU General Public License

# вместе с этой программой;

# если нет, пишите в организацию Free Software Foundation,

# Inc. no адресу: 675 Mass Ave, Cambridge, MA 02139, USA.

2. Теперь убедитесь, что установлены некоторые глобальные переменные, которые будут использоваться во всем сценарии. Задайте заголовочный файл, файл с данными о дорожках и временный файл и перехватите нажатие комбинации клавиш <Ctrl>+<C> для того, чтобы удалить временный файл, если пользователь прервет выполнение сценария.

menu_choice=""

current cd=""

title_file="title.cdb"

tracks_file="tracks.cdb"

temp_file=/tmp/cdb.$$

trap 'rm -f $temp_file' EXIT

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

get_return() (

 echo -е "Press return \с"

 read x

 return 0

}

get_confirm() (

 echo -e "Are you sure? \c"

 while true do

  read x

  case "$x" in

   y | yes | Y | Yes | YES )

    return 0;;

   n | no | N | No | NO )

    echo

    echo "Cancelled"

    return 1;;

   *)

    echo "Please enter yes or no" ;;

  esac

 done

}

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

set_menu_choice() {

 clear

 echo "Options :-"

 echo

 echo " a) Add new CD"

 echo " f) Find CD"

 echo " c) Count the CDs and tracks in the catalog"

 if [ "$cdcatnum" != "" ]; then

  echo " 1) List tracks on $cdtitle"

  echo " r) Remove $cdtitle"

  echo " u) Update track information for $cdtitle"

 fi

 echo " q) Quit" echo

 echo -e "Please enter choice then press return \c"

 read menu_choice

 return

}

Примечание

Имейте в виду, что команда echo -е не переносится в некоторые командные оболочки.

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

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

insert_title() {

 echo $* >> $title_file

 return

}

insert_track() {

 echo $* >> $tracks_file

 return

}

add_record_tracks() {

 echo "Enter track information for this CD"

 echo "When no more tracks enter q"

 cdtrack=1

 cdttitle=""

 while [ "$cdttitle" != "q" ]

 do

  echo -e "Track $cdtrack, track title? \c"

  read tmp

  cdttitle=${tmp%%, *}

  if [ "$tmp" != "$cdttitle" ]; then

   echo "Sorry, no commas allowed"

   continue

  fi

  if [ -n "$cdttitle" ] ; then

   if [ "$cdttitle" ! = "q" ]; then

    insert_track $cdcatnum, $cdtrack, $cdttitle

   fi

  else

   cdtrack=$((cdtrack-1))

  fi

  cdtrack=$((cdtrack+1))

 done

}

6. Функция add_records позволяет вводить основную информацию о новом компакт-диске.

add_records() {

 # Подсказка для начала ввода информации

 echo -е "Enter catalog name \с"

 read tmp

 cdcatnum=${tmp%%, *}

 echo -e "Enter title \c"

 read tmp

 cdtitle=${tmp%%, *}

 echo -e "Enter type \c"

 read tmp

 cdtype=${tmp%%, *}

 echo -e "Enter artist/composer \c"

 read tmp

 cdac=${tmp%%, *}

 # Проверяет, хочет ли пользователь ввести информацию

 echo About to add new entry

 echo "$cdcatnum $cdtitle $cdtype $cdac"

 # Если получено подтверждение, добавляет данные в конец файла.

 # с заголовками

 if get_confirm ; then

  insert_title $cdcatnum, $cdtitle, $cdtype, $cdac

  add_record_tracks

 else

  remove_records

 fi

 return

}

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

У команды счетчика слов, wc, в выводе есть пробельный символ, разделяющий количества строк, слов и символов в файле. Используйте синтаксическую запись $(wc -l $temp_file) для извлечения первого параметра в выводе и переноса его в переменную linesfound. Если бы вам был нужен другой следующий далее параметр, нужно было бы воспользоваться командой set для установки значений переменных-параметров оболочки из вывода команды.

Изменив значение переменной IFS (Internal Field Separator, внутренний разделитель полей) на запятую, вы сможете разделить поля, разграниченные запятыми. Альтернативный вариант — применить команду cut.

find_сd() {

 if [ "$1" = "n" ]; then

  asklist=n

 else

  asklist=y

 fi

 cdcatnum=""

 echo -e "Enter a string to search for in the CD titles \c"

 read searchstr

 if [ "$searchstr" = "" ]; then

  return 0

 fi

 grep "$searchstr" $title_file > $temp_file

 set $(wc -l $temp_file)

 linesfound=$1

 case "$linesfound" in

  0)

   echo "Sorry, nothing found"

   get_return

   return 0 ;;

  1) ;;

  2)

   echo "Sorry, not unique."

   echo "Found the following"

   cat $temp_file

   get_return

   return 0

 esac

 IFS=", "

 read cdcatnum cdtitle cdtype cdac < $temp_file

 IFS=" "

 if [ -z "$cdcatnum" ]; then

  echo "Sorry, could not extract catalog field from $temp_file"

  get_return

  return 0

 fi

 echo

 echo Catalog number: $cdcatnum echo Title: $cdtitle

 echo Type: $cdtype

 echo Artist/Composer: $cdac

 echo

 get_return

 if [ "$asklist" = "y" ]; then

  echo -e "View tracks for this CD? \c"

  read x

  if [ "$x" = "y" ]; then

   echo

   list_tracks

   echo

  fi

 fi

 return 1

}

8. Функция update_cd позволит вам повторно ввести сведения о компакт-диске. Учтите, что вы ищите (с помощью команды grep) строки, начинающиеся (^) с подстроки $cdcatnum, за которой следует ", " и должны заключить подстановку значения $cdcatnum в {}. Таким образом, вы сможете найти запятую без специального пробельного символа между ней и номером в каталоге. Эта функция также использует {} для образования блока из нескольких операторов, которые должны выполняться, если функция get_confirm вернет значение true.

update_cd() {

 if [ -z "$cdcatnum" ]; then

  echo "You must select a CD first"

  find_cd n

 fi

 if [ -n "$cdcatnum" ]; then

  echo "Current tracks are :-"

  list_tracks

  echo

  echo "This will re-enter the tracks for $cdtitle"

  get_confirm && {

   grep -v "^${cdcatnum}, " $tracks_file > $temp_file

   mv $temp_file $tracks_file

   echo

   add_record_tracks

  }

 fi

 return

}

9. Функция count_cds дает возможность быстро пересчитать содержимое базы данных.

count_cds() {

 set $(wc -l $title_file)

 num_titles=$1

 set $(wc -l $tracks_file)

 num_tracks=$1

 echo found $num_titles CDs, with a total of $num_tracks tracks

 get_return

 return

}

10. Функция remove_records удаляет элементы из файлов базы данных с помощью команды grep -v, удаляющей все совпадающие строки. Учтите, что нужно применять временный файл.

Если вы попытаетесь применить команду:

grep -v "^$cdcatnum" > $title_file

файл $title_file станет пустым благодаря перенаправлению вывода > до того, как команда grep выполнится, поэтому она будет читать уже пустой файл.

remove_records() {

 if [ -z "$cdcatnum" ]; then

  echo You must select a CD first find_cd n

 fi

 if [ -n "$cdcatnum" ]; then

  echo "You are about to delete $cdtitle"

  get_confirm && {

   grep -v "^${cdcatnum}, " $title_file > $temp_file

   mv $temp_file $title_file

   grep -v "^${cdcatnum}, " $tracks_file > $temp_file

   mv $temp_file $tracks_file

   cdcatnum=""

   echo Entry removed

  }

  get_return

 fi

 return

}

11. Функция list_tracks снова использует команду grep для извлечения нужных вам строк, команду cut для доступа к отдельным полям и затем команду more для постраничного вывода. Если вы посмотрите, сколько строк на языке С займет повторная реализация этих 20 необычных строк кода, то поймете, каким мощным средством может быть командная оболочка.

list_tracks() {

 if [ "$cdcatnum" = "" ]; then

  echo no CD selected yet

  return

 else

  grep "^${cdcatnum}, " $tracks_file > $temp_file

  num_tracks=${wc -l $temp_file}

  if [ "$num_tracks" = "0" ]; then

   echo no tracks found for $cdtitle

  else

   {

    echo

    echo "$cdtitle :-"

    echo

    cut -f 2- -d , $temp_file

    echo

   } | ${PAGER:-more}

  fi

 fi

 get_return

 return

}

12. Теперь, когда все функции определены, можно вводить основную процедуру. Первые несколько строк просто приводят файлы в известное состояние; затем вы вызываете функцию формирования меню set_menu_choice и действуете в соответствии с ее выводом.

Если выбран вариант quit (завершение), вы удаляете временный файл, выводите сообщение и завершаете сценарий с успешным кодом завершения.

rm -f $temp_file

if [ ! -f $title_file ]; then

 touch $title_file

fi

if [ ! -f $tracks_file ]; then

 touch $tracks_file

fi

# Теперь непосредственно приложение

clear

echo

echo

echo "Mini CD manager" sleep 1

quit=n

while [ "$quit" != "y" ]; do

 set_menu_choice

 case "$menu_choice" in

  a) add_records;;

  r) remove records;;

  f) find_cd y;;

  u) update_cd;;

  c) count_cds;;

  l) list_tracks;;

  b)

   echo

   more $title_file

   echo

   get return;;

  q | Q ) quit=y;;

  *) echo "Sorry, choice not recognized";;

 esac

done

# Убираем и покидаем

rm -f $temp_file echo "Finished"

exit 0

Замечания, касающиеся приложения

Команда trap в начале сценария предназначена для перехвата нажатия пользователем комбинации клавиш <Ctrt>+<C>. Им может быть сигнал EXIT или INT, в зависимости от настроек терминала.

Существуют другие способы реализации выбора пункта меню, особенно конструкция select в оболочках bash и ksh (которая, тем не менее, не определена в стандарте X/Open). Она представляет собой специализированный селектор пунктов меню. Проверьте ее на практике, если ваш сценарий может позволить себе быть немного менее переносимым. Для передачи пользователям многострочной информации можно также воспользоваться встроенными документами.

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

1 First CD Track 1

2 First CD Track 2

1 Another CD

2 With the same CD key

Мы оставляем это и другие усовершенствования в расчете на ваше воображение и творческие способности, которые проявятся при корректировке вами программного кода в соответствии с требованиями GPL.

Резюме 

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

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

Глава 3

Работа с файлами

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

Прежде чем перейти к способам обработки файлового ввода/вывода в системе Linux, мы дадим краткий обзор понятий, связанных с файлами, каталогами и устройствами. Для управления файлами и каталогами вам придется выполнять системные вызовы (аналог Windows API в системах UNIX и Linux), но, кроме того, для обеспечения более эффективного управления файлами существует большой набор библиотечных функций стандартной библиотеки ввода/вывода (stdio).

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

□ файлы и устройства;

□ системные вызовы;

□ библиотечные функции;

□ низкоуровневый доступ к файлу;

□ управление файлами;

□ стандартная библиотека ввода/вывода;

□ форматированный ввод и вывод;

□ сопровождение файлов и каталогов;

□ просмотр каталогов;

□ ошибки;

□ файловая система /proc;

□ более сложные приемы — fcntl и mmap.

Структура файла в Linux

Вы можете спросить: "Зачем вы останавливаетесь на структуре файла? Я уже знаком с ней." Дело в том, что в среде Linux, как и UNIX, файлы особенно важны, поскольку они обеспечивают простую и согласованную взаимосвязь со службами операционной системы и устройствами. В ОС Linux файл — это все что угодно. Ну, или почти все!

Это означает, что в основном программы могут обрабатывать дисковые файлы, последовательные порты, принтеры и другие устройства точно так же, как они используют файлы. Мы расскажем о некоторых исключениях, таких как сетевые подключения, в главе 15, но в основном вы должны будете применять пять базовых функций: open, close, read, write и ioctl.

Каталоги — тоже специальный тип файлов. В современных версиях UNIX, включая Linux, даже суперпользователь не пишет непосредственно в них. Обычно все пользователи для чтения каталогов применяют интерфейс opendir/readdir, и им нет нужды знать подробности реализации каталогов в системе. Позже в этой главе мы вернемся к специальным функциям работы с каталогами.

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

Каталоги

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

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

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

Файлы помещаются в каталоги, которые могут содержать подкаталоги. Так формируется хорошо знакомая иерархия файловой системы. Пользователь, скажем neil, обычно хранит файлы в исходном (home) каталоге, возможно /home/neil, с подкаталогами для хранения электронной почты, деловых писем, служебных программ и т.д. Имейте в виду, что у многих командных оболочек систем UNIX и Linux есть отличное обозначение для указания начала пути в вашем исходном каталоге: символ "тильда" (~). Для другого пользователя наберите ~user. Как вы знаете, исходные каталоги пользователей — это, как правило, подкаталоги каталога более высокого уровня, создаваемого специально для этой цели, в нашем случае это каталог /home.

Примечание

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

Каталог /home в свою очередь является подкаталогом корневого каталога /, расположенного на верхнем уровне иерархии и содержащего все системные файлы и подкаталоги. В корневой каталог обычно включен каталог /bin для хранения системных программ (бинарных файлов), каталог /etc, предназначенный для хранения системных файлов конфигурации, и каталог /lib для хранения системных библиотек. Файлы, представляющие физические устройства и предоставляющие интерфейс для этих устройств, принято помещать в каталог /dev. На рис. 3.1 показана в качестве примера часть типичной файловой системы Linux. Мы рассмотрим структуру файловой системы Linux более подробно в главе 18, когда будем обсуждать стандарт файловой системы Linux (Linux File System Standard).

Рис. 3.1 

Файлы и устройства

Даже физические устройства очень часто представляют (отображают) с помощью файлов. Например, будучи суперпользователем, вы можете смонтировать дисковод IDE CD-ROM как файл:

# mount -t iso9660 /dev/hdc /mnt/cdrom

# cd /mnt/cdrom

который выбирает устройство CD-ROM (в данном случае вторичное ведущее (secondary master) устройство IDE, которое загружается как /dev/hdc во время начального запуска системы; у устройств других типов будут другие элементы каталога /dev) и монтирует его текущее содержимое как файловую структуру в каталоге /mnt/cdrom. Затем вы перемещаетесь по каталогам компакт-диска как обычно, конечно за исключением того, что их содержимое доступно только для чтения.

В системах UNIX и Linux есть три важных файла устройств: /dev/console, /dev/tty и /dev/null.

dev/console

Это устройство представляет системную консоль. На него часто отправляются сообщения об ошибках и диагностическая информация. У всех систем UNIX есть выделенный терминал или экран для получения сообщений консоли. Иногда он может быть выделенным печатающим терминалом. На современных рабочих станциях и в ОС Linux обычно это активная виртуальная консоль, а под управлением графической среды X Window это устройство станет специальным окном консоли на экране.

/dev/tty

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

Там где этот файл, /dev/tty может применяться, он позволяет программе писать непосредственно пользователю независимо от того, какой псевдотерминал или аппаратный терминал он использует. Это полезно при перенаправлении стандартного вывода. Примером может служить отображение содержимого длинного каталога в виде группы страниц с помощью команды ls -R | more, в которой у программы more есть пользовательская подсказка для каждой новой страницы вывода. Вы узнаете больше о файле /dev/tty в главе 5.

Учтите, что существует только одно устройство /dev/console, и в то же время может существовать много разных физических устройств, к которым можно обратиться с помощью файла dev/tty.

/dev/null

Файл /dev/null — это фиктивное устройство. Весь вывод, записанный на это устройство, отбрасывается. Когда устройство читается, немедленно возвращается конец файла, поэтому данное устройство можно применять с помощью команды cp как источник пустых файлов. Нежелательный вывод очень часто перенаправляется на dev/null.

$ echo do not want to see this >/dev/null

$ cp /dev/null empty_file

Примечание

Другой способ создания пустых файлов — применение команды touch <имя файла>, изменяющей время модификации файла или создающей новый файл при отсутствии файла с заданным именем. Хотя она и не очищает содержимое обрабатываемого файла.

В каталоге /dev можно найти и другие устройства, такие как дисководы жестких дисков и флоппи-дисководы, коммуникационные порты, ленточные накопители, дисководы CD-ROM, звуковые карты и некоторые устройства, представляющие внутреннюю структуру системы. Есть даже устройство /dev/zero, действующее как источник нулевых байтов для создания файлов, заполненных нулями. Для доступа к некоторым из этих устройств вам понадобятся права супер пользователя; обычные пользователи не могут писать программы, непосредственно обращающиеся к низкоуровневым устройствам, таким как накопители жестких дисков. Имена файлов устройств могут быть в разных системах различными. В дистрибутивах ОС Linux обычно есть приложения, выполняемые от имени суперпользователя и управляющие устройствами, которые иначе будут недоступны, например, mount для монтируемых пользователями файловых систем.

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

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

Системные вызовы и драйверы устройств

Вы можете обращаться к файлам и устройствам и управлять ими, применяя небольшой набор функций. Эти функции, известные как системные вызовы, непосредственно представляются системой UNIX (и Linux) и служат интерфейсом самой операционной системы.

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

Для формирования одинакового интерфейса драйверы устройств включают в себя все аппаратно-зависимые свойства. Уникальные аппаратные средства обычно доступны через системный вызов ioctl (I/O control, управление вводом/выводом).

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

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

open — открывает файл или устройство;

read — читает из открытого файла или устройства;

□ write — пишет в файл или устройство;

close — закрывает файл или устройство;

ioctl — передает управляющую информацию драйверу устройства.

Системный вызов ioctl применяется для аппаратно-зависимого управления (как альтернатива стандартного ввода/вывода), поэтому он у каждого устройства свой. Например, вызов ioctl может применяться для перемотки ленты в ленточном накопителе или установки характеристик управления потоками последовательного порта. Этим объясняется необязательная переносимость ioctl с машины на машину. Кроме того, у каждого драйвера определен собственный набор команд ioctl.

Этот системный вызов, как и другие, обычно описывается в разделе 2 интерактивного справочного руководства. Прототипы функций со списком параметров и типом возвращаемого функцией значения, используемые в системных вызовах, а также связанные с ними директивы #define с определением констант представлены в файлах include. Нужные для каждого системного вызова дополнения будут подключаться с описаниями отдельных вызовов.

Библиотечные функции

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

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

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

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

Библиотечные функции, как правило, описываются в разделе 3 интерактивного справочного руководства и часто снабжаются стандартным файлом директивы include, связанным с ними, например, файл stdio.h для стандартной библиотеки ввода/вывода.

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

Рис. 3.2

Низкоуровневый доступ к файлам

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

□ 0 — стандартный ввод;

□ 1 — стандартный вывод;

□ 2 — стандартный поток ошибок.

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

write

Системный вызов write предназначен для записи из buf первых nbytes байтов в файл, ассоциированный с дескриптором fildes. Он возвращает количество реально записанных байтов, которое может быть меньше nbytes, если в дескрипторе файла обнаружена ошибка или дескриптор файла, расположенный на более низком уровне драйвера устройства, чувствителен к размеру блока. Если функция возвращает 0, это означает, что ничего не записано; если она возвращает -1, в системном вызове write возникла ошибка, которая описывается в глобальной переменной errno,

Далее приведена синтаксическая запись.

#include <unistd.h>

size_t write(int fildes, const void *buf, size_t nbytes);

Благодаря полученным знаниям вы можете написать свою первую программу, simple_write.c:

#include <unistd.h>

#include <stdlib.h>

int main() {

 if ((write(1, "Here is some data\n", 18)) != 18)

  write(2, "A write error has occurred on file descriptor 1\n", 46);

 exit(0);

}

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

$ ./simple_write

Here is some data

$

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

read

Системный вызов read считывает до nbytes байтов данных из файла, ассоциированного с дескриптором файла fildes, и помещает их в область данных buf. Он возвращает количество действительно прочитанных байтов, которое может быть меньше требуемого количества. Если вызов read возвращает 0, ему нечего считывать; он достиг конца файла. Ошибка при вызове заставляет его вернуть -1.

#include <unistd.h>

size_t read(int fildes, void *buf, size_t nbytes);

Программа simple_read.c копирует первые 128 байтов стандартного ввода в стандартный вывод. Она копирует все вводимые данные, если их меньше 128 байтов.

#include <unistd.h>

#include <stdlib.h>

int main() {

 char buffer[128];

 int nread;

 nread = read(0, buffer, 128);

 if (nread == -1)

  write(2, "A read error has occurred\n", 26);

 if ((write(1, buffer, nread)) != nread)

  write(2, "A write error has occurred\n", 27);

 exit(0);

}

Если вы выполните программу, то получите следующий результат:

$ echo hello there | ./simple_read

hello there

$ ./simple_read < draft1.txt

Files

In this chapter we will be looking at files and directories and how to

manipulate them. We will learn how to create files, $

Первое выполнение программы с помощью команды echo формирует некоторый ввод программы, который по каналу передается в вашу программу. Во втором выполнении вы перенаправляете ввод из файла draft1.txt. В этом случае вы видите первую часть указанного файла, появляющуюся в стандартном выводе.

Примечание

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

open

Для создания дескриптора нового файла вы должны применить системный вызов open.

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

int open(const char *path, int oflags);

int open(const char *path, int oflags, mode_t mode);

Примечание

Строго говоря, для использования вызова open вы не должны включать файлы sys/types.h и sys/stat.h в системах, удовлетворяющих стандартам POSIX, но они могут понадобиться в некоторых системах UNIX.

Не вдаваясь в подробности, скажем, что вызов open устанавливает путь к файлу или устройству. Если установка прошла успешно, он возвращает дескриптор файла, который может применяться в системных вызовах read, write и др. Дескриптор файла уникален и не используется совместно другими процессами, которые могут в данный момент выполняться. Если файл открыт одновременно в двух программах, они поддерживают отдельные дескрипторы файла. Если они обе пишут в файл, то продолжат запись с того места, где остановились. Их данные не чередуются, но данные одной программы могут быть записаны поверх данных другой. У каждой программы свое представление о том, какая порция файла (каково смещение текущей позиции в файле) прочитана или записана. Вы можете помешать нежелательным накладкам такого сорта с помощью блокировки файла, которая будет обсуждаться в главе 7.

Имя открываемого файла или устройства передается как параметр path; параметр oflags применяется для указания действий, предпринимаемых при открытии файла.

Параметр oflags задается как комбинация обязательного режима доступа к файлу и других необязательных режимов. Системный вызов open должен задавать один из режимов доступа к файлу, указанных в табл. 3.1.

Таблица 3.1

Режим Описание
О_RDONLY Открытие только для чтения
О_WRONLY Открытие только для записи
O_RDWR Открытие для чтения и записи

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

O_APPEND — помещает записываемые данные в конец файла;

O_TRUNC — задает нулевую длину файла, отбрасывая существующее содержимое;

O_CREAT — при необходимости создает файл с правами доступа, заданными в параметре mode;

O_EXCL — применяется с режимом O_CREAT, который гарантирует, что вызывающая программа создаст файл. Вызов open атомарный, т.е. он выполняется только одним вызовом функции. Это предотвращает одновременное создание файла двумя программами. Если файл уже существует, open завершится неудачно.

Другие возможные значения параметра oflags описаны на странице интерактивного справочного руководства, посвященной open; ее можно найти в разделе 2 руководства (примените команду man 2 open).

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

Существует также системный вызов creat, стандартизованный POSIX, но он применяется не часто. Он не только создает файл, как можно ожидать; но также и открывает его. Такой вызов эквивалентен вызову open с параметром oflags, равным O_CREAT|О_WRONLY|O_TRUNC.

Количество файлов, одновременно открытых в любой выполняющейся программе, ограничено. Предельное значение обычно определяется константой OPEN_MAX в файле limits.h и меняется от системы к системе, но стандарт POSIX требует, чтобы оно было не меньше 16. Это значение само по себе может быть ограничено в соответствии с предельными значениями локальной системы, поскольку программа не сможет всегда иметь возможность держать открытыми такое количество файлов. В ОС Linux это предельное значение можно изменять во время выполнения и поэтому OPEN_MAX уже не константа. Как правило, ее начальное значение равно 256.

Исходные права доступа

Когда вы создаете файл, применяя флаг O_CREAT в системном вызове open, вы должны использовать форму с тремя параметрами. Третий параметр mode формируется из флагов, определенных в заголовочном файле sys/stat.h и соединенных поразрядной операцией OR. К ним относятся:

S_IRUSR — право на чтение, владелец;

S_IWUSR — право на запись, владелец;

S_IXUSR — право на выполнение, владелец;

S_IRGRP — право на чтение, группа;

S_IWGRP — право на запись, группа;

S_IXGRP — право на выполнение, группа;

S_IROTH — право на чтение, остальные;

S_IWOTH — право на запись, остальные;

S_IXOTH — право на выполнение, остальные.

Например, вызов

open("myfile", O_CREAT, S_IRUSR|S_IXOTH);

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

$ ls -ls myfile

0 -r-------х 1 neil software 0 Sep 22 08:11 myfile*

Есть пара факторов, способных повлиять на права доступа к файлу. Во-первых, заданные права применяются, только если файл создается. Во-вторых, на права доступа к созданному файлу оказывает воздействие маска пользователя (заданная командой командной оболочки, umask). Значение параметра mode, заданное в вызове open, на этапе выполнения объединяется с помощью операции AND с инвертированной маской пользователя. Например, если заданы маска пользователя 001 и в параметре mode флаг S_IXOTH, у созданного файла не будет права на выполнение для "остальных", т.к. маска пользователя указывает на то, что это право не должно предоставляться. Флаги в вызовах open и creat являются на самом деле запросами на установку прав доступа. Будут ли предоставлены запрошенные права, зависит от значения umask во время выполнения.

umask

umask — это системная переменная, содержащая маску для прав доступа к файлу, которые будут применяться при создании файла. Вы можете изменить значение переменной, выполнив команду umask, предоставляющую новое значение. Значение этой переменной представляет собой трёхзнаковое восьмеричное число. Каждая цифра — результат объединения с помощью операций OR значений 1, 2 или 4 (табл. 3.2). Отдельные цифры указывают на права доступа "пользователя", "группы" и "остальных" соответственно.

Таблица 3.2

Цифра Значение Смысл
1 0 Никакие права пользователя не отвергнуты
4 Право пользователя на чтение отвергается
2 Право пользователя на запись отвергается
1 Право пользователя на выполнение отвергается
2 0 Никакие права группы не отвергнуты
4 Право группы на чтение отвергается
2 Право группы на запись отвергается
1 Право группы на выполнение отвергается
3 0 Никакие права остальных не отвергнуты
4 Право остальных на чтение отвергается
2 Право остальных на запись отвергается
1 Право остальных на выполнение отвергается

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

Таблица 3.3

Цифра Значение
1 0
2 2
1
3 2

Значения каждой цифры объединяются операциями OR, поэтому для получения значения второй цифры нужна операция 2 | 1, дающая в результате 3. Результирующее значение umask — 032.

Когда вы создаете файл с помощью системного вызова open или creat, параметр mode сравнивается с текущим значением переменной umask. Любой бит, установленный в параметре mode и одновременно в переменной umask, удаляется. В результате пользователи могут настроить свое окружение, например, потребовав не создавать никаких файлов с правом на запись для остальных, даже если программа, создающая файл, требует предоставить такое право. Это не мешает программе или пользователю впоследствии применить команду chmod (или системный вызов chmod в программе), чтобы добавить право на запись для остальных, но поможет защитить пользователей, избавив их от необходимости проверять и задавать права доступа для всех новых файлов.

close

Системный вызов close применяется для разрыва связи файлового дескриптора fildes с его файлом. Дескриптор файла после этого может использоваться повторно. Вызов возвращает 0 в случае успешного завершения и -1 при возникновении ошибки.

#include <unistd.h>

int close (int fildes);

Примечание

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

ioctl

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

#include <unistd.h>

int ioctl(int fildes, int cmd, ...)

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

Например, следующий вызов ioctl в ОС Linux включает световые индикаторы клавиатуры (LEDs).

ioctl(tty_fd, KDSETLED, LED_NUM|LED_CAP|LED_SCR);

Выполните упражнения 3.1 и 3.2.

Упражнение 3.1. Программа копирования файла

Теперь вы знаете достаточно о системных вызовах open, read и write, чтобы написать простенькую программу copy_system.c для посимвольного копирования одного файла в другой.

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

1. Сначала вам нужно создать тестовый входной файл размером, скажем, 1 Мбайт и именем file.in.

2. Далее откомпилируйте программу copy_system.c.

#include <unistd.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdlib.h>

int main() {

 char c;

 int in, out;

 in = open("file.in", O_RDONLY);

 put = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);

 while(read(in, &c, 1) == 1) write(out, &c, 1);

 exit(0);

}

Примечание

Имейте в виду, что строка #include <unistd.h> должна быть первой, поскольку она определяет флаги, касающиеся соответствия стандарту POSIX и способные повлиять на другие включенные в #include файлы.

3. Выполнение программы даст результат, похожий на следующий:

$ TIMEPORMAT="" time ./copy_system

4.67user 146.90system 2:32.57elapsed 99%CPU

...

$ ls -ls file.in file.out

1029 -rw-r--r-- 1 neil users 1048576 Sep 17 10:46 file.in

1029 -rw------- 1 neil users 1048576 Sep 17 10:51 file.out

Как это работает

Вы используете команду time для определения времени выполнения программы. В ОС Linux переменная TIMEFORMAT применяется для переопределения принятого по умолчанию в стандарте POSIX формата вывода времени, в который не включено время использования ЦПУ. Как видите, что в этой очень старой системе входной файл file.in размером 1 Мбайт был успешно скопирован в файл file.out, созданный с правами на чтение/запись только для владельца. Копирование заняло две с половиной минуты и затратило фактически все доступное время ЦПУ. Программа так медлительна потому, что вынуждена была выполнить более двух миллионов системных вызовов.

В последние годы ОС Linux продемонстрировала огромные успехи в повышении производительности системных вызовов и файловой системы. Для сравнения аналогичный тест с применением ядра 2.6 занял чуть менее 14 секунд:

$ TIMEFORMAT="" time ./copy_system

2.08user 10.59system 0:13.74elapsed 92%CPU

...

Упражнение 3.2. Вторая версия программы кодирования файла

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

#include <unistd.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdlib.h>

int main() {

 char block[1024];

 int in, out;

 int nread;

 in = open("file.in", O_RDONLY);

 out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);

 while((nread = read(in, block, sizeof(block))) > 0)

  write(out, block, nread);

 exit(0);

}

Теперь испытайте программу, но сначала удалите старый выходной файл.

$ rm file.out

$ TIMEFORMAT="" time ./copy_block

0.00user 0.02system 0:00.04elapsed 78%CPU

...

Как это работает

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

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

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

lseek

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

#include <unistd.h>

#include <sys/types.h>

off_t lseek(int fildes, off_t offset, int whence);

Параметр offset применяется для указания позиции, а параметр whence определяет способ применения offset и может принимать следующие значения:

SEEK_SEToffset задает абсолютную позицию;

SEEK_CURoffset задается относительно текущей позиции;

SEEK_ENDoffset задается относительно конца файла.

Вызов lseek возвращает величину параметра offset в байтах, измеряемую от начала файла, для которого установлен указатель, или -1 в случае неудачного завершения. Тип данных off_t, применяемый для параметра offset в операциях поиска, — зависящий от реализации тип integer (целое), определенный в файле sys/types.h.

fstat, stat и lstat

Системный вызов fstat возвращает информацию о состоянии файла, ассоциированного с открытым дескриптором файла. Эта информация записывается в структуру buf, адрес которой передается как параметр.

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

#include <unistd.h>

#include <sys/stat.h>

#include <sys/types.h>

int fstat(int fildes, struct stat *buf);

int stat(const char *path, struct stat *buf);

int lstat(const char *path, struct stat *buf);

Примечание

Учтите, что включение файла sys/types.h не обязательное, но мы рекомендуем включать его при использовании системных вызовов, поскольку некоторые из их определений применяют для стандартных типов псевдонимы, которые могут измениться когда-нибудь.

Родственные функции stat и lstat возвращают информацию о состоянии названного файла. Они возвращают те же результаты за исключением того, что файл является символической ссылкой. Вызов lstat возвращает данные о самой ссылке, а вызов stat — о файле, на который ссылка указывает.

Элементы вызываемой структуры stat могут меняться в разных UNIX-подобных системах, но обязательно включают перечисленные в табл. 3.4 элементы.

Таблица 3.4

Элемент структуры stat  Описание
st_mode Права доступа к файлу и сведения о типе файла
st_ino Индекс, ассоциированный с файлом
st_dev Устройство, на котором размещен файл
st_uid Идентификатор (user identity) владельца файла
st_gid Идентификатор группы (group identity) владельца файла
st_atime Время последнего обращения
st_ctime Время последнего изменения прав доступа, владельца, группы или объема
st_mtime Время последней модификации содержимого
st_nlink Количество жестких ссылок на файл

У флагов st_mode, возвращаемых в структуре stat, также есть ряд ассоциированных макросов в заголовочном файле sys/stat.h. В эти макросы включены имена флагов для прав доступа и типов файлов и некоторые маски, помогающие проверять специфические типы и права.

Флаги прав доступа такие же, как в системном вызове open, описанном ранее. Для флагов типов файла включены следующие имена:

S_IFBLK — блочное устройство;

S_IFDIR — каталог;

S_IFCHR — символьное устройство;

S_IFIFO — FIFO (именованный канал);

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

S_IFLNK — символическая ссылка.

Для других флагов режима файла включены следующие имена:

S_ISUID — элемент получает setUID при выполнении;

S_ISGUID — элемент получает setGID при выполнении.

Для масок, интерпретирующих флаги st_mode, включены следующие имена:

S_IFMT — тип файла;

S_IRWXU — права пользователя на чтение/запись/выполнение;

S_IRWXG — права группы на чтение/запись/выполнение;

S_IRWXO — права остальных на чтение/запись/выполнение.

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

S_ISBLK — проверка для блочного файла;

S_ISCHR — проверка для символьного файла;

S_ISDIR — проверка для каталога;

S_ISFIFO — проверка для FIFO;

S_ISREG — проверка для обычного файла;

S_ISLNK — проверка для символической ссылки.

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

struct stat statbuf;

mode_t modes;

stat("filename", &statbuf);

modes = statbuf.st_mode;

if (!S_ISDIR(modes) && (modes & S_IRWXU) = S_IXUSR)

...

dup и dup2

Системные вызовы dup позволяют дублировать дескриптор файла, предоставляя два или несколько разных дескрипторов, обращающихся к одному и тому же файлу. Эта возможность может применяться для чтения и записи в разные части файла. Системный вызов dup дублирует файловый дескриптор fildes и возвращает новый дескриптор. Системный вызов dup2 умело копирует один дескриптор файла в другой, задавая дескриптор, применяемый для копии.

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

#include <unistd.h>

int dup(int fildes);

int dup2(int fildes, int fildes2);

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

Стандартная библиотека ввода/вывода

Стандартная библиотека ввода/вывода (stdio) и ее заголовочный файл stdio.h предоставляют универсальный интерфейс для системных вызовов ввода/вывода нижнего уровня. Библиотека, теперь часть языка С стандарта ANSI, в отличие от системных вызовов, с которыми вы встречались ранее, включает много сложных функций для форматирования вывода и просмотра ввода. Она также обеспечивает необходимые условия буферизации для устройств.

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

Примечание

Не путайте эти потоки файлов с потоками ввода/вывода в языке С++ и механизмом STREAMS, описывающим взаимодействие процессов и введенным в системе AT&T UNIX System V Release 3, который не рассматривается в данной книге. Для получения дополнительной информации о средствах STREAMS обратитесь к спецификации X/Open (по адресу http://www.opengroup.org) и руководству по программированию AT&T STREAMS Programming Guide, поставляемому с системой System V.

Три файловых потока открываются автоматически при старте программы. К ним относятся stdin, stdout и stderr. Эти потоки объявлены в файле stdio.h и представляют вывод, ввод и стандартный поток ошибок, которым соответствуют низкоуровневые файловые дескрипторы 0, 1 и 2.

В данном разделе мы рассмотрим следующие функции:

fopen, fclose;

fread, fwrite;

fflush;

fseek;

fgetc, getc, getchar;

fputc, putc, putchar;

fgets, gets;

printf, fprintf и sprintf;

scanf, fscanf и sscanf;

fopen.

fopen

Библиотечная функция fopen — это аналог низкоуровневого системного вызова open. Она используется в основном для файлов и терминального, ввода и вывода. Там, где нужно явное управление устройствами, больше подойдут системные вызовы, поскольку они устраняют потенциальные нежелательные побочные эффекты применения библиотек, например, в случае буферизации ввода/вывода.

Далее приведена синтаксическая запись функции:

#include <stdio.h>

FILE *fopen(const char *filename, const char *mode);

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

□ "r" или "rb" — открыть только для чтения;

□ "w" или "wb" — открыть для записи, укоротить до нулевой длины;

□ "а" или "ab" — открыть для записи, дописывать в конец файла;

□ "r+" или "rb+" или "r+b" — открыть для изменения (чтение и запись);

□ "w+" или "wb+" или "w+b" — открыть для изменения, укоротить до нулевой длины;

□ "a+" или "ab+" или "а+b" — открыть для изменения, дописывать в конец файла. Символ b означает, что файл бинарный, а не текстовый.

Примечание

В отличие от MS-DOS, системы UNIX и Linux не делают различий между текстовыми и бинарными файлами. UNIX и Linux обрабатывают их одинаково с эффективностью обработки бинарных файлов. Важно также учесть, что параметр mode должен быть строкой, а не символом. Всегда применяйте двойные кавычки, а не апострофы.

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

Количество доступных потоков ограничено, как и число дескрипторов файлов. Реальное предельное значение содержится в определенной в файле stdio.h константе FOPEN_MAX и всегда не менее 8, а в ОС Linux обычно 16.

fread

Библиотечная функция fread применяется для чтения данных из файлового потока. Данные считываются из потока stream в буфер данных, заданный в параметре ptr. Функции fread и fwrite имеют дело с записями данных. Записи описываются размером size и количеством передаваемых записей nitems. Функция возвращает количество записей (а не байтов), успешно считанных в буфер данных. При достижении конца файла может быть возвращено меньше записей, чем nitems, вплоть до нуля.

Далее приведена синтаксическая запись функции:

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);

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

fwrite

Интерфейс библиотечной функции fwrite аналогичен интерфейсу функции fread. Она принимает записи данных из заданного буфера данных и записывает их в поток вывода. Функция возвращает количество успешно записанных записей.

Далее приведена синтаксическая запись функции:

#include <stdio.h>

size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);

Примечание

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

fclose

Библиотечная функция fclose закрывает заданный поток stream, заставляя записать все незаписанные данные. Важно применять функцию fclose, поскольку библиотека stdio будет использовать буфер для данных. Если программе нужна уверенность в том, что все данные записаны, следует вызвать fclose. Имейте в виду, что функция fclose вызывается автоматически для всех файловых потоков, которые все еще открыты к моменту нормального завершения программы, но при этом у вас, конечно же, не будет возможности проверить ошибки, о которых сообщает fclose.

Далее приведена синтаксическая запись функции:

#include <stdio.h>

int fclose(FILE* stream);

fflush