Текст
                    


МАТЕМАТИЧЕСКОЕ
ОБЕСПЕЧЕНИЕ
ЭВМ
С. Баурн
ОПЕРАЦИОННАЯ
СИСТЕМА UNIX
3
I
I




ФФФФФ4М
ФФФФФ4Н

Операционная система UNIX
INTERNATIONAL COMPUTER SCIENCE SERIES THE UNIX SYSTEM S.R. Bourne Bell Laboratories Addison-Wesley Publishing Company London-Reading, Massachusetts* Menlo Park, California Amsterdam • Don Mills* Ontario-Manila-Singapore Sydney.Tokyo 1983
МАТЕМАТИЧЕСКОЕ ОБЕСПЕЧЕНИЕ ЭВМ С.Баурн ОПЕРАЦИОННАЯ СИСТЕМА UNIX Перевод с английского Н. Б. Дерябина и О.А, Савицкой под редакцией Ю.М. Банковского Москва «Мир» 1986
ББК 32.973 Б 29 УДК 6Ф77.3 Баурн С. Б29 Операционная система UNIX: Пер с англ. — Мл Мир, 1986.—463 с. Описание одной из самых распространенных зарубежных операционных систем и языка С, данное известным американским специалистом. Книга отличается практической направленностью, материал хорошо систематизи- рован и доступен для первоначального ознакомления. Она интересна и с теоретической точки зрения как описание образца удачной операционной системы. Для программистов, работающих на СМ-4, для преподавателей вузов, аспирантов и студентов. 1702050000-341 Б 041 (01)-86 29-86, ч. 1 ББК 32.973 Редакция литературы по математическим наукам © 1983 Bell Telephone Labora tories, Incorporated © перевод на русский язык, «Мир», 1986
ПРЕДИСЛОВИЕ РЕДАКТОРА ПЕРЕВОДА Система UNIX была создана несколькими программистами в интересах небольшого коллектива. Первоначальная задача со- стояла в обеспечении удобной операционной среды на машине PDP 9, но затем задача несколько трансформировалась — нужно было перенести эту среду на машину PDP 11. Разработчики и не предполагали, что спустя короткое время система получит столь широкое распространение и, в сущности, станет стандартом де- факто. Чем объяснить этот феномен UN IX’а? Называют несколько причин. Во-первых, система UNIX в полной мере отвечает популярному принципу «красота в про- стоте»; она явилась антиподом операционной системе OS/360, которая для многих пользователей так и осталась «вещью в себе» из-за своей эклектичности и громоздкости. Во-вторых, универси- теты могли свободно, без всякой оплаты, получить систему, даже с исходными текстами, и она оказалась прекрасным педагоги- ческим материалом для целого поколения студентов, ставших затем ее проводниками в жизнь. В-третьих, благодаря языку С, на котором написана система UNIX, ее удается без чрезмерных затрат переносить на другие машины. Далее, появление системы UNIX совпало с началом эры микрокомпьютеров и персональных ЭВМ; в силу их разнообразия существенно усилился интерес к стандартизации операционных систем и как следствие к сис- теме UNIX. И наконец, немаловажную роль сыграл тот факт, что разработчики системы делали ее «для себя» и на себе испы- тывали — известно, что системам, сделанным из общих сообра- жений и рассчитанным на абстрактного пользователя, редко со- путствует успех. Разумеется, система UNIX не панацея от всех бед. Сама система требует развития и, очевидно, будет совершенствоваться. Можно выделить хотя бы следующие актуальные направления такого совершенствования: повышение надежности и эффектив- ности, обеспечение механизма виртуальной памяти и средств для работы в реальном масштабе времени, создание современного
Предисловие редактора перевода интерфейса с человеком (существующий одномерный текстовый язык команд уже не может удовлетворить), ориентация на мно- гопроцессорную, сетевую и распределенную обработку инфор- мации. Работы в этих направлениях уже ведутся. В книге С. Баурна отражено текущее состояние системы UNIX. Обширный материал по системе изложен очень компактно. Книга снабжена приложениями, которые превращают ее в спра- вочник, настольное руководство для программиста. Если принять во внимание еще и то, что автор книги непосредственно причастен к работам, связанным с системой, то можно утверждать: настоя- щая книга будет чрезвычайно полезна широкому кругу програм- мистов. Ю. Банковский
ПРЕДИСЛОВИЕ Предлагаемая книга является практическим руководством по системе UNIX; она будет полезна всем пользователям от нович- ков до профессионалов. Преимущества этой системы иллюстри- руются в тексте многочисленными примерами. Такие примеры, демонстрирующие связи между командами, помогут пользовате- лю полностью овладеть всей мощью системы UNIX. Во введении вкратце приведена история появления ранних версий системы UNIX и представлены компоненты системы, в том числе файлы и процессы. В последующих главах книги рассмат- риваются основные элементы системы UNIX, включающие управление файлами, управление процессами, системные вы- зовы, редактор, оболочку, язык С, форматоры текстов troff и nroff и средства обработки данных. Глава 2 знакомит пользователя с системой и с наиболее ши- роко используемыми командами. Разъясняется процедура входа в систему, описывается имеющаяся документация. Основные рассматриваемые здесь темы — оболочка и файловая система. Во всех последующих главах предполагается, что читатель озна- комился с этой главой. В гл. 3 представлены два редактора — ed и vi. Обе програм- мы предназначены для работы с терминала и используются для создания и модификации файлов. При чтении гл. 2 и 3 хорошо иметь под рукой терминал и испытать на практике некоторые команды. Если вы зайдете в тупик, попросите совета у вашего коллеги. Часто это самый быстрый способ научиться пользо- ваться системой. Оболочка обеспечивает интерфейс с системой UNIX как для пользователей, работающих в интерактивном режиме, так и при выполнении командных файлов, применяемых для на- стройки среды программирования в соответствии с потребностя- ми отдельных лиц или групп пользователей. В гл. 2 дается пер- воначальное представление об интерактивном использовании
8 Предисловие оболочки. Вопрос о том, как писать командные файлы (програм- мы), подробно рассматривается в гл. 4. В гл. 5 описание языка С приводится в объеме, вполне доста- точном для составления программ «разумного» размера. Знаком- ство с каким-либо другим языком программирования облегчит чтение этой главы. Здесь же рассматривается использование системы управления программами make и отладчика adb. В следующей главе описан интерфейс с системой UNIX для программистов, пишущих на языке С. Особое внимание уделяется написанию программ, которые используют возможности, предо- ставляемые непосредственно операционной системой. Эта глава предназначена для пользователей, пишущих системные команды на языке С. Приводятся также дополнительные сведения о фай- ловой системе, не упомянутые в гл. 2. Одним из главных применений системы UNIX является об- работка текстов и подготовка документов. В гл. 7 описывается набор программ nroff, troff, eqn и tbl, которые вместе с текстовым редактором облегчают создание и правку документов. В этой главе содержится также пример испытанного на практике пакета форматирования текстов, который использовался при подготовке английского оригинала этой книги. В последней главе рассматривается набор средств обработки данных, состоящий из таких программ, как awk, grep, sort и join, обеспечивающих гибкое управление небольшими базами данных или же обработку небольших объемов данных. Приво- дятся примеры полностью завершенных систем, в основу кото- рых положены эти средства. Каждый пример подробно разби- рается и рассматривается создание новых средств обработки данных, отсутствующих в стандартной системе UNIX. При чте- нии гл. 8 потребуются сведения из всех предшествующих глав. В приложениях дается сводка различных команд, используе- мых в книге. В этой книге многие аспекты системы UNIX рассматриваются с точки зрения пользователя. Предполагается, что читатель знаком с терминологией в области современной информатики. Благодарности В создании этой книги принимала участие большая группа лю- дей. Я в долгу у Эндрю Макгеттрика, без чьей помощи эта книга никогда не была бы закончена. Я весьма признателен Элу Ахо, Дугу Макилрою и Бобу Аллену, прочитавшим всю книгу от начала до конца и сделавшим подробные замечания.
Предисловие 9 Мне хотелось бы также поблагодарить Боба Морриса, Дэн- ниса Ритчи и других моих коллег из Computer Science Research Center за многочисленные обсуждения наиболее тонких моментов в системе UNIX. Я хотел бы поблагодарить Марка Баурна, Чарли Харриса, Питера Хонимэна, Джима Кайзера, Майка Леска, Дэйва Син- коски, Энди Таненбаума, Тода Тигера и ряд других сотрудников отделения, возглавляемого Энди Холлом (фирма Bell Laborato- ries), выборочно читавших ранние варианты книги и сделавших свои замечания. . Я признателен Саре Маллен и Энн Странк из издательства Addison Wesley и Джейн Баурн за помощь в контрольном чтении окончательного варианта книги. Большую помощь в подготовке первых версий книги оказали Мэрилу Бьюно, Джери Марки и Сью Уорд. Создание этой книги стало возможным благодаря прекрасным условиям в Computer Systems Research Laboratory, обеспеченным Бобом Лакки и Холлом Аллесом. Наконец, мне хотелось бы поблагодарить за поддержку чле- нов моей семьи.
Посвящается Джейн, Питеру, Марку и Саре ГЛАВА 1 ВВЕДЕНИЕ Слово UNIX обозначает семейство операционных систем, разра- ботанных фирмой Bell Laboratories. Система UNIX включает в себя как собственно операционную систему, так и набор свя- занных с ней системных программ-команд. Операционная сис- тема управляет информационно-вычислительными ресурсами (иерархической файловой системой, процессами) и выполняет прочие административные функции. К командам относятся ба- зисные средства обработки файлов и данных, редакторы, ассемб- леры, компиляторы, форматоры текстов. Существует мощный интерпретатор команд, позволяющий отдельным пользователям или пользовательским коллективам настраивать операционную среду на свой собственный лад путем определения своих соб- ственных команд. Большой интерес представляет история возникновения первой версии системы UNIX. В шестидесятые годы основные усилия в области разработки программного обеспечения были направле- ны на развитие языков программирования и операционных сис- тем. В это время были разработаны такие языки, как PL/I, APL, Симула 67, Алгол 68 и Кобол. Относительные достоинства и недостатки этих языков стали предметом дискуссий, часто до- вольно острых. В Великобритании Лондонский и Кембриджский университеты предприняли совместный проект по разработке комбинированного языка программирования — CPL (Combined Programming Language), но этот проект не был реализован в пол- ном объеме. Тем не менее в рамках этого проекта сформирова- лись основные понятия языка BCPL (Basic CPL), сыгравшего важную роль в истории создания системы UNIX. Операционные системы в те времена разрабатывались для ЭВМ среднего и высокого быстродействия как средства эффек- тивного разделения ресурсов между пользователями. В противо- положность пакетной обработке появились интерактивный режим работы и режим разделения времени. Были изучены стратегии управления страничной памятью, механизмы защиты, плани-
1.1. Историческая справка 11 рование заданий, принципы построения файловых систем и дру- гие родственные вопросы. Были разработаны операционные системы, такие, как CTSS (Крисмэн, 1965), Multics (Фейертаг, 1969) и (в Европе) Cambridge Multiple Access System (Хартли, 1968). Многие фундаментальные концепции системы UNIX заимствованы из этих систем. Так, например, файловые системы и независимый от устройств ввод-вывод, процессы и командные языки — все это в том или ином виде уже имелось в упомянутых системах. 1.1. ИСТОРИЧЕСКАЯ СПРАВКА Все началось с Кена Томпсона в 1968 году. Он тогда только что вернулся из Беркли, где Батлер Лэмпсон работал над созда- нием операционной системы SDS930 (Дейтч и Лэмпсон, 1965). Деннис Ритчи пришел в фирму Bell Laboratories в 1967 году из Гарварда, где занимался прикладной математикой. Томпсон оказался в группе талантливых людей, многие из которых только что прервали свои работы над системой Mul- tics — совместным проектом фирм Bell Laboratories, General Electric и Массачусетского технологического института. После отказа фирмы Bell Laboratories от участия в проекте Multics и ликвидации системы GE 645 в марте 1969 года перед исследо- вательской группой по информатике встала проблема замены вы- числительных средств. Предложения на установку нового обо- рудования рассматривались и затем отвергались из-за излишней дороговизны. К тому же после краха проекта Multics разработка операционных систем стала непопулярной областью исследова- ний. Самого Томпсона интересовали вопросы создания файловой системы, а совсем не операционной системы. Проект такой сис- темы сложился в ходе бесед между Раддом Кенеди, Томпсоном и Ритчи. Макеты первых версий файловой системы были написаны Томпсоном в системе GECOS. Другая ветвь истории связана с программой «космическое путешествие», написанной Томпсоном и Ритчи в системе GECOS. Эта программа плохо работала в системе разделения времени GECOS — требовалось более быстрое время ответа. В их рас- поряжении имелась ЭВМ PDP 7 с дисплеем 340, но в ее программ- ное обеспечение входили только ассемблер и загрузчик. В каж- дый момент времени на машине мог работать только один поль- зователь (в монопольном режиме). Такой режим работы был несовершенным, и вскоре стали появляться компоненты одно- пользовательской системы UNIX. Программа «космическое путешествие» была переписана для PDP 7. Были написаны и отранслированы в системе GECOS с использованием кроссассемб-
12 Гл. 1. Введение лера для PDP 7 ассемблер и ядро простейшей операционной системы. Эта первая система не обеспечивала разделение вре- мени: ЭВМ PDP 7, как и современные персональные компью- теры, имеет простейшее устройство и не годится для такого ре- жима работы. Вскоре в системе появились ассемблер и интер- претатор команд. Файловая система имела структуру ориенти- рованного графа с поименованными вершинами. Для всех под- оглавлений использовалось единое оглавление. С его помощью осуществлялись связи между файлами. Применение кроссассемблера означало использование двух ЭВМ и перенос перфолент с программами с одной ЭВМ на другую всякий раз при внесении изменений в программу. Система вскоре была модифицирована для PDP 7. При модификации системы возникла концепция образов процессов и был реализован при- митив образования нового процесса — fork. Затем появились основные обслуживающие программы (утилиты), такие, как копирование, редактирование, исключение и печать файлов. Система обеспечивала одновременную работу двух пользовате- лей. Брайан Керниган в 1970 году придумал для нее название UNIX. Группа все еще не имела своей собственной вычислительной машины. После ряда неудачных попыток для осуществления проекта подготовки текстовых документов Джо Оссанна пред- ложил приобрести PDP 11/20. В конце 1970 года PDP 11 была установлена, и начались работы по переносу системы на эту более мощную машину. Проект подготовки документов был успешно осуществлен, и патентный отдел фирмы, работавший на этом оборудовании совместно с исследовательской группой, стал первым пользова- телем системы UNIX. Томпсон и Ритчи составили руководство по этой первой версии системы, датированное ноябрем 1971 года. В этой версии были воплощены все важнейшие концепции, лежа- щие в основе современных версий системы UNIX, включая фай- ловую систему, управление процессами, системный интерфейс и основные команды-утилиты; единственным исключением были транспортеры. В июне 1972 года появилась вторая версия. По настоянию Дуга Макилроя в нее были включены транспортеры. Система и ее утилиты все еще были написаны на ассемблере. Томпсон за- нимался также и разработкой языка В (произносится «би») — на нем был написан ассемблер системы. Язык В — прямой по- томок BCPL, но компилятор В мог выдавать программу для ин- терпретации за один проход. Как В, так и BCPL — языки без типов данных, единственные объекты данных в них — машин- ные слова. Это затрудняло использование побайтных команд PDP 11, и поэтому в язык В были введены типы. Новый язык
1.1. Историческая справка 13 был назван NB, однако попытка переписать на нем систему оказалась неудачной. Для того чтобы ускорить выполнение программ, Ритчи начал работу над генератором кода для NB. Язык был назван С (произносится «си»), и, хотя тогда в нем еще не было структур и глобальных переменных, он оказался доста- точно заманчивым, так что новые утилиты стали писать непо- средственно на С. Очень плодотворным стал 1973 год. До сих пор система была написана на ассемблере, но после добавления структур в язык С она была успешно переписана на С. Томпсон написал управление процессами, а Ритчи — систему ввода-вывода. Шестая версия системы UNIX, ставшая первой широко распространенной версией, была закончена в мае 1975 года й распространялась за очень низкую плату. Работы по совершенствованию системы продолжались. Была написана новая файловая система, рассчитанная на файлы боль- ших размеров, и переделана оболочка системы с целью обеспе- чить более удобные средства программирования на командном языке. Последним значительным проектом Томпсона и Ритчи стало создание мобильной версии системы. Ее реализация велась на ЭВМ Interdata 8/32, машине с 32-разрядным словом, подобной ЭВМ серии IBM 370, но достаточно отличающейся от PDP 11 для того, чтобы вскрыть большинство машиннозависимых мест в системе. Этот проект привел и к ряду нововведений в язык С, среди которых — объединения, шаблоны и определения типов. Результатом этой работы стало создание Седьмой Версии систе- мы UNIX, доступной для широкого использования с 1979 года. И хотя шестая версия системы до сих пор используется, в боль- шинстве случаев она заменена седьмой версией. В настоящее время система UNIX считается стандартной операционной системой и реализована на самых разнообразных ЭВМ от микро до универсальных ЭВМ. Седьмая Версия была выпущена для ЭВМ PDP 11 с 16-разрядным словом. Первой системой для VAX 11/780 стала UNIX 32V, работа по переносу которой была выполнена также в фирме Bell Laboratories Джо- ном Рейзером и Томом Лондоном. Система получила дальнейшее развитие и в настоящее время распространяется Калифорний- ским университетом (Беркли, США). Развитие системы продол- жалось и в фирме Bell Laboratories. Сейчас версия UNIX Sys- tem V поставляется компанией Western Electric Company. Между этими версиями существуют некоторые различия как в операционной системе, так и в командах, но это не вызовет у читателя больших затруднений. Материал книги применим к каждой из этих систем, а особенности, присущие какой-либо одной из них, не рассматриваются. Программы, приведенные в этой книге, были скомпилированы и опробованы в системе
14 Гл. 1. Введение UNIX System V Bell Laboratories и в версии Калифорнийского университета (Беркли). Многие системные программы (команды) были первоначально написаны для ЭВМ PDP 11, адресное пространство которой ограничено 64К байтами. Это ограничение оказало в целом бла- гоприятное воздействие на программное обеспечение. Системы разрабатываются как набор легко стыкующихся компонент. Однако недостаток адресного пространства все же не позволил эффективно реализовать такие языки, как Лисп, до тех пор пока не появились машины с 32-разрядным словом. Система UNIX удачно спроектирована. Она стала эталоном простоты для операционных систем с разделением времени. Это одна из первых операционных систем, получивших широкое, применение на миниЭВМ, а именно на PDP 11. Сочетание этих факторов соответствовало потребностям факультетов универси- тетов, и целое поколение специалистов по информатике обуча- лось на системе UNIX. Первоначальный системный интерфейс выдержал испытание временем и не претерпел существенных изменений с момента первоначальной разработки. Эта стабильность послужила осно- вой для развития прикладных систем. Документация системы UNIX привлекает краткостью изложения, хотя некоторые и считают ее слишком краткой. Система UNIX оказалась на редкость удачной. Во время написания этой книги в мире насчитывалось свыше 3000 уста- новок, на которых она активно использовалась. Такие уста- новки можно найти в университетах, государственных учреж- дениях, коммерческих организациях и в разнообразных отрас- лях промышленности. В фирме Bell Laboratories система UNIX используется сотрудниками для разработки программ в диало- говом режиме, а также как система связи и система обработки текстов. Система UNIX мобильна, легко осваивается как поль- зователями, так и обслуживающим персоналом и предоставляет возможности, отсутствующие в других, иногда больших по раз- меру системах. 1.2. СРЕДА ПРОГРАММИРОВАНИЯ Система UNIX проста, изящна и предоставляет пользователям удобную среду программирования. Среди имеющихся средств — следующие: • компилятор языка С и отладчик; • ряд других языковых процессоров, включая APL, Бей- сик, Фортран 77, Паскаль и Снобол; • редакторы текстов ed, vi и emacs; • средства обработки текстов и подготовки документов
1.3. Концепции системы UNIX 15 (в том числе и содержащих математические формулы) tbl, eqn, troff и nroff; • средства построения компиляторов уасс и lex; • средства связи между пользователями mail и write; • средства машинной графики; • прикладные пакеты, например пакеты для расчета элект- рических цепей. Эти средства доступны пользователям через командный язык, обеспечивающий интерфейс между пользователями и опе- рационной системой UNIX. Программа, реализующая команд- ный язык, называется оболочкой, а программы, написанные на командном языке, иногда называются командными процедурами. Командный язык позволяет задавать в командах входные и выходные файлы и предоставляет типичные для алгоритмических языков управляющие конструкции. В существующей среде программирования сложилась мето- дика эффективной разработки программ, включающая в себя следующие рекомендации: • Стремитесь к тому, чтобы каждая программа выполняла только одну функцию. • Избегайте избыточного, хаотического, неструктурирован- ного вывода в программе. Имейте в виду, что вывод лю- бой программы может быть вводом для другой. • По мере возможности используйте или переделайте уже существующее средство, вместо того чтобы создавать новое, начиная с нуля. • Как можно скорее создайте маленький работающий про- тотип системы, а затем постепенно модифицируйте и на- ращивайте его до тех пор, пока не получится законченная система. При этом необходимо, чтобы каркас системы оп- ределился прежде, чем будет написана значительная, часть программы. 1.3. КОНЦЕПЦИИ СИСТЕМЫ UNIX 1.3.1. Файловая система Файловая система позволяет пользователям хранить поимено- ванные совокупности данных. Обеспечиваются средства защиты от сбоев аппаратуры и от несанкционированного доступа. Файло- вая система UNIX проста: отсутствуют блоки управления, спе- цифика устройств ввода-вывода скрыта от пользователя, для всех видов ввода-вывода используется единый интерфейс. В фай- ловой системе UNIX различаются три вида файлов. • Обычный файл содержит текст документа или программы. В виде обычных файлов хранятся также и выполняемые
16 Гл. 1. Введение программы (двоичные файлы). Концептуально файл со- стоит не из записей, а представляет собой просто после- довательность литер. Там, где это требуется, для выде- ления записей можно использовать литеру «новая стро- ка». ев оглавлении содержатся имена других файлов и/или оглавлений. Пользователь может создавать свои подо- главления для объединения файлов в группы по их тематике. Таким образом, файловая система является иерархической. Оглавление можно читать как обычный файл, но в него запрещено писать. • Устройствам ввода-вывода соответствуют специальные файлы. Внешне операции со специальными файлами ни- чем не отличаются от операций с обычными файлами, но обмен данными осуществляется непосредственно с уст- ройством, а не с файловой системой. Для специальных файлов предоставляются такие же средства защиты от несанкционированного доступа, как и для обычных файлов. 1.3.2. Процессы Исполнителями программ в системе UNIX являются процессы. Процесс — это единая последовательность событий. С процессом соотносятся некоторая часть памяти ЭВМ и множество доступ- ных файлов. Новый процесс создается путем копирования ста- рого. Единственное различие между старым и новым процесса- ми заключается в том, что старый (родительский) процесс может подождать завершения своего потомка. Процесс может заменить свою программу на новую и выполнить ее. Такой механизм и изящен, и эффективен. 1.3.3. Оболочка системы Оболочка — это в сущности командный' язык, который обеспе- чивает интерфейс пользователя с операционной системой UNIX. Оболочка выполняет команды, поступающие либо с терминала, либо из файла. Пользователь может строить свои собственные команды, создавая командные файлы (т. е. файлы, состоящие из команд). Эти новые команды имеют тот же статус, что и «систем- ные» команды. Таким образом, можно построить новую среду, отвечающую потребностям отдельного человека или группы пользователей. Транспортеры позволяют связывать процессы таким образом, что вывод от одного процесса становится вводом для другого. Командный язык предоставляет простые средства для использо- вания транспортеров.
ГЛАВА 2 НАЧАЛЬНЫЕ СВЕДЕНИЯ 2.1. ВХОД В СИСТЕМУ Прежде чем начать работу в системе, вы должны получить ре- гистрационное имя у администратора вашей системы. Если подключение к системе осуществляется через телефонную сеть, вам также нужно узнать телефонный номер вашей системы. Вместе с регистрационным именем вы получите пароль, который не позволит посторонним лицам войти в систему под вашим именем. Система UNIX обеспечивает работу с терминалами различных типов, начиная от простейших печатающих устройств и кончая графическими терминалами с высокой разрешающей способ- ностью. Поскольку терминалы отличаются друг от друга, убе- дитесь в том, что все параметры для вашего терминала установ- лены надлежащим образом. Такими параметрами являются: скорость ввода-вывода, контроль на четность (или нечетность), дуплексный режим (для удаленных терминалов) и наличие верх- него/нижнего регистров. Соединения по телефонным каналам связи (через модемы) обычно работают со скоростью 300 и 1200 бод, в то время как жестко замонтированные соединения, как правило, обеспечивают скорость 1200 или 9600 бод (300 бод — это примерно 30 литер в секунду). Когда вы первый раз подключаетесь к системе, она пытается определить скорость вашего канала связи по первой набранной вами литере. Обычно это литера возврата каретки (return). Если компьютер выдает на терминал бессмысленный набор литер, попробуйте просто нажать клавишу break (прерывание); в не- которых системах это сигнализирует о необходимости изменить скорость. В конце концов вы должны получить приглашение для входа в систему login: Теперь следует набрать свое регистрационное имя и нажать кла- вишу return, Пока вы не нажмете клавишу возврата каретки, система ничего делать не будет. Далее процедура login запросит ваш пароль. Пока вы набираете пароль, вводимые литеры не
18 Гл. 2. Начальные сведения будут отображаться на терминале (если это возможно для дан- ного терминала). Если введенные вами имя и пароль не зареги- стрированы в системе, будет выдано сообщение об ошибке login incorrect и вы снова получите приглашение для входа в систему. В не- которых реализациях процедуры login устанавливается тайм-аут на ввод пароля. Если вы за определенное время не наберете пароль, связь с системой будет прервана. Если вам удалось войти в систему, то, возможно, на терминал будет выдано сообщение, называемое «новости дня». За ним по- следует приглашение программы-оболочки — обычно это ли- тера $ или %. Теперь можно набирать команды. Если что-то не так и вы не можете войти в систему, следует обратиться за помощью к специалисту. Потенциальных причин для этого так много, что их невозможно здесь перечислить. 2.2. КОМАНДЫ Команда состоит из последовательности слов, разделенных пробелами или литерами табуляции. Первое (и, возможно, един- ственное) слово в команде — ее имя. Например, в ответ на коман- ду date на терминал будет выведена строка вида Wed Sep 1 12:12:19 EDT 1982 Три буквы EDT (Eastern Daylight Time) означают восточное по- ясное время. Команда who выдает список пользователей, работающих в системе в данный момент, упорядоченный по номерам терминалов, например: srb ityOO May 5 11:30 jmg ttyO1 May 5 21:22 lea tty13 May 5 22:29 cc tty29 May 5 16:11 jack tty41 May 5 08:39 В этом списке должна появиться строка, содержащая ваше соб- ственное регистрационное время, а также время входа в систему и номер вашего терминала (tty). Сокращение tty образовано от названия фирмы-изготовителя терминалов Teletype.
2.1. Характеристики терминала 19 Если команда состоит из нескольких слов, то второе и все последующие слова доступны выполняемой команде как пара- метры. Например, для того чтобы изменить свой пароль, войдите в систему и наберите passwd srb заменив srb на свое регистрационное имя. Команда passwd за- просит сначала ваш старый пароль, затем новый пароль и по- просит повторить новый пароль: Old password: New password: 'Retype new password: Рекомендуется использовать пароль длиной не менее шести ли- тер; в некоторых системах запрещается использовать пароль, состоящий менее, чем из шести литер. Практика показывает, что чем сложнее пароль, тем труднее его разгадать. При наборе па- ролей литеры не отображаются на терминале. Другим примером команды с параметрами является команда печати календаря cal. Она печатает календарь на определенный год или месяц. Если задан один параметр, то по команде cal печатается календарь на весь год, заданный этим параметром. Чтобы получить календарь на определенный месяц, требуется два параметра: месяц и год. Например, по команде cal 2000 на- печатается календарь на 2000 год, а по команде cal 9 1982 будет выдан календарь на сентябрь 1982 года: 1 September 1982 S М In W Th F S 12 3 4 5 в 7 8 9 10 11 12 13. 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 2.3. ХАРАКТЕРИСТИКИ ТЕРМИНАЛА Ошибки, допущенные при вводе с терминала, можно исправить путем стирания (erasing) отдельных литер или отмены (killing) всей строки и последующим вводом правильных литер. Для того чтобы стереть одну литеру, используется клавиша На- пример, строка passq^wd
20 Гл. 2, Начальные сведения эквивалентна строке passwd Для отмены строки используется клавиша Реакцией системы будет переход на новую строку. Эти две литеры (стирание и отмена) могут быть переопределены пользователем. В ранних версиях системы UNIX для стирания литеры и отмены строки применялись соответственно литеры 4k и Они и сейчас ис- пользуются по умолчанию; однако в дисплеях для стирания удобнее использовать литеру backspace (шаг назад). Для терминалов, работающих в коде ASCII, каждая вводимая строка заканчивается литерой return (возврат каретки). При вы- воде возврат каретки дополняется переводом строки (linefeed). Комбинация этих двух литер часто называется новой строкой (newline). Для установки параметров терминала служит команда stty. Естественно предположить, что от терминала к терминалу пара- метры варьируются. Пользователь может изменить параметры своего терминала. Например, команда stty erase ~ заменяет литеру стирания на так что теперь ввод слова whx~o эквивалентен вводу слова who. Если требуется ввести сами литеры 4k или @, то в качестве авторегистра используется литера\, отменяющая интерпрета- цию их как литер стирания и отмены. Таким образом, комбина- циях^ эквивалентна литере 4k, а \@—литере @. Если программа зациклилась или уже вывела достаточное количество информации, может оказаться необходимым прервать ее выполнение. Для этого следует нажать клавишу del или break, в результате чего вырабатывается сигнал прерывания. Оболочка и другие интерактивные программы обычно по прерыванию воз- вращаются на командный уровень и выдают приглашение для ввода очередной команды. Используя команду stty, можно заме- нить литеру del, возбуждающую прерывание, на другую ли- теру. Однако клавиша break порождает сигнал в канале связи и поэтому не может быть переопределена. В некоторых терминалах имеется механизм табуляции, для которого при включении терминала может понадобиться началь- ная установка. Для установки механизма табуляции терминала можно воспользоваться командой tabs. Не все старые терминалы выполняют табуляцию при выводе, а в некоторых нет клавиши для ввода табуляции (tab). На терминалах, не имеющих клавиши tab, литеру табуляции можно ввести как управляющее I (cont- rol-1). Если ваш терминал выполняет табуляцию неправильно, то можно воспользоваться командой stty —tabs. В результате
2.4. Документация 21 при выводе система будет заменять табуляции на соответствую- щее число пробелов. Предполагаемый интервал табулирования — 8 символов. В команде stty (см. приложение 1) имеется множество других вариантов для установки параметров терминала. Для обозначения управляющих литер, таких, как управляю- щее I, в книге используется сокращение вида Л1. Две другие управляющие литеры позволяют приостанавливать и возобнов- лять вывод на терминал. Литера AS приостановит вывод на тер- минал, а литера AQ возобновит его. Ввод любой другой литеры также возобновит вывод, а сама введенная литера будет передана в программу. Такое управление выводом невидимо для выпол- няющейся программы. 2.4. ДОКУМЕНТАЦИЯ Пользователь имеет доступ более чем к 150 различным системным командам. Сюда не относятся команды, написанные самими поль- зователями. Полные описания системных команд содержатся в UNIX Programmer's Manual (Руководство для программиста системы UNIX). Сведения о наиболее часто используемых командах системы UNIX приводятся также в приложении 1. Эту документацию можно также’ получить прямо из системы при помощи команды man, которая по заданному в ней имени команды выдает ее описание из руководства для пользователей. Например, команда man cal выведет страничку руководства с описанием команды cal (рис. 2.1)i). Ссылки на описания других команд делаются в виде имя-команды (том). Например, ссылка на описание команды stty имела бы вид stty (1). Все описания в руководстве имеют стандартный формат. Опи- сание состоит по крайней мере из трех разделов: ИМЯ (NAME) Приводится имя команды и ее значение. РЕЗЮМЕ (SYNOPSIS) Содержится краткое описание способа использования команды (ее формат). Слова, набранные жирным шриф- том, воспроизводятся при вводе команды без изменений. Остальные слова обозначают параметры команды, смысл которых поясняется в разделе ОПИСАНИЕ. Параметры, начинающиеся со знака минус, часто обозначают опции х) Описания, естественно, печатаются на английском языке, На рисунке дается перевод.'—Прим, перев.
22 Гл. 2. Начальные сведения данной команды. Квадратные скобки означают, что заключенные в них параметры можно опускать. Много- точие (...) указывает, что предшествующий ему параметр можно повторить произвольное число раз. Например, РЕЗЮМЕ для команды stty имеет вид stty [ опция . . . ]. За именем команды stty могут следовать одна или не- сколько опций. ОПИСАНИЕ (DESCRIPTION) Приводится описание действий, выполняемых командой, и смысл ее параметров. Могут присутствовать и другие разделы. В разделе ФАЙЛЫ (FILES) перечисляются используемые командой файлы; в разделе СМОТРИТЕ ТАКЖЕ (SEE ALSO) содержатся ссылки на род- CAL (1) UNIX. Руководство для программиста CAL(l) NAME cal — выдать календарь SYNOPSIS cal [месяц] год DESCRIPTION Команда cal выдает календарь на указанный год. Если задан и месяц, то календарь выдается только на этот месяц. Год задается числом от 1 до 9999. Месяц—это число от 1 до 12. Календарь выво- дится в виде, принятом в Англии и ее колониях. BUGS Считается, что год всегда начинается с января, хотя это чисто исторически сложившаяся услов- ность. Отметим, что “cal 78” относится к началу христианской эры, а не к 20 веку. Рис. 2.1 Вывод команды man cal ственные команды; в разделе ДИАГНОСТИКА (DIAGNOSTICS) обсуждается диагностика, которая может быть получена при выполнении команды; в разделе НЕДОСТАТКИ (BUGS) пере- числяются известные недочеты и ошибки, допущенные при разра- ботке и реализации команды. Руководство подразделяется на восемь томов. Информация распределяется по томам следующим образом: 1 Команды для пользователей. 2 Системные функции, интерфейс UNIX/C. 3 Библиотечные программы для языка С, в том числе стан- дартный пакет ввода-вывода (stdio) и библиотека матема- тических функций.
2.5. Файловая система 23 4 Специальные файлы. 5 Форматы файлов и соглашения о файлах. 6 Игры. 7 Пакеты обработки текстов. 8 Команды и процедуры для администратора системы. В приложении 1 данной книги содержится краткое описание команд системы, а в библиографии имеются ссылки на соответ- ствующие документы. Кроме man существует обучающая программа learn. Она может дать уроки по следующим темам: основные команды и работа с файлами; редактор; дополнительные возможности при работе с файлами; программа eqn для подготовки математических фор- мул; форматирующий пакет макроопределений —ms; введение в язык программирования С. 2.5. ФАЙЛОВАЯ СИСТЕМА Файловая система представляет собой иерархическую структуру с поименованными узлами. Каждое оглавление содержит имена файлов и оглавлений нижнего уровня. На структуру файла не накладывается никаких ограничений; каждый файл состоит про- сто из последовательности литер. Удобно, когда приняты согла- шения о структуре файлов, но в данном случае структура опре- деляется самостоятельно каждым программистом. Системе из- вестен формат файлов, содержащих выполняемые программы (файлы типа a.out). Полное (или составное) имя файла записывается в виде по- следовательности простых имен, разделенных косыми чертами. Составное имя /usr/srb/mbox начинается с оглавления //называемого корневым оглавлением, которое содержит оглавление usr. Оглавление /usr содержит оглавление srb, а внутри этого оглавления находится файл mbox. В приведенном примере имеются три оглавления:/,/usr и/usr/srb, каждое из которых, за исключением /, содержится в предше- ствующем (родительском) оглавлении. Длина каждого из про- стых имен, разделенных косыми чертами, не может превышать 14 литер. Простые имена не должны содержать косую черту и их не следует начинать с таких литер, как минус, так как это может привести к неправильной интерпретации опций и имен файлов в командах. Оглавления используются для объединения в группы файлов, имеющих общего владельца или связанных общей тематикой.
24 Гл. 2. Начальные сведения Первоначально администратор системы создает для каждого пользователя регистрационное оглавление. Пользователь rhm обычно получает оглавление /usr/rhm. Каждый работающий в системе пользователь имеет текущее (рабочее) оглавление. Команда pwd выдает полное имя текущего оглавления. Сразу после входа в систему текущим становится регистрационное оглавление поль- зователя. (См. также описание переменной $НОМЕ в оболочке.) Для задания нового текущего оглавления используется команда cd (изменить оглавление). Имена файлов можно зада- вать относительно текущего оглавления. Например, после вы- полнения команды cd /usr/srb/unix текущим оглавлением станет оглавление /usr/srb/unix, и на файл /usr/srb/unix/chl можно будет ссылаться, указывая только имя chi. Пользователь может создавать новые подоглавления для фай- лов, относящихся к разным темам, как это показано ниже. - usr - srb — - bin - include - src - cat - etc - lib - date passwd - group - cO - libc.a u’cds --- .. mbox Оглавление /usr содержит системные оглавления и оглавления пользователей. В оглавлении /etc содержатся системные файлы, в том числе и файл паролей. В оглавление /bin входят программы, реализующие системные команды, а в оглавлении /lib содержатся основные библиотеки программ, в том числе компилятор и биб- лиотека языка С. Более подробную информацию о структуре это- го оглавления можно найти в разделе hier Руководства для программиста системы UNIX.
2.5. Файловая система 25 2.5.1. Простейшие средства работы с файлами Команда cat (конкатенация) — одна из самых простых команд копирования файлов. Она копирует содержимое заданных файлов в стандартный файл вывода. По умолчанию, стандартный вывод происходит на терминал, хотя его можно направить и на дру- гое устройство (см. ниже). Команда cat обычно используется для вывода содержимого файла на терминал. Так, команда cat /etc/motd выводит файл /etc/motd. Этот файл содержит «новости дня», сооб- щаемые при входе в систему. Если в команде не задано ни одного параметра-имени файла, то cat читает данные из стандартного файла ввода. По умолчанию, стандартный ввод также осуществля- ется с терминала. Команду cat можно использовать для создания файлов путем ввода их содержимого с терминала, например: cat >newfile текст файла AD Вводимый текст помещается во вновь созданный файл newfile. Ввод заканчивается признаком конца файла, роль которого в случае терминала играет последовательность литер возврат каретки, AD (управляющее D). Такой способ создания файлов не всегда удобен. Для исправ- ления ошибок, сделанных при вводе, можно воспользоваться только литерами стирания и отмены. Гораздо удобнее для созда- ния файлов использовать редактор текстов; в этом случае ошибки можно исправлять по ходу редактирования. Редактирование файлов рассматривается в гл. 3. Для копирования содержимого одного файла в другой обычно используется команда ср (копирование). Она имеет вид ср from to Параметры from (откуда) и to (куда), задающие имена файлов, обязательны. Две команды cat file и ср file /dev/tty эквивалентны, поскольку /dev/tty — это имя файла, соответ- ствующего терминалу. Если необходимо, при выполнении команд ср и cat создаются новые файлы. Файлы можно также переименовывать и уничто-
26 Гл. 2. Начальные сведения жать. Команда гт исключает имя файла из оглавления и уничто- жает содержимое файла. Например, команда rm junk уничтожит файл junk в текущем оглавлении. Если файл был унич’ тожен по ошибке, то в некоторых случаях он может быть восста" новлен администратором системы. Команда mv перемещает (или переименовывает) файлы. На- пример, команда mv oldname newname переименует файл oldname (старое имя) в файл newname (новое имя). Имя файла изменяется, а содержимое остается неизмен- ным. Если файл с именем newname уже существует, то команда mv заменит его. Если при этом пользователь не имеет полномочий на запись в этот файл, система попросит его подтвердить свое намерение уничтожить файл, и, если в ответ будет введена буква у (т. е. «да»), файл уничтожается. Команды rm и mv следует ис- пользовать осторожно, поскольку они могут привести к потере информации. 2.5.2. Работа с оглавлениями Файлы можно создавать, уничтожать и переименовывать. Такие же операции могут применяться и к оглавлениям. Однако для оглавлений эти операции являются привилегированными и могут быть выполнены только при помощи команд: mkdir Создать оглавление. rmdir Уничтожить пустое оглавление. mv Переименовать оглавление. Если текущим оглавлением является /usr/mdm, то команда mkdir tmg создаст оглавление /usr/mdm/tmg, а команда rmdir tmg уничтожит его. Команда rmdir уничтожает только пустые оглав- ления, которые к тому же не являются текущими. При создании оглавления в нем для удобства автоматически создаются два стандартных элемента с именами . и .. . Имя . обо- значает само это оглавление, а имя .. — предшествующее (роди- тельское) оглавление. Поэтому имена файлов ./mbox и mbox эквивалентны. Если /usr/mdm/tmg — текущее оглавление, то после выполнения команды cd .. новым текущим оглавлением станет родительское оглавление /usr/mdm.
2.6. Оболочка 27 При помощи команд mv и ср можно переносить или копиро- вать группы файлов в заданное оглавление. Как команда mv файлг файл2 ... оглавление так и ср файлх файл2 ... оглавление выполняют эти действия, причем в новом оглавлении файлы со- храняют свои исходные имена. Первая команда переносит или переименовывает файлы, а вторая — копирует, сохраняя исход- ные экземпляры файлов неизменными. При переносе или копировании файлов в другое оглавление следует быть особенно внимательным и убедиться в том, что последним параметром действительно является оглавление, по- скольку команда mv также и уничтожает файлы. Например, команды mv х d mv yd mv z d переименуют файлы x, у и z в файлы d/x, d/y и d/z соответственно, если d — оглавление. В противном случае файл х переименовы- вается в d, затем файл у переименовывается в d, затирая при этом предыдущий файл. Наконец, файл z также будет переименован в d. 2.6. ОБОЛОЧКА Простейшие команды состоят из последовательности слов, раз- деленных пробелами. Первым словом является имя команды, которую нужно выполнить. Все остальные слова передаются вы- зываемой команде в качестве параметров. Например, команда 1s -1 выдает список имен файлов в текущем оглавлении. Параметр —I (от слова long — длинный) означает, что команда 1s должна на- печатать для каждого файла дату последнего обращения, размер и статус. Имена файлов в списке упорядочиваются по алфавиту. Для того чтобы выполнять команду, оболочка обычно создает новый процесс и ждет его завершения. Оба этих действия явля- ются примитивами операционной системы. Команду можно вы- полнять как фоновую и не ждать, пока она завершится. Для этого используется литера &, которая помещается непосредственно за командой. Например, print file &
28 Гл. 2. Начальные сведения запускает команду печати print с параметром file (имя файла) как фоновую. Литера & является металитерой, интерпретируе- мой оболочкой, и не передается команде print в качестве пара- метра. Для того чтобы дать возможность пользователю следить за ходом выполнения команды, оболочка после создания фонового процесса сообщает его номер. Список активных в данный момент процессов можно получить при помощи команды ps. Для каждого процесса в системе содержится набор дескрип- торов файлов с номерами 0, 1, ..., которые используются про- цессором и операционной системой во всех операциях ввода-вы- вода. Файл с дескриптором 0 называется стандартным файлом ввода (или просто стандартным вводом), а файл с дескриптором 1 — стандартным файлом вывода (стандартным выводом). При выполнении большинства команд результаты передаются в стан- дартный файл вывода, который первоначально (сразу после входа в систему) связан с терминалом. Имеется также стандартный файл диагностики, который обычно используется для вывода сообщений об ошибках. На время выполнения команды стандарт- ный файл вывода можно подменить (переназначить), например: Is —1 >файл Запись >файл интерпретируется оболочкой и не передается команде 1s как параметр. Если указанный файл не существует, оболочка создает его. В противном случае исходное содержимое этого файла заменится выводом команды 1s. Для дозаписи в конец файла используется обозначение >>, например: Is —1 >>файл Аналогично стандартным файлом ввода вместо терминала можно сделать обычный файл, например: wc <файл Команда wc печатает число литер, слов и строк в стандартном файле ввода. 2.6.1. Транспортеры и фильтры Стандартный вывод одной команды можно связать со стандарт- ным вводом другой команды при помощи транспортера — опе- ратора |. Например: 1s —1 I wc Две команды, соединенные таким образом, образуют конвейер, действие которого эквивалентно: Is —1 >файл wc <файл
2.6. Оболочка 29 за исключением того, что в этом случае не используется проме- жуточный файл. Вместо этого два процесса связываются транспортером, который создается путем обращения к системе. Транспортеры позволяют передавать данные только в одном на- правлении. Синхронизация осуществляется путем приостановки выполнения команды wc, когда нечего читать, и приостановки выполнения команды 1s при переполнении транспортера. Синхро- низацию осуществляет операционная система, а не оболочка. Фильтр — это команда, которая читает входные данные, преобразует их некоторым образом и выводит результат. Один из таких фильтров, grep, выбирает из своего файла ввода строки, содержащие некоторую заданную цепочку <питер. Например, Is | grep old распечатает из вывода команды 1s только те строки, которые со- держат цепочку литер old (если таковые имеются). Другой полез- ный фильтр — это команда сортировки sort. Например, who | sort распечатает упорядоченный по алфавиту список пользователей, работающих в данный момент в системе. Конвейер может состо- ять более чем из двух команд. Например, Is | grep old | wc —1 напечатает количество файлов в текущем оглавлении, имена ко- торых содержат цепочку литер old. 2.6.2. Порождение имен файлов Во многих командах параметрами являются имена файлов. На- пример, команда Is —1 main.с распечатает информацию о файле main. с. Оболочка предоставляет средства для порождения списка имен файлов по заданному образцу. Например, команда 1s —1 *.с порождает список параметров, содержащий все имена файлов из текущего оглавления, оканчивающиеся на .с. Литера * явля- ется образцом, который сопоставляется с любой цепочкой, вклю- чая и пустую. В общем случае образцы в командах задаются при помощи следующих металитер: * Сопоставляется с любой цепочкой литер, включая и пустую. ? Сопоставляется с любой одиночной литерой.
30 Гл. 2. Начальные сведения [. . .] Сопоставляется с любой из литер, заключенных в скобки. Пара литер, разделенных знаком минус, задает диапазон литер. Например, образцу [a—z]* соответствуют все имена из текущего оглавления, начинающиеся с одной из букв от а до z, а образцу /usr/fred/epns/?* соответствуют все имена из оглавления /usr/fred/epns, состоящие хотя бы из одной литеры. Если не окажется ни одного имени файла, соответствующего заданному образцу, в качестве пара- метра передается сам этот образец. Этот механизм позволяет, во-первых, сократить число литер, набираемых при, вводе команды, и, во-вторых, подбирать имена по некоторому образцу. Он может также использоваться для поиска файлов. Например, echo /usr/fred/*/core ищет и выдает имена всех файлов core в подоглавлениях оглав- ления /usr/fred. (Команда echo — это стандартная команда, ко- торая выводит список своих параметров, разделенных пробела- ми.) Выполнение данной команды может потребовать значитель- ных затрат, так как это связано с просмотром всех подоглавлений оглавления /usr/fred. Существует одно исключение из общих правил для образцов. Литера . в начале имени файла должна указываться явно. По- этому команда echo * выдает только те имена файлов из текущего оглавления, которые не начинаются с точки, а команда echo .* распечатает все те имена файлов из текущего оглавления, ко- торые начинаются с точки. Это позволяет избежать случайного сопоставления с именами . и .., обозначающими соответственно текущее и родительское оглавления. (Команда 1s не выводит ин- формацию о файлах . и .. .) Следует быть осторожным при использовании команды rm с образцами. Легко можно уничтожить больше файлов, чем было задумано. Один из способов уменьшить вероятность ошибки — применить сначала команду echo с данным образцом, например echo tmp*
2.6. Оболочка 31 а потом уже команду rm: rm tmp# Будьте внимательны — не введите случайно пробел между tmp и *. 2.6.3. Отмена специальных значений литер Литеры, имеющие специальное значение для оболочки, такие, как <, >, *, ?, | и &, называются металитерами. Любая ли- тера, если ей предшествует \, теряет свое специальное значение, если таковое имелось. Сама литера\отбрасывается, поэтому echo \? напечатает только ?, а echo \\ напечатает одну литеру \. Литера «новая строка», которой предшествует \, игнорируется (иногда это называют скрытой новой строкой). "Таким образом, длинные цепочки могут продол- жаться в нескольких строках. Литера \ удобна для отмены специального значения одиноч- ных литер. Если же требуется отменить специальные значения нескольких литер, такой способ оказывается неудобным и может привести к ошибкам. Специальные значения для групп литер можно отменить, заключив их в апострофы (одинарные кавычки). Например, результатом выполнения команды echo '*?[' будет цепочка литер *?[ Заключенная в апострофы цепочка литер не может содержать апострофы внутри себя, но может содержать литеры «новая стро- ка», которые сохраняются. Этот способ самый простой, его ре- комендуется использовать при каждом удобном случае. Существует также третий способ отмены специальных значе- ний литер, заключающийся в использовании (двойных) кавычек, при этом подавляется интерпретация некоторых, но не всех мета- литер. Подробнее этот вопрос будет рассмотрен в разд. 4.2.4. Комментарии в командном языке начинаются с литер 4k и заканчиваются литерой новой строки.
32 Гл. 2. Начальные сведения 2.6.4. Приглашения Когда оболочка обслуживает терминал, то перед считыванием очередной команды она выдает приглашение (подсказку). По умолчанию, таким приглашением является литера $. Пригла- шение можно заменить на любую другую цепочку литер, на- пример так: PSl=yesdear В результате в качестве приглашения будет использоваться цепочка литер yesdear. Если введена литера новой строки, а для завершения команды требуется продолжение ввода, оболочка выдаст приглашение >. Иногда это может произойти из-за не- соответствия кавычек. Если вы не желаете продолжить ввод, нажмите клавишу прерывания. Прерывание возвратит оболочку к чтению следующей команды. Это приглашение также можно изменить, введя, например, PS2=more 2.6.5. Оболочка и вход в систему Сразу после входа в систему вызывается программа-оболочка, которая читает и выполняет команды, вводимые пользователем с терминала. Если регистрационное оглавление пользователя содержит файл с именем .profile (так называемый входной файл), то оболочка выполнит его перед тем, как начать считывать коман- ды с терминала. date calendar MAIL=/usr/spool/mail/srb HOME==/usr/srb PATH«.:./bin:/bin:/usr/bin:$HOME/bin TERM==,.. export MAIL HOME PATH TERM Рис. 2.2 Типичный входной файл .profile Входной файл, приведенный на рис. 2.2, содержит типичные установки переменных оболочки, описываемых ниже в разд. 4.1.4. Этот входной файл также печатает дату и напоминания о бли- жайших мероприятиях. Команда export описывается в разд. 4.2.1.
2.7, Наиболее употребительные команды 33 Если вы всегда пользуетесь терминалами одного и того же типа, может оказаться полезной установка во входном файле перемен- ной TERM. 2.6.6. Сводка команд Is Напечатать имена файлов из текущего оглавле- ния. Is > file Is | wc - Поместить результаты вывода команды Is в файл file. -1 Напечатать число файлов в текущем оглавлении. Is 1 grep old Напечатать имена файлов, содержащие це- Is 1 grep почку old. old | wc —1 cc pgm.c Напечатать число файлов, имена которых со- держат цепочку old. & Выполнить команду сс как фоновую. 2.7. НАИБОЛЕЕ УПОТРЕБИТЕЛЬНЫЕ КОМАНДЫ 2.7.1. Средства связи В системе UNIX приятно работать, в частности, потому, что она предоставляет пользователям простые и удобные средства обще- ния друг с другом и с системой. Команды, описанные в этом раз- деле, позволяют пользователям, работающим на' одной и той же или на разных машинах, посылать почту другим пользователям или общаться друг с другом. Команда mail посылает сообщения (письма) другим пользователям, в то время как команда write используется для связи с пользователем, работающим за другим терминалом, в интерактивном режиме. Команда mail Команда mail позволяет принимать сообщения и посылать сооб- щения другим пользователям системы. Эти сообщения хранятся в некотором файле до тех пор, пока адресат не прочитает их и не уничтожит. Если вас дожидается почта, то при входе в систему вы полу- чите сообщение об этом. Оболочка также даст вам знать о поступ- лении любой новой почты перед выдачей на терминал очередного приглашения (см. описание переменной $MAIL в оболочке). Различным версиям системы UNIX соответствуют различные версии команды mail. В целом они функционируют одинаково, однако между ними существуют различия в деталях. Описывае- 2 С. Баурн
34 Гл. 2. Начальные сведения мый здесь вариант команды mail распространялся с седьмой вер- сией системы UNIX, Команда mail напечатает на терминале первое письмо из текущей почты, снаб- див его «почтовым штемпелем». Затем будет выдано приглашение ?. Полученное письмо можно уничтожить, набрав литеру d, на- печатать его снова, набрав р, или напечатать следующее письмо из текущей почты, нажав клавишу возврата каретки. Чтобы сохранить письмо в некотором файле, следует воспользоваться запросом s имя-файла Если в запросе s не указано имя файла, письмо будет сохранено в файле mbox в регистрационном оглавлении пользователя. Со- храненные и уничтоженные письма удаляются из почтового файла при выходе из команды mail. Выход осуществляется при помощи запроса q. Чтобы выйти из команды mail без каких-либо изменений в почтовом файле, следует воспользоваться запро- сом х. Это полезно в случае, если некоторые письма были уничто- жены по ошибке. В процессе обработки корреспонденции нажатие клавиши del приводит к прекращению выполнения текущего действия (обыч- но печати письма); выдается приглашение для ввода следующего запроса. Почту можно послать одному или нескольким пользователям с помощью команды mail jhc ken текст письма которая отправит письмо указанным пользователям (jhc, ken)t дополнив его именем отправителя и почтовым штемпелем. Текст письма заканчивается признаком конца файла или литерой введенной на отдельной строке. Если в процессе составления пись- ма произойдет какое-либо прерывание, то недописанное письмо будет сохранено в файле dead, letter в регистрационном оглавле- нии пользователя, и произойдет выход из команды mail. Почтовые системы обеспечивают связь между пользователями в пределах .одной машины. Связь между пользователями, рабо- тающими на разных машинах, осуществляется через специальную сеть, использующую обычные телефонные каналы. Такая сеть была создана и все еще применяется для пересылки файлов между машинами. Каждая машина, входящая в сеть, имеет некоторое имя и список имен и телефонных номеров других машин. Почто- вая служба использует эту сеть для доставки межмашинной
2.7. Наиболее употребительные команды 35 почты. Почтовый адрес состоит из имени машины и регистрацион- ного имени пользователя, разделенных восклицательным знаком. Например, для того чтобы послать почту пользователю 11с, находящемуся на машине с именем research, достаточно ввести команду mail research! 11с с последующим вводом текста письма. Некоторые машины могут также выполнять функции транзитного узла по пересылке почты. Так, команда mail allegra!ucbvax!wnj отправит письмо пользователю ucbvaxlwnj через транзитную ма- шину allegra. Такой транзит следует использовать только с со- гласия администрации промежуточной машины. Команда uucp Команда uucp (копирование UNIX — UNIX) копирует файлы с одной машины на другую. Связь между машинами устанавли- вается через телефонную линию, а в некоторых случаях использу- ются прямые высокоскоростные каналы связи. Команда uuname напечатает список имен систем, непосредственно доступных из вашей системы. Такая простая сеть ЭВМ эксплуатируется посто- янно. С машины (allegra), используемой автором, можно вы- звать свыше 300 машин. Формат команды uucp аналогичен формату команды ср. Например, команда uucp file research!/usr/srb/ufile скопирует файл file с местной машины на машину research; на новой машине файл будет иметь имя ‘/usr/srb/ufile. В своем первоначальном виде команда uucp могла использо- ваться для пересылки с машины на машину любых файлов. Един- ственными средствами защиты были средства, предусмотренные в файловых системах каждой из ЭВМ. Предоставление возможности произвольно копировать файлы в некоторую ЭВМ или из нее, зная ее телефонный номер и исполь- зуя относительно простой протокол, привело в ряде систем к не- допустимо высокой степени риска (с точки зрения обеспечения сохранности данных). Поэтому команда uucp накладывает на пересылаемые файлы дополнительные ограничения помимо огра- ничений, накладываемых файловой системой. Во многих систе- мах существует только одно оглавление, используемое для копирования файлов между машинами,—/usr/spool/uucppublic. Как правило это оглавление доступно всем пользователям 2*
36 Гл. 2. Начальные сведения данной машины, и каждый пользователь может создать в нем подоглавление для личного пользования. Администратор системы может разрешить использовать в команде uucp и любые другие оглавления. Команда write Помимо почтовой службы имеется команда write, позволяющая пользователям устанавливать связь между терминалами и не- посредственно общаться друг с другом. Команда write bill проверит, работает ли пользователь bill в системе в данный мо- мент, и, если да, пошлет на его терминал сообщение вида Message from srb on tty20 at 13:36 (сообщение от пользователя srb с терминала 20, время 13:36). Если пользователь bill вошел в систему более чем с одного тер- минала, то команда write сообщит об этом и выберет для связи один из этих терминалов. По принятому протоколу взаимодей- ствия bill должен ответить вводом команды write srb которая пошлет аналогичное сообщение на терминал инициатора диалога. Пользователь srb теперь может послать первое сообще- ние, например Hi, are you ready to eat lunch (о) Здесь (о) означает прием (over). Диалог продолжается до тех пор, пока одна из сторон не решит его закончить. Ввод признака конца файла AD завершит диалог на одном терминале и выведет текст EOF на другом терминале. Иногда, когда терминал используется как печатающее уст- ройство, или при работе с экранным редактором (см. описание редактора vi) появление случайного сообщения не желательно. Некоторые пользователи попросту предпочитают, чтобы их не прерывали. Команда mesg п запрещает другим пользователям посылать сообщения на ваш терминал, команда mesg у отменяет этот запрет, а команда mesg без параметров выдает информацию о текущем состоянии терминала (у или п).
2.7. Наиболее употребительные команды 37 2.7.2. Системные справки Команда ps Список всех активных процессов в системе можно получить с по- мощью команды ps (process status — состояние процессов). Опции и формат вывода этой команды различны в различных версиях PID ТТ STAT TIME COMMAND 22932 00 1 0:03 ed ama.temp 22864 16 S 0:07 vi t2 22963 16 S 0:00 sh -I 22968 16 R 0:00 ps a 22967 40 S 0:00 sleep 15 27786 40 S 6:28 /bin/sh /usr/haw/bin/print Рис. 2.3 Результат работы команды ps системы UNIX. Параметр а требует вывода списка всех процессов в системе; если он не задан, то выдается только список процессов, запущенных данным пользователем. Типичный вывод резуль- тата работы команды ps а приводится на рис. 2.3. Поля имеют следующий смысл: PID Идентификатор (номер) процесса. ТТ Номер терминала. STAT Состояние процесса: R — процесс выполняется, S — процесс приостановлен, I — процесс оста- новлен более чем на 20 секунд. TIME Время процессора, затраченное на выполнение команды (в минутах и секундах). COMMAND Выполняемая команда (вместе с параметрами). Команда du Эта команда определяет размер дискового пространства, отве- денного под файлы из некоторого оглавления и (рекурсивно) под файлы из всех его подоглавлений. Результатом работы команды du является список, содержащий имена файлов (оглавлений) и количество блоков по 512 литер, занятых этими файлами (оглав- лениями). Команда du без параметров выдает список, относящий- ся к текущему оглавлению. Если параметры заданы, то выдается список для указанных файлов и оглавлений.
38 Гл. 2. Начальные сведения Команда df Файлы хранятся в файловой системе, которой соответствует некоторая область на диске. Команда df печатает количество свободных и занятых блоков в каждой файловой системе. Еди- ницами дискового пространства являются блоки; блок обычно имеет размер 512 или 1024 байт (в зависимости от системы). В не- которых системах имеется постоянный дефицит дискового про- странства. Эта команда полезна, если вы собираетесь создавать большие файлы и хотите убедиться в том, что в файловой систе- ме достаточно свободного пространства. 2.7.3. Управление процессами Команды nice, nohup и kill Процесс, выполняющий любую команду, всегда конкурирует со всеми другими активными процессами в системе за процессорное время. Команда nice сообщает системе, что некоторое задание не является срочным и может выполняться с более низким приори- тетом. Например, nice ср largefile newfile выполнит команду ср largefile newfile с более низким приоритетом. Это ускорит выполнение других, более срочных заданий. Последовательность действий при работе за терминалом часто состоит из ввода команд и ожидания завершения их выполнения, как, например, при работе с файлами или при редактировании. Если выполнение команды или группы команд занимает много времени, то удобнее запустить это задание как фоновое и про- должить работу за терминалом. Для этой цели в командном языке предусмотрен оператор &. По команде ср largefile newfile & оболочка запустит копирование, однако не станет дожидаться его завершения. Команду nice также можно употреблять с опе- ратором &, так что по команде nice ср largefile newfile & копирование будет выполняться как фоновое и с низким приори- тетом. Если вы собираетесь выйти из системы и покинуть свой тер- минал, не дожидаясь завершения выполнения фонового задания, следует воспользоваться командой nohup. Это предотвратит
2.7, Наиболее употребительные команды 39 преждевременное завершение задания при отключении терминала и разрыве связи. Команда nohup используется аналогично коман- де nice, так что типичным примером является nohup ср largefile newfile & Если выполняющийся процесс больше не нужен, его можно уничтожить командой kill. В команде kill требуется указать номер (идентификатор) процесса, например номер, выданный командой ps. Обычно команда kill используется для уничтожения фоновых процессов. Однако процессы, запущенные посредством команды nohup, не восприимчивы к простой команде kill. Такие процессы можно уничтожать командой kill —9 идентификатор-процесса Такая форма команды kill обычно не используется, поскольку она не позволяет уничтожаемым процессам выполнить завершающие действия. Команда at Команда at дает возможность пользователям включать в очередь задания, которые будут выполняться в определенный момент в будущем. В каждой организации действуют свои правила, регламентирующие использование вычислительных ресурсов. На очень загруженных машинах, например, может быть неприемле- мым выполнение более одного фонового задания. Команду at можно использовать для того, чтобы отложить выполнение не очень важного задания до того момента, когда машина не будет столь загруженной. Все команды, описанные до сих пор, начинали выполняться сразу после их ввода. Запланировать выполнение задания в бу- дущем можно, воспользовавшись командой at. Например, при помощи at 1600 fri runcmd делается заказ на выполнение задания (командного файла) runcmd в ближайшую пятницу (friday) в 4 часа дня. Команда runcmd выполнится точно так же, как если бы она была введена только что, за исключением того, что ей не будет доступен тер- минал для ввода и вывода. Стандартные файлы ввода, вывода и диагностики следует обеспечить каким-либо другим образом. В системе нет специальных средств для отмены заданий, вве- денных посредством команды at. Эти задания хранятся в оглав- лении /usr/spool/at и могут быть уничтожены своим владельцем при помощи команды rm. В некоторых системах команда at отсутствует, хотя, может быть, в них имеются другие эквивалентные средства.
40 Гл. 2. Начальные сведения 2.7.4. Некоторые другие команды Команда calendar Команда calendar представляет собой службу напоминания (календарную службу) для индивидуального использования. Для того чтобы воспользоваться этой услугой, создайте в своем ре- гистрационном оглавлении файл calendar, содержащий строки вида июнь 27 9 часов посещение зубного врача Каждый день система будет просматривать этот файл и посылать вам по почте те строки, в которых содержится сегодняшняя или завтрашняя дата. Файл calendar остается неизменным и превра- щается в дневник событий. Команда file Команда file по содержимому файлов, имена которых заданы ей в качестве параметров, определяет тип этих файлов. Она недо- статочно надежна и иногда путает командные процедуры с про- граммами на языке С. Чтобы познакомиться с типичным выводом команды file, попробуйте ввести, например, file /usr/lib/* Команда find Команда find просматривает иерархию оглавлений, т. е. оглав- ление и, рекурсивно, его подоглавления, в поисках файлов, об- ладающих заданным свойством. Проверяются также условия, накладываемые на имя, возраст, владельца и полномочия файла. При обнаружении подходящего файла может быть выполнена некоторая команда или распечатано имя этого файла. Команда find поможет в случае, если вы не можете вспомнить имя оглав- ления, в котором находится файл, но помните имя самого файла. Команда find . —name precious —print просмотрит дерево оглавлений, корнем которого является теку- щее оглавление, и, если файл с именем precious будет найден, выведет его составное имя относительно текущего оглавления. Команда find . —name precious —exec Is —1 {} \; аналогична предыдущей, но при обнаружении файла precious выполнит также команду Is —1... . Каждый раз при выполнении команды 1s ее параметр, имеющий вид {}, будет заменяться на
2.7. Наиболее употребительные команды 41 полное составное имя найденного файла. Команда, которая будет выполняться, заканчивается комбинацией литер \;. Литера \ от» меняет специальное значение символа ; в командном языке. В следующих примерах приводятся другие варианты использо- вания команды find: find / —user mark—b —print Ищет во всей файловой системе (т. е. начиная с корня) файлы, принадлежащие пользователю mark—b. find . —size 0 —print Распечатает имена пустых файлов в текущем оглавле- нии и его подоглавлениях. Команда grep Команда grep уже встречалась в ранее рассмотренных примерах. Она просматривает файлы из заданного списка и выбирает из них строки, содержащие некоторую цепочку литер. Если не задано ни одного файла, просматривается стандартный ввод. Найденные строки копируются в стандартный вывод. Например, команда grep Unix * распечатает строки из файлов текущего оглавления, содержащие цепочку литер Unix. Если просматривается несколько файлов, grep добавляет в начало каждой выводимой строки имя файла. Общий формат вызова команды grep имеет вид grep образец [файл ...] Образец напоминает образец, используемый в командном языке для порождения имен файлов. В простейшем случае образец — это явная цепочка литер. Описание общего вида образцов при- водится в разд. 8.1. Вот несколько из наиболее употребительных в команде grep опций: — h Отмена печати имени файла в начале каждой строки. — п Перед каждой строкой выводится ее номер в файле. — v Печатаются только строки, не содержащие заданную цепочку литер. Команда 1s Команда Is выдает информацию о файлах и оглавлениях. Команда Is без параметров выведет имена файлов текущего оглавления в алфавитном порядке. Имена . и .. обычно не печатаются.
42 Гл. 2. Начальные сведения —d В случае оглавления выдавать только имя оглавления, а не имена содержащихся в нем файлов. —g Выдавать имя группы вместо имени владельца. —1 Выдавать (длинный вариант): полномочия файла, число ссылок на файл, имя владельца, размер файла и время последней модификации. —г Изменить порядок выводимых строк на обратный. ‘ —t Упорядочивать по времени последней модификации, а не по имени файла. —и Использовать (для вывода или сортировки) время по- следнего доступа вместо времени последней модифика- ции. Команда od Файлы, содержащие непонятные литеры, можно распечатать с помощью команды «восьмеричного дампа» od. Например, команда od —b file выводит каждый байт файла file в виде целого восьмеричного числа. Вот некоторые другие опции команды od: —с Печать в коде ASCII. Для представления неграфических литер в качестве авторегистра используется литера \. —х Печатать по 16 бит в шестнадцатеричном виде. Файлы с более сложной структурой можно распечатать с по- мощью отладчика adb. Команда рг Листинги одного или нескольких файлов можно получить, вос- пользовавшись командой рг. Например, команда рг *.с распечатает все файлы из текущего оглавления, имена которых оканчиваются на .с. Выводимый текст разбивается на страницы по 66 строк. В начале каждой страницы помещается заголовок, содержащий имя файла, дату, время и номер страницы. Последняя страница дополняется пустыми строками до целой страницы. Вот наиболее часто используемые опции: —t Отменить вывод дополнительных (пустых и содержа- щих заголовок) строк в начале и в конце каждой страницы. —п Выполнять вывод в п колонок.
2.7. Наиболее употребительные команды 43 —h текст Использовать текст в качестве заголовка каждой страницы. —ш Слияние. Одновременная печать нескольких файлов. Каждый файл печатается в отдельной колонке. Команда stty Терминалы могут иметь совершенно различные характеристики. Параметры терминалов сообщаются системе посредством коман- ды stty. Команда stty без параметров выдает текущие установки наиболее важных параметров терминала, таких, как скорость обмена, контроль на четность или нечетность, литеры стирания и отмены. Если ваш терминал ведет себя странным образом, следует проверить соответствие его конфигурации и действующих опций команды stty. Организация ввода-вывода для терминалов обсуждается в гл. 6. Команда tar Файлы можно копировать на магнитную ленту и с магнитной ленты, используя команды cat или ср. Однако копирование от- дельных файлов таким образом не очень удобно. Команда tar (архив на ленте) копирует целое оглавление или дерево оглавле- ний обычно с ленты на диск или с диска на диск. Например, команда tar гс . считает все файлы и (рекурсивно) все подоглавления из текущего оглавления (.) и запишет их на ленту. Опция г задает чтение оглавления, а опция с создает новый архив на ленте. Лента — это специальный файл наподобие /dev/tty, имя которого зависит от системы. Имя этого специального файла можно не указывать, поскольку оно встроено в команду tar. Чтобы получить список имен файлов, хранящихся на ленте, используется команда tar t Опция t означает titles — заголовки. Файлы могут считываться с ленты и записываться на диск командой tar х Имена записанных на диск файлов совпадают с именами, выдан- ными командой tar t. Имена, задаваемые в команде tar, следует указывать относи- тельно текущего оглавления, для того'чтобы можно было счи-
44 Гл. 2. Начальные сведения тывать файлы в другое место файловой системы. Например, вместо tar rc /usr/srb лучше воспользоваться командами cd /usr/srb tar rc. 2.7.5. Сводка команд Следующие команды позволяют пользователю войти в систему и начать работу за терминалом. login mail man stty who Запрашивает регистрационное имя и пароль. Посылает и получает почту. Печатает разделы из руководства для пользователя. Устанавливает параметры терминала. Выдает список пользователей, работающих в сис- теме в данный момент. Были также рассмотрены следующие важные команды для рабо- ты с файлами. cat ср Is mkdir mv Конкатенация и печать файлов. Копирование файлов. Получение списка элементов оглавления. Создание оглавления. Перемещение или переименование файла или оглав- ления. rmdir rm pr Уничтожение оглавления. Уничтожение файла. Форматирование и печать файла.
ГЛАВА 3 РЕДАКТИРОВАНИЕ ФАЙЛОВ Редактор текстов дает возможность пользователю непосредствен- но с терминала создавать файлы, содержащие программы, тексты документов и т. п., а также вносить в них изменения. Редакторы являются интерактивными, что позволяет пользователю вести диалог с системой. Существует два общедоступных редактора: ed и vi. Из них ed более широко распространен и использует только базисные средства, имеющиеся на любом терминале. Другой редактор, vi, является экранным редактором и рассчитан на работу с дисплея- ми. В этой главе описываются характерные особенности обоих редакторов. 3.1. РЕДАКТОР ed Редактор ed вызывается командой ed На терминал не выдается никакого приглашения; редактор будет ждать ввода запроса на редактирование. Термин запрос исполь- зуется для того, чтобы отличить инструкции, задаваемые коман- дам (т. е. программам, вызываемым на уровне оболочки) от самих команд. Редактор ed хранит редактируемый текст в области памяти, называемой буфером, и выполняет запросы, позволяющие до- бавлять, исключать и изменять текст. Имена всех запросов со- стоят из одной буквы. За исключением особо указанных случаев, в одной вводимой строке не может быть более одного запроса. При любой ошибке ed напечатает символ ? и будет ждать ввода нового запроса.
46 Гл. 3. Редактирование файлов 3.1.1. Создание файлов Изначально буфер редактора пуст. Чтобы поместить в буфер информацию, используется запрос дозаписи текста a (append — добавление): а текст, который нужно поместить в буфер Пока вводится текст, редактор находится в режиме дозаписи. Для того чтобы закончить ввод текста и выйти из режима доза- писи, требуется в отдельной строке ввести точку (точка должна стоять в начале строки и за ней должен следовать возврат ка- ретки). Содержимое буфера записывается в файл draft. 1 при по- мощи запроса записи w draft. 1 Литера w (write — запись) отделяется от имени файла пробелом. В ответ редактор выдает количество литер, записанных в файл. После выполнения этого запроса содержимое буфера не меняется. Записав содержимое буфера в файл, можно выйти из редактора при помощи запроса выхода q (quit). К выходу из редактора приведет также ввод признака конца файла — литеры AD. Если содержимое буфера менялось и не было записано обратно в файл, редактор на запрос w или q ответит литерой ?. Обычно это един- ственное сообщение об ошибке, получаемое от редактора, и оно может сбить с толку начинающего пользователя. Приведем ти- пичные ошибки, возникающие при работе с ed: • Несуществующий запрос. • Ошибка в формате запроса. • Файл не существует, или его нельзя читать, или в него нельзя писать. Выше был описан способ создания файла, но ничего не было сказано о том, как редактировать содержимое уже существую- щего файла. Точно так же как запрос w draft, ch 1 записывает содержимое буфера в файл, запрос е draft, ch 1 считывает содержимое файла draft.ch 1 в буфер, уничтожая все, что было в буфере до этого. Вот один из способов вызова редактора ed:
3.1. Редактор ed 47 ed e имя-файла [Запросы редактирования, изменяющие содержимое буфера] w имя-файла Ч Тот же результат можно получить и другим способом: ed имя-файла [Запросы редактирования, изменяющие содержимое буфера] w q Если имя файла передается редактору ed как параметр вызова, то запрос е не нужен. Кроме того, нет необходимости помнить имя редактируемого файла для того, чтобы в дальнейшем ука- зать его в запросе w (как во втором примере). Редактор хранит имя редактируемого файла. Это имя переустанавливается при выполнении запросов е и w. Его можно вывести при помощи запроса f. 3.1.2. Редактирование строк текста Текст в буфере состоит из строк, и ряд запросов выполняет опе- рации над строками текста. Общий формат запросов имеет вид: начальная-строка, конечная-строка запрос Запрос применяется к каждой строке буфера от начальной-стро- ки до конечной-строки включительно. Строки можно адресовать по номерам, начиная с 1. Если начальная и конечная строки сов- падают, можно использовать сокращенную форму: конкретная-строка запрос В следующих примерах приведен ряд наиболее употребитель- ных запросов редактирования. 1,4р Распечатать строки с номерами от 1 до 4 включи- тельно. 1,4п Распечатать строки с 1 по 4 включительно, вна- чале каждой строки помещается ее номер. 10,15d Исключить строки с 10 по 15 включительно. 4,14w parti Записать строки с 4 по 14 включительно в файл с именем parti. Если начальная и конечная стро- ки не заданы, записывается весь буфер. 4г new. part Считать содержимое файла new. part и вставить в буфер непосредственно после строки 4; если но- мер строки не задан, содержимое файла записы- вается в конец буфера. 4,14т21 Перестановка строк. Строки с 4 по 14 изымаются
48 Гл. 3. Редактирование файлов со своего места и помещаются за строкой 21 (и перед строкой 22). И2 Поместить копию строки 1 за строкой 2. Команды г, m и t помещают текст после заданной строки. Последняя строка буфера адресуется при помощи литеры $. Так, запрос l,$d исключает из буфера все строки с первой по последнюю включительно. Этот запрос следует использовать только тогда, когда нужно очистить весь буфер. Аналогично запрос 1,$р распечатывает содержимое всего буфера. Текущая строка В каждый момент времени одна из строк буфера является те- кущей. На нее как бы показывает некоторый указатель. Ссыл- кой на текущую строку является точка. Текущей обычно яв- ляется последняя отредактированная строка. После выполнения запроса а это последняя введенная строка, а после выполнения запроса m — последняя из переставленных строк. Запрос d является исключением, так как последней редактировавшейся строки больше не существует; текущей становится строка, сле- дующая за исключенными. В сомнительных ситуациях текущую строку можно распечатать при помощи запроса р. Кроме того, номер текущей строки можно узнать, введя запрос .= . Чтобы сделать текущей некоторую строку без выполнения каких-либо редактирующих действий, наберите ее номер. Так, запрос 4 вы- водит строку 4 и присваивает указателю текущей строки (точке) значение 4. Вообще всегда, когда устанавливается новая текущая строка и требуется ввести новый запрос редактирования, эта текущая строка выводится. Адресация строк Строки в редакторе ed можно адресовать по их порядковым но- мерам. Для указания текущей строки можно использовать точку, а для указания последней строки текста — литеру $. Адреса можно образовывать также путем прибавления к одной из этих величин или вычитания из нее некоторого числа (прибавление к $ всегда вызовет ошибку). Адреса, начинающиеся с «+» или «—», берутся относительно текущей строки. Знаки + и — сами по себе используются для продвижения вперед или назад на одну строку. В приведенных ниже запросах редактирования иллюстри- руются типичные способы адресации строк. + Перейти к следующей строке и распечатать ее. — Вернуться назад на одну строку и распечатать ее.
3.1. Редактор ed 49 .,$d Исключить все строки до конца буфера, начиная с текущей. •-1..+ 1Р Распечатать предыдущую, текущую и следующую строки. .4-2,$—1р Распечатать все строки, начиная со строки с но- мером, превышающим номер текущей строки на 2, вплоть до предпоследней (включительно) стро- ки буфера. Во всех случаях значение указателя текущей строки (точки) изменяется. Добавление и исключение текста Общим средством добавления нового текста в буфер является запрос дозаписи. Запрос 10а текст, который нужно поместить между строками 10 и И поместит введенный текст в буфер непосредственно за строкой 10. Имеются два других похожих на а запроса. Можно заменять строки, используя запрос с, сочетающий в себе исключение и вставку текста. Например, запрос 4,27с новый текст заменяет данным текстом строки с 4-й по 27-ю включительно. Как и следовало ожидать, запрос 4с добавляемый текст заменит строку 4. В обоих случаях ввод текста завершается строкой, содержащей только точку. Запрос вставки i внешне похож на запрос а, но вставляет текст перед указанной строкой, а не за ней. Так, запрос 11 .се 2 Глава 1 .sp 2 Основные принципы .sp 2
50 Гл. 3. Редактирование, файлов добавляет новый текст в начало буфера. Эквивалентный резуль- тат достигается при помощи запроса 0а Если в запросах а, с, d, i и р номера строк не заданы, то, по умолчанию, используется номер текущей строки, так что запрос а добавляемый текст эквивалентен запросу .а добавляемый текст Кроме того, запрос d исключает текущую строку, а последующий запрос р печатает новую текущую строку, т. е. следующую стро- ку буфера. Эти запросы можно записать в одной строке в виде dp После выполнения каждого из запросов а, с, i и m текущей строкой становится последняя добавленная, замененная, встав- ленная или передвинутая строка соответственно. 3.1.3. Поиск по контексту Ссылаться на строки только по номерам неудобно, если под рукой нет листинга. К тому же простые вставки и удаления приводят к изменению нумерации строк. Удобнее при помощи редактора найти некоторую цепочку литер, проверить, что найдена именно нужная строка, а затем отредактировать ее и просмотреть снова. Например, запрос /Fotran/ будет искать в буфере следующую строку, содержащую цепочку литер, ограниченную косыми чертами. Найденная строка выво- дится, так что можно проверить, является ли она искомой. При поиске редактор последовательно просматривает строки текста, начиная со строки, следующей за текущей. Дойдя до конца бу- фера и не встретив подходящую строку, редактор продолжит поиск с начала буфера и будет искать до тех пор, пока не про- смотрит все содержимое буфера, дойдя опять до текущей строки. Если подходящей строки нет (возможно, из-за ошибки в запро- се), выводится литера ?.
3fl. Редактор ed 51 Задание образца для поиска (например, /Fotran/) является одним из способов адресации строки. Образцы поиска рассмат- риваемого здесь вида можно использовать точно так же, как выше использовались номера строк. Например, запрос /switch/,/case/d удаляет весь текст между ближайшей строкой, содержащей це- почку литер switch, и ближайшей строкой, содержащей цепочку case. Предполагается, что строка, содержащая case, следует за строкой, содержащей switch, или совпадает с ней. Дойдя до последней строки буфера и не встретив подходящую строку, ed продолжит поиск с первой строки. Поиск заканчива- ется при достижении текущей строки или когда встретится под- ходящая строка. Образец поиска можно использовать как часть адреса. На- пример, запрос /main/— 1 сделает текущей строку, предшествующую ближайшей строке, содержащей цепочку литер main. Использование точки с запятой Запрос /begin/, /end/d начинает поиск цепочки end с того же самого места, с которого начинался поиск цепочки begin, т. е. с текущей строки. Для того чтобы поиск цепочки end начался с того места, где встретилась цепочка begin, в качестве разделителя используется вместо за- пятой точка с запятой, как, например, в запросе /begin/;;/end/d Поиск в обратном направлении При поиске по контексту с использованием конструкции /.../ текст просматривается в прямом направлении (от начала к концу буфера). Конструкция ?...? позволяет вести поиск в об- ратном направлении. Обратный поиск также циклический: дойдя до первой строки, редактор перейдет на конец буфера и продол- жит поиск в обратном направлении, пока не просмотрит все содержимое буфера.
52 Гл. 3. Редактирование файлов 3.1.4. Контекстное редактирование Редактирование текста путем добавления и удаления целых строк занимает много времени и часто приводит к ошибкам. Преобра- зование текста в пределах строки известно под названием «кон- текстное редактирование». Часть строки можно изменить, воспользовавшись запросом замены s (substitute — заменить). В результате выполнения запроса s/Fotran/ Fortran/ первое вхождение цепочки литер Fotran в текущей строке за- менится на цепочку Fortran. Вот несколько примеров использо- вания запроса замены: s/Fotran// Заменить цепочку Fotran пустой цепоч- кой, т. е. удалить ее. s/Fotran/Fortran/p Заменить первое вхождение Fotran на Fortran и распечатать отредактированную строку для проверки. + Is/Fotran/Fortran/ Заменить первое вхождение Fotran на Fortran в текущей и в следующей за ней строках. Запрос замены имеет тот же общий формат, что и ранее встре- чавшиеся запросы: начальная-строка, конечная-строка s/старый текст/новый текст/ или конкретная-строка s/старый текст/новый текст/ Если номера строк опущены, подразумевается текущая строка. При поисках по контексту искомая цепочка часто использу- ется как образец в запросе замены. Так бывает, например, когда нужно исправить орфографические ошибки или опечатки. При вводе запроса поиска /Fotran/ с последующей заменой s/Fotran/Fortran/ возможно сокращение: /Fotran/ редактор отвечает найденной строкой s//Fortran/
3,1. Редактор ed 53 Редактор хранит последний образец, использовавшийся в за- просе поиска или замены. После выполнения запроса замены s запрос // снова ищет очередное вхождение того же самого образ- ца. Если встретилась не та строка, которая требуется, можно снова применить запрос // для поиска следующей строки по тому же самому образцу. Глобальная замена Простое применение запроса замены, например s/ Fotran/ Fortran/ заменит в текущей строке только первое вхождение Fotran на Fortran. Запрос s/ Fotran/ Fortran/g заменит все вхождения Fotran в текущей строке. Модификатор g означает глобальную замену во всей строке. Можно использовать также и модификатор р. Запрос s/ Fotran/ Fortran/gp выполнит глобальную замену и распечатает отредактированную строку. Запрос 1, $s/F ot ran/ Fortran/g заменит все вхождения цепочки Fotran в буфере на Fortran. При поиске по контексту иногда полезно указывать, что искомая цепочка находится в начале или в конце строки. В образцах начало и конец строки обозначаются литерами Л и $ соответ- ственно. Примеры: / А At/ Найти следующую строку, начинающуюся с At. /};$/ Найти следующую строку, оканчивающую- ся литерами};. /Л$/ Найти следующую пустую строку. /AUnix$/ Найти строку, содержащую только слово Unix. Такие же образцы можно использовать и в запросах замены (в роли заменяемой цепочки). s/A/The /р Вставить слово The в начало текущей стро- ки и распечатать ее. 4s/$/;/ Добавить литеру ; в конец четвертой строки. /А U NIX$/s//Unix/ Найти следующую строку, содержащую только слово UNIX и заменить эти четыре литеры на Unix.
54 Гл. 3. Редактирование файлов Запрос аннулирования Если запрос замены был выполнен ошибочно, запрос аннули- рования u (undo — аннулировать) вернет строку к ее первона- чальному виду. Этот запрос применим только к самой последней замене. 3.1.5. Сопоставление с образцом В образцах, используемых для поиска или замены, некоторые литеры имеют специальные значения. Эти литеры рассматри- ваются ниже. Литера точка Точка сопоставляется с любой литерой, так что по запросу /х.у/ будет найдена первая строка, содержащая цепочку литер типа х+у, х—-у, х=у. Литера звездочка Звездочка обозначает повторение литеры и используется, когда надо найти несколько (нуль или более) вхождений литеры. С об- разцом /.*;/ сопоставляется (в пределах одной строки) весь текст вплоть до последней точки с запятой включительно. Сопоставле- ние осуществляется с цепочкой максимально возможной длины, так что, если точка с запятой встречается в строке более одного раза, в сопоставленную цепочку войдут все ее вхождения. На- пример, запрос s/.*;// удаляет из строки все литеры вплоть до последней точки с за- пятой включительно. Запрос s/(.*)// удаляет первую левую скобку, последнюю правую скобку и все литеры между ними. Сопоставленные скобки не обязательно яв- ляются парными. Следует различать случаи употребления литеры * в редакторе и в командном языке оболочки. Использование литеры * в команд- ном языке аналогично использованию комбинации .* в редак- торе. Еще один пример. Запрос /а.*Ь/ будет искать строку, содержащую литеры а и b в этом порядке, разделенные, возможно, другими литерами.
3.1, Редактор ed 55 Квадратные скобки Квадратные скобки задают группы литер. Как и в командном языке, сопоставляется одна литера из группы. Задание группы литер иллюстрируется следующими примерами: Сопоставляется с любой из литер;, : или , . [а—z] Сопоставляется с любой строчной буквой от а до z. [А—Н] Сопоставляется с любой из прописных букв от А до Н включительно. [О—9] Сопоставляется с любой цифрой. [Л0—9] Литера л, если она стоит в группе первой, обозначает дополнение. Данный образец сопо- ставляется с любой литерой, кроме цифры. 1.\$*Л[] Сопоставляется с любой специальной литерой. В группах особо трактуются только литеры Л и ]. Чтобы включить в группу литеру ], ее сле- дует указать в списке первой. Чтобы вклю- чить в группу литеру Л, ее следует поместить в любую позицию списка, кроме первой. Следующие примеры поясняют использование образцов в за- просах замены: 1,$s/a[0—91*// Удалить все цифры в начале каждой l,$s/10—91*$// строки. Удалить все цифры в конце каждой s/[.\$*All// строки. Удалить из текущей строки первое вхож- S/, e\.g\..*/7 дение любой специальной литеры. Заменить текст, начиная с , e.g. до конца строки, на точку. Выделение части образца Часть образца, используемого в запросах поиска или замены, можно выделить, заключив ее в скобки \(и \). Например, запрос /\(.*\)ф\(.*\)/ будет искать строку, содержащую литеру табуляции (изобра- женную здесь как ф). Две подцепочки, заключенные в скобки \(...\), можно использовать в заменяющей цепочке запроса замены, например так: з//\2ф\17
56 Гл. 3. Редактирование файлов Обозначения \1 и \2 именуют первую и вторую выделенные подцепочки. В этом примере два поля меняются местами. Если в строке встречается более одной литеры табуляции, то сопо- ставляется самая правая. Отмена специальных значений литер Приведенные ниже литеры в образцах трактуются особым об- разом: Л . $ [ * \ / Чтобы использовать их на правах обычных литер, необходимо помещать перед ними литеру\. Так, /\./ ищет следующую строку, содержащую точку, тогда как /./ ищет следующую строку, содержащую какую-нибудь литеру (т. е. следующую непустую строку). Напомним, что то же самое соглашение используется для литер стирания и отмены 41= и @. При вводе с терминала эти литеры имеют специальное значение, и при работе с редактором их следует набирать как\4^ и \@ соответственно. Для поиска строки, содержащей /, также требуется исполь- зовать литеру \. Так, запрос S/\//*/p заменит в текущей строке / на * и затем распечатает измененную строку. 3.1.6. Глобальное редактирование Глобальный запрос g отбирает строки по образцу и для каждой найденной строки выполняет некоторый другой запрос редакти- рования. Глобальный запрос можно использовать, например, для исправления повторяющейся орфографической ошибки или вся- кий раз, когда требуется сделать массовые замены. Запрос g имеет формат g/образец/запросы Например, запрос g/A{/.—ip ищет все строки, начинающиеся с {, и печатает строки, пред- шествующие им. Как и в других запросах, диапазон действия запроса g можно ограничить. Так, l,20g/Achapter/s/c/C/
3.1. Редактор ed 57 выполнит запрос s/c/С/ для всех тех строк среди первых двадцати, которые начинаются со слова chapter. Когда нужно выполнить несколько запросов, перед каждой литерой «новая строка» сле- дует поместить литеру \. Например, запрос g/Achapter/s/c/C/\ лП1\ ищет строки, начинающиеся со слова chapter, заменяет в них слово chapter на Chapter и вставляет перед ними строку, содержа- щую текст .till. Все строки, кроме последней, должны закан- чиваться литерой\. Глобальный запрос не может содержать другой глобальный запрос. Запрос v аналогичен запросу g, за исключением того, что он отбирает строки, которые не сопоставляются с заданным образ- цом. Например, запрос v/A\./d исключает все строки, не начинающиеся с точки. 3.1.7. Другие возможности Прерывание и зависание При прерывании, вызванном нажатием клавиши del, редак- тор прекращает выполнение текущего действия и переходит к чтению следующего запроса. Этим можно воспользоваться, на- пример, для прерывания потока выдач от запроса печати вида g/-/P Если в процессе редактирования файла произошло зависание терминала, содержимое буфера сохраняется в файле с именем ed.hup в текущем оглавлении. Для того чтобы сравнить содержи- мое файла ed.hup с исходным содержимым файла, редактировав- шегося в момент зависания, можно воспользоваться командой diff. Восстановить файл из частично отредактированной его версии ed.hup можно командой ср или mv. Ограничители Поиск по контексту задается при помощи конструкции /.../. Литера / часто используется как ограничитель цепочек в запросе замены. Однако в качестве ограничителя в запросе замены можно использовать и любую другую литеру (например, двоеточие)
58 Гл. 3. Редактирование файлов при условии, что все ее вхождения в данный запрос согласуются с форматом команды. Например: s: Fotran: Fortran: Если литеру-ограничитель требуется использовать внутри об- разца, перед ней следует поместить литеру \. Выполнение команды Чтобы выполнить команду, не выходя из редактора, можно вос- пользоваться запросом !, например: .’mail honey позвони мне по номеру х7419 В этом примере пользователю honey посылается сообщение и осуществляется возврат в редактор. Ни буфер, ни редактируемый файл не изменяются. Сокращения В выражениях, задающих адреса строк, допускается ряд со- кращений. • Знак + может быть опущен. К примеру, выражения .+4 и .4 имеют одинаковый смысл. Другим сокращением для .4-4 является 4-4. • Знак — сам по себе означает сдвиг назад на одну строку. Выражение----------означает сдвиг назад на три строки и эквивалентно выражению .—3. • Выражение /. . ./— эквивалентно выражению /. . 1. Использование амперсанда В запросе замены s/abc/.../ литеру & можно использовать в заменяющем тексте для обозна- чения цепочки литер, сопоставленной с образцом. Например, запрос s/x4-y/(&)/ заключает выражение х4-у в круглые скобки.
3.1. Редактор ed 59 Неграфические литеры Запрос I аналогичен запросу р, за исключением того, что он за- меняет при выводе неграфические литеры (такие, как табуляция, шаг назад) комбинациями графических литер, как это показано ниже. (Представление неграфических литер различно в разных версиях редактора ed.) табуляция возврат на шаг обратная косая черта звонок перевод формата Если последними литерами в строке являются пробелы, за ними выводится комбинация \п: ...\п Объединение и разбиение строк Рассмотрим для примера строку текста ...abc... Чтобы вставить литеру новой строки между а и Ь, можно восполь- зоваться запросом замены, в котором для отмены специального значения этой литеры перед ней помещается \. Например, запрос s/ab/a\ Ь/ вставляет литеру новой строки между а и Ь. Один запрос занимает две строки. И наоборот, две строки, например if х==у && y==z можно объединить в одну. Если текущей является первая из этих двух строк, то запрос j объединит обе строки в одну и сделает ее текущей. В общем случае можно объединить целую группу смежных строк. Например, запрос 1,4jp объединяет в одну строку строки с 1-й по 4-ю включительно и печатает получившуюся строку. Маркировка строк Запрос номер-строки кх
60 Гл. 3. Редактирование файлов помечает указанную строку (по умолчанию — текущую) меткой х. В качестве меток могут использоваться любые строчные (латин- ские) буквы. На помеченные строки можно ссылаться при по- мощи конструкции, состоящей из одиночной кавычки и метки: 'х Запросы Е, Q и W Запросы е и q выведут литеру ?, если результаты редактирования не были записаны в какой-нибудь файл. Запрашиваемое действие в этом случае не выполняется. Защита отключается, если исполь- зуются команды Е и Q. Эти команды следует применять осторож- но, так как при небрежном их использовании можно потерять результаты целого сеанса редактирования. Запрос w записывает содержимое буфера в файл, полностью исключая прежнее содержимое этого файла. Для того чтобы до- писать информацию в конец файла, не уничтожая его старое со- держимое, можно воспользоваться запросом W. 3.2. РЕДАКТОР vi Освоив редактор ed, пользователи вскоре осознают и свойствен- ную ему ограниченность. Этот редактор был разработан в те времена, когда использовались медленные механические терми- налы, такие, как телетайпы моделей 33 и 37 фирмы Teletype. Экраны современных дисплеев вмещают по крайней мере 24 стро- ки по 80 литер каждая. На работу с такими устройствами и рас- считан редактор vi. Наилучший эффект достигается, когда терми- налы работают со скоростью 9600 бод, но имеет смысл исполь- зовать vi и при скорости 1200 бод. При меньших скоростях лучше использовать редактор ed. Редактору vi необходимо знать тип используемого терминала. Тип терминала задается установкой переменной среды TERM. На командном языке оболочки это записывается, например, так: TERM=2621 export TERM Значение, присваиваемое переменной TERM, зависит от типа терминала (в данном примере это устройство 2621 фирмы Hewlett Packard). Если имя типа терминала неизвестно, можно присвоить пере- менной TERM значение dumb. В этом случае редактор vi будет работать с любым терминалом, но экранные средства, которыми как раз привлекателен редактор vi, использоваться не будут. По команде vi pgm.c
3.2. Редактор vi 61 на экране появится несколько первых строк файла pgm.c, после чего можно вводить запросы редактирования. Появление на эк- ране странных литер свидетельствует, по всей видимости, о том, что тип терминала (значение переменной TERM) был установлен неправильно, или же переменная TERM не экспортирована из оболочки. Запрос :q, завершаемый возвратом каретки, осуществ- ляет выход из редактора. Запрос :q! соответствует запросу Q редактора ed: выход из редактора произойдет, даже если теку- щее содержимое буфера не было записано в файл. В отличие от редактора ed запросы редактора vi состоят из одной или более литер, причем некоторые (но не все) запросы должны оканчиваться литерой возврата каретки или esc. Если тип терминала установлен должным образом, сразу ста- новятся заметны отличия редактора vi от редактора ed. Некото- рые запросы исполняются немедленно и не выводятся на экран. Другие запросы появляются в нижней части экрана. Выводятся содержательные сообщения об ошибках, а не знак ?, как в ре- дакторе ed. Эти сообщения появляются также в нижней части экрана. 3.2.1. Управление окном Редактор vi устроен таким образом, что текст, который вы ви- дите на экране, представляет собой содержимое части буфера. Экран играет роль окна, через которое можно видеть содержимое буфера. Для продвижения окна вперед по буферу используются следующие запросы: AF продвигает окно на целую страницу (т. е. размер окна), AD продвигает окно на половину страницы. Обозначение ЛЕбыло введено в гл. 2 и означает управляющее F. Для продвижения по буферу в обратном направлении исполь- зуются запросы: ЛВ продвигает окно на целую страницу, AU продвигает окно на половину страницы. При достижении конца буфера вместо недостающих строк выво- дится литера ~. Теперь обратите внимание на курсор. В некоторых термина- лах он мерцает, в других — высвечивается в негативном изобра- жении. Курсор отмечает текущую позицию внутри буфера. 3.2.2. Управление курсором В процессе редактирования можно установить курсор в требуе- мую позицию на экране. Строка, содержащая курсор, является текущей. Положение курсора используется также в запросах
62 Гл. 3. Редактирование файлов редактирования. Управление курсором осуществляется при по- мощи следующих запросов: пробел Сдвиг курсора вправо на одну позицию, возврат на шаг Сдвиг курсора влево на одну позицию, возврат каретки Установить курсор на начало следующей строки. + Действует как «новая строка». — Установить курсор на начало предыдущей строки. $ Установить курсор в конец текущей строки. Имеется и ряд других запросов для перемещения курсора. При необходимости эти запросы выполняют и продвижение окна. За- прос /образец/ осуществляет поиск по образцу в прямом направлении и уста- навливает курсор на следующее встретившееся в буфере вхожде- ние этого образца. Как и в редакторе ed, для поиска в обратном направлении используется конструкция ?...? . Для того чтобы установить курсор на произвольную строку буфера, можно воспользоваться запросом G. Этот запрос, состоя- щий из номера строки и буквы G, установит курсор на строку буфера с заданным номером. По умолчанию, подразумевается последняя строка буфера, поэтому команда G установит курсор на конец буфера. Можно перемещать курсор по строке вперед или назад сразу на целое слово. Если при этом в текущей строке слов не оста- лось, курсор перемещается соответственно на следующую или предыдущую строку. w Установить курсор на начало следующего слова, b Установить курсор на начало предыдущего слова, е Установить курсор на конец текущего слова. В этих запросах словом считается последовательность из букв, цифр и знаков подчеркивания. В вариантах W, В и Е этих за- просов (т. е. когда имя команды задается прописной буквой) словом считается любая последовательность литер, не содержа- щая пробелов и табуляций. Это позволяет перемещать курсор че- рез знаки препинания и другие не буквенно-цифровые литеры. Перед всеми этими запросами можно задать коэффициент кратно- сти. Например, по запросу 3w курсор продвинется вперед на 3 слова.
3.2. Редактор vi 63 3.2.3. Вставки и удаления Можно вставлять или удалять текст в том месте, где находится курсор. Чтобы удалить одну литеру, наберите х, а для удаления следующих трех литер наберите Зх. Вставка текста осуществляет- ся при помощи запросов i и а. Запрос вставки I (insert — вста- вить), например, записывается в виде i... esc где многоточие означает вводимый текст, a esc — клавишу escape (авторегистр 2) на терминале. Текст вставляется непосредственно перед курсором и может содержать произвольное количество строк. Вставка текста продолжается до тех пор, пока не будет нажата клавиша esc. Запрос а начинает вставку текста с позиции за (а не перед) курсором и имеет вид a...esc Прежде чем продолжить описание запросов редактора vi, стоит заметить, что в vi можно использовать любой запрос редактора ed. Для этого надо ввести двоеточие и за ним — запрос редактора ed. Например, для выхода из редактора vi наберите запрос :q, а для записи содержимого буфера назад в файл и выхо- да — запрос :wq. Если в буфер были внесены изменения, вы не сможете выйти из редактора при помощи запроса :q, пока не запишите содержимое буфера в файл. Если у вас нет права записи в файл, редактор выдаст сообще- ние об этом, и запись выполнена не будет. 3.2.4. Редактирование строк Для вставок текста внутри строки можно использовать запросы i и а. Добавление новых строк удобнее выполнять с помощью запроса о: о esc который вставляет текст ... строка за строкой после текущей строки. Запрос О выполняет аналогичные действия, но вставляет текст перед текущей строкой. Имеются разнообразные запросы, позволяющие удалять текст из редактируемого документа: х Исключить текущую литеру. dd Исключить текущую строку. dw Исключить текущее слово.
64 Гл. 3. Редактирование файлов D Исключить остаток текущей строки. гс Заменить текущую литеру на литеру с. Перед большинством команд можно задавать коэффициент крат- ности. Запрос 3dd исключит 3 строки, начиная с текущей. 3.2.5. Пересылка текста Переслать текст из одного места в другое можно, сначала удалив его, а затем поместив в новое место при помощи запроса р (put — поместить). Запрос р помещает исключенный текст в место, ука- зываемое курсором. Если были исключены целые строки, то они вставляются вслед за текущей строкой; если же было исключено несколько литер в пределах строки, то эти литеры вставляются вслед за текущей литерой. Вот пример типичной последователь- ности действий: /main/ Найти цепочку main. 4dd Исключить 4 строки. 3+ Продвинуться вперед на 3 строки. р Поместить 4 исключенные строки вслед за новой текущей строкой. Если несколько литер были исключены запросом х, запрос р поместит их за текущей литерой; поэтому, например, запрос хр поменяет местами текущую и следующую за ней литеры. Исклю- ченный текст можно поместить перед текущей строкой или перед текущей литерой при помощи- запроса Р. Если при редактировании допущена ошибка, ее можно ис- править при помощи запроса и, который аннулирует последнее сделанное в буфере изменение. Аннулировать все изменения, сделанные в текущей строке, можно, воспользовавшись запро- сом U. Этот запрос, однако, не сработает, если вы перейдете на другую строку, а потом вернетесь назад, даже если при этом вы не выполните никаких редактирующих действий. Исключенный текст можно восстановить при помощи запроса р или Р. Можно восстановить до 9 фрагментов текста, исключен- ных последними. Например, запрос "5р возвратит в буфер текст, удаленный пятым по счету запросом исключения (счет ведется от последнего запроса). Запрос . повторяет последнее редактирующее действие, по- этому dd . . . исключит 4 строки. Для копирования строк можно воспользоваться запросом Y* В примере Yp
3.2. Редактор vi запрос Y скопирует текущую строку в специальный скрытый буфер, а запрос р поместит эту копию вслед за текущей строкой. 3.2.6. Сводка запросов Подведем итог. На первое время для работы с редактором vi достаточно знать перечисленные ниже запросы. Эти запросы позволяют вставлять и исключать текст, записывать результаты редактирования в файл и выходить из редактора. Ими следует овладеть до чтения следующего раздела. AD Продвинуть-бкно вперед. Л11 Продвинуть окно назад. AF Продвинуться вперед на страницу. АВ Продвинуться назад на страницу. возврат каретки Переместить курсор вниз. — Переместить курсор вверх. пробел Сдвинуть курсор вправо. возврат на шаг Сдвинуть курсор влево. dd Исключить текущую строку. i Вставить текст перед текущей литерой. о Вставить текст вслед за текущей строкой. р Вставить исключенный или скопированный текст. х Исключить текущую литеру. Y Скопировать группу строк в скрытый буфер. :w file Записать отредактированный текст в файл file. :q Выход из редактора. Команда :q! осущест- вляет выход, не проверяя, сохранен ли редактировавшийся текст. del Прервать выполнение текущей команды. 3.2.7. Дополнительные возможности Изучить описанные выше запросы можно за несколько сеансов работы за терминалом. В данном разделе описываются средства, полезные при частом использовании редактора. Управление курсором и окном Н Установить курсор на первую строку экрана. L Установить курсор на последнюю строку экрана. М Установить курсор на середину экрана. hjkl Сдвинуть, курсор влево, вниз, вверх или вправо соответственно. wbe Установить курсор на следующее слово, на преды- дущее слово или на конец текущего слова. 3 С. Баурн
66 Гл. 3. Редактирование файлов /.../ ?...? Z. Поиск по образцу ... в прямом направлении. Поиск по образцу ... в обратном направлении. Сдвинуть изображение так, чтобы текущая строка оказалась в центре экрана. z вк Сдвинуть изображение так, чтобы текущая строка стала первой строкой экрана. ВК означает возврат каретки. Z п. Использовать окно из п строк. Окно размещается % в центре экрана. Установить курсор на следующую или предыдущую парную скобку ( , ), { или }. ЛЕ Сдвинуть изображение на экране на одну строку вперед. AL лу Восстановить изображение на экране. Сдвинуть изображение на экране на одну строку назад. О Установить курсор в начало строки. Запросы редактирования А... Вставить текст в конец текущей строки (вставляе- мый текст оканчивается авторегистром esc). С... Заменить остаток строки (заменяющий текст окан- чивается авторегистром esc). D Исключить остаток строки. I... Вставить текст в начало текущей строки (вставляе- мый текст оканчивается авторегистром esc). J Объединить текущую и следующую строки. X Удалить литеру перед курсором. cw... Заменить текущее слово (заменяющий текст окан- чивается авторегистром esc). гх Заменить текущую литеру на литеру х. ~ Изменить регистр текущей литеры. Прописная бук- ва заменяется строчной, строчная — прописной. & Повторить последний запрос замены :s. Использование запросов редактора ed В редакторе vi можно использовать запросы редактора ed, пред- варяя их двоеточием. Наиболее часто используются следующие запросы: :sh Вызвать программу-оболочку. :!cmd Выполнить команду cmd и вернуться в vi. :r file Считать файл file. :s... Заменить одну цепочку литер на другую. _ :g... Глобальный поиск по цепочке.
3.2. Редактор vi 67 Объекты В некоторых запросах параметром является объект. В запросе d, например, параметр-объект указывает, что нужно удалить. Если параметром является буква, совпадающая с именем запроса (например, dd), то запрос применяется к текущей строке. Выше уже встречались спецификации объектов. Например, в командах dw и cw буква w обозначает слово. Спецификация объектов происходит следующим образом: с Одна литера. w Следующее буквенно-цифровое слово. W Следующее слово, составленное из литер, отличных от пробела и табуляции. Н Верхняя первая строка в экране. ЗН означает тре- тью сверху строку в экране. L Последняя строка в экране. 3L означает третью снизу строку в экране. I ... / Следующая строка, содержащая образец ... . ) Конец текущего предложения. Предложение за- канчивается пустой строкой или одной из литер ., !, ?, за которой следует новая строка или два пробела. ( Начало текущего предложения. } Конец текущего абзаца. Абзац заканчивается пус- той строкой или одним из запросов форматора nroff: .bp, .IP, .LP, .PP, .QP, .LI, .P. { Начало текущего абзаца. .]] Конец текущего раздела. Раздел заканчивается одной из макрокоманд .NH, . SH, .HU или .Н фор- матора nroff. [[ Начало текущего раздела. Эти объекты используются как параметры в следующих командах: сх... Заменить текст вплоть до х включительно новым текстом, заканчивающимся авторегистром esc. dx Исключить текст вплоть до х включительно. ух Скопировать объект х для последующего исполь- зования в команде р или Р. >х Сделать отступ из 8 пробелов во всех строках вплоть до строки, содержащей х, включительно. <Zx Устранить отступ из 8 пробелов во всех строках вплоть до строки, содержащей х, включительно. lx cmd Заданный объект х передается команде cmd в ка- честве стандартного ввода. Команда выполняется, и объект замещается ее стандартным выводом. з*
68 Гл. 3. Редактирование файлов Например, по команде ! Idate текущая строка заменяется выводом команды date. Команда ! }sort вызовет утилиту сортировки sort, передав ей в качестве стандарт- ного ввода очередной абзац текста, и заменит этот абзац отсор- тированным выводом. Коэффициенты Число, предшествующее команде, является коэффициентом крат- ности выполнения команды, за исключением следующих случаев: новый размер окна [[ ]] : / ? величина, на которую продвигается окно AD AU номер строки или столбца z G | Редактирование совокупности файлов В vi имеется возможность редактировать совокупность файлов. Когда vi вызывается командой vi файл ... то сначала в буфер считывается первый файл из списка. После того как он будет отредактирован, по команде :п в буфер считы- вается следующий файл. Если содержимое буфера не было сохра- нено, выводится сообщение об ошибке, и команда не выполняет- ся. Сообщение об ошибке выводится также и в ответ на запрос выхода :q — команда не выполняется до тех пор, пока не будут отредактированы все файлы из заданного списка. При помощи команды :set (см. ниже) можно, однако, установить режим autowrite — автоматической записи буфера в файл. Нейтрализация сбоев Под сбоем системы понимается аппаратная или программная ошибка, повлекшая за собой перевызов системы. Хотя это слу- чается и не часто, редактор vi защищает пользователя от потери сеанса редактирования, сохраняя копию буфера в некотором файле. Копия буфера сохраняется и в случае, если в процессе редактирования произошло зависание терминала. В любом случае редактор пытается оставить для вас почтовое сообщение, содержащее имя редактировавшегося файла и описание способа его восстановления. Файл можно восстановить, задав при вызове
3.2. Редактор vi 69 редактора опцию —г. Если, например, в момент сбоя редакти- ровался файл с именем precious, то по команде vi —г precious считается сохраненная копия буфера, и редактирование возоб- новится с прерванного места. В зависимости от того, в какой мо- мент произошел сбой, возможна потеря операции редактирова- ния, выполнявшейся последней. Установка внутренних переменных Редактор vi имеет ряд внутренних переменных, определяющих различные режимы его работы. Текущие значения этих перемен- ных выводятся командой :set all Каждая переменная имеет имя и устанавливается при помощи одной из команд: :set имя-переменной :set имя-переменной = значение Соответствующий режим отменяется командой :set по имя-переменной Ниже приводится список имен внутренних переменных. В скоб- ках даются допустимые сокращения. autoindent(ai) Делать автоматические отступы в тексте про- граммы. autowrite(aw) Автоматическая запись буфера в файл перед выполнением команд :п и !. ignorecase(ic) При выполнении поиска не различаются list number(nu) прописные и строчные буквы. Выдавать литеры табуляции в виде Л1. Выдавать строки текста вместе с их номерами. paragraphs(para) Список имен макрокоманд форматора nroff, redraw(re) задающих начало абзаца (для запросов } и {). Начальное значение — IPLPPPQPbpP LI. Моделирование интеллектуального термина- ТТЯ sections(sect) l/ld • Список имен макрокоманд, задающих нача- ло раздела (для запросов [[ и ]]). Начальное значение— NHSHH HU. term Имя типа используемого терминала. Эти режимы можно также устанавливать автоматически, исполь- зуя переменную EXINIT программы-оболочки. Например, вы- полнение команд
70 Гл. 3. Редактирование файлов EXINIT-'set ai aw' export EX I NIT приводит к автоматической установке режимов автоотступа и автозаписи при входе в редактор. Определение новых запросов В редакторе vi предусмотрены простые макросредства, позво- ляющие заменять некоторую последовательность запросов одной литерой. Например, после выполнения запроса :тар = :. = луВК ввод литеры = становится эквивалентным вводу запроса . Литера AV отменяет специальное значение литеры возврат ка- ретки (ВК), завершающей запрос . В приведенном примере требуются две литеры ВК*. одна входит в определение, а вторая завершает собственно запрос :тар. Другой пример. После выполнения :тар ; easAVesc запрос ; будет добавлять букву s в конец текущего слова. Ли- тера AV снова используется для отмены специального значения, на этот раз авторегистра esc. Ниже перечислены литеры, с которыми в редакторе vi не связано никаких действий. Эти литеры являются кандидатами на роль имен новых команд, определяемых посредством :тпар i KV.gqvt;_*«
ГЛАВА 4 ОБОЛОЧКА Оболочка (shell) является одновременно и языком программиро- вания, и командным языком. Оболочку часто называют также интерпретатором команд системы UNIX. Существует определен- ное сходство между оболочкой системы UNIX и интерпретато- рами команд в системах Cambridge Multiple Access System и CTSS: форма записи простейших команд и конструкции для передачи и подстановки параметров в командных языках всех этих систем аналогичны. Как командный язык оболочка обеспечивает интерфейс поль- зователей с операционной системой UNIX. Оболочка выполняет команды, поступающие либо с терминала, либо из файла. Поль- зователь может строить свои собственные команды, создавая командные файлы (т. е. файлы, состоящие из команд). Такие вновь созданные команды могут иметь параметры и обладают тем же статусом, что и «системные» команды, содержащиеся в оглав- лениях /bin и /usr/bin. Таким образом можно построить новую операционную среду, отвечающую потребностям отдельного поль- зователя или группы пользователей. При разработке оболочки, поэтому, учитывалась возможность ее использования как в интерактивном, так и в неинтерактивном режимах. За исключением нескольких несущественных деталей, функционирование оболочки не зависит от типа источника ввода. Как язык программирования оболочка предоставляет пере- менные, значениями которых являются цепочки литер, и управ- ляющие конструкции, в том числе ветвление и итерацию. Язык ориентирован на то, чтобы им было удобно пользоваться при работе за терминалом, поэтому цепочки литер в нем обычно не заключаются в кавычки. В качестве цепочки литер может исполь- зоваться вывод любой команды. Это позволяет использовать команды для выполнения арифметических операций и других действий, не предусмотренных в оболочке. Команды оболочки подобны вызовам функций в языках про- граммирования типа С. Однако в форме записи имеются два
72 Гл. 4. Оболочка отличия. Во-первых, хотя параметрами команд являются произ- вольные цепочки литер, в большинстве случаев не требуется за- ключать их в кавычки. Во-вторых, список параметров не заклю- чается в скобки, и параметры не отделяются друг от друга запя- тыми. Тенденцией развития командных языков является отказ от сложного синтаксиса выражений, характерного для языков программирования типа С. Командный язык предназначен в пер- вую очередь для того, чтобы вводить команды; важно, чтобы он был свободен от избыточных литер. 4.1. КОМАНДНЫЕ ПРОЦЕДУРЫ В гл. 2 было показано использование оболочки в интерактивном режиме. В этой главе оболочка рассматривается как язык про- граммирования, и теперь мы опишем, как создавать новые команды. Оболочку можно использовать для чтения и выполнения команд, содержащихся в некотором файле. Например, команда sh файл [ параметр ... ] вызывает программу-оболочку для чтения команд из указанного файла. Такой файл называется командным файлом или командной процедурой. При вызове могут быть заданы параметры. В команд- ном файле на них можно ссылаться, используя позиционные па- раметры $1, $2,... . Например, если файл wg содержит команду who I grep $1 то вызов процедуры sh wg fred эквивалентен выполнению команды who | grep fred Файлы в системе UNIX имеют три независимых атрибута: чтения, записи и выполнения. Чтобы сделать файл выполнимым, можно воспользоваться командой chmod (подробные сведения об этой команде приводятся в разд. 6.3.2). Например, команда chmod +х wg сделает файл wg выполнимым. После этого команда wg fred будет эквивалентна команде sh wg fred
4.1. Командные процедуры 73 Таким образом обеспечивается взаимозаменяемость командных процедур и программ. В любом случае для выполнения команды создается новый процесс. Наряду с именами позиционных параметров можно исполь- зовать и количество позиционных параметров в вызове — $4Н- Имя выполняемого файла доступно как $0. Специальный параметр $* языка-оболочки используется для подстановки всех позиционных параметров, кроме $0. Обычно $* используется при задании некоторых стандартных парамет- ров. Так, в примере nroff —Т450 —ms $* к уже заданным просто добавляются некоторые другие параметры. Командные процедуры можно использовать для перестраи- вания операционной среды с учетом привычек и потребностей группы пользователей или отдельного пользователя. Поскольку такие процедуры представляют собой текстовые файлы и не тре- буют компиляции, их создание и сопровождение не вызывает никаких затруднений. 4.1.1. Управляющая конструкция for Командная процедура часто представляет собой цикл по пара- метрам $1, $2, ... . Команды цикла выполняются один раз для каждого из параметров. Примером такой процедуры является tel, которая просматривает телефонный справочник в файле /usr/lib/telnos, содержащем строки вида fred mhO 123 bert mh0789 Процедура tel имеет вид for i do grep $i /usr/lib/telnos done Команда tel fred напечатает строки из файла /usr/lib/telnos, содержащие слово fred. А по команде tel fred bert будут распечатаны сначала строки, содержащие слово fred, а затем строки, содержащие слово bert.
74 Гл. 4. Оболочка Цикл for распознается оболочкой и имеет следующий общий вид: for имя in wl w2 ... do список-команд done Список-команд — это последовательность из одной или несколь- ких простых команд, которые разделяются точкой с запятой или заканчиваются литерой новой строки. Кроме того, зарезерви- рованные слова, такие, как do или done, обычно должны следо- вать за литерой новой строки или точкой с запятой. Список зарезервированных слов приведен в приложении 6. Имя — это переменная оболочки, принимающая по очереди значения wl, w2, ... при каждом выполнении следующего за do списка- команд. Если конструкция in wl w2... опущена, то тело цикла выполняется один раз для каждого позиционного параметра, т. е. подразумевается in $*. Другим примером использования цикла for является команда create, текст которой имеет вид for i do >$i; done Команда create alpha beta создает два пустых файла: alpha и beta. Конструкцию >файл саму по себе можно использовать для создания файла или унич- тожения его содержимого. Обратите внимание также на то, что перед словом done требуется точка с запятой (или литера новой строки). Для проверки существования файла можно использо- вать и конструкцию <файл. 4.1.2. Управляющая конструкция case Конструкция case предназначена для организации ветвления программы по нескольким направлениям. Например, команда append имеет вид case 1п 1) cat >>$1 ;; 2) cat >>$2 <$1 ;; * ) echo 'usage: append [ from J to' ;; esac При вызове с одним параметром append file
4.1, Командные процедуры 75 значением $=Н= является цепочка, состоящая из литеры 1, и стан- дартный ввод копируется в конец файла file при помощи коман- ды cat. Команда append filel file2 добавляет содержимое файла filel к содержимому file2. Если количество параметров в команде append отлично от 1 или 2, то выдается сообщение о том, как пользоваться командой («ис- пользование: .append [ из ] в»). Общий вид команды case следующий: case слово in образец) список-команд ;; esac Каждая альтернатива заканчивается литерами Литеры ;; перед esac можно опускать. Оболочка пытается сопоставить указанное слово с каждым из образцов в порядке йх записи. Если найден подходящий об- разец, выполняется соответствующий список-команд, после чего выполнение конструкции case завершается. Поскольку образец * сопоставляется с любой цепочкой литер, его можно исполь- зовать, когда требуется выполнить некоторые действия для «всех остальных» случаев. Предостережение. Не делается никакой проверки на то, что слову, заданному в case, соответствует только один образец. Выполняемый набор команд определяется по первому сопоста- вившемуся образцу. В приведенном ниже примере команды, сле- дующие за второй звездочкой, никогда не выполнятся. case $# in • )... ;; * ) ... ;; esac Конструкцию case можно использовать для распознавания различных видов параметров. Следующий пример представляет собой фрагмент команды сс1>: for i do case $i in — Iocs]) ... ;; — «) echo 'unknown flag $i' ;; * .c) /lib/сО $i ... ;; * ) echo 'unexpected argument $i' ;; done i> Команды echo в этом фрагменте используются для вывода сообщений об ошибках.— Прим, перёй.
76 Гл. 4. Оболочка В конструкции case можно связать один и тот же набор команд с несколькими образцами. Такие альтернативные образцы разде- ляются литерой |. Например, case $i in —х|—у) ... esac эквивалентно case $i in —[xy]) ... esac Используются обычные соглашения об отмене специальных зна- чений литер, поэтому в примере case $i in \?) ... esac будет выполнено сопоставление с литерой ?. 4.1.3. Встроенные документы Командная процедура tel, описанная в разд. 4.1.1, использует в качестве источника данных для команды grep файл /usr/lib/telnos. Другой способ состоит в том, чтобы включить эти данные в саму процедуру в виде встроенного документа: for i do grep $i <<! fred mhO 123 bert mh0789 ! done В этом примере в качестве стандартного ввода для команды grep берутся строки, заключенные между «! и !. Встроенный документ заканчивается строкой, содержащей цепочку литер, заданную после «, поэтому вместо литеры I может быть исполь- зована любая другая литера. Прежде чем встроенный документ станет доступен команде grep, в нем выполняется подстановка значений параметров. Это иллюстрируется на примере процедуры edg (рис. 4.1). Вызов edg string 1 string2 file
4.1. Командные процедуры 77 эквивалентен команде ed file «% g/stringl/s//string2/g w % . и приводит к замене всех вхождений цепочки литер string! в файле file на цепочку string2. Если содержимое буфера было записано в файл, команда выхода из редактора ed не обязательна, достаточно признака конца файла. ed $3 «% fl/Я/е//$2/д w % Рис. 4.1 Процедура edg Подстановку значения параметра можно предотвратить, ис- пользуя литеру \ для отмены специального значения литеры $, как, например, это делается в другом варианте команды edg: ed $3 << + l,\$s/$l/$2/g w + Этот вариант команды edg эквивалентен первому с той лишь разницей, что редактор ed будет печатать знак ?, если в файле нет ни одного вхождения цепочки $1. Подстановку значений параметров во встроенный документ можно отменить вообще, задав отмену специальных литер при указании завершающей це- почки литер, например: grep $i «\: Здесь встроенный документ передается команде grep без каких- либо изменений. Если во встроенном документе не требуется выполнять подстановку значений параметров, то последняя фор- ма записи более эффективна. 4.1.4. Переменные оболочки В оболочке допускаются переменные, значениями которых яв- ляются цепочки литер. Имена переменных начинаются с буквы и состоят из букв, цифр и литер подчеркивания. Переменным
78 Гл. 4. Оболочка можно присваивать значения, например: user=fred box=m000 acct=mhOOOO В этом примере присваиваются значения переменным user, box и acct. По обе стороны от знака = пробелы не допускаются. Переменной можно присвоить пустую цепочку, например: TERM= Для подстановки значения переменной перед ее именем указы- вается литера $. Например, команда echo $user выведет слово fred. Переменные можно использовать при работе в интерактивном режиме как сокращения для часто используемых цепочек литер. Например, b=/usr/fred/bin mv pgm $b выполнит пересылку файла pgm из текущего оглавления в оглав- ление /usr/fred/bin. Для подстановки значений параметров (или переменных) существует более общая форма записи. Например, команда echo ${user} эквивалентна команде echo $user Такая форма записи используется, когда за именем переменной следует буква или цифра. Например, tmp=/tmp/ps ps a >${tmp}a направит вывод команды ps в файл /tmp/psa, тогда как ps a >$tmpa привела бы к подстановке значения переменной tmpa. Специальные переменные За исключением переменной $?, все перечисленные ниже пере- менные устанавливаются программой-оболочкой в начале ее работы. Переменная $? устанавливается после выполнения каж- дой команды. $? Код ответа (статус завершения) последней выполнен- ной команды в виде цепочки десятичных цифр. Боль-
4,1. Командные процедуры 79 шинство команд возвращают нулевой код ответа, если их выполнение завершилось успешно, и ненулевой код в противном случае. Проверка значений кодов от- вета будет рассмотрена в разделе, где описываются конструкции if и while. $0 Имя выполняемой командной процедуры. Одна и та же команда может быть вызвана под разными име- нами. Переменная $0 позволяет различать эти слу- чаи. Например, приведенный ниже фрагмент входит в процедуру ttytek; кроме того, на тот же самый файл существует ссылка ttyblit. Если на файл делается но- вая ссылка и он вызывается под новым именем, вы- полняется последовательность действий, предусмот- ренная для всех остальных случаев. Эта команда ус- танавливает параметры терминала при помощи ко- манды stty case $0 in ttyblit) stty erase ЛН kill @ ffO tabs ;; ttytek) stty erase ЛН kill @ tek —tabs ;; *) stty erase ЛН kill @ ;; esac $Ф Количество позиционных параметров (в десятичном виде). Используется, например, в команде append (разд. 4.1.2) для проверки числа заданных параметров. Значение переменной также переустанавливается при выполнении команды set. $$ Номер процесса, в рамках которого выполняется дан- ная оболочка (в десятичном виде). Поскольку номера всех процессов уникальны, эту переменную часто ис- пользуют для генерации имен временных файлов. Например: ps a >/tmp/ps$$ rm /tmp/ps$$ $! Номер последнего фонового процесса (в десятичном виде). $— Флаги программы-оболочки, такие, как —х и —v, установленные на данный момент. Некоторые переменные имеют для оболочки особое значение, их не следует использовать для других целей. Эти переменные обычно устанавливаются во входном файле .profile в регистра- ционном оглавлении пользователя. $MAIL При работе с терминалом оболочка перед выда- чей очередного приглашения обследует состоя- ние файла, определяемого этой переменной.
80 Гл. 4. Оболочка $НОМЕ SCDPATH $РАТН Если с момента предыдущего обследования в этот файл были внесены изменения, оболочка выведет сообщение you have mail (для вас есть почта) перед выдачей приглашения для ввода очередной команды. Например, для пользователя fred эта переменная устанавливается следующим образом: МА IL=/usr/spool/mail/fred Подразумеваемое значение параметра для команды cd. Имена файлов, которые не начинаются с лите- ры /, считаются именами относительно текущего оглавления. Текущее оглавление можно сменить командой cd. Например, команда cd /usr/fred/bin сделает текущим оглавление /usr/fred/bin. Ко- манда cd без параметров эквивалентна команде cd $НОМЕ Список оглавлений, просматриваемых командой cd. Имена оглавлений разделяются двоеточиями. Типичная установка этой переменной выглядит так: CDPATH$HOME/desk В соответствии со значением этой переменной cd будет просматривать текущее оглавление, роди- тельское оглавление (обозначенное..), и, наконец, оглавление $HOME/desk. Например, если су- ществует оглавление ../src и в текущем оглав- лении нет оглавления src, то команда cd src сделает текущим оглавление ../src и выведет это имя в качестве подтверждения. Список оглавлений, содержащих команды {путь поиска). Каждый раз, когда требуется выполнить коман- ду, оболочка ищет соответствующий выполни- мый файл в заданном списке оглавлений. Если переменная $РАТН не установлена, то, по умолчанию, поиск ведется в текущем оглавле- нии, оглавлениях /bin и /usr/bin. Значением переменной $РАТН является последователь- ность имен оглавлений, разделенных двоето- чиями. Например, PATH=.:./bin:$HOME/bin:/bin:/usr/bin задает следующий порядок просмотра оглавле- ний: текущее оглавление (литера . перед первым
4.1. Командные процедуры 81 двоеточием), оглавления ./bin, $HOME/bin, /bin и /usr/bin. Таким образом, отдельные поль- зователи могут хранить свои собственные коман- ды в оглавлении $HOME/bin и пользоваться ими независимо от того, какое оглавление яв- ляется текущим. Оглавление ./bin включено в список для того, чтобы можно было использо- вать команды из подоглавления bin текущего оглавления (если такое оглавление существует). Это позволяет отделять команды от файлов с дан- ными внутри одного оглавления, связанного с конкретной тематикой. Если имя команды содержит литеру /, просмотр оглавлений не производится; делается единст- венная попытка выполнить команду. Форму записи ./cmd можно использовать для того, чтобы не выполнять поиск по оглавлениям, если команда находится в текущем оглавлении. $PS1 Текст основного приглашения оболочки; по умолчанию — $. $PS2 Оболочка выдает приглашение $PS2, если тре- буется продолжить ввод команды; по умолчанию используется литера >. $IFS Набор литер, которые интерпретируются как пробелы (см. разд. 4.2.4). 4.1.5. Команда test Команда test предназначена для использования в командных процедурах. В некоторых версиях программы-оболочки для по- вышения эффективности команда test реализована как встроен- ная функция. Например, команда test —f file возвращает нулевой код ответа, если файл file существует, и не- нулевой код ответа в противном случае. В общем случае test вычисляет некоторый предикат и возвращает полученный результат в виде кода ответа. Вот некоторые из наиболее часто используемых в команде test параметров: test s Истина, если параметр s не является пустой це- почкой литер. test —f file Истина, если файл file существует и не является оглавлением. test —г file Истина, если файл file доступен для чтения.
82 Гл. 4. Оболочка test —w file Истина, если файл file доступен для записи, test —d file Истина, если файл file является оглавлением. В оболочке отсутствуют средства для выполнения арифмети- ческих операций, однако эти операции доступны косвенно через команду ехрг. 4.1.6. Управляющие конструкции while и until Действия, выполняемые циклом for и ветвлением case, зависят от значений переменных оболочки. Имеются также циклы while и until и ветвление if then else; выполняемые ими действия определяются кодами ответа, возвращаемыми командами. Цикл while имеет следующий вид: while список-командх do сп и со к-ко манд 2 done Значением, проверяемым в цикле while, является код ответа последней из команд, следующих за словом while. При каждой итерации выполняется список-команда если возвращается нуле- вой код ответа, то выполняется список-команд2; в противном слу- чае выполнение цикла заканчивается. Например, цикл while test $1 do ... shift done эквивалентен циклу for i do ... done Команда shift (сдвиг) — это команда оболочки, которая переиме- новывает позиционные параметры $2, $3, ... в $1, $2, ... и от- брасывает $1. Если вместо слова while используется until, условие завер- шения цикла заменяется на противоположное. В следующем примере программа ждет, пока не будет создан файл file: until test —f file do sleep 300; done команды Условием завершения цикла является существование файла file. Каждая итерация цикла заключается в пятиминутном ожидании (300 секунд), после чего условие проверяется снова. (Предпо- лагается, что какой-нибудь другой процесс в некоторый момент создаст этот файл.)
4.1. Командные процедуры 83 4.1.7. Управляющая конструкция if Имеется общая условная конструкция ветвления вида if список-команд then список-команд else список-команд fi В этой конструкции проверяется значение, возвращаемое послед- ней простой командой, следующей за if. Часть else может отсут- ствовать. Поскольку эта конструкция заключена в скобки if ... fi, она может использоваться везде, где допускается ис- пользование простых команд, не вызывая двусмысленного тол- кования. Более того, отсутствует «висячее else», создающее трудности при интерактивном режиме работы. Команду if можно использовать в сочетании с командой test для проверки существования некоторого файла, например: if test —f file then * обработка файла else ф выполнение каких-либо иных действий fi Полный пример использования конструкций if, case и for приве- ден в разд. 4.1.10. Вложенные конструкции if вида if ... then ... else if ... fi fi можно записать с использованием расширенной конструкции if: if ... then ... elif fi В качестве примера на рис. 4.2 приведена команда dw, кото- рая рекурсивно просматривает дерево оглавлений и печатает имена обнаруженных файлов. Установка переменной PATH га- рантирует, что будут выполняться нужные команды. Путь поиска команды часто включает в себя текущее оглавление. За- дание явного пути поиска защищает команду dw от случайности встретить команду, также называемую dw или test в каком-либо подоглавлении.
84 Гл. 4. Оболочка Цикл for анализирует каждое имя в текущем оглавлении. Если файл с таким именем существует и не является оглавле- нием, то никаких действий не выполняется; если же это имя Р АТН= / bin; / usr/bin;$HOME /bin cd $1 Is -I for I- in • do if feet -f $1 then else dw $1 fi done Рис. 4.2 Команда dw оглавления, то для выдачи списка имен файлов из этого оглав- ления рекурсивно вызывается команда dw. Последовательность if команда] then команда2 fi можно также записать в виде команда] && команда2 Наоборот, в конструкции команда] II команда^ команда2 будет выполнена только в том случае, если выполнение команды] закончилось неудачно. В любом случае возвращаемым значением является код ответа последней выполненной простой команды. 4.1.8. Группировка команд Команды можно группировать двумя способами: { список-команд ; } или ( список-команд )
4,1. Командные процедуры 85 В первом случае просто выполняется список-команд. Во вто- ром случае список-команд выполняется с помощью отдельного процесса оболочки. Например, (cd х; rm junk) выполнит команду rm junk в оглавлении х, не изменяя текущее оглавление в вызывающей оболочке. Команды cd х; rm junk выполняют те же самые действия, но текущим оглавлением станет оглавление х. Исторически сложилось так, что круглые скобки являются такими же специальными литерами, как и | и ;, и распознаются в любом контексте, если только их специальное значение не от- менено. Фигурные скобки являются зарезервированными сло- вами, такими же как if и do, поэтому они распознаются, только когда следуют за литерой новой строки или за точкой с запятой. 4.1.9. Отладка командных процедур В оболочке предусмотрены два механизма, облегчающие отладку командных процедур. Первый из них приводится в действие в результате выполнения в самой процедуре команды set —V (флаг v — сокращение от verbose — многословный) и вызывает распечатку строк процедуры по мере их считывания. Это облег- чает поиск синтаксических ошибок. Указанный механизм можно включить и без вмешательства в процедуру с помощью команды вида sh —v proc ... где proc — это имя отлаживаемой процедуры. Нежелательных побочных эффектов можно избежать, если использовать флаг —п, который блокирует выполнение команд процедуры. (Коман- да set —п, введенная с терминала, сделает этот терминал бездей- ствующим до тех пор, пока не будет введен признак конца файла.) Команда set —х включит трассировку выполнения. Команды будут печататься по мере выполнения после подстановки в них значений пере- менных. При помощи флага —и можно выявить случаи использования неинициализированных переменных оболочки. Если такой флаг
86 Гл. 4. Оболочка установлен, любая попытка подстановки переменной, которой не было присвоено значение, приведет к выдаче сообщения об ошиб- ке и прекращению выполнения процедуры. (Попробуйте испы- cd /usr/man N=n з=“1 # подразумевается nroff ($N), том 1 ($з) for i do case $i in [1-9]*) s-$i ;; - t) N-t ;; — n) N-n :: — *) echo unknown flag ;; • ) if test -f man$s/$i.$s then ${N)roff -man man$s/$i.$s Qlse # поиск no всем томам руководства found-no for j in 1 2 3 4 5-6 7 8 do if test -f man$j/$i.$j then. man $j $i ’found-yes fi done case $found in no) echo \'$i; manual page not found\ . esac fi esac done Рис. 4.3 Один из вариантов команды man тать все эти средства за терминалом, чтобы понять их действие.) Все флаги можно отменить командой set — Флаги оболочки, установленные на данный момент, доступны как $—. (В некоторых системах для отмены флагов используется другая форма записи.)
4.2. Дополнительные возможности 87 4.1.10. Команда man На рис. 4.3 приведен один из вариантов команды man, которая печатает разделы из Programmer s Manual (Руководство для программиста). Эта команда вызывается, например, так: man sh man —t ed man 2 fork Первая команда выдаст описание команды sh. Поскольку том руководства не задан, подразумевается первый том. Вторая команда подготовит описание редактора ed для печати на фото- наборном устройстве (опция —t). Последняя команда выдаст страницу руководства с описанием системной функции fork из второго тома. 4.2. ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ 4.2.1. Передача параметров Переменные языка оболочки могут получать значения либо в ре- зультате явного присваивания, либо при вызове командной процедуры. Если параметр процедуры имеет вид имя = значение и предшествует имени команды, то значение присваивается пере- менной имя перед началом выполнения процедуры. Значение переменной имя в вызывающей оболочке не изменяется. Напри- мер, в результате user=fred команда указанная команда будет выполнена со значением переменной user, равным fred. Если установлен флаг —к, то выражения вида имя=значение интерпретируются таким образом в любом месте списка параметров вызываемой процедуры. Такие переменные иногда называют ключевыми параметрами. Остальные параметры являются позиционными параметрами $1, $2, ... . Команду set можно использовать также для установки пози- ционных параметров внутри самой процедуры. Например, в ре- зультате выполнения команды set — * значением $1 станет имя первого файла в текущем оглавлении, значением $2 — имя следующего файла и т. д. Первый пара- метр — минус — гарантирует правильную обработку в том слу- чае, если имя первого файла начинается со знака минус. Среда процесса — это список пар «имя-значение», который передается в выполняемую программу таким же образом, как
88 Гл. 4. Оболочка и список обычных параметров. Оболочка взаимодействует со средой несколькими способами. Когда оболочка вызывается, она просматривает среду и создает для каждого найденного имени переменную, присваивая ей соответствующее значение. Выпол- няемые команды наследуют ту же среду. Изменение значений этих переменных и создание новых переменных не влияет на сре- ду, если только для внесения параметров в нее не использовалась команда export. Среда выполняемой команды строится из всех неизмененных пар «имя-значение», первоначально унаследован- ных оболочкой, и из всех пар для измененных или новых экспор- тируемых переменных. При вызове командной процедуры можно задавать как по- зиционные, так и ключевые параметры. Ключевые параметры для командной процедуры можно. также задавать неявно, если определить их заранее как экспортируемые. Например, команда export user box помечает переменные user и box как экспортируемые. При вызове командной процедуры делаются копии всех экспортируемых переменных; эти копии и используются в вызываемой процедуре. Изменение значений таких переменных внутри вызываемой про- цедуры не меняет их значений в вызывающей программе-обо- лочке. И вообще командная процедура, как правило, не может изменить состояние вызвавшей ее программы без явного на то запроса. (Исключение из этого правила составляют совместно используемые дескрипторы файлов.) Переменные, значения которых желательно сохранить неиз- менными, можно объявить доступными только для чтения (readon- ly). Эта команда имеет такой же вид, что и команда export: readonly имя-переменной ... Последующие попытки присвоить значения таким переменным отвергаются. 4.2.2. Подстановка переменных Если переменной (параметру) оболочки не присвоено никакого значения, то значением такой переменной будет пустая цепочка. Например, если переменной d не присвоено значение, то команды echo $d и echo ${d} ничего не напечатают. Можно задавать подразумеваемую цепочку литер. Так, команда echo ${d—.}
4.2. Дополнительные возможности 89 напечатает значение переменной d, если оно есть, и литеру . в противном случае. Подразумеваемая цепочка литер интерпрети- руется в соответствии с обычными соглашениями об отмене специальных значений литер, поэтому команда echo ${d—'*'} напечатает просто литеру *, если значение переменной d не уста- новлено. Аналогично команда echo ${d—$1} напечатает значение переменной d, если оно присвоено, и значе- ние $1 (если оно существует) в противном случае. Переменной можно присвоить некоторое подразумеваемое значение: команда echo ${d=.} напечатает ту же цепочку литер, что и команда echo ${d—.} и, если значение переменной d не было установлено, ей будет при- своена цепочка, состоящая из одной точки. Запись вида ${...=...} не допустима для позиционных параметров. Если переменная обязательно должна иметь значение, а под- разумеваемого значения не существует, запись вида echo $ ^сообщение} можно использовать для печати значения переменной d, если оно есть; в противном случае оболочка печатает сообщение, и выполнение командной процедуры прекращается. Если сооб- щение не задано, то выдается некоторое стандартное сообщение. Командная процедура, для выполнения которой требуется уста- новить ряд параметров, может начинаться следующим образом: : ${user?} ${acct?} ${bin?} Двоеточие представляет собой встроенную в оболочку команду, которая не выполняет никаких действий, однако ее параметры вычисляются. Если же хотя бы одна из переменных user, acct или bin не установлена, то выполнение процедуры будет пре- кращено. 4.2.3. Подстановка команд По аналогии с параметрами можно подставлять и стандартный вывод команды. Команда pwd выдает в стандартный вывод имя текущего оглавления. Например, если текущим является оглав- ление /usr/fred/bin, то команда d='pwd'
90 Гл. 4. Оболочка эквивалентна команде d=/usr/fred/bin Вся заключенная между знаками тупого ударения цепочка литер воспринимается как команда, которую следует выполнить, и заменяется результатом выполнения этой команды. Подстав- ляемая команда записывается с использованием обычных согла- шений об отмене специальных значений литер, за исключением того, что перед самим знаком тупого ударения, если он исполь- зуется в команде, следует помещать обратную косую черту. Например, команда Is 'echo эквивалентна команде 1s $1 Подстановка команд выполняется везде, где допускается под- становка параметров (включая встроенные документы), а трак- товка полученного в результате текста одинакова в обоих слу- чаях. Этот механизм позволяет использовать в процедурах коман- ды обработки цепочек литер. Примером такой команды является команда basename, которая удаляет из цепочки заданный суф- фикс. Например, команда basename main.с .с напечатает цепочку main. Ее использование показано на примере следующего фрагмента команды сс: case $А in *.с) B='basename $А .с' esac Здесь переменной В будет присвоена часть значения $А без суф- фикса .с. Вот несколько более сложных примеров: for i in 'Is —t'; do... Переменной i в качестве значений присваиваются имена файлов, упорядоченные по времени последней модифи- кации, начиная с имени файла, модифицированного последним. set 'date'; echo $6 $2 $3, $4 Напечатается, например, сообщение 1970 Feb 3, 11:59:59. Вывод команды date имеет вид Tue Feb 3 11:59:59 GMT 1970. Оболочка разбивает эту строку на слова и пере-
4.2. Дополнительные возможности 91 дает их в качестве параметров команде set, которая при- сваивает их позиционным параметрам. а='ехрг $а + Г Значение переменной а увеличивается на 1. 4.2.4. Вычисление и отмена специальных значений Оболочка представляет собой макропроцессор, который обеспе- чивает подстановку переменных, подстановку команд и порож- дение имен файлов для использования в качестве параметров команд. В этом разделе рассматриваются порядок выполнения этих операций (порядок «вычисления» команды) и действие различных механизмов отмены специальных значений. Перед выполнением команды производятся следующие под- становки: • Подстановка переменных Конструкция $user — пример подстановки переменной. Подстановка выполняется только один раз, оболочка не анализирует подставленную цепочку на предмет даль- нейших подстановок. Например, если значением пере- менной X является цепочка $у, то команда echo $Х выведет $у. • Подстановка команд Конструкция 'pwd'— пример подстановки команды. Например, внутри входного файла переменную НОМЕ можно установить следующим образом: HOME='pwd' Такой входной файл не зависит от имени регистрацион- ного оглавления. • Интерпретация пробелов После выполнения описанных выше подстановок полу- ченная в результате последовательность литер разби- вается на слова, не содержащие пробелы (этот процесс называется интерпретацией пробелов). Пробелами счи- таются литеры, содержащиеся в цепочке $IFS. По умол- чанию, эта цепочка состоит из собственно пробела, табу- ляции и новой строки. Пустая цепочка считается словом, только если она заключена в кавычки. Например, команда echo "
92 Гл. 4. Оболочка передаст программе echo в качестве первого параметра пустую цепочку, в то время как команда echo $null вызовет программу echo без параметров, если значение переменной null не установлено или является пустой цепочкой. • Порождение имен файлов Наконец, каждое слово просматривается с целью обна- ружения литер *, ?, и [ ... ], используемых в образцах, и такое слово заменяется порождаемым списком имен файлов (в алфавитном порядке). Каждое порожденное имя файла представляет собой отдельный параметр. Описанные выше действия выполняются и при вычислении списка слов в цикле for. В слове, по которому осуществляется переключение в конструкции case, выполняется только подста- новка переменных и команд. Наряду с механизмами отмены специальных значений с по- мощью обратной косой черты и одинарных кавычек, описанными в разд. 2.6.3, существует третий механизм — с использованием двойных кавычек. Внутри цепочки литер, заключенной в двойные кавычки, производится подстановка переменных и команд, но порождение имен файлов и интерпретация пробелов не вы- полняются. Приведенные ниже литеры имеют специальное зна- чение внутри двойных кавычек, однако эти значения можно от- менить с помощью литеры \. $ Подстановка переменной. ' Подстановка команды. ” Закрывающая кавычка. \ Отмена специального значения литер $, \ \. Например, команда echo "$х" передаст программе echo значение переменной х в качестве един- ственного параметра. Аналогично команда echo "$*" передаст список позиционных параметров в виде одного пара- метра. Она эквивалентна команде echo "$1 $2 ..." Конструкция $@ эквивалентна $*, за исключением случая, когда она заключена в кавычки. Команда echo
4,2. Дополнительные возможности 93 передаст программе echo последовательность позиционных пара- метров, не вычисляя их. Она эквивалента команде echo "$1" "$2" ... Когда параметры отсутствуют, значением такой конструкции является пустое слово. Это используется в следующем фраг- менте: for I In do cat $i done Гарантируется, что цикл выполнится хотя бы один раз. В этом примере обрабатывается каждый заданный файл. Если не задано ни одного параметра-файла, то копируется стандартный ввод. Металитеры t и ч \ $ * ! 0 нет нет нет нет нет И нет о да да да нет \ нет нет о да нет нет Обозначения: о—ограничитель да—интерпретируется нет—не интерпретируется Рис. 4.4 Отмена специальных значений литер в оболочке На рис. 4.4 для каждого из механизмов отмены специальных значений приводится список интерпретируемых металитер. В тех случаях, когда требуется вычислить цепочку литер более одного раза, можно воспользоваться встроенной командой eval. Например, если переменная X имеет значение $у, а пере- менная у имеет значение pqr, то команда eval echo $Х напечатает pqr. В общем случае команда eval вычисляет свои параметры (как это делают все команды) и передает результат оболочке в качест- ве ввода. Этот ввод считывается и получившаяся команда (коман- ды) выполняете^. Например, если переменная subscript имеет значения user, то команда eval echo \$L_$ubscript
94 Гл. 4. Оболочка эквивалентна команде echo $L_user В этом примере команда eval необходима, поскольку интерпре- тация литеры $ после первой подстановки не производится. Команду eval можно также использовать в случае, когда после подстановки требуется проинтерпретировать синтаксические ли- теры, такие, как |. 4.2.5. Обработка ошибок Обработка ошибок, обнаруженных оболочкой, зависит от вида ошибки и от того, используется ли оболочка в интерактивном режиме или нет. Оболочка считается интерактивной, если ее ввод и вывод связаны с некоторым терминалом (что определяется посредством обращения к системной функции gtty). Оболочка, вызванная с флагом —i, также считается интерактивной. Выполнение команды (см. также разд. 4.2.7) может завер- шиться неудачно по одной из следующих причин: • Переадресацию ввода-вывода выполнить не удалось, на- пример файл не существует или не может быть создан. • Сама команда не существует или не может быть выпол-* йена. Сигнал Описание 1 зависание 2 прерывание х 3 выход 9 уничтожение (не перехватывается и не игнори- 15 руется) программное завершение (от команды kill) Рис. 4.5 Сигналы системы UNIX, используемые в командных процедурах • Выполнение команды завершилось неудачно, например, из-за обращения в чужую память. На рис. 4.5 приво- дится список сигналов, которые представляют интерес для командных процедур. По сигналу выхода, если он не перехвачен, генерируется дамп оперативной памяти. Однако сама оболочка игнорирует сигнал выхода, кото- рый является единственным внешним сигналом, вызы- вающим дамп.
4.2. Дополнительные возможности 95 • Выполнение команды завершилось нормально, но с ко- дом ответа, отличным от нуля. Во всех указанных случаях оболочка перейдет к выполнению следующей команды. Во всех случаях, кроме последнего, обо- лочка выведет сообщение об ошибке. Все прочие ошибки вызы- вают выход из командной процедуры. (Интерактивная же обо- лочка перейдет к чтению следующей команды с терминала.) К таким ошибкам относятся: • Синтаксические ошибки, например if...then...done, где вместо done должно быть fi. • Сигнал, такой, как прерывание. Оболочка ждет оконча- ния выполнения текущей команды (если она есть), а за- тем либо завершает работу, либо вновь обращается к терминалу. Изменить эту подразумеваемую реакцию на прерывания позволяет команда trap. • Ошибка при выполнении любой из встроенных команд, например такой, как cd. Флаг —е вызывает выход из оболочки при обнаружении любой ошибки. 4.2.6. Обработка сигналов Командные процедуры обычно завершаются, если с терминала поступил сигнал прерывания. Команда trap используется в слу- чае, если требуется выполнить какие-либо завершающие дейст- вия, скажем, уничтожить временные файлы. Например, trap 'rm /tmp/ps$$; exit' 2 устанавливает реакцию на сигнал 2 (прерывание с терминала). Если будет получен этот сигнал, выполнятся команды rm /tmp/ps$$; exit Команда exit — это еще одна встроенная команда. Эта команда вызывает завершение выполнения командной процедуры. Коман- да exit в этом примере необходима, поскольку без нее после об- работки прерывания оболочка продолжила бы выполнение про- цедуры с прерванного места. Сигналы могут быть обработаны одним из трех способов. Их можно игнорировать, в таком случае сигнал никогда не пере- дается в соответствующий процесс. Их можно перехватывать, в этом случае процесс должен задать действия, которые следует выполнить при получении сигнала. Наконец, можно вообще не задавать реакции на сигнал, и тогда сигнал вызовет завершение процесса без выполнения каких-либо дальнейших действий. Если к моменту входа в командную процедуру уже задано игнори-
96 Гл. 4. Оболочка рование некоторых сигналов, как это происходит, например, при выполнении фоновой командной процедуры (см. разд. 4.2.7), то команды trap для этих сигналов (как и сами эти сигналы) игнорируются. trap 'rm -f junk$$; exit' 0 1 2 3 15 Рис. 4.6 Пример использования команды trap Использование команды trap иллюстрируется на примере фрагмента командной процедуры на рис. 4.6. Завершающие действия состоят в удалении файла junk$$ и выходе из команд- ной процедуры. Команда trap должна встретиться раньше, чем команда создания временного файла; в противном случае процесс может исчезнуть, но временный файл не будет уничтожен. В самой системе UNIX не используется сигнал 0; в оболочке он означает выход из командной процедуры. Процедура может по своему усмотрению игнорировать неко- торые сигналы. Для этого в качестве параметра команды trap задается пустая цепочка. Следующий фрагмент взят из команды nohup: trap "12 3 15 В результате этого сигналы «зависание», «прерывание», «выход» и «программное завершение» будут игнорироваться как самой про- цедурой, так и запускаемыми ею командами. Реакцию на сигналы можно переустанавливать. Например, команда trap 2 3 восстановит для сигналов 2 и 3 стандартную реакцию. Перечень реакций, установленных на текущий момент, можно получить с помощью команды trap В команде trap совсем не обязательно задавать exit. Про- цедура scan (рис. 4.7) — пример такого использования команды trap: выполнение процедуры возобновляется после обработки сигнала. Процедура scan перебирает все подоглавления в теку- щем оглавлении. Для каждого подоглавления выдается его имя и выполняются команды, вводимые с терминала. Ввод команд заканчивается по признаку конца файла или по прерыванию. Команда : только вычисляет свои параметры, поэтому команда trap : 2
4.2» Дополнительные возможности 47 приводит к фактическому игнорированию прерываний при вы- полнении eval $х. Когда scan ждет ввода, т. е. выполняется read х, действует команда trap exit 2 Встроенная команда read х читает одну строку из файла стан- дартного ввода и помещает результат в переменную х. Она воз- вращает ненулевой код ответа, если был считан признак конца файла или получено прерывание. d=4pwd' for i in ♦ do if fest -d $d/$i then cd $d/$i while echo ”$i‘? trap exit 2 read x do trap : 2; eval $x; done fl done Рис. 4.7 Процедура scan 4.2.7. Выполнение команд Для выполнения большинства команд оболочка прежде всего создает новый процесс, обращаясь к системной функции fork. Новый отдельный процесс требуется для выполнения простых команд, списка команд, заключенного в круглые скобки, таких конструкций, как while и for, при наличии переадресации ввода- вывода. Для встроенных команд новые процессы не создаются. Среда, в которой выполняется команда, включает в себя открытые файлы, значение маски umask, текущее оглавление, реакции на сигналы и устанавливается в порожденном процессе перед началом выполнения команды. Если новый процесс не нужен, используется встроенная команда ехес, которая просто заменяет оболочку новой командой. Например, простейший вариант команды nohup выглядит так: trap "12 3 15 exec "${@?}" Команда trap отключает указанные сигналы, они будут игнори- роваться во всех последующих командах, а ехес заменяет обо- лочку на команду, заданную параметрами. 4 С. Баурн
98 Гл. 4. Оболочка 4.2.8. Переадресация ввода-вывода Большинство способов переадресации ввода-вывода было уже рассмотрено. В приводимых ниже описаниях слово обозначает цепочку литер, в которой выполняются только подстановки переменных и команд. Порождение имен файлов и интерпретация пробелов не производятся. Поэтому, например, команда echo ... >*.с поместит свой вывод в файл с именем *.с. Спецификации ввода- вывода вычисляются слева направо в порядке их записи в коман- де. Если слово является пустой цепочкой, переадресация ввода- вывода не выполняется. Например, команда cat <$1 эквивалентна команде cat без переадресации ввода, если значение параметра $1 не установлено или является пустой цепочкой. > слово Стандартный вывод (дескриптор файла 1) на- правляется в файл слово, который будет создан, если не существует. >> слово Стандартный вывод направляется в файл слово. Если файл существует, то вывод добавляется в конец файла (путем установки указателя записи на конце файла). В противном случае создается новый файл. < слово Стандартный ввод (дескриптор файла 0) берется из файла слово. << слово В качестве стандартного ввода команды берутся последующие строки из файла ввода оболочки до строки, содержащей только это слово (не включая ее). Если перед цепочкой слово стоит литера \, то интерпретация текста во встроен- ном документе не производится. В противном случае выполняется подстановка переменных и команд, а литера \ отменяет специальные значения литер \, $, ' и первой литеры це- почки слово. В этом случае комбинация \новая-строка игнорируется (сравните с цепоч- ками литер, заключенными в кавычки). Если слово является пустой цепочкой, то вводимый текст завершается пустой строкой. >& цифра Дескриптор файла стандартного вывода полу- чается в результате дублирования дескриптора файла цифра при помощи системной функции dup.
4.2. Дополнительные возможности 99 < & цифра Дескриптор файла стандартного ввода полу- чается в результате дублирования дескриптора файла цифра. <&— Стандартный ввод закрывается. >&— Стандартный вывод закрывается. Любой из приведенных выше конструкций может предшество- вать цифра. В этом случае файл будет иметь дескриптор, зада- ваемый этой цифрой, вместо используемых по умолчанию деск- рипторов О или 1. Например, если задана конструкция...2>файл, то при выполнении команды диагностическое сообщение (дескрип- тор файла 2) будет направляться в указанный файл. А конструк- ция ...2>&1 приведет к совмещению файлов стандартного вывода и диагностики при выполнении команды. (Строго говоря, дескрип- тор файла 2 создается посредством дублирования дескриптора файла 1, однако результатом этого является слияние двух по- токов вывода.) Внутри командной процедуры можно переназначить файлы стандартного ввода, вывода и диагностики, используя рассмот- ренные выше конструкции и команду ехес. Например, команда exec >stdout 2>errout переадресует стандартный вывод в файл stdout, а диагностиче- ские сообщения — в файл errout. Эти файлы станут стандартными файлами вывода и диагностики для последующих команд. В среду выполнения фоновой команды, например такой, как list *.с | 1рг & вносятся два изменения. Во-первых, подразумеваемым файлом стандартного ввода для такой команды будет пустой файл /dev/null. Благодаря этому два параллельно выполняющихся процесса (оболочка и фоновая команда) не смогут читать один и тот же файл ввода. В противном случае была бы полная нераз- бериха. Например, команда ed файл & привела бы к тому, что и редактор, и оболочка читали бы одно- временно один и тот же файл ввода. Другим изменением в среде выполнения фоновой команды является отключение сигналов выхода и прерывания. Указанные сигналы будут игнорироваться фоновой командой. Это позволяет использовать их в дальнейшей работе за терминалом, не вызывая при этом прекращения выполнения фоновых команд. Сигнал зависания, вообще говоря, не игнорируется фоновыми процес- сами; для того чтобы он игнорировался, следует воспользоваться командой nohup. Поэтому в системе существует соглашение 4Ф
100 Гл. 4. Оболочка о сигналах, согласно которому, если некоторый сигнал установ- лен в 1 (т. е. игнорируется), его состояние никогда не изменяется, даже на короткое время. Команда оболочки trap не изменяет тостояние игнорируемых сигналов. 4,2.9. Вызов оболочки Приведенные ниже флаги интерпретируются оболочкой при ее вызове. Если первой литерой нулевого параметра1) является знак минус, то считываются и выполняются команды из входного файла .profile. В некоторых версиях системы UNIX также вы- полняется системный входной файл. Вывод программы-оболочки записывается в файл с дескриптором 2. «—с s Если задан флаг —с, то считываются и выполняются команды из цепочки s. Эта опция часто используется программами на языке С для передачи команд оболочке для выполнения. Именно так в редакторе ed реализу- ется команда !. —s Если задан флаг —s или же в команде нет ни одного параметра-файла, команды считываются из файла стандартного ввода. —i Если задан флаг —i или если ввод и вывод оболочки связаны с терминалом (это определяется с помощью системной функции gtty), оболочка является интерак- тивной. В этом случае сигнал завершения игнори- руется (так что команда kill 0 не вызовет прекраще- ния работы интерактивной оболочки), а сигнал пре- рывания перехватывается и игнорируется (поэтому выполнение системной функции ожидания wait может быть прервано). Сигнал выхода всегда игнорируется оболочкой. При обнаружении ошибок, например синтаксических, обо- лочка возвращает ненулевой статус завершения. Если оболочка работала в неинтерактивном режиме, выполнение командного файла прекращается. В противном случае (когда ошибок нет) оболочка возвращает статус завершения последней выполненной команды (см. также команду exit). 4.3. ВСТРОЕННЫЕ КОМАНДЫ Некоторые функции встроены в оболочку либо по необходимости, либо из соображений эффективности. Эти команды выполняются в рамках того же процесса, что и оболочка; переадресация ввода- J) Имеется в виду параметр, значением которого обычно является имя вызываемой команды,— Прим, перев^
4.3. Встроенные команды 101 вывода для них не допускается, за исключением специально оговоренных случаев. : Пустая команда. Эта команда ничего не делает и воз- вращает нулевой код ответа. . файл Считать и выполнить команды из указанного файла, после чего вернуться назад (за команду . файл). Для поиска оглавления, содержащего файл, используется путь поиска $РАТН. break [ п ] Выход из объемлющего цикла for или while, если тако- вой существует. Если задано п, то выполняется выход из п вложенных циклов. continue [nJ Начать следующую итерацию объемлющего цикла for или while. Если задано п, то возобновляется выполне- ние n-го объемлющего цикла. cd [ парам ] Сделать текущим оглавлением парам. Подразумевае- мым значением парам является $НОМЕ. Для поиска оглавления парам используется также переменная оболочки CDPATH, как это подробно описано в разд. 4.1.4. Синонимом команды cd является команда chdir. eval [ парам ... ] Заданные параметры передаются оболочке в качестве ввода, и полученная в результате команда (команды) выполняется. ехес [ парам ... ] Вместо оболочки выполняется команда, задаваемая параметрами ехес. Новый процесс не создается. В команде могут присутствовать спецификации ввода- вывода. Если кроме них в команде ехес нет других параметров, происходит переназначение ввода-вывода оболочки. exit I п ] Выход из оболочки с кодом ответа п. Если параметр п опущен, кодом ответа будет код ответа последней выполненной команды. (Признак конца файла также приведет к выходу из оболочки.) export [ имя-переменной ... ] Указанные переменные помечаются как экспортируе- мые и будут автоматически передаваться в среду вы- полняемых далее команд. Если параметры не заданы, выдается список имен экспортируемых переменных, login [ парам ... ] Эквивалентна команде ехес login парам ... .
102 Гл. 4. Оболочка newgrp [ парам ... ] Эквивалентна команде ехес newgrp парам ... . Команды login и newgrp являются встроенными только в некоторых версиях оболочки. read имя-переменной ... Из стандартного файла ввода считывается одна строка. Слова из этой строки последовательно присваиваются заданным в команде переменным; все оставшиеся слова присваиваются последней переменной. Ненуле- вой код ответа возвращается только при достижении конца файла. readonly [ имя-переменной ... ] Список переменных, доступных только для чтения. Значения указанных переменных в последующих командах присваивания изменить будет невозможно. Если параметры не заданы, выдается список всех переменных, доступных только для чтения. set [ —ekntuvx [ парам ... ] ] —е В неинтерактивном режиме работы прекратить выполнение командного файла при неудачном завершении любой команды. —к Поместить в среду вызываемой команды все ключе- вые параметры, заданные в списке параметров вы- зова, а не только те, которые предшествуют име- ни команды. — п Считывать команды, но не выполнять их. — t Прекратить работу после чтения и выполнения одной команды. — и Считать ошибкой подстановку неустановленных переменных. — v Печатать строки командного файла по мере их считывания. — х Печатать команды и их параметры по мере их вы- полнения. — Отменить опции —х и —v. Форма записи этой оп- ции различна в разных версиях системы. Эти флаги могут задаваться также при вызове обо- лочки. Текущие установки флагов можно найти в $—. Остальные параметры команды set присваиваются по порядку переменным $1, $2, ... . Если не задано ни одного параметра, то будут распечатаны значения всех переменных. shift Позиционные параметры $2 ... переименовываются в $1, ... . times Выдать суммарные времена — время пользователя и
4.3. Встроенные команды 103 системное время,— затраченные на выполнение про-, цессов, запущенных из данной оболочки. trap [ парам И п ] ... Параметр парам представляет собой команду, кото- рую следует считать и выполнить при получении сиг- нала (сигналов) п. Парам вычисляется дважды: сна- чала при установке реакции на сигнал, а потом при его обработке. Одновременно полученные сигналы обрабатываются в порядке их номеров. Если парам отсутствует, то для сигнала (сигналов) п восстанавли- вается исходная (подразумеваемая) реакция. Если парам является пустой цепочкой, то указанный сигнал будет игнорироваться оболочкой и вызываемыми из нее командами. Если п равно 0, команда парам вы- полнится при выходе из оболочки. Если же и отлично от нуля, то парам выполняется по получению сигнала с номером п. Команда trap без параметров выдает список реакций на все сигналы. umask [ ddd ] Маске пользователя, используемой для ограничения полномочий при создании файлов, присваивается восьмеричное значение ddd (см. описание системной функции umask). Если ddd опущено, то выдается те- кущее значение маски. wait I п ] Эта команда ждет окончания работы заданного про- цесса и сообщает его статус завершения. Если п не задано, то wait ждет завершения всех порожденных процессов, активных в данный момент. Кодом ответа этой команды является код ответа ожидавшегося процесса.
ГЛАВА 5 ЯЗЫК ПРОГРАММИРОВАНИЯ С Язык программирования С («Си») использовался для написания операционной системы UNIX и большинства ее команд. Он был разработан в то время, когда писалась операционная система, и совершенствовался в течение 10 лет. На языке С можно писать как машиннозависимые, так и переносимые программы. Первый компилятор с языка был написан для ЭВМ PDP 11/45, теперь же С-компиляторы имеются на многих различных машинах. Исторически на С оказал влияние язык BCPL, разрабатывав- шийся сначала в Массачусетском технологическом институте, а позднее — в Кембриджском университете. Связующим звеном между этими двумя языками был язык В. В языке BCPL отсутствуют типы данных, единственным объ- ектом является машинное слово. Все операции языка определены над словами, и по этому же принципу выполняется распределение памяти. Язык привлекателен своей простотой. Переносимый компилятор с BCPL появился в конце 1960-х годов. Язык ис- пользовался для создания операционных систем. Некоторые особенности языка С легче понять, зная историю его возникновения. Язык предназначался для системного про- граммирования, и важным аспектом считалась его близость к ма- шине. Например, операция ++ имеет прямой эквивалент в на- боре команд ЭВМ PDP 11. В конце 1970-х годов в язык были до- бавлены такие конструкции, как объединения, что позволило писать переносимые программы. Следы происхождения С от языков без типов данных до сих пор видны в том, как свободно в некоторых реализациях можно смешивать целые и текстовые величины, указатели и целые. Проверка типов параметров при вызове функций также отсутствует. В С имеются различные базисные типы данных, такие, как int, char, float и double, а также производные типы данных, ко- торые строятся из базисных при помощи массивов и структур. Другими производными типами являются перечисления, указа- тели, объединения и структуры. Механизм типов в С подобен механизму типов в таких языках, как Алгол 68 и Паскаль. Язык С отличается от них мерой строгости при проверке соот-
5.1. Примеры программ на языке С 105 ветствия типов. Некоторые компиляторы с языка С, например, разрешают присваивание значения указателя переменной целого типа. В Паскале семантика этой операции не определена, и по- этому данная операция не реализована. Вольный подход к типам, принятый в С, позволяет писать такие программы, как, напри- мер, программы распределения памяти, при условии что имеется некоторая информация о реализации системы. В языке С предусмотрены стандартные управляющие конст- рукции: if и switch —для выбора, while и for — для повторения. Кроме того, функции могут возвращать значения, и к ним можно обращаться рекурсивно. Функцию, не возвращающую никакого значения, по аналогии с Алголом 68 можно описать как void. Эта глава представляет собой введение в язык программиро- вания С. В ней также описываются способы организации, со- провождения и отладки С-программ. В следующей главе язык G используется для описания системных функций. В первом разделе данной главы кратко, на примерах, описы- ваются основные элементы языка С. Так как язык развивался в течение многих лет, существуют различные версии компиля- тора. Мы попытались обойти эти различия, однако на вашей машине некоторые средства могут или отсутствовать, или могут быть реализованы другим способом. 5.1. ПРИМЕРЫ ПРОГРАММ НА ЯЗЫКЕ С 5.1.1. Простейшая программа Следующая простейшая С-программа main() { printf("It works.\n"); } выведет на стандартное устройство вывода (терминал) текст It works. Исходный текст программы должен быть помещен в файл, имя которого оканчивается на .с, например simplex. Для ком- пиляции программы нужно вызвать С-компилятор. Для этого служит команда сс simplex которая создает готовую к выполнению программу под именем a.out. Если же задана опция —о, то команда сс —о simple simplex поместит готовую к выполнению программу в файл simple. Для
106 Гл. 5. fl зык программирования С того чтобы выполнить готовую программу, достаточно теперь набрать команду simple. Вернемся к рассматриваемой С-программе. По соглашению, все С-программы должны иметь функцию с именем main, с ко- торой программа начинает выполняться. В данной программе main является единственной функцией. Скобки ( ) означают, что параметры в функции main не используются. Тело функции main заключено в фигурные скобки { и }. Оно состоит из одного оператора, оканчивающегося точкой с запятой, который вызы- вает функцию printf, выполняющую форматный вывод. Текстовые цепочки заключаются в двойные кавычки, как показано в приве- денном выше примере. Комбинация \п внутри цепочек обозна- чает литеру новой строки и при выводе вызывает переход на начало следующей строки. 5.1.2. Восьмеричный дамп Программа на рис. 5.1 представляет собой упрощенный вариант команды od, которая читает свой стандартный ввод и выводит каждую прочитанную литеру в виде восьмеричного целого в файл стандартного вывода. После каждых 10 литер вывод начинается с новой строки. В этой программе демонстрируется несколько новых осо- бенностей языка С. Функции getchar и putchar являются двумя основными подпрограммами ввода-вывода из системной библиоте- ки С. Функция getchar возвращает очередную литеру из стандарт- ного ввода в виде целого числа, a putchar, наоборот, получив це- лое число как параметр, печатает соответствующую ему литеру на стандартном выводе. Первая строка программы — конструкция ^include, кото- рая вставляет в программу определения, необходимые при ис- пользовании стандартной библиотеки программ ввода-вывода. Значение, возвращаемое функцией getchar при достижении конца файла, обозначается символической константой EOF, которая определена в файле заголовков <stdio.h>. Тело функции main состоит из описаний и следующих за ними операторов. Рассмотрим более подробно отдельные конструкции. main( ) Описание функции main, которая является обяза- тельной начальной подпрограммой во всех Сопро- граммах. Int с, п; Описание двух целых переменных сип. п=1; Присваивание переменной п значения 1. (c=getchar( ))! = EOF Выражение c=getchar( ) представляет собой прис- ваивание. Присваивание можно также использовать как часть выражения. Его значением внутри рассмат-
5.1. Примеры программ на языке С 107 риваемого выражения является величина, присваивае- мая переменной с. Операция «!=»—это проверка на неравенство, поэтому ( ... )! = EOF истинно, если #lnclude <stdio.h> int main() { int c, n; n « 1; while ((c « getcharO) I- EOF) { printf("%o ", c); if (n++ >= 10) { n = 1; printf("\n"); }else[ printfC "); } } if (n 1“ 1) { /* завершить неполную строку *f printf("\n"); 1 return(O); } /♦ main */ Рис. 5.1 Упрощенный вариант команды восьмеричного дампа очередная прочитанная литера не является признаком конца файла. while (...){ ... } Вычисляется условие в круглых скобках и, если оно истинно, выполняется заключенное в фигурные скоб- ки тело цикла. Этот процесс повторяется до тех пор, пока условие не станет ложным. Тело цикла — это операторы, заключенные в фигурные скобки. printf("%o", с); Напечатать значение переменной с в восьмеричном формате % о. п++ Увеличить п на 1. Значение этого выражения — зна- чение п до прибавления 1. п++>* = 10; Значение переменной п сравнивается с числом 10 и возвращается результат сравнения — истина
108 Гл. 5. Язык, программирования С или ложь. После выполнения сравнения значение п увеличивается на 1. if (•••) {•••} Вычисляется условие в круглых скобках и, если оно истинно, выполняются заключенные в фигурные скоб- ки операторы. В противном случае выполняется опе- ратор, следующий за else. 5.1.3. Средние расстояния В игре Star Trek знание среднего расстояния до звездной базы из каждого квадранта перед началом игры дает игроку некоторое преимущество. Для решения вопроса о том, разыгрывать ли дан- ную конкретную игру, полезно знать среднее расстояние от лю- бой точки сетки до всех остальных. Приведенная на рис. 5.2 программа печатает средние расстояния для сетки размером 9x9. • Конструкции фinclude и ^define интерпретируются С-препроцессором. Конструкция ^include уже рассма- тривалась в предыдущем примере. Она используется здесь для того, чтобы получить описание функции sqrt( ) из стандартной библиотеки математических фун- кций. Конструкция ^define представляет собой опи- сание константы. Везде, где в программе используется константа ХМАХ, она заменяется на 9. • Описание double average( ); объявляет функцию, возвращающую результат типа double. Это позволяет использовать функцию до ее опре- деления в программе. • Описание double sum = 0.0; объявляет вещественную переменную sum и присваивает ей начальное значение нуль. Все переменные должны быть описаны. Описание переменной должно предшество- вать первому ее использованию. • Оператор for for (х=0; х<ХМАХ; х++) { операторы эквивалентен оператору while
5.1. Примеры программ на языке С 109 #include <math.h> tfdefine ХМАХ 9 #define YMAX 9 double average(); int main() I int x, y; double sum - 0.0; for (x-0; x<XMAX; x++). { printf("°/od: ", x); for (у—0; у < YMAX; y++) ( double s=average(x, y); sum+=s; printf("°/o.2f ”, s); } printf("\n"); } printf("average=°/of\n", sum / (YMAX*XMAX)); j /♦ main */ double average(xO, yO) int xO, yO; { int x, y; double sum = 0.0; for (x-0; x<XMAX; x++) { for (y-0; у<YMAX; y++) { double d = (x-xO)*(x-x0)+(y-y0)*(y~y0); sum += sqrt(d); } } sum /= (XMAX*YMAX-1); return(sum); } /♦ average *! Рис. 5.2 Программа average
по Гл. 5. Язык программирования С х=О; while (х<ХМАХ) { операторы; х++> } Последовательность операторов в фигурных скобках циклически выполняется до тех пор, пока х не станет больше или равен ХМАХ. • Оператор вывода printf("%d: ", х); печатает х в виде десятичного целого и двоеточие с пробе- лом за ним. Первым параметром функции printf является цепочка литер, которая целиком выдается на печать, за исключением содержащихся в ней форматов. Форматы определяют, в каком виде вставляются в печатаемую строку оставшиеся параметры. Например, формат %d заменяется на десятичное представление последующего целого параметра. В приведенной программе исполь- зуются также форматы %f и %.2f. Первый печатает чис- ло с плавающей точкой по подразумеваемому по умолча- нию формату, тогда как во втором случае число с пла- вающей точкой выводится с двумя цифрами после деся- тичной точки. Формат %6.2f определяет поле длиной в 6 позиций, причем две позиции отводятся под дробную часть. • Оператор double s = average(x, у); описывает переменную s и присваивает ей начальное значение, получаемое в результате выполнения функции average с параметрами х и у. • Оператор sum + = s; объявляет операцию присваивания += и эквивалентен оператору sum = sum-f-s; Аналогично в функции average оператор sum /= (XMAX*YMAX— 1); эквивалентен оператору sum = sum/(XMAX*YMAX—1); I В определении функции average описываются тип ее
5.2. Описание языка 111 результата — double и два используемых ею параметра — хО и уО. Оператор return(sum); выполняет возврат из функции с результатом sum. Компиляция программы average выполняется командой сс —о average average, с —1m Параметр —1m требует от сс загрузить библиотеку математиче- ских подпрограмм, содержащую функцию sqrt. 5.2. ОПИСАНИЕ ЯЗЫКА В этом разделе описываются элементы языка программирова- ния С: сначала идентификаторы, константы, базисные типы дан- ных, выражения, затем управляющие операторы, описания функ- ций, массивы и указатели и, наконец, классы памяти. 5.2.1. Лексика Комментарии и пробелы Комментарии начинаются с литер /* и заканчиваются литерами */. Внутри комментария литеры /* не интерпретируются, поэтому вложенные комментарии не допускаются. Для разделения иден- тификаторов и зарезервированных слов используются также пробелы, табуляция и литеры новой строки. Идентификаторы Идентификаторы используются для обозначения переменных, меток и функций. Идентификатор представляет собой последо- auto else int typedef break entry long switch case enum register union char extern return unsigned continue float short void default for sizeof while do goto static double if struct Рис. 5.3 Зарезервированные слова языка С
112 Гл. 5. Язык программирования С вательность литер, состоящую из букв и цифр, начинающуюся с буквы. Литера подчеркивания также считается буквой. Пропис- ные и строчные буквы считаются различными. Длина внешних идентификаторов (используемых более чем в одном программном файле) часто ограничивается 6, 7 или 8 ли- терами, а прописные и строчные буквы в них могут не разли- чаться. Это зависит от конкретной реализации. Ряд идентификаторов зарезервирован под ключевые слова языка; их нельзя использовать в качестве имен переменных. Список зарезервированных ключевых слов приведен на рис. 5.3. В некоторых реализациях зарезервированы также слова asm и fortran. Базисные типы данных В языке С имеется несколько базисных типов данных. Коротко говоря, тип переменной определяет, как будет интерпретиро- ваться значение этой переменной. Значением литерной переменной может быть любая литера из множества допустимых литер. Литера представляется в виде целого числа в коде ASCII или EBCDIC. Имеется три целочисленных типа, которые могут отличаться размером: short int (короткое целое), int (целое) и long int (длин- ное целое). Под более длинные целые отводится память по край- ней мере такого же размера, как под более короткие. Целые без знака описываются как unsigned short int, unsigned int или unsigned long int. Если присутствует unsigned, то int можно опускать. Целые без знака подчиняются законам арифметики по модулю 2Л, где п зависит от реализации. Тип PDP 11/45 VAX 11/780 char 8 8 short int 16 16 int 16 32 long int 32 32 float 32 32 double 64 64 Рис. 5.4 Размеры объектов в С Числа с плавающей точкой обычной точности имеют тип float, а числа с плавающей точкой двойной точности имеют тип double. На рис. 5.4 приведены типичные размеры памяти (в битах),
5.2. Описание языка 113 отводимой под переменные базисных типов данных на ЭВМ PDP 11/45 и VAX 11/780. На обеих машинах литеры представ- ляются в коде ASCII. Константы Целочисленная константа представляет собой последователь- ность цифр. Константа является десятичной, если она не начи- нается с нуля; в противном случае константа считается восьме- ричной. Если последовательности цифр предшествуют литеры Ох или ОХ (0 — цифра нуль), то все последующие цифры считаются шестнадцатеричными, причем буквы a—f или A—F обозначают цифры от 10 до 15 соответственно. Восьмеричная, десятичная или шестнадцатеричная констан- та, значение которой превышает наибольшее для данной машины целое без знака, будет иметь тип long. Для обозначения длинных констант используется буква 1 или L, которая помещается непо- средственно за обычной константой. Например, 0L — это длин- ный нуль. Литерные константы заключаются в одинарные кавычки, например 'с'. Значением литерной константы является код литеры в соответствии с используемой в ЭВМ кодировкой. Ли- тера \ используется для представления ряда специальных литер, как показано ниже: Таблица 5.1 Литера Авторегистр новая строка горизонтальная табуляция возврат на шаг возврат каретки перевод формата обратная косая черта одиночная кавычка пусто - О //////// Любую литеру можно записать как \ddd, где d — восьмеричная цифра. Если за \ следует любая литера, отличная от упомянутых выше, то литера \ игнорируется. Текстовые константы (цепочки) записываются в виде после- довательности литер, заключенной в двойные кавычки: "..Л Внутри текстовой константы действуют те же соглашения о пред- ставлении специальных литер, что и для литерных констант. Цифра, следующая за литерой пусто, должна записываться как
114 Гл. 5. Язык программирования С \000d, где d — эта цифра. Кроме того, \" используется для представления самой литеры двойной кавычки, а комбинация \новая-строка игнорируется, что позволяет записывать длинные текстовые константы в нескольких строках. В конец текстовых констант автоматически вставляется литера пусто \0. Константы с плавающей точкой содержат либо десятичную точку, либо экспоненциальную часть, либо и то и другое вместе. Общая форма записи такой константы имеет вид целая-часть . дробная-часть е ± показатель-степени Все константы с плавающей точкой представляются в памяти как числа с двойной точностью. Ниже приведены примеры констант с плавающей точкой. 0.0 3.14159 ЗеЮ 5.2.2. Выражения и операции Выражения в языке С строятся из констант, идентификаторов, индексированных переменных, ссылок на структуры и объеди- нения и вызовов функций, соединенных знаками унарных и бинарных операций. Бинарные операции, если не оговорено особо, выполняются слева направо. Поэтому выражение а — b — с интерпретируется как ( а — b ) — с Бинарные операции можно разбить на несколько классов. Арифметические операции Бинарными арифметическими операциями являются +, —, * и /, а также операция % получения остатка от деления целых чисел. Если операндами операции деления / являются целые числа, то деление выполняется нацело; десятичная точка и вся дробная часть результата отбрасываются. При делении поло- жительных целых чисел частное округляется в меньшую сторону. Если же один из операндов отрицателен, результат деления зависит от реализации. Операции отношения Операциями отношения являются операции > (больше), >= (больше или равно), <= (меньше или равно) и < (меньше). Эти
5.2. Описание языка 115 операции возвращают значения 1, если условие выполнено, и О в противном случае. Операции сравнения на равенство Для сравнения на равенство используется операция = = , а для сравнения на неравенство — операция ! = . Эти операции воз- вращают 1, если результатом является истина, и 0 в противном случае. Операции сдвига Две операции << и >> позволяют сдвигать битовую шкалу (значение левого операнда) на п позиций влево или вправо соответ- ственно; п должно быть неотрицательным и не должно превышать длину значения в битах. Если сдвигаемая величина имеет знак, то при сдвиге вправо освобождающиеся разряды в зависимости от реализации заполняются или нулями, или единицами. Если же сдвигаемая величина — беззнаковая, то освобождающиеся раз- ряды всегда заполняются нулями. Поразрядные операции Имеются три поразрядные операции: &, I и Л. Операция & пред- ставляет собой поразрядное И, операция I — поразрядное вклю- чающее ИЛИ, а операция Л — поразрядное исключающее ИЛИ. Логические операции Логических операций две: && й ||. Логическая операция && (И) возвращает 1, если значения обоих операндов отличны от нуля, и 0 в противном случае. Операция && отличается от операции & тем, что правый операнд вычисляется только в том случае, если левый операнд не равен нулю. Логическая операция || (ИЛИ) возвращает 1, если значение хотя бы одного из операндов отлично от нуля, и 0 в противном случае. И здесь вычисления выполняются слева направо; второй операнд не вычисляется, если первый отличен от нуля. Операция запятая ВЫр! , ВЫр2 Вычисляются выражения вырх и выр2. Результат вычисления выр! отбрасывается. Результатом операции является 'значение выр2. Эту операцию удобно использовать в случае, когда вы- числение выр! может повлиять на результат вычисления выр2,
116 Гл. 5. Язык программирования С а синтаксис языка требует задания только одного выражения. Например: while ( reserv+ + , word()! = '\n' ) ... В этом примере значение переменной reserv используется функцией word. Операции присваивания Присваивание имеет вид 1-значение == г-значение В качестве 1-значения можно использовать следующие конструк- ции: идентификатор * выражение выражение . поле выражение —> поле выражение [ выражение ] ( 1-значение ) а r-значением может быть любое выражение языка С. Вычисля- ются 1-значение и r-значение, и r-значение присваивается пере- менной, определяемой 1-значением. При присваивании может произойти усечение значения. На- пример, в контексте описаний int i; char с; при присваивании с = i; возможна потеря старших разрядов значения i. Это иногда называется усечением. Верификатор программ lint обнаруживает такие потенциальные ошибки. Операция + = определена таким образом, что х +== у эквивалентно х = х + У- Результатом выполнения операции присваивания является значение, присвоенное переменной в ле- вой части. Операция += является одной операцией присваивания из множества, в которое входят ==, —=, *=, /=, % = , >> = , << = , &=, Л= и | = . Эти операции определяются следующим образом: х ор= у эквивалентно х = х ор у, за исключением того, что выражение х вычисляется только один раз. Условная операция Условная операция ?: имеет вид выр ? выр! : выр2
5.2. Описание языка 117 Если значение выр не равно 0, результатом операции является значение выр!, в противном случае — значение выр2. Если выр! и выр2 имеют различные типы, выполняется преобразование ти- пов, однако арифметические и неарифметические выражения рекомендуется не смешивать. Условное выражение не может быть левой Частью операции присваивания, так как его результат не является 1-значением. Унарные операции Список унарных операций приведен в табл. 5.2. Буква е означает выражение, a v — 1-значение. Таблица 5.2 Операция Описание *е &V —е !е ~е -H-V ' v v-|—j- v (тип)е sizeof(e) sizeof(THn) Значение переменной с адресом е. Используется при работе с указателями. Адрес v. Используется при работе с указателями. Унарный минус. Логическое отрицание. Поразрядное дополнение е. Эквивалентно v = v-(-l. Эквивалентно v=v— 1. Значение v увеличивается на 1. Значением выражения является значение v до увеличения. Значение v уменьшается на 1. Значением выражения яв- ляется значение v до уменьшения. Преобразование значения е к заданному типу. Количество байтов, занимаемых значением е. Количество байтов, занимаемых объектами указанного типа. Преобразование типов значений Явное преобразование значений от одного типа к другому вы- полняется при помощи операции преобразования типа. Преобра- зование типов выполняется автоматически, когда выражение является параметром функции или операндом. Правила преоб- разования типов арифметических значений перечислены ниже. Указатели рассматриваются в разд. 5.2.5. • Значения типов char и short преобразуются к типу int. Литера рассматривается как величина со знаком, и при преобразовании ее в целое происходит распространение знакового разряда. Там, где это уместно, следует исполь-
118 Гл. 5. Язык программирования С зовать короткие целые без знака (unsigned short). Анало- гичным образом значения типа float преобразуются к типу double. • Далее если один из операндов имеет тип double, то и дру- гой преобразуется к типу double. • Иначе если один из операндов имеет тип long, то и дру- гой преобразуется к типу long. • Иначе если один из операндов имеет тип unsigned, то и другой преобразуется к типу unsigned. • Иначе оба операнда имеют тий int. Проверка типов параметров при вызове функций не выполняется. Тем не менее в этом контексте значения типов short или char всегда преобразуются к. типу int, а значения типа float — к типу double. Рекомендуется убедиться в том, что необходимые преоб- разования типов действительно имеют место. Например, выраже- ние sin(3) ошибочно, так как параметр функции sin должен иметь тип double, а не int. Правильное обращение к этой функции имеет вид sin((double)3) или sin(3.0). Приоритеты и порядок выполнения операций На рис. 5.5 приведена сводка всех операций в порядке убывания их приоритетов. Операции, перечисленные в одной строке, имеют одинаковый приоритет. Операции с одинаковым приоритетом Рис. 5.5 Приоритеты операций в С
5.2. Описание языка 119 обычно выполняются слева направо. Однако на рисунке отме- чены исключения из этого правила. Если возникают сомнения или выражение слишком сложное, можно воспользоваться круг- лыми скобками. Порядок вычисления операндов в выражении и параметров в вызове функции в языке С не определен. Поэтому нельзя пола- гаться на то, что побочные эффекты при обращении к функциям имеют место в определенном порядке. Более того, операнды ком- мутативных операций * + & | л могут быть переупорядочены компилятором произвольным об- разом, поэтому выражение а + b + с может вычисляться как (а+Ь)+с или как а+(Ь+с). 5.2.3. Управляющие операторы В языке С имеется ряд управляющих конструкций. Операторы, следующие друг за другом, выполняются последовательно. Для ветвления предоставляются операторы If и switch, а для повто- рения — операторы for, while и do. Условный оператор Условный оператор имеет вид if (выражение) оператор] else оператор2 Вычисляется выражение и, если оно отлично от нуля, выпол- няется оператор!, в противном случае — оператор2. Как опера- тор!, так и оператора могут быть блоками или составными опера- торами вида { необязательные-описания операторы } Часть else можно опускать: if (выражение) оператор! Оператор if (выражение]) if (выражение2) оператор] else оператору
120 Гл. 5. Язык программирования С можно интерпретировать двояко, относя else-часть либо к пер- вому, либо ко второму if. В С else связывается с ближайшим предшествующим if. Рекомендуется избегать такой формы записи и использовать составной оператор, как показано ниже: if (выражение) { Г Оператор switch Оператор switch (переключатель) обеспечивает ветвление по не- скольким направлениям и имеет вид switch (выражение) оператор Вычисляется выражение, значением которого должно быть це- лое. Оператор обычно является составным оператором, в котором некоторые операторы могут быть помечены следующим образом: case константное-выражение: Константное-выражение — это целая или литерная константа (например, 1 или 'А') либо выражение, составленное из таких констант с использованием операций: -----* / о/о + __ << >> & Л I ?: < <= > >= == ! = Оператор, помеченный константным-выражением, выполняет- ся, если целое значение выражения в switch равно значению этого константного-выражения. Если же значение выражения не равно ни одному из константных-выражений, управление передается на оператор default (если он есть). Меткой default: можно поме- чать не более одного оператора переключателя. После выполнения альтернативы будет выполняться следую- щая за ней альтернатива. Для выхода из переключателя исполь- зуется оператор break; Управление будет передано на оператор, следующий за переклю- чателем. Циклы Цикл while имеет вид while (выражение) оператор
5.2. Описание языка 121 Вычисляется выражение и, если его значение отлично от нуля, выполняется оператор. Этот процесс повторяется до тех пор, пока значение выражения не станет равным 0. Оператор в цикле while может быть составным. Оператор for удобен для записи циклов, в которых задаются начальная установка, шаг и условие выхода. Он имеет следую- щий общий вид: for (выражение!*, выражение^; выражение^ оператор Формально этот оператор эквивалентен последовательности выражение!; while (выражение^) { оператор; выражение3; } Операторы for и while проверяют условие завершения в начале цикла. Цикл do...while проверяет условие после выполнения оператора — тела цикла. Таким образом, в цикле do оператор while (выражение); сначала выполняется оператор, а затем проверяется значение выражения. Для выхода из цикла до его окончания используется оператор break. Оператор break; вызывает немедленное прекращение выполнения самого внутрен- него из объемлющих его циклов for, while или do или переклю- чателя switch. Оператор break обеспечивает выход только из од- ного объемлющего цикла. Для выхода сразу из нескольких вло- женных циклов используется оператор goto, например: goto error; Здесь error — метка 'некоторого оператора: error: оператор Метка имеет тот же вид, что и идентификатор. Она должна быть определена в теле той же функции, в которой находится оператор goto. Таким образом, переход из одной функции в другую не до- пускается. Используя подпрограммы setjmp и longjmp, описан- ные в разд. 5.2.9, можно совершать «нелокальные» переходы»
122 Гл. 5. Язык программирования С Другим оператором, который позволяет избежать использо- вания goto, является оператор continue; вызывающий немедленное прекращение выполнения тела цикла и начало следующей итерации. Действие этого оператора эквива- лентно переходу на конец тела цикла, но не за пределы самого цикла. 5.2.4. Функции Функции в языке С аналогичны подпрограммам в Алголе 68 и процедурам в PL/I, Телу программы можно присвоить имя, по которому эту программу можно вызывать отовсюду и столько раз, сколько потребуется. Функцию можно определить, даже если она используется только один раз, просто для того, чтобы вы- делить некоторое действие. Каждая функция должна быть определена. Определение функции включает в себя тип результата, имена и типы формаль- ных параметров и тело функции. Если функция используется в программе до того, как она определена, ее можно описать, ука- зав тип результата и имя функции. В разд. 5.1.3 приведено такое описание для функции average. В противном случае требуется только определение функции. Определение функции имеет следующий общий вид: тип-результата имя-функции (имена-параметров) оп исан ия- п араметро в { тело-функции } Функции, не возвращающей никакого значения, соответствует тип-результата void. Если тип-результата опущен, подразуме- вается int. Кроме того, считается, что функции, которые исполь- зуются в программе до их описания или определения, также воз- вращают целый результат. Тело функции начинается с описаний локальных переменных. Среди последовательности операторов может встретиться опера- тор возврата return. Если нет ни одного оператора return, функ- ция заканчивается после выполнения последнего оператора из ее тела. Оператор возврата вида return; выполняет возврат из функции в точку вызова, не возвращая никакого значения. Если в вызывающую программу значение должно быть возвращено, следует использовать оператор return выражение;
5.2. Описание языка 123 Вызовы функций Вызов функции либо может быть оформлен в виде отдельного оператора, либо может быть частью выражения. Если вызов функции оформлен как отдельный оператор, возвращаемое функ- цией значение теряется. Если функция используется в выраже- нии, она должна возвращать значение. Параметры функций передаются по значению. Каждый пара- метр вычисляется, и копия его значения становится доступной вызываемой функции. Это позволяет внутри тела функции исполь- зовать формальные параметры в качестве локальных перемен- ных, не изменяя значений соответствующих переменных в вызы- вающей программе. В некоторых функциях, например в printf, допускается произвольное число параметров различных типов. Однако printf предполагает, что количество параметров и их типы согласуются с форматом, заданным в качестве первого параметра. Если это не так, то полученные результаты могут быть непредсказуемы. Проверка соответствия типов формальных и фактических параметров не выполняется. Однако такие проверки осуществ- ляет верификатор программ lint. Оператор return позволяет возвратить только одно значение. Результаты можно также возвращать через параметры, исполь- зуя указатели. Если функция не возвращает результата, ее вызов следует оформлять в виде отдельного оператора. Функции могут быть рекурсивными и даже взаимно рекурсивными. Однако определе- ния функций не могут быть вложенными. 5.2.5. Массивы и указатели Массивы Массив — это набор переменных одного типа. Так, описание int а[10]; вводит 10 целочисленных переменных alOl, аШ, ... , а[91. Номер элемента массива задается в квадратных скобках. Переменные с индексами, такие, как аШ или af2*i+4], являются 1-значениями и могут использоваться в выражениях, передаваться в качестве параметров функций. Им можно присваивать значения, напри- мер: a[i]=4; Аналогично описание char с[100];
124 Гл. 5. Язык программирования С вводит 100 литерных переменных с[0], с[1], , с[99]. Можно описывать многомерные массивы, например: int matrix! 10][ 100]; Тем самым создаются элементы матрицы matrix[i][j] для 0^i^9, 0<j<99. Границы массивов являются константами и фиксируются на этапе компиляции. Для определения констант—границ массй- вов обычно используется С-препроцессор. Массивы можно передавать в качестве параметров функций. Однако в отличие от языка Паскаль при описании формальных параметров границы для одномерных массивов можно опускать, например: int length(buffer, size) char buffer!], size; { int i=0; while (i<size && (butfer[i]! = * ') ) { i++; } retum(i); } При вызове функции для самого параметра-массива память не отводится; передаваемым значением является указатель на пер- вый элемент массива. В приведенном примере предполагается, что фактическое количество элементов в массиве buffer передается как отдельный параметр size. Один из способов вызова функции length, гарантирующий правильность обращения, выглядит так: length(array, sizeof(array)) При описании формального параметра-массива может быть опущена только последняя граница; поэтому двумерный массив описывается, например, так: void invert(matrix) int matrix! 10][]; Цепочки литер представляют собой массивы литер. По со- глашению, цепочки в С заканчиваются литерой пусто \0 (об- ратная косая черта, за которой следует нуль). Наличие такой литеры существенно упрощает алгоритм копирования цепочек. Например, функция void copystr (strl, str2) char strl[], str2[]; { int i;
5.2. Описание языка 125 for (i=0; (str2[i]=strl[i]) != '\0'; i++) » } копирует цепочку strl в str2. Тело цикла for в этой функции является пустым оператором. Копирование выполняется при помощи присваивания str2[i] =str 1 [i], которое является побоч- ным результатом вычисления условия выхода из цикла. Указатели Обработку цепочек можно упростить, если использовать указа- тели. Указатель — это переменная, значением которой является адрес другой переменной. Пусть рп — указатель на переменную п, содержащую число 3. Схематически это можно изобразить так, рп п Рис. 5.6 Указатели в языке С как показано на рис. 5.6. Над каждым из прямоугольников ука- зано 1-значение (имя переменной). Внутри прямоугольника по- мещено значение соответствующей переменной. Для описания указателей используется операция косвенной адресации *. Например, указатель рп описывается как int *рп; Обоснованием такого рода описаний является то, что *рп — это значение целого типа, и такое сочетание соответствует исполь- зованию в выражениях операции косвенной адресации *. Унарная операция & позволяет получить адрес переменной. Например, значением выражения &х является адрес перемен- ной х. Оба оператора *рп = п; и рп = &п; имеют смысл. В результате выполнения второго из них рп будет указывать на значение переменной п (первоначально равное 3). Однако присваивание рп == п; ошибочно, так как в рп вместо требуемого адреса целой пере- менной записывается значение 3.
126 Гл. 5. Язык программирования С Между указателями и массивами существует тесная связь. В контексте описаний char *ср; char buffer! 100]; операторы ср = &buffer[O]; и ср = buffer; эквивалентны. Значением указателя ср становится адрес нуле- вого элемента буфера. Аналогично операторы ср = &buffer[4]; и ср = buffer+4; присваивают указателю ср адрес четвертого элемента массива. Указателю на литеры можно присваивать текстовую кон- станту, поскольку значением такой константы является адрес ее первого элемента. Например: char *ср = В общем случае можно складывать указатель р с целым чис- лом i; результатом будет адрес i-ro элемента относительно эле- мента массива, на который указывает указатель р. Так, если ср указывает на bufferfO], то выражение bufferti] эквивалентно вы- ражению *(cp+i). Результат является 1-значением. Вычитание целого из указателя определяется аналогичным образом. Более того, можно вычитать друг из друга два указа- теля на объекты одного и того же типа. Результатом является целое, равное числу элементов между ними. Обычно это указа- тели на элементы одного массива. Для указателей определены операции увеличения и умень- шения. Операция ср++ увеличивает значение ср на размер объекта, на который этот указатель указывает. Значение ср используется в выражении, а затем увеличивается. Аналогично определяются операции ср------, ++ср и-------ср. Примеры Использование указателей иллюстрируют следующие два при- мера. Функция swap определяется так: swap(x, у) int *х, *у; int temp;
5.2. Описание языка 127 temp = *х; *х = *у; *у = temp; } Обращение к ней имеет вид swap(&a, &b); где а и b были ранее определены как int а, Ь; Эта функция меняет местами значения двух целых переменных. Второй пример демонстрирует еще один способ копирования цепочек: void copy(strl, str2) char *strl, *str2; { while (*strH—h = »str2++) J } Эта функция более эффективна по сравнению с ранее рассмотрен- ной функцией копирования цепочек copystr. Проверка на '\0' преднамеренно опущена, так как это условие проверяется по умолчанию. 5.2.6. Структуры и объединения Структуры позволяют объединять вместе группу взаимосвязан- ных данных. Например, характеризуя человека, можно исполь- зовать такие атрибуты, как имя, возраст и вес. Так, конструкция struct { char name [30]; int age; float weight; } someone; определяет, что переменная someone — это структура, состоящая из трех членов (или полей) с именами name (имя), age (возраст) и weight (вес). Для обращения к членам структуры используется операция выделения поля, обозначаемая точкой. Например, операторы someone, age = 21; или copystr("Spike", someone.name); присваивают значения полям age и name соответственно.
128 Гл. 5. Язык программирования С Чтобы не описывать структуру повторно при каждом ее ис- пользовании, для нее можно ввести аббревиатуру. Например, описание struct person { char name[30]; int age; float weight; }; задает для структуры ярлык person, который эквивалентен типу struct{...}, введенному ранее. Используя этот ярлык, можно переписать приведенное выше описание так: struct person someone; Альтернативный способ введения аббревиатуры для типа данных состоит в использовании оператора определения типа typedef. Этот оператор имеет тот же вид, что и описания, за ис- ключением того, что он начинается с ключевого слова typedef. Описанный идентификатор является не переменной, а именем для типа. Например, оператор typedef struct{...} person; вводит имя person для обозначения типа struct{...}. Описание struct { char name[30j; int age; float weight; } someone; теперь можно заменить на person someone; Единственными операциями, которые могут применяться к струк- турам, являются операции , и &. В некоторых версиях языка С разрешается присваивание структур и передача их как парамет- ров при вызове функций. Однако обычно со структурами рабо- тают через указатели. При добавлении к указателю на структуру или вычитании из него целого числа размер структуры учиты- вается автоматически. Пусть ptr — указатель на структуру типа person. Тогда опе- рация ptr —> age
5.2. Описание языка 129 определяется как *(ptr . age ) Результат является 1-значением, соответствующим полю струк- туры типа person. Можно определять структуры, ссылающиеся на себя. На- пример, вершину дерева можно определить так: struct node { struct node «left; struct node «right; int vai; }; В определении структуры node используется сама структура node, однако в структуре содержится не член типа node, а ука- затели left и right на объекты типа node. Указатели на такие структуры широко используются, и для каждой такой ранее определенной структуры удобно определить nodeptr как указа- тель на структуру типа node: typedef struct node «nodeptr; и использовать его наряду с самим определением структуры. Когда требуется узнать размер памяти, занимаемой данной структурой, используется sizeof. Выражение sizeof(o6beKT) или sizeof(THn) возвращает число байтов, занимаемых за- данным объектом или объектом заданного типа. Для создания бинарных деревьев с вершинами типа node требуются средства распределения памяти. В С выделением и освобождением памяти управляет сам программист. В его рас- поряжении имеются две подпрограммы. Подпрограмма malloc(n) выделяет п последовательных байтов памяти и возвращает адрес первого из них, например: struct node «ptr; ptr — malloc(sizeof(struct node)); Поскольку возвращаемый malloc указатель всегда отличен от нуля, значение нуль используется для обозначения того, что ука- затель не указывает ни на какой объект. Такое значение указа- теля определяется, например, следующим образом: ^define nilnode ((nodeptr)O) Память возвращается в свободный пул оператором free(ptr); Указатель ptr должен иметь значение, полученное ранее в ре- зультате обращения к функции malloc. 5 С. Баурн
130 Гл. 5. Язык программирования С В приводимом ниже примере показано использование струк- тур, указателей и подпрограммы распределения памяти malloc. Используются определения, введенные выше для структуры node. Функция new возвращает указатель на вновь созданную вершину дерева, содержащую целочисленное значение, переда- ваемое в качестве параметра: nodeptr new(v) int v; nodeptr n * (nodeptr)malloc(sizeof(struct node)) n->left - n-> right - nilnode; newval «» n->val “ v; return(n); 1 /♦ new Переменной newval присваивается значение вершины, создан- ной последней; эта переменная используется ниже. Другая требуемая функция — это печать дерева. Она опре- деляется следующим образом: void print(n) nodeptr n; { if(n 1- nilnode) { print(n->left); printfC%d\n"1 n->vai); print(n-> right); ) } /♦ print *f. Остальная часть программы приведена на рис. 5.7. Она состоит из функции split, которая вставляет новую вершину в упорядо- ченное дерево с заданным корнем, и функции main, которая вы- полняет все необходимые организационные действия. Особен- ностью работы функции split является то, что недавно вставлен- ные вершины находятся вблизи от корня дерева. Объединения Переменные используются для хранения значений определенного типа. Например, переменной i, описанной как int, нельзя при- своить значение типа float. В программе, требующей значитель- ного объема памяти, часто необходимо уметь использовать одну и ту же переменную для хранения значений различных типов. Для этой цели имеется тип union (объединение).
5.2. Описание языка 131 Например, описание union number { int i; float f; } x; int newval; void split(ladr, radr, tree) nodeptr ♦ladr, *radr; nodeptr tree; if( tree == nilnode ) { ♦ ladr - *radr - nilnode; ) else if(newva1 > tree->val) { ♦ ladr — tree; split(&tree-> right, radr, tree-> right); }else( ♦ radr - tree; split(ladr, &tree->left, tree->left); ) } /♦ split */ main(argc, argv) int argc; char *argv[]; { nodeptr root-new(O); while(argc— > 1) { nodeptr r — new(atoi(argv[1])); split(&r->left, &r->right, root); root - r; argv++; } print(root); } /♦ main ★/ Рис. 5.7 Древовидная сортировка 5*
132 Гл. 5, Язык программирования С отводит под переменную х память, которая достаточна для хра- нения значений как типа int, так и типа float. В каждый момент времени в х может храниться только одно значение. Если х содержит целое значение, то оно доступно как x.i, если же в х находится значение типа float, то оно доступно как x.f. Сам язык не следит за тем, значение какого типа хранится в х в данный момент,— это дело программиста. Для этой цели можно использовать целочисленную переменную или же можно попытаться получить тип х и при помощи какой-либо другой информации, доступной при написании программы. 5.2.7. Препроцессор языка С При компиляции С-программы автоматически вызывается пре- процессор. Строки, начинающиеся литерой *, интерпретируются препроцессором. Программисту предоставляется несколько воз- можностей. Определение констант и макроподстановка Строка вида #define идентификатор лексема... вызывает в оставшейся части программы замену всех вхождений идентификатора на одну или более лексем. Лексема — это иден- тификатор, ключевое слово, константа, цепочка литер, знак операции или знак пунктуации (такой, как ;). Например, опре- деление #define BUFSIZ 512 позволяет использовать в программе идентификатор BUFSIZ, все вхождения которого будут заменяться на 512. Внутри тек- стовых констант замена на определяемые константы не делается. Можно также определять макро, которые могут иметь пара- метры. Макроопределение ^define идентификатор(парп пар2, ... , парп) цепочка-лексем вызывает замену всех последующих вхождений выражения идентификатор(р1,р2,...,рп) на цепочку-лексем, в которой napi заменяется на рп пар2 — на р2, ... , парп — на рп. Список пара- метров может быть пуст, однако макропрепроцессор проверяет, что число передаваемых параметров соответствует числу пара- метров макро. Макроопределения иногда используются вместо определений функций, обычно из соображений эффективности. В отличие от параметра функции параметр макроопределения вычисляется
5.2. Описание языка 133 при каждом вхождении в макроопределение. Поэтому если имеет- ся макроопределение ^define max(a, b) ((a)>(b) ? (a) : (b)) то макровызов max(i++, j++) увеличит i или j на 2. Параметры в этом макроопределении заключены в скобки для того, чтобы избежать возможной двусмысленности. Так как макроопределения обрабатываются препроцессором, то из-за различных приоритетов операций могут возникнуть неожидан- ные результаты. Например, если убрать все скобки в определе- нии, приведенном выше, то расширением max(p?q:r,s) будет выражение p?q:r>s?p?q:r:s которое интерпретируется в С как ( р ? q : ( (r>s) ? (p?q:r) : s ) ) Включение файлов Используя конструкцию Ф include "имя-файла" можно включить в программу содержимое другого файла. Пре- процессор заменяет такую конструкцию на содержимое всего указанного файла. Обычно включаемый файл содержит некото- рый набор определений. Файл ищется сначала в том оглавлении, где находится исходная С-программа, а если его там нет, про- сматривается ряд стандартных системных оглавлений. Конструк- ция Фinclude <имя-файла> также заменяется содержимым указанного файла, однако файл ищется только в стандартных системных оглавлениях; в оглав- лении, содержащем исходную С-программу, поиск не произво- дится. Условная компиляция Условные конструкции позволяют компилировать или не компи- лировать часть программы в зависимости от выполнения неко- торого условия. Обычно это зависит от того, какая ЭВМ исполь- зуется или находится ли программа в стадии отладки.
134 Гл. 5. fl зык программирования С Конструкция для условной компиляции имеет вид #if... часть 1 else часть 2 *endif или #if... часть 1 #endif Условие может принимать одну из следующих форм: * if константное-выражение Проверяется значение константного выражения (разд. 5.2.3) и, если оно равно нулю, компилируется (включается) последую- щий текст. # if def идентификатор Последующий текст компилируется, если идентификатор уже был определен для препроцессора в конструкции ^define. 4j=ifndef идентификатор Последующий текст компилируется, если идентификатор в дан- ный момент не определен для препроцессора. Конструкция =fcundef идентификатор исключает идентификатор из списка определенных идентифика- торов. В некоторых реализациях имеется ряд предопределенных идентификаторов. Их рекомендуется использовать только для определения типа машины, на которой будет выполняться ком- пиляция. Такими идентификаторами являются: geos ibm interdata mert os pdpll tss unix vax 5.2.8. Структура языка Язык С имеет блочную структуру, похожую на блочную струк- туру языка Алгол 60. Однако он отличается от Алгола в одном важном аспекте: описания функций не могут быть вложенными. В С блок представляет собой последовательность описаний и операторов, заключенную в фигурные скобки. Если описания отсутствуют, такая конструкция называется составным операто-
5.2. Описание языка 135 ром. Внутри блоков или составных операторов отдельные опе- раторы сами могут быть блоками или составными операторами. В языке С используются обычные правила для области дейст- вия, характерные для языков с блочной структурой. Описание переменной х во внутреннем блоке отменяет до конца текущего блока действие любого описания переменной х, имеющегося вне данного блока. В этом случае к переменной, описанной вне блока, можно все же обратиться косвенно, например через ука- затель. Спецификации класса памяти определяют требования к па- мяти и область действия переменных. В языке С существуют несколько таких спецификаций: auto, static, extern, register и typedef. Они уточняют описания, например: static int а[20]; или auto char с; Из них typedef называется спецификатором класса памяти только для удобства — он не связан с отведением памяти. Кроме того, описание register аналогично описанию auto. Оно указывает компилятору на то, что из соображений эффективности соответст- вующие переменные следует поместить в регистры. На каждой машине имеется ограниченное число регистров, и их можно ис- пользовать только под переменные определенных типов, напри- мер под целые или указатели. В описании может быть задано не более одного спецификатора класса памяти. Если спецификатор класса памяти отсутствует, то подразумевается auto внутри определения функции и extern вне определений функций. Рассмотрим сначала внешние переменные. Если функция ис- пользует глобальную переменную, эта переменная должна быть внешней (extern). Ее следует определить вне этой функции (возможно, в другом файле). При определении переменной под нее отводится память. Эту переменную можно также описать 'Внутри функции, например: char Iine[80j; main() extern char line []; } Переменные, не являющиеся внешними, будем называть вну- тренними. Каждая переменная, локальная для некоторой функции, на-
136 Гл. 5. Язык программирования С зывается автоматической (динамической) переменной и имеет класс памяти auto. Для таких переменных память обычно отво- дится в стеке. Автоматические переменные создаются при входе в функцию и уничтожаются при выходе из нее. Поскольку время жизни таких переменных ограниченно, следует соблюдать осто- рожность при работе с указателями на них. Наконец, существует класс памяти static. Статические пере- менные могут быть как внешними, так и внутренними. Внутрен- ние статические переменные, т. е. те, которые описаны внутри функций, аналогичны собственным (own) переменным Алгола 60. В отличие от автоматических переменных значения статических внутренних переменных сохраняются от одного вызова функции к другому. Внешние статические переменные описываются вне функций. Областью их действия является вся оставшаяся часть файла, в котором они описаны; они недоступны, однако, ни в каком другом файле. Функцию можно описать как статическую, тем самым ограничив ее область действия файлом, содержащим определение функции. Это используется для ограничения до- ступности функции. В описаниях переменных может быть задана их инициали- зация. • Статические и внешние переменные по умолчанию ини- циализируются нулем. • Все выражения, которые используются для инициализа- ции статических и внешних переменных, должны быть константными выражениями или же выражениями, кото- рые сводятся к адресу ранее определенной переменной плюс или минус некоторое константное выражение. • Автоматические и регистровые переменные могут быть инициализированы произвольными выражениями, ко- рые могут содержать константы, а также ранее опреде- ленные переменные и функции. • Нельзя инициализировать автоматические массивы, струк- туры и объединения. Примеры инициализации переменных: int prime [] = {2, 3, 5, 7}; Так как границы массива не указаны, его размер определяется из размера заданной совокупности констант. Таким образом, создаются переменные prime [0], prime [3]. char messaged = ''You have mail.\n"; Заданная цепочка литер инициализирует массив message и определяет его границы. Заметим, что в конец массива добавляется литера '\0'.
5.2. Описание языка 137 int table [41(3] = { {2, 3, 5 }, { 7, 9, 11 }, { 13, 17, 19 }, { о, О, 0 } }; 5.2.9. Библиотека стандартных программ языка С Стандартная библиотека ввода-вывода Функции из стандартной библиотеки ввода-вывода (stdio) предоставляют пользователям эффективные средства ввода-вы- вода. Для чтения и записи литер используются функции getc и putc, реализованные в виде макроопределений. Обращения к подпрограммам более высокого уровня gets, fgets, scant, fscanf, tread, puts, fputs, printf, fprintf, fwrite можно произвольным об- разом сочетать с выполнением операций более низкого уровня getc и putc. Файл вместе с предоставляемыми средствами буферизации называется потоком. Поток описывается как указатель на пере- менную предопределенного типа FILE. Функция fopen инициа- лизирует поток и возвращает указатель на него. Этот указатель будет идентифицировать поток во всех последующих операциях. Имеется 3 обычно всегда открытых потока с постоянными ука- зателями, которые описаны в файле заголовков и связаны со стандартными файлами ввода-вывода: stdin Стандартный файл ввода. stdout Стандартный файл вывода. stderr Стандартный файл диагностики. Константа NULL означает отсутствие потока. Целочисленные функции, работающие с потоками, возвра- щают целую константу EOF при достижении конца файла или в случае ошибки. Константа EOF определена в файле заголовков <stdio.h>. Этот файл заголовков необходимо включать в любую программу, использующую стандартный пакет ввода-вывода. Из соображений эффективности стандартный пакет ввода-вы- вода по умолчанию буферизует одну строку вывода на терминал и пытается делать это прозрачным образом, выводя накопленную строку всякий раз, когда требуется чтение из. стандартного файла ввода. Прозрачность удается сохранить почти всегда, однако такая буферизация может вызвать неправильное функциониро- вание программ, которые используют стандартную библиотеку ввода-вывода и в то же время сами читают файл стандартного ввода при помощи системной функции read. -
138 Гл. 5. Язык программирования С Функция printf Функция printf уже встречалась в разд. 5.1.2. Она форматирует вывод в соответствии с управляющей цепочкой. Допускаются следующие форматы: % с Литера %d Десятичное целое. %f Число с плавающей точкой. %о Восьмеричное целое. %s Цепочка литер. %х Шестнадцатеричное целое. % % Литера %. Перед буквой, обозначающей любой числовой формат, может стоять буква 1, которая указывает на тип long. Формат также мо- жет включать спецификации размера поля. Знак минус означает, что поле выравнивается по левому краю. Число, следующее за литерой %, определяет минимальный размер поля, отводимого под выводимое значение. Может быть задано и второе число, отделенное от первого точкой; оно определяет максимальную длину поля. Рассмотрим несколько примеров использования форматов: % 2d Напечатать десятичное число, дополнив его при не- обходимости пробелами слева до минимального раз- мера в две литеры. %—3d Напечатать десятичное целое, дополнив его при необходимости пробелами справа до минимального размера поля в три литеры. %. 12s Напечатать цепочку не более чем из 12 литер. % Id Напечатать длинное целое. Модификатор 1 приме- няется ко всем числовым форматам. Функция scanf Функции форматного вывода printf соответствует функция фор- матного ввода scant. В scant формат задается аналогичным об- разом, однако оставшиеся параметры являются адресами пере- менных, куда будут помещены преобразованные значения. Пробелы обычно игнорируются, а любая литера-константа в уп- равляющей строке сопоставляется с соответствующей вводимой литерой. Например, double d; scanf('%ir, &d); читает в переменную d значение константы с плавающей точкой из файла ввода.
5.2. Описание языка 139 Другой пример. Если вводимая строка имеет вид 25 54.32Е—1 thompson то при обращении int i; float х; char name[50]; scanf("%d%f%s", &i, &x, name); переменной i будет присвоено значение 25, x — значение 5.432, a name будет содержать цепочку thompson\0. Если же вводимая строка имеет вид 56789 0123 56а72 то при тех же описаниях в результате обращения scanf("%2d%f%*d%[1234567890]", &i, &х, name); переменной i будет присвоено значение 56, переменной х — значение 789.0, число 0123 игнорируется, а в name будет поме- щено 56\0. Последующий вызов getchar вернет литеру а. Функция scanf возвращает число прочитанных элементов; проигнорированные и неверные элементы данных не учитывают- ся. При достижении конца файла возвращается EOF. Функции для работы с цепочками Пользователю предоставляется полный набор функций для ко- пирования и сравнения цепочек. Эти функции описаны в прило- жении 3. Обработка ошибочных ситуаций В библиотеке стандартных подпрограмм имеются две подпро- граммы — setjmp и longjmp, которые можно использовать для обработки ошибочных ситуаций. Функция setjmp описывается следующим образом: int setjmp(where) jmp__________buf where; При вызове она сохраняет состояние обратившейся к ней про- граммы, чтобы далее можно было использовать функцию longjmp. Возвращаемое значение равно нулю. Для восстановления сохраненного состояния программы ис- пользуется функция longjmp: void longjmp(where, val) jmp_Jbuf where; int val;
140 Гл. 5. Язык программирования С После вызова longjmp выполнение программы возобновляется с точки вызова функции setjmp. Функция setjmp как бы возвра- щает значение val. Описание типа данных jmp__buf содержится в системном файле заголовков. Эти функции используются в интерактивных программах, когда требуется обработать серьезные ошибки, возвратиться в стандартное место в программе и запросить у пользователя дальнейшие инструкции. 5.2.10. Послесловие Приведенные ниже правила соблюдаются в большинстве Сопро- грамм, входящих в состав стандартной распространяемой версии системы UNIX. Одни правила приводятся только потому, что им следуют много людей. Другие — упрощают чтение и написание программ. • Используйте typedef и избегайте сложных описаний. • Используйте *de^ne Для определения символических констант. В частности, не употребляйте в программе в явном виде целые константы для задания размеров бу- феров и других величин, которые могут измениться. Для символических констант используйте имена, состав- ленные из прописных букв. • Избегайте использования макро вместо функции, за исключением случаев, когда важна эффективность. • Всегда описывайте тип результата для функций и исполь- зуйте явный оператор возврата return. При возврате из функции main всегда указывайте возвращаемое значе- ние, поскольку оно является статусом завершения ко- манды. • Проверяйте значения, возвращаемые системными функ- циями, с целью обнаружения ошибочных ситуаций. • Используйте системные файлы заголовков, такие, как <math.h> и <stdio.h> (разд. 5.1). Не делайте предположе- ний относительно значений символических констант (например, EOF) и кодов литер, используемых в данной машине. • Избегайте условных выражений с операндами нечисло- вых типов. • Примите на вооружение структурированный способ оформления программы и используйте фигурные скобки в управляющих операторах. Комментарий после закры- вающей скобки определения функции полезен, если тело функции занимает больше страницы.
5.3. Организация программ и управление программами 141 • Используйте операцию преобразования типов для преоб- разования к соответствующему типу значений, возвра- щаемых подпрограммами распределения памяти. • Храните файлы заголовков, содержащие определения структур, отдельно от главной программы. Не исполь- зуйте несколько копий одних и тех же описаний; ссы- лайтесь на файлы заголовков, используя конструкцию Фinclude. Храните связанные друг с другом функции в одном файле и используйте спецификацию static для функций, локальных для некоторого файла. • Используйте ifdef как можно реже. Чрезмерное его употребление может затруднить чтение программы. • Выполните команду lint и разберитесь в ее выводе. 5.3. ОРГАНИЗАЦИЯ ПРОГРАММ И УПРАВЛЕНИЕ ПРОГРАММАМИ 5.3.1. Компиляция программ Компиляция С-программ выполняется при помощи команды сс. Простые программы, такие, как программы в приведенных выше примерах, хранятся в виде отдельных файлов. Команда сс average, с компилирует программу average.с и помещает готовую программу в выполнимый файл a.out. Команда сс —о average average, с Поместит готовую программу в файл average. Если же файл average.с является частью большой программы, разбитой на два файла average.с и main.с, то компиляция обоих файлов с полу- чением готовой программы выполняется при помощи команды сс —о average main.с average.c Чтобы не выполнять компиляцию обоих файлов main, с и average.c при изменении в одном из них, можно ввести проме- жуточный шаг. Он состоит в сохранении объектных файлов main.o и average.o, полученных в результате выполнения команды сс —с main.с average.c Готовую программу average в таком случае можно получить, выполнив команду сс —о average main.o average.o Программа-оболочка, например, состоит из 35 файлов с об- щей длиной 4800 строк. Из них 24 исходных файла содержат опре-
142 Гл. 5. Язык программирования С деления функций, а остальные являются файлами заголовков, содержащими определения, которые используются в одном или нескольких исходных файлах. Такая организация программ типична для системы UNIX. Разбиение сложной исходной про- граммы на функциональные части позволяет сократить время компиляции после внесения изменения, а также локализовать имена функций в пределах одной функциональной части, исполь- зуя спецификации static. Это может также облегчить чтение и понимание таких программ. 5.3.2. Команда make При разработке большой программы, состоящей из нескольких исходных файлов и файлов заголовков, приходится постоянно следить за файлами, которые требуют перекомпиляции после внесения изменений. Занятие это утомительное и чревато ошиб- ками. Команда make освобождает пользователя от такой рутин- ной работы и служит для документирования взаимосвязей между файлами. Команда make позволяет задавать информацию о зависимо- стях между файлами. При вызове make обновляет целевой файл, если он зависит от файлов, в которые были внесены изменения с момента последней модификации целевого файла, или создает целевой файл, если он не существует. Для каждой зависимости могут быть заданы некоторые действия, например компиляция исходного файла с получением соответствующего объектного файла или печать файла, измененного с момента его последней печати. Описания зависимостей и соответствующих действий хра- нятся в так называемом make-файле, который по умолчанию имеет имя makefile или Makefile. Например, make-файл для про- граммы average, приведенной в начале этой главы, выглядит так: average: main.o average.o сс —о average main.o average.o Этот файл следует читать так: average зависит от main.o и average.o и если какой-либо из файлов .о имеет более позднюю дату моди- фикации по сравнению с файлом average, то надо выполнить команду сс —о average main.o average.o Команда make имеет достаточно информации о файлах, оканчи- вающихся на .с и .о, чтобы самостоятельно перекомпилировать
5.3. Организация программ и управление программами 143 исходный файл .с, если он был модифицирован позднее соответст- вующего объектного файла .о. Если бы main.с и average.с включали некоторый файл заго- ловков, например defs.h, то make-файл пришлось бы уточнить. Информация о добавочной зависимости могла бы выглядеть так: main.о average.o: defs.h Это вызвало бы перекомпиляцию обоих файлов при изменении содержимого defs.h. В общем случае make-файл содержит последовательность записей, определяющих зависимости между файлами. Первая строка записи представляет собой список целевых (зависимых) файлов, разделенных пробелами, за которым следуют двоеточие и список файлов, от которых зависят целевые. Текст, следующий за точкой с запятой, и все последующие строки, начинающиеся с литеры табуляции, являются командами оболочки, которые необходимо выполнить для обновления целевого файла. Разработка программы g использованием make состоит из следующих шагов: • Подумать и отредактировать файлы. • Выполнить команду make. • Проверить и повторить. В команде make без параметров предполагается, что целевым является первый зависимый файл в make-файле. Целевой файл может быть задан явно. Например, команда make main.o гарантировала бы обновление файла main.o. Команда make имеет несколько полезных опций. Чтобы уз- нать, какие действия требуются для обновления файла, но не выполнять их, можно воспользоваться командой make —п Обычно make печатает каждую команду перед ее выполнением. Эту печать можно отменить, задав опцию —s. Иногда случается, что все файлы находятся в обновленном состоянии, а даты их модификации некорректны. Обеспечить корректность дат можно, воспользовавшись командой make —t которая обновляет эти даты при помощи команды touch. В make-файле могут также содержаться комментарии и за- просы на замену цепочек. Комментарий начинается с литеры ф и заканчивается литерой новой строки.
144 Гл. 5. Язык программирования С, Записи в make-файле вида цепочка! = цепочка2 являются макроопределениями; все последующие вхождения $(цепочка1) заменяются на текст цепочка2. Если цепочка! со- стоит из одной литеры, то скобки можно опускать. Команда make содержит список суффиксов, которые опреде- ляют ряд подразумеваемых правил. По умолчанию, этот список имеет вид .SUFFIXES:; .out .о .с .е .г Л .у .1 .s .р Правило для создания файла с суффиксом s2, зависящего от фай- ла с таким же именем, но с суффиксом si, задается в виде записи, в которой роль имени целевого файла играет sls2. В такой записи специальная макропеременная $* обозначает имя целевого файла с отброшенным суффиксом, $@ — полное имя целевого файла, $< — полный список необходимых условий и $? — список фай- лов, требующих обновления. Например, правило для создания оптимизированных объектных файлов .о из исходных файлов с суффиксом .с имеет вид .с.о: ; сс —с —О —о $@ $*.с 5.3.3. Команда lint При создании не очень маленьких программ можно использовать и ряд других полезных средств. Команда lint не раз уже упоми- налась. Она стоит того, чтобы с ней познакомиться поближе. Хотя некоторые из ее сообщений могут оказаться несуществен- ными, на другие следует обратить внимание, так как они указы- вают на потенциальные ошибки в программе. С-компиляторы обычно не проверяют соответствие между определениями функций и их вызовами. Различные другие про- верки — их часто называют проверками типов — также не вы- полняются. Команда lint пытается восполнить этот пробел. Помимо выполнения проверки на соответствие типов lint печа- тает список неиспользуемых переменных и функций, а также распознает ряд случаев использования неинициализированных переменных. Некоторые из конструкций, которые lint считает сомнитель- ными, формально являются правильными. Например, встретив выражение if (11=0) lint выдает сообщение «константа в условном контексте». Более реальный пример сомнительной конструкции — отсутствие ско-
5.3. Организация программ и управление программами 145 бок: if (х&1 ==0) Вероятно предполагалось if ((х&1)==0) 5.3.4. Библиотеки программ Библиотеки программ, используемые при программировании на С, хранятся в виде архивных файлов. Каждый элемент архив- ного файла сам является объектным файлом. Элементы можно добавлять, модифицировать или исключать, используя коман- ду аг. По соглашению, архивные или библиотечные файлы имеют имена, оканчивающиеся суффиксом .а. Например, библиотека, которая автоматически вызывается в команде сс при компиляции С-программы, хранится в файле /lib/libc.a. В распространяемой версии системы UNIX документация по стандартным библиотекам содержится в разд. 2 и 3 «Руковод- ства для программиста». Стандартная библиотека языка С (libc) состоит из функций, описанных в разд. 2 (системные функции), в разд. 3 (без суффикса) и разд. 3S, содержащем пакет стандарт- ного ввода-вывода (stdio). Если в программе используются функ- ции из разд. 3S, в ней должен быть оператор ^include <stdio.h> В разд. ЗМ описаны некоторые математические функции (на- пример, sqrt()). Их можно использовать, если задать ^include <math.h> в исходной программе. Чтобы получить готовую программу, ис- пользующую эту библиотеку математических функций, в коман- де сс следует задать опцию —Im: сс —о average main.o average.o —Im Другие библиотеки вызываются по имени библиотечного (архивного) файла. Для обращения к стандартным библиотекам можно использовать опцию —1... в команде сс. Сопровождение библиотек Библиотека позволяет использовать набор подпрограмм в других программах. Для создания и сопровождения библиотек служит команда аг. По соглашению, библиотеки хранятся в файлах, имена которых оканчиваются суффиксом .а. Библиотеки состоят из объектных файлов, которые содержат всю необходимую ин-
146 Гл. 5. Язык программирования С формацию для перемещения подпрограммы и связывания ее с главной программой. Для получения выполнимых файлов a.out из объектных файлов и библиотек команда сс использует редактор связей (загрузчик) Id. При поиске неопределенных имен библиотечный файл про- сматривается только один раз. Если библиотечная подпрограмма ссылается на другие имена из той же библиотеки, то ее определе- ние должно предшествовать определениям этих имен. Это на- кладывает определенные соглашения на порядок элементов библиотечного файла. Современные версии системы UNIX содержат программу ranlib, которая создает оглавление библиотеки, что позволяет загрузчику Id обращаться к элементам библиотеки в произволь- ном порядке. В UNIX System V эта функция встроена в аг и Id. В более старых версиях системы UNIX для этих целей предназ- начались программы lorder и tsort. По заданному списку объект- ных файлов lorder образует список пар зависимых имен, a tsort осуществляет частичное упорядочение этого списка, приемлемое для Id. Архивную программу аг можно также использовать для объе- динения любого набора файлов. 5.3.5. Измерение производительности Команда time Команда time позволяет определить суммарное время выпол- нения команды. Возьмем для примера программу average из разд. 5.1.3. Команда time average напечатает сначала результаты выполнения команды average, а затем — строку вида 6.0 real 5.4 user 0.1 sys Реальное время (real) — это астрономическое время, в течении которого выполнялась команда. Время пользователя (user) — это время центрального процессора, затраченное на выполнение пользовательской части программы, а системное время (sys) — это время, затраченное на выполнение системных функций. Про- граммы, выполняющие ввод или вывод большого объема, могут израсходовать значительное количество системного времени. Профилирование Имеется возможность получить профиль выполнения Сопро- граммы, содержащий число обращений и время выполнения для каждой функции. Никаких изменений в программе для этого
5.3. Организация программ и управление программами 147 делать не нужно, но при компиляции в команде сс следует задать опцию —р. Во время выполнения программы создается файл mon.out, содержащий профильные данные. Для печати профиля используется команда prof. %time cumsecs#call ms /call name 62.8 3.62 _sqrt 30.4 5.37 frexp 5.2 5.67 81 3.71 -average 0.6 5.71 doprnt 0.3 5.72 flsbuf 0.3 5.74 _close 0.3 5.76 _printf 0.3 5.77 -Write 0.0 5.77 1 0.00 -main Рис. 5.8 Результаты выполнения команды prof average Например, команда average из разд. 5.1.3 была перекомпи- лирована с опцией —р, а затем выполнена. Вывод, полученный в результате выполнения команды prof average приведен на рис. 5.8. В этом примере профилирование выполнялось только для функций, скомпилированных с опцией —р. 5.3.6. Разное Готовые программы, полученные в результате выполнения коман- ды сс, содержат таблицу символов, которую можно использовать при отладке. Когда программа отлажена, эта таблица больше не нужна, и ее можно удалить (сэкономив тем самым файловое пространство) командой strip, например: strip average Размер памяти, занимаемой готовой программой и ее данны- ми, можно получить с помощью команды size, например: size average Результат работы этой команды имеет вид размер-кода + размер-данных + размер-bss = общий-размер
148 Гл. 5. Язык программирования С где размер-bss — это размер неинициализированного сегмента данных. Получить список всех имен, используемых в программе, позволяет команда пт, например: пт average Для каждого символа будет выведено его имя и указано, является ли он символом данных (переменной) или программным символом (меткой или именем функции). 5.4. ОТЛАДКА С-ПРОГРАММ Большинство программ с первого раза не работает. Если вам повезет, вы сообразите, в чем ошибка, исходя из поведения программы. В противном случае вы окажетесь перед выбором: думать, вставлять операторы printf для печати значений ключе- вых переменных во время счета или же использовать отладчик. Если вы выбрали отладчик, то на PDP 11/45 или 11/70 к вашим услугам отладчик adb, а на VAX 11/780 — отладчик sdb (см. приложение 1), который в большей степени ориентирован на язык С. В этом разделе описывается отладчик adb, поскольку он предоставляет более фундаментальные возможности и более широко распространен, хотя действия, выполняемые отладчиком sdb, аналогичны. В программе могут возникнуть следующие ошибочные ситу- ации: • Она может зациклиться. • Программа может завершиться неожиданно из-за нару- шения защиты памяти или другой ошибки, обнаруживае- мой аппаратурой. • Программа выполняется, хотя ведет себя странным образом и функционирует неправильно. • Ей не удалось получить требуемые ресурсы (например, файлы). Для программы, выполнение которой прервано из-за одной из ошибок, приведенных в разд. 6.6.1, может быть образован файл core, содержащий дамп этой программы (программный код и данные). Возбуждение с терминала сигнала выхода путем ввода литеры А \вызывает завершение процесса с образованием дампа, если только в процессе не был задан перехват этого сиг- нала. Отладчик adb предоставляет запросы для просмотра содер- жимого core-файлов, получаемых при прерывании программ, для печати переменных в различных форматах, для внесения из- менений в двоичные файлы и для выполнения программ в интер-
5.4. Отладка С-программ 149 активном режиме с использованием контрольных точек. Его можно применять для исследования содержимого любых файлов, хотя он предназначен для файлов типа a.out и core. 5.4.1. Отладка программы с использованием дампа Для отладки adb вызывается командой adb objfile corefile где objfile — файл, содержащий готовую программу, a corefile — это core-файл. Часто достаточно набрать только adb, поскольку по умолчанию подразумеваются файлы a.out и core соответствен- но. К выходу из отладчика приводит ввод признака конца файла или запроса $q Запрос $с выдает последовательность вложенных вызовов С-подпрограмм в обратном порядке. Запрос $С выдает последовательность вложенных вызовов вместе со значе- ниями всех локальных переменных для каждой функции (в вось- меричном виде). Запрос $е печатает значения всех внешних переменных. Значение конкрет- ной внешней переменной можно также напечатать в десятичном виде запросом имя-переменной/d 5.4.2. Запросы adb Отладчик adb имеет запросы для анализа содержимого как программного файла, так и core-файла. Запрос ? выводит со- держимое программного файла objfile, а запрос / —содержимое файла corefile. Эти запросы имеют общий вид адрес ? формат и адрес / формат Отладчик хранит текущий адрес, который, как и текущая строка в редакторе ed, обозначается точкой. Когда вводится
150 Гл. 5. Язык программирования С адрес, он становится текущим и присваивается точке. Например, запрос 0l26?i присвоит точке восьмеричное значение 0126 и распечатает коман- ду по этому адресу. Запрос .,10/d распечатает 10 десятичных чисел, начиная с текущего адреса. Значением точки становится адрес последнего распечатанного элемента. После выполнения запроса ? или / продвинуть теку- щий адрес вперед и распечатать следующий элемент можно, просто нажав клавишу возврата каретки, а продвинуть текущий адрес назад и распечатать предыдущий элемент — набрав ли- теру Л. Адрес в adb — это выражение, составленное из десятичных, восьмеричных и шестнадцатеричных целых и символов тестируе- мой программы, которые могут быть объединены с помощью операций: Таблица 5.3 Операция Описание + * % & *а @а Сложение. Вычитание. Умножение. Деление нацело. Поразрядная конъюнкция. Поразрядная дизъюнкция. Округление до ближайшего кратного. Поразрядное отрицание. Содержимое по адресу а в соге-файле. Содержимое по адресу а в объектном файле. Унарные операции имеют больший приоритет по сравнению с би- нарными. Все бинарные операции имеют одинаковый приоритет и выполняются слева направо. Вся арифметика в adb выпол- няется над 32-разрядными словами. 5.4.3. Форматы adb Формат описывает представление выводимых данных. Ниже приведены наиболее часто используемые в запросах / и ? спе- цификации форматов (они следуют за именем запроса). Сущест- вуют также спецификации форматов для длинных значений, на-
5.4. Отладка С-программ 151 пример D — для печати длинного целого в десятичном виде и F — для печати чисел с плавающей точкой двойной точности. Последний формат запоминается, поэтому если в запросе формат не задан, то запрашиваемая величина печатается по предыду- щему формату. Таблица 5.4 Формат Описание b с о d f i s u w Один байт в восьмеричном виде. Один байт в литерном виде. Одно слово в восьмеричном виде. Одно слово в десятичном виде. Два слова как число с плавающей точкой. Машинная команда. Цепочка литер, заканчивающаяся литерой \0. Одно слово в виде целого без знака. Записать слово в файл. В общем случае запрос имеет вид адрес, п запрос модификатор Точке присваивается указанный адрес, и запрос выполняется п раз. В приведенной ниже таблице перечислены запросы отладчи- ка adb. Таблица 5.5 Запрос Описание ? Печать содержимого файла a.out. Печать содержимого соге-файла. Печать текущего адреса. Управление контрольными точками. $ ! Смешанный набор запросов. Выход в оболочку. Присваивание значения переменной отладчика. Отладчик перехватывает сигналы, поэтому сигнал выхода не приведет к выходу из adb. Прерывание вызывает прекращение выполнения текущего запроса и переходит к чтению нового запроса. Для выхода из отладчика используется запрос $q ($Q или AD).
152 Гл, 5. Язык программирования С 5.4.4. Установка контрольных точек в adb Программу можно выполнять под управлением отладчика. Команда adb a.out —* подготавливает файл a.out для тестирования. Чтобы начать вы- полнение тестируемой программы, следует ввести запрос :г ... В этом запросе можно задавать параметры вызова, а также пере- адресацию ввода-вывода в упрощенном виде. Управление пере- дается в программу, а отладчик ждет либо завершения выполне- ния программы, либо получения ею сигнала. Если пришел сиг- нал, например прерывание с терминала или нарушение защиты памяти, отладчик вновь получает управление, а выполнение программы приостанавливается. Тестируемую программу можно также остановить при обращении к заданной функции, установив контрольную точку. Контрольные точки устанавливаются за- просом адрес [, n ] : b где адрес — имя С-функции. Запрос $Ь выдает информацию о местонахождении контрольных точек. Наряду с адресом печатается также значение счетчика — п. Останов на контрольной точке произойдет при n-м проходе через нее. Для снятия контрольной точки используется запрос адрес : d Всякий раз, когда управление попадает в отладчик, можно анализировать содержимое памяти, переустанавливать контроль- ные точки, изменять содержимое переменных и возобновлять выполнение программы. Сигналы выхода и прерывания воздействуют на сам отладчик, а не на отлаживаемую программу. При возбуждении такого сиг- нала выполнение отлаживаемой программы приостанавливается и управление возвращается отладчику. Сигнал сохраняется от- ладчиком и может быть передан в отлаживаемую программу за- просом :с ' , -.s ,
5.4. Отладка С-программ 153 Эту возможность можно использовать для тестирования подпро- грамм обработки сигналов. Если вместо этого ввести запрос :с О то сигнал в тестируемую программу передан не будет. Отлаживаемую программу можно выполнять по шагам, ис- пользуя запрос :s При необходимости этот запрос может запустить программу и приостановить ее после выполнения первой команды. 5.4.5. Карта памяти В системе UNIX имеется несколько форматов для выполнимых файлов. Они информируют систему о том, каким образом следует разместить программу в памяти. Отладчик adb интерпретирует эти форматы по-разному и обеспечивает доступ к сегментам с по- мощью набора карт памяти. Карта памяти создается для каждого файла, анализируемого -отладчиком, и используется для преобразования адресов памяти в смещения относительно начала файла. Запрос ? использует карту памяти для файла a.out, а запрос / — карту памяти для файла core. Хорошим правилом при анализе программы является использование ? для печати команд и / — для печати данных. Карты памяти выводятся запросом $т Каждая карта памяти состоит из двух сегментов, определяе- мых базами Ы, Ь2, длинами el, е2 и смещениями fl, f2. По за- данному адресу памяти А адрес в файле вычисляется следующим образом: Ы А el => адрес-в-файле = (А — Ы) + fl Ь2 А е2 => адрес-в-файле = (А — Ь2) 4- f2
ГЛАВА 6 ПРОГРАММИРОВАНИЕ В СИСТЕМЕ UNIX В этой главе содержится описание интерфейса язык С — опера- ционная система UNIX. Рассматриваются вопросы, возникающие при написании программ, непосредственно взаимодействующих с операционной системой UNIX: создание процессов, обработка прерываний, посылка сигналов и использование транспортеров. 6.1. СОГЛАШЕНИЯ О ПАРАМЕТРАХ Большинство команд в системе UNIX реализовано либо в виде командных процедур, либо в виде программ на С. В обоих слу- чаях программе (процедуре) доступны значения параметров команды. Параметры представляют собой цепочки литер. Сущест- вует ряд не строго определенных соглашений о параметрах, которые следует соблюдать при разработке новых команд. Пер- вые параметры вида — буква часто задают опции. Остальные имена обычно считаются именами файлов. Следовательно, не разумно использовать имена файлов, начинающиеся со знака минус,— это может вызвать большую путаницу. В различных командах параметры интерпретируются по-разному. Одни команды более тщательно проверяют правиль- ность задания параметров, чем другие. Если команда не требует параметров, нельзя полагаться на то, что она проверяет их дейст- вительное отсутствие. В большинстве команд параметры задаются в следующем виде: имя-команды [ опции ] [ файлы ] Иногда в состав опций входят два параметра, как, например, в случае опции —о команды сс: сс —о simple simple.с Из данного правила интерпретации параметров как опций имеется ряд исключений. Команда test, описанная в гл. 4, вычисляет
6.1. Соглашения о параметрах 155 выражение, составленное из своих параметров. Другой пример — команда dd, описанная в приложении 1, которая выполняет пре- образование и копирование файла. Обработка параметров в С-программах Программа на языке С начинает выполняться с вызова функции main, описание которой имеет вид int main(argc, argv, arge) int argc; char «argvl ]; char *arge[ ]; Здесь argv представляет собой указатель на вектор параметров команды, a argc содержит количество параметров в векторе argv. Предположим, что соответствующей программе требуются три параметра: pl, р2 и рЗ. Тогда argc равно 4, argvlOl — имя данной команды, argvll]—указатель на pl, argv[2], argv[3] — указатели на р2 и рЗ соответственно, argv[4] равно 0. Каждый из параметров, доступных через указатели argvl 1], argv[2], argv[3], представляет собой цепочку литер и заканчи- вается литерой '\0'. Int main(argc, argv) int argc; char *argv[J; While (—arge>0) ( printf(’’%s°/oc''1 »++argv, (argc>1) ? ’ ' : V*); retum(O); Рис. 6.1 Команда echo Вектор arge содержит информацию о среде. Во многих про- граммах вектор arge не описывается, а для доступа к среде ис- пользуется подпрограмма getenv. Более подробно этот вопрос рассматривается в разд. 6.5.6.
156 Гл. 6. Программирование в системе UNIX Хотя компиляторы С позволяют опускать параметры, если они не используются, рекомендуется описывать их в любом слу- чае. Верификатор С-программ lint обнаруживает неиспользуемые параметры. int unbuff; int main(argc, argv) int argc; char ♦argv[]; ( char c; char *cp; int copied = 0; while (argc > 1) ( switch (*argv[1)) { case /• цепочка argv[1] содержит опции * I ср - argv[1]; while(c — *++cp){ /* обработка каждой буквы*/ switch(c){ / case 'u': unbuff++; break; default: /♦ непредусмотренная букМ */ I } } break; default: /♦ прочие параметры */ copy(argv[1]); copied++; ) argc—; argv++; } if(copied--0){ copy((char»)0); return(O); } Рис. 6.2 Набросок команды cat
6.2. Базисные средства ввода-вывода 157 tfdefine BUFSIZ 512 copy(s) char ♦s; { char bufferfBUFSIZ]; int length; int fd - 0; /♦ подразумевается стандартный ввод*! if (s) { fd “ open(s, 0); ) do( length - read(fd, buffer, BUFSIZ); write(1, buffer, length); } while (length > 0); return(O); 1 Рис. 6.3 Подпрограмма copy В качестве примера обработки параметров на рис. 6.1 приве- дена простая программа, реализующая команду echo. Команда echo выводит в файл стандартного вывода свои параметры, раз- деленные пробелами. Более полный пример обработки параметров команды приве- ден на рис. 6.2, представляющем собой набросок команды cat. Опция —и устанавливает флаг, используемый функцией сору. Если среди параметров не задано имен файлов, то копируется файл стандартного ввода. Предполагается, что функция сору с пустым указателем в качестве параметра копирует файл стан- дартного ввода. Эта функция приведена на рис. 6.3. В теле цикла анализируется каждый параметр из вектора argv. Если первой литерой является знак минус, то параметр считается опцией. В противном случае это имя файла, и этот файл копируется в стандартный вывод. Добавление новых опций не вызывает никаких проблем, так как схема обработки парамет- ров реализована в общем виде. 6.2. БАЗИСНЫЕ СРЕДСТВА ВВОДА-ВЫВОДА Пакет ввода-вывода с буферизацией (stdio), кратко рассмотрен- ный в гл. 5, обычно годится для выполнения простых операций ввода-вывода. В данном разделе описываются средства ввода-
158 Гл. 6. Программирование в системе UNIX вывода нижнего уровня, предоставляемые операционной систе- мой’ UNIX. Операции ввода-вывода выполняются над открытыми фай- лами. Открытые файлы представлены в системе дескрипторами файлов, причем каждый дескриптор имеет свой номер. При вы- полнении любой прикладной программы можно считать, что не- которые файлы уже открыты. Это файлы с дескрипторами О стандартный файл ввода (только чтение), 1 стандартный файл вывода (только запись), 2 стандартный файл диагностики (чтение и запись). Эти файлы открываются процедурой login и могут быть перена- значены программой-оболочкой. После того как в программе был открыт или создан какой-либо файл, следует сохранить его дескриптор для последующего использования в операциях ввода- вывода. Дескрипторы файлов используются как для транспор- теров, так и для файлов. Файл может быть открыт на чтение, на запись или на модифи- кацию, т. е. и на чтение, и на запись одновременно. Для файлов (но не для транспортеров) предусмотрены средства произволь- ного доступа; эти средства обсуждаются в разд. 6.4.2. Обычно ввод-вывод выполняется в последовательном режиме. Если последним был прочитан или записан некоторый байт фай- ла, то очередная операция ввода-вывода неявно относится к сле- дующему за ним байту. Для каждого открытого файла хранится указатель текущей позиции; значением этого указателя является номер байта, который будет прочитан или записан следующим. Если читается или записывается сразу п байтов, указатель текущей позиции продвигается сразу на п байтов. В файле могут храниться не только литеры, но и другие объ- екты. Например, при выполнении фрагмента программы int i; while(...) { write(l, &i, sizeof(i)); } в файл стандартного вывода будет записана последовательность целых чисел. Для чтения данных из файлов следует использовать аналогичную последовательность операторов. Следует также ис- пользовать ЭВМ того же типа, так как способ хранения такой последовательности литер в файле зависит от реализации. Те же ограничения распространяются на любой тип данных, отличный от char. Вывод: если запись ведется байтами, читать надо тоже по байтам; если записываются целые числа, читать надо тоже целыми числами.
6.2. Базисные средства ввода-вывода 159 6.2.1. Системная функция open Системная функция open позволяет создавать файлы или откры- вать существующие файлы на чтение или запись. Ее описание имеет вид int open(name, mode) char *name; int mode; Здесь name — адрес цепочки литер (заканчивающейся лите- рой '\0'), содержащей полное составное имя файла. Второй параметр (mode) определяет режим доступа к файлу: О чтение, 1 запись, 2 чтение и запись. В некоторых системах функция open имеет и третий параметр. Значением, возвращаемым функцией open, является дескриптор файла. Этот дескриптор используется во всех операциях ввода- вывода с этим файлом. Указатель текущей позиции чтения или записи первоначально устанавливается на начало файла. Если файл не может быть открыт, возвращается значение —1. Это может произойти по одной из следующих причин: • Файл не существует. • Оглавление, имя которого входит в составное имя файла, не существует. • Слишком много открытых файлов. • Попытка открыть на чтение (запись) файл, который нельзя читать (в который нельзя писать). Воспользовавшись системной функцией dup, можно узнать, соответствует ли дескриптор файла какому-либо открытому файлу. Функция dup(fd) возвращает новый дескриптор файла (дубликат), если параметром является дескриптор открытого файла. Если параметр не является дескриптором открытого фай- ла или уже было открыто слишком много файлов, возвращается значение —1. Другой способ проверки состояния открытого файла заключается в использовании системной функции fstat (разд. 6.4.3). 6.2.2. Чтение и запись файлов После того как файл был открыт, над ним можно выполнять операции чтения и записи, используя функции read и write. Описание функции read имеет вид
160 Гл. 6. Программирование в системе UNIX int read(fd, buf, n) int fd; char *buf; int n; При обращении к функции read п литер из файла с дескрипто- ром fd будут помещены в область памяти, на которую указывает указатель buf. Возвращаемым функцией read значением является либо • количество прочитанных литер, если не был достигнут конец файла, либо • нуль, если достигнут конец файла. Функция read может вернуть меньшее количество литер, чем было запрошено. Ввод литеры AD с терминала приводит к тому, что частично набранная строка передается программе, обратившейся к сис- темной функции read (см. также разд. 6.4.4). Если не было на- брано ни одной литеры, то функция read возвращает нуль. По соглашению, это означает конец файла. При последующих запросах чтения можно получить новые литеры, однако этим приемом лучше не пользоваться. Описание функции write имеет вид int write(fd, buf, n) int fd; char *buf; int n; Функция write выводит n литер из буфера buf в файл, задаваемый дескриптором файла fd. Из соображений эффективности рекомен- дуется использовать буфера длиной не менее 64 байт; можно, однако, использовать любую длину в пределах размера наличной памяти и размера массива buf. При обнаружении ошибки (на- пример, неправильный дескриптор файла или неправильный адрес буфера) функция write возвращает значение —1. Для повышения надежности целесообразно включать в программу проверки на ошибочные ситуации. Это позволяет реагировать на такие события, как, например, выход за пределы файла. Проиллюстрируем использование функций read и write на примере. Следующая программа копирует свой стандартный файл ввода в свой стандартный файл вывода. Копирование произ- водится по одной литере char с[11; while (read(0, с, 1) = = 1) { write(l, с, 1);
6.3. Дополнительные сведения о файловой системе 161 Идентификатор с описан как массив литер, что позволяет при его употреблении опускать операцию &. В качестве параметра авто- матически используется адрес литеры с[0]. Приведенная выше программа не эффективна, так как она об- ращается к системной функции для чтения или записи каждой литеры. Программа на рис. 6.3 значительно более эффективна, поскольку в ней нет этих издержек. Эта программа представляет собой функцию сору, используемую в команде cat, приведенной на рис. 6.2. При достижении конца файла, когда больше уже нет литер, которые можно было бы прочитать, функция read, как уже было сказано, возвращает 0. Однако чтение может быть не выполнено и по другим причинам, например если произошло прерывание или задан неправильный дескриптор файла. Полный список всех ошибочных ситуаций, которые могут возникнуть при обращении к системным функциям, приведен в разделе err по приложения 2. Чтобы обрабатывать эти ситуации, пользуясь их символическими именами, необходимо включить в программу файл errno.h: ^include <errno.h> Завершает наш вводный обзор системных функций, опери- рующих с дескрипторами файлов, функция close (закрыть файл). Обращение close(fd) приводит к разрыву связи между дескриптором файла fd и свя- занным с ним файлом (или транспортером). Обычно все файлы закрываются автоматически — при завершении процесса, с ко- торым они связаны; но, так как число одновременно открытых файлов для одного процесса ограничено, иногда необходимо вы- полнять эту операцию и в самой программе. Функция close возвращает 0 в случае успешного завершения и _—1, если задан неверный дескриптор файла. 6.3. ДОПОЛНИТЕЛЬНЫЕ СВЕДЕНИЯ О ФАЙЛОВОЙ СИСТЕМЕ 6 .3.1. Полномочия файла С каждым файлом связан набор полномочий, определяющих, кто имеет права доступа к этому файлу и каковы эти права. Пользо- ватель может иметь право на чтение (г), право на запись (w), право на выполнение (х) или некоторую комбинацию этих прав. В соответствии с правами доступа пользователи делятся на три класса: и Владелец (создатель) файла. 6 С. Баурн
162 Гл. 6. Программирование в системе UNIX g Члены группы. о Прочие пользователи. Полномочия для каждого класса пользователей устанавли- ваются владельцем файла. Полную информацию о текущем состоянии конкретного файла (или оглавления) можно получить, используя опцию —1 команды 1s. Например, команда 1s —1 /etc/motd выдаст строку rw—rw—г------1 adm 0 Jul 28 20:03 /etc/motd Цепочка rw—rw—г представляет собой полномочия файла /etc/motd. Три первые литеры, rw—, определяют права доступа для владельца файла, следующие три — права доступа для чле- нов группы владельца (группы рассматриваются ниже), а три последние, г----, — права доступа для всех прочих пользова- телей. В этом примере владелец и члены группы могут читать данный файл и писать в него, а прочие пользователи могут только читать. Следующее поле — это количество ссылок на файл; для вновь созданных файлов оно равно 1. Слово adm представляет собой регистрационное имя владельца файла. Следующее поле содержит количество байтов в файле (размер файла). В данном примере файл имеет нулевую длину. Дата указывает время по- следней записи в файл (время последней модификации). Наконец, последнее поле содержит имя файла. Если файл предназначен для выполнения, т, е. является либо готовой программой, либо командной процедурой, в его полно- мочиях должен быть установлен флаг выполнения. Например, информация о выполнимой программе /bin/sh выдается командой Is —I в виде rwx----х-----х 1 bin 24272 Jan 10 18:03 /bin/sh Это означает, что программа /bin/sh может выполняться пользо- вателями всех трех классов, но читать этот файл и писать в него может только владелец. Выполнимые программы могут также иметь флаги установки идентификации пользователя и установки идентификации груп- пы. Этот вопрос рассматривается в разд. 6.5.4. Права доступа владельца файла определяются только полно- мочиями, относящимися к владельцу; то, что он может быть и членом группы, значения не имеет. В файл с полномочиями —-—г------rwx
6.3. Дополнительные сведения о файловой системе 163 не сможет писать ни пользователь, ни член группы, но прочие пользователи могут с ним делать все что угодно. Полномочия такого рода не нашли широкого применения. €.3.2. Изменение полномочий файла Полномочия файла могут быть изменены его владельцем при помощи команды chmod. В ранних версиях этой команды полно- мочия задавались только в абсолютном восьмеричном виде (как в команде umask, рассматриваемой ниже). Например, в результате выполнения команды chmod 751 run полномочия файла run будут иметь вид rwxr—X----X т. е. владелец будет иметь все права доступа, в то время как члены группы смогут только читать и выполнять, а прочие поль- зователи — только выполнять этот файл. В команде chmod полномочия могут задаваться как в абсо- лютном, так и в относительном виде. Примеры: и—г Отменить право на чтение для владельца файла, g+w Добавить право на запись для членов группы. о=х Установить для прочих пользователей право на выполнение. Чтобы сделать файл run выполнимым для всех пользователей, достаточно выполнить команду chmod +х run Файл run в этом примере можно было бы заменить списком фай- лов — тогда выполнимыми стали бы все эти файлы. Полномочия файла устанавливаются при создании и впослед- ствии могут быть изменены его владельцем. Максимальный на- бор полномочий, устанавливаемых по умолчанию при создании пользователем любого файла, можно задать командой umask (установить маску пользователя). Команда umask О снимает всякие ограничения — создаваемые файлы будут иметь полномочия rwxrwxrwx. Команда umask 22 задает максимальный набор полномочий на доступ rwxr—хг—х (эта маска используется по умолчанию в некоторых системах). Команда chmod игнорирует текущую маску пользователя. 6*
164 Гл. 6. Программирование в системе UNIX Следующая процедура выдает полномочия файла, созданного с учетом текущей маски пользователя: >/tmp/$$ Is —1 /tmp/$$ rm /tmp/$$ Взаимоотношения между людьми во многих коллективах пользователей UNIX основаны на дружбе и сотрудничестве. В этих условиях нормальным явлением может быть предоставле- ние всем пользователям права на чтение всех файлов. Там, где важна секретность, можно ограничить полномочия, создаваемых файлов до rwx-----------, воспользовавшись командой umask 77; тем самым права доступа будут предоставляться только вла- дельцу. Во многих системах обычные пользователи не могут изменить владельца файла или оглавления. Однако в случае файлов это за- труднение можно иногда обойти путем копирования файла, ис- ключения оригинала и переименования копии. Одним из побоч- ных эффектов этой операции является изменение даты создания файла. 6.3.3. Доступ к оглавлениям Для оглавлений полномочия на чтение, запись и выполнение трактуются несколько иначе, чем для файлов. г Разрешает читать оглавление так, как будто это обыч- ный файл. Оглавление читается, например, програм- мой-оболочкой в процессе порождения имен файлов. х Разрешает доступ к элементам (файлам и подоглавле- ниям) данного оглавления. w Разрешает создавать и исключать элементы оглавле- ния. (В системе UNIX нет отдельных полномочий для этих двух операций.) Чтобы распечатать полномочия для некоторого оглавления, в команде 1s следует задать опцию — d, так как иначе 1s выдаст сведения об элементах оглавления, а не о самом оглавлении. Например, сведения о текущем оглавлении выдаются командой Is — Id . Вывод этой команды имеет вид drwxrwxr—х 5 srb 496 May 5 18:06 . где начальная литера d означает, что этот элемент является ог- лавлением. В некоторых версиях системы команда 1s печатает также имя группы файла (оглавления).
6.3. Дополнительные сведения о файловой системе 165 Ограничения на права доступа к файлам и оглавлениям рас- пространяются на всех обычных пользователей. Единственный пользователь, называемый суперпользователем, освобожден от проверок права доступа. Некоторые операции, например созда- ние оглавлений, могут выполняться только суперпользователем. Это позволяет реализовывать эти операции в виде команд, а не как части операционной системы. Также только суперпользова- телю разрешается изменять владельца или группу файла. Обычно суперпользователь имеет регистрационное имя root. 6.3.4. Группы Как и индентификация владельца, идентификация группы уста- навливается при создании файла. При входе в систему вам на- значается некоторая группа. В некоторых системах всем пользо- вателям назначается по умолчанию одна и та же группа. Иденти- фикацию группы файла можно вывести при помощи опции —g команды 1s. Например, команда Is —1g /etc/motd выводит строку вида rw—rw—г------1 adm 0 Маг 30 20:03 /etc/motd Здесь adm — имя группы. Во время сеанса вы можете изменить свою группу командой newgrp имя-группы при условии что вы — член группы имя-группы. В некоторых системах команда newgrp выводит слово sorry («извините») и при- нудительно осуществляет выход из системы, если вы не являе- тесь членом группы. (Этот недостаток является следствием вы- бранного способа реализации данной команды.) Новые группы создаются администратором системы. Группа может использоваться для объединения пользователей, работаю- щих над общим проектом, для того, чтобы ограничить круг лю- дей, имеющих право доступа к файлам. Этот механизм является громоздким и не пригоден для гибкого использования. С каждым файлом связан как идентификатор владельца^ так и идентификатор группы. Эти идентификаторы представле- ны внутри файловой системы целыми числами и преобразуются в имена (такие, как srb и root) программами типа 1s. Имена поль- зователей и групп наряду с некоторой дополнительной инфор- мацией хранятся в двух файлах — /etc/passwd и /etc/group. Файл паролей /etc/passwd содержит строки вида srb:yKohajlbawhyg:142:3:mh5967,m044:/usr/srb:
166 Гл. 6. Программирование в системе UNIX Двоеточие является разделителем полей. Первое поле — регист- рационное имя пользователя, второе — пароль в зашифрованном виде, третье и четвертое поля — идентификаторы пользователя и группы (в виде целых чисел). Следующее поле содержит инфор- мацию, которая в прошлом использовалась для удаленного ввода заданий. Оно включает регистрационное имя и учетный номер пользователя в местном вычислительном центре. Шестое поле содержит имя оглавления, которое становится текущим при входе в систему. Это то самое регистрационное оглавление, о котором говорилось выше. Последнее поле обычно пусто и используется в случае, когда пользователь запрашивает командный интерпре- татор, отличный от /bin/sh. Файл групп /etc/group имеет сходный формат и состоит из строк вида a68::3:srb,a68 Как и в файле паролей, двоеточие служит разделителем полей. Первое поле представляет собой имя группы, следующее поле — пароль, используемый командой newgrp, третье поле — идентификатор группы. Последнее поле содержит список регист- рационных имен пользователей — членов группы. Имена раз- деляются запятыми. Команда newgrp подобна команде login: она изменяет идентификатор группы процесса. 6.4. ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ ВВОДА-ВЫВОДА 6.4.1. Создание и уничтожение файлов Для создания файлов в программах можно использовать систем- ную функцию creat, описание которой имеет вид int creat(name, pmode) char *name; int pmode; где name — имя файла, a pmode — его полномочия. Обращение к функции creat приводит к тому, что либо создается новый файл, либо подготавливается к перезаписи уже существующий файл (он усекается до нулевой длины). В обоих случаях файл открывается на запись. Напомним, что в файловой системе для защиты используются 9 бит, определяющих полномочия на чтение, запись и выполне- ние для владельца, членов группы и всех прочих пользователей. Права доступа обычно задаются в виде трех восьмеричных цифр. Права доступа, заданные параметром pmode функции creat,
6.4. Дополнительные возможности ввода-вывода 167 модифицируются согласно маске доступа (umask) процесса, соз- дающего файл. Фактически используется выражение ( ~umask ) & pmode Функция creat возвращает дескриптор файла при успешном завершении и —1 в противном случае. Уничтожить файл можно при помощи системной функции unlink, описываемой в виде int unlink(name) char *name; где name — имя файла. Эта функция обычно возвращает нуль, но —1 обозначает, что соответствующий файл не существует или не может быть уничтожен. Системная функция creat не создает оглавлений. Вместо этого для создания оглавлений используется команда mkdir. Анало- гично, исключать оглавления можно только командой rmdir. С физическим файлом может быть связано более одного имени. Первое имя файл получает при создании. Последующие имена (ссылки) образуются при помощи системной функции link (соз- дать ссылку): int link(namen name2) char *namex, *name2; где name2 — альтернативное имя для файла с именем namef. Функция не выполняется, если файл с именем name2 уже сущест- вует. Прямым аналогом этой системной функции является коман- да In: In файл новое-имя в результате выполнения которой старый файл получает новое имя. Создание файла-замка В завершение данного раздела мы рассмотрим еще несколько примеров. Файл-замок является монопольной собственностью своего владельца. Его можно использовать для обеспечения моно- польного доступа к некоторому ресурсу. Файл-замок создается, например, программой, управляющей построчным печатающим устройством, в начальной фазе ее работы. Если случайно начнет выполняться другой экземпляр той же самой программы, он об- наружит файл-замок и прекратит выполнение.
168 Гл. 6. Программирование в системе UNIX Один из способов создания такого файла показан на рис. 6.4. Используемый метод состоит в создании и открытии файла сле- дующим образом: creat("lock", 0); Второй параметр (0) — полномочия создаваемого файла --------------. Создание файла без права доступа на чтение гарантирует, что другая попытка открыть этот файл на чтение обречена на неудачу. Успешное завершение функции creat означает запирание замка. В противном случае файл уже открыт другим процессом. /* * использование: mklock файл»,» */ int ma1n(argc, argv) int argc; char *argv[J; { int rc * 0; char *n; int f; while (argc—• > 1) ( if ((f - creat(*++argv, 0)) < 0) { rc++; }else{ close(f); ) } return(rc); } Рис. 6.4 Команда mklock Данная программа некорректна, если пользователем является суперпользователь, так как отсутствие полномочий на чтение не является для него преградой. Можно применить и другой метод, заключающийся в создании для уже существующего файла альтернативного имени-замка.
6.4. Дополнительные возможности ввода-вывода 169 #define BUFSIZ 512 #define MODE 0644 /♦ rw - для владельца t Г—— для членов группы ♦/ „ /* и прочих »/ int errflg; void error(s) char s(]; ( . write(2, s, strlen(s)); errflg++; } int main(argc, argv) int argc; char »argv[],’ int rd, wt, n; char b[BUFSIZ]; if (argc!—3) ( /* неверное число параметров •/ errorf'usage; ср fromfile tofile\n"); return(l); } if ((rd-open(argv(1], 0)) -- -1) errorC'cp: cannot open input\n"); if ((wt-creat(argv[2), MODE))----------1) errorC'cp: cannot create output\n"); if (errflg--O) { while (<n-read(rd, b, BUFSIZ)) > 0) { if (write(wt, b, n)!-n) { errorC'cp: write error\n”); break; } } 1 return(errflg); } Рис. 6.5 Упрощенный вариант команды ср
170 Гл. 6. Программирование в системе UNIX Команда ср На рис. 6.5 приведена реализация упрощенной версии коман- ды ср. Прежде всего программа проверяет, что задано правиль- ное количество параметров. Если это условие не выполнено, осуществляется возврат из функции main и, следовательно, вы- ход из программы. Если количество параметров правильное, открываются файлы ввода и вывода и, если это удалось сделать, в цикле while выполняется копирование. В программу включена также проверка на ошибочные ситуации при записи — этот во- прос обсуждается далее в разд. 6.6.3. Функция strlen, входящая в системную библиотеку стандартных С-программ, возвращает длину цепочки, переданной ей в качестве параметра. Создание временного файла В командных процедурах часто создаются временные файлы, используемые только во время выполнения данной процедуры. Имеется два оглавления, /tmp и /usr/tmp, в которых любые пользователи могут создавать временные файлы. Файлы, создан- ные в этих оглавлениях, должны быть уничтожены программой, создавшей их. При перезагрузке системы эти оглавления обычно очищаются. Одно из часто используемых в командных процедурах согла- шений заключается в создании файлов с именем /tmp/$$ Этот метод надежен при условии, что ему следуют все пользо- ватели. Команда mktemp, приведенная на рис. 6.6, использует для порождения новых файлов функцию mktemp из библиотеки стан- дартных С-программ. Типичное обращение к команде mktemp имеет вид mktemp /tmp/XXXXXX В результате печатается имя созданного файла, например: /tmp/022780 Это имя можно затем использовать в командных процедурах. Например, в результате выполнения tmp='mktemp /tmp/XXXXXX' переменной языка-оболочки tmp будет присвоено имя временного файла, созданного командой mktemp.
6.4. Дополнительные возможности ввода-вывода И #include <stdio.h> /* * mktemp — создать временный файл */ char ♦mktempO; int main(argc, argv) int argc; char ♦argvfj; ( int rc “ 0; char *n; int f; while(argc— > 1) { n - mktemp(*++argv); if((f - creat(n, 0644)) < 0) {. fprintf(stderr, "mktemp: cannot create '%s’\nl’( rc++; } else ( , fprintf(stdout, "%s\n",-n); close(f); } • return(rc); } Рис. 6.6 Создание временного файла 6.4.2. Ввод-вывод — метод произвольного доступа Файл состоит из последовательности литер, и чаще всего ис- пользуется метод последовательного доступа. Однако файл мож- но читать или записывать и не последовательно путем установки указателя текущей позиции внутри файла при помощи системной функции Iseek. Определение функции Iseek имеет вид long Iseek(fd, offset, whence) int fd; long offset; int whence;
172 Гл. 6. Программирование в системе UNIX Функция Iseek устанавливает указатель текущей позиции файла с дескриптором fd на позицию offset (смещение). Смещение бе- рется относительно позиции, определяемом параметром whence (откуда). Последующее чтение или запись начнется с этой пози- ции. Параметр смещение (offset) является длинным целым, fd и whence — оба целые, причем whence может принимать значения О, 1 или 2. Эти три значения имеют следующий смысл: О Смещение относительно начала файла. 1 Текущая позиция + смещение. 2 Конец файла + смещение. Примеры: lseek(fd, 0L, 2) Указатель текущей позиции устанавливается на конец файла. lseek(fd, 0L, 0) Установка на начало файла без потери данных. (В ран- них версиях системы UNIX вместо функции Iseek ис- пользовалась функция seek; различие между ними за- ключается в том, что в функции seek параметр-смещение является обычным, а не длинным целым.) В ошибочных ситуациях, которыми являются, например, • неопределенный дескриптор файла, • попытка установки указателя текущей позиции для тран- спортера, • попытка выйти за начало файла, функция Iseek возвращает значение —1. Используя Iseek, можно создавать файлы с «дырами». Если записать файл до некоторого места, а затем установить указа- тель текущей позиции за конец файла, то образуется участок файла, не содержащий данных. Этот прием полезен в некоторых приложениях, например при сортировке большого объема дан- ных с использованием хеширования. В действительности же эти дыры не занимают физического пространства в файловой сис- теме. 6.4.3. Состояние файлов Состояние файлов можно определить при помощи системных функций stat и fstat. Обращаясь к функции stat, можно получить информацию о любом поименованном файле. Параметром функ- ции fstat является дескриптор файла; эта функция возвращает
6.4. Дополнительные возможности ввода-вывода 173 информацию об открытых файлах и транспортерах. В остальном эти функции являются полностью аналогичными, поэтому ниже описывается только функция stat. Описание этих двух функций имеет вид int stat(name, but) char «name; struct stat *buf; struct stat { devj st_dev; inoj stjno; unsigned short stjnode; short stjilink; short st_uid; short st_gid; devj stjdev; off jt st_size; timej st_atime; timej stjntime; time t st ctime; ); ' ' #define SJFMT 0170000 /♦ маска для выделения типа файла ♦ / #define- SJFDIR 0040000 /♦ оглавление ♦/ #define SJFCHR 0020000 /♦ специальный литерный *1 # define SJFBLK 0060000 /♦ специальный блочный */ #define SJFREG 0100000 /♦ обычный файл ♦/ #define SJFMPC 0030000 / ♦ му ль типлексируемый спец литерный * / tfdefine SJFMPB 0070000 /♦ мультиплексируемый спец блочный • / #define S1SUID 0004000 /♦ установить идентификатор пользователя*! /* при выполнении */ #define SISGID 0002000 /♦ установить идентификатор группы при выполнении */ #define SJSVTX 0001000 /♦ сохранить программный сегмент *1 Sdefine SJREAD 0000400 / ♦ право на чтение для владельца */ ^define SJWRITE 0000200 / ♦ право на запись для владельца ♦ / ttcfofine S IEXEC 0000100 / ♦ право на выполнение/поиск для владельца ♦ / Рис. 6.7 Системный файл заголовков (sys/stat.h)
174 Гл. 6. Программирование в системе UNIX /* » Вычисление контрольной суммы для списка файлов *1 V. #includfe <sys/types.h> ^include <sys/stat.h> char *ctime(); struct stat STATBUF[1]; int fd; char buf[512); int main(argc, argv). int argc; char ♦argv[J; { int i; for(i - 1; i < argc; i++) { close(fd); if ((fd - open(argvl'i), 0)) < 0) printf("°/os: cannot open\n",’ argv[i]); else{ fstat(fd, STATBUF); chksum(argv[i]); } 1 return(O); } /* main */ chksum(nam) char ♦nam; { register int p, e; register unsigned short sum '» 0; char *c; while(e - readbO) ( for(p = 0; p < e; p++) { if(sum&01) ( sum = (sum»1) I 0x8000;
6.4. Дополнительные возможности ввода-вывода 175 .} else{ sum »“ 1; sum л= buf[p]; } } с = ctime(&STATBUF->st_mtime); if((STATBUF->st_mode&S_IFMT) — SJFREG) { printf("%-14s °/oo\t0/os", nam, sum, c); } } /♦ chksum *1 int readb() register int r; r = read(fd, but, sizeof(buf)); return((r <« 0) ? 0 : r); )• /♦ readb *! Рис. 6.8 Команда chksum int fstat(fd, buf) int fd; struct stat *buf; Структура stat описана в системном файле заголовков <sys/stat.h>. В этом файле определены также константы, которые можно использовать для обращения к полям структуры stat. Содержимое этого системного файла заголовков приведено на рис. 6.7. Поля имеют следующий смысл: st__mode Тип файла. Это поле определяет, является ли дан- ный файл обычным файлом (например, файлом поль- зователя), оглавлением, специальным блочным (без буферизации) или специальным литерным (с буфе- ризацией) файлом. st__uid Идентификатор владельца. st__gid Идентификатор группы владельца. st__size (Относительный) номер байта, записанного послед- ним. st__atime Время последнего чтения из файла (последнего до-
176 Гл. 6. Программирование в системе UNIX ступа к файлу). Это поле не устанавливается при просмотре оглавлений. st__mtime Время последней записи в файл (или время созда- ния файла). На это поле не влияют изменения вла- дельца, группы или полномочий. st__ctime Это время устанавливается при записи в файл или при изменении владельца, группы или полномочий. Все эти поля выводятся командой 1s при задании соответствую- щих опций. Использование информации о состоянии файлов иллюстрируется на рис. 6.8 на примере программы chksum. Программа chksum для каждого параметра выводит строку вида chksum.с 170242 Thu Dec 16 03:32:55 1982 Константа S_____IF REG определена в системном файле заголов- ков <sys/stat.h> и обозначает значение (st_mode&S__IFMT) для обычного файла. На поле st__mode приходится накладывать ма- ску S_______________________IFMT потому, что оно содержит также и биты, опреде- ляющие полномочия. 6.4.4. Организация ввода-вывода для терминалов Для каждого процесса файл /dev/tty представляет собой управ- ляющий терминал, связанный с данным процессом. Этот файл можно использовать в программах, когда требуется выдать со- общение на терминал, даже если файлы стандартного вывода и диагностики переназначены. Его можно также использовать для ввода с терминала или вывода на терминал в случае, если вызываемая команда требует задания имени файла.. Самый первый файл-терминал, открытый процессом, становит- ся управляющим терминалом данного процесса. Обычно терми- нал открывается в процедуре входа в систему (login), и все порож- даемые процессы наследуют этот же управляющий терминал. С управляющего терминала можно возбуждать сигналы выхода и прерывания — этот вопрос обсуждается ниже. Множество процессов, связанных с одним и тем же управляющим термина- лом, называется группой процессов. Любой терминал, связанный с одним из этих файлов, работает в дуплексном режиме. Литеры, набранные на клавиатуре тер- минала, поступают в ЭВМ, где они передаются программе, а так- же отображаются системой обратно на терминал. Литеры можно вводить в любое время, даже когда идет вывод. Имеется ограни- чение на максимальное количество литер, которое можно ввести до того, как их прочитает процесс. Это ограничение определяется размером системных буферов (обычно 256). При выходе за эту границу вводимые литеры просто теряются.
6А. Дополнительные возможности ввода-вывода 177 Ввод с терминала собирается в строку, так что при обраще- нии к функции read выполнение задерживается до тех пор, пока не будет набрана целая строка. Функция read возвращает только одну строку, даже если запрошено большее количество литер. Если запрошено меньшее количество литер, функция read возвращает именно это количество литер. Никакой потери информации при этом не происходит. Путем обращения к систем- ной функции ioctl с соответствующей опцией программа может также потребовать, чтобы чтение выполнялось до окончания ввода целой строки. В процессе ввода обычно выполняется обработка литер сти- рания и отмены. Литера стирания 41= позволяет стирать предыду- щие литеры вплоть до начала строки, а литера отмены @ отме- няет всю набранную строку. Эти две литеры можно заменить другими при помощи системной функции ioctl. Специальный смысл имеет и ряд других литер. Эти литеры перечислены ниже. Они не передаются в читающую программу, если только не был установлен режим ввода без первичной об- работки текста, при котором они теряют свой специальный смысл. А D Формирование признака конца файла для терминала. Все накопленные для чтения литеры немедленно (без ожидания возврата каретки) передаются в про- грамму, а сама литера AD отбрасывается. Если не было накоплено ни одной литеры, функция read вер- нет нуль литер, что является общепринятым при- знаком конца файла. del Посылка сигнала прерывания всем процессам, свя- занным^ данным управляющим терминалом. Если не предусмотрены специальные меры, процессы будут завершены. л\ Формирование сигнала выхода. Этот сигнал ана- логичен прерыванию, за исключением того, что если он не перехвачен, выполняется дампинг па- мяти процесса в файл core текущего оглавления. Дампинг производится только при условии, что файл core существует и имеются полномочия на запись в него или если файл core может быть соз- дан. AS Приостановка вывода на терминал до тех пор, пока не будет введена какая-нибудь литера. AQ Эта литера всегда игнорируется, но в случае, когда она вводится после AS, возобновляется вывод на терминал.
178 Гл. 6. Программирование в системе UNIX При разрыве связи с терминалом всем процессам его группы посылается сигнал «разрыв связи». Этот сигнал формируется например, при исчезновении сигнала с несущей от устройства сопряжения. Если этот сигнал был проигнорирован процессом, то при любом последующем обращении к функции read возвра- щается признак конца файла. Система позволяет устанавливать следующие характеристики терминалов: • Скорость ввода-вывода в бодах. • Паритет при вводе. • Временные задержки для различных литер, таких, как табуляция и новая строка. • Преобразование табуляции в пробелы при вводе и вы- воде. • Литеры стирания, отмены и прерывания. Эти характеристики можно считать и переустановить при помощи системной функции ioctl. Эта функция используется в команде stty. В шестой версии системы UNIX имелись две системные функции — stty (установка характеристик терминала) и gtty (чтение характеристик терминала); в седьмой версии системы они были заменены одной функцией ioctl (управление вводом- выводом). 6.4.5. Транспортеры Два процесса могут обмениваться данными через межпроцессный канал, называемый транспортером. По транспортеру можно пере- давать данные только в одну сторону. Транспортер имеет два конца, представляемых дескрипторами файлов р[0] и р[1], кото- рые получаются в результате обращения к системной функции pipe: int р[2]; pipe (р); Через дескриптор файла р[0] осуществляется чтение из транспор- тера, а через р[1] — запись в транспортер. Значение, возвращае- мое самой функцией pipe, равно 0 при успешном завершении и —1 в противном случае. Если транспортер пуст, то процесс, читаю- щий из него, приостанавливается и будет ждать, пока в транспор- тер не будет записано некоторое количество литер. Аналогично если транспортер заполнен и процесс осуществляет запись в него, то этот процесс будет задержан до тех пор, пока процесс на другом конце транспортера не прочитает некоторое количество литер.
6.5. Процессы 179 Транспортеры обычно создаются общим предком двух процес- сов. Например, при выполнении конвейерной команды а | b оболочка прежде всего создает транспортер, а затем распаралле- ливается с образованием процессов для команд а и Ь. В процессе, созданном для выполнения команды а, стандартный вывод закрывается, а транспортер дублируется и занимает место стан- дартного вывода, как это показано в следующем фрагменте программы: close(l); dup(p[lj); close(p[l]); Системная функция dup возвращает дескриптор файла с наимень- шим свободным номером. Предполагается, что стандартный вы- вод — файл с дескриптором 0 — уже назначен. Аналогичная последовательность действий выполняется и для команды Ь. После того как файлы стандартного ввода и вывода соответствую- щим образом переназначены, для вызова команд а и b исполь- зуется системная функция ехес. Конвейерная форма записи, предоставляемая оболочкой, в большинстве случаев удовлетворяет потребностям пользовате- лей. Однако оболочка позволяет соединять транспортерами толь- ко простую цепочку команд. Более сложные соединения не предусмотрены. 6.5. ПРОЦЕССЫ Согласно терминологии Ритчи и Томпсона (Ритчи, 1978), среда,, в которой выполняется программа, называется образом. Образ включает программу, связанные с ней данные, состояние откры- тых файлов, текущее оглавление. Некоторые атрибуты образов, например идентификатор пользователя и идентификатор группы, доступны пользователю непосредственно, в данном случае через системные функции getuid и getgid. К другим атрибутам, таким, как список порожденных процессов, можно обратиться только косвенно, в данном случае при помощи системной функции wait (ждать). Процесс — это выполнение образа. В системе имеется список поддерживаемых ею процессов. Большинство процессов ожидают либо ввода с терминала или из файла, либо завершения выпол- нения какой-либо системной функции. Список процессов можно распечатать при помощи команды ps. Имеется ограничение (обычно не обременительное) на число одновременно существую- щих процессов как для одного пользователя, так и для всей
180 Гл. 6. Программирование в системе UNIX системы в целом. Размер системной таблицы процессов имеет имя NPROC. Эта величина хранится в исходных модулях системы в оглавлении /usr/sys/h. Ограничение на максимальное число процессов пользователя задается константой MAXUPRC. Для VAX 11/780 типичные значения этих величин — 250 и 50 соот- ветственно. 6.5.1. Исполнение процесса Адресное пространство исполняемого процесса состоит из трех частей: • Код исполняемой программы. Программа часто исполь- зуется совместно несколькими процессами и поэтому защищена от записи. Защита от записи позволяет избе- жать случайного саморазрушения программы (Сопро- граммы не модифицируют свой код). е Сегмент данных, в который пользователь может писать и который не доступен другим пользователям. Эта область начинается сразу за программой. Пользователь может увеличивать или уменьшать размер этой области при помощи системной функции brk. е Не разделяемый стековый сегмент, который располага- ется, начиная от верхней границы памяти, и растет вниз х). Эта область автоматически расширяется по мере надобности. Адресное пространство каждого процесса отлично от адрес- ного пространства других процессов в системе. Взаимодействие между процессами может осуществляться через транспортеры и сигналы. Для управления процессами служат четыре системные функ- ции: fork, ехес, wait и exit. 6.5.2. Системная функция fork Системная функция fork («вилка») порождает две почти идентич- ные копии процесса. Одна из этих копий называется родитель- ским процессом (родителем), а другая — порожденным процес- сом (потомком). Родительскому процессу функция fork возвра- щает номер порожденного процесса, а значение, возвращаемое в порожденный процесс, равно нулю. Если функции fork не уда- лось создать новый процесс, возвращается значение —1. Это может случиться, например, при переполнении системной таб- лицы процессов или если при выполнении fork произошло пре- рывание. От больших адресов к меньшим.— Прим. ред.
6.5. Процессы 181 Порожденный процесс наследует все компоненты образа роди- тельского процесса, включая открытые файлы. Новый процесс имеет свой собственный сегмент данных и стек. Единственными совместно используемыми родителем и потомком ресурсами яв- ляются файлы, которые были открыты в момент распараллелива- ния родительского процесса. Это не вызывает никаких проблем в случае, если родительский процесс ожидает завершения работы потомка. Однако если порожденный процесс исполняется парал- лельно с родительским, то должно соблюдаться некоторое со- глашение о том, кому принадлежат эти открытые файлы. Например, когда оболочка выполняет команду а & она при помощи функции fork создает новый процесс, а затем переназначает в нем стандартный ввод на файл /dev/null. Тем самым оболочка сохраняет за собой ввод с терминала. 6.5.3. Системная функция wait После создания (при помощи функции fork) нового процесса родительский процесс имеет выбор: он может либо исполняться далее параллельно с потомком, либо подождать завершения ра- боты потомка. Системная функция wait приостанавливает обратившийся к ней процесс до тех пор, пока не завершится один из его потом- ков. Если потомков несколько, то порядок их завершения не определен, поэтому для ожидания завершения исполнения конкретного процесса требуется выполнить следующий цикл: int status; /*статус завершения*/ int childpid; ^идентификатор потомка*/ while (wait(&status)!=childpid) Нельзя предполагать, что процессы, о завершении которых сооб- щает функция wait, были порождены выполняемой в данный момент программой. Программа может унаследовать порожден- ные процессы от предыдущего пользователя процесса, так как одна программа может, обратившись к функции fork, создать про- цесс, а затем при помощи функции ехес вызвать другую про- грамму. Ожидать завершения работы порожденных процессов может только их родительский процесс. Если родитель завершает ис- полнение, его потомки наследуются процессом 1.
182 Гл. 6. Программирование в системе UNIX 6.5.4. Системная функция ехес Системная функция ехес замещает выполняемую в данный мо- мент программу новой программой и начинает выполнять ее, передавая управление на ее точку входа. Идентификатор про- цесса функцией ехес не меняется. В случае успешного заверше- ния возврата из функции ехес не бывает, а образ вызывающей программы теряется. Имеются несколько разновидностей функ- ции ехес. Простейшая из них имеет вид int execv(name, argv) char *name; char *argv[J; Параметр name задает имя файла, содержащего программу, ко- торую требуется выполнить (файл типа a.out), a argv — вектор из указателей на параметры — цепочки литер, которые будут переданы в вызываемую программу. Вектор завершается нуле- вым указателем. По соглашению, argv[0] является именем вы- полняемой команды. Вторая разновидность функции ехес описывается как int execl(name, arg0, argn ..., argn, 0); и позволяет задавать параметры явным образом. После выполнения ехес открытые файлы остаются открыты- ми, если только при помощи функции ioctl не установлена соот- ветствующая опция. Это позволяет родителю (например, оболоч- ке) открывать файлы для потомка. Неизменными остаются и со- стояния сигналов. Исключение составляют перехватываемые сигналы — в этом случае состояние сигнала сбрасывается в ну- левое. Не изменяются функцией ехес и действительные идентифи- каторы пользователя и группы п. Однако если среди полномочий заданного для выполнения файла имеется установка идентифи- катора владельца (или установка идентификатора группы), то в качестве эффективного идентификатора пользователя (груп- пы) устанавливается идентификатор владельца (группы) данного файла. Рассмотрим пример. Программа-оболочка перед обращением к функции ехес выполняет переадресацию ввода-вывода путем закрытия и открытия файлов. Если команда запускается как фо- новая, то в качестве стандартного ввода открывается файл /dev/null. Это позволяет избежать путаницы при вводе с терми- нала. Отключаются и порождаемые с терминала сигналы. Х) Действительный идентификатор пользователя (группы) определяет, по чьей инициативе запущен процесс. Эффективный идентификатор пользователя (группы) определяет, чьими правами пользуется процесс.— Прим, перев.
6.5. Процессы 183 Наиболее вероятная ошибка при выполнении ехес заключает- ся в том, что файл с указанным именем name не существует или не является выполнимым. Другая распространенная причина не- удачного завершения ехес, которая особенно часто возникает при использовании литеры * для порождения имен файлов в обо- лочке,— слишком большой список параметров (более 5120 байт). Среди прочих причин — недостаток адресного пространства для инициирования процесса (это более вероятно на PDP 11/45 или 11/70, чем на VAX 11/780). В большинстве программ нет надобности в управлении про- цессом — эту работу может выполнять оболочка, интерпретируя командные процедуры. Если в программе требуется выполнить подкоманду, можно с успехом воспользоваться подпрограммой system из библиотеки стандартных С-программ. 6.5.5. Системная функция exit Процесс может прекратить выполнение (завершиться) либо до- бровольно, обратившись к системной функции exit, либо прину- дительно, получив сигнал. Параметр функции exit трактуется int status, signal; while (wait(istatus) !* childpid) if (status&0200) { /♦ Образован файл core*/ } if (status — 0177) ( /♦ Порожденный процесс приостановлен, но может быть * возобновлен. Этот статус предназначен главным образом для ♦ отладчиков, которые используют для управления другим ♦ процессом системную функцию ptrace } ♦/ signal - status&0177; if (signal —- О) { гс - (status »8)&0377; < /♦ Процесс завершился нормально, Гс • статус завершения. }else{ / * Процесс завершился аварийно, по сигналу ♦ / Рис. 6.9 Анализ статуса завершения процесса
184 Гл. 6. Программирование в системе UNIX как статус завершения процесса. По соглашению, нуль означает успешное завершение, а отличное от нуля значение — аварийное завершение. По статусу завершения процесса, который возвращается ро- дителю функцией wait, можно распознать различные ситуации. На рис. 6.9 показана программа, анализирующая статус завер- шения процесса. Предполагается, что childpid содержит иденти- фикатор порожденного процесса. Данный фрагмент программы выполняется родительским процессом. 6.5.6. Среда процесса Параметры argv и argc функции main позволяют обращаться к яв- ным (позиционным) параметрам, заданным в обращении к ехес. Когда программа начинает выполнение, ехес передает ей допол- нительно еще один массив цепочек литер, называемый средой процесса. Наиболее часто используемые переменные среды были рассмотрены в гл. 4. По соглашению, эти цепочки литер имеют вид имя=значение Приведенная выше функция execv передает эту среду в неизме- ненном виде. Измененную среду можно передать в вызываемую программу при помощи функции execve: int execve(name, argv, envp) char «name; char «argv[ ]; char *envp[ ]; Параметр envp представляет собой вектор указателей на цепочки литер, содержащие среду. Этот вектор завершается нулевым указателем. Среда доступна в вызываемой программе через внешнюю переменную environ, описываемую как char ««environ; В программах на языке С значения переменных среды можно получить при помощи подпрограммы getenv, описание которой имеет вид char «getenv(name) char «name; Подпрограмма getenv ищет в среде цепочку вида имя=значение и возвращает указатель на значение, если имя name найдено. В противном случае возвращается нуль.
6.6. Сигналы и прерывания 185 6.6. СИГНАЛЫ И ПРЕРЫВАНИЯ Программа получает уведомление о различных событиях через аппарат асинхронных сигналов. Примером такого события может служить нажатие клавиши break на терминале. Среди прочих событий — программные ошибки (например, нарушение защиты памяти) и сигнал из другой программы (kill). Обычной реакцией на сигнал является завершение процесса. За исключением SIGK1LL, сигналы могут либо игнорироваться, либо вызывать переход на заданную С-функцию в программе. После некоторых сигналов (см. приведенный ниже список) содержимое файла core позволяет проанализировать ошибочную ситуацию с помощью отладчика adb или sdb. Можно предотвратить запись в файл core, например создав его без права записи. 6.6.1. Сигналы Ниже приведен полный перечень сигналов. Имена сигналов оп- ределены в системном файле заголовков <signal.h>. Сигналы, но- мера которых помечены звездочкой, вызывают запись дампа в файл core, если они не перехватываются или не игнорируются. 1 SIGH UP Разрыв связи с терминалом. Обычно возбуждается при исчезновении несущей в телефонном канале связи, но может быть также сгенерирован путем выполнения stty 0 >/dev/tty 2 S1GINT Прерывание от терминала. Происходит при нажатии кла- виши del или break на клавиатуре терминала. 3* SIGQUIT Сигнал выхода. Возбуждение этого сигнала является обычным способом завершения работы программы в слу- чае, если требуется дамп памяти. Генерируется с тер- минала путем ввода литеры «разделитель файла» л\. 4* SIGILL Попытка выполнить нелегальную машинную команду. 5* SIGTRAP Трассировочное прерывание (используется отладчиком adb). 6* SIG ЮТ Выполнение машинной команды ЮТ (используется от- ладчиком adb). Этот сигнал иногда называется выбросом. 7* SIGEMT Выполнение машинной команды ЕМТ. Используется в не- которых машинах, не имеющих арифметики с плаваю- щей точкой.
186 Гл. 6. Программирование в системе UNIX 8* SIGFPE Исключительная ситуация (переполнение или деление на нуль) при выполнении операций с плавающей точкой. 9 SIGK1LL Уничтожение процесса. Этот сигнал можно использовать для того, чтобы заведомо избавиться от любого своего процесса. Он не может быть ни перехвачен, ни проиг- норирован никаким процессом. 10* SIGBUS Ошибка шины. Этот сигнал обычно возбуждается из-за ошибки в косвенной адресации с использованием указа- теля в С-программе. 11* SIGSEGV Обращение за пределы сегмента. Этот сигнал может возникнуть также из-за ошибки в косвенной адресации или же из-за выхода за пределы сегмента. 12* SIGSYS Неправильный параметр при обращении к системной функции. Этот сигнал не должен появляться, если про- грамма написана на языке С. 13 SIGPIPE Запись в транспортер, из которого некому читать. Это происходит, если получатель завершает работу, остав- ляя пишущую сторону с разорванным транспортером. 14 SIGALRM Сигнал от таймера. Генерируется после обращения к системной функции pause (см. приложение 2). 15 SIGTERM Сигнал программного прерывания. Этот сигнал посы- лается по умолчанию командой kill. Он предоставляет получившему его процессу возможность уничтожить временные файлы и благополучно завершить работу. Если простая команда kill не уничтожила процесс, попробуйте послать сигнал SIGK1LL командой kill —9. 6.6.2. Посылка сигналов Команда kill посылает сигнал процессу с тем же самым эффектив- ным идентификатором пользователя, обращаясь к системной функции kill: int kill(pid, sig) int pid; int sig; которая посылает сигнал sig процессу с номером pid. Если pid равен нулю, сигнал посылается всем процессам, имеющим гот же
6.6. Сигналы и прерывания 187 самый эффективный идентификатор пользователя. Если sig лежит вне допустимого диапазона либо если указанного процесса не существует, функция kill возвращает значение —1. При помощи kill можно послать сигнал только процессам, имеющим тот же самый эффективный идентификатор пользователя. 6.6.3. Перехват сигналов Процесс может либо перехватить сигнал, либо полностью его проигнорировать, либо позволить системе выполнить по сигналу подразумеваемое действие. Если не предприняты специальные меры, все сигналы вызывают завершение работы получающего их процесса. Заказать перехват сигнала, его игнорирование или вос- становить подразумеваемую реакцию на него позволяет систем- ная функция signal. При перехвате сигнала управление пере- tfinclude <signal.h> int sigcnt; handler(a) /♦ Значением 'а' является SIGINT. *! { /♦ Сбросить сигнал для того, чтобы его можно было ♦' /♦ перехватить снова signal (a, handler); / ♦ Зарегистрировать прерывание и возвратить работу */ sigcnt ++; } mainO signal(eiGINT, handler); } Рис. 6.10 Обработчик сигнала дается заданной пользователем С-функции, называемой обработ- чиком сигнала. Например, программа, приведенная на рис. 6.10, обрабатывает сигнал SIGINT при помощи функции handler. При получении сигнала эта функция вызывается с параметром — номером сигнала. В данном примере подсчитывается, сколько раз произошло прерывание от терминала.
188 Гл. 6. Программирование в системе UNIX После перехвата сигнала реакция на него, как правило, из- меняется на подразумеваемую. Если требуется перехватывать сигнал при каждом его возбуждении, в обработчике должно быть еще одно обращение к функции signal. Чтобы восстановить подра- зумеваемую системную реакцию на сигнал SIGI NT, нужно об- ратиться к функции signal следующим образом: signaI(SIGINT, SIG_DFL); Установка режима полного игнорирования того же сигнала вы- полняется так: signal(SIGI NT, SIG_IGN); Функция signal возвращает предыдущее состояние указанного сигнала. Помимо сказанного выше, нормальное выполнение программы может быть нарушено только из-за неудачного завершения обра- щений к системным функциям. Вызов системной функции можно повторить, если кодом ошибки в еггпо является EINTR. Про- грамма ср на рис. 6.5, например, при прерывании все-таки пе- чатает сообщение об ошибке. Этого можно избежать при помощи оператора if(errno != EINTR) еггог("..."); 6.6.4. Фоновые процессы При выполнении функции fork порождаемый процесс наследует состояния сигналов своего родителя. По соглашению, если в на- чале работы процесса (после ехес) сигнал находился в состоянии игнорирования, его состояние никогда не изменяется. Это позво- ляет запускать с терминала процессы, которые будут затем вы- полняться как фоновые, и на них не будут оказывать никакого воздействия сигналы прерывания и выхода, возбуждаемые с тер- минала. Чтобы это соглашение не нарушалось даже на короткое время, при установке сигналов используется следующий надеж- ный прием: if (signal(SIGI NT, SIG_IGN)! = S1G_IGN) signal(SIGI NT, handler); Тем самым сигнал с состоянием SIG___IGN всегда остается в том же состоянии. Для выполнения подкоманды в С-программе можно восполь- зоваться подпрограммой system, приведенной на рис. 6.11. Во время ожидания завершения работы порожденного процесса сиг- налы прерываний (и выхода) игнорируются; порожденный про- цесс наследует состояния этих сигналов от родителя. После за-
6.6. Сигналы и прерывания 189 вершения порожденного процесса в родительском процессе вос- станавливаются первоначальные состояния сигналов и осуществ- ляется возврат из подпрограммы. #include <signal.h> int system(name, argv) char *name; char ♦argvf]; int pid; int status; int del; int quit; f * игнорировать сигналы в родительском процессе ♦ / del = signaKSIGINT, SIGJGN); quit ~ signal(SIGQUIT, sTgJGN); switch (pid « fork()) { case 0; /♦ Выполняется порожденным процессом *7 /*. сбрасываются сигналы в порожденном процессе ♦ signaKSIGINT, del); signal(SIGQUIT, quit); exec(name, argv); / * Неудачное завершение exec в порожден - ♦ / J*ном процессе */ / * причина - в еггпо ♦ / exit(1); case —1: /* Неудачное завершение fork ♦ / status ~ 0; break; default: 7 * Ожидание завершения порожденного процесса ♦ f / ♦ Выполняется родительским процессом while (wait(&status)!*=pid); } signaKSIGINT, del); signaKSIGQUIT, quit); return(status); 1 Рис. 6.11 Подпрограмма system
ГЛАВА 7 ПОДГОТОВКА ДОКУМЕНТОВ Подготовка текстов, писем, докладов и книг — трудоемкий процесс, требующий большого внимания к мелочам. Система UNIX предоставляет средства, облегчающие подготовку докумен- тов. Среди этих средств — программы форматирования текстов, которые позволяют задавать размер страницы, длину строки, поля, интервал между строками ц шрифты. Другие средства, используемые совместно с этими форматорами текстов, позволяют печатать таблицы и математические формулы. Перечисленные средства вместе с редактором текстов значи- тельно облегчают подготовку первого варианта и последующую корректировку документов. Имеются также программы для вы- явления орфографических ошибок, создания предметных указа- телей, обнаружения плохо или неправильно построенных фраз. В целом при умеренных затратах можно получить довольно качественный результат. Если же подходящего средства нет, его часто можно сравнительно легко создать, воспользовавшись методами, описанными в этой главе. 7.1. ФОРМАТОРЫ ТЕКСТОВ nroff И troff Основными инструментами для подготовки документов являются форматоры текстов troff (произносится ти-роф) и nroff. Для вы- вода текста на устройства типа пишущей машинки используется nroff, а для печати с полиграфическим качеством используется troff. Обе программы произошли от более раннего форматора текстов roff, имеющегося в системе CTSS (Крисмэн, 1965). Вход- ные данные содержат как текст самого документа, так и инструк- ции, задающие желаемый формат вывода. Вывод может выпол- няться на различные устройства, начиная с простейших терми- налов, работающих в коде ASCII, и кончая фотонаборными уст- ройствами с высокой разрешающей способностью. Большая часть этой главы посвящена описанию форматора nroff, который ис- пользуется главным образом для вывода текста на устройства
7.1. Форматоры текстов nroff и troff 191 типа пишущей машинки (например, DASI-450 или Diablo Hi- term) и на построчные печатающие устройства. С небольшими оговорками nroff и troff совместимы, и для них может исполь- зоваться один и тот же входной текст. Документы можно печа- тать на самых разнообразных устройствах, в том числе на мат- ричном печатающем устройстве (например, Versatec V80), ла- зерном печатающем устройстве Imagen или на фотонаборном уст- ройстве (например, APS-5). Все, что говорится в этой главе о форматоре nroff, относится также и к форматору troff. Форматор troff упоминается только при описании возможностей, отсутствующих в nroff (например, изменение типа шрифта). Если встретился запрос, имеющийся только в troff, nroff обычно пропускает его и продолжает обра- ботку. Список запросов форматора troff приведен в приложе- нии 7. Форматор nroff позволяет управлять процессом форматиро- вания текстов, в частности задавать размер страниц, длину строк и интервал между строками, а также предоставляет средства, позволяющие перемещаться по странице по горизонтали и по вертикали произвольным образом, делать отступы и выполнять табулирование. Можно также устанавливать режим с заполне- нием строк (с выравниванием по правому краю или без выравни- вания) и задавать способ переноса. Форматор troff помимо этого позволяет управлять типом шрифта и размером литер и обладает более высокой разрешающей способностью. В nroff можно непосредственно воспользоваться всеми этими средствами форматирования. Кроме того, имеются средства, позволяющие писать программы для nroff. Предусмотрены тек- стовые переменные и условные запросы, а также числовые ре- гистры и обычные арифметические операции. Можно описывать макроопределения и обращаться к ним; это позволяет создавать библиотеки запросов. Можно устанавливать ловушки, позволяю- щие выполнять заранее определенную последовательность запро- сов при достижении заданного места на странице. Текст можно перенаправить в поименованный буфер, что позволяет накапли- вать примечания и выводить их в конце страницы. 7.1.1. Основные принципы подготовки документов Данные, передаваемые для обработки форматору troff, содержат как собственно текст документа, так и инструкции (или запросы), описывающие, в каком виде должен быть распечатан этот текст. Запросы форматора troff начинаются со специальной литеры: точки . или апострофа ' . Даже если в тексте нет ни одного запроса, troff все равно будет форматировать его некоторым подразумеваемым образом. Для получения вывода в требуемом
192 Гл. 7. Подготовка документов виде можно было бы затем добавить необходимые запросы. Такой подход редко используется. Документы подготавливаются с ис- пользованием пакетов запросов, которые обеспечивают обработ- ку абзацев, колонтитулов, иллюстраций и названий разделов. Перед тем как ввести документ в машину, вы должны опреде- лить его формат и выбрать пакет макроопределений, который бу- дете использовать. Имеются пакеты макроопределений общего назначения, которые пригодны для широкого круга приложений. Например, в фирме Bell Laboratories для подготовки внутренней документации используются библиотеки —ms и —mm. Пакет —ms поставляется вместе с системой UNIX. Он описан в прило- жении 10. В вашей организации может существовать свой собст- венный пакет, отвечающий ее потребностям. Для оформления заголовков, абзацев и иллюстраций следует использовать макроопределения. Например, при подготовке английского оригинала этой книги для создания заголовков ос- новных разделов использовался запрос вида . SH 2.1 Для того чтобы создать абзац без отступа, в строке, предшествую- щей тексту этого абзаца, ставится запрос .LP Аналогично ,РР ставится перед абзацем с отступом в первой строке. Сдвинутый вправо абзац с висячей меткой (а) вида (а) можно было бы записать как .IP (а) Эти макроопределения аналогичны макроопределениям из макро- пакета —ms. Однако, для того чтобы стиль книги соответствовал стилю издательства, в эти макроопределения были внесены неко- торые изменения. Использование макроопределений позволяет откладывать окончательный выбор формата текста и впоследствии менять его в зависимости от текущих потребностей. Меняя макропакет сле- дует проявлять осторожность при использовании запросов самого форматора nroff. Где это возможно, к ним лучше не прибегать, поскольку это может повлиять на работу макропакета. В любом
7.1. Форматоры текстов nroff и troff 193 случае проверьте сначала, нет ли в макропакете нужного вам средства. Эта глава знакомит с некоторыми из возможностей, предо- ставляемых форматорами nroff и troff, в том числе с библиотекой макроопределений, использовавшейся при подготовке этой кни- ги. В этой главе содержатся также сведения о nroff и troff, до- статочные для подготовки несложных документов, таких, как письма и диаграммы. Однако написание макроопределений nroff для сложных задач форматирования (например, для вывода в две колонки) требует более глубокого знания системы и под силу только хорошо разбирающимся в nroff и troff программистам. При подготовке документов для nroff рекомендуется избегать длинных строк. Каждое предложение следует начинать с новой строки и завершать строку на каждом знаке препинания. Это поз- воляет легко вносить изменения. Документ большого размера следует размещать в нескольких файлах. Например, каждая гла- ва этой книги хранилась в отдельном файде, что позволяло легко устанавливать принадлежность текста некоторой главе при использовании средств типа grep. Форматирующие макрозапросы вставляются прямо в текст документа. Документы можно составлять или непосредственно за терминалом, или вводить их с рукописи автора, вставляя, где надо, макрозапросы. Вызов форматора nroff Форматор nroff вызывается командой nroff [ опции ... ] документ Весь, документ будет распечатан, начиная, по умолчанию, со страницы с номером 1. Приведем некоторые из имеющихся опций: —ms Использовать макропакет, определяемый s. —пр -Установить номер первой страницы равным р. —о список Распечатать только страницы с номерами, задан- ными в списке. —sn Делать останов после печати каждых п страниц. Например: troff —оЗ—5,8,12— chapterl или nroff —si chapter4 По опции —о распечатываются только перечисленные в списке страницы. В первом примере будут распечатаны страницы с 3-й по 5-ю, 8-я и с 12-й до конца документа. Во втором примере 7 С. Баурн
194 Гл. 7. Подготовка документов использование опции —si приведет к тому, что nroff будет при- останавливать печать после каждой страницы и ждать нажатия клавиши возврата каретки для продолжения работы. Этим можно воспользоваться, например, когда требуется в пишущую, машин- ку заправлять отдельные листы специальной бумаги. 7.1.2. Простые запросы В простейшем случае nroff считывает входные строки до тех пор, пока не будет накоплено достаточное количество слов для запол- нения одной выходной строки. Если входная строка начинается с пробела, то все, что было накоплено, выводится, и начинает накапливаться новая строка; в начало этой строки помещается то же количество пробелов. Новая выходная строка также начи- нается в случае, если встречается пустая входная строка. Запросы форматора nroff записываются в отдельной строке и начинаются литерой . в первой позиции этой строки. На- пример, .sp эквивалентно пустой входной строке. Функции аналогичны запросам; они могут помещаться внутри строк текста и вводятся литерой \ . Например, комментарий для nroff записывается при помощи литер \'' : .sp \"вывод пустой строки Все запросы и функции вставляются в текст документа. Их имена состоят из одной или двух литер. Для имен макро справедливо то же правило. Запросы могут иметь один или более параметров, разделенных пробелами. По запросу .sp 5 будет пропущено 5 строк, и вывод возобновится о начала новой строки. Обычно строки выводятся через один интервал. При подго- товке проекта документа вывод через два интервала оставляет место для внесения правки. Запрос .1s 2 устанавливает вывод строк через два интервала. Вывод через один интервал можно восстановить, воспользовавшись запросом •1s 1
7.1. Форматоры текстов nroff и troff 195 Строки можно центрировать с помощью запроса .се. Напри- мер, .се 4. Оболочка порождает заголовок 4. Оболочка При помощи запроса .се с целым числом в качестве параметра можно центрировать несколько последовательных строк. В nroff заголовок можно подчеркнуть, воспользовавшись запросом .ul, например: .се .ul 4. Оболочка В troff по этому запросу заголовок будет напечатан курсивом: 4. Оболочка Управление полями Можно управлять размером как левого, так и правого полей страницы. Поле слева изменяется запросом отступа границы .in. Например, .in 4 сдвинет левый край в позицию четвертой литеры в строке. Запрос .in 4-4 увеличит отступ на 4 позиции, а .in —4 уменьшит этот отступ на 4 позиции. В начале абзаца первая строка часто сдвигается относительно остальных строк. Запрос временного отступа .ti выполняет заданный сдвиг только для одной следующей выходной строки. Например, .ti 4-3 сдвинет левый край строки на 3 позиции вправо. Полем справа можно управлять, задавая длину строки. Запрос .11 4i установит длину выводимых строк, равную 4 дюймам. Перво- начальная длина строк — 6.5 дюйма. Длину строк можно уве- 7*
196 Гл. 7. Подготовка документов личить или уменьшить, используя знаки + или —. Например, запрос .11 -8 укоротит строку на 8 литер. Заполнение и выравнивание строк Когда nroff накопит достаточное количество слов для того, чтобы заполнить одну выходную строку, выполняется выравнивание строки по правой границе, после чего строка выводится. Для выравнивания между словами вставляются пробелы. В форма- торе troff расстояние между словами может быть очень малень- ким, равным одной трети ширины буквы т. Запрос .па устанавливает режим вывода строк без выравнивания по правому краю. В этом режиме дополнительные пробелы между словами не вставляются. Для восстановления режима выравнивания по правому краю используется запрос .ad Запрос .nf отменяет режим заполнения выводимых строк. После этого запроса выводимые строки не заполняются и не выравниваются. Вхюдные строки копируются в неизменном виде в файл вывода независимо от их длины и расстояний между словами. Режим заполнения строк восстанавливается запросом .fi Для того чтобы разбить выводимый текст на две строки, исполь- зуется запрос .Ьг. Этот запрос вызывает разрыв строки', теку- щая строка выводится, и начинается накопление новой строки. Для того чтобы правильно расположить выводимый текст относительно физической страницы, всю страницу можно сме- стить при помощи запроса • .ро Первоначально смещение страницы устанавливается равным О в системе nroff и 26/27 дюйма в системе troff. Соотношение между запросами, задающими длину строки, сдвиг края, центрирование и смещение страницы показаны на рис. 7.1. Длина строки измеряется от левого края страницы до правого. Текущий сдвиг также отсчитывается от левого края.
7.1. Форматоры текстов nroff и troff 197 Запросы 11, in и ро без параметров восстанавливают предыду- щие значения соответствующих величин. Запоминается только одно предшествующее значение, поэтому вложенность лучше всего выражать увеличением и уменьшением соответствующих величин на некоторое число. DO in | се II Рис. 7.1 Поля в системе nroff Форматор nroff при выводе обычно выполняет перенос слов. Перенос слов во всем последующем тексте можно запретить за- просом .nh Запрос .hw слово запрещает перенос одного конкретного слова. Можно также задать предпочтительные варианты переноса слова, например: .hw in—di—vid—ual Размещение текста no вертикали Смещение по вертикали можно задавать либо относительно теку- щей позиции, либо относительно начала текущей страницы. Например, запрос .sp 1.5i выполняет сдвиг на 1.5 дюйма вниз относительно текущей по- зиции, а запрос .sp |1.5i выполняет сдвиг на 1.5 дюйма относительно верхнего края теку- щей страницы. Для того чтобы сдвинуться назад на одну строку, задается отрицательное расстояние, например: .sp —1 Обычно размер страницы устанавливается по умолчанию равным 11 дюймам (28 см); его можно изменить запросом .pl Сдвиги разрешается выполнять только в пределах текущей страницы.
198 Гл. 7. Подготовка документов Поскольку ряд устройств не выполняет сдвиг назад или на полстроки вперед, предусмотрен специальный фильтр col, кото- рый буферизует страницу и печатает строки в нужном порядке. Расстояния Допустимый размер сдвигов, выполняемых при выводе, опреде- ляется разрешающей способностью устройства вывода. Построч- ное печатающее устройство допускает сдвиг только на одну строку, в то время как устройство Diablo Hiterm имеет вертикаль- ное разрешение в 1/48 дюйма. Фотонаборное устройство CAT, которое использовалось первоначально при разработке troff, имеет разрешающую способность 1/144 дюйма по вертикали и 1/432 дюйма по горизонтали. Единица измерения расстояния, используемая в troff, зависит от устройства вывода. Для устрой- ства APS-5, которое использовалось при подготовке английского оригинала данной книги, эта величина равна 1/723 дюйма. Для nroff единица расстояния равна 1/240 дюйма. В запросах, в которых задаются расстояния, можно исполь- зовать единицы, указанные в таблице. Таблица 7.1 Единица Смысл Размер i дюймы с сантиметры 1/2.54 дюйма Р пункты 1/72 дюйма Р цицеро 1/6 дюйма и базисные единицы зависит от устройства m эм ширина буквы m п эн ширина буквы п (1/2 эм) V расстояние между стро- ками переменный Если единица измерения не задана, подразумеваемое значение зависит от запроса. Для запросов, управляющих размещением текста по горизонтали, используется размер буквы т(эм). В сис- теме troff размер единицы эм зависит от текущего размера букв.
7.1. Форматоры текстов nroff и troff 199 В запросах, управляющих размещением текста по вертикали, под- разумеваемой единицей измерения является расстояние между строками. Расстояние между строками можно также изменить, используя запрос .vs (см. ниже). Разрешается использование десятичных дробей и арифметических действий. Полученные результаты всегда округляются в соответствии с разрешающей способностью устройства вывода. Внутри выражений допускают- ся круглые скобки и арифметические операции +, —, /, * и % (взятие остатка от деления). В системе nroff все операции имеют равный приоритет; следует использовать круглые скобки. Поскольку nroff округляет расстояния в соответствии с раз- решающей способностью по горизонтали или по вертикали, могут возникнуть погрешности, в особенности когда используются спе- цификации в абсолютных единицах, например: .sp 16.7511 Табуляции и колонки Таблицы и колонки на рисунках иногда бывают достаточно про- стыми, и их можно форматировать, используя непосредственно nroff. Однако обычно лучше воспользоваться препроцессором, таким, как tbl, предназначенным специально для подготовки таблиц. Символ табуляции во входной строке преобразуется при выводе в последовательность пробелов с таким расчетом, чтобы последующий текст попал в следующую колонку. По умолчанию позиции табуляции устанавливаются в системе nroff через каж- дые 8 литер, а в troff — через каждые 0.5 дюйма. Табуляции можно переустанавливать. Например, Ла 8 40 60 установит позиции табуляции на расстоянии 8, 40 и 60 эм. Если текст уже миновал позицию табуляции, то на пишущих машинках с механическим табулятором каретка автоматически передви- гается до следующей позиции табуляции. Система nroff ведет себя аналогично. По умолчанию, колонки выравниваются по левому краю. Для колонок чисел предусмотрена возможность выравнивания по правому краю; имеется также возможность центрирования колонок. Например: •nf Ла .5i 1.51 3.5iR .ul ф Фамилия ф Поставщик ф Цена ф Kubik ф Computer Technology ф $183 ф Anderson ф System Memories ф $79 .fi
200 Гл. 7, Подготовка документов В этом примере (ф обозначает символ табуляции) третье поле будет выровнено по правому краю относительно позиции, находя- щейся на расстоянии 3.5 дюйма: Фамилия Поставщик Цена Kubik Computer Technology $183 Anderson System Memories $79 Для центрирования поля используется спецификация С. Для заполнения позиций между колонками обычно исполь- зуется пробел, однако можно заменить его на другую литеру, воспользовавшись запросом tc. Например, в результате выпол- нения .nf Ла 20 .ul ФамилияфТелефон .tc. Hal Alles ф ХХХХ .fi будет напечатано Фамилия Hal Alles..... Телефон ХХХХ Команда Лс без параметров восстанавливает пробел в качестве литеры, используемой для заполнения позиций между колон- ками. Команда .nf в начале таблицы отменяет режим заполнения строк. Управление страницами Размер страницы обычно равен 11 дюймам. Его можно изменить запросом установки размера страницы, например: .pl 6i Для того чтобы прервать заполнение текущей страницы и начать новую, можно воспользоваться командой .Ьр. Прерывания стра- ниц обычно используются в ловушке конца страницы (см. разд. 7.1.3). При выводе таблицы может оказаться необходимым, чтобы вся таблица размещалась на одной странице. Для этого исполь- зуется запрос .пе. Например, по запросу .пе 6 вывод будет продолжаться в текущую страницу, если в ней может поместиться еще не менее 6 строк; в противном случае выполняется переход на следующую страницу.
7.1. Форматоры текстов nroff и troff 201 Колонтитулы Запрос .tl печатает колонтитул, состоящий из трех частей. Первая часть выравнивается по левому краю, вторая центри- руется, а последняя выравнивается по правому краю. Например, запрос .tl 'Февраль 3, 1982'ПРОЕКТ'Глава 7' напечатает Февраль 3, 1982 ПРОЕКТ Глава 7 В качестве ограничителя можно использовать любую литеру (в данном случае '). Ограничителем является первая встретив- шаяся после имени запроса литера, отличная от пробела и ли- теры табуляции. Длина строки и длина колонтитула не зависят друг от друга. Текущий отступ не влияет на колонтитул, однако колонтитул зависит от смещения страницы. Для установки длины колонти- тула служит запрос .It. 7.1.3. Дополнительные возможности Макро Макро позволяет задать имя для группы запросов или некоторого фрагмента текста и вызывать их по этому имени. Макро следует определять до его использования, так как макрозапросы с неоп- ределенными именами игнорируются. При написании макро- определения можно использовать уже описанные макроопреде- ления. Например, при подготовке английского оригинала этой книги для оформления абзацев первоначально использовалась последовательность запросов .sp .ti 4 .ne 2 задающая пропуск одной строки и отступ длиной 4 эм в первой строке абзаца. Эти запросы составили макроопределение РР: .de РР •sp .ti 4 .ne 2 Первая строка .de РР является началом макроопределения и за- дает имя макро — РР. Конец макроопределения обозначается
202 Г л. 7. Подготовка документов литерами .., помещаемыми в отдельной строке. В эту строку нельзя включать комментарий. Каждый абзац в последующем тексте теперь начинается с вызова РР, например: .РР Во многих макробиблиотеках... Во многих макробиблиотеках для имен макро используются прописные буквы, что позволяет избежать совпадения этих имен с зарезервированными именами форматора nroff. При определе- нии новых запросов следует избегать употребления имен, исполь- зуемых в самой системе troff или в вашей макробиблиотеке. Для получения списка имен, определенных в библиотеке, можно вос- пользоваться запросом .pm. В макрозапросах можно задавать параметры; они помещаются в той же строке, что и вызов. Например, макроопределение, форматирующее заголовки разделов английского оригинала этой книги, вызывается так: .SH 7.2 "Получение итогового документа" Параметры разделяются пробелами. Еслй внутри параметра со- держится пробел или символ табуляции, этот параметр можно заключить в двойные кавычки. В данном примере первым пара- метром является 7.2, а вторым — Получение итогового документа. В макроопределении можно использовать до 9 параметров; они доступны как \$1, \$2, ... . Если параметр не задан при вызове, его значением считается пустая цепочка литер. В данном примере SH определяется следующим образом: .de SH \"заголовок раздела: .SH номер-раздела название .sp \\$2 В этом макроопределении содержится также комментарий, введенный с помощью литер \", в котором описано назначение макро и его параметры. . Макроопределение считывается дважды: когда оно опреде- ляется и когда его вызывают. В обоих случаях по'мере считыва- ния текста макро выполняются запросы форматора nroff. В при- веденном выше определении макрозапроса SH для того, чтобы предотвратить интерпретацию \$1 во время первого просмотра макроопределения, перед\$1 требуется поместить литеру \. Далее, если бы первым параметром был запрос форматора nroff, скажем .Ьр, произошел бы разрыв печати страницы. Литера авто- регистра \& служит для того, чтобы скрыть действие точки в начале строки.
7.1. Форматоры текстов nroff и troff 203 Пара двойных кавычек внутри параметра, заключенного в двойные кавычки, передается в макроопределение как одна двой- ная кавычка, например: .SH ... "Обработка литеры "" в системе troff" Страничные ловушки Описанные выше запросы позволяют легко форматировать обыч- ный текст, однако вскоре возникает потребность в средствах уп- равления страницами. Никаких готовых средств для формирова- ния колонтитулов вверху и внизу страниц nroff не предоставляет. Вместо этого можно установить ловушку на определенную по- зицию по вертикали внутри страницы. При достижении этой позиции выполняется заданный запрос. Часто устанавливаются две ловушки, одна в начале страницы .wh 0 ... а другая недалеко от конца страницы. Например, .wh —5 РЕ устанавливает ловушку на расстоянии 5 строк от конца каждой страницы. После того как выведется строка, отстоящая на 5 строк от конца страницы, выполняется макрозапрос РЕ. Макроопре- деление конца страницы РЕ описывается отдельно от определения ловушки. Например, в макроопределении .de РЕ 'sp 2 Л1 руководство по UNIX: Г я редакция 1971: Стр %: Ър задаются пропуск двух строк и печать колонтитула. В качестве ограничителя используется литера :, поскольку апостроф ' встречается в тексте заголовка. Если перед запросом стоит знак ', а не ., то перед его выпол- нением вывод частично сформированной строки не производится. Запрос 'sp 2 в других отношениях идентичен запросу .sp 2 и пропускает 2 строки. Когда используется литера ., nroff вы- полняет разрыв печати строки так же, как и по запросу .Ьг, а использование литеры ' приводит к переносу частично сфор- мированной строки на следующую страницу. Прерывание печати строки происходит, когда входная строка начинается с пробела или является пустой строкой, или когда встречается один из следующих запросов: .bp .br .се .fi .nf .sp .in .ti
204 Гл. 7. Подготовка документов Вместо 'tl может использоваться .tl, поскольку запрос Л1 не вызывает перехода на следующую строку (хотя использова- ние ' дает тот же результат). Если в колонтитуле встречается специальная литера %, она заменяется на номер текущей страницы в десятичном виде. Запрос .af позволяет изменить формат, по которому печатается номер страницы. Например, .af % i вызывает печать номера страницы малыми римскими цифрами: i, ii, iii и т. д. При написании макроопределений для ловушек следует со- блюдать осторожность, особенно когда в колонтитуле исполь- зуются шрифт другого типа или буквы другого размера. Это обсуждается ниже в разделе, посвященном среде выполнения. Сдвиги по горизонтали и вертикали Для сдвига по вертикали внутри строки служат функции \d, \u и \v. Сдвиг на половину строки вниз выполняет функция \d, а сдвиг на половину строки вверх — функция \и. Функ- ция \v обеспечивает произвольный сдвиг вниз, например: \v'li' или вверх, например: \v'—11' Сдвиг на полстроки используется для печати верхних и нижних индексов. Например, X\dl\u порождает Х1 Сдвиги вниз и вверх должны быть сбалансированы; в противном случае можно получить нежелательный результат. Аналогичным образом с помощью функции задается сдвиг по горизонтали. Например, символ для обозначе- ния встроенного документа в языке-оболочке << записывается как <\h'—.2m'< Для большей наглядности эти две литеры сдвигаются поближе друг к другу.
7.1. Форматоры текстов nroff и troff 205 Расстояние, на которое надо выполнить сдвиг, может зависеть от длины цепочки литер. Для определения этой длины предназ- начена функция \w. Например, \w'do' дает длину цепочки символов do в базисных единицах. Напри- мер, do\h'—\w'do'u'do выделяет слово do при выводе на печатающее устройство. Это' слово напечатается один раз, затем функция \h выполнит воз- врат на то же место, и слово напечатается еще раз. Внутри функции \w могут присутствовать изменения типа и размера шрифта, однако эти изменения не оказывают влияния на окру- жающий текст. Символ и после функции \w, строго говоря, не обязателен, поскольку результат этой функции выдается в ба-, зисных единицах. Тот же эффект двойной печати можно получить с помощью функции \к, которая помечает текущую горизонтальную по- зицию в строке и запоминает результат в некотором регистре. Например, \kxdo\h' | \п хи 'do также напечатает слово do дважды на одном месте. Функция \кх запоминает горизонтальную позицию в регистре х, а функция \h'|...' выполняет сдвиг в заданную абсолютную позицию по горизонтали. Вертикальную позицию на странице можно пометить с по- мощью запроса .mk. Вывод продолжается как обычно, а для возврата в ту же самую позицию по вертикали внутри страницы используется запрос .rt. Это полезно при выполнении . вывода в несколько колонок. Например, в результате обработки фрагмента .mk о е > t ! : п .rt .in +4 Номер страницы нечетный. Номер страницы четный. Использование troff. Использование nroff. .in —4
206 Гл. 7. Подготовка документов будут напечатаны две колонки: первая с текущим отступом, а вторая со сдвигом на 4 эм, как показано ниже. о Номер страницы нечетный. е Номер страницы четный. t Использование troff. п Использование nroff. Если высота колонок различна, следует принять меры для того, чтобы после вывода второй колонки вернуться в правильное место. Можно рисовать вертикальные и горизонтальные линии. Функция \1'1Г нарисует горизонтальную линию длиной в 1 дюйм (вот такую: _____________). Можно также задать литеру, которой будет нарисована строка. Например, функция \l'li.' нарисует Функция \L аналогична \1, однако рисует линии по вертикали. Например, \кх\1'Н'\Ь'2'\Г—li'\h'|\nx'\L'—2' нарисует прямоугольник шириной в 1 дюйм и высотой в 2 строки: Условное форматирование Запросы и текст могут обрабатываться условно в зависимости от выполнения некоторого условия. Можно задавать как арифмети- ческие сравнения, так и сравнения цепочек литер. Простейшая форма условного запроса имеет вид .if с любые-данные Если условие с истинно, любые-данные обрабатываются систе- мой; любые-данные могут представлять собой текст или другие запросы форматора nroff. Список условий, встроенных в систему nroff, приведен на рис. 7.2.
7.1. Форматоры текстов nroff и troff 207 Например, условные запросы .if е Л1 '%'" .if о Л1 "'%' для четных страниц помещают их номера слева, а для нечет- ных — справа. Для проверки других условий (например, первая страница документа) следует использовать регистровые перемен- ные, предусмотренные в форматоре nroff. Имя Условие О Номер текущей страницы нечет- ный. е Номер текущей страницы чет- ный. t п Форматор troff. Форматор nroff. Рис. 7.2 Условия, проверяемые системой nroff Имеется несколько других видов условий: .if N любые-данные Любые-данные обрабатываются, если значение N больше нуля. ЛС 'текст1'текст2' любые-данные Любые-данные обрабатываются, если обе цепочки литер одина- ковы. Для отрицания условия можно использовать операцию I. Таким образом, .if !N любые-данные обрабатывает любые-данные, если N^O. Имеется также условный запрос типа if-then-else: .ie с любые-данные1 .el любые-данные2 Если с истинно, то обрабатываются любые-данные^; в противном случае обрабатываются любые-данные2. Если требуется выполнить несколько запросов, их можно заключить в скобки \{ и \}, как показано на рис. 7.3.
208 Гл. 7. Подготовка документов .ie \\n(sw \{ .nr sw О .in О .Ьр .ns \} .el \{ . ПГ SW 1 .rt .in I3i \} Рис. 7.3 Пример использования условного запроса Текстовые регистры Любую часто используемую последовательность литер можно определить как цепочку. Так же как и макро, цепочки имеют имена, состоящие из одной или двух литер. Имена цепочек и мак- ро принадлежат одному и тому же множеству имен, поэтому, если определить цепочку с именем хх, она заменит макро с таким же именем. Запрос определения цепочки имеет вид .ds хх цепочка где цепочка начинается с первой литеры, отличной от пробела и табуляции, и заканчивается в конце строки. Начальная двой- ная кавычка в цепочке отбрасывается; это позволяет определять цепочки, начинающиеся с пробелов. Например, нижний индекс 1 можно было бы определить так: .ds si \dl\u где \d и \u — сдвиги на полстроки вниз и вверх соответствен- но. Цепочка si используется в последующем тексте: x\*(sl и порождает xl. Имя может состоять и из одной литеры. Например, в nroff можно смоделировать жирную точку с помощью функции \о, наложив на о литеру +: .ds b \о'+о' На цепочку b в тексте можно ссылаться так: \*Ь -
7.1. Форматоры текстов nroff и troff 209 В цепочку можно вставить функцию перемещения по гори- зонтали. Например, символ встроенного документа, использо- вавшийся в гл. 4, определяется как .ds НЕ <\h'—.2т'< и используется в тексте как \*(НЕ. Числовые регистры В форматоре nroff имеется ряд числовых регистров, предназна- ченных для хранения числовых значений. Можно присваивать значения регистрам, увеличивать и уменьшать эти значения и использовать их в тексте. Имена регистров состоят из одной или двух литер; обращение к регистру имеет вид \пх или \п(хх Эти имена не зависят от имен, которыми обозначаются макро и цепочки. Регистры можно использовать в арифметических вы- ражениях и непосредственно в тексте. Например, nroff хранит день, месяц и год (отсчитываемый от 1900 года) в регистрах dy, mo, yr Поэтому 24 января 1983 года строка 19\n(yr \n(mo \n(dy напечаталась бы так: 1983 1 24 По умолчанию значения числовых регистров представляются арабскими цифрами, как в рассмотренном примере. Запрос af используется для определения формата значений числовых ре- гистров- и имеет вид •af R f где R — имя регистра, a f — один из форматов, приведенных ниже. Если формат содержит п цифр, то соответствующее поле будет состоять по крайней мере из п цифр. В форматоре nroff не определена цепочка, содержащая назва- ние текущего месяца, однако такую цепочку можно определить следующим образом: .if \n(mo—0 .ds МО Январь •if 4 n(mo—1 .ds МО Февраль
210 Гл. 7. Подготовка документов Таблица 7.2 Формат Последовательность нумерации 1 0, 1, 2, 3, 4, 5, ... 001 000, 001, 002, 003, 004, 005, ... i 0, i, ii, iii, iv, v, ... I 0, I, II, III, IV, V, ... а 0, a, b, c, ..., z, aa, ab, ..., zz, aaa, ... А 0, А, В, C, ..., Z, AA, AB, ..., ZZ, AAA, ... Эти запросы определяют цепочку с именем МО, содержащую на- звание текущего месяца. Числовым регистрам значение присваивается с помощью за- проса .пг. Например, запрос .nr IN 2m присваивает значение 2m (в базисных единицах) регистру IN, а запрос .nr IN +2m увеличивает значение IN на 2 эм. Значения регистров можно увеличивать или уменьшать авто- матически, используя следующие формы записи: \п+х Прибавить т к х. \п+(хх Прибавить tn к хх. \п—х Вычесть т из х. \п—(хх Вычесть т из хх. Величина т, прибавляемая или вычитаемая из регистра, уста- навливается запросом .пг. Например, запрос .nr sn 0 1 присваивает sn значение 0, а т — значение 1. Такая форма записи полезна для автоматического увеличения номеров раз- делов. Некоторые запросы, например .ink, также присваивают зна- чения регистрам. Так, запрос .mk pl
7.1. Форматоры текстов nroff и troff 211 Таблица 7.3 Имя Значение Описание .Н 1 Доступная разрешающая способность по горизонтали (в базисных единицах). .V 1 Доступная разрешающая способность по вертикали (в базисных единицах). .с 2691 Количество считанных входных строк. .1 270 Текущий сдвиг границы .1 3361 Текущая длина строки. .п 3020 Длина текста в предшествующей выходной строке. .0 697 Текущее смещение страницы. •р 7953 Текущая длина страницы. .S 10 Текущий размер шрифта (в пунктах) .t 3527 Расстояние до следующей ловушки. .V 120 Текущий интервал между строками. заносит в pl текущую вертикальную позицию (в базисных еди- ницах). В данном случае регистру pl присваивается значение 3540. Для возврата в это место можно использовать запрос .sp: .sp |\n(plu где I указывает на абсолютную позицию внутри страницы. Зна- чение, занесенное в регистр запросом .mk, выражено в базисных единицах, и символ и гарантирует, что nroff обработает это зна- чение правильно. Подразумеваемыми единицами, используемыми в запросе .sp, являются интервалы между строками. Если бы и не было задано, это было бы эквивалентно запросу .sp 13540 требующему пропуск 3540 строк от начала страницы. (Числовые значения, приводимые в этом разделе, зависят от устройства вывода.)
212 Гл. 7, Подготовка документов Для надежности в арифметических выражениях следует всег- да использовать и. Например, запрос .sp li/2 эквивалентен запросу .sp 3u так как подразумеваемыми единицами здесь являются интервалы между строками, и 2 будет рассматриваться как 2 интервала. Для того чтобы разделить 1 дюйм пополам, запрос следует запи- сать в виде .sp li/2u что эквивалентно запросу .sp 361 и Приведенные в табл. 7.3. регистры устанавливаются самим фор- матором nroff и доступны только для чтения. Во второй колонке приводятся значения, установленные для этой страницы англий- ского оригинала книги при выводе на устройство APS. Следующие регистры устанавливаются форматором troff и мо- гут быть изменены пользователем. Как и в предыдущей таблице, во второй колонке приведены значения, установленные для этой страницы. Таблица 7.4 Имя Значение Описание % 159 Номер текущей страницы. dw 2 День недели (1-7). dy 24 День месяца (1-31). hp 457 Текущая позиция по горизонтали. In 0 Номер выходной строки. nl 4600 Текущая позиция по вертикали. mo 1 Месяц (1-12). yr 83 Две последние цифры года (00-99).
7.1. Форматоры текстов nroff и troff 213 Шрифты Если не принимать во внимание разрешающую способность уст- ройства, то все рассмотренные до сих пор запросы были приме- нимы как к nroff, так и к troff. Современные фотонаборные уст- ройства предоставляют от 4 до 32 «штатных» шрифтов и ряд шриф- тов по требованию, а профессиональное печатающее устройство может иметь до 1000 различных способов вывода литер. В форматоре troff предполагается, что стандартными шриф- тами являются прямой светлый шрифт (гарнитура тайме), курсив и полужирный шрифт. Текущий шрифт можно сменить запросом •ft. Например, запрос •ft I вызывает печать текста курсивом, а .ft В изменяет шрифт на прямой полужирный. Такую смену шрифта можно выполнять и внутри строки при помощи функции Для того чтобы восстановить предыдущий шрифт, можно восполь- зоваться запросом .ft или .ft р Аналогично внутри строки конструкция \f(HIxx\fP вызовет печать хх гарнитурой гельветика курсив с последующим восстановлением предыдущего шрифта. Для восстановления прямого светлого шрифта используется запрос .ft R или функция ...\fR. Размер литер Размер литер (в том числе и литер, которыми набраны эти стро- ки) обычно равен 10 пунктам, где пункт равен 1/72 дюйма. Раз- мер литер можно увеличивать или уменьшать в зависимости от возможностей устройства вывода. Для этого предназначен за- прос .ps, а внутри текста используется функция \s. Например,
214 Гл. 7. Подготовка документов чтобы уменьшить размер литер в колонтитуле, можно воспользо- ваться конструкцией .tl '\s—2Руководство по системе UNIX\sO'... которая уменьшает размер шрифта на 2 пункта, а затем восста- навливает исходный размер. Чтобы сделать размер литер в на- званиях разделов равным 12 пунктам, макро SH можно было бы определить так: .de SH .ps 12 .ps Запрос .ps без параметра и функция \s0 восстанавливают пре- дыдущий размер литер. Макроопределение в таком виде может дать неожиданные результаты, если в параметре \$1 содержатся изменения раз- мера шрифта. Эту проблему можно обойти, сделав изменения размера шрифта относительными: .de SH .ps +2 .ps —2 Кроме того, если бы, например, размер литер в документе был изменен на 9, то размер заголовков разделов был бы все равно на 2 пункта больше. В этой книге расстояние между строками равно 120и (около 12 пунктов). Его можно было бы установить запросом .vs 12р Стандартный размер шрифта в книге равен 10 пунктам, а стан- дартный интервал между строками — 12р. Интервал между строками — это расстояние между соседними строками текста; он является подразумеваемым расстоянием, на которое выпол- няется вертикальный сдвиг по запросу .sp. При изменении размера литер интервал между строками не изменяется автоматически. По общему правилу интервал между строками должен быть примерно на двадцать процентов больше, чем текущий размер литер. Текущий размер литер доступен через регистр .s, а текущий интервал между строками — через регистр .v.
7.1. Форматоры текстов nroff и troff 215 Специальные литеры Специальные литеры, например греческие .буквы и некоторые математические символы, вводятся в систему troff в виде \(хх где хх — имя, состоящее из двух литер. Греческие буквы всегда записываются как \(*х где х представляет собой эквивалентную латинскую букву. Например, J Vu dr записывается как \(is \(gru d\(*t а а2 = k/p записывается как \(*a\u\s—22\s+2\d \(eq k\(sl\(*r Для такого рода приложений следует использовать препро- цессор eqn. Приведенные примеры иллюстрируют запросы фор- матора troff, предназначенные для порождения специальных ли- тер. Полный список специальных литер troff приведен в прило- жении 7. Авторегистровые последовательности Все литеры из набора ASCII, за исключением трех, приведенных ниже, выводятся в том же виде, как и вводятся. Некоторые специальные литеры и функции уже встречались и описываются здесь повторно; полный список специальных ли- тер и функций приведен в приложении 7. Таблица 7,5 Вводимая литера ASCII Вывод системы troff t > с 1 1 '
216 Гл. 7. Подготовка документов Таблица 7.6 Последова- тельность символов Действие \.е Печать литеры авторегистра. Печать знака острого ударения. Печать знака тупого ударения. — Печать знака минус. Хкх Сохранение текущей позиции по горизонтали в регистре х. Наложение друг на друга всех литер, заключенных в ка- вычки. Конец макроопределения. \& Отмена действия точки в начале строки. Начало комментария. //// Задание двойных кавычек в параметрах макровызова. Среда выполнения При переходе на следующую страницу для печати колонтитула может понадобиться сменить шрифт и размер литер. Обычно эти действия выполняются в ловушке конца страницы, где процесс заполнения строк приостанавливается для перехода на новую страницу. В форматоре nroff имеется механизм среды, который позволяет исключить влияние печати колонтитула на основной текст. Существует три среды выполнения. Каждая среда содер- жит независимые значения параметров, определяющих формат страницы, шрифт, размер литер, длину заголовка, режимы заполнения и частично обработанный текст. Запрос .ev п вы- полняет переключение на среду выполнения п, где п равно О, 1 или 2. Команда .ev без параметров восстанавливает предыдущую среду выполнения. Первоначально обработка начинается в сре- де 0. Например, для того чтобы изменить длину колонтитула и шрифт в макроопределении конца страницы РЕ, приведенном
7.1. Форматоры текстов nroff и troff 217 ранее, его можно переписать следующим образом: .de РЕ .ev 1 .It 4i .ps 12 .ft В 'sp 2 .tl руководство no UNIX :Г-я редакция 1971:Стр%: 'bp .ev Накопители и ловушки Текст, получаемый в процессе заполнения и выравнивания строк, обычно выводится. Однако текст примечаний необходимо хранить до конца страницы. Кроме того, размещение примечаний и длина основного текста зависят от размера накопленных примечаний. Накопители представляют собой механизм, позволяющий ре- шить проблему примечаний и другие аналогичные проблемы. Выводимый текст можно накапливать в макро с заданным именем. Таким образом можно собирать текст для примечания и определять его размер. Этот накопленный текст можно снова считать позднее, вызвав данное макроопределение. По запросу .di хх выводимый текст начинает накапливаться в макро с именем хх, замещая его прежнее содержимое. Накопление текста заканчи- вается, когда считывается запрос .di без параметров. Накоплен- ный текст можно ввести снова запросом .XX Вертикальный и горизонтальный размеры последнего накоп- ленного текста находятся в регистрах dn и dl соответственно. В форматоре nroff имеется три вида ловушек: страничные Ловушки, ловушки в накопителях и ловушки по количеству вход- ных строк. Страничные ловушки уже были описаны. Ловушки в накопителях устанавливаются запросом .dt и позволяют пре- рывать обработку при достижении заданной позиции по верти- кали в накопителе. Этот запрос используется аналогично запросу .wh. Например, .dt 1 DT
218 Гл. 7. Подготовка документов устанавливает ловушку в накопителе. Когда в текущий накопи- тель будет помещена одна выходная строка, выполнится запрос .DT. В каждый момент времени в накопителе может быть установ- лена только одна ловушка; следующий запрос .dt отменяет пре- дыдущую ловушку и устанавливает новую. Ловушка по количеству входных строк устанавливается за- просом .it; в нем указывается макро, которое следует вызвать после того, как будет считано некоторое количество входных строк. Данная ловушка выполняется только один раз. Напри- мер, по запросу .it 1 IT вызовется макроопределение IT после того, как будет считана (еще) одна входная строка. Используя этот механизм, запрос .ul для подчеркивания можно определить следующим образом: .de ul .it 1 UL .ft 1 .de UL .ft P 7.1.4. Макробиблиотека При подготовке документов целесообразно использовать макро- библиотеки. Имеются стандартные пакеты макроопределений, удовлетворяющие многим потребностям форматирования. Пакет макроопределений общего назначения позволяет выдавать до- кументы разнообразных форматов. Если в одном из этих пакетов нет требуемых средств, его иногда проще видоизменить, чем переписать, начиная с нуля. В этом разделе рассматриваются методы программирования, применяемые для создания пакетов макроопределений. Описанные здесь макроопределения исполь- зовались при подготовке английского оригинала текста этой книги; аналогичные средства предоставляются другими макро- библиотеками, например —ms. Последующее изложение имеет непосредственное отношение к приложению 9, содержащему полный листинг макробиблиотеки, использовавшейся при созда- нии английского оригинала этой книги. Краткая характеристика запросов, предоставляемых рас- сматриваемой здесь макробиблиотекой, дана в приведенной ниже таблице. Более подробное описание приводится в тексте. Форматор nroff терпимо относится к ошибкам: при обнаруже- нии ошибки обработка обычно продолжается без выдачи диагно- стического сообщения. Это усложняет процесс отладки. Чтобы
7.1. Форматоры текстов nroff и troff 219 Таблица 7.7 Запрос Описание .АН 1 "Команды" .CH 1 "Введение" .SH 1.1 "Историческая справка" .SS 6.3.1 "Полномочия файла" .MS "Использование амперсанда" Заголовок приложения. Заголовок главы. Заголовок раздела. Заголовок подраздела. Подзаголовок. .BU •IP (а) .LP .МР х++ .РР Абзац, выделенный жирной точкой. Сдвинутый вправо абзац с висячим текстом слева. Начало абзаца без отступа. Сдвинутый вправо абзац с висячим текстом программы. Начало обычного абзаца. .DS 13 .DE .ЕХ 24 .ХЕ .FX 34 .XF .FC 6.3 "Создание файла-замка" .FG lock, с 13 .RS .RE Начало иллюстрации. Конец иллюстрации. Начало примера. Конец примера. Начало рисунка в тексте. Конец рисунка в тексте. Подпись к рисунку. Включение файла, содержащего ри- сунок. Начало сдвинутого подраздела. Конец сдвинутого подраздела. .CN "1s" .DN "полномочия" .SN "новая строка" .HI "заголовок" .HL .RU Имя команды в тексте. Определение в тексте. Имя символа (литеры) в тексте. Шрифт, используемый для заголов- ков подразделов. Интервал в полстроки. Горизонтальная линия. .СХ парам! парам2 .IX парам! парам2 парамЗ Поместить в предметный указатель парам!, парам2 и парам2, парам!. Порождение элементов предметного указателя. избежать этих трудностей, описываемая здесь библиотека соз- давалась поэтапно. Сначала была написана и отлажена неболь- шая часть библиотеки и тем самым получен работающий костяк. Далее в эту «программу» (в данном случае набор макроопреде- лений для nroff) по одному вносились изменения. Поскольку ошибки, как правило, возникают в результате последнего вне- сенного в программу изменения, этот метод можно применять
220 Гл. 7. Подготовка документов при написании любых программ. Когда большая программа написана без тщательного тестирования всех ее частей, трудно понять, с чего начинать поиск ошибки. Начав с небольшого, но работающего варианта и постепенно развивая его, можно эф- фективно использовать ЭВМ в процессе построения программы. Первые макроопределения были написаны для абзацев и за- головков разделов. По мере необходимости добавлялись новые макроопределения; переделывались уже написанные макроопре- деления, если в них обнаруживались недостатки. В приведенных ниже примерах не всегда даются «окончательные» варианты мак- роопределений; показаны последовательные приближения, на- чиная с первого. Приложение . 9 содержит макроопределения в том виде, в каком они использовались для формирования английского оригинала этой книги. Для того чтобы избежать возможной путаницы с именами ба- зисных запросов nroff, имена макроопределений, как и в других макробиблиотеках, составлены из заглавных букв. Заголовки разделов Первый вариант макроопределения для порождения заголовков разделов показан на рис. 7.4. Обращение к этому макроопреде- лению имеет вид .SH номер заголовок Если заголовок содержит пробелы, его следует заключить в двой- ные кавычки. Номер раздела и его заголовок задаются отдель- ными параметрами, что позволяет подобрать подходящее расстоя- .de SH .sp 2 .ft В .in 0 \&\\$1 \\$2 .ft R .sp \” Заголовок раздела ’ . SH номер название \" пропуск пустых строк \" жирный шрифт ( игнорируется в nroff) \" переустановка сдвига границы \” параметры макроопределения \" восстановление стандартного шрифта Рис. 7.4 Первоначальный вариант макроопределения SH для формирования заголовков разделов ние между ними по горизонтали и изменять это расстояние, не меняя каждый макровызов. В последующих вариантах номер раздела был включен в колонтитул.
7.1. Форматоры текстов nroff и troff 221 Проверка на задание правильного количества параметров в этом макроопределении не производится. Строки, начинающиеся с . или и строки, содержащие \г обрабатываются форматором nroff при каждом их считывании. В макроопределениях это происходит один раз при обработке самого макроопределения и один раз при’ его вызове. Поэтому для обращения к параметру используется конструкция Комбинация \& в начале строки предотвращает интерпретацию начального символа . или ' в параметре при вызове макроопре- деления. Литера \ перед \$1 откладывает интерпретацию па- раметра до вызова макроопределения. Возврат к прямому светлому шрифту при помощи запроса .ft R вместо .ft Р защищает от возможной смены шрифта внутри заголовка, например: .SH 7.1 "\fHnroff\fP и \fHtroff\fP" Это также гарантирует переход на прямой светлый шрифт, даже если непосредственно перед новым разделом использовался ка- кой-либо другой шрифт. Впоследствии в SH были добавлены изменения размера шрифта и запрос, гарантирующий, что новый раздел начнется на данной странице только если на ней осталось по крайней мере 4 строки. Это позволяет избежать ситуации, когда название раз- дела попадает на одну страницу, а его текст — на следующую страницу. Добавления выглядят так: .пе 4 .ps +2 .ps —2 Заключительная правка SH состояла во введении регистра rs для управления левым полем страницы. Для участков текста с отступом левое поле меняется путем изменения значения регистра rs. Участок текста с отступом вво- дится макрозапросом RS (Relative Start). Чтобы запрос .RS можно было использовать вложенным образом, значение регистра rs увеличивается и уменьшается на некоторую величину. Макро- определение RS состоит из запроса .nr rs +2m который увеличивает значение регистра rs на 2 эм. Начальное значение присваивается rs запросом .nr rs О
222 Гл. 7. Подготовка документов в начале макробиблиотеки. Конец участка текста с отступом по- мечается запросом .RE. Проверка на соответствие запросов . RS и .RE не выполняется. Эта проверка может быть сделана отдель- ной программой, хотя ее можно встроить и в данные макроопре- деления следующим образом: .de RS .nr rs 4-2m .nr rc +1 \\n( '.de RE .ie \\n(rc \{ .nr rc —1 .nr rs —2m \) •el \{ .tm "Непарное RE" .nr rc 0 \) Регистр rc увеличивается на 1 при каждом обращении к RS и уменьшается на 1 при каждом обращении к RE. Если значение этого счетчика становится отрицательным, то при помощи за- проса Лт в файл диагностики выдается сообщение, а в регистр заносится 0. Заголовки глав Каждая глава этой книги хранится в отдельном файле и начи- нается следующим образом: .so maclib .CH 7 "Подготовка документов" По запросу .so считывается файл maclib, содержащий макробиб- ,de СН \н заголовок главы: .СН номер текст \\.ds cf \\$1 \\.ds ct \\$2 .се 2 \\$1 \\$2 .sp 8 Рис. 7.5 Первоначальный вариант макроопределения СН для формирования заголовков глав
7.1. Форматоры текстов nroff и troff 223 .de CH \” заголовок главы eCH 1 "Введение" .}Н "Глава \\$1" ’\\$2” .de АН \” заголовок приложения .АН 1 "Команды" .}Н "Приложение $ Г' ”\\$2И •de }Н \” макро для заголовков глав и приложений .sp 8 \\.ds cf \\$1 \\.ds ct \\$2 .ce 2 \\$1 .sp \\$2 .sp 3 Рис. 7.6 Макроопределения для формирования заголовков глав и приложений лиотеку, а затем возобновляется чтение файла, содержащего за- прос .so. Макрозапрос СН запоминает название и номер главы для последующего использования. Первоначальный вариант оп- ределения СН приведен на рис. 7.5. Название главы сохраняется в переменной ct, а номер главы — в переменной cf. Название главы используется в макроопределении для оформления начала страницы, а номер главы использовался (в предварительных вариантах) в тексте, помещавшемся в конце страниц. И номер, и название главы печатаются (с центрированием) в начале первой страницы главы. В последующие варианты этого макроопределения были вне- сены изменения, позволяющие формировать заголовки приложе- ний. Было введено третье макроопределение с именем }Н, как показано на рис. 7.6. Абзацы Для оформления абзацев сначала были написаны макроопреде- ления LP и РР; LP задает абзац без отступа (каким является дан- ный абзац), а РР — абзац с отступом в первой строке. Макроопределение для сдвинутого абзаца IP задает пол- ностью сдвинутый вправо абзац, слева от которого в первой строке помещается некоторый текст. Для получения абзацев подобного типа служат также макроопределения МР и BU.
224 Гл. 7, Подготовка документов Макро МР выводит висячий текст тем же шрифтом, которым выводятся тексты программ, а макро BU в качестве висячего текста использует жирную точку •. Написать эти три определе- ния оказалось сложнее, чем макроопределения для заголовков .de IP V сдвинутый абзац; .if n .sp .if t .sp .5 • ne 3 • in 6 • ta 4 .ti -4 \&\\$l\t\c \.if \^\*\\$1\'и-4т .br • IP (a) Рис. 7.7 Макроопределение IP — абзац с отступом разделов и глав. Типичным является макроопределение IP, первоначальный вариант которого приведен на рис. 7.7. Сдвиг для всех строк абзаца, кроме первой, обеспечивается запросом •in 6. Параметр макро IP обрабатывается строкой \&\\$l\t\c Метка сдвигается влево при помощи отрицательного временного отступа .ti —4. Для того чтобы достичь левого края текста, ис- пользуется табуляция (\t). Текст метки является параметром. В макроопределении ему предшествует \&, что позволяет предотвратить интерпретацию параметров как запросов в слу- чае, если они начинаются с точки. Комбинация\с блокирует переход на новую строку, так что текст может продолжаться на той же самой строке, несмотря на то что во входном файле при- сутствует литера новой строки. Если требуется, переход на новую строку выполняется в макроопределении запросом .if \w'\$ru-—4m .br (один уровень литер \ был удален при сканировании макроопре- деления системой nroff). Запрос в этом виде будет выполнен при вызове макро. Условие удовлетворяется, если ширина параметра $1 больше 4 эм. Буква и за \w'...z гарантирует использование правильных единиц измерения расстояния; строго говоря, она не является необходимой, так как \w возвращает длину в ба- зисных единицах.
7.1. Форматоры текстов nroff и troff 225 Оформление страниц В начале и в конце каждой страницы содержится текст, который выводится двумя специальными макроопределениями. Запрос .wh 0 аа информирует troff о том, что всякий раз, когда текущая позиция отстоит на 0 единиц от начала страницы, требуется выполнить макрозапрос аа. Макроопределение аа приведено на рис. 7.8., .de аа .ev 1 .It 7i .tl '—"—' .II 4.65i .It 4.65i 'sp 6 .ps 8 .ft R .tl '%\h'2m' Система UNIX "\\*(ct\h'2m’%' 'sp •ev Рис. 7.8 Макроопределение для оформления начала страницы Чтобы исключить влияние на основной текст, запрос в первой строке выполняет переключение на новую среду. Запросы в двух последующих строках порождают пометки для разрезания стра- ниц при выводе на рулонную бумагу. Так как длина заголовка отлична от ширины бумаги, эта длина устанавливается сначала равной 7 дюймам, а затем восстанавливается до нормальной величины. Первоначально номера страниц в колонтитулах выводились (при помощи специальной литеры %) и слева, и справа. В более поздних вариантах были введены два различных колонтитула страниц — левый и правый, печатаемые в зависимости от того, является ли номер страницы четным или нечетным. Для этого используются встроенные в nroff проверки на четность и нечет- ность номеров страниц, как в следующих двух примерах; .if е .tl '%\h' 2ш'Система UNIX"* .if о .« *"\\*(ct\h'2m'%' 8 С. Баурн
226 Гл. 7. Подготовка документов Конец страницы обрабатывается аналогичным образом. Вто- рая ловушка с именем zz устанавливается запросами .if n .wh —5 zz .if t .wh 9i zz В nroff эта ловушка ставится за 5 строк до конца страницы, а в troff — на расстоянии 9 дюймов от начала страницы. Внутри макроопределений, выполняемых ловушками, в за- просах, вызывающих переход на новую строку, используется ' вместо точки. Это блокирует вывод частично накопленной строки. Иллюстрации Иллюстрация — это фрагмент текста, который должен разме- щаться целиком на одной странице и в котором заполнение строк не требуется. Рисунки в этой книге представляют собой особый вид иллюстраций, начинающихся и заканчивающихся горизон- тальной прямой чертой. В макроопределениях для иллюстраций, описанных в этом разделе, пользователь сам должен задавать размер иллюстраций. Если иллюстрация не помещается на оставшейся части текущей страницы, эта часть остается незапол- ненной. Для оформления иллюстраций предоставляются два базисных макроопределения DS и DE, которые задают начало и конец иллюстрации. Определение DS выглядит примерно так: .de DS начало иллюстрации .DS 'in +2m .ta 4m 8m 12m 16m 20m 24m 28m 32m 'nf 'ne \\$1 Текст иллюстрации сдвигается немного вправо; устанавливается ряд подразумеваемых табуляций. Устанавливается режим без заполнения строк. Запрос .пе гарантирует, что будет зарезерви- ровано требуемое количество строк. Запрос DE задает конец иллюстрации. Он определяется как .de DE .HL .fi .in —2m Восстанавливается режим заполнения строк, и сдвиг края умень- шается на 2 эм. Запрос .HL генерирует сдвиг вниз на полстроки.
7.1. Форматоры текстов nroff и troff 227 Для оформления примеров программ используются запросы .ЕХ и .ХЕ, которые определяются следующим образом: .de EX .ft Н •fl .ss 20 .DS \\$1 .de XE .DE .ss 12 .ft R В этом примере были введены два новых запроса. Запрос .ss 20 устанавливает минимальное расстояние между словами равным 20/36 эм. Используемое по умолчанию минимальное расстояние 12/36 эм слишком мало для программ, набранных гельветикой. Интерпретация размеров литер является последним действием, выполняемым системой troff, когда выводимый текст копируется из внутренних буферов в выходной поток. Запрос .fl выталкивает текст из такого буфера. Этот запрос необходим здесь, чтобы пре- дотвратить влияние изменения размеров пробелов на еще не выведенный текст. Макроопределения .FX и .XF предназначены для оформления рисунков, в начале и конце которых помещается прямая гори- зонтальная черта. Каждое из этих макро определяется через базисные макро DS и DE. Последнее, на что следует обратить внимание в приложении 9,— это набор текстовых регистров. Эти регистры были введены для того, чтобы предоставить литеры, визуально более удобные, чем обычно используемые. Например, литера тильда обычно печатается как однако в программах требуется помещать ее на той же высоте, что и знак равенства = . Для красоты были выровнены также размеры литер. Создание предметного указателя Предметный указатель можно накапливать, вставляя в текст вызовы специального макроопределения, содержащие элементы предметного указателя. Это макроопределение, используя за- прос .tm, направляет элемент предметного указателя в файл диагностики. Если nroff вызывается командой troff глава 2>1х/глава 8*
228 Гл. 7. Подготовка документов то элементы предметного указателя будут накапливаться в файле ix/глава. Подоглавление используется для того, чтобы не засо- рять рабочее оглавление файлами, содержимое которых можно всегда сгенерировать заново. Макро IX для формирования элемента предметного указа- теля определяется следующим образом: .de IX \" элемент предметного указателя .if t .tm \\$1 \\$2 \\$3 \\$4 \\п% Для удобства было написано другое макроопределение СХ: .de СХ .IX "\\$1," " \\$2" .IX "\\$2," "\\$1" Используя СХ, одним запросом можно создать сразу два эле- мента в предметном указателе. Например, .СХ "макроопределение" \fHnroff\fP создаст два элемента предметного указателя: макроопределение, nroff\п % nroff, макроопределение\п% В вывод автоматически помещается номер страницы, содер- жащей запрос IX. Этот вывод обрабатывается потом с помощью командной процедуры index, приведенной в разд. 7.2. 7.2. ПОЛУЧЕНИЕ ИТОГОВОГО ДОКУМЕНТА По мере продвижения проекта разрабатываются новые команды и устанавливаются соглашения об именах файлов. Например, файл, содержащий предметный указатель для данной главы, хранится в подоглавлении с именем ix. Аналогично номера стра- ниц хранятся в другом оглавлении с именем page. Команды, описанные в этом разделе, создавались и дорабатывались в про- цессе написания данной книги. Эти команды выполняют: • Печать главы при помощи nroff и troff на различных устройствах вывода. • Генерацию номеров страниц. • Создание предметного указателя. Печать главы Команда печати главы имеет различные имена в зависимости от требуемого устройства вывода. Поскольку все варианты команды похожи друг на друга, удобно хранить их в одном командном
7,2, Получение итогового документа 229 файле. Это также упрощает процесс редактирования при добав- лении нового варианта команды. Команда вызывается по именам, перечисленным в альтернативах оператора case на рис. 7.9. t Используя troff, вывести текст на фотонаборном устрой- стве APS в виде, удобном для размножения. п Использовать nroff и выдать текст в стандартный файл вывода. Эта команда использовалась для просмотра тек- ста на простых устройствах вывода. args-'chapters' for i in ${*-$args} , do echo "$0 $$ $i ’date'11 >>log n«'cat page/$i' # для главы 7 использовать f bl и eqn case $i in t7) tbl $i I eqn ;; *) cat $i esac I case $0 in t) # Послать текст в вычислительный центр арз troff —n$n 2>ix/$i I aps n) # использовать nroff nroff —n$n 2>ix/$i •» p) # использовать proof troff -n$n 2>/dev/null I proof continue ix) # генерация предметного указателя troff -n$n 2>ix/$i >/dev/null esac makepage $i done Рис. 7.9 Печать глав
230 Гл. 7. Подготовка документов р Вывести текст на растровый дисплей с высокой разре- шающей способностью, используя специальный troff- инте^претатор. Так как этот вариант команды исполь- зуется интерактивно и ее выполнение часто прерывается до завершения вывода всего текста, предметный указатель не генерируется. ix Создать предметный указатель в файле ix/..., не выпол- няя вывода текста. Файл log содержит журнал работ. В него записываются имя команды $0, номер процесса $$, имя главы и дата. echo tp ТС t1 t2 t3 t4 t5 t6 t7 t8 echo А1 A2 АЗ a4 a5 a6 a7 a8 a9 a10 a11 tb TX Рис. 7.10 Имена файлов, содержащих тексты глав Номер страницы, с которой начинается глава, хранится в файле page/глава и присваивается переменной оболочки п коман- дой n='cat page/$i' Первоначально этот файл создается вручную. При вызове фор- матора troff номер страницы передается ему при помощи опции args-'chapters* for i in ${*-$args) do set — "'grep LAST <ix/$i' start='expr ${2-0} + Г if test —f page/next$i then next«'cat page/next$i' echo глава $next начиналась co страницы *cat page/$next' echo а теперь начинается co страницы Sstart echo $start >page/$next fi done Рис. 7.11 Процедура makepage —n. Номера страниц обновляются автоматически командой makepage, приведенной на рис. 7.11. Команда chapters, приведенная на рис. 7.10, представляет собой простую процедуру, использующую echo. В ней перечне-
7.2, Получение итогового документа 231 ляются главы в том порядке, в котором они будут следовать в итоговом документе. Некоторые главы порождаются автомати- чески путем обработки других файлов; чтобы отличить эти главы от остальных, их имена набраны прописными буквами. Нумерация страниц Главы печатаются раздельно, но нумерация страниц у них об- щая. Для каждой главы номер страницы, с которой она начина- ется, хранится в файле page/глава. Номера страниц обновля- ются командой makepage (рис. 7.11). • • \* задание действия по концу файла .ет ее V макро для обработки конца файла .de ее .af % 1 V установка прямого светлого шрифта Лт LAST \\п°/о. Рис. 7.12 Действие по концу файла При помощи макрозапроса .ет системы nroff (действие по концу файла) в предметный указатель вставляется ключевое слово LAST, как это показано на рис. 7.12. Процедура makepage использует это ключевое слово для определения номера послед- ней страницы данной главы. К этому номеру при помощи коман- ды ехрг прибавляется единица, и результат запоминается в файле page/..., соответствующем следующей главе. Команда set в процедуре makepage записывает номер стра- ницы, соответствующий ключевому слову LAST, в переменную оболочки $2. Переменной start присваивается номер страницы, с которой начнется следующая глава. Для каждой главы в оглавлении page содержится два файла. Первый из них содержит номер начальной страницы главы, а второй, с именем page/nexta^aea,— имя файла, содержащего текст следующей главы. Заключительная обработка предметного указателя Предметный указатель создается в файлах ix/... . Его нужно упорядочить по алфавиту и заново форматировать. Команда index обрабатывает элементы предметного указателя, полученные в результате работы troff, и создает итоговый предметный ука- затель в виде входного файла для troff. Если бы в предметном указателе применялся один шрифт, а размер литер оставался
232 Гл. 7. Подготовка документов неизменным, эта процедура была бы простой. В описанной ниже процедуре используется редактор sed. К каждой строке припи- сывается ее копия, а затем в левых частях строк удаляются за- просы изменения шрифта и размера литер. Результат сортиру- ется, после чего левые части отбрасываются. cat «! • so maclib ,)Н Index Ла 12m 14m 16m 18m 20m 22m 24m 26m .ps 8 .nf I args^'chapters’ see” for I in ${*-$args) do sed -e "s/ ♦// s /.♦/&—"--& I s/\\\\s-* \(.A)~~~~Al~~~~/g s/\\\\s-<\(.*\)~--/\1~---/g s / \\\\s. \(. *\)~— A1 ~— / g s/\\\\s.\(.»\)~—Д1-—/g s/\\\\f.\(.*\)-Д1-—/g s / \\\\f. \(. *\)-~—Д1 ~— / g s/\\\\f . ♦\)~'’~~/\l’~~~/g s/ / /g" <ix/$i done I sort -Lift' ' +0 -1 +1n I sed -e 's/. I xrefb Рис. 7.13 Постпроцессор index Команда cat в первой строке процедуры на рис. 7.13 генери- рует некоторый фиксированный набор запросов troff Для вклю- чения макробиблиотеки, установки табуляций, размера литер и режима без заполнения строк. Далее следует цикл по заданным параметрам — именам файлов. По умолчанию используется стандартный список имен. В первоначальном варианте предполагалось упорядочивать элементы предметного указателя непосредственно командой sort ix/*
7.3. Средства обработки текстовых документов 233 К сожалению, встречающиеся в элементах указателя запросы изменения шрифта и размера литер вызывают ошибки при сорти- ровке. В новом варианте команды (рис. 7.13) при помощи редактора sed создается файл с записями, состоящими из двух полей, разде- ленных некоторой легко распознаваемой цепочкой литер (в дан- ном примере цепочкой Первое поле содержит элемент указателя, в котором удалены запросы изменения шрифта и размера литер, а второе поле — элемент в первоначальном виде. Такой список создается в цикле for (рис. 7.13) и по конвейеру передается команде сортировки sort, причем ключом для сорти- ровки служит первое поле. Команда sort с опцией —uf исключает записи-дубликаты и при сортировке игнорирует различие между прописными и строчными буквами. Последний шаг заключается в использовании программы составления перекрестных ссылок, описанной в гл. 8. Эта про- грамма считываем строки вида элемент-указателя номер-страницы и порождает строки вида элемент-указателя номер-страницы! номер-страницы^ ... для элементов, на которые имеется несколько ссылок. 7.3. СРЕДСТВА ОБРАБОТКИ ТЕКСТОВЫХ ДОКУМЕНТОВ Средства, описанные до сих пор, позволяют создавать и форма- тировать текстовые документы. Имеются также средства для обнаружения орфографических ошибок, анализа стиля и предва- рительной обработки содержащихся в тексте таблиц и математиче- ских формул. col Преобразование вывода системы nroff с целью исклю- чить при печати перевод строк назад. diction Обнаружение плохо построенных фраз. eqn Подготовка математических формул. refer Оформление библиографических ссылок. spell Печать слов с сомнительным или неправильным на- писанием. style Сравнение внешних характеристик (ясность изложе- ния, структура предложений) каждой главы. tbl Подготовка табличных данных. Программы eqn, refer и tbl считывают текст из файла стан- дартного ввода и записывают переработанный текст в файл
234 Гл. 7. Подготовка документов стандартного вывода. Обрабатываются только участки входного текста, заключенные между парой определенных запросов; остальной текст копируется без изменений. 7.3.1. Команда со) Команда col считывает данные, полуденные в результате работы nroff и содержащие литеры перевода строки назад. Такие ли- теры порождаются, например, запросом .rt. Этот запрос гене- рируется препроцессором tbl и опциями печати в несколько коло- нок в некоторых макробиблиотеках. Команда col отфильтровы- вает эту и ряд других неграфических литер и порождает вывод, пригодный для простейших печатающих устройств. Обычный вывод системы nroff пригоден для телетайпа модели 37 (механического устройства, замечательного для своего вре- мени, но уже вышедшего из употребления). Вместо того чтобы использовать команду col, можно задать в команде nroff опцию, указывающую, что нужно сгенерировать вывод для простого терминала. Для терминала DASJ-450, например, эта опция за- дается так: nroff —Т450 ... 7.3.2. Команда diction Команда diction < доку мент находит в заданном тексте плохо или неправильно построенные предложения. В файл стандартного вывода записываются фразы, которые в обиходе часто употребляются неправильно. Хотя эти фразы могут использоваться преднамеренно, они все-таки отри- цательно влияют на восприятие документа. Как и в случае команды spell, можно сохранить в некотором файле те «ошибки», которые были допущены сознательно, и использовать команду comm или diff для распечатки «новых» ошибок. 7.3.3. Команда eqn Препроцессор eqn используется при подготовке математических формул для печати на фотонаборном устройстве. Родственная команда neqn выполняет те же функции для устройств типа пи- шущей машинки. Пользуясь eqn, можно печатать, например, знаки интегрирования, суммирования, извлечения квадратного корня, а также сложные выражения. Идеи, положенные в основу
7.3. Средства обработки текстовых документов 235 препроцессора, достаточно просты и понятны даже нематемати- кам. Участок текста, предназначенный для eqn, заключается между строками, начинающимися с .EQ и .EN. В простейшем примере .EQ х=а+Ь .EN будет выдано выделенное курсивом уравнение х=а+Ь Участок текста для eqn может быть также заключен между литерами-разделителями, определяемыми, например, так: .EQ delim $$ .EN . В этом случае текст Let $ 2 pi omega $ be the ... выводится в виде Let 2ло) be the ... Препроцессор eqn обрабатывает исходный текст, выдавая инструкции для nroff и troff. Прочие форматирующие действия (например, центрирование и выравнивание по левому краю) выполняются при помощи запросов nroff/troff, возможно, с ис- пользованием пакета макроопределений (например, —ms). Внутри eqn-запросов пробелы используются для разделения элементов; для вывода явных дополнительных пробелов исполь- зуется литера Входные данные имеют свободный формат и могут при необходимости занимать несколько строк. Элементы запросов можно группировать при помощи скобок { и }. Препроцессор eqn позволяет также набирать выражения с индексами, пределы, знаки частных производных, большие квадратные скобки для векторов и матриц, знаки суммирования (сигма) и длинные дробные выражения. Ряд примеров eqn-кон- струкций приведен в таблице, а ниже даются краткие пояснения. Нижние и верхние индексы генерируются при помощи ключе- вых слов sub и sup. Для получения дробей используется ключевое слово over, а для получения квадратного корня — sqrt. Ключевые слова from и to вводят любые нижние и верхние пределы. Левые и правые квадратные скобки подходящей высоты генерируются при помощи ключевых слов left и right. Правую скобку можно опускать. После слов left и right допускаются фигурные и квад- ратные скобки, вертикальная черта, символы с и f (округление
236 Гл. 7. Подготовка документов Текст для eqn Вывод troff х sub i Xi a sub i sup 2 aP- e sup {x sup 2 + у sup 2} a over b a T> x sup 2 over a sup 2 ‘ X2 ~aT sqrt {ax sup 2 +bx+c} у/ax2+bx+c lim from {n-> inf) sum from 0 to n x sub i lim Л“*ОО'ТГ left [ x sup 2 + у sup 2 over alpha right ] -=~1 -1 pile {a above b above c) % c matrix { Icol { x above у } cool { 1 above 2 } ) X 1 У ? x dot - f(t) bar x-f(t) у dotdot bar n under У=п x vec у dyad X -y до ближайшего целого с избытком или недостатком) и пустая комбинация "" (полезная, когда требуется только правая скобка). Элементы располагаются в столбец при помощи ключевых слов pile, Ipile, cpile и rpile. В столбце может содержаться про- извольное число элементов. Ключевое слово Ipile выравнивает столбец по левому краю, pile и cpile центрируют его, но с различ- ным интервалом по вертикали, a rpile выравнивает по правому краю. Матрицы формируются при помощи слова matrix. Кроме приведенных в таблице ключевых слов имеется еще ключевое слово rcol для выравнивания столбца по правому краю. Диакритические знаки формируются ключевыми словами dot (точка), dotdot (две точки), hat (шляпка), tilde (тильда), bar (черта сверху), vec (вектор), dyad (диада) и under (подчеркива- ние). Размер литер можно изменить, используя конструкцию size п или size ±п, а шрифт — при помощи спецификаций roman (пря- мой светлый), italic (курсив), bold (жирный) и font п (шрифт п). Размеры литер и шрифт можно изменить глобально во всем до- кументе, используя gsize п или gfont п, или же задав опции —sn и —fn в командной строке.
7.3. Средства обработки текстовых документов 237 Обычно размер нижних и верхних индексов уменьшается на 3 пункта по сравнению с размером литеры, к которой относятся эти индексы; эту величину можно изменить, задав опцию —рп в командной строке. Последовательные строки текста можно выравнивать по вертикали. Поместите mark перед желаемой позицией выравни- вания в первом уравнении; поместите lineup в позиции, которую требуется выровнять по вертикали, в последующих уравнениях. Процессор eqn распознает ключевые слова типа sum (2), int (J), inf (oo) и сокращения типа >= Q>), —> (->) и ! = (¥=)• В результате обработки текста .EQ t ~=~ 2 pi int sub 0 sup 1 sin ( sqrt { x sup 2 + a sup 2 } ) dx .EN будет получено уравнение t = 2л J sin(Kx2+a?)dx 0 Греческие буквы задаются их латинскими названиями в же- лаемом (верхнем или нижнем) регистре, например alpha и GAM- MA. Математические обозначения типа sin, cos, log автоматически выводятся прямым светлым шрифтом. В любом месте можно ис- пользовать принятый в troff четырехлитерный авторегистр, как в случае\(Ьз (@) . Цепочки литер, заключенные в двойные кавычки передаются без изменения; это позволяет вставлять в результирующий текст ключевые слова и передавать текст в troff в требуемом виде, если иначе это не удается. Если требуется подготовить и таблицы, и формулы, сначала следует использовать препроцессор tbl, например: tbl глава I eqn I troff —ms Для того чтобы включить библиографические ссылки, первой следует выполнить команду refer, так как и eqn, и tbl значительно увеличивают размер обрабатываемого текста. Например: refer статья I tbl I eqn I troff —ms 7.3.4. Команда ptx — порождение циклического указателя Циклический указатель, с которого начинается Руководство для программиста системы UNIX, был получен с помощью к команды ptx. Команда ptx <входной-текст >выходной-текст
238 Гл. 7. Подготовка документов создает циклический указатель из записей входного файла и помещает результат в выходной файл. Для указания файла клю- чевых слов используется опция —о файл-ключевых-слов Каждая строка входного файла циклически сдвигается таким образом, что ключевое слово попадает в начало строки. Файл, получившийся после выполнения всех операций такого рода, сортируется. Наконец, отсортированные строки циклически сдвигаются таким образом, чтобы ключевое слово попало в се- редину страницы. 7.3.5. Команда refer Команда refer ищет и форматирует библиографические ссылки. Она просматривает файл стандартного ввода в поисках ссылок вида .[ ссылка на литературу Ссылки на литературу задаются ключевыми словами. Например: ритчи томпсон система unix bstj 1978 .] Команда refer просматривает библиографическую базу данных и заменяет текст между .[ и .] набором определений цепочек в смысле nroff. При использовании пакета макроопределений —ms из этих цепочек печатается полная ссылка. Если задана опция —е, например refer —е статья то ссылки накапливаются и печатаются в том месте, где встре- чается $LIST$ .1 (обычно в конце документа). Предоставляется ряд опций для сортировки и нумерации ссылок. Стандартным файлом, содержащим библиографию, является /usr/dict/papers, но его можно заменить другим при помощи оп- ции —р. Команда refer печатает сообщения о неправильных или неоднозначных ссылках. Команда pubindex анализирует заданные файлы, содержащие требуемые библиографические ссылки, и создает для них эф-
7.3. Средства обработки текстовых документов 239 фективный индекс, который используется командой refer. Биб- лиографические ссылки в файлах отделяются друг от друга пустыми строками. Каждая ссылка состоит из нескольких строк, содержащих поля с библиографической информацией. Каждое поле начинается со строки, первой литерой которой является %; за этой литерой следует ключевая буква, пробел и далее — содер- жимое поля вплоть до следующей строки, начинающейся с лите- ры %. Наиболее часто используемыми ключевыми буквами и соответствующими полями являются: А Фамилия автора. В Название книги, содержащей данную статью. С Город. D Дата. d Дата (альтернативное поле). Е Редактор книги, содержащей данную статью. G Номер государственного заказа (CFSTI). I Издательство. J Журнал. К Прочие ключевые слова для поиска данной ссылки. М Номер технического документа. N Номер выпуска в пределах тома. О Комментарий, который следует напечатать в конце ссылки. Р Номера страниц. R Номер отчета. г Номер отчета (альтернативное поле). Т Название статьи, книги и т. п. V Номер тома. X Комментарий, не используемый командой pubindex. За исключением поля А, каждое поле должно задаваться не более одного раза. Следует задавать только существенные поля. Например: %Т 5-by-5 Palindromic Word Squares %А М. D. McIlroy % J Word Ways %V 9 %P 199-202 %D 1976 Родственная команда lookbib обеспечивает поиск библио- графии в интерактивном режиме. Например, команда lookbib unix bstj печатает все ссылки на статьи из журнала Bell System. Technical Journal, содержащие ключевое слово UNIX.
240 Гл, 7. Подготовка документов 7.3.6. Команда spell Команда spell проверяет написание слов из файла ввода по имею- щемуся у нее словарю. Обращение к ней имеет вид spell документ или spell —b документ Последняя форма проверяет английское правописание в проти- воположность американскому. Выводится список слов, не содержащихся в словаре. Этот список может содержать правильные слова, которые, однако, отсутствуют в стандартном словаре. Чтобы распечатать только новые орфографические ошибки, можно завести собственный файл необычных слов и воспользоваться командой comm, как это показано в следующем примере: for i do mv spell/$i spell/$i.prev <$i spell >spell/$i comm —23 spell/$i spell/$i.prev done Администратор системы (или лицо, имеющее доступ к файлу, содержащему словарь) может внести эти слова в стандартный словарь. Прежде чем начать проверку правописания слов по словарю, команда spell отсеивает запросы nroff/troff, используя фильтр deroff. 7.3.7. Команда style Команда style документ позволяет анализировать стиль (ясность изложения) документа. Печатается частота использования различных грамматических форм (в процентах). Сообщается уровень восприятия и инфор- мация о длине предложений, структуре предложений, длине слов, типе глаголов и способах построения фраз. Хотя эта инфор- мация является чисто структурной и поверхностной, она полезна при сравнении двух документов. Она может также указывать на чрезмерное употребление данной грамматической формы.
7.3. Средства обработки текстовых документов 241 7.3.8. Команда tbl Препроцессор tbl воспринимает простое описание таблицы и ге- нерирует запросы nroff для печати этой таблицы. Вводом для tbl служит текст документа вместе с информацией, описывающей таблицы. Запросы препроцессора tbl помещаются между .TS (начало таблицы) и .ТЕ (конец таблицы): .TS описание таблицы .ТЕ Весь остальной текст препроцессором tbl не изменяется. Сами строки .TS и .ТЕ также сохраняются в неизменном виде и могут использоваться в других макрозапросах. Описание таблиц имеет вид .TS опции, задающие внешний вид таблицы; формат столбцов и строк. табличные данные .ТЕ Литеры ; и . должны присутствовать явно, завершая соответ- ствующий раздел. Формат столбцов и строк в общем случае за- дается шаблоном, который по внешнему виду похож на саму таблицу. В следующем примере ф обозначает литеру табуляции, которая служит разделителем столбцов. .TS center; csss с с с с linn. Отделения фирмы Bell Labs Название ф Адрес ф Код ф Номер Холмдел ф -Холмдел, NJ 07733 ф 201 ф 949 Марри Хилл ф Марри Хилл, NJ 07974 ф 201 ф 582 Уиппени ф Уиппени, NJ 07981 ф 201 ф 386 Индиан Хилл ф Нейпирвиль, IL 60540 ф 312 ф 690 .ТЕ В результате получается таблица вида Таблица 7.8 Отделения фирмы Bell Labs Название Адрес Код Номер Холмдел Холмдел, NJ 07733 201 949 Марри Хилл Марри Хилл, NJ 07974 201 582 Уиппени Уиппени, NJ 07981 201 386 Индиан Хилл Нейпирвиль, IL 60540 312 690
242 Гл. 7, Подготовка документов В разделе описания форматов можно задавать следующие опции: с Элемент столбца центрируется. 1 Элемент выравнивается по левому краю столбца. п Числовая величина. г Элемент выравнивается по правому краю столбца. s Расширенный заголовок. Элемент из предыдущего столбца продолжается и в этом столбце. Опция center вызывает размещение таблицы посередине страницы. По умолчанию таблица располагается с левой стороны страницы. Внешний вид таблицы задается следующими опциями: allbox Поместить каждый элемент таблицы в прямо- угольную рамку. box Поместить всю таблицу в прямоугольную рамку. center Разместить таблицу посередине страницы (по умолчанию таблица располагается с левой стороны страницы). doublebox Поместить таблицу в двойную рамку. expand Разрядка. Использовать для размещения таб- лицы всю ширину страницы (текущую длину строки). tab(x) В качестве разделителя столбцов использо- вать литеру х вместо табуляции. Например, с использованием запроса allbox приведенная выше таблица будет выглядеть так: Таблица 7.9 Отделения фирмы Bell Labs Название Адрес Код Номер Холмдел Холмдел, NJ 07733 201 949 Марри Хилл Марри Хилл, NJ 07974 201 582 Уиппени . Уиппени, NJ 07981 201 386 Ин ди ан Хилл Нейпирвиль, IL 60540 312 690 Строка csss
7.3. Средства обработки текстовых документов 243 описывает поля в первой строке таблицы. Опция с означает, что поле должно центрироваться, a s указывает на то, что предше- ствующее поле должно быть продолжено и в этом столбце. Запрос с с с с задает формат второй строки. Эта строка состоит из четырех подзаголовков, размещаемых в середине столбцов. Формат остальных строк описывается как linn а . завершает раздел описания форматов. Буква 1 означает поле, выравниваемое по левому краю, ап — числовое поле, в котором выравнивание выполняется по младшей цифре.
ГЛАВА 8 СРЕДСТВА ОБРАБОТКИ ДАННЫХ Система UNIX хорошо известна своими средствами обработки данных. Среди них: awk Язык сопоставления с образцами и генератор отчетов, стр Сравнение двух файлов. сотт Выборка строк, общих для двух упорядоченных файлов. diff Выявление различий между двумя файлами. grep Сопоставление с образцом для группы файлов. join Объединение двух файлов путем соединения записей с одинаковыми ключами. sed Пакетный редактор (типа ed, но не интерактивный), sort Сортировка или построчное слияние файлов. tail Печать п последних строк входного файла. tr Преобразование литер. uniq Исключение из файлов последовательных строк-дуб- ликатов. В данной главе рассматриваются перечисленные выше средства и приводятся примеры их использования. Упор делается на по- строение новых средств из уже имеющихся. Объединение этих средств достигается при помощи оболочки, которая выполняет роль «клея». Программы на языке-оболочке легко читать и сопровождать. Если не приняты специальные меры, программирование на языке-оболочке с использованием команд как строительных блоков может оказаться неэффектив- ным. Однако работающую систему часто можно создать за более короткое время, чем если бы все программы были написаны на С. Если система оказалась удачной и требуется сделать ее более эффективной, то отдельные части этой уже работающей системы можно переписать. Далее в этой главе рассматриваются примеры, иллюстрирую- щие области приложений, в которых с успехом применяются перечисленные выше средства.
8.1. Краткое описание средств 245 Телефонный справочник В первом примере рассматривается организация работы со спис- ком телефонных номеров, имен абонентов и их адресов. Приво- дится программа для внесения в файл (список) новой информации. Номер можно получить по имени или по части имени абонента. Аналогично, по заданному номеру можно найти соответствую- щее ему имя абонента. В этом примере поиск в файле выполняется аналогично поиску в записной книжке. Программа для составления перекрестных ссылок В этом разделе показано, как можно объединить С-программу и программу, построенную с помощью lex, в одну систему, вы- дающую список перекрестных ссылок для С-программ. В этом списке для каждого идентификатора перечислены номера строк и файлы, в которых он встречается. Кроме того, для поддержки объектных модулей в корректном состоянии используется типич- ный make-файл. Сама программа построения перекрестных ссы- лок представляет собой командную процедуру, использующую две упомянутые выше программы. Одна из этих программ ис- пользуется также в гл. 7 в процедуре, формирующей предметный указатель для английского издания этой книги. Обработка результатов соревнований по теннису В этом примере описывается набор командных процедур для управления небольшой базой данных. Эта система ведет итоговую таблицу результатов встреч по теннису («табель»). Каждую неделю выводятся результаты и таблица модифицируется согласно некоторому набору пра- вил. В системе UNIX большинство из требуемых операций можно автоматизировать. Модификации таблицы выполняются каждую неделю в определенное время автоматически — никто не набирает команду непосредственно. 8.1. КРАТКОЕ ОПИСАНИЕ СРЕДСТВ В этом разделе кратко рассматриваются основные средства об- работки данных. Более подробные сведения, включая широкий набор имеющихся опций, можно найти в приложении 1 или полу- чить при помощи команды man. При обсуждении некоторых команд (например, join и sort) используется понятие полей записи. Запись — это строка текста. Поля внутри записей разделяются литерой — разделителем по- лей. Обычно в качестве разделителя полей используется литера
246 Гл. 8. Средства обработки данных табуляции. Это удобно, так как позволяет легко подготавливать итоговые документы с помощью системы nroff. Одни команды позволяют задавать разделитель полей, другие — нет. В некоторых случаях описываемые средства выполняют сход- ные функции. Например, в командах awk, ed, grep, lex и sed осуществляется отбор строк по некоторому регулярному выра- жению. В последующих примерах демонстрируются области применения, для которых эти средства наиболее подходят. Как правило, чем более узкую специализацию имеет средство, тем оно более эффективно для своей области применений. Например, команду wc следует использовать для подсчета числа строк, a grep или egrep — для выборки строк согласно некоторому об- разцу. Редактор sed используется, когда требуется преобразовать входной файл; пример этому — создание предметного указателя в разд. 7.2. Система awk обладает весьма большой общностью, но первое знакомство с ней может вызвать затруднения. Генератор программ lex требует понимания среды языка С. Регулярные выражения Термин «образец» используется в этой книге для обозначения набора цепочек литер. В зависимости от приложения этот набор задается различными способами. Например, в оболочке литера * сопоставляется с подцепочкой из нуля или более литер, а ? со- поставляется ровно с одной литерой* Оболочка предоставляет простейшие возможности сопоставления с образцами. В командах awk, ed, grep, lex и sed используются образцы, которые сопостав- ляются с входными цепочками. Для описания этих образцов иногда используется термин «регулярное выражение». Здесь приводится краткое описание регулярных выражений; более подробное описание можно найти в книге Ахо (1977). Регулярное выражение е определяется следующим образом (см. также описание образцов в редакторе ed — разд. 3.1.5). Термин «литера» в этом описании означает литеру, отличную от литеры новой строки. \с Обратная косая черта с последующей отличной от новой строки литерой сопоставляется с этой лите- рой. Л $ Литера л ($) сопоставляется с началом (концом) строки. . Сопоставляется с любой литерой. с Литера, не наделенная специальным значением, сопоставляется с такой же литерой. Цепочка литер, заключенная в квадратные скобки, сопоставляется с любой одной литерой из этой це-
8.1. Краткое описание средств 247 почки. Диапазоны литер, согласно кодировке ASCII, можно задавать сокращенно, например a—zO—-9. Скобка ] может быть только первой ли- терой цепочки. Собственно знак минус должен рас- полагаться в цепочке таким образом, чтобы его нельзя было спутать с указателем диапазона. е* Регулярное выражение, за которым следует лите- ра * (4-, ?), сопоставляется с последовательностью из 0 или более (1 или более, 0 или 1) вхождений этого регулярного выражения. el е2 При конкатенации двух регулярных выражений сопоставление производится с первым и следующим за ним вторым регулярным выражением. Описанные только что выражения можно использовать во всех программах, предоставляющих средства сопоставления с образ- цами, а именно в awk, ed, grep, lex и sed. Следующие регулярные выражения допускаются'только в awk, lex и egrep: el | е2 Если два регулярных выражения разделены лите- рой | или новой строкой, то сопоставление произ- водится либо с первым, либо со вторым регуляр- ным выражением. (...) Если регулярное выражение заключено в круглые скобки, то сопоставление производится с этим ре- гулярным выражением. Порядок выполнения операций на одном скобочном уровне следующий: [ ], *4-?, конкатенация, |. 8.1.1. Генератор отчетов awk Команда awk — это фильтр, в котором предусмотрены средства для обработки текста. Поведение awk полностью программирует- ся. Имеются условные операторы, циклы и переменные, причем форма записи аналогична форме записи в языке С. По команде awk программа файл! файл2 ... выполняются инструкции, заданные в цепочке программа; вход- ные данные берутся из указанных файлов. Если не задано ни одного файла, используется файл стандартного ввода. Вывод команды awk обычно поступает в файл стандартного вывода. Команда awk сканирует каждую входную строку в поисках селектора. Когда строка сопоставляется с селектором, выполня- ется связанное с этим селектором действие. Общая форма про-
248 Гл. 8. Средства обработки данных граммы такова: BEGIN{ начальные операторы } { селектор { действие } } END{ завершающие операторы } Может быть задано несколько пар селектор—действие. Считы- вается входная строка и по очереди вычисляются все селекторы. Если значение селектора истинно, выполняется связанное с ним действие. Селектор или действие могут отсутствовать. Если не задан селектор, действие выполняется для каждой входной строки, и если опущено действие, то входные строки копируются в выходной файл. Действие может состоять из нескольких операторов, разде- ленных точкой с запятой. Ключевые слова BEGIN и END вводят действия, которые выполняются один раз в начале и в конце файла соответственно. Команда awk считывает каждую входную строку, или за- пись, и разбивает ее на поля. Поля обычно разделяются пробе- лами или табуляциями. В выражении $0 обозначает всю теку- щую запись, а $1, $2, ... обозначают первое поле, второе поле и т. д. Число полей в текущей записи содержится в переменной NF, а число считанных входных записей — в переменной NR. Текущий разделитель входных полей и текущий разделитель входных записей хранятся в переменных FS и, RS. Подразуме- ваемым значением для FS является пробел или табуляция, а для RS — литера новой строки. Для обработки файла с полями, разделенными литерой : (таких, как /etc/passwd), в разделе BEGIN следует поместить оператор FS = Чтобы вернуться к подразумеваемым разделителям полей, пере- менной FS следует присвоить пустую цепочку литер. Раздели- тель выходных полей хранится в переменной OFS; по умолчанию, этим разделителем является пробел. Имя обрабатываемого файла хранится в переменной FILENAME. (Присваивание зна- чения этой переменной не изменяет источника ввода.) Примеры awk '/srb/' Распечатать все записи из файла стандартного ввода, содержащие цепочку srb.
8.1. Краткое описание средств 249 awk 'END{print NR}' Распечатать число входных строк. awk 'print $3' Распечатать третье поле каждой записи. Выражения Значением выражений являются, в зависимости от контекста, либо цепочки литер, либо числа. При необходимости awk пре- образует цепочки в числа. В выражениях можно использовать операции +, —, *, /, %, конкатенацию (задается пробелом), а также операции языка С: ++,-------, +=, —=, *=, /= и % = . Переменные могут быть скалярами, массивами элементов (обозначаемыми x[i]) или именами полей $1, $2, ... . Перемен- ные инициализируются пустой цепочкой. Операции отношения <, <=, = = ,! = , >= и > имеют тот же смысл, что и в язы- ке С. Аргументами этих операций могут быть как цепочки литер, так и числа. Например, выражение NF > 5 истинно для записей, состоящих более чем из 5 полей. Выражение $l>"s" истинно, если первое поле строки больше, чем цепочка "s", со- гласно лексикографическому порядку. Например, это выражение истинно, если первое поле содержит "st" или "tom". Выражение $2>=0 истинно, если второе поле содержит положительное число. Предусмотрены также явные операции сопоставления с образ- цом ~ и !~. Например, выражение $l~/srb|SRB/ истинно для записей, первое поле которых содержит цепочку srb или SRB, а $1~/[SsJtreet/ истинно тогда, когда первое поле сопоставляется с образцом [Ss]treet. Образцы, используемые в системе awk, описаны в разд. 8.1. Используя операцию запятая, можно ограничить действие диапазоном строк. Например, если задать /begin/,/end/ {...} то операторы в фигурных скобках будут применяться к участкам текста, начинающимся со строки, содержащей begin, и заканчи- вающимся строкой, содержащей end.
250 Гл, 8. Средства обработки данных Действия Действие в системе awk состоит из одного или нескольких опера- торов, разделенных точкой с запятой или литерой новой строки. Оператор может быть оператором присваивания, условным опе- ратором, оператором цикла или встроенной функцией (например, print). В целом синтаксис и набор операторов в awk такие же, как и в языке С. Операторы бывают следующего вида: if ( условие ) оператор [ else оператор ] while ( условие ) оператор for ( выражение ; условие ; выражение ) оператор for ( идентификатор in массив ) оператор break continue { [ оператор ] ... } переменная = выражение print [ список-выражений ] [ >выражение ] printf формат [ , список-выражений ] [ ^выражение ] next ф пропустить оставшиеся селекторы для этой вход- ной строки exit # пропустить остаток входного файла Операторы завершаются точкой с запятой, литерой новой строки или правой фигурной скобкой. Пустой список-выражений обозначает всю строку. Пример. Программа if ($2 > $1) { х = $1 $1 = $2 $2 = х } копирует файл стандартного ввода в файл стандартного вывода, меняя' местами $1 и $2, если значение $2 превышает значение $1. Сравнение является арифметическим, если это возможно, и лексикографическим в противном случае. Форматная печать Для форматной печати в awk имеются два оператора: print и printf. Оператор print без параметров выводит всю текущую запись. Оператор printf — более общий, чем print, и позволяет задавать любой формат, принятый в языке С (см. гл. 5).
8.1. Краткое описание средств 251 Например, программа i = 1 while (i <== NF) { printf "%s, ", $i i++ } печатает все поля, вставляя запятую после каждого поля. Опе- ратор print без параметров печатает всю текущую запись; зада- вая параметры, с его помощью можно распечатать отдельные поля, например: print $2, $1 Этот оператор печатает сначала второе поле, затем первое поле и завершает печатаемую строку литерой новой строки. Оператор printf не выводит никаких неявных разделителей — все разделители должны быть заданы в формате. Например, printf "%s, %s\n", $1, $2 распечатает два первых поля, разделив их запятой. Массивы в команде awk можно индексировать цепочками литер. Таким образом, имеется ассоциативная память. Приведем пример. Первое поле файла паролей содержит регистрационное имя пользователя. Следующая программа проверяет, что каждое регистрационное имя встречается только один раз. awk ' BEGIN{ FS=":" { if (user[$H) { print $1, "duplicated" user[$l] = NR }' </etc/passwd Все элементы ассоциативного массива доступны через оператор for. Например, for (i in user) оператор В этом примере выполняется цикл по всем индексам массива user. Стандартные функции Единственными функциями, доступными программисту, исполь- зующему awk, являются встроенные функции. Все они перечис- лены ниже.
252 Гл. 8. Средства обработки данных sqrt, log, exp, int Квадратный корень, логарифм по основанию е, экс- понента и целая часть. Параметр функции задается в скобках. length Длина параметра — цепочки литер. Если параметр не задан, возвращается длина всей текущей записи. substr(s,m,n) Выделить из цепочки s подцепочку, начинающуюся с m-й литеры (первая литера имеет номер 1) и со- держащую максимум п литер. index(s, t) Позиция первого вхождения подцепочки t в цепочку s. Первая литера имеет номер 1. Если t не является подцепочкой s, возвращается 0. 8.1.2. Команда стр—сравнение двух файлов Команда стр файлх файл2 сравнивает файл! и файл2. Если файлы идентичны, ничего не выводится; если же они не идентичны, стр выдает номер байта и номер строки, где встречается первое различие. Возвращаются следующие коды ответа: 0 Файлы идентичны. 1 Файлы различаются. 2 Файлы не доступны или не заданы параметры. 8.1.3. Команда comm — выделение общих строк Команда comm выделяет или исключает общие для двух упоря- доченных файлов строки. comm файл* файл2 Оба файла (файлх и файл2) должны быть упорядочены в лек- сикографическом порядке. Вывод выполняется в три колонки. Первая колонка состоит из строк, содержащихся только в пер- вом файле, вторая — из строк, содержащихся только во втором файле, а третья — из строк, общих для обоих файлов. Вывод некоторых из колонок можно отменить при помощи опции —[123]. Например, команда comm —23 f2
8.1. Краткое описание средств 253 отменяет вывод второй и третьей колонок и печатает только стро- ки, содержащиеся в первом файле, но не содержащиеся во вто- ром файле, в то время как команда comm —12 fj f2 печатает только строки, содержащиеся в обоих файлах. В качестве примера рассмотрим командную процедуру для обработки файла слов, отвергнутых командой spell. for i do z mv spell/$i spell/$i.prev <$i spell >spell/$i comm —23 spell/$i spell/$i.prev done При каждом выполнении процедуры предыдущее содержимое файла сохраняется, а сам этот файл обновляется. Команда comm печатает новые орфографические ошибки. 8.1.4. Команда diff — обнаружение различий в файлах Команда diff обнаруживает различия между двумя файлами. Она выводит из обоих файлов те строки, которыми эти файлы отличаются друг от друга, пытаясь минимизировать объем этих отличий. Вызов команды diff имеет вид diff файл] файл2 Вывод команды diff напоминает запросы а, с и d редактора ed. Перед строками из первого файла помещается литера <, а перёд строками второго файла — литера >. Если задана опция —е, порождаются запросы редактора ed для преобразования файлам в файл2. Например, команда diff —е fl f2 >diffs поместит в файл diffs запросы редактора ed, которые требуется выполнить для того, чтобы преобразовать файл fl в файл f2. Коды ответа команды diff: О Файлы одинаковые. 1 Файлы различаются. 2 Параметр-файл не задан или не доступен. В некоторых версиях системы UNIX команда diff с опцией —г рекурсивно просматривает два оглавления и выполняет команду diff для каждого файла в этих оглавлениях.
254 Гл. 8. Средства обработки данных 8.1.5. Команда grep — выборка по образцу Команда grep — это фильтр для сопоставления с образцом. Она уже рассматривалась в гл. 2. Команда grep образец файл ... выбирает из одного или нескольких файлов строки, соответствую- щие заданному регулярному выражению. Выражения, задающие образцы, описаны в разд. 8.1. Типичный случай использования команды grep — поиск име- ни переменной или функции в ряде файлов, содержащих исход- ные программы на языке С. Например, команда grep main *.с просматривает в текущем оглавлении все файлы, имена которых оканчиваются на .с, и печатает строки, содержащие цепочку main. Если просматривается несколько файлов, в начало каждой выходной строки помещается имя соответствующего файла. Если образец содержит литеры, имеющие специальное зна- чение для оболочки (например, * или Л), этот образец следует заключить в кавычки. Например: grep 'Al0—9][0—9]*' Коды ответа команды grep: О Ни одной подходящей строки не найдено. 1 Обнаружены строки, соответствующие образцу. 2 Файлы не доступны либо в регулярном выражении име- ются синтаксические ошибки. Код ответа команды grep можно использовать для проверки наличия в файле некоторой цепочки. В следующем примере if grep образец файл then ... fi в заданном файле ищется заданный образец. Если образец найден, выполняется часть then условного оператора. В команде grep не допускаются альтернативы в регулярном выражении. В команде egrep можно использовать регулярное выражение в общем виде, включающем операции | (альтернати- ва), + (одно или несколько вхождений),? (нуль или одно вхож- дение) и круглые скобки для подвыражений.
8.1. Краткое описание средств 255 8.1.6. Команда join — объединение файлов Эта команда объединяет два файла, соединяя записи с одинако- выми значениями ключей. Ключом является некоторое поле записи. Типичное обращение к команде join имеет вид join файлх файл2 где оба файла файлх и файл2 должны быть отсортированы в лек- сикографическом порядке по полям, по которым будет выпол- няться соединение. Подразумеваемым ключом является первое поле каждой строки. Для каждой пары строк из файлах и файла2, имеющих оди- наковое значение ключа, в выходной файл помещается одна строка. Обычно выходные строки имеют вид общее-поле остаток-строки-из-файлах остаток-строки-из-файла2 Предположим, например, что файл dept содержит строки вида имя ф отделение а файл cost содержит строки вида имя ф сумма ф дата-покупки Литера ф обозначает табуляцию. Предположим, что оба файла упорядочены по первому полю. Тогда команда join dept cost будет печатать строку вида имя отделение сумма дата-покупки для каждой пары строк из этих двух файлов, имеющих одинако- вые значения поля имя. Предположим, что файл dept содержит Leo 13 Shaw 12 а файл cost содержит Leo $35 3/82 Shaw $20 2/82 Shaw $45 4/82 Тогда выводом команды join dept cost будет Leo 13 $35 3/82 Shaw 12 $20 2/82 Shaw 12 $45 4/82
256 Гл. 8. Средства обработки данных Команда join имеет ряд разнообразных опций. Поля в вы- ходных строках в приведенном примере разделены пробелами. Если изменить литеру-разделитель полей при помощи опции —t'®', то поля как во входных, так и в выходных строках разде- ляются литерой табуляции. При помощи опции —о задается список выходных полей. Например, команда join —о 1.2 2.1 2.2 файлх файл, будет печатать второе поле из первого файла и поля 1 и 2 из второго файла (в указанном порядке). Для файлов dept и cost из рассматриваемого примера было бы получено 13 Leo $35 12 Shaw $20 12 Shaw $45 Поля, по которым будет выполняться соединение, можно за- дать при помощи опции —j. Параметр —jn m вызывает соединение по m-му полю n-го файла. Например, команда join —jl 2 —j2 2 файлх файл, выполняет соединение по второму полю обоих файлов. Наиболее частая проблема, возникающая при использовании команды join — не полный вывод. Это может произойти из-за неправильного задания опций, в которых важен пробел, или из-за того, что файлы не упорядочены по значениям соответ- ствующих полей. Следующую процедуру можно использовать для печати всех тех ключей из файла cost, которые не содержатся в файле dept. Как и прежде, ключ содержится в первом поле каждого файла. # распечатать имена из файла cost, не содержащиеся в файле * dept field 1 Ccost I sort —u >/temp/cost$$ field 1 <dept I sort —u >/temp/dept$$ comm —23 /tmp/cost$$ /tmp/dept$$ rm /tmp/cost$$ /tmp/dept$$ Команды в первых двух строках порождают упорядоченный список ключей из каждого файла (без дубликатов). Первая версия этой процедуры была написана без использо- вания опции —и в команде sort. Были получены неожиданные результаты, и потребовалось некоторое время, чтобы понять,
8.1. Краткое описание средств 257 в чем дело. Этого можно было бы избежать, если бы процедура строилась в два этапа. На первом этапе следовало бы получить временные файлы. Эти файлы можно было бы затем распечатать и проверить на непредвиденные обстоятельства (такие, как наличие строк-дубликатов). 8.1.7. Пакетный редактор sed Редактор sed используется как фильтр (не интерактивно). Функ- ционирование редактора sed во многом напоминает функциони- рование системы сопоставления с образцами awk, описанной выше. Считывается каждая строка входного файла и выполня- ются применимые к ней запросы. Запросы редактора sed анало- гичны запросам редактора ed. Последовательность глобальных подстановок эффективнее выполнять при помощи редактора sed, чем ed. Редактор sed вызывается командой sed —-е цепочка-запросов или sed —f файл-запросов Запросы из цепочки-запросов или файла-запросов применяются к файлу стандартного ввода. Чтобы избежать интерпретации литер программой-оболочкой, цепочку-запросов можно заключить в кавычки. В общем случае запрос имеет вид адресх,адрес2 запрос параметр Если адреса опущены, запрос применяется к каждой строке. Если оба адреса совпадают, можно задавать только один. Если задано два адреса, они ограничивают участок файла, к которому будет применяться запрос редактирования. Адреса могут за- даваться либо в абсолютном виде, как десятичные числа, либо контекстом. В последнем случае используются регулярные вы- ражения, заключенные между косыми чертами и аналогичные регулярным выражениям в редакторе ed. Операции и "—" при адресации строк не допускаются. В отличии от редактора ed запрос применяется к каждой строке, сопоставляющейся с адресом. Предусмотрены, в частности, следующие запросы: s/.../.../ Заменить подцепочку. d Исключить строку. а\ Добавить текст вслед за текущей строкой. i\ Вставить текст перед текущей строкой. с\ Заменить текущую строку. 9 С. Баурн
258 Гл. 8. Средства обработки данных Каждая строка текста, следующего за запросами a, i или с, кроме последней, завершается литерой\. Имеются также запросы для чтения файлов (г), записи в файлы (w) и печати (р). # печать полного имени команды, найденной в SPATH for j do for i in 'echo SPATH f sed —e 's/:/ /g” do if test -f $i/$j then echo $i/$j fi done done Рис. 8.1 Команда path Типичным примером использования редактора sed является команда path, приведенная на рис. 8.1. Во внешнем цикле пере- менной j присваиваются по очереди значения параметров коман- ды path. Во внутреннем цикле переменной i присваиваются по очереди все слова (цепочки, не содержащие пробелов) из вывода команды echo SPATH | sed — e's/:/ /g' Этот вывод состоит из списка имен оглавлений, взятых из пере- менной оболочки PATH. Имена в списке разделены пробелами. 8.1.8. Команда sort — сортировка и слияние файлов Если обращение к sort имеет вид sort файл! файл2 ... эта команда сортирует все строки из файлов файл!, файл2, ... и помещает результат в файл стандартного вывода. Если ни одного файла не задано, считывается файл стандартного ввода, поэтому команду sort можно использовать как фильтр. По умол- чанию ключом сортировки является вся строка; сравнение вы- полняется побайтно в соответствии с машинным упорядочением литер (обычно литеры представляются в коде ASCII). Этот по- рядок называется лексикографическим; примерно такой же порядок используется в словарях. Имеется опция для сортировки в числовом порядке. Запись вида + ПОЗх —поза
8.1. Краткое описание средств 259 ограничивает ключ сортировки цепочкой, начинающейся с пози- ции no3i и заканчивающейся непосредственно перед позицией поз2г Например, команда sort -J-0 —1 3 —4 сортирует сначала по первому полю, а затем (при равенстве первых полей) по четвертому полю. (Частой ошибкой при исполь- зовании команды sort является неправильное задание номеров полей.) Вслед за начальной позицией может следовать буква п, означающая, что сравнение для этого поля должно быть число- вым. Например, команда sort 4-0п ... задает сортировку по первому полю в числовом порядке. По умолчанию поля разделяются пробелами. При помощи опции —t можно .задать свою литеру-разделитель полей. Так, например, команда sort —t: ... выполняет сортировку по полям, разделенным двоеточием. Среди других полезных опций команды sort следующие: —п Сортировать по числовому значению поля. —г Изменить порядок сортировки на противополож- ный/ —и Выводить только одну из множества одинаковых строк. В командах sort и join используются различные способы ну- мерации полей внутри записи, и это может вызвать путаницу. В команде sort запись +0 —1 означает первое поле. В команде join первое поле задается как 1. 8.1.9. Команда tail — вывести последние строки файла Команда tail — фильтр, печатающий несколько последних строк файла. Опция —п, где п — число, указывает, что требуется вывести последние п строк файла. В некоторых версиях системы можно задавать опцию —г, которая вызывает печать файла строка за строкой в обратном порядке. Например, команда uulog, пе- чатающая журнал работ, выполненных системой пересылки файлов uucp, записывается так: tail —г /usr/spool/uucp/SYSLOG 9*
260 Гл. 8. Средства обработки данных 8.1.10. Команда tr — преобразование литер Команда tr цепочка1 цепочка2 преобразует и копирует в файл стандартного вывода литеры из файла стандартного ввода. Все литеры, заданные в цепочкеп преобразуются в соответствующие литеры из цепочки2. Если цепочка2 короче, чем цепочка^ она расширяется до размера цепочких посредством повторения последней литеры требуемое число раз. tr '[A-Z]' 'Ca-z]' Рис. 8.2 Команда lower Команда lower на рис. 8.2 преобразует все прописные буквы в строчные. Обратите внимание на то, что параметры заключены .в кавычки. Это позволяет избежать их интерпретации програм- мой-оболочкой как образцов для имен файлов. 8.1.11. Команда uniq — исключить повторяющиеся строки Эта команда читает упорядоченный файл и сравнивает соседние строки. Если обращение к команде имеет вид uniq файл то из файла исключаются вторая и последующие копии из каж- дого набора соседних повторяющихся строк и остаток выводится в файл стандартного вывода. Если файл не задан, читается файл стандартного ввода. Одинаковые строки обнаруживаются только в том случае, если они соседние; поэтому перед выполнением команды uniq может потребоваться выполнить команду sort. ЛЭпция —и команды sort выполняет то же действие, что и команда uniq, но в команде sort нет некоторых опций, имеющихся в uniq. Предоставляются следующие опции: —с Перед каждой строкой печатать, сколько раз она встре- чается в файле. —d Печатать только строки, имеющие дубликаты, и только по одной копии таких строк. Следующая программа порождает список слов из заданного множества файлов и выдает частоту использования каждого слова. Если ни одного параметра не задано, цикл for выполняется
8.1. Краткое описание средств 261 один раз, причем переменной i присваивается пустая цепочка, в результате чего команда tr будет читать файл стандартного ввода. for i in do <$i tr —cs A—Za—z '\012' done | sort —f | uniq —c 8.1.12. Команда field — выделение колонок Команда field является базисным средством, хотя и не входит в стандартные версии системы UNIX. Реализация этой команды приводится в разд. 8.4 как пример построения новых средств обработки данных. Параметрами команды field являются номера полей, которые нужно скопировать из каждой записи. Например, по команде field 2 4 3 читается файл стандартного ввода, и в файл стандартного вывода выдаются поля 2, 4 и 3, разделенные литерами табуляции. Ана- логичное действие выполняется посредством awk '{print $2, $4, $3}' но поля в выходном файле разделяются пробелами. 8.1.13. Генераторы программ lex и уасс В этом разделе кратко рассматриваются два средства построения компиляторов — lex и уасс. Система lex воспринимает набор регулярных выражении и связанных с ними действий — фраг- ментов программ на языке С. В результате порождается про- грамма, которая распознает в своем входном файле эти регуляр- ные выражения и выполняет соответствующие им действия. Эта программа используется в качестве лексического анализатора в компиляторах языков программирования, например языка С. Особенно удобно использовать lex в сочетании с уасс. Система уасс воспринимает на входе ЬАЬК(1)-грамматику вместе с фрагментами программ на языке С и генерирует про- грамму, выполняющую синтаксический разбор своего входного текста согласно этой грамматике. Каждый раз, когда распозна- ется некоторое правило, выполняется фрагмент программы пользователя, заданный для этого правила. Предполагается, что входной текст разбивается на лексемы лексическим анализато- ром, например анализатором, сгенерированным системой lex. Пример использования системы lex приводится в разд. 8.2.2.
262 Гл. 8. Средства обработки данных 8.2. ПРОСТЕЙШИЕ ПРИМЕРЫ 8.2.1. Сопровождение простой базы данных В этом разделе мы рассмотрим процесс разработки программы, которая добавляет новую информацию в файл, содержащий имена абонентов, их адреса и телефонные номера. Эта программа назы- вается enter; она приведена на рис. 8.3. date^'date' while prompt ’’name: “ read name do case $name in ") continue ;* q) break ;; esac prompt ’’address; “ address- while read a do case $a in ") # адрес кончается пустой строкой break ;; ♦) address-"$address~$a” ;; esac done prompt ’’phone: ” read phone 11 break echo "$name®$address®$phone®$date"»$HOh/IE/tel done Рис. 8.3 Команда enter Первоначальный набросок программы выглядит так: while read name * имя do read address ф адрес read phone ф номеР * добавить информацию в файл done
8.2. Простейшие примеры 263 Для чтения данных используется предоставляемая оболочкой команда read read v i v 2 которая считывает одну строку из файла стандартного ввода, разбивает ее на слова и записывает эти слова в переменные Vi, v 2» ... . Если слов больше, чем переменных, все оставшиеся слова присваиваются последней переменной. Команда read всегда возвращает код ответа 0, за исключением случая, когда достигнут конец файла. Если дополнить команду read командой break read phone || break то при неудачном завершении команды чтения будет происходить выход из цикла while. Причиной этого может быть, например, достижение конца файла при попытке прочитать данные или получение сигнала прерывания. Для того чтобы для обработки файла-справочника можно было использовать стандартные средства обработки данных, новая информация добавляется в этот файл в виде отдельной записи. В приведенном выше наброске предполагалось, что адрес состоит из одной строки. Ниже показано, как мож