Текст
                    ii пользователей
У ЛЯ	3-е издание
программистов о пользователей

Unix® for Programmers and Users Third Edition
Unix® for Programmers and Users Third Edition Graham Glass and King Ables PEARSON Education Pearson Education, Inc. Upper Saddle River, New Jersey 07458
Г. Гласс К. Эйблс УЛЯ З-е издание программистов и пользователей Санкт-Петербург «БХВ-Петербург» 2004
УДК 681.3.066 ББК 32.973.26-018.2 Г52 Гласс Г., Эйблс К. Г52 UNIX для программистов и пользователей. — 3-е изд., перераб. и доп.— СПб.: БХВ-Петербург, 2004. — 848 с.: ил. ISBN 5-94157-404-5 Приводятся общие сведения о развитии UNIX. Рассматриваются утилиты, команды, системные вызовы и библиотечные функции для различных категорий пользователей. Описываются командные интерпретаторы Bourne shell, Korn shell, C shell и Bourne Again shell. Обсуждаются проблемы организации сети и использования Интернета. Подробно рассматриваются организация файловой системы, управление вызовами, ввод/вывод и взаимодействие процессов. Обсуждаются вопросы системного администрирования. Особое внимание уделено средствам программирования на языке С и системному программированию. Для преподавателей и студентов, а также широкого круга пользователей, программистов и системных администраторов УДК 681.3.066 ББК 32.973.26-018.2 Группа подготовки издания: Главный редактор Зав. редакцией Перевод с английского Редактор Компьютерная верстка Корректор Дизайн обложки Зав. производством Екатерина Кондукова Григорий Добин Андрей Питько Анна Кузьмина Натальи Смирновой Наталия Першакова Игоря Цырульникова Николай Тверских Authorized translation from the english language edition, entitled UNIX FOR PROGRAMMERS AND USERS, 3rd Edition, ISBN 0130465534, by ABLES, KING and GLASS, GRAHAM, published by Pearson Education, Inc, publishing as Prentice Hall Copyright © 2003. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. RUSSIAN language edition published by BHV St. Peteisbuig, Copyright © 2004. Авторизованный перевод английской редакции, выпущенной Prentice Hall, Pearson Education, Inc, © 2003. Bee права защищены. Никакая часть настоящей книги не может быть воспроизведена или передана в какой бы то ни было с}юрме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на то нет разрешения Pearson Education, Inc. Перевод на русский язык "БХВ-Петербург", © 2004. Лицензия ИД № 02429 от 24.07.00. Подписано в печать 21.06.04. Формат 70х1001/16. Печать офсетная. Усл. печ. л. 68,37. Тираж 4000 экз. Заказ № 786 "БХВ-Петербург", 190005, Санкт-Петербург, Измайловский пр., 29. Гигиеническое заключение на продукцию, товар № 77.99.02.953.Д.001537.03.02 от 13.03.2002 г. выдано Департаментом ГСЭН Минздрава России. Отпечатано с готовых диапозитивов в ОАО "Техническая книга" 190005, Санкт-Петербург, Измайловский пр., 29. ISBN 0-13-046553-4 (англ.) ISBN 5-94157-404-5 (русск.) © 2003 Pearson Education, Inc., Pearson Prentice Hall © Перевод на русский язык "БХВ-Петербург", 2004
Содержание Информация о торговых марках.......................................2 Об авторах.........................................................3 Введение......................................................... 4 О чем эта книга..................................................4 Структура книги..................................................5 Структура глав................................................. 6 Мотивация.....................................................6. Предпосылки....................................................6 Задачи.........................................................6 Изложение......................................................6 Утилиты........................................................6 Системные вызовы...............................................6 Команды shell..................................................6 Перечень тем...................................................6 Контрольные вопросы............................................6 Упражнения.....................................................7 Проекты........................................................7 Руководство для преподавателей...................................7 Условные обозначения.............................................7 Ссылки на другие книги...........................................8 Доступность исходного кода в режиме on-line......................9 Благодарности.....................................................10 Глава 1. Что такое UNIX?...........................................И Мотивация.........:............................................. 11 Предпосылки.....................................................11 Задачи..........................................................11 Изложение....................................................... 11 Компьютерные системы............................................12 Операционные системы............................................15 Программное обеспечение.........................................15 Разделение ресурсов.............................................16 Коммуникация................................................... 17 Утилиты.........................................................18
Поддержка программиста..........................................................19 Стандарты.......................................................................19 Особенности UNIX................................................................20 Принципы UNIX...................................................................20 Прошлое UNIX....................................................................22 Настоящее UNIX..................................................................23 Будущее UNIX.................................................................. 24 Остальная часть этой книги......................................................24 Обзор главы.....................................................................25 Перечень тем.................................................................25 Контрольные вопросы..........................................................25 Упражнение...................................................................26 Проект.......................................................................26 Глава 2. Утилиты UNIX для непрограммистов.........................................27 Мотивация.......................................................................27 Предпосылки.....................................................................27 Цели............................................................................27 Представление................................................................. 27 Утилиты.........................................................................28 Команда shell...................................................................28 Получение учетной записи........................................................28 Вход в систему..................................................................29 Командные интерпретаторы — shell................................................30 Выполнение программ.............................................................30 Входной, выходной каналы и канал сообщения об ошибках...........................31 Получение оперативной помощи: man...............................................32 Специальные символы.............................................................34 Завершение процесса: <Ctrl>+<C>..............................................35 Приостановка вывода: <Ctrl>+<S>/<Ctrl>+<Q>...................................35 Завершение ввода: <Ctrl>+<D>.................................................36 Установка пароля: passwd........................................................................................ 36 Выход из системы.............................................................. 37 Поэзия в движении: изучение файловой системы................................38 Определение текущего каталога: pwd..............................................39 • Абсолютные и относительные имена путей..........................................40 Создание файла..................................................................41 Просмотр содержимого каталога: Is...............................................42 Распечатка файла: cat/ тоге/page/ head/ tail............................................................. 44 Переименование файла: mv........................................................46 Создание каталога: mkdir........................................................46 Перемещение по каталогам: cd....................................................47 Копирование файла: ср........................................................................................... 49 Редактирование файла: vi........................................................49 Удаление каталога: rmdir........................................................50 Удаление файла: гт..............................................................51
Печать файла: lp/lpstat/cancel.................................................................52 Печать файла: Ipr/lpq/lprm ....................................................................54 Подсчет слов в файле: wc.......................................................................56 Атрибуты файла............................................................................... 56 Размер файла................................................................................57 Имя файла...................................................................................57 Время модификации...........................................................................58 Владелец файла..............................................................................58 Группа владельцев файла.....................................................................58 Типы файла..................................................................................59 Параметры полномочий файла..................................................................60 Счетчик жесткой связи.......................................................................62 Группы.........................................................................................62 Вывод групп: groups............................................................................63 Изменение группы: chgrp........................................................................................ 63 Изменения прав доступа файла: chmod............................................................64 Изменение владельца файла: chown ...................................................................... 66 Изменение групп: newgrp........................................................................................ 67 Поэзия в движении: эпилог......................................................................68 Определение типа вашего терминала: tset........................................................68 Изменение характеристик терминала: stty............................................................ 71 Редактирование файла: vi....................................................................................... 73 Запуск vi............................................................................................................... 73 Режим ввода текста..........................................................................74 Режим ввода команд..........................................................................75 Буфер памяти и временные файлы..............................................................76 Общие функции редактирования................................................................76 Перемещение курсора.........................................................................77 Удаление текста.............................................................................77 Замена текста...............................................................................78 Вставка текста..............................................................................79 Поиск.......................................................................................80 Поиск и замена..............................................................................80 Сохранение или загрузка файлов..............................................................81 Разное......................................................................................82 Настройка vi................................................................................83 Сохранение пользовательских настроек........................................................84 Для дополнительной информации...............................................................84 Редактирование файла: emacs....................................................................84 Запуск emacs................................................................................84 Команды emacs...............................................................................85 Как избежать проблем...................................................................... 86 Получение помощи............................................................................86 Выход из emacs..............................................................................86 Режимы emacs................................................................................86 Ввод текста.................................................................................87
Общие функции редактирования..................................................87 Перемещение курсора...........................................................87 Удаление, вставка и отмена....................................................88 Поиск....................’....................................................89 Поиск и замена................................................................90 Сохранение и загрузка файлов..................................................90 Дополнительные команды........................................................90 Для дополнительной информации.................................................90 Электронная почта: mail/mailx....................................................91 Отправка почты................................................................93 Чтение почты..................................................................94 Связь с системным администратором.............................................96 Обзор главы......................................................................96 Перечень тем..................................................................96 Контрольные вопросы...........................................................96 Упражнения....................................................................97 Проект........................................................................97 Глава 3. Утилиты UNIX для опытных пользователей....................................99 Мотивация........................................................................99 Предпосылки......................................................................99 Задачи...........................................................................99 Изложение........................................................................99 Утилиты.........................................................................100 Фильтрация файлов...............................................................101 Шаблоны фильтрации: egrep/fgrep/grep.........................................101 Шаблоны соответствия....................................................... 104 Удаление одинаковых строк: uniq.....................................................................105 Сортировка файлов: sort.........................................................106 Сравнение файлов................................................................109 Тестирование на сходство: стр.........................................................................109 Различие файлов: diff........................................................111 Поиск файлов: find..............................................................113 Архивы..........................................................................115 Копирование файлов: cpio.....................................................116 Архивация на ленту: tar......................................................119 Инкрементальные резервные копирования: dump и restore...........................122 Планирование выполнения команд..................................................123 Периодическое выполнение: cron/crontab.......................................124 Однократное выполнение: at............................................................................126 Программируемая обработка текста: awk...:.......................................128 яи;А:-программы..............................................................129 Доступ к отдельным полям.....................................................130 BEGIN и END..................................................................130 Операторы....................................................................131 Переменные...................................................................131
Структуры управления....................................................................132 Расширенные регулярные выражения........................................................132 Цепочки условий.........................................................................133 Разделители полей.......................................................................133 Встроенные функции......................................................................134 Жесткие и символические связи: In..........................................................134 Идентификация shell: whoami................................................................137 Замена пользователя: su.........................................................................................137 Проверка почты: biff...............................................................................................138 Преобразование файлов......................................................................139 Сжатие файлов: compress/uncompress и gzip/gunzip........................................139 Шифрование файла: crypt.................................................................141 Потоковое редактирование: sed......................................................... 142 Команды sed........................................143 Замена текста.........................................................................143 Удаление текста.......................................................................144 Вставка текста........................................................................145 Замена текста.........................................................................145 Вставка файлов........................................................................146 Составные команды sed.................................................................................146 Преобразование символов: tr.............................................................147 Преобразование подчеркнутых последовательностей: id...............................148 Просмотр необработанного содержимого файла: od..........................................149 Монтирование файловых систем: mount/ amount.............................................151 Идентификация терминалов: tty.......................................................................152 Форматирование текста: nroff/troff/style/spell....................................................153 Измерение времени выполнения: time......................................................153 Создание собственных программ: Perl........................................................154 Получение Perl..........................................................................154 Печать текста...........................................................................155 Переменные, строки и целые..............................................................155 Массивы.................................................................................156 Математические и логические операторы...................................................158 Строковые операторы.....................................................................159 Операторы сравнения.....................................................................160 Конструкции if, while, for и foreach ................................................................... 160 Ввод/вывод файла........................................................................161 Функции.................................................................................162 Библиотека функций......................................................................163 Аргументы командной строки..............................................................164 Пример из реального мира............................................................... 165 Обзор главы................................................................................168 Перечень тем............................................................................168 Контрольные вопросы.....................................................................168 Упражнения..............................................................................169 Проекты............................................................................... 169
Глава 4. Командные интерпретаторы shell.........................................171 Мотивация.....................................................................171 Предпосылки...................................................................171 Задачи........................................................................171 Утилиты.......................................................................172 Команды shell.................................................................172 Общие сведения о shell...................................................... 172 Функциональные возможности shell..............................................173 Выбор shell...................................................................173 Функционирование shell........................................................175 Исполняемые файлы и встроенные команды....................................... 175 Вывод информации на экран: echo............................................176 Смена каталогов: cd............................................................................................176 Метасимволы...................................................................176 Перенаправление ввода/вывода..................................................178 Перенаправление вывода.....................................................178 Переназначение ввода.......................................................179 Групповые символы поиска файлов...............................................180 Конвейеры.....................................................................181 Замещение команды.............................................................183 Последовательности............................................................184 Условные последовательности................................................184 Группирование команд..........................................................185 Фоновое выполнение........................................................ 186 Перенаправление фоновых процессов.............................................187 Перенаправление вывода.....................................................187 Перенаправление ввода......................................................188 Программы shell: скрипты......................................................188 Дочерние shell................................................................190 Переменные....................................................................191 Использование кавычек.........................................................193 Перенаправление ввода в буфер shell...........................................194 Управление работой............................................................195 Статус процесса: ps........................................................196 Процессы передачи сигналов: kill...........................................199 Ожидание дочернего процесса: wait.................................................................201 Поиск команды: $РАТН........................................................................................202 Подмена стандартных утилит....................................................203 Завершение и коды выхода.................................................... 203 Основные встроенные команды...................................................205 eval.......................................................................205 exec.......................................................................205 shift......................................................................206 umask......................................................................207 Обзор главы...................................................................208 Перечень тем...............................................................208
Контрольные вопросы..............................................................209 Упражнения.......................................................................209 Проект...........................................................................210 Глава 5. Bourne shell................................................................211 Мотивация..........................................................................211 Предпосылки........................................................................211 Задачи.............................................................................211 Изложение..........................................................................211 Утилиты............................................................................211 Команды shell......................................................................212 Общие сведения о Bourne shell......................................................212 Запуск.............................................................................212 Переменные.........................................................................213 Создание и назначение переменной.................................................213 Доступ к переменной..............................................................214 Чтение переменной со стандартного ввода..........................................216 Экспорт переменных...............................................................217 Переменные только для чтения.....................................................218 Предопределенные локальные переменные.......................................... 219 Предопределенные переменные окружения............................................220 Арифметические действия............................................................222 Условные выражения.................................................................224 Структуры управления...............................................................226 case ... in ... esac..................................................................................................226 for ... do ... done....................................................................................................228 if... then ...ft........................................................................................................229 trap.............................................................................230 until... do ... done.................:...........................................231 while ... done.........................................................................................................232 Пример проекта: track..............................................................233 track.sed........................................................................235 track, cleanup...................................................................236 track............................................................................236 Дополнительные встроенные.команды..................................................237 Команда чтения: точка (.)........................................................237 Команда null........................................................................................................238 Установка опций shell: set.......................................................238 Усовершенствования.................................................................239 Перенаправление................................................................ 239 Последовательности команд........................................................240 Опции командной строки........................................................... 241 Обзор главы........................................................................241 Перечень тем.....................................................................241 Контрольные вопросы..............................................................242 Упражнения.......................................................................242 Проекты..........................................................................243
Глава 6. Korn shell......................................................245 Мотивация..............................................................245 Предпосылки............................................................245 Задачи.................................................................245 Изложение..............................................................245 Команды shell..........................................................245 Общие сведения о Korn shell............................................246 Запуск.................................................................246 Псевдонимы.............................................................248 Создание псевдонимов встроенных команд...............................249 Удаление псевдонимов.................................................249 Предопределенные псевдонимы..........................................250 Некоторые полезные псевдонимы........................................250 Псевдонимы путей.....................................................251 Экспортируемые псевдонимы............................................251 История................................................................252 Пронумерованные команды............................................ 252 Сохранение команд....................................................253 Повторное выполнение команд..........................................253 Редактирование команд................................................254 Редактирование команд..................................................255 Встроенный редактор и................................................256 Дополнительные перемещения.........................................256 Дополнительный поиск...............................................257 Использование маски для имени файла................................257 Замещение псевдонима...............................................258 Встроенный редактор emacs/gmacs......................................258 Арифметические действия................................................259 Предотвращение интерпретации метасимволов............................260 Возвращаемые значения................................................261 Замена тильды..........................................................261 Меню: select.............................................................................................................262 Функции................................................................263 Использование параметров в функциях..................................265 Возврат из функции...................................................265 Контекст.............................................................266 Локальные переменные.................................................266 Рекурсия.............................................................267 Рекурсивный факториал: использование кода возврата.................267 Рекурсивный факториал: использование стандартного вывода...........268 Использование кода функций в нескольких скриптах.....................268 Расширенное управление заданиями.......................................269 Задания..............................................................269 Спецификация задания.................................................270 bg...................................................................270 fg................................................................. 271 kill.................................................................272
Усовершенствования...........................................................................273 Перенаправление............................................................................273 Конвейеры..................................................................................274 Замещение команды..........................................................................274 Переменные.................................................................................275 Гибкие методы доступа...................................................................275 Предопределенные локальные переменные...................................................276 Одномерные массивы......................................................................278 typeset.................................................................................278 Форматирование.............................................................................279 Регистр....................................................................................279 Тип........................................................................................280 Разное................................................................................... 281 typeset о не именованными переменными...................................................282 Встроенные команды.........................................................................283 cd.......................................................................................................................283 set.....................................................................................284 print................................................................................. 286 read...................................................................................................................287 test.....................................................................................................................287 trap....................................................................................................................288 Пример проекта: junk.......................................................................289 junk...................................................................................................................290 Ограниченный shell...........................................................................293 Опции командной строки.......................................................................293 Обзор главы..................................................................................294 Перечень тем...............................................................................294 Контрольные вопросы........................................................................294 Упражнения.................................................................................294 Проекты....................................................................................295 Глава 7. С shell................................................................................297 Мотивация....................................................................................297 Предпосылки..................................................................................297 Задачи..................................................................................... 297 Изложение.................................................................................. 297 Команды shell................................................................................297 Общие сведения о С shell.....................................................................298 Запуск.......................................................................................299 Переменные...................................................................................300 Создание и присвоение значений простым переменным..........................................300 Доступ к простой переменной.............................................................. 301 Создание списочных переменных..............................................................302 Доступ к списочной переменной..............................................................303 Построение списков.........................................................................304
Предопределенные локальные переменные...............................................304 Создание и присвоение переменных окружения..........................................306 Предопределенные переменные окружения...............................................306 Выражения............................................................................ 307 Строковые выражения............................................................... 307 Арифметические выражения............................................................307 Выражения, ориентированные на файл..................................................310 Автоматическое дописывание имени файла................................................311 Псевдонимы............................................................................311 Удаление псевдонима.................................................................313 Полезные псевдонимы.................................................................313 Разделение псевдонимов..............................................................314 Параметризованные псевдонимы...................................................... 314 История...............................................................................314 Пронумерованные команды.............................................................314 Сохранение команд...................................................................315 Чтение истории......................................................................315 Повторное выполнение команды...................................................... 316 Доступ к частям команд истории................................................317 Доступ к частям имен файлов....................................................318 Замена истории......................................................................318 Структуры управления..................................................................319 foreach ... end.....................................................................319 goto................................................................................320 if... then ... else ... endif.......................................................321 onintr............................................................................ 322 repeat..............................................................................323 switch ... case ... endsw...........................................................323 while ... end...........................................................................................................326 Пример проекта: junk.............................................................................................327 junk.............................................................................. 327 Усовершенствования....................................................................329 Повторное выполнение команды: клавиатурная комбинация быстрого вызова.....................................................................330 Метасимволы: {}................................................................................................330 Замещение имен файлов...............................................................330 Запрещение замещения имени файла.................................................331 Ситуации несовпадения............................................................331 П еренаправление....................................................................331 Перенаправление стандартного канала ошибки.......................................331 Защита файлов от случайного переписывания........................................332 Конвейеризация......................................................................332 Управление заданием.................................................................333 stop.............................................................................333 suspend..............................................................................................................334 nice.............................................................................334
nohup.................................................................................................................334 notify..................................................................................................................335 Завершение входного shell................................................................335 Встроенные команды.........................................................................336 chdir......................................................................................................................336 glob........................................................................................................................336 source....................................................................................................................336 Стек каталогов.............................................................................337 Хеш-таблица..............................................................................339 Опции командной строки.....................................................................340 Обзор главы................................................................................341 Перечень тем.............................................................................341 Контрольные вопросы......................................................................341 Упражнения...............................................................................342 Проект...................................................................................342 Глава 8. Bourne Again shell................................................................. 343 Мотивация..................................................................................343 Предпосылки................................................................................343 Задачи.....................................................................................343 Изложение..................................................................................344 Команды shell..............................................................................344 Общие сведения о Bourne Again shell........................................................344 Получение Bash...........................................................................345 Запуск.....................................................................................345 Переменные.................................................................................346 Создание и назначение простой переменной.................................................346 Доступ к простой переменной..............................................................347 Создание списочных переменных............................................................347 Доступ к списочным переменным............................................................348 Построение списков.......................................................................349 Удаление списков.........................................................................350 Экспорт переменных.......................................................................350 Предопределенные переменные..............................................................351 Клавиатурные комбинации быстрого вызова команд.............................................352 Псевдонимы...............................................................................352 История команд...........................................................................353 Хранение команд.......................................................................353 Чтение истории команд.................................................................353 Повторное выполнение команд...........................................................353 Замена истории........................................................................354 Редактирование команды................................................................355 Автозаполнение...........................................................................356 Арифметические действия....................................................................356 Условные выражения.........................................................................357 Арифметические выражения.................................................................357
Сравнение строк..............................................................358 Выражения, ориентированные на файл...........................................358 Структуры управления...........................................................360 case ... in ... esac...................................................................................................360 if... then ... elif... then ... else ...ft........................................................................361 for ... do ... done....................................................................................................362 while/until... do ... done...................................................362 Стек каталогов............................................................... 363 Управление заданием............................................................364 Функции........................................................................365 Разные встроенные команды......................................................366 Опции командной строки....................................................... 367 Обзор главы....................................................................368 Перечень тем.................................................................368 Контрольные вопросы..........................................................368 Упражнение...................................................................369 Проект.......................................................................369 Глава 9. Организация сети........................................................371 Мотивация......................................................................371 Предпосылки....................................................................371 Задачи.........................................................................371 Изложение......................................................................371 Утилиты........................................................................372 Общие сведения о сетях.........................................................372 Построение сети................................................................372 Сети Ethernet................................................................373 Мосты........................................................................374 Маршрутизаторы............................................................. 375 Шлюзы........................................................................375 Объединение сетей..............................................................376 Коммутация пакетов...........................................................376 Адреса Интернета.............................................................377 Именование...................................................................378 Маршрутизация................................................................378 Безопасность.................................................................378 Порты и общие сервисы...................................................... 379 Сетевое программирование.....................................................380 Пользователи...................................................................380 Распечатка списка пользователей: users/rusers................................381 Расширенные сведения о пользователях: who/rwho/w....................................382 Собственное имя хоста: hostname..............................................383 Персональные данные: finger..................................................384 Взаимодействие с пользователями................................................386 Защита от общения: mesg......................................................386 Передача строки за один раз: write...........................................387
Интерактивные диалоги: talk............................................................................388 Сообщения всем пользователям: wall..............................................................388 Распределенные данные..............................................................389 Копирование файлов между двумя UNIX-хостами: гср................................390 Копирование файлов между He-UNIX-хостами: ftp.......................................390 Распределенная обработка...........................................................393 Удаленный вход в систему: rlogin .....................................................................394 Выполнение удаленных команд: rsh.................................................................395 Удаленные соединения: telnet....................................................396 Сетевая файловая система: NFS......................................................399 Для дополнительной информации......................................................400 Обзор главы........................................................................400 Перечень тем....................................................................400 Контрольные вопросы.............................................................400 Упражнения......................................................................400 Проект..........................................................................401 Глава 10. Интернет...................................................................403 Мотивация..........................................................................403 Предпосылки........................................................................403 Задачи.............................................................................403 Изложение..........................................................................403 Эволюция Интернета.................................................................404 В начале: 1960-е................................................................404 Сетевая связь.................................................................405 ARPANET.......................................................................405 Стандартизация Интернета: 1970-е................................................405 Семейство протокола IP........................................................406 TCP/IP........................................................................406 UDP/IP........................................................................407 Интернет-адресация............................................................407 Интернет-приложения...........................................................408 Изменение архитектуры и переименование Интернета: 1980-е........................408 Служба доменных имен........................................................ 409 Дальнейшее развитие...........................................................412 Web: 1990-е.....................................................................413 "Убийственное приложение"................................................... 413 Web против Интернета..........................................................415 Достижимость..................................................................415 Изменения в Интернете.........................................................416 Безопасность..................................................................417 Авторское право...............................................................418 Цензура.......................................................................418 Дезинформация.................................................................418 Приемлемое использование......................................................419
Современный Интернет...........................................419 URL..........................................................420 Web-поиск..................................................421 Поиск пользователей и доменов............................ 422 Факторы, влияющие на будущее использование.................422 Обзор главы....................................................422 Перечень тем............................................... 422 Контрольные вопросы..........................................423 Упражнения...................................................423 Проект.......................................................423 Глава 11. Пользовательские интерфейсы............................425 Мотивация......................................................425 Предпосылки....................................................425 Задачи.........................................................425 Изложение......................................................425 Утилиты........................................................426 Общие сведения.................................................426 Графические пользовательские интерфейсы......................426 X Window System..............................................427 Х-серверы......................................................428 Геометрия экрана.............................................428 Безопасность и авторизация...................................429 Диспетчеры окон................................................430 Фокус........................................................430 Запуск программы.............................................431 Открытие и закрытие окон.....................................431 Выбор диспетчера окна........................................432 Виджеты........................................................433 Меню....................................................... 434 Командные кнопки.............................................434 Флажки и переключатели.......................................435 Полосы прокрутки.............................................435 Функции диспетчера окна Motif..................................436 Вызов корневого меню.........................................436 Открытие окна................................................437 Закрытие окна................................................437 Перемещение окна.............................................437 Изменение размера окна.......................................437 Размещение поверх других окон.........;......................437 Вызов меню окна............................................. 437 Приложение-клиент..............................................438 xclock.......................................................438 xbiff........................................................438 xterm...................................................... 440
Стандартные аргументы X-клиента................................440 Геометрия....................................................440 Цвет переднего и заднего плана...............................441 Заголовок....................................................441 Значок.......................................................441 Дополнительные темы............................................441 Копирование и вставка........................................441 Сетевые возможности..........................................443 Ресурсы приложения...........................................443 Как работают ресурсы.......................................444 Определение ресурсов.......................................445 Конфигурация и запуск........................................447 xinit и .xinitrc...........................................447 mwm и .mwmrc...............................................448 Обзор других Х-совместимых рабочих столов......................448 CDE..........................................................449 Gnome........................................................449 KDE..........................................................449 Open Windows.................................................449 VUE..........................................................450 Обзор главы....................................................450 Перечень тем.................................................450 Контрольные вопросы..........................................450 Упражнения...................................................451 Проект.......................................................451 Глава 12. Инструментальные средства программирования на С........453 Мотивация......................................................453 Предпосылки....................................................453 Задачи.........................................................453 Изложение......................................................453 Утилиты........................................................454 Язык С.........................................................454 Компиляторы С..................................................455 Одномодульные программы........................................455 Компиляция С-программы.......................................456 Листинг скорректированной программы reverse..................457 Выполнение С-программы.......................................458 Переопределение имени исполняемого файла.....................459 Многомодульные программы.......................................459 Многократно используемые функции.......................459 Подготовка многократно используемой функции..................460 reverse.h..................................................460 reverse, с.................................................460 mainl.c....................................................461
Отдельное компилирование и компоновка модулей......................................461 Автономный загрузчик: Id.................................................................................462 Повторное использование функции reverse.....................................................463 palindrome.h....................................................................463 palindrome.с....................................................................463 main2.c.........................................................................464 Поддержка многомодульных программ..................................................464 Система зависимости файлов UNIX: make................................................465 Make-файлы.........................................................................466 Порядок make-правил................................................................467 Выполнение таке...............................................................................................468 Маке-правила.......................................................................469 Написание собственных пользовательских правил......................................470 touch..............................................................................470 Макросы............................................................................471 Другие возможности таке............................................................472 Система поддержки архивов UNIX: аг...................................................472 Создание архива....................................................................473 Добавление файла...................................................................473 Присоединение файла................................................................473 Получение оглавления...............................................................474 Удаление файла.....................................................................474 Извлечение файла...................................................................474 Обработка архива из командной строки...............................................474 Обработка архива при помощи утилиты таке...........................................475 Упорядочение архивов...............................................................476 Создание оглавления: ranlib........................................................477 Разделяемые библиотеки.............................................................478 Система управления исходным кодом UNIX: SCCS.........................................479 Создание SCCS-файла................................................................480 Выборка файла......................................................................481 Наблюдение SCCS-активности.........................................................482 Отмена выборки и возврат файла.....................................................483 Создание новой дельты..............................................................483 Получение истории файла............................................................485 Идентифицирующие ключевые слова SCCS...............................................485 Создание нового релиза.............................................................486 Выборка копий только для чтения предыдущих версий..................................487 Выборка редактируемых копий предыдущих версий......................................487 Редактирование различных версии....................................................488 Удаление версии....................................................................489 Сжатие SCCS-фаилов.................................................................490 Ограничение доступа к SCCS-фаилам..................................................491 Блокировка релизов.................................................................492 Профайлер UNIX: prof...........................................................................................492 Перепроверка программ : lint................................................................................493
Отладчик UNIX: dbx............................................................................494 Подготовка программы для отладки............................................................496 Вход в режим отладки........................................................................496 Выполнение программы........................................................................497 Трассировка программы.......................................................................497 Трассировка переменных и вызовов функции....................................................498 Ошибки......................................................................................500 Точки останова..............................................................................500 Пошаговое выполнение........................................................................501 Организация доступа к переменным............................................................501 Листинг программы...........................................................................502 Выход из отладчика..........................................................................503 Резюме......................................................................................503 Удаление лишнего кода: strip..................................................................503 Обзор главы...................................................................................504 Перечень тем................................................................................504 Контрольные воросы..........................................................................504 Упражнения..................................................................................505 Проекты.....................................................................................505 Глава 13. Системное программирование.............................................................507 Мотивация.....................................................................................507 Предпосылки...................................................................................507 Задачи........................................................................................507 Изложение.....................................................................................507 Системные вызовы и библиотечные процедуры.....................................................508 Общие сведения................................................................................508 Обработка ошибки: реггог()...................................................................................510 Управление обычными файлами...................................................................513 Общие сведения об управлении файлами........................................................513 Первый пример: reverse......................................................................................515 Как работает reverse............................................................................................517 Листинг: reverse.с..........................................................................518 Открытие файла: ореп()......................................................................524 Создание файла...........................................................................526 Открытие существующего файла.............................................................526 Другие флаги открытия....................................................................526 Чтение из файла: read().....................................................................526 Запись в файл: \vrite().........................................................................................527 Перемещение в файле: lseek()................................................................528 Закрытие файла: close()......................................................................................530 Удаление файла: unlink()....................................................................531 Второй пример: monitor.................................................................... 532 Как работает monitor...................................................................................... . ..533 Листинг: monitor.c..........................................................................534 Получение информации о файле: stat()...........................................................543
Чтение информации каталога: getdentsQ...................................................545 Смешанные вызовы системы управления файлами.............................................546 Замена владельца файла или группы: chown() и fchovm()...................................547 Изменение прав доступа файла: chmod() и fchmod()........................................548 Дублирование дескриптора файла: dup() и dup2() ..........................................549 Действия с дескриптором файла: fcntl()..................................................550 Управление устройствами: ioctl()......................................................................552 Создание жестких связей: Нпк()........................................................................552 Создание специальных файлов: mknod()..........................................................554 Сбрасывание на диск буферов файловой системы: sync()....................................554 Усечение файла: truncate() и ftruncateQ.................................................555 STREAMS.................................................................................556 Усовершенствования по сравнению с традиционным вводом/выводом UNIX..................................................................556 Анатомия STREAMS.....................................................................557 Системные вызовы STREAMS.............................................................557 Управление процессами.....................................................................558 Создание нового процесса: fork() .....................................................................560 Осиротевшие процессы....................................................................562 Завершение процесса: exit().............................................................564 Зомби-процессы..........................................................................565 Ожидание дочернего процесса: wait().....................................................566 Замена кода процесса: ехес()............................................................568 Смена каталогов: chdir()................................................................569 Смена приоритетов: nice().................................................................................570 Доступ к ID пользователя и ID группы....................................................572 Пример программы: фоновая обработка.....................................................573 Пример программы: использование диска...................................................573 Подпроцессы-нити........................................................................576 Управление нитями....................................................................577 Синхронизация нитей..................................................................577 Безопасность нитей...................'...............................................577 Перенаправление.........................................................................578 Сигналы...................................................................................580 Определенные сигналы....................................................................581 Список сигналов.........................................................................581 Сигналы терминала.......................................................................582 Запрос аварийного сигнала: alarm()......................................................583 Обработка сигналов: signal()..............................................................................584 Защита критического кода и формирование цепочки обработчиков прерываний..............................................................................586 Посылка сигналов: кШ()..................................................................587 ’’Смерть" дочерних процессов............................................................587 Приостановка и возобновление процессов..................................................589 Группы процесса и терминалы управления..................................................591
IPC..................................................................596 Конвейеры..........................................................596 Безымянные конвейеры: pipe()......................................................................597 Именованные конвейеры............................................601 Программа-читатель...............................................603 Программа-писатель...............................................604 Примерный вывод..................................................605 Сокеты.............................................................605 Виды сокетов.....................................................607 Написание сокетных программ......................................608 Листинг примера “Chef-Cook"......................................609 Анализ исходного кода............................................613 Разделяемая память.................................................628 Семафоры...........................................................629 Internet shell.......................................................630 Ограничения........................................................630 Синтаксис команды..................................................631 Запуск Internet shell..............................................631 Встроенные команды.................................................631 Примеры обычных команд.............................................632 И нтернет-примеры..................................................633 Как работает Internet shell........................................635 Основной командный цикл............................................636 Анализ........................................................... 636 Выполнение командной последовательности............................637 Выполнение конвейеров..............................................637 Выполнение простой команды.........................................638 Переключение.......................................................638 Расширение возможностей............................................639 Листинг исходного кода Internet shell............................639 Обзор главы..........................................................666 Перечень тем.......................................................666 Контрольные вопросы................................................666 Упражнения.........................................................667 Проекты............................................................669 Глава 14. UNIX изнутри.................................................673 Мотивация............................................................673 Предпосылки..........................................................673 Задачи...............................................................673 Изложение............................................................673 Общие сведения...................................................... 674 Основы ядра..........................................................674 Подсистемы ядра....................................................674 Процессы и файлы...................................................675 Взаимодействие с ядром.............................................675
Системные вызовы..............................................676 Пользовательский режим и режим ядра...........................677 Синхронная обработка против асинхронной.......................679 Прерывания....................................................680 Прерывание прерывании.........................................681 Файловая система................................................683 Архитектура диска.............................................684 Чередование...................................................686 Хранение файла................................................686 Блочный ввод/вывод............................................687 Индексный дескриптор или inode................................688 Содержимое inode..............................................689 Карта блоков..................................................690 Размещение файловой системы...................................691 Суперблок.....................................................692 Bad-блоки.....................................................693 Каталоги......................................................693 Преобразование путевых имен в номера inode....................694 Пример преобразования путевого имени в inode..................695 Монтирование файловых систем..................................696 Система ввода/вывода файлов...................................697 Управление процессами...........................................697 Исполняемые файлы.............................................697 Первые процессы...............................................698 Процессы ядра и пользовательские процессы.....................699 Иерархия процессов............................................699 Состояния процесса............................................700 Состав процесса...............................................701 Область пользователя..........................................701 Таблица процессов.............................................702 Планировщик...................................................703 Правила планирования..........................................704 Управление памятью............................................706 Страницы памяти............................................. 706 Таблицы страниц и области.................................... 706 Таблица ОЗУ...................................................708 Загрузка исполняемого файла: ехес()...........................708 Преобразование адреса.........................................709 Иллюстрация алгоритма MMU.....................................710 ММU и таблица страниц.........................................710 Схема памяти после первой команды.............................711 Схема памяти после нескольких команд..........................712 Демон страниц.................................................714 Пространство обмена...........................................714 Алгоритм демона страниц.......................................714 Схема памяти после удаления нескольких страниц................715
Доступ к странице, которая хранится в пространстве обмена.................716 Дублирование процесса: fork()............................................................717 Обработка ссылок на разделяемые страницы ОЗУ и пространства обмена....................................................................719 Пробуксовка и свопинг....................................................................720 Прекращение процесса: exit() ...............................................................720 Сигналы..................................................................................721 setpgrp()..............................................................................721 signal()...............................................................................................................722 Сигналы после системных вызовов fork() или ехес() ................................722 Обработка сигнала......................................................................723 exit()..................................................................................................................723 wait ()................................................................................723 кШ()...................................................................................724 Ввод/вывод..................................................................................724 Объекты ввода/вывода.....................................................................724 Системные вызовы ввода/вывода............................................................725 Буферизация ввода/вывода.................................................................725 sync().................................................................................727 Ввод/вывод обычных файлов................................................................728 ореп()................................................................................................................728 read().................................................................................................................729 yvrite()...............................................................................730 IseekQ.................................................................................730 close()................................................................................731 dup()................................................................................ 733 unlink()........................................................................................ ....................733 Ввод/вывод каталогов.....................................................................734 mknod()................................................................................734 link().................................................................................735 Монтирование файловых систем.............................................................735 mount().............................................................................. 735 Перевод имен файлов......................................................................736 amount()...............................................................................737 Ввод/вывод специальных файлов............................................................737 Интерфейс устройства...................................................................738 Старшие и младшие номера...............................................................739 Таблицы переключения...................................................................739 ореп().................................................................................741 read().................................................................................742 write()................................................................................742 close()742 ioctl()................................................................................742 Ввод/вывод терминала.....................................................................743 Структуры данных терминала.............................................................744
Чтение с терминала.........................................744 Запись на терминал.........................................745 Потоки.......................................................745 Взаимодействие процессов.......................................746 Конвейеры....................................................746 Конвейеры System V Release 3...............................746 Структуры данных конвейера.................................747 Запись в конвейер..........................................747 Чтение из конвейера........................................747 Закрытие конвейера....................................... 747 Конвейеры System V Release 4............................. 748 Конвейеры BSD..............................................748 Сокеты.......................................................748 Управление памятью.........................................748 Сокеты и таблица открытых файлов......................... 749 Запись в сокет.............................................749 Чтение из сокета...........................................750 Обзор главы....................................................750 Перечень тем.................................................750 Контрольные вопросы..........................................750 Упражнения...................................................751 Проекты......................................................752 Глава 15. Системное администрирование............................753 Мотивация......................................................753 Предпосылки....................................................753 Задачи.........................................................753 Изложение......................................................753 Утилиты........................................................754 Общие сведения о системном администрировании...................754 Как стать привилегированным пользователем......................755 Запуск UNIX....................................................755 Остановка системы..............................................757 Поддержка файловой системы.....................................759 Целостность файловой системы.................................759 Использование диска........................................ 760 Назначение квот..............................................763 Создание новых файловых систем...............................763 Создание резервных копий файловых систем.....................764 Поддержка пользовательских учетных записей.....................764 Файл паролей.................................................765 Файл групп...................................................766 Установка программного обеспечения.............................767 Периферийные устройства........................................769 Установка драйвера устройства................................769 Терминальные файлы.......................................-...770
Сетевой интерфейс...............................................771 Автоматизация задач.............................................772 Учет системных ресурсов.........................................773 Конфигурирование ядра...........................................773 Проблемы безопасности...........................................774 Обзор главы.....................................................776 Перечень тем..................................................776 Контрольные вопросы...........................................776 Упражнения....................................................777 Проект........................................................777 Глава 16. Будущее.................................................779 Мотивация.......................................................779 Предпосылки.....................................................779 Задачи..........................................................779 Изложение.......................................................779 Общие сведения..................................................780 Влияние настоящего и будущего на UNIX...........................780 Объектно-ориентированное программирование.....................780 Что такое объект?...........................................781 Как объект используется.....................................781 Так чем же хорошо все это?..................................782 Наследование................................................782 Открытые программные средства.................................783 Free Software Foundation....................................783 Linux.......................................................784 Параллельные, распределенные и многопроцессорные системы......784 Параллельная обработка......................................785 Распределенные системы......................................785 Многопроцессорные системы...................................786 "Ошибка" 2000 года............................................786 Что же это в действительности?..............................787 Год в две цифры.............................................787 Годы, начиная с 1900........................................788 UNIX и XXI столетие.........................................789 64-разрядные системы..........................................789 Интернет-адресация: IPv6......................................790 Сети с высокой пропускной способностью........................791 Отказоустойчивые системы......................................792 Обзор современных популярных версий UNIX........................792 AIX...........................................................794 Caldera SCO/Unixware..........................................794 FreeBSD.......................................................794 HP-UX.........................................................795 IRIX..........................................................795 Linux.........................................................796
NetBSD......................................................796 OpenBSD.....................................................797 Tru64 UNIX..................................................797 Solaris.....................................................797 Обзор главы...................................................798 Перечень тем................................................798 Контрольные вопросы.........................................798 Упражнение..................................................799 Проект......................................................799 Приложение......................................................801 Регулярные выражения..........................................801 Шаблоны.....................................................802 Расширенные регулярные выражения............................802 Модифицированная для UNIX нотация Бэкуса—Наура................803 Библиография....................................................805 Предметный указатель............................................808
Правде и Красоте, где бы их ни находили, и Свободе, и всем, кто жертвовал ради них
Информация о торговых марках AIX — торговая марка корпорации IBM. Ethernet — зарегистрированная торговая марка корпорации Xerox. FreeBSD — торговая марка Berkeley Software Design, Inc. GNU — торговая марка Free Software Foundation. HP-UX — зарегистрированная торговая марка компании Hewlett-Packard. Itanium — зарегистрированная торговая марка корпорации Intel. IRIX — зарегистрированная торговая марка Silicon Graphics, Inc. Java — торговая марка Sun Microsystems, Inc. KDE и К Desktop Environment — торговые марки KDE e. V. Linux — торговая марка Linus Torvalds. MacOS — зарегистрированная торговая марка Apple Computer. Microsoft Windows, Windows NT и Windows 2000 — зарегистрированные торговые марки корпорации Microsoft. Netscape — зарегистрированная торговая марка корпорации Netscape Communications. SCO и UNIXWare — торговые марки Caldera. Solaris, Spare и Open Windows — торговые марки Sun Microsystems, Inc. Tru64 — торговая марка компании Hewlett-Packard. UNIX — зарегистрированная торговая марка The Open Group. VMS и Open VMS — зарегистрированные торговые марки компании Hewlett-Packard. X Window System — торговая марка The Open Group.
Об авторах Грэхэм Гласс (Graham Glass) окончил Университет Саутгемптона (Англия) со степенью бакалавра по информатике и математике в 1983 г. Эмигрировав в Соединенные Штаты, он получил степень мастера по информатике Техасского университета в Далласе (University of Texas at Dallas) в 1985 г. В то время он работал как системный аналитик UNIX/С и активно принимал участие в исследованиях нейронных сетей и параллельной распределенной обработки данных. Позже он преподавал в Техасском университете в Далласе разнообразные курсы, включая UNIX, С, ассемблер, языки программирования C++ и Smalltalk. Он был соучредителем корпорации ObjectSpace и в настоящее время обучает и консультирует такие компании, как DSC Corporation, Texas Instruments, Northern Telecom, J.C. Penney и Bell Northern Research, используя свои знания объектно-ориентированного программирования (ООП) и параллельных систем, чтобы проектировать и строить параллельную объектно-ориентированную компьютерную систему и язык, основанные на транспьютерном чипе Inmos Т9000. В свободное время он пишет музыку, ныряет с аквалангом, ходит на лыжах и иногда спит. Кинг Эйблс (King Ables) получил степень бакалавра по информатике в Техасском университете в Остине (University of Texas at Austin) в 1982 г. Он был пользователем, разработчиком, системным администратором UNIX и консультантом с 1979 г., работая как в маленьких начинающих компаниях, так и в больших корпорациях. Обеспечивал поддержку и обучение, разрабатывал программное обеспечение UNIX и системные инструментальные средства, писал документацию на продукты и материалы для обучения. В 1990-х гг. он был единоличным владельцем консультационной фирмы по UNIX в Остине до момента, когда решил перебраться в горы Колорадо. До этого проекта он издал книгу по администрированию системы UNIX. Он также написал много журнальных статей на различные темы, относящиеся к UNIX, и имеет патент на механизм секретности для электронной коммерции. Его профессиональные интересы включают организацию безопасности сети и конфиденциальности в Интернете, но он любит путешествовать пешком и кататься на лыжах немного больше.
Введение О чем эта книга Одной из моих работ1 перед написанием этой книги было преподавание UNIX множеству разных людей, включая университетских студентов, промышленных С-хакеров и иногда друзей и коллег. За это время я приобрел обширные знания, в том числе и в форме существенной библиотеки примеров. Поэтому я часто думал, что будет хорошо систематизировать их и написать книгу. Когда я начал готовить мою университетскую серию лекций о UNIX, то нашел, что ни один из доступных учебников UNIX не удовлетворяет моей цели — они были или слишком неструктурированными, слишком специализированными или страдали недостатком неподходящих упражнений и проектов для моих студентов. В ответ на сложившуюся ситуацию я написал самую первую версию этой книги. После прошествия пары лет я полностью ее переписал, допуская осторожную мысль о включении нового материала. Я решил сгруппировать информацию, основываясь на типичных категориях пользователей UNIX, позволяя использовать книгу большому количеству людей без превышения (или принижения) умственных способностей каждого. Одно хитрое решение касалось уровня деталей, чтобы включить темы, подобные системным вызовам и утилитам. Большинство их имеет множество специализированных опций и особенностей, которые редко используются, и описание их всех при охвате всего диапазона желаемых тем завершится книгой толщиной приблизительно в два фута1 2. Из-за этого я включил в книгу информацию только о наиболее общих и полезных особенностях утилит, командных интерпретаторов (shells) и системных вызовов. Я включаю ссылки на другие коммерчески доступные книги для большего количества деталей. Этот гибридный подход оказался хорошим компромиссом; я надеюсь, что вы согласны. В это издание добавлена глава о командном интерпретаторе Bourne Again shell (также называемом Bash), который стал важным из-за его всеобъемлющей позиции в Linux. Также обновлена информация о версиях UNIX/Linux, рабочих столах X Window System и нюансах работы менеджера окон, расширено описание' редактора vi, языка Perl и стандарта IPv6, добавлено и улучшено описание некоторых команд, контрольных вопросов и упражнений. 1 Здесь, по видимому, повествование идет от имени Грэхэма Гласса. — Ред. 2 Около 60 см. — Ред.
Структура книги UNIX — большая система. Чтобы описать ее полностью, требуется объяснение многих различных вопросов с нескольких различных точек зрения. Что я как раз и попытался сделать. Книга разбита на следующие секции, каждая предназначена для конкретной категории пользователей: П Глава 1. Что такое UNIX? П Глава 2. Утилиты UNIX для непрограммистов. □ Глава 3. Утилиты UNIX для опытных пользователей. □ Глава 4. Командные интерпретаторы shell. П Глава 5. Bourne shell. □ Глава 6. Korn shell. П Глава 7. С shell. П Глава 8. Bourne Again shell. □ Глава 9. Организация сети. □ Глава 10. Интернет. □ Глава 11. Пользовательские интерфейсы. □ Глава 12. Инструментальные средства программирования на С. □ Глава 13. Системное программирование. □ Глава 14. UNIX изнутри. □ Глава 15. Системное администрирование. П Глава 16. Будущее. □ Приложение. □ Библиография. Я рекомендую, чтобы различные категории пользователей читали главы так, как представлено в табл. В1. Таблица В1 Категория пользователя Главы Повседневные нерегулярные пользователи 1,2 Опытные пользователи 1-4, 9-11 Программисты 1-13, 16 Системные аналитики 1-14, 16 Мастера Все главы 2 Зак. 786
Структура глав Каждая глава в этой книге имеет перечисленные ниже стандартные вводные разделы. Мотивация Объясняется, почему полезно изучить материал, который представлен в главе. Предпосылки Описано, что читатель должен знать, чтобы успешно усвоить материал главы. Задачи Приводится список представленных тем. Изложение Описывается метод, которым представлены темы. Утилиты Дается список утилит, описываемых в главе (когда уместно). Системные вызовы Приводится список системных вызовов, рассматриваемых в главе (когда уместно). Команды shell Представлен список команд shell, охваченных в главе (когда уместно). Кроме того, каждая глава заканчивается разделом обзора, содержащим перечисленные ниже разделы. Перечень тем Резюме тем. Контрольные вопросы Быстрая самопроверка.
Упражнения Список упражнений, расположенных по рангу: легкий, средний или высокий. Проекты Один или более имеющих отношение к теме главы проектов, расположенных по рангу: легкий, средний или высокий. Руководство для преподавателей Как было упомянуто ранее, эта книга была первоначально написана для студентов старших курсов и аспирантов. Я советую разрабатывать лекционный цикл, основанный на книге, следующим образом. П Если студенты не знают язык С, то среднескоростной курс может начинаться с глав 1, 2, 4 и 12. Лектор может тогда представить студентам С и использовать содержание главы 13 для упражнений в аудитории и проектов. □ Если студенты уже знают язык С, то среднескоростнои курс может включать главы 1, 2, 4, 7, 12—14. Проекты, ориентированные на параллельную обработку и взаимодействие процессов, будут гарантировать, что студенты окончат курс с хорошим знанием основных принципов UNIX. □ Если студенты знают язык С и полны энтузиазма, предлагаю охватить все главы за исключением глав 3, 5 и 6 в одном семестре. Знаю, что это возможно, поскольку я преподавал курс лекций этим способом! Условные обозначения В книге имеются ссылки на утилиты UNIX, команды shell (т. е. команды, которые являются непосредственно частью командного интерпретатора) и системные вызовы (функции библиотеки UNIX). Весьма легко перепутать эти три вещи, так что я принял непротиворечивый способ для их различия: □ утилиты UNIX всегда написаны полужирным моноширинным шрифтом, вот так: "утилита mkdir создает каталог"; П команды shell всегда написаны моноширинным шрифтом, вот так: "команда history выводит ваши предыдущие команды"; □ системные вызовы всегда сопровождаются круглыми скобками, вот так: "системный вызов fork() дублирует процесс".
Формальные описания утилит, команд shell и системных вызовов предваряются выделенной серым цветом строкой, за которой следует синтаксис и разъяснение. Для примера ниже приведено описание утилиты UNIX man. Синтаксис man [глава] слово man -к ключевоеСлово Первое использование man выводит содержимое секций справочника, связанных с параметром слово. Значение параметра глава необязательно. Если номер главы не указан, будет показана первая глава, которую найдет man. Второе использование man выводит список всех статей справочника, которые содержат ключевоеСлово. Примеры UNIX-сессий представлены шрифтом Courier. Пользовательский ввод с клавиатуры всегда показывается курсивом, а примечаниям всегда предшествуют пропуски слов (...). Вот пример: $ 1s ...генерирует листинг каталога. myfile.txt yourfile.txt $ whoami glass $ ... выводится новое приглашение. Ссылки на другие книги Насколько удобно повторно использовать существующий код, настолько хорошо иметь другой справочный материал, когда он не пересекается с естественным потоком изложения. Информация, которая, как мы считаем, слишком специализирована для данной книги, отмечается ссылкой на публикацию в библиографии, приведенной в конце книги. Например, "Для получения информации относительно порта UNIX на процессоре 68030, см. с. 426 из [Wait 1987]". В квадратных скобках обычно указаны имя автора и год публикации, когда книге дано право "относиться к литературе по UNIX".
Доступность исходного кода в режиме on-line Примеры исходного кода, используемого в этом издании, доступны в режиме on-line. Короткие примеры не включены, но примеры любой "существенной” длины могут быть найдены в сети по адресу ftp://ftp.prenhall.com/pub/esm/the_apt__series.s-042/glass_ables_unix-3e/. (Вы можете напечатать эту строку в браузере или см. главу 9 для получения дополнительной информации об FTP.)
Благодарности Следующие люди способствовали выпуску предыдущих изданий этой книги: Джеймс Ф. Петерс (James F. Peters), Фади Диик (Fadi Deek), доктор Уильям Бурне (Dr. William Burns), Ричард Невман-Волф (Richard Newman-Wolfe), Дэвид Карвер (David Carver), Билл Тепфенхарт (Bill Tepfenhart), Стивен Раго (Stephen Rago) и Марк Эллис (Mark Ellis). Этому изданию способствовал ценный технический вклад от Лори Мурфи (Lori Murphy), Лоренса Б. Веллса (Lowrence В. Wells) и Майкла Д. Брека (Michael D. Breck). Как всегда, сотрудники Prentice Hall — особенно Петра Ректер (Petra Recter), Камилла Трентакост (Camille Trentacoste) и Лакшми Баласубраманиан (Lakshmi Balasubramanian) — всегда были благосклонны и ободряли нас.
Глава 1 Что такое UNIX? Мотивация UNIX — хорошо известная в техническом мире операционная система, популярность которой в последнее время растет и в деловых кругах. Знание ее возможностей и особенностей поможет понять, почему все больше и больше людей предпочитает использовать UNIX, и сделает вашу работу более эффективной. Предпосылки Для понимания данной главы необходим небольшой опыт работы с компьютером и знакомство с такими компьютерными терминами, как программа, файл и центральный процессор (ЦП). Задачи В этой главе будет дано определение термину "операционная система" н описаны ее основные компоненты, а также рассказано, почему UNIX очень перспективна. Мы рассмотрим эту систему с нескольких различных ракурсов, поэтому информация будет интересна и полезна достаточно широкой аудитории, от начинающего пользователя до продвинутого системного программиста. Изложение Сначала будет рассказано о главных частях и элементах, которые формируют обычную компьютерную систему. Затем мы убедимся, насколько необходима специальная программа, называемая операционной системой, чтобы эффективно управлять этими частями, и рассмотрим некоторые ее возможности. Далее обратимся к философии UNIX, которая лежит в основе всей
этой книги. И, наконец, познакомимся с краткой историей UNIX и получим некоторое представление, о ее (т. е. системы) происхождении. Компьютерные системы Обычная однопользовательская компьютерная система состоит из многих частей, таких как центральный процессор (ЦП- Central Processing Unit, CPU), память, диски, монитор и клавиатура. Маленькие системы, подобные этой, могут быть связаны вместе, чтобы формировать большие компьютерные сети, распределять задачи между отдельными компьютерами. Рис. 1.1 является иллюстрацией такой сети. ОЗУ ПЗУ Рис. 1.1. Типичная компьютерная сеть
Аппаратные средства ЭВМ, которые составляют компьютерную сеть — только половина истории; программное обеспечение, которое работает на компьютерах, не менее важно. Давайте рассмотрим детальнее различные аппаратные средства ЭВМ и компоненты программного обеспечения компьютерной системы. □ Аппаратные средства (hardware). Компьютерные системы, большие или маленькие, одно- или многопользовательские, дорогие или дешевые, состоят из множества компонентов. П Центральный процессор (ЦП). ЦП (Central Processing Unit, CPU) читает машинные коды (инструкции в форме, которую компьютер может понимать) из памяти и выполняет их. Как часто считают, ЦП является "мозгом" компьютера. П Оперативное запоминающее устройство (ОЗУ). ОЗУ (Random Access Memory, RAM) хранит машинные коды и данные, к которым обращается ЦП. ОЗУ обычно "забывает" все, что хранит, когда питание выключено. □ Постоянное запоминающее устройство (ПЗУ). ЗУ (Read Only Memory, ROM) хранит машинные коды и данные. Его содержимое не изменяется и хранится, даже когда питание выключено. □ Диск (disk). Диски хранят большое количество данных и кода в магнитной или оптической среде и "помнят” все это, даже когда питание выключено. Дискеты, как правило, сменные, тогда как жесткие диски — нет. Объем жестких дисков позволяет хранить на нем гораздо больше информации, чем на дискете. □ Накопитель CD-ROM (CD-ROM Drive). Накопители CD-ROM позволяют компьютеру читать информацию, записанную в цифровой форме, с компактного диска. Информация может представлять собой поток данных или файловую систему, которую ОС (операционная система) может читать так, как если бы она была расположена на жестком диске. □ Монитор (monitor). Мониторы отображают информацию и поставляются в двух разновидностях: монохромные и цветные. Монохромные мониторы редки в более новых (современных) компьютерных системах. □ Видеокарта (Graphics card). Видеокарты позволяют ЦП показывать информацию на мониторе. Некоторые графические карты могут показывать только знаки, тогда как другие поддерживают графику.
□ Клавиатура (keyboard). Клавиатура позволяет пользователю вводить алфавитно-цифровую информацию. Доступны несколько различных видов раскладок клавиатуры, что частично-зависит от языка пользователя. Например, японская раскладка клавиатура больше, чем западная, поскольку алфавит первой намного больше. Западная раскладка клавиатуры часто называется QWERTY-клавиатура — по первым шести символам левой верхней строки. □ Мышь (mouse). Мышь обеспечивает легкое позиционирование курсора на экране короткими движениями руки. Большинство мышей имеет ’’хвосты”, которые соединяют их с компьютером, но некоторые имеют радио- или инфракрасные связи, которые делают ’’хвост" ненужным. Беспроводная форма будет весьма удобна для того, кто имеет кота, т. к. эти животные очень часто запутываются в этом проводе. □ Принтер (printer). Принтер позволяет пользователю получить твердую копию информации. Некоторые принтеры печатают только знаки, тогда как другие печатают графику. □ Лента (tape). Магнитные ленты используются для создания резервных копий информации, хранящейся на дисках. Они медленнее, чем диски, но хранят большие количества данных и довольно дешевы. □ Модем (modem). Модем позволяет связываться с другими компьютерами по телефонной линии. Различные модемы допускают разные скорости связи. Большинство модемов даже исправляет ошибки, которые происходят из-за плохой телефонной связи. □ Ethernet-интерфейс (Ethernet interface). Ethernet — это среда (обычно провода), которая делает доступной связь на высоких скоростях. Компьютеры подсоединяются к Ethernet через специальное устройство аппаратных средств, называемое интерфейсом Ethernet. □ Другие периферийные устройства (peripherals). Существует множество периферийных устройств, которые могут поддерживать компьютерные системы, включая графические планшеты, оптические сканеры, матричные процессоры, звуковые карты, карты распознавания голоса и синтезаторы (это далеко не полный список). Вы не можете просто соединить эти части аппаратных средств ЭВМ вместе и иметь рабочую систему: необходимо установить программное обеспече
ние, управляющее и координирующее все части. Способность разделять периферийные устройства, распределять память, связываться с другими компьютерами, выполнять более, чем одну программу одновременно обеспечивается специальным видом программы, называемой операционной системой (ОС). Можно представить себе ее как "суперпрограмму", которая обеспечивает работу остальных программ. Давайте поближе познакомимся с операционными системами. Операционные системы Как вы могли заметить, компьютер не может функционировать без операционной системы. Существует множество различных операционных систем, доступных для персональных компьютеров (ПК — Personal Computer, PC), мини-компьютеров и универсальных ЭВМ; наиболее распространенными являются Windows NT и Windows 20001, VMS, MacOS и разновидности UNIX. Операционная система UNIX доступна для различных платформ, тогда как большинство других ОС привязано к определенному семейству аппаратных средств. Одно из главных достоинств в UNIX — доступность практически для любого компьютера. Из перечисленных операционных систем только UNIX и VMS позволяют использовать ресурсы компьютера более, чем одному пользователю одновременно, что является очевидным преимуществом для коммерческих систем. Многие фирмы покупают мощный мини-компьютер с двадцатью или более терминалами и устанавливают операционную систему UNIX, позволяющую распределять ЦП, память и дисковое пространство между пользователями. Что же мы получаем, когда выбираем UNIX, как операционную систему? Давайте рассмотрим ее с точки зрения программного обеспечения. Программное обеспечение Один из способов описать аппаратные средства системы состоит в том, чтобы рассказать, какие они обеспечивают условия для выполнения программ и хранения файлов. Виды программ, работающих на UNIX-платформах, различаются по размеру и сложности, но имеют некоторые общие характеристики. Далее приведен список характеристик UNIX-программ и файлов. □ Файл — набор данных, который обычно хранится на диске или другом носителе. UNIX обращается с периферийными устройствами как со специальными файлами, поэтому терминалы, принтеры и другие устройства были доступны таким же образом, как файлы на обычном диске. * В настоящий момент есть и другие версии Windows. — Ред.
□ Программа — собрание байтов, представляющих код и данные, которые хранятся в файле. П Запущенная программа загружается с диска в ОЗУ (фактически загружена только ее часть, но мы поговорим об этом позже). Когда программа выполняется, она называется процессом. □ Большинство процессов читает и пишет данные через файлы. □ Процессы и файлы имеют владельца и могут быть защищены от несанкционированного доступа. □ UNIX поддерживает иерархическую структуру каталогов. □ Файлы и процессы имеют "местоположение" в пределах иерархии каталогов. Процесс может изменять его собственное местоположение или местоположение файла. □ UNIX предоставляет возможность создания, модификации и уничтожения программ, процессов и файлов. На рис. 1.2 приведена иллюстрация крошечной иерархии UNIX-каталогов, которая содержит четыре файла и процесс, управляющий обслуживающей программой sort. / (Корневой каталог) I---------------1---------------------. home bin •---------------1--------------• — glass tim who sort myfile.txt afile.txt Обозначения: Процесс Файл Рис. 1.2. Иерархия каталогов Разделение ресурсов Другая функция операционной системы, которую реализует UNIX, — разделение ограниченных ресурсов среди конкурирующих процессов. Чаще всего
ограниченные ресурсы в компьютерной системе включают ЦП, память, место на диске и периферийные устройства типа принтеров. Вот краткая схема того, как эти ресурсы разделяются. □ UNIX разделяет ЦП среди процессов путем деления каждой секунды процессорного времени на "кванты" равного размера (обычно 1/10 секунды) и затем выдает их процессам на основе системы приоритетов. Важным процессам выделяется большее количество квантов, чем другим. □ UNIX разделяет память среди процессов путем деления ОЗУ на тысячи "страниц" памяти равного размера и затем выделяет их процессам на основе системы приоритетов. Только те части процесса, которые фактически должны быть в ОЗУ, всегда загружаются с диска. Страницы ОЗУ, к которым не обращаются некоторое время, вновь сохраняются на диск так, чтобы память могла быть перераспределена другим процессам. □ UNIX разделяет дисковое пространство среди пользователей путем деления дисков на тысячи "блоков" равного размера и затем выделяет их пользователям согласно системе квот. Отдельный файл построен из одного или более блоков. Глава 14 содержит подробное описание, как реализуется механизм разделения ресурсов. Итак, мы рассмотрели все функции, которые выполняет UNIX как операционная система, кроме одной — среды коммуникации. Коммуникация Компоненты системы малоэффективны без взаимодействия друг с другом. Это хорошо иллюстрируют следующие примеры: □ процесс может нуждаться во взаимодействии с видеокартой, чтобы вывести результаты на экран; □ процессу требуется взаимодействие с клавиатурой, чтобы получить входные данные; □ почтовой системе необходимо взаимодействие с другими компьютерами, чтобы посылать и получать почту; □ два процесса должны взаимодействовать друг с другом для совместного решения отдельной проблемы. В зависимости от типа и скорости связи UNIX предусматривает несколько различных способов взаимодействия процессов и периферийных устройств. Например, один из путей, посредством которого процесс может взаимодействовать с другим процессом, —- это механизм межпроцессной связи, называемый pipe (конвейер). Конвейер — это односторонний канал данных средней скорости, который позволяет взаимодействовать двум процессам на той
же самой машине. Если процессы находятся на различных машинах, связанных сетью, то вместо него используется механизм, называемый сокетом (socket — гнездо, двунаправленный канал). Сокет — двусторонний быстродействующий канал данных. Разделение задачи между различными процессами — распространенная в настоящее время технология. Например, есть графическая система X Window, которая работает, используя так называемую клиент-серверную модель. Один компьютер (Х-сервер) используется для управления графическим терминалом и рисования различных линий, кругов и окон, в то время как другой (X-клиент) воспроизводит данные, которые должны быть показаны. Системы, подобные этой, являются примерами распределенной обработки, в которой вычислительная нагрузка разделена среди нескольких компьютеров. Фактически единственный Х-сервер может обслуживать множество Х-клиентов. На рис. 1.3 представлена иллюстрация X Window System. Х-сервер Ethernet Рис. 1.3. Х-сервер и Х-клиенты Мы обсудим X Window System в главе 11. Утилиты Даже очень мощная операционная система не представляет большой ценности для пользователя, если не содержит достаточного количества полезных программ. Успех на рынке и достаточно ’’солидный” возраст UNIX делают очень разнообразным состав входящего в эту операционную систему программного обеспечения. Стандартная UNIX-система дополняется, как минимум, парой редакторов, С-компилятором, утилитами сортировки, графическим пользовательским интерфейсом, различными оболочками, программами обработки текста. Коммерчески доступны популярные пакеты, включающие в
себя электронные таблицы, компиляторы, издательские системы и т. д. Кроме того, большое количество бесплатного программного обеспечения распространяется через Интернет, которому посвящена глава 10. Поддержка программиста Система UNIX очень удобна для программистов. Это открытая система, внутренняя программная архитектура которой хорошо документирована и доступна либо в форме исходного текста, либо за небольшую плату. Особенности этой операционной системы, такие как параллельная обработка, взаимодействие процессов и работа с файлами, — все является легко доступным из языка программирования С и набора библиотечных подпрограмм, называемых системными вызовами. Многие функции, использование которых было затруднено в старых операционных системах, теперь легкодоступны для любого системного программиста. Стандарты Эта операционная система придерживается определенных стандартов, разработанных двумя основными группами. UNIX была создана в AT&T Bell Laboratories и впоследствии развилась в систему, известную как System И Калифорнийский Университет (Беркли) получил копию UNIX в самом начале его разработки и создал другую версию, впоследствии ставшую доминирующей, — Berkeley Standard Distribution (BSD) UNIX. System V и BSD UNIX имеют свои сильные и слабые стороны, но в них и много общего. Два консорциума ведущих компьютерных изготовителей развивают каждый свою систему и полагают, что их версия будет лучшей. Консорциум UNIX International, возглавляемый AT&T и Sun, поддержал самую последнюю версию System V UNIX — System V Release 4. Группа Open Software Foundation (OSF), возглавляемая IBM, Digital Equipment Corporation и Hewlett-Packard, попыталась создать преемника BSD UNIX, названного OSF/1. Обе группы следовали набору стандартов, установленных комитетом Portable Operating System Interface (POSIX) и подобными организациями. Проект OSF не так давно провалился, сделав System V "победителем UNIX-войны”, хотя лучшие черты BSD UNIX были включены в большинство версий операционных систем, базирующихся на System V. В разной степени черты BSD UNIX свойственны таким дистрибутивам, как Solaris (от Sun Microsystems), HP-UX (от Hewlett-Packard), AIX (от IBM) и IRIX (от Silicon Graphics, Inc). Система UNIX написана главным образом на языке С, который делает относительно несложным ее перенос на различные аппаратные платформы. Такая мобильность — одно из основных свойств, способствующих быстрому распространению и успеху UNIX.
Особенности UNIX Краткий перечень основных особенностей UNIX таков: □ позволяет нескольким пользователям одновременно взаимодействовать с системой; □ поддерживает создание, модификацию и уничтожение программ, процессов и файлов; □ реализует иерархическую систему каталогов; □ эффективно разделяет ресурсы системы: ЦП, память и дисковое пространство между процессами; □ позволяет процессам и внешним устройствам, взаимодействовать друг с другом, даже если они находятся на различных компьютерах; □ включает в себя большое количество различных утилит; □ для большинства версий UNIX имеется множество коммерческих программных пакетов; □ позволяет программистам взаимодействовать с операционной системой через набор системных вызовов, аналогичных библиотеке подпрограмм. UNIX — мобильная операционная система, доступная практически на всех аппаратных платформах. Теперь, после того как мы рассмотрели основные особенности UNIX, пришло время исследовать принципы ее организации и заглянуть в прошлое и будущее. Принципы UNIX Исходная реализация UNIX-системы была весьма и весьма ограничена. Она содержала небольшое число утилит, не имела практически никаких сетевых возможностей и средств администрирования. Первые разработчики UNIX выработали основной принцип, положенный в основу создания утилит: программа должна хорошо справляться с конкретной проблемой, а сложные задачи выполняются путем использования этих утилит вместе. Так возник специальный механизм, называемый конвейером (pipe). Конвейер предоставляет возможность использовать выходные данные одного процесса как входные данные для другого процесса. Два или более процесса могут быть связаны этим способом, создающим конвейер данных, текущих от первого процесса до последнего, как показано на рис. 1.4 и 1.5.
Рис. 1.4. Конвейер устройство Рис. 1.5. Сортирующий конвейер Компоновка процессов в виде конвейера оказалась исключительно эффективной. Каждый процесс в этом случае выполняет набор операций над данными и затем передает результаты следующему процессу для дальнейшей обработки. Например, вообразите, что вы желаете получить отсортированный список всех пользователей UNIX-системы. Есть утилита who, которая выводит несортированный список пользователей, и другая утилита sort, которая выводит отсортированную версию ее входных данных. Обе утилиты могут быть связаны вместе каналом так, чтобы выходные данные от who передавались непосредственно в sort, что дает в результате отсортированный список пользователей. Это более эффективный подход к решению проблем, чем написание новой программы с нуля или использование двух программ, одна из которых должна будет сохранять промежуточные данные во временном файле, который в последствии будет использоваться второй программой. Основной алгоритм UNIX, подходящий для решения любой задачи, можно выразить следующим образом: □ если можно решить проблему, используя каналы для объединения существующих утилит, делайте это; □ иначе обратитесь к другим пользователям в Интернете. Если они знают, как решить эту проблему, воспользуйтесь их советом; □ иначе если вы можете написать собственную утилиту для решения этой проблемы — напишите ее и добавьте в коллекцию UNIX. Проектируйте каждую утилиту так, чтобы она хорошо справлялась с поставленной задачей и могла использоваться другими для решения аналогичных проблем. Если дописанные утилиты не могут решить проблему, напишите программу для ее решения (например, на С, C++ или Java). ОС UNIX реализует еще одну концепцию, от которой постепенно приходится отказываться. Изначально система была разработана программистами, которые хотели иметь доступ к данным или коду вне зависимости от уста
новленных прав доступа. Для реализации этой идеи они разработали концепцию суперпользователя (привилегированного пользователя, root), которая подразумевала получение неограниченных прав некоторыми пользователями. Например, администратор UNIX-системы всегда имеет возможность стать привилегированным пользователем для выполнения таких задач, как завершение нестандартных процессов или удаление нежелательных пользователей из системы. Очевидно, что концепция суперпользователя подрывает систему безопасности: любой пользователь с правильным паролем может получить доступ к защищенным данным или уничтожить всю систему. Поэтому в некоторых, находящихся в разработке, версиях UNIX полностью отказываются от концепции привилегированного пользователя, подразделяя задачи между несколькими "менее привилегированными". Прошлое UNIX Кен Томпсон (Ken Thompson) из Bell Laboratories разработал самую первую версию UNIX. Кен работал над проблемой создания видеоигры под названием "Космические войны" ("Space Wars"), которая требовала быстрой реакции системы. Используемая им операционная система MULTICS не давала ему той производительности, в которой тот нуждался. Он назвал свою систему UNIX: часть названия "UNI" подразумевала, что она будет делать нечто единственное хорошо, в противоположность "MULTI" — части имени "MULTICS" — которая пыталась делать многое, но без особого успеха. Кен написал систему на ассемблере, и первая версия оказалась очень простой: это была однопользовательская система без каких-либо сетевых возможностей с примитивной системой управления разделением памяти. Однако она была компактной и быстродействующей, как и хотел Кен Томпсон. Несколькими годами позже коллега Кена, Деннис Ритчи (Dennis Ritchie), предложил переписать UNIX, используя язык С, который Деннис недавно разработал на основе языка В. Идея написания операционной системы на языке высокого уровня в те времена была необычной. Большинство программистов полагали, что скомпилированный код не будет работать достаточно быстро2 и что для операционной системы обоснованно только прямое использование машинного языка. К счастью, выбор С оказался удачным, и система UNIX внезапно получила огромное преимущество над другими операционными системами: ее исходный код был понятен. На ассемблере осталась лишь небольшая часть, а это означало возможность переноса операционной системы на другую платформу. Если платформа имеет С-компилятор, большая часть кода будет работать без изменений; должны быть переписаны только секции ассемблера. 2 С тех пор технология компиляторов также претерпела изменения, поэтому большинство компиляторов создает более эффективный код.
Bell Laboratories начала использовать этот прототип версии UNIX в ее патентном отделе, прежде всего, для обработки текста, и множество UNIX-утилит, которые присутствуют в современных UNIX-системах — например, nroff и troff — были разработаны в этот период. Но в то время в соответствии с антимонопольными инструкциями AT&T были запрещены продажи программного обеспечения, поэтому Bell Laboratories бесплатно передала лицензию на исходный код UNIX университетам, надеясь, что инициативные студенты расширят систему и поспособствуют ее продвижению на рынок. Действительно, аспиранты Университета Калифорнии в Беркли отнеслись к задаче серьезно и внесли немало усовершенствований, включая создание хорошей системы управления памятью и реальной возможности организации сети. Университет начал продавать всем желающим свою собственную версию UNIX, названную Berkeley Standard Distribution (BSD) UNIX. Различия между версиями UNIX сохраняются и по сей день. Настоящее UNIX Многие компании производят и продают собственные версии UNIX, обычно предназначенные для работы на собственных аппаратных платформах. Не так давно появилась еще одна UNIX-подобная операционная система — Linux. В разработке Linux участвовали многие программисты со всего мира, и теперь она продается и поддерживается несколькими различными компаниями. UNIX Рис. 1.6. Сокращенная UNIX-генеалогия
Предыдущие версии UNIX базировались либо на System V, либо на BSD, тогда как современные включают в себя особенности обеих систем. На рйс. 1.6 представлена сокращенная генеалогия UNIX. Так как ОС Linux полностью написана заново и не использует общих кодов UNIX, она не входит в состав "генеалогического дерева". Тем не менее, ощущается значительное влияние всех версий UNIX на создание и развитие этой операционной системы. В главе 16 мы увидим, какие версии UNIX являются доступными для различных аппаратных платформ. Будущее UNIX Вероятно, последующие версии UNIX будут поддерживать ее философию подобно современным версиям UNIX — например, сохраняя идею построения приложения из конвейера связанных утилит. Кроме того, UNIX должна реализовать некоторые актуальные тенденции, такие как параллельная обработка и объектно-ориентированное программирование. Мы рассмотрим ряд вопросов, связанных с перспективами развития UNIX, в главе 16. Остальная часть этой книги Теперь вы, скорее всего, осознали, что описание UNIX — довольно обширная тема, которая может быть должным образом усвоена лишь небольшими порциями. Для удобства пользователей и предоставления возможности сосредоточиться на наиболее интересных для них темах главы книги ориентированы на различные категории пользователей. Эти пользователи могут быть объединены в следующие группы: □ непрограммисты, или простые пользователи, которым время от времени необходимо выполнять простые задачи, такие как отправление и получение электронной почты, использование электронных таблиц или обработка текстов; □ пользователи shell (shell — командный интерпретатор), использующие фоновую обработку и пишущие небольшие программы на макроязыке shell в рамках пользовательского интерфейса; □ опытные пользователи, применяющие шифрование файлов, потоковые редакторы и файловую обработку данных; □ опытные пользователи shell, пишущие программы на языке высокого уровня shell (он немного похож на язык управления задачами, или JCL) для того, чтобы автоматизировать такие задачи, как создание резервных
копий файлов, мониторинг использования диска, установка программного обеспечения; □ программисты, создающие программы на языке типа С; □ системные программисты, которым необходимо хорошее знание операционной системы для создания программ взаимодействия с сетью или доступа к файлам; □ системные архитекторы, совершенствующие компьютерные системы; □ системные администраторы, поддерживающие работу системы и пользователей. Читайте наиболее заинтересовавшие вас главы. Затем, если располагаете временем, вернитесь назад и заполните пробелы в своих знаниях. Если вы не можете определить, где содержится необходимая информация, ознакомьтесь с Введением. Обзор главы Перечень тем В этой главе мы познакомились с: □ главными компонентами аппаратных средств системы; П назначением операционной системы; □ значением терминов программа, процесс и файл', □ схемой иерархической структуры каталогов; П разделением процессорного времени, памяти, дискового пространства между процессами; П взаимодействием между процессами и внешними устройствами; □ набором стандартных утилит UNIX; П главными пакетами программ, доступными для UNIX-систем; П концепцией "открытой" системы; □ перспективами развития UNIX. Контрольные вопросы 1. Как называются основные версии UNIX, как они развивались? 2. Назовите пять главных функций операционной системы. 3. Укажите различие между процессом и программой.
4. Расскажите об основных концепциях, легших в основу UNIX. 5. Назовите создателей UNIX. 6. Почему UNIX называется "открытой" системой? Упражнение Составьте список популярных в настоящее время операционных систем, отличных от UNIX, выделите их достоинства и недостатки. [Уровень: средний.] Проект Рассмотрите систему MULTICS и найдите сходство и различия с UNIX-системой. [Уровень: средний.]
Глава 2 Утилиты UNIX для непрограммистов Мотивация Эта глава содержит основы, необходимые вам для работы с UNIX. Предпосылки Для понимания изложенной ниже информации вам необходимо ознакомиться с главой 7. Кроме того, более глубокому усвоению будет способствовать наличие UNIX на вашем компьютере для того, чтобы вы могли познакомиться на практике с теми особенностями, которые мы рассмотрим в теории. Цели В этой главе мы познакомимся с процедурой входа/выхода из системы, научимся изменять пароль, пользоваться встроенным справочником, работать с файловой системой. Кроме того, мы узнаем, как пользоваться почтовой системой, что станет первым шагом на нашем пути в Интернет. Представление Информация в этой главе представлена в форме нескольких примеров UNIX-сессий. Если сейчас на вашем компьютере не установлена ОС UNIX, попробуйте повторить описанные процедуры, как только появится такая возможность.
Утилиты Ниже в алфавитном порядке приведен список стандартных утилит UNIX: cancel head mv wc cat Ip newgrp chgrp Ipr page chmod Iprm passwd chown Ipq pwd clear Ipstat rm cp Is rmdir date mail stty emacs man tail file mkdir tset groups more vi Команда shell Этот раздел посвящен следующей команде shell: cd Получение учетной записи Для того чтобы установить UNIX-систему на своем компьютере, вы можете купить один цз представленных дистрибутивов Linux в любом компьютерном магазине либо скачать ее из Интернета. Если вы располагаете необходимыми средствами, то можете приобрести коммерческий дистрибутив UNIX. В настоящее время существуют как коммерческие, так и бесплатные версии UNIX (см. главу 16). Преимущество в наличии собственной UNIX-системы состоит в том, что вы можете быть привилегированным пользователем, полноправно распоряжающимся всеми ресурсами. Если у вас нет возможности установить UNIX, то для того, чтобы поработать с этой операционной системой, вам придется получить учетную запись у обладателя этой установленной системы. Естественно, ваши права в этом случае будут сильно ограничены.
Вход в систему Первое, что необходимо сделать для работы с UNIX-системой — войти в нее, бведя учетное имя пользователя (suitable username) — уникальную комбинацию символов, отличающую вас от других пользователей. Как правило, учетное имя пользователя и пароль назначаются администратором системы, либо в случае, если вы установили UNIX на свой компьютер, эти параметры установлены в процессе конфигурации или заложены по умолчанию. Бывает, необходимо нажать клавишу <Enter> (также известна как клавиша <Return>) несколько раз, прежде чем появится приглашение ввести имя пользователя (login) и пароль (password). Из соображений безопасности символы, которые вы вводите при наборе пароля, не отображаются. UNIX чувствительна к регистру клавиатуры, поэтому удостоверьтесь, что вводите имя пользователя и пароль в том регистре, в котором оно было выдано вам администратором. В зависимости от настроек системы вы должны увидеть приглашение $ или %. Эти символы означают, что регистрация в системе прошла успешно, и она ожидает ваших дальнейших команд. Так выглядит процедура регистрации пользователя в UNIX: UNIX(г) System V Release 4.0 login: glass Password: ...то, что я напечатал здесь — секрет и не показано Last login: Sun Feb 15 18:33:26 from dialin $ _ В процессе регистрации система спросит вас, какой вид терминала вы используете для того, чтобы адекватно реагировать на вводимые вами символы. Если вам неизвестна эта информация, просто нажмите клавишу <Enter>, тем самым соглашаясь использовать установленный по умолчанию терминал. Позже будет рассказано, как в случае необходимости изменить тип терминала. В зависимости от настроек системы в процессе регистрации может происходить следующее: □ если вы входите в систему в первый раз, вам может быть предложено пройти ознакомительный тур по UNIX; □ на вашем экране могут появляться новости дня и другая полезная информация. Ниже приведен еще один пример входа в систему, где предлагается выбрать тип терминала. Нажав клавишу <Enter>, мы подтверждаем выбор терминала по умолчанию — vtioo: UNIX(г) System V Release 4.0 login: glass Password: ...секрет
Last login: Sun Feb 15 21:26:13 from dialin You have mail. ...система сообщает о наличии почты. TERM = (vtlOO) ...я нажал клавишу <Enter>. Командные интерпретаторы — shell Приглашения $ или %, которые пользователь видит, когда в первый раз регистрируется в UNIX, выводятся специальным видом программы, называемой командным интерпретатором или shell, являющейся посредником между пользователем и операционной системой. Shell позволяет выполнять программы, строить конвейеры из процессов, сохранять результаты в файлы и управлять несколькими программами одновременно. Shell выполняет все команды, которые вы вводите. Существуют четыре наиболее популярных программных оболочки: □ Bourne shell; □ Korn shell; П C shell; □ Bourne Again shell (или Bash). Все эти оболочки похожи друг на друга, хотя и имеют некоторый ряд отличий. Большинство пользователей предпочитают Korn shell, потому что эта оболочка имеет лучший интерфейс командной строки и в ней легко программировать. Каждой оболочке посвящена одна из глав этой книги. Но до этого, в главе 4, мы рассмотрим, общие принципы работы с shell. Каждый shell имеет свой собственный язык программирования. Вы можете спросить, зачем писать программу на языке shell, а не языке С или Java? Ответ — языки shell разработаны специально для управления файлами и процессами в UNIX, поэтому во многих ситуациях они более удобны. В этой главе мы научимся пользоваться разнообразными утилитами UNIX и сохранять результаты работы в файл. Рассмотрим выполнение нескольких простых UNIX-команд. Выполнение программ Для того чтобы запустить программу, просто вводят ее название и нажимают клавишу <Enter>. Давайте договоримся, что в тех случаях, когда в книге говорится, что вы должны ввести какой-нибудь текст, имеется в виду также, что вы должны нажать клавишу <Enter> после того, как закончите набор. Этим вы сообщаете операционной системе, что закончили ввод команды и желаете, чтобы она была выполнена.
Все системы обладают разным набором утилит, так что, возможно, некоторые примеры не будут работать в вашей версии UNIX. Рассматривая специфические утилиты, мы будем указывать, в каких версиях они будут работать. А сейчас рассмотрим утилиту date, имеющуюся в каждой системе и показывающую текущую дату и время. $ date ... запуск утилиты date. Thu Mar 12 10:41:50 MST 1998 $ _ Для описания синтаксиса используется интуитивно понятная система обозначений в форме Бэкуса—Наура (Backus—Naur FORM, BNF), расшифровка которой описана в Приложении. Книга не ставит своей целью описать все параметры какой-либо утилиты. При необходимости вы можете узнать их, пользуясь встроенным в систему справочником или специализированной литературой. Синтаксис date [yymmddhhmm [.ss]] Утилита date по умолчанию показывает текущие дату и время. В остальных случаях date возвращает заданные в параметрах значения, где уу — последние две цифры года, первые mm — месяц (числовая нумерация), dd — день, hh — час (используется 24-часовая система) и следующие mm — минуты. Дополнительный параметр — ss — секунды. Устанавливать дату может только привилегированный пользователь (root). Ниже описана команда, очищающая экран. Синтаксис clear Эта утилита очищает экран. Входной, выходной каналы и канал сообщения об ошибках Результаты выполнения команды date, рассмотренной в предыдущем разделе, выводились на терминал. Кроме того, что UNIX может записывать полу
ченные результаты в файл, для любой команды или программы существуют три стандартных канала ввода/вывода: □ стандартный вход, известный как stdin, при помощи которого программа получает входные данные; П стандартный выход, называемый stdout, в который по умолчанию программа пишет выходные данные; □ стандартный канал вывода сообщений об ошибках — stder. По умолчанию все три канала ввода/вывода взаимодействуют с терминалом, выполняющим команду или программу, что позволяет одним программам использовать результаты выполнения других как входные данные. Вывод результатов работы одной команды может быть легко передан на вход другой с помощью переназначения. Чуть позже мы поподробнее остановимся на переназначении ввода/вывода. А более подробно разберемся с механизмом в работы каналов в главе 13. Получение оперативной помощи: man При работе часто случается, что вы не знаете, что делает или как называется та или иная утилита, или, возможно, не помните ее синтаксиса. Для выхода из такой ситуации во всех версиях UNIX предусмотрен встроенный справочник, вызываемый по команде man (сокращение от англ, manual — справочник). Синтаксис man [ [—s] раздел] слово man -к ключевое слово На страницах этого справочника собрана вся документация по UNIX, предназначенная для работы в диалоговом режиме и разделенная на восемь секций. В справочнике содержится информация об утилитах, системных вызовах, форматах файлов и командных интерпретаторах (shell). При выводе справки о некоторой конкретной утилите указывается также, к какой секции она принадлежит. При первом обращении man выводит содержимое раздела справочника, связанного с интересующей вас командой. Некоторые версии UNIX используют аргумент -s, чтобы указать номер секции. Если номер секцищ не указан, будет показана первая секция, которую найдет man. Второе использование man выводит список всех статей справочника, которые содержат ключевое_слово (keyword).
Справочник содержит следующие разделы. 1. Команды и прикладные программы. 2. Системные вызовы. 3. Библиотечные функции. 4. Специальные файлы. 5. Файловые форматы. 6. Игры. 7. Разное. 8. Утилиты системного администрирования. Иногда ключевое слово входит в несколько различных секций справочника. Например, есть утилита chmod, описание которой находится в секции 1, и системный вызов chmod (), информация о котором находится в секции 2. По умолчанию man выдает первую статью, в которой встречается искомое слово. В случае повторного поиска программа выдаст список всех статей, в которых искомое слово встречается. Ниже приведен пример работы утилиты man. $ man -к mode chmod (IV) chmod, fchmod (2V) getty (8) ieeeflags (3M) umask (2V) $ man chmod ...поиск ключевого слова "mode". - change the permissions mode of a file - change mode of file - set terminal mode - mode and status function - set file creation mode mask ...выбор первой статьи справочника. CHMOD(IV) USER COMMANDSCHMOD (IV) NAME chmod — change the permissions mode of a file SYNOPSIS chmod C -fR V mode filename ... ...the description of chmod goes here. SEE ALSO csh(l), Is(IV), sh(l), chmod (2V), chown(8) $ man 2 chmod ...выбор статьи справочника из секции 2. CHMOD(2V) SYSTEM CALLS CHMOD(2V) NAME chmod, fchmod — change mode of file SYNOPSIS #include <sys/stat.h> int chmod (path, mode)
char *path; mode_t mode; ...the description of chmod () goes here. SEE ALSO chown(2V), open(2V), stat(2V), sticky(8) $ Специальные символы Некоторые символы интерпретируются специальным образом, вводятся с UNIX-терминала. Такие знаки называются метасимволами и могут быть выведены утилитой stty с опцией -а (от англ. all). Утилита stty более подробно рассматривается в конце этой главы. Пример: $ stty -а ...получение списка метасимволов терминала speed 38400 baud; -parity hupcl rows = 24; columns = 80; ypixels = 0; xpixels = 0; -inpck -istrip ixoff imaxbel crt tostop iexten erase kill werase rprnt flush Inext susp intr quit stop eof Лн Ли Aw ло AV Лг/Лу Лс Л\ Лз/Ло $ _ Знак Л перед каждым символом означает, что клавиша <Ctrl> должна быть нажата одновременно с символом. В табл. 2.1 перечислены значения по умолчанию. Описание символов этого списка ожидает вас в других главах данной книги, однако о некоторых из них мы поговорим прямо сейчас. Таблица 2.1. Опции утилиты stty Опция Описание erase Сдвиг назад на один символ kill Стирание всей текущей строки werase Стирание последнего слова rprnt Повторение строки flush Игнорирование любого незаконченного ввода и повторный вывод строки Inext Отказаться от рассмотрения следующего символа, как специального susp Приостановка процесса для будущего пробуждения
Таблица 2.1 (окончание) Опция Описание Intr Завершение (прерывание) фоновой работы без дампа ядра операционной системы quit Завершение фоновой работы и генерация дампа ядра операционной системы stop Остановка/возобновление вывода на терминал Eof Конец ввода Завершение процесса: <Ctrl>+<C> Иногда возникает необходимость завершить программу до того, как она выполнится. Сделать это можно, нажав комбинацию клавиш <Ctrl>+<C>. Большинство программ сразу же реагируют, выводя приглашение shell. Но есть программы, которые невозможно завершить таким способом. Давайте посмотрим на примере, что происходит при нажатии этих клавиш: $ man chmod CHMOD(IV) USER COMMANDS CHMOD(IV) NAME chmod — change the permissions mode of a file SYNOPSIS ЛС ...прерывание работы и возвращение в shell. $_ Приостановка вывода: <Ctrl>+<S>/<Ctrl>+<Q> Иногда, в результате выполнения команды выводится слишком много данных, которые не помещаются на экране, в таких случаях можно приостановить вывод, нажав комбинацию клавиш <Ctrl>+<S>. Для возобновления вывода необходимо вновь воспользоваться этой комбинацией (<Ctrl>+<S>) или нажать комбинацию клавиш <Ctrl>+<Q>. Такая последовательность управляющих символов известна также, как протокол XON/XOFF. Пример: $ man chmod CHMOD(IV) USER COMMANDS CHMOD(IV) NAME chmod — change the permissions mode of a file AS ...приостанавливает вывод на терминал. ...возобновляет вывод.
SYNOPSIS chmod С -fR V mode filename ... ...the rest of the manual page is displayed here. SEE ALSO csh(l), Is(IV), sh(l), chmod (2V) , chown(8) $ _ Завершение ввода: <Ctrl>+<D> Многие утилиты UNIX получают входную информацию из файла или с клавиатуры. Если данные вводятся с клавиатуры, вам необходимо сообщить системе о завершении ввода. Для этого нажмите комбинацию клавиш <Ctrl>+<D> после того, как наберете последнюю строчку. Система воспринимает эту комбинацию как завершение ввода информации. Рассмотрим это на примере утилиты mail, позволяющей посылать почту конкретному пользователю: $ mail tim . . .посылка почты моему другу Тиму. Hi Tim, ...ввод информации с клавиатуры. I hope you get this piece of mail. How about building a country one of these days? - with best wishes from Graham ...говорит системе, что ввод окончен. $_ Более подробно утилита mail будет рассмотрена в конце этой главы. Установка пароля: passwd После того как вы зарегистрировались в системе, целесообразно будет сменить свой первоначальный пароль, т. к. по крайней мере, он известен человеку, который выдал вам его. Отнеситесь к выбору пароля серьезно. Он должен состоять не менее, чем из шести символов, и не являться правильным существительным или каким-либо другим словом из словаря. Это необходимо для того, чтобы злоумышленник, решивший воспользоваться вашим паролем, не смог получить его, установив специальную программу, которая попробует подобрать пароль, перебирая слова стандартного словаря. В качестве примера хорошего пароля можно привести: "GWK145W". Для изменения существующего пароля воспользуйтесь утилитой passwd.
Синтаксис passwd Утилита passwd позволяет изменять пароль. Для этого вам потребуется ввести старый пароль, после чего дважды ввести новый. Это необходимо потому, что вводимый пароль не отображается на экране, и проверить, не допустили ли вы опечатку, можно только подтверждением ввода и проверкой их на совпадение. После того как вы поменяли пароль, он в зашифрованном виде сохраняется в зависимости от версии UNIX в файле пароля /etc/passwd или в shadow-файле (для большей безопасности). В некоторых версиях UNIX пароль может сохраняться в базе данных на удаленном компьютере. Для большей наглядности в приведенном далее примере отображаются пароли, но обычно при работе с UNIX символы, которые вы вводите, на экране не отображаются. $ passwd Current password: penguin New password (? For help): GWK145W New password (again): GWK145W Password changed for glass $_ Если вы забыли пароль, единственное, что можно сделать, — связаться с системным администратором и попросить его выдать вам новый. Выход из системы Для выхода из системы воспользуйтесь комбинацией клавиш <Ctrl>+<D>. Подавляющее большинство систем после этого выводит приглашение login:, после чего к работе может приступить другой пользователь. Пример: $ ... я все сделал! UNIX(г) System V Release 4.0 login: ...ожидает другого пользователя для регистрации. Теперь вы знаете, как зарегистрироваться в системе, изменить пароль и выйти из системы. В следующих разделах мы рассмотрим утилиты, которые позволят вам перемещаться по каталогам и управлять файлами. 3 Зак. 786
Поэзия в движении: изучение файловой системы Вероятно, лучший способ познакомиться с общими утилитами UNIX — это рассмотреть их на примере. Представим себе поэта, которому необходимо написать заключительную версию стихотворения. В табл. 2.2 приведена последовательность действий, которую ему для этого необходимо совершить. Таблица 2.2. Пример последовательности действий для написания стихотворения Действие Утилита Определение текущего местонахождения pwd Сохранение наброска в файле heart cat Вывод содержимого каталога для определения размера файла Is Просмотр файла с использованием нескольких утилит cat, more, page, head, tail Переименование файла heart в файл heart.verl mv Создание каталога lyrics для сохранения в нем вариантов mkdir стихотворений Перемещение файла heart.verl в каталог lyrics mv Создание копии файла heart.verl с именем heart.ver2 ср Редактирование файла heart.ver2 vi Возвращение в домашний каталог cd Создание каталога lyrics.final mkdir Переименование каталога lyrics в lyrics.draft mv Копирование файла heart.ver5 из каталога lyrics.draft ср в каталог lyrics.final, переименование файла в heart, final Удаление файлов из каталога lyrics.draft rm Удаление каталога lyrics.draft rmdir Перемещение в каталог lyrics.final cd Распечатка файла heart.final 1рг Подсчет слов в файле heart.final wc Определение атрибутов файла heart.final Is Определение типа файла heart.final file
Таблица 2.2 (окончание) Действие Утилита Определение группы владельцев groups Изменение группы владельцев файла heart.final chgrp Изменение прав на файл heart.final chmod Определение текущего каталога: pwd Работа в UNIX начинается с определенного места в системе, называемого текущим рабочим каталогом. Когда происходит регистрация пользователя в системе UNIX, shell начинает работать в индивидуальном каталоге — домашнем каталоге. Вообще, каждый пользователь имеет свой домашний каталог, который обычно начинается с префикса /home. Например, каталог пользователя glass может располагаться в каталоге /home/glass. Системный администратор указывает местонахождение каждого пользователя. Для определения местонахождения пользователя применяется утилита pwd. Синтаксис pwd Печатает имя текущего рабочего каталога. Для иллюстрации утилиты рассмотрим, что произошло при регистрации в UNIX пользователя, который хочет начать работу над лирическими стихами: UNIX(г) System V Release 4.0 login: glass Password: ...секрет. $ pwd /home/glass $ _ На рис. 2.1 изображена схема нахождения пользователя glass во время регистрации в системе.
home glass Положение Korn shell во время входа в систему Рис. , 2.1. Запуск командного интерпретатора из домашнего каталога пользователя Абсолютные и относительные имена путей Перед тем как мы продолжим знакомство с системой каталогов, нам необходимо обратить внимание на уникальность названий файлов и каталогов. Два файла, расположенные в одном и том же каталоге, не могут называться одинаково. Однако никто не запрещает вам давать одни и те же имена файлам, находящимся в разных каталогах. На рис. 2.2 представлена иерархия каталога, содержащего процесс ksh и три файла с одинаковым именем my File. home myFile myFile , Г" glass myFile Рис. 2.2. Различные файлы с одинаковыми именами Несмотря на то, что файлы имеют одинаковые имена, они идентифицируются по месту расположения относительно каталога / — корня всей иерархии каталогов. Путь — последовательность имен каталогов, от стартового каталога до необходимого файла. Путь, определяемый относительно корневого каталога, часто называют абсолютным или полным. В табл. 2.3 представлено местоположение трех файлов с одинаковыми именами.
Таблица 2.3. Абсолютные пути Файл Абсолютное путевое имя А /home/glass/myFile В /home/myFile С /bin/myFile Не менее успешно можно определить файл, указывая его расположение относительно текущего рабочего каталога. При задании относительного пути в UNIX принято использовать символы, представленные в табл. 2.4. Табл. 2.5 иллюстрирует расположение файлов myFile относительно домашнего каталога пользователя glass. Внимание Указание пути myFile эквивалентно ./myFile, однако эта форма является избыточной и поэтому используется редко. Таблица 2.4. Условные обозначения текущего и родительского каталога Поле Описание Текущий каталог • • Родительский каталог Таблица 2.5. Относительные пути Файл Относительный путь А myFile В ../myFile С ../../bin/myFile Создание файла Для того чтобы создать файл в UNIX, как правило, пользуются специальными редакторами, например, vi или emacs, но в этой главе мы рассмотрим более простой, но не менее эффективный способ — утилиту cat.
Синтаксис cat -п {имяФайла} * Название утилиты — cat (от сокращения англ, concatenate — связывать) — переводится, как соединять в последовательность связей. Она читает входные данные из файла или стандартного входа и передает их на стандартный выход. Опция -п позволяет указать номера необходимых строк. По умолчанию стандартный вход процесса — это ввод данных с клавиатуры, стандартный выход — вывод данных на экран. Мы можем перенаправить стандартный выход в файл, используя переназначение вывода. Если после команды вы укажете символ > и имя файла, результат работы этой программы будет сохранен в файле с указанным именем. Если такого файла не существует, он создается, в противном случае, вся информация, содержащаяся в нем до этого, перезаписывается. В главе 4 мы рассмотрим этот вопрос более подробно. Ниже приведен пример работы утилиты cat: $ cat > heart , ...сохраняет ввод с клавиатуры в файл heart. I hear her breathing, I’m surrounded by the sound. Floating in this secret place, I never shall be found. ...говорит cat, что конец ввода был достигнут. $ Просмотр содержимого каталога: Is После создания файла вам, вероятно, захочется убедиться, что он действительно существует, и узнать его размер. Для получения этой информации служит утилита is. Синтаксис Is -adglsFR {имяФайла}* {имяКаталога}★ По умолчанию утилита is выводит в алфавитном порядке список всех файлов текущего каталога за исключением скрытых файлов, имена которых начинаются с точки. Опция -а включает в листинг скрытые файлы.
Для вывода содержимого каталогов необходимо указать их имена после параметров команды. Опция -d включает в листинг описание каталогов, а не их содержание. Опция -д вносит в список группу файла. Опция -1 выводит список файлов с такими параметрами, как время последней модификации, имя владельца файла и флаги доступа. Опция -s выводит размер файла. Опция f указывает тип файла: • * — исполняемый файл; • / — каталог; • @ — связанный файл; • = — сокет. Опция -r рекурсивно выводит содержимое каталога и его подкаталогов. Большинство описанных выше опций утилиты is вам пока не потребуются. Пример выполнения утилиты is: $ Is ...распечатывает все файлы в текущем каталоге, heart $ Is -1 heart ...большая распечатка файла heart. -rw-r—г— 1 glass 106 Jan 30 19:46 heart $_ Немного позже мы рассмотрим значение каждого поля, выводимого утилитой is, а пока в табл. 2.6 представлен их краткий обзор. Таблица 2.6. Описание вывода утилиты 1s Номер поля Символы Значение 1 -rw -г —г-- Тип и режим полномочий файла, которые указывают, кто может читать, писать и выполнять файл 2 1 Счетчик жестких связей (обсуждаемый намного позже в этой книге) 3 glass Имя пользователя — владельца файла 4 106 Размер файла в байтах 5 Jan 30 19:46 Время последней модификации файла 6 heart Имя файла $ Is -algFs total 3 ...полный листинг текущего каталога. ...общее число блоков памяти.
1 drwxr-xr-x 3 glass cs 512 Jan 30 22:52 , / 1 drwxr-xr-x 12 root cs 1024 Jan 30 19:45 • . / 1 —rw-r—r— 1 glass cs 106 Jan 30 19:46 heart $ Опция -s выводит размер каждого файла. Если размер блока диска равен 1024 байта, то файл размером в 106 байт займет его целиком. Это следствие физического устройства файловой системы, которое мы рассмотрим в главе 14. Опция -а выводит полный листинг каталога, включая скрытые файлы, имена которых начинаются с точки. Например, "." и — скрытые файлы, которые представляют собой текущий и родительский каталоги соответственно. Опция -F добавляет в конец символ / ко всем именам, которые являются каталогами, а опция -д выводит группу, владеющую файлом. Распечатка файла: cat/тоге/раде/head/tail Чтобы просмотреть содержимое файла, можно воспользоваться утилитой cat. Для этого после команды необходимо указать имя файла: $ cat heart ...выводит содержимое файла heart. I hear her breathing, I’m surrounded by the sound. Floating in this secret place, I never shall be found. $ _ Утилита cat принимает в качестве аргументов любое количество файлов, имена которых указываются друг за другом. Она отлично подходит для вывода небольших файлов. Для вывода информации большого объема следует пользоваться утилитами more и раде, которые позволяют приостановить вывод и прокручивать файл. Ниже представлено описание этих утилит. Синтаксис more -f [+номерС троки] {имяФайла}* Утилита more организует постраничный вывод на экран. Вывод начинается с первой строки каждого файла, но с помощью опции + можно указать стартовую строку. Опция -f предписывает не переносить длинные строки. После заполнения страницы появляется сообщение "—More—". Для распечатки следующей страницы вам нужно всего лишь нажать клавишу <Пробел>. Для
выхода используйте клавишу <q>. Для получения более подробной информации нажмите клавишу <h>. Синтаксис page -f [ +номерСтроки] {имяФайла}* Утилита раде работает подобно утилите more, за исключением того, что первая очищает экран перед выводом каждой страницы. В некоторых случаях это помогает ускорить вывод. Следует упомянуть еще о двух полезных утилитах: head и tail, с помощью которых можно просмотреть начало и конец файла соответственно. Синтаксис head -п {имяФайла}* Утилита head показывает первые п строк файла. Если п не определено, по умолчанию выводятся 10 строк. Если необходимо вывести начальные строки нескольких файлов, перед каждым из них будет указано его имя. Синтаксис tail -п {имяФайла}* Утилита tail показывает последние п строк файла. Если п не определено, по умолчанию выводятся последние 10 строк. Выводу содержимого нескольких файлов предшествуют их имена. Пример: S head -2 heart ...вывод первых двух строк I hear her breathing I’m surrounded by the sound. $ tail -2 heart ...вывод последних двух строк Floating in this secret place I never shall be found.
Переименование файла: mv Очень часто в процессе работы возникает необходимость переименовать файл. Для этого используется утилита mv. Синтаксис mv -i староеИмяФайла новоеИмяФайла mv -i {имяФайла}* имяКаталога mv -i староеИмяКаталога новоеИмяКаталога Утилита mv перемещает или переименовывает файлы и каталоги. Для того чтобы переименовать файл или каталог, необходимо указать в качестве параметров старое и новое имя файла/каталога. Для перемещения файлов в каталог потребуется указать имена файлов и каталога после команды. Вследствие того, что физического перемещения не происходит, а меняются только метки, утилита mv работает очень быстро. Опция -i выводит предупреждение, что файл с указанным именем уже существует. Пример: $ mv heart heart.ver1 ...переименование в heart.verl $ Is heart.verl $ _ Пример перемещения каталогов и файлов в каталоги будет приведен чуть позже. Создание каталога: mkdir Для создания каталогов пользуются утилитой mkdir. Синтаксис mkdir [~р] новоеИмяКаталога Утилита mkdir создает каталог. Опция -р создает любые родительские каталоги в каталоге новоеИмяКаталога, который еще не существует. Если новоеИмяКаталога уже существует, выводится сообщение об ошибке, и существующий файл никаким образом не изменяется.
Пример: $ mkdir lyrics ...создает каталог lyrics. $ Is -IF ...подтвердить. -rw-r—г— 1 glass 106 Jan 30 23:28 heart.verl drwxr-xr-x 2 glass 512 Jan 30 19:49 lyrics/ $ _ Символ d в начале поля полномочий говорит о том, что это каталог. Для облегчения поиска необходимо, чтобы имя каталога отвечало его содержанию. Чтобы убедиться, удалось ли переместить файл в нужное место, также воспользуемся утилитой is: $ mv heart.verl lyrics ...переместить в lyrics. $ Is ...вывод текущего каталога, lyrics/ ...heart.verl ушел. $ Is lyrics ...вывод каталога lyrics, heart.verl ...heart.verl перемещен. $ Перемещение по каталогам: cd Можно оставаться в своем рабочем каталоге, создавая по мере необходимости подкаталоги внутри него. Но это неудобно, по крайней мере, по нескольким причинам: во-первых, это затруднит поиск, а во-вторых, придется указывать очень длинные пути, содержащие имена всех каталогов, предшествующих каталогу, в котором находится необходимый файл. Чтобы сделать работу более удобной, лучше заранее переместиться в каталог, с содержимым которого вы собираетесь работать. Например, если вы собираетесь редактировать файл heart.verl, то целесообразно переместиться из своего стартового каталога в каталог lyrics, в котором этот файл находится. $ vi lyrics/heart.verl ...запуск'редактора vi. Для перемещения по каталогам служит команда cd, которая фактически представляет собой встроенную команду shell. Внимание Название всех команд shell дается светлым моноширинным шрифтом, согласно системе обозначений, описанных во Введении.
Синтаксис cd [имяКаталога] Команда cd (от англ, change directory — изменить каталог) изменяет текущий рабочий каталог на указанный в имяКаталога. Если аргумент имяКаталога опущен, shell перемещается в домашний каталог его владельца Проверить, произошло ли перемещение в необходимый каталог можно, используя утилиту pwd: $ pwd ...определение текущего каталога. /home/glass $ cd lyrics ...переместить в .каталог lyrics. $ pwd ...вывести, где я теперь. /home/glass/lyrics На рис. 2.3 проиллюстрировано перемещение shell, инициированное командой cd: home lyrics glass shell перемещается в каталог /home/glass/lyrics Рис. 2.3. Перемещение shell Возможно перемещение на уровень выше или в рабочий каталог с помощью указания ".и соответственно. Пример: /home/glass/lyrics $ cd . . ...переместиться вверх на один уровень. $ pwd /home/glass $ ...вывести новое текущее положение.
Копирование файла: ср Часто возникает необходимость скопировать файл, например, для того, чтобы отредактировать его, не испортив оригинал. Чуть ниже описана утилита ср, предназначенная для копирования файлов. Синтаксис ср -i староеИмяФайла новоеИмяФайла ср -ir {имяФайла}* имяКаталога Утилита ср копирует файлы, при этом необходимо указать староеИмяФайла и новоеИмяФайла. Если файл с таким именем уже существует, его содержимое перезаписывается. Опция -i выводит предупреждение о том, что файл с таким именем уже существует. Опция -г рекурсивно копирует файлы и дочерние каталоги в указанный каталог. Утилита ср создает физическую копию содержимого каталога и новую метку, указывающую на скопированные файлы. Поэтому любая работа с копией документа никак не затронет оригинал. Ниже приведен пример работы этой утилиты: $ ср heart.verl heart.ver2 ...копировать в файл heart.ver2. $ Is -1 heart.verl heart.ver2 ...подтверждение. -rw-r—r— 1 glass -rw-r—r— 1 glass 106 Jan 30 23:28 heart.verl 106 Jan 31 00:12 heart.ver2 $ Редактирование файла: vi В этой главе мы познакомимся с двумя программами обработки текстов: редакторами vi и emacs. Представим себе, что нам необходимо отредактировать файл в vi. Ниже показано, какие действия необходимо совершить для этого: $ vi heart.ver2 ...редактировать файл. ...сессия редактирования приводится здесь. $ cat heart.ver2 ...вывести файл. I hear her breathing, I’m surrounded by the sound. Floating in this secret place,
I never shall be found. She pushed me into the daylight, I had to leave my home. But I am a survivor, And I’ll make it on my own. $ После создания пяти версий лирических стихов песни моя работа была завершена. Я вернулся к своему домашнему каталогу и создал подкаталог lyrics.final, чтобы хранить заключительную версию лирических стихов. Я также переименовал первоначальный каталог lyrics в lyrics.draft. Команды, реализующие описанный алгоритм, таковы: $ cd $ mkdir lyrics.final $ mv lyrics lyrics.draft ...вернуться в свой домашний каталог. ...создать финальный каталог lyrics. ...переименовать старый каталог lyrics. $ Заключительная версия лирических стихов была сохранена в файле heart.ver5 каталога lyrics.draft, который я затем скопировал в файл heart.final в каталоге lyrics.final: $ ср lyrics.draft/heart.ver5 lyrics.final/heart.final $ Удаление каталога: rmdir Сохранение информации для потомков — цель весьма и весьма достойная, однако не реализуемая в условиях ограниченного дискового пространства. Поэтому время от времени необходимо удалять ненужные файлы и каталоги. Однако перед тем как подписать смертный приговор какому-либо файлу, подумайте, не потребуется ли он (т. е. файл) вам в будущем. Если возникают сомнения по этому поводу, то перед удалением лучше заархивировать файл при помощи утилиты cpio, о которой мы подробнее поговорим в главе 3. Теперь, когда информация сохранена, можно удалить каталог, пользуясь утилитой rmdir. Синтаксис rmdir {имяКаталога}+ Утилита rmdir удаляет указанный каталог и все его подкаталоги. Удалить можно только пустые каталоги. Для того чтобы рекурсивно удалить ката
логи и подкаталоги вместе со всем их содержимым, следует воспользоваться утилитой rm с опцией -г. При попытке удалить каталог, содержащий файлы, система выдаст сообщение об ошибке: $ rmdi г lyri cs. dr a f t nadir: lyrics.draft: $ Directory not empty1 Удаление файла: rm Утилита rm удаляет метки, ссылающиеся на файл. Как правило, этого достаточно, чтобы удалить и сам файл. Однако, если файл имеет более чем одну метку — такие случаи мы рассмотрим в главе 3 — данная утилита удаляет лишь ссылку на файл. Синтаксис rm -fir {имяФайла} * Утилита rm удаляет ссылку на файл из иерархии каталогов. Если файла не существует, выводится сообщение об ошибке. Опция -i выводит предупреждение перед удалением файла; требуется нажать клавишу <у> для подтверждения операции или клавишу <п> для отмены. Использование этой утилиты с опцией -г для удаления каталога вызывает удаление всего его содержимого, включая подкаталоги. Опция -f подавляет вывод сообщений об ошибках и предупреждений. Удаление файлов из каталога: $ cd lyrics.draft ...переместиться в каталог lyrics.draft. $ rm heart.verl heart.ver2 heart.ver3 heart.verA heart.ver5 $ Is ... ничего не осталось. $ _ Удаление пустого каталога: $ cd ... возвращаемся в домашний каталог. $ rmdir lyrics.draft ...каталог пуст, и теперь мы можем его удалить. $ 1 Каталог не пуст. — Пер.
В главе 4 мы обсудим более простой способ удаления файлов: $ cd lyrics.draft ...переместиться в каталог lyrics.draft. $ rm * ...удалить все файлы из текущего каталога. $ Можно воспользоваться лучшим способом: указать более "продвинутую" опцию -г утилиты rm, чтобы удалить каталог lyrics.draft и все его содержимое одной командой: $ cd $ rm -г lyrics.draft $ ...переместиться в домашний каталог. ...рекурсивно удалить каталог. Печать файла: 1р/Ipstat/cancel Мы выяснили, как создавать, редактировать и удалять файлы. Теперь было бы неплохо научиться их распечатывать. Для вывода документа на печать используется утилита 1р. Синтаксис ip [-d пунктНазначения] [-п копий] {имяФайла}* Утилита 1р отправляет выбранные файлы на принтер, который необходимо указать с помощью опции -d. Если не заданы имена файлов, на печать выводятся данные, поступающие со стандартного входа. По умолчанию печатается одна копия. Однако количество необходимых экземпляров можно изменить, пользуясь опцией -п. Говоря о печати файлов, нельзя обойти вниманием еще одну полезную утилиту — ipstat — отображающую состояние печати и загрузку принтера. Синтаксис Ipstat [пунктНазначения] Утилита ipstat выводит информацию о статусе принтера, объеме выполненной работы, пользователе, отправившем задание на печать с помощью команды 1р. Если принтер определен, ipstat сообщает информацию об очереди только для этого принтера.
Если по каким-либо причинам вам нужно отменить печать документа, вы можете сделать это, воспользовавшись утилитой cancel. Синтаксис cancel {ID—запроса} + Утилита cancel удаляет все указанные задания из очереди принтера. В качестве параметра команды необходимо указать ID задания, который можно узнать, воспользовавшись утилитой ipstat. Привилегированный пользователь (root) может отменить печать любого документа. Представим, что нам поручили распечатать два экземпляра файла heart.final на принтере Iwcs. После отправки задания на печать оказалось, что вполне достаточно будет одного экземпляра. Ниже приведена последовательность действий, которые необходимо совершить, чтобы реализовать задуманное: $ Ip -d Iwcs heart.final ...отправили на печать, request id is lwcs-37 (1 file) $ Ipstat Iwcs ...узнали статус принтера, printer queue for Iwcs lwcs-36 ables priority 0 Mar 18 17:02 on Iwcs inventory.txt 457 bytes lwcs-37 glass priority 0 Mar 18 17:04 on Iwcs heart.final 213 bytes $ Ip -n 2 -d Iwcs heart.final ...заказали еще две копии, request id is lwcs-38 (1 file) $ Ipstat Iwcs ...еще раз посмотрели статус принтера. printer queue for Iwcs lwcs-37 glass priority 0 Mar 18 17:04 on Iwcs heart.final 213 bytes lwcs-38 glass priority 0 Mar 18 17:05 on Iwcs heart.final 2 copies 213 bytes $ cancel lwcs-38 ...удалили последнее задание. request "lwcs-38" cancelled $ В приведенном ниже примере проиллюстрирован вывод на печать данных, введенных с клавиатуры: $ Ip -d Iwcs ...печать со стандартного входа. Hi there,
This is a test of the print facility. - Graham ... конец ввода. request id is lwcs-42 (standard input) $ Печать файла: Ipr/lpq/lprm Утилиты печати, с которыми мы уже познакомились, уходят корнями к System V и поддерживаются практически во всех версиях UNIX. Однако в некоторых версиях и Linux используются другие команды, берущие свое начало в BSD UNIX. Эти команды выполняют те же самые функции, но имеют другие названия и аргументы. Так, например, для печати файла применяется утилита ipr. Для определения статуса принтера и мониторинга выполнения задания служит утилита ipq. Остановимся на них более подробно. Синтаксис 1рг -ш [-Рпринтер] [-#копий] {имяФайла} * Утилита ipr выводит на печать указанные файлы, принтер задается опцией -р. Если принтер не определен, используется значение переменной окружения $ printer (об этих переменных мы поговорим в главе 4). Если не указано имя файла, печатаются данные со стандартного входа. По умолчанию печатается одна копия каждого файла, хотя необходимое количество экземпляров задается опцией -#. С помощью опции -т можно настроить отправление уведомления о завершении печати. Синтаксис Ipq -1 [-Рпринтер] {номерЗа Дания}* {Ю_пользователя}* Утилита ipq показывает информацию о текущих заданиях и пользователях, отправивших их на печать. Если не определено конкретное задание, будут показаны все задания на указанном с помощью опции -р принтере. В противном случае выводятся данные работы принтера, прописанного в переменной окружения $ printer. Опция -1 позволяет получить более подробную информацию.
Отменить печать можно с помощью утилиты iprm. Синтаксис Iprm [-Рпринтер] [-] [номерЗа дания] * {Ю_пользователя} * Утилита iprm отменяет задания на принтере, указанном опцией -р, либо (если принтер не определен) прописанном в переменной окружения $ printer. Опция - отменяет все назначенные вами задания. Привилегированный пользователь root может прекратить печать заданий конкретного пользователя, идентифицируя его по ю_пользователя. Пример: $ Ipr -Plwcs heart.final ...вывод на печать. $ Ipq -Plwcs glass ...просмотр статуса принтера. Iwcs is ready and printing Rank Owner Job Files Total Size active glass 731 heart.final 213 bytes $ Ipr -#2 -Plwcs heart. final ...еще две 2 копии. $ Ipq -Iwcs is Plwcs glass ready and printing ...статус принтера снова. Rank Owner Job Files Total Size active glass 731 heart.final 213 bytes active glass 732 heart.final 426 bytes $ Iprm -Plwcs 732 ...удаление последнего задания. centaur: dfA732vanguard dequeued centaur: cfA732vanguard.utdallas.edu dequeued $ _ Пример вывода на печать данных, введенных непосредственно с клавиатуры. $ Ipr -m -Plwcs ...печать со стандартного входа. Hi there, This is a test of the print facility. - Graham ...конец ввода. $ . подождал немного. $ mail ...читать почту.
Mail version SMI 4.0 Sat Oct 13 20:32:29 PDT 1990 Type ? for help. >N 1 daemon@utdallas.edu Fri Jan 31 16:59 15/502 printer job & 1 ...читать первое почтовое сообщение. From: daemon@utdallas.edu То: glass@utdallas.edu Subject: printer job Date: Wed, 18 Mar 1998 18:04:32 -0600 Your printer job (stdin) Completed successfully & q ...завершил почтовый сеанс. $ Подсчет слов в файле: wc Иногда бывает весьма полезно узнать, сколько слов содержит тот или иной файл. Сделать это гораздо проще, чем можно предположить: достаточно воспользоваться утилитой wc. Синтаксис wc -Iwc {имяФайла}* Утилита wc считает строки, слова или символы в одном или нескольких файлах, либо в данных, введенных с клавиатуры Опция -1 осуществляет подсчет строк, опция -w — слов, опция -с — символов. Если никакие опции не определены, то выполняются все три подсчета. Слово определено как последовательность символов, окруженная символами табуляции, пробелами или новыми строками. Рассмотрим пример: $ wc heart.final ...выполнить подсчет слов. 9 43 213 heart.final Атрибуты файла Теперь, когда рассмотрены основные утилиты работы с файлом, пришло время обратить внимание на его атрибуты.
Для получения подробной информации о файле используется рассмотренная уже утилита is: $ Is -IgsF heart. final 1 -rw-r—r— 1 glass cs 213 Jan 31 00:12 heart.final $ _ В табл. 2.7 расшифровано значение каждого поля. Таблица 2.7. Атрибуты файла Номер поля Символы Описание 1 1 Число блоков физической памяти, занятой файлом ' 2 -rw-r—г— Тип и режим полномочий файла, который указывает, кто может читать, записывать и выполнять файл 3 1 Счетчик жестких связей (обсуждаемый в главе 3) 4 glass Имя владельца файла 5 cs Группа, владеющая файлом 6 213 Размер файла в байтах 7 Jan 31 00:12 Время последней модификации 8 heart.final Имя файла В следующих разделах мы поподробнее остановимся на значениях каждого поля. Размер файла Первое поле атрибутов файла указывает количество занимаемых файлом блоков. Фрагментированные файлы, имеющие большой объем в байтах, могут занимать совсем немного физической памяти. Фрагментированные файлы обсуждаются подробно в главе 13. Имя файла Имя файла отображается в восьмом поле. Имена файлов в UNIX могут содержать до 255 символов. При этом можно использовать любые печатные символы, кроме косой черты (/). Однако не рекомендуется также использовать знаки, являющиеся специальными для shell, например, <,>,*, ?, табуляции, т. к. они могут неправильно трактоваться как пользователем, так и системой. Нельзя также называть файлы и поскольку эти имена
зарезервированы для обозначения родительского и текущего каталогов. В отличие от некоторых операционных систем, не требуется, чтобы файл заканчивался расширением типа .с и .h, хотя многие утилиты UNIX (например, С-компилятор) работают с файлами, которые заканчиваются определенным суффиксом. Поэтому, heart и heart.fmal вполне допустимые имена. Время модификации Седьмое поле показывает время последней модификации файла. Например, утилита make, описанная в главе 12, использует время последней модификации файлов, чтобы управлять средством контроля зависимости. Утилита find, представленная в главе 3, использует это поле для поиска наиболее поздних версий файлов. Владелец файла Четвертое поле сообщает нам имя владельца файла. Каждый процесс UNIX имеет владельца, имя которого совпадает с именем пользователя, его запустившего. Например, мой shell запущен от имени пользователя glass, поэтому каждый создаваемый файл принадлежит данному пользователю, владельцу shell. В главе 13 мы более подробно рассмотрим процессы и их владельцев. Имя пользователя ассоциируется с тем или иным набором символов, хотя сама система использует для идентификации число, называемое пользовательским ID. Однако человеку проще запомнить какое-либо имя, нежели число, поэтому, когда мы будем говорить об имени пользователя, то станем иметь в виду какую-либо буквенную комбинацию и применять термин "пользовательский ID" для указания числового значения. Группа владельцев файла Пятое поле информирует нас о группе, владеющей файлом. Каждый пользователь UNIX является членом какой-либо группы. Принадлежность к группе назначается системным администратором и является инструментом, позволяющим повысить безопасность системы. Каждый процесс UNIX также принадлежит определенной группе, обычно эквивалентной группе пользователя, запустившего процесс. Если пользователь входит в группу cs, то все создаваемые им файлы также принадлежат этой группе. В главе 13 этот вопрос будет рассмотрен подробнее. Использование групп относится к механизму безопасности UNIX, которому посвящены следующие разделы. Как и в ситуации с именем пользователя, имя группы задается набором буквенных символов. Однако системой используется числовой идентификатор — ID группы.
Типы файла Во втором поле указывается тип файла и полномочия. Для удобства еще раз приведем еже знакомую нам строчку листинга: 1 -rw-r—г— 1 glass cs 213 Jan 31 00:12 heart.final Первый символ второго поля указывает на тип файла. В табл. 2.8 даны описание и обозначение различных типов. Мы с вами познакомились только с обычными файлами. В главе 3 будут рассмотрены файлы с символическими связями, в главе 13 — каналы и сокеты, а в главе 14 — буферированные и небуферированные файлы. Таблица 2.8. Типы файла Символ Тип файла Обычный файл d Каталог Ь Буферированный специальный файл (например, драйвер диска) с Небуферированный специальный файл (например, терминал) 1 Символическая связь р Канал s Сокет Тип файла определяется утилитой file, которую мы сейчас и рассмотрим. Чаще всего эта утилита будет выдавать такой результат: $ file heart.final ...определить тип файла. heart.final: ascii text $ Синтаксис file {имяФайла} + Утилита file определяет тип файла по его содержимому2. При использовании для файла символической связи утилита выдает характеристики того файла, на который эта связь указывает. 2 Эта программа весьма полезна, однако ее использование не гарантирует 100% верного результата.
Параметры полномочий файла Следующие 9 символов второго поля указывают назначения параметров полномочии файла. В текущем примере назначения параметров полномочий — это rw-r--г--: 1 -rw-r—г— 1 glass cs 213 Jan 31 00:12 heart.final Итак, мы разобрались со значением первого символа, теперь пришло время рассмотреть следующие девять, представляющие собой три группы по три символа (табл. 2.9 и 2.10). Прочерк на месте какого-либо символа говорит о юм, что этот вид работы с файлом запрещен. Разрешение на чтение, запись и выполнение файла очень часто зависят от типа файла (табл. 2.11). Таблица 2.9. Гоуппы параметров полномочий файла Группа параметров Описание rw- Пользователь (владелец) г-- Группа г-- Другие Таблица 2.10. Значения параметров полномочий файла Значение Описание г Разрешение на чтение W Разрешение на запись X Разрешение на выполнение Таблица 2.11. Описание значений полномочий для файлов различных типов Операция Обычный файл Каталог Специальный файл Чтение Процесс может читать содержимое Процесс может читать каталог (выводить имена входящих в него файлов) Процесс может читать из файла, используя системный вызов read() Запись Процесс может изменять содержимое Процесс может добавлять и удалять файлы из каталога Процесс может писать в файл, используя системный вызов write() Выполнение Процесс может выполнять файл (при условии что файл — программа) Процессу разрешен доступ к файлам каталога и его подкаталогов —
Запущенный процесс обладает следующими характеристиками: □ реальный пользовательский ID; П эффективный пользовательский ID; □ реальный ID группы; □ эффективный ID группы. Эти значения определяются во время регистрации в системе и являются характеристиками любого запушенного вами процесса. Параметры полномочий применяются следующим образом: П если эффективный пользовательский ID процесса такой же, как у владельца файла, применяются пользовательские параметры полномочий; а если эффективный пользовательский ID процесса отличается от ID владельца файла, но его эффективный ID группы равен ID группы, владеющей файлом, то применяются групповые параметры полномочий; □ если ни эффективный пользовательский ID процесса, ни эффективный ID группы не совпадают, применяются другие параметры полномочий. Следовательно, система полномочий является средством, с одной стороны, позволяющим повысить безопасность, защитив ваши файлы от несанкционированного доступа, а с другой — разрешает работать с ними пользователям, принадлежащим к определенной группе. Мы рассмотрим оптимальные для работы и безопасности установки доступа к файлам и научимся пользоваться утилитами, их изменяющими. Обратите внимание, что разрешение на доступ к файлу устанавливается на основе эффективного пользовательского и группового ID; его реальный пользовательский и групповой ID используются только для учетных целей. Кроме того, права на доступ к файлу обычно зависят от имени инициатора процесса, а не от владельца исполняемого файла. Тем не менее, иногда эта особенность мешает. Например, существует игра rogue, которая поставляется с некоторыми системами UNIX, записывает в отдельный файл лучшие результаты предыдущих игроков. Очевидно, во время выполнения rogue-процесс должен иметь право изменить этот файл, но игрок, от имени которого тот запущен, таких возможностей иметь не должен. Это кажется невозможным на основе той системы полномочий, которая была описана выше. Для того чтобы обойти эту проблему, разработчики UNIX добавили еще две характеристики файла: когда выполняется файл с полномочием "присваиваемый пользовательский ID", эффективный пользовательский и групповой ID устанавливаются равными ID исполняемого файла. Реальный пользовательский или групповой ID не затрагиваются. В нашем примере исполняемый файл и файл результатов принадлежат пользователю rogue, а т. к. в файл результатов может записывать результаты только его владелец (в данном случае rogue), то он (т. е. файл) защищен от изменений обычными пользователями. Так что, когда игрок запускает игру,
она выполняется с эффективным пользовательским ID rogue и таким образом способна изменить файл результатов. Полномочия ’’присваиваемый пользовательский ID” и "присваиваемый групповой ID" обозначаются как s вместо х в пользовательском и групповом наборе полномочий. Они могут быть установлены с использованием утилиты chmod и системного вызова chmod (), описанного в главе 13. Вот еще несколько примечаний, касающихся параметров полномочий файла: П когда процесс создает файл, полномочия по умолчанию, данные этому файлу, заменяются специальным значением, называемым umask. Об этом значении мы поговорим позже, в главе 4; □ привилегированный пользователь автоматически имеет все права доступа независимо от того, предоставлены они или нет; П владелец файла может иметь меньшие права доступа, чем группа или кто-то еще. Счетчик жесткой связи Третье поле указывает на счетчик жесткой связи файла, т. е. сколько ссылок на один физический файл существует в иерархии. Жесткие связи являются отдельной темой и обсуждаются в главе 3 вместе с утилитой in. Г руппы Теперь, когда мы описали параметры полномочий файла, пришло время выяснить, как они работают. Рассмотрим уже известный нам пример, в котором имя пользователя установлено равным glass, а группа равной cs: $ Is -lg heart.final -rw-r—r— 1 glass cs 213 Jan 31 00:12 heart.final $ _ По умолчанию установки доступа разрешают любому пользователю читать файл, однако правами на запись в него обладает только его владелец. Представим себе, что нам необходимо запретить доступ к файлу всех пользователей, кроме входящих в группу music, которым бы разрешалось чтение файла, и его владельца, у которого были бы права на чтение и запись в него. Если такой группы не существует, то единственный способ ее создать — попросить об этом системного администратора. Сам же процесс создания будет рассмотрен в главе 15.
Вывод групп: groups Утилита groups позволяет вывести названия всех групп, членом которых вы являетесь. Синтаксис groups [ID—Пользова теля] По умолчанию утилита groups показывает список всех групп, членом которых вы являетесь. Если имя пользователя определено, выводится список групп, в которые он входит. Пример: $ groups cs music $ . . .вывести мои группы. Изменение группы: chgrp Первый шаг к защите моих лирических стихов должен был состоять в изменении имени группы heart.final с cs на music. Сделано это с помощью утилиты chgrp: $ Is -lg heart.final -rw-r—r— 1 glass cs $ chgrp music heart.final $ Is -lg heart.final 213 Jan 31 00:12 heart.final ...изменить группу. ...подтвердить, что изменилось. -rw-r—г— 1 glass music 213 Jan 31 00:12 heart.final $ Синтаксис chgrp -R имяГруппы {имяФайла}* Утилита chgrp предоставляет возможность пользователю изменять групповую принадлежность файлов, которыми он владеет. Привилегированный пользователь (root) может изменять групповую принадлежность любого файла. За аргументом имяГруппы следуют имена файлов, групповую
принадлежность которых необходимо изменить. Опция -R рекурсивно изменяет групповую принадлежность файлов в каталоге. Вы можете также использовать утилиту chgrp для изменения группы каталога. Изменения прав доступа файла: chmod Теперь, когда групповая принадлежность файла была изменена, нам может потребоваться изменить уровень доступа. Для этого используется утилита chmod: $ Is -lg heart.final -rw-r—r— 1 glass $ chmod o-r heart.final $ Is -lg heart.final -rw-r----- 1 glass $ ...прежде. music 213 Jan 31 00:12 heart.final ...запрещение чтения для других. ...после. music 213 Jan 31 00:12 heart.final Синтаксис chmod -R замена {, замена} * {имяФайла} + Утилита chmod изменяет атрибуты указанных файлов согласно параметрам замена, которые могут принимать любую из форм: • ВыборГруппы + новыеПрава (добавить права)’ • ВыборГруппы - новыеПрава (убрать права); • ВыборГруппы = новыеПрава (назначить права абсолютно). Здесь ВыборГруппы — это любая комбинация: • и (пользователь/владелец); • g (группа); • о (другие); • а (все). новыеПрава — это любая комбинация: • г (читать); • w (писать); • х (выполнять);
• s (присваиваемый пользовательский ID или присваиваемый групповой ID). Опция -R рекурсивно изменяет атрибуты файлов в каталогах. (Для примера см. фрагмент кода, который следует далее.) Изменение прав доступа каталога не меняет права доступа файлов, содержащихся в каталоге. В табл. 2.12 представлены дополнительные параметры утилиты chmod. Таблица 2.12. Параметры утилиты chmod Требование Параметры изменения Добавить группе право записи g+w Удалить право чтения и записи для пользователя u-rw Добавить право выполнения для пользователя, группы и других а+х Предоставить группе только право чтения g=r Добавить право записи для пользователя и удалить право чтения u+w, g-r для группы Желательно защитить свой домашний каталог от несанкционированного доступа, запретив всем пользователям записывать в него, оставив лишь права чтения и, возможно, выполнения: $ cd $ Is -Id . drwxr-xr-x 45 $ chmod o-rx $ Is -Id drwxr-x--- 45 $ ...перейти в домашний каталог. ...вывести атрибуты домашнего каталога, glass 4096 Apr 29 14:35 ...изменить права доступа. ...получить подтверждение. glass 4096 Apr 29 14:35 Обратите внимание, что для просмотра атрибутов каталога, а не содержащихся в нем файлов, была использована опция -d утилиты 1s. Вы можете определять права доступа, используя восьмеричное число — триплет значений, разрешающих запуск, чтение и изменение файлов. Например, необходимо, чтобы файл имел следующие разрешения: rwxr-x-- Восьмеричный эквивалент этой записи выражается числом 750. В табл. 2.13 представлены различные форматы записи прав доступа.
Ниже приведен пример задания флагов доступа с использованием восьмеричных значений: $ chmod 750 ...обновить права. $ Is -Id ...подтвердить. drwxr-x--- 45 glass 4096 Apr 29 14:35 $ Таблица 2.13. Восьмеричный эквивалент прав доступа Пользователь Группа Другие Установка rwx r-x — Двоичная 111 101 ООО Восьмеричная 7 5 0 Изменение владельца файла: chown Если, по каким-либо причинам, вы захотите передать права собственности на файл, вы сможете это сделать, используя утилиту chown. Некоторые версии UNIX позволяют только привилегированному пользователю (root) изменять права собственности файла, особенно в случае квотирования дискового пространства, доступного каждому пользователю, другие — передавать права может и владелец файла. Случаи, когда системному администратору приходится изменять права собственности на файлы и каталоги, рассмотрены в главе 15. Синтаксис chown -R новыиПользовательскийЮ {имяФайла} + Утилита chown позволяет привилегированному пользователю изменять права собственности на файлы. Смена принадлежности производится для всех файлов, следующих за аргументом новыйпользовательскийю. Опция -R рекурсивно изменяет владельца файлов в каталогах и подкаталогах. Пример передачи прав собственности на файл от владельца glass пользователю tim и возращение их glass: $ Is -lg heart.final ...определить владельца. -rw-r---- 1 glass music 213 Jan 31 00:12 heart.final
$ chown tim heart, final ... изменить - владельца на tim. $ Is -lg heart.final ...проверить владельца. -rw-r---- 1 tim music 213 Jan 31 00:12 heart, final $ chown glass heart.final ...изменить владельца на glass. $ Изменение групп: newgrp Если вы член нескольких групп, то какой из них будет принадлежать создаваемый вами файл? Ответ на вопрос прост — вы можете быть членом нескольких групп, но текущая эффективная группа у вас одна, и процесс, создающий файл, использует эффективный ID именно этой группы. Это означает, что после создания файла из shell групповой ID файла устанавливается на эффективный групповой ID вашего shell. В рассматриваемых примерах членство принадлежит группам cs и music, а эффективное имя группы рабочего shell — cs. Системный администратор выбирает, какая группа является для вас основной. Поэтому при необходимости изменить текущую группу можно воспользоваться УТИЛИТОЙ newgrep. Синтаксис newgrp [-] [имяГруппы\ Утилита newgrp изменят текущую группу пользователя. Если вы указываете символ - вместо имени группы, то ваша группа остается той же, что и при регистрации в системе. В представленном ниже примере созданы файл test 1 с группой владельцев cs и файл test2, которым владеет music: $ date > testl ...создать из shell группу cs. $ newgrp music ...создать группу music. $ date > test2 ...создать из shell группу music. ...прекратить работу нового shell. $ Is -lg testl test2 ...посмотреть атрибуты каждого файла. -rw-r—г— 1 glass cs 29 Jan 31 22:57 testl -rw-r—r— 1 glass music 29 Jan 31 22:57 test2
Поэзия в движении: эпилог Мы познакомились с основными утилитами UNIX, и, желательно, перед дальнейшим чтением попробовать проделать действия, описанные в примерах. Оставшаяся часть главы посвящена настройке терминала и двум наиболее популярным редакторам UNIX. Определение типа вашего терминала: tset Отдельные утилиты UNIX, включая два стандартных редактора vi и emacs, должны ’’знать", какой терминал вы используете, чтобы правильно управлять выводом. Тип вашего терминала сохраняется в глобальной переменной, называемой переменной окружения (см. главу 4). По умолчанию эта переменная чаще всего равна vtioo или vt52. Есть несколько способов задать необходимый тип. П Тип term может устанавливаться непосредственно, через строку вида setenv, TERM vtlOO (ДЛЯ С shell) ИЛИ TERM=vti00; export term (для Bourne, Korn и Bash) вашего терминала. Этот метод установки эффективен, если вы знаете его тип заранее и всегда регистрируетесь с одного и того же терминала. □ Стартовый файл запуска shell может вызвать утилиту tset, определяющую порт коммуникаций, с которым вы связаны, и затем производящую поиск соответствия порт/терминал в специальном файле /etc/ttytab. Если утилита tset не может найти тип терминала, она запрашивает информацию о нем при регистрации в системе. П Вы можете вручную установить term из shell. Далее мы познакомимся с двумя утилитами, позволяющими определить и установить тип терминала: tset и stty. Самый простой способ установить тип терминала состоит в использовании утилиты tset. Синтаксис tset -s [-ес] [~ic] {-m портЮ: [?] типТерминала] * Утилита tset определяет тип терминала. Если опция -s не используется, то предполагается, что ваш тип терминала уже сохранен в переменной term окружающей среды, и утилита tset
определяет тип, используя информацию в зависимости от вашей версии UNIX, хранящуюся в файле /etc/termcap или базе данных terminfo. Если вы используете опцию -s, утилита tset исследует файл /etc/ttytab и пробует соотнести порт вашего терминала с его типом. Если тип найден, утилита указанным в файле /etc/termcap образом его инициирует. Опция -s также выводит команды shell на стандартный выход. Переменные окружающей среды term и termcap устанавливаются в необходимое значение, и запускается нужный тип терминала. В имени файла не должно содержаться символов, которые могут быть неправильно интерпретированы shell. Эти символы будут указаны чуть ниже. Опция -е задает стирающий символ терминала в аргументе с вместо установленной по умолчанию комбинации клавиш <Ctrl>+<H>. Символы управления могут быть обозначены или просто введенным символом, или вместе с предшествующим ему символом л (чтобы указать на комбинацию клавиш <Ctrl>+<H>). Опция -i определяет прерывающий символ терминала в аргументе с вместо установленной по умолчанию комбинации клавиш <Ctrl>+<C>. Обозначение символов управления рассматривается в разд. "Режим ввода текста " далее в этой главе. Записи файла /etc/ttytab могут быть удалены или добавлены с помощью опции -т. Последовательность -mpp:tt сообщает утилите tset следующее: если тип порта терминала является рр, то он должен быть заменен на тип терминала tt. Помещенный после двоеточия (:) знак вопроса (?) показывает tt и просит пользователя либо нажать клавишу <Enter> для подтверждения, либо ввести другой тип терминала. Пример: ttyOf ”usr/etc getty std.9600” vtlOO off local ttypO none network off secure ttypl none network off secure Первое поле содержит имена портов, а третье — типы терминалов. Например, если пользователь зарегистрировался через порт ttyOf, то его тип терминала, в соответствии с записями этого файла, определялся бы как vtioo. В конфигурациях, где терминалы являются жестко замонтированными (т. е. непосредственно связаны с определенным портом), этот алгоритм работает прекрасно, поскольку терминал всегда связан с тем же самым портом. Но, как мы увидим вскоре, это не работает в сетевых конфигурациях. В следующем примере определим фактическое имя порта, используя утилиту tty (описанную в главе 3)\ $ tty ...вывести ID порта моего терминала. /dev/ttypO 4 Зак 786
$ tset -s ...вызов tset. set noglob; ...команды shell, генерируемые tset. TERM=network; export TERM; TERMCAP='sa;cent;network:li#24:co#80:am:do=AJ: ’; export TERMCAP; unset noglob; Erase is Ctrl-H $ Предыдущий пример дан только для иллюстрации работы утилиты tset. Чтобы действительно изменить переменные term и termcap, вы должны выполнить команду eval, которая подробно описана в главе 4. Вот более реалистический пример использования утилиты tset: $ set noglob $ eval 'tset -s' Erase is Backspace $ unset noglob $ echo $TERM network $ ...временно запрещает расширение имени файла. ...оценить вывод tset. ...сообщение tset. ...разрешение расширения имени файла. ...посмотреть новое значение TERM. ...тип терминала, который нашел tset. К сожалению, тип терминала network (сеть) — не очень удобен, поскольку он не имеет практически никаких возможностей. Утилита tset определяется правилом: если обнаружен тип терминала network, следует предполагать, что данный терминал — vtlOO — и запросить у пользователя подтверждение этого. Пример: $ set noglob ...запрещает расширение имени файла. $ eval 'tset -s -т 'network:vtlOO'' ...предоставить правило. TERM = (vtlOO) <Enter> ...нажата клавиша <Enter>. Erase is Backspace $ unset noglob ...разрешение расширения имени файла. $ echo $TERM ...вывести новую установку TERM. vtlOO ...это тип терминала, который использует tset. $ _ Обобщая, можно сказать, что достаточно удобно записать запуск утилиты tset в стартовый файл вашего терминала. Файлы запуска shell описаны в главе 4. Самый простой синтаксис утилиты tset представлен ниже. □ С shell setenv TERM vtlOO tset
□ Bourne/Korn/Bash shell TERM=vt100; export TERM tset Более сложная форма tset определяет местонахождение файла /etc/ttytab и чаще всего выглядит, как это показано в примере. □ С shell set noglob eval ’tset -s -m ’network:?vtl00’' unset noglob □ Bourne/Korn/Bash shell eval ’tset -s -m ’network:?vtl00’’ Изменение характеристик терминала: stty Каждый терминал определенным образом обрабатывает некоторые символы. Эти знаки называются метасимволами. Например, метасимволом является символ возврата на шаг назад и завершающая программу последовательность, генерируемая комбинацией клавиш <Ctrl>+<C>. Переопределить значения метасимволов можно, используя утилиту stty. Синтаксис stty -а {опция}★ {строкаМета символа <значение>} * Утилита stty позволяет проверить и установить характеристики терминала. С ее помощью можно изменить около 100 различных параметров, здесь будут рассмотрены наиболее используемые.. Для получения листинга текущих установок терминала служит опция -а. Чтобы изменить конкретный параметр, используйте одну или несколько следующих опций: Опция Значение -echo He отображать печатаемые символы echo Отображать печатаемые символы -raw Разрешить специальное значение метасимволов raw Запретить специальное значение метасимволов
(окончание) Опция Значение -tostop Позволить фоновым процессам посылать информацию на терминал tostop Остановить фоновые процессы, которые пытаются послать информацию на терминал sane Установить характеристики терминала по умолчанию Также имени или \ 1 новны> вы можете переопределить значение метасимвола, присваивая его новое значение. Символ управления может быть обозначен как Л 1епосредственно перед этим символом. Ниже приведен список ос-с метасимволов и их значений. Опция Значение erase Удалить предыдущий символ kill Стереть всю текущую строку Inext Не обращаться со следующим символом, как со специальным susp Приостановить текущий процесс intr Прервать фоновую работу без распечатки памяти quit Завершить фоновую работу с распечаткой памяти stop Остановить/возобновить вывод на терминал eof Задать конец ввода Вот пример работы утилиты stty: $ stty -а . ..вывести текущие установки терминала. speed 38400 baud, 24 rows, 80 columns parenb -parodd cs7 -cstopb -hupcl cread -clocal -crtscts -ignbrk brkint ignpar -parmrk -inpck istrip -inlcr -igncr icrnl -iuclc ixon -ixany -ixoff imaxbel isig rexten icanon -xcase echo echoe echok -echonl -noflsh -tostop echoctl -echoprt echoke opost -olcuc onlcr -ocrnl —onocr -onlret -ofill -ofdel erase kill werase rprnt flush Inext susp intr quit stop eof
$ stty erase $ stty erase ^h ...задать удаление символа ...через комбинацию клавиш <Ctrl>+<B>. ...задать удаление символа ...через комбинацию клавиш <Ctrl>+<H>. $ _ Используйте утилиту stty для установки более удобных для вас значений метасимволов. Настройте свой терминал "под себя" путем создания сценариев, один из вариантов которого мы рассмотрим в главе 7. Вот пример кода, в котором используется утилита stty, чтобы отключить вывод символов: $ stty -echo $ stty echo $ ...отключить вывод. ...включить его снова. Обратите внимание, что последнюю строку ввода (stty echo) вы не увидите из-за ранее установленного запрета вывода. Теперь, когда мы узнали, как определить тип терминала и его установки, пришло время рассмотреть два наиболее популярных редактора UNIX: vi и emacs. Редактирование файла: vi Как мы уже упомянули, наиболее популярными редакторами в UNIX являются vi и emacs. Неплохо иметь опыт работы с vi, т. к. он поставляется практически с каждой версией UNIX. Второй редактор, emacs, встречается не во всех версиях, однако достаточно традиционен для этой системы. В этом и последующих разделах мы поподробнее познакомимся с каждым из них. Также будут даны ссылки на другие источники, из которых вы можете получить более подробную информацию. Запуск vi Немного истории. Изначально Билл Джой (Bill Joy) из Sun Microsystems, Inc., работая в Калифорнийском университете в Беркли создал этот редактор для BSD UNIX. Однако популярность vi выросла настолько, что он стал стандартной утилитой большинства версий UNIX. Название vi (от англ, visual editor) расшифровывается, как визуальный редактор. Для запуска этого редактора необходимо просто ввести команду vi без параметров. В случае, если вам необходимо редактировать существующий файл, то в качестве параметра следует передать его имя. После запуска чистые (пустые) строки указываются тильдами (~), после чего vi ожидает от вас ввода команд. Для экономии места мы будем рассматривать примеры длиной не более шести строк.
Например, при запуске vi без параметров на вашем экране отобразится следующее: Режим ввода команды — один из двух режимов, которые предоставляет редактор vi. Другой режим называется режимом ввода текста. Так как легче проиллюстрировать работу командного режима на примере каких-нибудь фраз, мы начнем знакомство с режима ввода текста. Режим ввода текста В табл. 2.14 дано описание клавиш ввода текста. Любой текст, который вы вводите, отображается на экране. Для перехода на следующую строку служит клавиша <Enter>. Для удаления последнего символа пользуйтесь клавишей < Backspaced Внимание Вы не можете двигаться по экрану с помощью клавиш перемещения курсора, когда находитесь в режиме ввода текста. В режиме ввода текста эти клавиши интерпретируются, как обычные символы ASCII, и коды управления вводятся, как нормальный текст. Таблица 2.14. Клавиши ввода текста в редакторе vi Клавиша Действие <i> Текст вставляется перед курсором Текст вставляется в начало текущей строки <а> Текст добавляется после курсора <А> Текст добавляется к концу текущей строки <0> Текст добавляется после текущей строки <О> Текст вставляется перед текущей строкой <R> Текст заменяется (переписывается) Для перехода к вводу текста из режима команд нажмите клавишу <Esc>.
Так, чтобы ввести короткую поэму, необходимо нажать клавишу <а> для перехода в текстовый режим, и клавишу <Esc> для возврата к режиму команд. I always remember standing in the rains, On a cold and damp September, Brown Autumn leaves were falling softly to the ground, Like the dreams of a life as they slide away. В следующем разделе мы узнаем, как сделать из представленного текста нечто более интересное. Режим ввода команд Для редактирования текста следует войти в командный режим. Для этого нажмите клавишу <Esc>. Если вы нажмете ее в командном режиме, ничего не произойдет. Система лишь звуковым сигналом напомнит вам, что вы уже работаете с ним. Редактирование документа происходит путем нажатия последовательности специальных клавиш. Например, для того чтобы стереть отдельное слово, поместите курсор к его началу и нажмите клавишу <d> и затем клавишу <w> (от англ, delete word — удалить слово). Некоторым функциям редактирования необходимы параметры, которые передаются после двоеточия (:). Если эта клавиша нажата, остальная часть команды выводится внизу экрана. Например, чтобы удалить строки с 1 по 3, необходимо ввести следующую командную последовательность: :1,3d<Enter> Некоторые функции редактирования типа команды удаления блока, которую мы только что рассмотрели, действуют в каком-либо диапазоне строк. Редактор понимает два формата указания диапазона: □ чтобы выбрать единственную строку, укажите ее номер; □ чтобы выбрать блок строк, укажите номер первой и последней строк включительно, разделенных запятой. Символ $ используется для указания номера последней строки в файле; символ . — для строки, содержащей курсор. Кроме того, для определения строк можно пользоваться различными арифметическими выражениями. Например, такой последовательностью, которая удалила бщ текущую строку и две следующие за ней строки: :.,.+2d<Enter> В табл. 2.15 представлены другие способы задания диапазона.
Таблица 2.15. Выбор диапазона строк в редакторе vi Диапазон Выбирает Все строки в файле 1,. Все строки от начала файла до текущей строки включительно Все строки от текущей строки до конца файла включительно .-2 Отдельную строку, которая находится на две строки выше текущей строки Буфер памяти и временные файлы Во время редактирования файла vi хранит его копию в памяти и вносит туда изменения. Файл на диске не изменяется, пока вы не сохраните его или не выйдете из vi с помощью одной из команд, которая автоматически сохраняет файл (эти команды мы рассмотрим чуть позже). По этой причине рекомендуется почаще сохранять изменения во время работы. В противном случае, при сбое системы будут потеряны все внесенные вами изменения. Однако, если вы сохраняете промежуточные версии, в случае сбоя системы будут потеряны не все изменения, и вы сможете восстановить файл, используя опцию -г (vi -г имяФайла) из временного файла. Некоторые версии UNIX пошлют вам электронное письмо со сведениями, как восстановить утерянный файл. Но, несмотря на существующую возможность восстановления, безопаснее все-таки почаще сохранять изменения. Общие функции редактирования Основные функции редактирования в vi^ могут быть сгруппированы в следующие категории: □ перемещение курсора; □ удаление текста; □ замена текста; □ вставка текста; □ поиск по тексту; □ поиск и замена текста; □ сохранение или загрузка файлов; □ разное (включая, выход из vi). Данные категории описаны и проиллюстрированы чуть ниже, на примере поэмы, о которой мы говорили в разд. "Режим ввода текста" ранее в этой главе.
Перемещение курсора В табл. 2.16 представлены общие команды перемещения курсора. Например, чтобы вставить слово Just перед словом Like в четвертой строке, необходимо переместить курсор на четвертую строку, нажав клавишу <i>, чтобы войти в режим ввода текста, а затем нажать клавишу <Esc> для возвращения в режим команд. Для перемещения курсора на четвертую строку используется последовательность: 4<Enter> (либо 4G). Таблица 2.16. Команды перемещения курсора в редакторе vi Перемещение Ключевая последовательность Вверх на строку <?> или <к> Вниз на строку <Х> или <j> Вправо на один символ <-»> или <1> Влево на один символ «-> или <h> К началу строки В конец строки Назад на одно слово <~> <$> <Ь> Вперед на одно слово Вперед к концу текущего слова К началу экрана В середину экрана В конец экрана Вниз на половину экрана Вперед на один экран Вверх на половину экрана Назад на экран К строке пп <w> <е> <Н> <м> <Ctrl>+<D> <Ctrl>+<F> <Ctrl>+<U> <Ctrl>+<B> : nn<Enter> (nnG также работает) К концу файла <G> Удаление текста В табл. 2.17 приведены общие команды удаления текста. Например, для удаления слова always следует воспользоваться последовательностью: □ i<Enter> для перемещения на начало первой строки;
□ нажать клавишу <w> для перемещения на одно слово вперед; □ ввести dw для удаления. Для удаления буквы s в конце слова rains, находящегося в первой строке, нам необходимо переместить курсор на символ s и затем нажать клавишу <х>. Таблица 2.17. Команды, удаляющие текст Элемент для удаления Ключевая последовательность Символ Поместите курсор на символ и затем нажмите клавишу <х> Слово Поместите курсор на начало слова и затем введите dw Строка Поместите курсор куда-нибудь в строку, а затем введите dd. (Ввод числа перед dd заставит vi удалить указанное число строк, начиная с текущей строки.) С текущей позиции до конца текущей строки Г руппа строк Нажмите клавишу <D> : <диапазон>&<ЕпЪег> Вот как выглядит текст после описанных выше манипуляций: I remember standing in the rain, On a cold and damp September, Brown Autumn leaves were falling softly to the ground, Just Like the dreams of a life as they slide away. Замена текста Основные команды замены текста рассмотрены в табл. 2.18. Так, например, для замены слова standing на walking необходимо переместиться в его начало и набрать cw, а затем напечатать walking и нажать клавишу <Esc>. Для замены строчной s в слове September на прописную S следует поместить курсор на s и нажать клавишу <г>, а затем набрать s. Таблица 2.18. Замена текста в редакторе vi Элемент для замены Символ Ключевая последовательность Поместите курсор на символ, нажмите клавишу <г>, а затем напечатайте символ замены
Таблица 2.18 (окончание) Элемент для замены Ключевая последовательность Слово Поместите курсор в начало слова, наберите cw и затем напечатайте текст замены, в конце нажмите клавишу <Esc> Строка Разместите курсор где-нибудь в строке, наберите с с, а затем напечатайте текст замены, в конце нажмите клавишу <Esc> Теперь наша поэма выглядит так: I remember walking in the rain, On a cold and dark September, Brown Autumn leaves were falling softly to the ground, Just like the dreams of a life as they slip away. Вставка текста Для копирования и вставки текста редактор vi пользуется специальным буфером. Например, для копирования первых двух строк в буфер и вставки их после третьей нам необходимо совершить следующее: :1,2у : Зри В табл. 2.19 описаны основные команды работы с буфером. Таблица 2.19. Команды работы с буфером Действие Ключевая последовательность Скопировать строки в буфер вставки :<диапазон>у<Enter> Скопировать текущую строку в буфер вставки <Y> Вставить содержимое буфера вставки после текущей строки р или :pu<Enter> (содержимое буфера вставки не изменяется) Вставить содержимое буфера вставки после строки ПП : nnpu<Enter> (содержимое буфера вставки не изменяется) Сейчас наша поэма выглядит так: I remember walking in the rain, On a cold and dark September,
Brown Autumn leaves were falling softly to the ground, I remember walking in the rain, On a cold and dark September, Just like the dreams of a life as they slip away. Поиск Если необходимо найти то или иное слово или выражение в тексте, вы можете воспользоваться поиском, который осуществляется выше и ниже от текущей строки. В табл. 2.20 рассмотрены наиболее общие команды поиска. Замыкающие символы / и ? необязательны для первых двух команд (т. к. редактор понимает, что вы имели в виду; однако использование / и ? является полезной привычкой, позволяющей добавить другие команды после этих символов). Таблица 2.20. Команды поиска в vi Действие Ключевая последовательность Поиск вперед от текущей позиции строки sss /sss/<Enter> Поиск назад от текущей позиции строки sss ?sss?<Enter> Повторить последний поиск <n> Повторить последний поиск в обратном направлении <N> Например, для поиска слова ark, начиная с первой строки, необходимо ввести следующие команды: :l<Enter> /ark/<Enter> В результате выполнения этой последовательности курсор будет установлен на часть слова dark, содержащего эту последовательность: I remember walking in the rain, On a cold and dark September, Brown Autumn leaves were falling softly to the ground, Just like the dreams of a life as they slip away. Поиск и замена Очень часто возникает необходимость замены определенных выражений в тексте. Для этого следует сначала найти указанные выражения, а потом
произвести замену. Эта задача выполняется с помощью команд, описанных в табл. 2.21. Таблица 2.21. Поиск и замена в редакторе vi Действие Ключевая последовательность Заменяет первое вхождение sss на ttt :<.диапазонуз/ sss/ ttt/<Enter> в каждой строке Заменяет каждое вхождение sss на ttt :<диапазонУг/ sss/ ttt/g<Enter> в каждой строке После замены всех символов ге на XXX получим следующее: I XXXmember walking in the rain, On a cold and dark September, Brown Autumn leaves weXXX falling softly to the ground, Just like the dXXXams of a life as they slip away. :1,$s/re/XXX/g Сохранение или загрузка файлов Для сохранения и загрузки файла используются команды, описанные в табл. 2.22. Таблица 2.22. Команды сохранения и загрузки файлов в редакторе vi Действие Ключевая последовательность Сохранить файл как <имя> : w<MMtf><Enter> Сохранить файл с текущим именем :w<Enter> Сохранить файл с текущим именем и выйти :wq<Enter> (zz также работает) Сохранить только определенные строки в другой файл : <диапазон>ы<имя><Еп^гУ Прочитать содержимое другого файла в текущую позицию :г <имяХЕп!ег> Редактировать файл <имя> вместо текущего файла : e<wMHXEnter> Редактировать следующий файл из первоначальной командной строки :n<Enter>
Пример записи содержимого буфера в файл с именем rain.doc: I remember walking in the rain, On a cold and dark September, Brown Autumn leaves were falling softly to the ground, Just like the dreams of a life as they slip away. :w rain, doc При сохранении файла редактор vi сообщает его размер. Случайно выйти из vi, не сохранив изменения, невозможно. Если вы задаете больше, чем один файл в командной строке во время первоначального вызова vi, редактор стартует, загружая первый файл. Перейти к редактированию следующего файла можно, используя ключевую последовательность :п. Разное В табл. 2.23 приведены наиболее общие команды работы с редактором vi, включая выход из него. Комбинация клавиш <Ctrl>+<L> особенно полезна для обновления экрана в случае вывода сообщений о сбоях в работы системы во время работы с vi. Таблица 2.23. Основные команды работы с редактором vi Действие Ключевая последовательность Перерисовать экран Отменить последнюю операцию Отменить многочисленные изменения в текущей строке Соединить следующую строку с текущей Повторить последнюю операцию Выполнить команду в новом shell и затем вернутся В vi <Ctrl>+<L> <u> <U> :!<команда><Еп1ег> Выполнить команду в новом shell и прочитать ее вывод в буфер редактирования в текущую позицию Выйти из vi, если работа сохранена :г!<команда><Еп1ег> :q<Enter> Выйти из vi без сохранения :q!<Enter>
Выход из редактора: I remember walking in the rain, On a cold and dark September, Brown Autumn leaves were falling softly to the ground, Just like the dreams of a life as they slip away. Настройка vi Путем установки определенных опций вы можете настроить поведение вашего редактора в тех или иных ситуациях. Полный список доступных опций изменяется в зависимости от версии и платформы, но мы обсудим наиболее часто используемые. Команда :set служит для вывода опций, поддерживаемых редактором vi. После ввода .-set all будут выведены текущие и поддерживаемые редактором установки. Они задаются либо числовыми и строковыми величинами, либо переключателями "Вкл" и "Выкл". Наиболее используемые опции показаны в табл. 2.24. Таблица 2.24. Настройки редактора vi Опция Описание Установка по умолчанию autoindent Во включенном состоянии говорит о том, что следующая строка, которую вы печатаете, имеет такой же отступ, как и предыдущая строка Выкл ignorecase В процессе поиска и замены строчные и прописные символы удовлетворяют критерию, требуемому для совпадения Выкл number Нумерация строк выводится по левой стороне экрана Выкл showmode Показывает текущий режим (текстовый или командный) Выкл showmatch Подсвечивает открывающую скобку при печати закрывающей Выкл Например, для того чтобы включить опцию autoindent, напечатайте :set autoindent<Enter> А для того чтобы выключить, напечатайте :set noautoindent<Enter>
Сохранение пользовательских настроек Для того чтобы не настраивать каждый раз заново редактор, вы можете создать файл, в котором укажите необходимые установки. Файл конфигурации должен называться .ехгс. Внимание Обратите внимание, что имя файла начинается с точки: это специальное соглашение, о котором мы узнаем позже, когда будем рассматривать команды shell. set autoindent set ignorecase set nonumber В действительности нет необходимости устанавливать опцию nonumber, т. к. ее начальное значение "Выкл", но в данном случае имеется в виду, что вы как бы выключили ее. Потому-то она и была установлена. Для дополнительной информации Для более глубокого знакомства с редактором vi рекомендуется обратиться к книге [Christian 1988]. Редактирование файла: emacs emacs (от англ. Editor MACroS) — не менее популярный редактор, который можно обнаружить на многих UNIX-системах (либо загрузить из Интернета). История emacs начиналась в Lisp-based artificial intelligence community. В 1975 г. Ричард Сталлман (Richard Stallman) и Гай Стил (Guy Steele) написали первоначальную версию, которая была в последствии доработана и сейчас свободно распространяется в форме исходных кодов через Free Software Foundation (FSF). О группах Open Sourse и Free Software Foundation мы поговорим в главе 16. Запуск emacs Для запуска этого редактора, как и для запуска vi, необходимо просто ввести команду emacs без параметров. Чтобы начать редактирование существующего файла, задайте его имя в качестве параметра. При запуске emacs без параметров на вашем экране отобразится следующее: GNU Emacs 19.34.1 Copyright (С) 1996 Free Software Foundation, Inc.
Type С-х С-с to exit Emacs. Type C-h for help; C-x u to undo changes. Type C-h t for a tutorial on using Emacs. Emacs: ^scratch* (Fundamental)-----All Для экономии места будем рассматривать небольшие примеры. Вторая снизу строка называется строкой режима и содержит следующую информацию: □ если первые три черты содержат **, то текущий файл был изменен; □ название, которое следует за Emacs: — имя текущего файла; если ни один файл не загружен, вместо него используется имя *scratch*; □ текущий режим редактирования показывается в круглых скобках. В данном случае это Fundamental, который является стандартным режимом редактирования; □ следующий элемент указывает текущую рабочую позицию в файле, как процент относительно размера всего документа. Если файл очень маленький и полностью помещается на экране, то это значение равно ан. Если вы находитесь в начале или конце файла, то соответственно отображается Тор И Bot. Команды emacs В отличие от vi, в редакторе emacs не различаются режим ввода текста и режим команд. Для того чтобы ввести текст, просто начинают печатать. Начальный приветствующий заголовок emacs автоматически исчезает, когда вы вводите первый символ. Длинные строки не разрываются автоматически, так что необходимо нажать клавишу <Enter> для перехода на новую строку. Строки, превышающие ширину экрана, заканчиваются символом \ и переносятся на следующую строку, как это показано в следующем примере: Это очень длинная строка, иллюстрирует способ, каким выв \ одятся неразрывные строки Это более короткая строка --- Emacs: ^scratch* (Fundamental) -- All --------- Редактирование в emacs происходит через набор определенных команд. В данной книге одновременное нажатие клавиш будет обозначаться следующим образом: <Ctrl>+<H> <t> ’
Это означает, что нажимают и удерживают клавишу <Ctrl> и затем нажимают клавишу <Н>. Затем отпускают обе клавиши и нажимают клавишу <t>. Точно так же используется клавиша <Esc>. Например, последовательность <Esc> <х> означает, что нажимают клавишу <Esc> (но не держат ее) и затем нажимают клавишу <х>. Если же когда-нибудь вы нажмете <Esc> два раза подряд, то emacs предложит вам нажать клавишу <п> для отмены операции. В следующих разделах мы познакомимся подробнее с приемами редактирования В emacs. Как избежать проблем Всякий раз, когда вы изучаете новую программу, весьма легко растеряться и запутаться. Вот пара полезных команд для возврата к нормальному состоянию: □ комбинация клавиш <Ctrl>+<G> заканчивает любую команду редактора emacs, даже если она (т. е. команда) только частично введена, и возвращает emacs к ожиданию новых команд; □ комбинация клавиш <Ctrl>+<X> <1> закрывает все окна emacs кроме главного окна редактирования файла. Это полезно, поскольку часто редактор выводит какую-либо информацию в новом окне, и следует знать, как его закрыть после ознакомления с его содержимым. Получение помощи Существует немало способов получить помощь о работе emacs. Однако лучше всего воспользоваться встроенным в редактор справочником, вызов которого осуществляется комбинацией клавиш <Ctrl>+<H> <t>. Выход из emacs Для того чтобы закрыть emacs и сохранить файл, используют комбинацию клавиш <Ctrl>+<X> <Ctrl>+<C>. Если файл не сохранен со времени его последнего изменения, появится запрос на подтверждение сохранения. Режимы emacs Редактор поддерживает несколько различных режимов для ввода текста, включая Fundamental, Lisp Interaction И С. Каждый режим имеет специальные функции для работы с конкретным видом текста. По умолчанию emacs запускается в режиме Fundamental. Рассмотрим именно этот режим. За информацией о других режимах обращайтесь ко встроенному справочнику.
Ввод текста Чтобы ввести текст, просто начинают печатать. Пример: There is no need for fear in the night, You know that your Mommy is there, To watch over her babies and hold them tight, When you are in her arms you can feel her sigh all night. --- Emacs: *scratch* (Fundamental)-----All------------ Общие функции редактирования Наиболее общие функции редактора emacs могут быть сгруппированы в следующие категории: □ перемещение курсора; □ удаление, вставка и отмена текста; □ поиск в тексте; □ поиск и замена текста; □ сохранение и загрузка файлов; □ разное. Далее мы рассмотрим эти функции на уже знакомом нам примере поэмы. Перемещение курсора Общие комбинации клавиш перемещения курсора описаны в табл. 2.25. Например, для того чтобы вставить слова worry or перед словом fear в первой строке, необходимо переместить курсор в первую строку файла, нажимая клавиши <Esc> «> и затем, передвинуться вперед на несколько слов, нажимая клавиши <Esc> <f>. После этого необходимо напечатать слова, которые будут автоматически вставлены в текущую позицию курсора. Таблица 2.25. Перемещение курсора в редакторе emacs Действие Ключевая последовательность Вверх на одну строку Вниз на одну строку <Ctrl>+<P> (предыдущий) <Ctrl>+<N> (следующий) Вправо на один символ <Ctrl>+<F> (вперед, переход на новую строку) Влево на один символ <Ctrl>+<E> (назад, переход на новую строку) К началу строки <Ctrl>+ <А> (а — это первая буква)
Таблица 2.25 (окончание) Действие Ключевая последовательность К концу строки Назад на одно слово <Ctrl>+<E> (end — конец) <Esc> <b> (back — назад) Вперед на одно слово Вниз на один экран Вверх на один экран В начало файла В конец файла <Esc> <f> (forward — вперед) <Ctrl>+<V> <Esc> <v> <Esc> «> <Esc> <» Удаление, вставка и отмена Всякий раз, когда удаляется элемент текста, emacs запоминает его в индивидуальном буфере уничтожения. Список буферов уничтожения поддерживается для того, чтобы удаленные элементы могли быть восстановлены после удаления с экрана. Чтобы восстановить последний стертый элемент, используйте комбинацию клавиш <Ctrl>+<Y>. После ее нажатия вы можете применить последовательность клавиш <Esc> <у>, чтобы заменить восстанавливаемый элемент предварительно удаленным. Каждый раз, когда вы нажимаете клавиши <Esc> <у>, восстанавливаемый элемент продвигается на одну позицию назад в списке буфера уничтожения. Вы можете добавить очередной удаляемый элемент в конец последнего буфера уничтожения, не создавая новый буфер, используя последовательность клавиш <Esc> <Ctrl>+<W> до того, как воспользуетесь командой удаления. Это может быть полезно, если необходимо сначала вырезать различные части файла, а потом вставить в одно место. В табл. 2.26 приведены комбинации клавиш, позволяющие удалять символы. Таблица 2.26. Удаление, вставка и отмена в редакторе emacs Элемент для удаления Ключевая последовательность Символ перед курсором <Delete> Символ после курсора <Ctrl>+<D> Слово пред курсором <Esc> <Delete> Слово после курсора <Esc> <d> До конца текущей строки <Ctrl>+<K> Предложение <Esc> <k>
Вы можете отменить сделанные изменения, нажимая клавиши <Ctrl>+<X> <u> для каждого отменяемого действия. В табл. 2.27 описаны комбинации клавиш работы с буфером. Таблица 2.27. Буфер уничтожения в редакторе emacs t Действие Ключевая последовательность Вставить последний буфер уничтожения <Ctrl>+<Y> Извлечь предыдущий удаленный фрагмент <Esc> <у> Добавить следующий удаляемый фрагмент <Esc> <Ctrl>+<W> Отменить последнее действие <Ctrl>+<X> <u> Поиск Работая в emacs, вы можете пользоваться инструментом, называемым инкрементным поиском. Для того чтобы найти конкретную последовательность символов вперед от текущего местонахождения курсора, нажмите комбинацию клавиш <Ctrl>+<S>. Внизу экрана появится подсказка "I-search:", после которой вы должны ввести искомую строку. По мере того, как вы будете вводить последовательность символов, редактор emacs станет искать ближайшую к текущему положению строку, совпадающую с введенной. То есть во время ввода поискового выражения подыскиваются подстроки, содержащие уже введенную последовательность. Для завершения поиска и остановки на текущей позиции следует нажать клавишу <Esc>. Стирая символы в поисковом выражении до нажатия клавиши <Esc>, вы передвигаете курсор к ближайшему совпадению с оставшимся набором символов поиска. Для повторения поиска не нажимайте клавишу <Esc>, а воспользуйтесь комбинацией клавиш <Ctrl>+<S> для поиска вперед от текущего положения курсора или <Ctrl>+<R> для поиска назад. В табл. 2.28 рассмотрены основные комбинации клавиш поиска. Таблица 2.28. Поиск в редакторе emacs Действие Ключевая последовательность Поиск вперед строки str <Ctrl>+<S> str Поиск назад строки str <Ctrl>+<R> str Повторить последний поиск вперед <Ctrl>+<S> Повторить последний поиск назад <Ctrl>+<R> Завершить режим поиска <Esc>
Поиск и замена Для того чтобы найти и заменить какие-либо выражения, нажмите клавиши <Esc> <х> и введите г epi s, а затем нажмите клавишу <Enter>. Далее emacs попросит ввести искомую строку, а затем заменяющую строку. После нажатия клавиши <Enter> будет выполнена замена по всему тексту. Сохранение и загрузка файлов Чтобы сохранить файл, нажмите комбинации клавиш <Ctrl>+<X> <Ctrl>+<S>. Если файл не имеет имени, вам будет предложено назвать его. Для редактирования файла нажмите комбинации клавиш <Ctrl>+<X> <Ctrl>+<F>. Появится запрос на ввод имени файла. Если файл уже существует, его содержимое загрузится в emacs; иначе файл будет создан. Для сохранения файла и выхода из emacs служат комбинации клавиш <Ctrl>+<X> <Ctrl>+<C>. В табл. 2.29 описаны основные комбинации клавиш для работы с emacs. Таблица 2.29. Основные команды работы с редактором emacs Действие Ключевая последовательность Сохранить текущую работу <Ctrl>+<X> <Ctrl>+<S> Редактировать другой файл <Ctrl>+<X> <Ctrl>+<F> Сохранить работу и затем выйти из редактора <Ctrl>+<X> <Ctrl>+<C> Дополнительные команды Для того чтобы обновить экран, нажмите комбинацию клавиш <Ctrl>+<L>. Для перехода в режим autowrap, который автоматически производит перенос слов на новую строку, когда достигается ее конец, нажмите последовательно клавиши <Esc> <х>, а затем клавишу <Enter>. Для выхода из этого режима повторите действие. Для дополнительной информации Для получения дополнительных сведений о emacs рекомендуется обратиться к книге [Roberts 1991].
Электронная почта: mail/mailx Последний раздел этой главы посвящен использованию электронной почты UNIX. Будет полезным как можно быстрее научиться работать с электронной почтой, потому что это весьма эффективный способ задать вопрос о работе UNIX системному администратору или другим, более опытным пользователям. Существуют, как минимум, две наиболее популярные почтовые программы. Одна из них называется mail, а другая maiix. Для упоминания обеих будем пользоваться термином "почта". Программа mail имеет большое количество функций, но мы с вами остановимся только на основных, тех — которые будут вам наиболее полезны. За дополнительной информацией обращайтесь к встроенному справочнику. Синтаксис mail -Н [-f имяФайла] [1р_пользователя]* Утилита mail позволяет посылать и получать почту. Если указан список пользователей, то программа отправляет им данные, полученные со стандартного входа, после чего завершается. Имя пользователя может быть задано следующим образом: • локальное имя пользователя (т. е. login name — имя входа в систему); • интернет-адрес в форме name@hostname.domain; • имя файла; • почтовая группа. Об использовании интернет-адресов мы поговорим в главе 9. Почтовые группы очень коротко мы рассмотрим сейчас. Если имя пользователя не определено, утилита mail предполагает, что вы собираетесь читать почту из каталога. Каталог /var/mail/ <имяПолъзователя>, где <имяПользователя> — собственное имя пользователя, читается по умолчанию, хотя это действие можно отменить через опцию -f. Тогда mail выдает приглашение & и ждет команды. Опция -н выводит заголовки содержимого вашего почтового каталога, но не входит в режим команды. Список наиболее полезных параметров командного режима представлен ниже. При запуске утилита mail читает конфигурационный файл .mailrc из вашего домашнего каталога, в котором содержатся параметры настройки почтовой программы. Имя файла конфигурации может быть изменено
путем установки соответствующего значения переменной окружения mailrc. Переменные окружения обсуждаются в главе 4. У программы существует большое количество разнообразных настроек. Наиболее важная — способность определить почтовые группы (также иногда называемые псевдоименами). Это переменные, обозначающие группу пользователей. Для задания почтовой группы разместите строку вида group имя {Ю_пользователя}+ в файле запуска почты. Вы сможете использовать это имя в качестве псевдонима заданной группы пользователей. В табл. 2.30 дано описание основных команд работы с утилитой mail. Таблица 2.30. Команды работы с утилитой mail Команда Описание 7 Выводит справку сору [списокСообщений] [имяФайла] Копирует сообщения в имяФайла, не помечая их как сохраненные delete [списокСообщений] Удаляет списокСообщений из системного почтового каталога file [имяФайла] Читает почту из почтового ящика имяФайла. Если никакое имяФайла не определено, показывает имя текущего почтового ящика вместе с размером и числом сообщений, в нем находящихся headers [сообщение] Выводит на экран заголовки сообщений, которые содержать в себе сообщение mail {10_пользователя}+ Посылает почту перечисленным пользователям print [списокСообщений] Выводит перечисленные сообщения с использованием утилиты more quit Осуществляет выход из утилиты mail reply [списокСообщений] Передает ответ отправителям списокСообщений save [списокСообщений] [имяФайла] Сохраняет указанные сообщения в имяФайла. Если никакое имяФайла не задано, сохраняет их в файле mbox пользовательского домашнего каталога В табл. 2.31 дано описание команд, используемых для работы с сообщениями.
Таблица 2.31. Работа с сообщениями в mail Синтаксис Описание Текущее сообщение nn Сообщение по номеру пп ' Первое неудаленное сообщение $ Последнее сообщение * Все сообщения nn-mm Сообщения, пронумерованные от пп до mm включительно user Все сообщения от пользователя Для вызова большинства команд в mail можно использовать первый символ их названия (например, р вместо print). Отправка почты Самый простой способ отправить почту состоит в том, чтобы ввести сообщение непосредственно с клавиатуры и закончить ввод, нажав комбинацию клавиш <Ctrl>+<D>: $ mail tim ...послать почту локальному пользователю tim. Subject: Mail Test ...ввести тему почты. Hi Tim,, How is Amanda doing? with best regards from Graham AD ...закончить ввод; стандартный вход посылается как почта. $ _ Мы уже немного обсудили создание почтовых групп, позволяющих посылать почту входящим в них людям. Теперь рассмотрим это на примере: group music jeff richard kelly bev $ mail music ...послать почту каждому члену группы. Subject: Music Hi guys How about a jam sometime? with best regards from Graham. AD $ ...конец ввода.
Для набора больших сообщений целесообразно будет воспользоваться каким-нибудь текстовым редактором и сохранить письмо в файл, который потом переадресовать на вход программы mail: $ mail music < jam.txt $ ...послать jam.txt по почте. Чтобы послать почту пользователям Интернета, используйте стандартную адресную схему Интернета, описанную в главе 9. $ mail glass@utdallas.edu < mesg.txt ...послать файл. Чтение почты Присланная вам почта сохраняется в файле /var/mai\/< ИмяПользователя>, где <ИмяПолъзователя> — это имя, с которым пользователь регистрируется в системе. Для чтения почтового каталога введите mail с необязательным именем каталога. Если в настоящее время почты нет, система уведомит вас об этом: $ mail ...попытаться прочитать мою почту из каталога по умолчанию. No mail for glass $ _ Если почта присутствует, утилита mail покажет список почтовых заголовков и выведет приглашение &. Следует нажать клавишу <Enter>, чтобы прочитать по порядку все сообщения, а затем — клавишу <q> (от англ, quit) для выхода из утилиты mail. Адресованная пользователю почта автоматически сохраняется в каталоге mbox его домашнего каталога, и ее можно прочитать позднее, введя следующую команду: $ mail -f mbox . . .читать почту, сохраненную в каталоге mbox. При чтении писем на экране будет отображено примерно следующее: $ Is -1 /var/mail/glass ...посмотреть, есть ли почта. -rw------- 1 glass 7 58 May 2 14:32 /var/mail/glass $ mail ...читать почту из каталога по умолчанию. Mail version SMI 4.0 Thu Oct 11 12:59:09 PDT 1990 Type ? for help. "/var/mail/glass": 2 messages 2 unread >U 1 tim@utdallas.edu Sat May 2 14:32 11/382 Mail test U 2 tim@utdallas.edu Sat May 2 14:32 11/376 Another & <Enter> ...нажал клавишу <Enter> для чтения сообщения 1. From tim@utdallas.edu Sat Mar 14 14:32:33 1998
То: glass@utdallas.edu Subject: Mail test hi there & <Enter> ...нажал <Enter> клавишу для чтения сообщения 2. From tim@utdallas.edu Sat Mar 14 14:32:33 1998 To: glass@utdallas.edu Subject: Another hi there again & <Enter> ...нажал клавишу <Enter> для чтения следующего сообщения. At EOF ...нет ни одного! & q ...покинуть mail. Saved 2 messages in /home/glass/mbox $ _ Для просмотра заголовков сообщений пользуйтесь опцией -н: $ mail -Н ...взглянуть на мой почтовый каталог. >U 1 tim@utdallas.edu Sat Мау 2 14:32 11/382 Mail test U 2 tim@utdallas.edu Sat May 2 14:32 11/376 Another $ _ Чтобы ответить на сообщение после его прочтения, используйте опцию г (от англ, reply). Для сохранения его в файл служит опция s (от англ. save). Если список сообщений не определен, выводится первое сообщение. Например: & 15 ...читать сообщение 15. From ssmith@utdallas.edu Tue Mar 17 23:27:11 1998 То: glass@utdallas.edu Subject: Re: come to a party The SIGGRAPH party begins Thursday NIGHT at 10:00 PM!! Hope you don’t have to teach Thursday night. & г ...ответить ssmith. To: ssmith@utdallas.edu Subject: Re: come to a party Thanks for the invitation. - see you there ... конец ввода. & s ssmith.party ...сохранить сообщение от ssmith. "ssmith.party" [New file] 27/1097 & q ...выйти из mail. $
В некоторых почтовых программах опция г применяется для ответа отправителю, а опция r — для ответа всем, в других — наоборот. Поэтому будьте внимательны и обратитесь за более подробной информацией к справочной системе. Вероятно, вам потребуется удалять некоторые сообщения, для этого используйте опцию d (от англ, delete)'. & dl-15 ...удалить сообщения с 1 по 15 включительно. & d* ...удалить все оставшиеся сообщения. Связь с системным администратором Как правило, почтовый адрес системного администратора — root или, возможно, sysadmin. Проблемы, связанные с работой электронной почты, чаще всего решает человек с псевдонимом postmaster. Обзор главы Перечень тем В этой главе мы научились: □ регистрироваться в системе UNIX; □ выходить из системы; П изменять пароль; □ работать с командами shell; □ запускать утилиты; □ работать со встроенной справочной системой; □ пользоваться специальными метасимволами терминала; □ работать с файлами; □ пользоваться двумя текстовыми редакторами; □ правильно конфигурировать терминал; П посылать электронную почту. Контрольные вопросы 1. Как можно нарушить безопасность UNIX? 2. Каков лучший вид пароля? 3. Какую команду UNIX надо использовать, чтобы изменить имя или местоположение файла?
4. Является ли ОС UNIX чувствительной к регистру клавиатуры? 5. Назовите четыре общих команды shell. 6. Почему командный интерпретатор shell лучше приспособлен для решения некоторых задач, чем С-программы? 7. Какая последовательность клавиш завершает процесс? 8. Какую комбинацию клавиш необходимо нажать для указания на конец ввода текста с клавиатуры? 9. Как вы заканчиваете работу с shell? 10. Как называется текущее расположение shell? 11. Какие атрибуты имеет файл? 12. Каково назначение групп? 13. Какие флаги права доступа существуют у каталогов? 14. Кто может изменять принадлежность файла? Упражнения 2.1. Отправьте письмо знакомому, проживающему в другой стране. Как вы думаете, за какое время оно дойдет до адресата? [Уровень: легкий.] 2.2. Почему процесс принадлежит только одной группе, в то время как запустивший его человек может быть членом нескольких? [Уровень: средний.] 2.3. Спроектируйте механизм файловой безопасности, который сможет исключить потребность в необходимости идентификации через команду set 10_пользователя. [Уровень: высокий.] 2.4. Многие даже весьма тривиальные изобретения защищены патентами. Как вы считаете, насколько необходим этот механизм защиты интеллектуальной собственности? [Уровень: высокий.] Проект Отправьте почту системному администратору и создайте две новых группы непосредственно для себя. Поэкспериментируйте с утилитами, используемыми для работы с системой полномочий файла. [Уровень: легкий.]

Глава 3 Утилиты UNIX для опытных пользователей Мотивация В дополнение к общим утилитам работы с файлами в UNIX-системах имеется немало программ, обрабатывающих текст, архивирующих и сортирующих файлы, выполняющих мониторинг системы. В этой главе дано описание утилит, помогающих сделать работу пользователя с операционной системой удобной и эффективной. Предпосылки Для понимания данной главы необходимо предварительно ознакомиться с предыдущими двумя. Желательно также, чтобы на вашем компьютере была установлена ОС UNIX, и вы могли закрепить на практике изученный материал. Задачи В этой главе мы научимся использовать более 30 утилит, способных облегчить работу в UNIX любому пользователю. Изложение Для лучшего понимания информация в данной главе представлена в виде нескольких UNIX-сессий.
Утилиты Ниже приведен список утилит, которые будут рассмотрены в данной главе. at crypt gzip tar awk diff In time biff dump mount tr cmp egrep od ul compress fgrep perl umount cpio find sed uncompress cron grep sort uniq crontab gunzip su whoami Кроме того, мы познакомимся с основами языка программирования Perl, ставшего неотъемлемой частью большинства UNIX-систем. Утилиты, которые мы будем обсуждать, могут быть логически сгруппированы, как показано в табл. 3.1. Далее мы рассмотрим каждую из групп поподробнее. Таблица 3.1. Утилиты UNIX Категория Утилиты Фильтрация файлов egrep, fgrep, grep, uniq Сортировка файлов sort Сравнение файлов cmp, diff Архивирование файлов tar, cpio, dump Поиск файлов find Команды планирования at, cron, crontab Программируемая обработка текста awk, perl Жесткие и символические связи In Переключение пользователей su Проверка почты biff Трансформация файлов compress, crypt, gunzip, gzip, sed, tr, ul, uncompress Поиск необработанного содержимого файла od Монтирование файловых систем mount, umount
Таблица 3.1 (окончание) Категория Утилиты Идентификация командных интерпретаторов whoami Подготовка документов nroff, spell, style, troff Выбор времени выполнения команды time Фильтрация файлов Часто бывает необходимо выбрать из файла только строки, отвечающие определенным критериям. Перечисленные ниже утилиты осуществляют такую обработку: □ утилиты egrep, fgrep и grep отфильтровывают все строки, которые не содержат установленный шаблон; □ утилита uniq выбирает одинаковые смежные строки. Шаблоны фильтрации: egrep/fgrep/grep Утилиты egrep, fgrep И grep Сканируют файл И отфильтровывают Все СТрО-ки, которые не соответствуют установленному шаблону. Перечисленные утилиты очень похожи, разница состоит в виде шаблонов, с помощью которых осуществляется поиск. Синтаксис grep -hilnvw шаблон {имяФайла}* fgrep -hilnvwx строка {имяФайла}* egrep -hilnvw шаблон {имяФайла}* Утилита grep (от англ. Global или Get Regular Expression or Print) позволяет находить соответствия заданному шаблону в списке файлов. Если файлы не определены, то поиск совпадения с искомой строкой проводится в стандартном входном потоке. Шаблоном может быть набор символов либо регулярное выражение. Все строки, соответствующие шаблону, отображаются как стандартный вывод. Если определено больше одного файла, то каждой строке предшествует имя файла, в котором она найдена, если определена опция -h. Опция -п нумерует найденные строки. Опция -i заставляет игнорировать регистр шаблонов. Опция -1 ото- 5 Зак. 786
Сражает список файлов, которые содержат определенный шаблон. Опция -v выводит все строки, не соответствующие заданной модели. Опция -w ограничивает соответствие только целыми словами. Утилита fgrep (от англ, fixed grep) осуществляет быстрый поиск, который производится только в указанных строках. Опция -х производит поиск по строкам. Утилита egrep (от англ, extended grep) использует в качестве шаблона регулярные выражения. В Приложении содержится информация о регулярных выражениях. Для получения всех строк, совпадающих с шаблоном, необходимо указать строку и имя файла, в котором следует производить поиск, непосредственно за командой grep: $ cat grepfile ...вывести файл, который следует отфильтровать. Well you know it’s your bedtime, So turn off the light, Say all your prayers and then, Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, And you will be swimming there too. $ grep the grepfile ...искать слово "the". So turn off the light, Say all your prayers and then, Beautiful mermaids will swim through the sea, And you will be swimming there too. $ _ Обратите внимание, что слова, которые содержат строку "the", также удовлетворяют соответствующему условию. Поэтому для вывода целых слов, совпадающих с моделью поиска, следует воспользоваться опцией -w. Опция -п нумерует строки: $ grep -wn the grepfile ...более частный случай! 2:So turn off the light, 5:Beautiful mermaids will swim through the sea, $ _ Для отображения слов, не совпавших с шаблоном, воспользуйтесь опцией -v: $ grep -wnv the grep file ...противоположный фильтр. l:Well you know it’s your bedtime, 3:Say all your prayers and then,
4:Oh you sleepy young heads dream of wonderful things, 6:And you will be swimming there too. $ _ При одновременном поиске в нескольких файлах найденные строки предваряются именем документа. В приведенном ниже примере был осуществлен поиск символа х в нескольких файлах одновременно: $ grep -w х *.с ...искать во всех файлах, заканчивающихся на .с. а.с:test (int х) fact2.с:long'factorial (х) fact2.c:int х; fact2.c: if ((x == 1) ,, (x == 0)) fact2.c: result = x * factorial (x-1); $ grep -wl x *.c ...перечислить имена соответствующих файлов. а. с fact2. с $ Утилиты fgrep, grep И egrep ПОДДерЖИВаюТ ОПЦИИ, КОТОрые МЫ ТОЛЬКО ЧТО описали. Отличие состоит в модели поиска, которую можно использовать в каждой из них. В табл. 3.2 приведены шаблоны, поддерживаемые конкретной утилитой. Таблица 3.2. Шаблоны различных утилит сортировки Утилита Вид шаблона, который может разыскиваться fgrep Только установленная строка grep Регулярное выражение egrep Расширенное регулярное выражение Регулярные выражения, используемые С утилитами grep И egrep, должны быть заключены в одиночные кавычки для отделения от других команд shell. Для лучшего понимания использования регулярных выражений с grep, egrep рассмотрим их на примере приведенного ниже текста: Well you know it’s your bedtime, So turn off the light, Say all your prayers and then, Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, And you will be swimming there too.
Шаблоны соответствия В табл. 3.3 и 3.4 строки, соответствующие регулярному выражению, выделены курсивом. Таблица 3.3. Шаблоны в grep grep-шаблон Строки, которые соответствуют . nd Say all your prayers and then, Oh you sleepy young heads dream of wonderful things, And you will be swimming there too. А. nd And you will be swimming there too. sw.*ng And you will be swimming there too. [А-D] Beautiful mermaids will swim through the sea, And you will be swimming there too. \ , And you will be swimming there too. а. Say all your prayers and then, Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, а. $ Beautiful mermaids will swim through the sea, [a-m]nd Say all your prayers and then, [Аа-т]nd • Oh you sleepy young heads dream of wonderful things, And you will be swimming there too. Таблица 3.4. Шаблоны в едгер едгер-шаблон Строки, которые соответствуют s. *w Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, And you will be swimming there too. s. +w Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, off I will So turn off the light, Beautiful mermaids will swim through the sea, And you will be swimming there too.
Таблица 3.4 (окончание) egrep-шаблон Строки, которые соответствуют im*ing And you will be swimming there too im?ing Нет совпадений Удаление одинаковых строк: uniq Утилита uniq сообщает о наличии в файле одинаковых строк. Синтаксис uniq -с -число [входнойФайл [ выходнойФайл] ] Утилита uniq выводит входной файл со всеми смежными одинаковыми строками, сокращенными до единственного вхождения повторенной строки. Если входной файл не определен, то производится чтение из стандартного ввода. Опция -с добавляет перед каждой строкой число вхождений, которые были найдены. Если определено число, то заданное число полей каждой строки игнорируется. Пример: $ cat animals ...рассмотрим тестовый файл, cat snake monkey snake dolphin elephant dolphin elephant goat elephant pig pig pig pig monkey pig $ uniq animals ...отфильтровать двойные смежные строки, cat snake monkey snake dolphin elephant goat elephant pig pig monkey pig
$ uniq -с animals ...отобразить строки с нумерацией. 1 cat snake 1 monkey snake 2 dolphin elephant 1 goat elephant 2 pig pig 1 monkey pig $ uniq -1 animals ...проигнорировать первое поле каждой строки, cat snake dolphin elephant pig pig $ Сортировка файлов: sort Утилита sort сортирует файл в лексикографическом или обратном порядке строк на основе одного или нескольких полей сортировки. Синтаксис sort -tc -г {полеСортировки-bfМп} * {имяФайла}* Утилита sort сортирует строки, отвечающие некоторому критерию, в одном или нескольких файлах. По умолчанию строки сортируются в порядке возрастания1. Опция -г устанавливает сортировку по убыванию. Строки разбиты на поля, отделенные пробелами или позициями табуляции. Для определения иного разделителя используйте опцию -t. По умолчанию сортировка производится по всем полям строки. Однако ее можно ограничить одним или несколькими полями путем задания одной или нескольких опций после команды. Опция -f заставляет утилиту sort игнорировать регистр поля. Опция -м обеспечивает сортировку поля по месяцам. Опция -п сортирует поле в числовом порядке. Опция -ь игнорирует предшествующие пробелы. Некоторые поля упорядочиваются лексикографически, это означает, что соответствующие символы сравниваются на основе их ASCII-кодов. (См. man ascii для получения списка всех символов и их соответствующих кодов.) Например, заглавная буква "меньше", чем ее строчный эквивалент, а 1 То есть в лексикографическом порядке символов этих строк. — Ред.
пробел имеет более низкий приоритет, чем какой-либо символ. Ниже приведен пример сортировки по возрастанию и убыванию: $ cat sortfile ...вывести файл, который нужно отсортировать. jan Start chapter 3 10th Jan Start chapter 1 30th Jan Start chapter 5 23rd Jan End chapter 3 23rd Mar Start chapter 7 27 may End chapter 7 17th Apr End Chapter 5 1 Feb End chapter 1 14 $ sort sortfile ...сортировать его. Feb End chapter 1 14 Jan End chapter 3 23rd Jan Start chapter 5 23rd may End chapter 7 17th Apr End Chapter 5 1 Jan Start chapter 1 30th Mar Start chapter 7 27 jan Start chapter 3 10th $ sort -r sortfile ...сортировать в обратном порядке. jan Start chapter 3 10th Mar Start chapter 7 27 Jan Start chapter 1 30th Apr End Chapter 5 1 may End chapter 7 17th Jan Start chapter 5 23rd Jan End chapter 3 23rd Feb End chapter 1 14 $ _ Для сортировки по конкретному полю необходимо задать диапазон, определив после префикса + номер стартового поля и после - номер конечного поля. Нумерация полей начинается с 0. Если не задано конечное поле, то сортируются все поля, следующие за стартовым. Сортировка по первому полю: $ sort +0 -1 sortfile ...сортировать только по первому полю. Feb End chapter 1 14 Jan End chapter 3 23rd
Jan Start chapter 5 23rd may End chapter 7 17th Apr End Chapter 5 1 Jan Start chapter 1 30th Mar Start chapter 7 27 jan Start chapter 3 10th $ Обратите внимание, что предшествующие пробелы были интерпретированы как часть первого поля, что привело к странным результатам. Логично, чтобы месяцы были отсортированы в правильном порядке — январь перед февралем и т. д. Для этого воспользуемся опцией -м, которая такую сортировку выполняет, и опцией -ь, игнорирующей лишние пробелы. $ sort +0 -1 —ЬМ sortfile ...сортировать по полю месяц. * Jan End chapter 3 23rd Jan Start chapter 5 23rd Jan Start chapter 1 30th jan Start chapter 3 10th Feb End chapter 1 14 Mar Start chapter 7 27 Apr End Chapter 5 1 may End chapter 7 17th $ _ Теперь наш файл правильно отсортирован по месяцам, однако даты все еще не в порядке. Для решения этой проблемы нужно определить несколько полей сортировки, т. к. утилита выбирает строки, отвечающие первому из заданных условий, а потом применяет к этой выборке следующие условия. Поэтому чтобы отсортировать текст по месяцу и дате, он сначала должен быть отсортирован по первому полю, а затем по пятому. Кроме того, к пятому полю должна быть применена числовая сортировка (опция -п). $ sort +0 -1 -ЬМ +4 -п sortfile jan Start chapter 3 10th Jan End chapter 3 23rd Jan Start chapter 5 23th Jan Start chapter 1 30th Feb End chapter 1 14 Mar Start chapter 7 27 Apr End Chapter 5 1 may End chapter 7 17th $
Очень часто поля отделяются друг от друга с помощью символов, отличных от пробела. Например, в файле /etc/passwd поля, содержащие информацию о пользователе, отделяются двоеточием. Для определения альтернативного разделителя воспользуйтесь опцией -t. Ниже приведен пример, разделителем полей в котором был символ двоеточие (:): $ cat sortfile2 ...посмотреть на тестовый файл. jan:Start chapter 3:10th Jan:Start chapter 1:30th Jan .-Start chapter 5:23 rd Jan:End chapter 3:23rd Mar:Start chapter 7:27 may:End chapter 7:17th Apr:End Chapter 5:1 Feb:End chapter 1:14 $ sort -t; +0 -1 -bM +2 —n sortfile2 ...двоеточие, как разделитель. jan:Start chapter 3:10th Jan:End chapter 3:23rd Jan:Start chapter 5:23rd Jan:Start chapter 1:30th Feb:End chapter 1:14 Mar:Start chapter 7:27 Apr:End Chapter 5:1 may:End chapter 7:17th $ _ Рассмотренная утилита имеет немало других опций, обсуждать которые мы не будем. Для получения информации о них пользуйтесь утилитой man. Сравнение файлов Следующие две утилиты позволяют сравнивать содержимое двух файлов: □ утилита стр ищет до нахождения первого отличающегося байта двух файлов; □ утилита diff отображает все различные и сходные элементы файлов. Тестирование на сходство: стр Утилита стр сравнивает файлы. В приведенном ниже примере сравниваются файлы ladyl, lady2, lady3: $ cat ladyl Lady of the night, ...посмотреть на первый тестовый файл.
I hold you close to me, And all those loving words you say are right. $ cat lady2 ...посмотреть на второй тестовый файл. Lady of the night, I hold you close to me, And everything you say to me is right. $ cat lady3 ...посмотреть на третий тестовый файл. Lady of the night, I hold you close to me, And everything you say to me is right. It makes me feel, I’m so in love with you. Even in the dark I see your light. $ cmp ladyl lady2 ...файлы различаются. ladyl lady 2 differ: char 48, line 3 $ cmp lady2 lady3 ...файл 2 — префикс файла 3. cmp: EOF on lady 2 $ cmp lady3 lady3 ...файлы одинаковые. $ Синтаксис cmp -Is имяФайла! имяФайла2 [смещение!] [смещение2] Утилита стр проверяет два файла на соответствие. Если имяФайла! и имяФайла2 равны, то утилита стр возвращает 0 и ничего не выводит; иначе возвращает 1 и показывает номер строки, начиная с первого несовпадающего байта. Если один файл является префиксом другого, то выводится сообщение EOF для файла, который короче. Опция -1 отображает смещение и значения всех несовпадающих байтов. Опция -s блокирует любой вывод. Дополнительные значения смещение! и смещение2 определяют стартовые значения В имяФайла! И имяФайла2, С КОТОРЫХ ДОЛЖНО начаться сравнение. Пример выполнения команды стр с опцией -1: $ стр -1 ladyl lady2 48 141 145 49 154 166 ...показывает байты, которые не совпадают.
81 145 56 82 40 12 cmp: EOF on lady2 $ ...lady2 меньше, чем ladyl. Различие файлов: diff Утилита diff сравнивает два файла и выводит на экран различия между ними. Существует три вида изменений: добавление, изменение и удаление строки. Синтаксис diff -i -Офлаг имяФайла! имя Файл а 2 Утилита diff сравнивает два файла и выводит описание их различий. Опция -i заставляет утилиту diff игнорировать регистр строк. Опция -D генерирует вывод, предназначенный для С-препроцессора (см. ниже). Результат работы утилиты выводится в зависимости от различий файлов в следующем виде. • Для добавления строки начало1 а начало2,конец2 > строки из второго файла, которые необходимо добавить к первому. Здесь начало1 — стартовая строка первого файла, начало2 — стартовая строка второго файла, конец2 — последняя строка второго файла. • Для удаления строки начало!,конец! d номерСтроки < строки первого файла, которые требуется удалить. Здесь начало 1 —- начало первого файла, конец1 — конец первого файла, номерСтроки — номер строки, начиная с которой, происходит сравнение. • Для изменения строки начало1,конец! с начало,конец2 < строки первого файла, которые должны быть заменены. > строки из второго файла, которые нужно использовать для замены Здесь начало! — стартовая строка первого файла, конец1 — конечная строка первого файла, начало2 — стартовая строка второго файла, конец2 — конечная строка второго файла.
Рассмотрим пример: $ cat ladyl ...посмотреть первый тестовый файл. Lady of the night, I hold you close to me, And all those loving words you say are right. $ cat lady2 ...посмотреть второй тестовый файл. Lady of the night, I hold you close to me, And everything you say to me is right. $ cat lady3 ...посмотреть третий тестовый файл. Lady of the night, I hold you close to me, And everything you say to me is right. It makes me feel, I’m so in love with you. Even in the dark I see your light. $ cat lady4 ...посмотреть четвертый тестовый файл. Lady of the night, I’m so in love with you. Even in the dark I see your light. $ diff ladyl lady2 ...сравнить ladyl и lady2. 3c3 < And all those loving words you say are right. > And everything you say to me is right. $ diff lady2 lady3 ...сравнись lady2 и lady3. 3a4,6 > It makes me feel, > I’m so in love with you. > Even in the dark I see your light. $ diff lady3 lady4 ...сравнить lady3 и lady4. 2,4dl < I hold you close to me, < And everything you say to me is right. < It makes me feel, $ _ Опция -d утилиты diff полезна для соединения двух файлов и записи их в отдельный файл, содержащий директивы препроцессора С. Каждая версия
файла может быть восстановлена с помощью компилятора сс с подходящими опциями и макроопределениями. Например: $ diff -Dflag lady3 lady4 ...посмотреть вывод. Lady of the night, ttifndef flag ...директива препроцессора. I hold you close to me, And everything you say to me is right. It makes me feel, ttendif flag ...директива препроцессора. I’m so in love with you. Even in the dark I see your light. $ diff -Dflag lady2 lady4 > lady.diff ...сохранить вывод. $ cc ~P lady.diff ...вызвать препроцессор. $ cat lady.i ...посмотреть вывод. Lady of the night, I hold you close to me, And everything you say to me is right. $ cc -Dflag -P lady.diff ...посмотреть другую версию. $ cat lady.i ...посмотреть вывод. Lady of the night, I’m so in love with you. Even in the dark I see your light. $ Поиск файлов: find Утилита find имеет гораздо больше возможностей, чем простой поиск заданного файла. Она выполняет действия над группой файлов, удовлетворяющих определенным условиям. Например, вы можете использовать утилиту find, чтобы удалить все файлы, принадлежащие пользователю tim, которые не изменялись в течение трех дней. Работа и синтаксис этой команды подробно описана в табл. 3.5. Синтаксис find списокПути выражение Утилита find рекурсивно проходит через списокПути И применяет выражение к каждому файлу.
Таблица 3.5. Синтаксис утилиты find Выражение Описание -name образец Возвращаемое значение равно 1, если имя файла соответствует образцу, который может включать метасимволы shell *, [,] и ? -perm oct Истинно, если восьмеричное описание флагов полномочий файла точно равно oct -тип ch Истинно, если тип файла — ch (b — блок, с — символ, и т. д.) -user Ю_пользователя Истинно, если владелец файла — Ю_пользователя -group 10_группы Истинно, если группа файла — 10_труппы -atime количествоДней Истинно, если к файлу обращались в течение количествоДней -mtime количествоДней Истинно, если содержимое файла изменялось в течение количествоДней -ctime количествоДней Истинно, если содержимое файла было изменено в течение количествоДней или если любой из атрибутов файла был изменен -exec команда Истинно, если код возврата от выполнения команда — 0. Команда должна заканчиваться \;. Если вы определяете {}, как аргумент строки команды, он заменяется именем текущего файла -print Распечатывает имя текущего файла и возвращает истину -Is Показывает атрибуты текущего файла и возвращает 1 -cpio устройство Пишет текущий файл в формате cpio на устройство и возвращает 1 !выражение Возвращает логическое отрицание выражение выражение1 [ - а ] выражение2 Если выражение! ложно, возвращает 0, и величину выражение2 не выполняется. Если выражение! истинно, возвращает величину выражение2 выражение 1 [-о] выражение2 Если выражение! истинно, возвращает истину. Если выражение! ложно, возвращает величину выражение2 Рассмотрим примеры работы утилиты find: $ find . -name r*.c' -print ...распечатать исходные С-файлы ...из текущего каталога или любого ...из его подкаталогов.
./proj/fall.89/play.с ./proj/fall.89/referee.c ./proj/fall.89/player.c ./госк/guess.c ./госк/play.c ./rock/player.c ./rock/referee.c $ find. . -mtime 14 -Is ...вывести файлы, модифицированные ...в течение последних 14 дней. -rw-r—г— 1 glass cs 14151 May 1 16:58 ./stty.txt -rw-r—г— 1 glass cs 48 May 1 14:02 ./myFile.doc -rw-r—г— 1 glass cs 10 May 1 14:02 ./rain.doc -rw-r—г— 1 glass cs 14855 May 1 16:58 ./tset.txt -rw-r—г— 1 glass cs 47794 May 2 10:56 . /mail.txt $ find . -name '*.bak' -Is -exec rm {} \; ...вывести и затем удалить все файлы, ...которые заканчиваются на .bak. -rw-r—г— 1 glass cs 9 May 16 12:01 ./a.bak -rw-r—r— 1 glass cs 9 May 16 12:01 ./b.bak -rw-r—r— 1 glass cs 15630 Jan 26 00:14 ./s6/gosh.bak -rw-r—r— 1 glass cs 18481 Jan 26 12:59 .7s6/gosh2.bak $ find . \f -name '*.c' -o -name txt' \) -print ...напечатать имена всех файлов, которые ...заканчиваются на .с или .txt. ./proj/fall.89/play.с ./proj/fall.89/referee.c ./proj/fall.89/player.c ./rock/guess.c ./rock/play.c ./rock/player.c ./rock/referee.c ./stty.txt ./tset.txt ./mail.txt $ Архивы Существует, как минимум, несколько причин, по которым необходимо сохранять файлы на дискеты или ленты: □ для ежедневного, еженедельного или ежемесячного резервирования (backup):
□ для переноса между несоединенными сетью UNIX-системами; П для будущих поколений. В UNIX существуют три утилиты, позволяющие архивировать файлы, каждая из которых имеет собственные сильные и слабые стороны. П Утилита cpio позволяет сохранять структуру каталога в одинарный резервный том. Удобна для сохранения небольших объемов данных, т. к. создание единственного тома делает его бесполезным для архивации больших массивов данных. □ Утилита tar предоставляет возможность сохранять структуры каталогов в одинарный резервный том. Специально разработана для сохранения файлов на ленту, поскольку она всегда архивирует файлы в конец носителя данных. Недостатком этой утилиты также является ограничение на создание одного тома. П Утилита dump обеспечивает сохранение файловой системы во множественные резервные тома. Специально разработана для выполнения полных и инкрементных резервных копирований, но восстановление отдельных файлов из архива с ее помощью достаточно трудоемко. Примечание Во многих системах, основанных на версии System V UNIX, существует программа uf sdump, эквивалентная утилите dump. Копирование файлов: cpio Утилита cpio позволяет создавать и восстанавливать из архивов файлы специального cpio-формата. Она очень эффективна для создания небольших архивов. К сожалению, утилита cpio не может разбивать файлы на несколько томов, поэтому весь архив должен помещаться на носитель данных. Если ЭТО НевОЗМОЖНО, ВОСПОЛЬЗуЙТеСЬ УТИЛИТОЙ dump. Синтаксис cpio -ov cpio -idtu шаблоны cpio -pl каталог Утилита cpio позволяет архивировать файлы. Опция -о берет список имен файлов со стандартного входа и создает файл формата cpio, который содержит резервную копию файлов с этими именами. Опция -v предписывает выводить имя каждого файла после копирования. Опция -i
читает файл cpio-формата из стандартного входа и воссоздает все файлы из входного канала, имена которых соответствуют указанному образцу. По умолчанию старые файлы не переписываются вместо более новых. Опция -и вызывает безоговорочное копирование. Опция -d создает каталоги, если они необходимы в течение процесса копирования. Опция -t показывает оглавление вместо выполнения копирования. Опция -р берет список имен файлов со стандартного входа и копирует содержимое файлов в указанный каталог. Данная опция полезна для копирования подкаталогов в другое место, хотя в большинстве случаев проще это сделать воспользовавшись утилитой ср с опцией -г. Опция -1 создает связи вместо создания физических копий. Чтобы продемонстрировать опции -о и -i, была создана резервная версия всех исходных С-файлов в рабочем каталоге и удалены исходные файлы, а затем восстановлены из архива. Действия, необходимые для выполнения этой задачи: $ Is -1 *.с ...вывести файлы, которые должны быть сохранены. -rw-r—r— 1 glass 172 Jan 5 19:44 mainl.c -rw-r—r— 1 glass 198 Jan 5 19:44 main2.c -rw-r—r— 1 glass 224 Jan 5 19:44 paliridrome.c -rw-r—r— 1 glass 266 Jan 5 23:46 reverse.c $ Is * .C / cpio -ov > backup ...сохранить в backup. 'ma ini.с main2.с palindrome.с reverse.с 3 blocks $ Is -1 backup ...просмотреть backup. -rw-r—г— 1 glass 1536 Jan 9 18:34 backup $ rm * .c ...удалить исходные файлы. $ cpio -it < backup ...восстановить файлы, mainl.c main2.c palindrome.c reverse.c 3 blocks $ Is -1 *.c ...подтвердить их восстановление, -rw-r—г— 1 glass 172 Jan 5 19:44 mainl.c -rw-r—r— 1 glass 198 Jan 5 19:44 main2.c
-rw-r—г— 1 glass 224 Jan 5 19:44 palindrome.с -rw-r—г— 1 glass 266 Jan 5 23:46 reverse.c $ _ Чтобы создать резервную копию всех файлов, включая подкаталоги, которые соответствуют образцу *.с, используют выход утилиты find как вход для утилиты cpio. Опция -depth утилиты find рекурсивно ищет соответствие шаблонам. В представленном ниже примере обратите внимание на символ обратной косой черты перед * в аргументе опции name. Это сделано для того, чтобы он не был воспринят shell как символ расширения: $ find. . -name \*.с -depth -print / cpio -ov > backup2 ma ini. c main2. c palindrome.c reverse.c tmp/b.c tmp/a. c 3 blocks $ rm -г *.c ...удалить исходные файлы. $ rm tmp/*.c ...удалить файлы нижнего уровня. $ cpio -it < backup2 . . .восстановить файлы. maini.с main2.с palindrome.с reverse.с tmp/b.с tmp/a. с 3 blocks $ _ Чтобы ПОНЯТЬ работу ОПЦИИ -р, воспользуемся УТИЛИТОЙ find для получения списка всех файлов текущего каталога, которые были изменены в последние два дня, и затем скопируем эти файлы в родительский каталог. Без использования опции -1 файлы будут физически скопированы, что увеличит объем занятого дискового пространства на 153 блока. Однако использование опции -1 создало ссылки на файлы и никак не отразилось на размере свободного пространства. Рассмотрим пример: $ find . -mtime -2 -print / cpio -p.........скопировать 153 блоков. $ Is -1 ../reverse.с ...посмотреть скопированные файлы. -rw-r—г— 1 glass 266 Jan 9 18:42 ../reverse.с $ find . -mtime -2 -print / cpio -pl........связать 0 блоков.
$ Is -1 ../reverse.с ...посмотреть связанные файлы. -rw-r—г— 2 glass 266 Jan 7 15:26 ../reverse.с $ _ Архивация на ленту: tar Утилита tar была специально разработана для создания архивов файлов на магнитной ленте. При добавлении файла к файлу архива с использованием утилиты tar файл всегда помещается в конце файла архива, поскольку нет возможности изменять середину файла, сохраненного на ленте. Если вы не архивируете файлы на ленту, лучше вместо утилиты tar использовать утилиту cpio. Синтаксис tar -cfrtuvx [Ёаг_имяФайла] списокФашюв Утилита tar позволяет создавать и получать доступ к специальным файлам архива в формате tar. Опция -с создает файл tar-формата. По умолчанию имя файла tar-формата — /dev/rmtO. (Оно может измениться в различных версиях UNIX.) Однако значение по умолчанию разрешено переопределить путем установки переменной окружающей среды $таре или с помощью опции -f, сопровождаемой необходимым именем файла. Опция -v вызывает многословный вывод. Опция -х позволяет извлекать указанные файлы, а опция -t генерирует оглавление. Опция -г безусловно добавляет перечисленные файлы к файлу архива. Опция —и добавляет последние версии имеющихся в архиве файлов. Если в аргументе списокФайлов указаны имена каталогов, содержимое каталогов добавляется или извлекается рекурсивно. В следующем примере показано, что все файлы из текущего каталога сохраняются в файле архива tarfile: $ Is ...просмотреть текущий каталог. mainl* main2 palindrome.с reverse.h mainl. c main2.c palindrome.h tarfile mainl.make main2.make reverse.c tmp/ $ Is tmp ...посмотреть каталог tmp. a.c b.c $ tar -cvf tarfile . ...архивировать текущий каталог. a ./mainl.с 1 blocks
а ./main2.с 1 blocks а ./main2 48 blocks a ./tmp/b.c 1 blocks а ./tmp/а.с 1 blocks $ Is -1 tarfile ...посмотреть архивный файл tarfile. -rw-r—г— 1 glass 65536 Jan 10 12:44 tarfile $ _ Чтобы получить оглавление tar-архива, используйте опцию -t, как показано в следующем примере: $ tar -tvf tarfile ...посмотреть оглавление. rwxr-xr-x 496/62 0 Jan 10 12:44 1998 , / rw-r—r— 496/62 172 Jan 10 12:41 1998 ./mainl.c rw-r—r— 496/62 198 Jan 9 18:36 1998 ./main2.c rw-r—r— 496/62 24576 Jan 7 15:26 1998 . /main2 rwxr-xr-x 496/62 0 Jan 10 12:42 1998 ./tmp/ rw-r—r— 496/62 9 Jan 10 12:42 1998 ./tmp/b.c rw-r—r— 496/62 9 Jan 10 12:42 1998 ./tmp/а.c $ _ Для безусловного добавления файла в конец tar-архива используйте опцию -г, сопровождаемую списком файлов или каталогов. Заметьте, что в следующем примере tar-архив содержит в конце две копии reverse.c: $ tar -rvf tarfile reverse.c a reverse.c 1 blocks ...безусловное добавление. $ tar -tvf tarfile ...посмотреть оглавление. rwxr-xr-x 496/62 0 Jan 10 12:44 1998 , / rw-r—r— 496/62 172 Jan 10 12:41 1998 ./mainl.с rw-r—r— 496/62 266 Jan 9 18:36 1998 ./reverse.c rw-r—r— 496/62 266 Jan 10 12:46 1998 reverse.с $ _ Чтобы добавить файл только тогда, когда он отсутствует в архиве или был изменен со времени последнего архивирования, используйте опцию -и вместо -г. В следующем примере обратите внимание, что файл reverse.c не был заархивирован, потому что не изменялся: $ tar -rvf tarfile reverse.c ...безусловное добавление. a reverse.c 1 bloc.ks $ tar -uvf tarfile reverse.c ...условное добавление. $
Чтобы извлечь файл из файла архива, используйте опцию -х, сопровождаемую списком файлов или каталогов. Если имя каталога определено, он рекурсивно извлекается, как показано в следующем примере: $ rm tmp/* ...удалить все файлы из tmp. $ tar -vxf tarfile ./tmp ...извлечь архивированные tmp-файлы. x ./tmp/b.с, 9 bytes, 1 tape blocks x ./tmp/а.c, 9 bytes, 1 tape blocks $ Is tmp ...подтвердить восстановление. a. c b. с $ _ К сожалению, утилита tar не поддерживает соответствие шаблону списка имен. Таким образом, чтобы извлечь файлы, которые соответствуют конкретному шаблону, используют утилиту grep, как часть командной последовательности: $ tar -xvf tarfile 'tar -tf tarfile / grep x ./mainl.c, 172 bytes, 1 tape blocks x ./main2.c, 198 bytes, 1 tape blocks x ./palindrome.c, 224 bytes, 1 tape blocks x ./reverse.c, 266 bytes, 1 tape blocks x ./tmp/b.c, 9 bytes, 1 tape blocks x ./tmp/а.c, 9 bytes, 1 tape blocks $ Если вы перемещаетесь в другой каталог, а затем извлекаете файлы, которые были сохранены, используя относительные имена пути, имена интерпретируются относительно текущего каталога. В следующем примере восстанавливается файл reverse.c из предварительно созданного tar-файла в новый каталог tmp2: $ mkdir tmp2 ...создать новый каталог. $ cd tmp2 ...переместиться туда. $ tar -vxf ../tarfile reverse.c ...восстановить один файл. х reverse.c, 266 bytes, 1 tape blocks x reverse.c, 266 bytes, 1 tape blocks $ is -1 . ..подтвердить восстановление, total 1 -rw-r—r— 1 glass 266 Jan 10 12:48 reverse.c $ Обратите внимание, что каждая копия файла reverse.c записалась, поверх предыдущей, поэтому самая последняя версия была оставлена неповрежденной.
Инкрементальные резервные копирования: dump и restore Утилиты dump и restore пришли из версии Berkeley UNIX, но были добавлены в большинство других версий. (Во многих системах, базирующихся на версии System V UNIX, они называются ufsdump и ufsrestore.) Вот типичная стратегия системного администратора для резервного копирования: □ выполнять еженедельное резервное копирование всей файловой системы; □ выполнять ежедневное инкрементное резервное копирование, сохраняя только те файлы, которые были изменены со времени последнего резервного копирования. Этот вид резервной стратегии точно поддержан утилитами dump и restore. Синтаксис dump [уровень] [f дампФайл] [v] [w] файловаяСистема dump [уровень] [f дампФайл] [v] [w] {имяФайла} + Утилита dump имеет две формы. Первая форма копирует файлы из указанной файловой системы в дампФаил, которым по умолчанию является /dev/rmtO. (Имя может изменяться в различных версиях UNIX.) Если параметр уровень определен как п, то копируются все файлы с более низким уровнем, чем л, при условии, что они были изменены после последнего дампа. Например, уровень дампа 0 всегда обеспечивает выполнение дампа всех файлов, тогда как уровень дампа 2 выполнит дамп всех измененных файлов, начиная с последнего дампа уровня 0 или уровня 1. Если никакой уровень дампа не определен, уровень дампа равен 9. Опция v предписывает утилите dump проверять каждый том носителя после того, как он записан. Опция w обеспечивает демонстрацию списка всех файловых систем, над которыми должен быть выполнен дамп вместо выполнения резервного копирования. Вторая форма утилиты dump позволяет задать имена файлов, которые нужно сохранить. Обе формы напоминают пользователю, когда необходимо вставить или удалить носитель дампа. Например, большой дамп системы на ленточное устройство часто требует, чтобы оператор удалил заполненную ленту и заменил ее пустой. Когда дамп выполнен, информация о нем записывается в файл /etc/dumpdates для использования в будущем при вызове утилиты dump.
Приведем пример, который выполняет дамп с проверкой уровня 0 файловой системы /dev/daO на ленточный накопитель /dev/rmtO: $ dump 0 fv /dev/rmtO /dev/daO Утилита restore позволяет восстанавливать файлы из резервной копии, выполненной утилитой dump. Синтаксис restore -irtx [f дампФайл] {имяФайла}* Утилита restore позволяет восстанавливать набор файлов из предыдущего дампФайла. Если дампФайл не определен, по умолчанию используется /dev/rtmO. (Имя может быть иным в других версиях UNIX.) Опция -г заставляет каждый файл из дампФайла восстанавливаться в текущий каталог, поэтому используйте эту опцию осмотрительно. Опция -t вместо восстановления файлов осуществляет ВЫВОД оглавления дампФайла. Опции -х служит для восстановления только файлов с указанными имена-миФайлов ИЗ дампФайла. ЕСЛИ имяФайла — ЭТО ИМЯ Каталога, еГО СОДерЖИ-мое восстанавливается рекурсивно. Опция -i предписывает утилите restore читать оглавление дампФайла и затем входить в диалоговый режим, который позволяет пользователю выбирать файлы для восстановления. Для получения дополнительной информации об этом диалоговом режиме обращайтесь за справкой — man restore. В следующем примере используется утилита restore, чтобы извлечь пару предварительно сохраненных файлов с дампа устройства /dev/rmtO: $ restore -х f /dev/rmtO wine.с hacking.с Планирование выполнения команд Представленные ниже две утилиты позволяют планировать команды, которые будут выполнены в более позднее время. □ Утилита crontab позволяет создать расписание, описывающее ряд работ, которые будут выполняться периодически. □ Утилита at предоставляет возможность планировать работы, которые будут выполнены однократно.
Периодическое выполнение: cron/crontab Утилита crontab позволяет запланировать ряд работ, которые будут выполняться периодически. Синтаксис crontab сгопЬаЬИмя crontab -ler [имяПользователя] Утилита crontab обеспечивает пользовательский интерфейс в системе UNIX. Когда она указывается без опций, crontab-файл cront-аьимя регистрируется, и его команды выполняются согласно указанным правилам выбора времени. Опция -1 выводит содержимое зарегистрированного crontab-файла. Опция -е редактирует и затем регистрирует crontab-файл. Опция -г отменяет регистрацию зарегистрированного crontab-файла. Опции -1, -е и -г может использовать привилегированный пользователь (root), чтобы получить доступ к crontab-файлу другого пользователя, добавляя имя пользователя как дополнительный аргумент. Содержимое crontab-файла описан ниже. Итак, ЧТОбы ИСПОЛЬЗОВаТЬ УТИЛИТУ crontab, вы должны подготовить входной crontab-файл, который содержит строки в формате: Минута Час День Месяц День_недели Команда Таблица 3.6. Значения полей для входного файла утилиты cron tab Поле Допустимое значение Минута 0-59 Час 0-23 День 1-31 Месяц 1-12 День_недели 1—7 (1 — понедельник, 2 — вторник, 3 — среда, 4 — четверг, 5 — пятница, 6 — суббота, 7 — воскресенье) Команда Любая команда UNIX Всякий раз, когда текущее время совпадает с описанным в строке, shell, указанный в переменной окружающей среды shell, выполняет ассоциированную команду. Если эта переменная не установлена, то используется
Bourne shell. Если любое из первых пяти полей содержит звездочку (*) вместо числа, то значение этих полей считается произвольным. Стандартный вывод команды автоматически посылается пользователю через утилиту mail. Любые символы после символа % копируются во временный файл и используются как стандартный вход команды. Ниже представлен образец crontab-файла с именем crontab.cron, который создан в пользовательском домашнем каталоге: $ cat cron tab. cron 0 8**1 Jr ★ Jr ★ Jr 30 14 1 * 1 $ ...вывод crontab-файла. echo Радостного утра понедельника echo Одна минута прошла > /dev/ttyl mail users % Jan Meeting At 3pm Первая строка отправляет сообщение "Радостного утра понедельника" в 8:00 каждый понедельник. Следующая строка выводит каждую минуту строку "Одна минута прошла" на устройство /dev/ttyl, которое играет роль терминала. Последняя строка посылает почту всем пользователям 1 января в 14:30, чтобы напомнить им о предстоящей встрече. Отдельный процесс, называемый cron, ответствен за своевременное выполнение команд в зарегистрированных crontab-файлах. Процесс запускается, когда UNIX-система загружается, и не останавливается, пока с ОС UNIX не будет завершена работа. Копии всех зарегистрированных crontab-файлов сохраняются в каталоге /var/spool/cron/crontabs. Чтобы зарегистрировать crontab-файл, используйте утилиту cron tab с именем crontab-файла в качестве единственного аргумента: $ crontab cron tab.cron ...зарегистрировать crontab файл. $ _ Если у вас уже есть зарегистрированный crontab-файл, новый регистрируется вместо старого. Чтобы просмотреть содержимое вашего зарегистрированного crontab-файла, используйте опцию -1. Чтобы посмотреть чей-нибудь еще зарегистрированный crontab-файл, добавьте имя пользователя в качестве аргумента. Эту опцию может использовать только привилегированный пользователь (root). В представленном ниже примере один из предварительно зарегистрированных crontab-файлов был вызван по совпадению времени после запуска ути- ЛИТЫ crontab: $ crontab -1 ...вывод содержимого текущего crontab-файла. 08**1 echo Радостного утра понедельника * * * * * echo Одна минута прошла > /dev/ttyl 30 14 1 * 1 mail users % Jan Meeting At 3pm
$ Одна минута прошла ...вывод одной из crontab-команд. $ _ Для редактирования crontab-файла и его сохранения используют опцию -е. Чтобы отменить регистрацию crontab-файла, используйте опцию -г: $ crontab -г ... отменить регистрацию crontab-файла. $ _ Привилегированный пользователь (root) может создавать файлы cron.allow и cron.deny в каталоге /var/spool/cron, чтобы разрешить и запретить индивидуальным пользователям применять crontab-средства. Каждый файл состоит из списка пользовательских имен: один пользователь на одной строке. Если этих файлов нет, то только привилегированный пользователь может запускать утилиту crontab. Если файл cron.deny пуст, а файл cron.allow не существует, все пользователи могут работать с утилитой crontab. Однократное выполнение: at Утилита at позволяет запланировать однократные команды или скрипты. Синтаксис at -csm время [дата [, под]][+ приращение] [скрипт] at -г {ID_задания} + at -1 {ID—задания} ★ Утилита at позволяет запланировать однократные команды или скрипты. Она поддерживает гибкий формат для задания времени. Опции -с и -s указывают, что команды будут выполняться соответственно С shell или Bourne shell. Опция -m предписывает утилите at послать вам почту, когда задание будет закончено. Если имя скрипта не определено, утилита at воспринимает список команд со стандартного входа. Опция -г удаляет указанные задания из очереди, а опция -1 выводит список заданий, находящихся в списке. Задание удаляется из очереди после ее выполнения. Аргумет время имеет формат нн или ннмм с необязательным спецификатором до полудня — дм, после полудня — рм, а дата записывается с указанием первых трех символов дня или месяца. Ключевое слово now (сейчас) может применяться вместо задания времени. Ключевые слова today (сегодня) и tomorrow (завтра) могут использоваться вместо даты. Если дата не определена, то вступают в силу следующие правила: • если время больше текущего времени, то предполагается дата today; • если время установлено меньше текущего времени, то предполагается дата — tomorrow.
Заявленное время может быть увеличено приращением — числом, следующим за аргументами время, дата, год. Скрипт выполняется командным интерпретатором shell, указанным в переменной окружающей среды shell, или Bourne shell, если эта переменная не установлена. Весь стандартный вывод от скрипта отправляется по почте пользователю. В следующем примере предусмотрено выполнение скрипта для передачи сообщения на терминал /dev/ttyl: $ cat at.csh ...посмотреть скрипт, который будет выполняться. #! /bin/csh echo at done > /dev/ttyl ...echo-вывод на терминал. $ date ...посмотреть текущее время. Sat Jan 10 17:27:42 CST 1998 $ at now + 2 minutes at.csh ...включить в расписание скрипт ...для выполнения через 2 минуты job 2519 at Sat Jan 10 17:30:00 1998 $ at -1 ...посмотреть расписание. 2519 a Sat Jan 10 17:30:00 1998 $ _ at done ...вывод выполненного скрипта. $ at 17:35 at.csh ...снова внести скрипт в расписание, job 2520 at Sat Jan 10 17:35:00 1998 $ at -г 2520 ...исключить из расписания. $ at -1 ...посмотреть расписание. $ _ Приведем несколько примеров правильных форматов времени для утилиты at: 0934am Sep 18 at.csh 9:34 Sep 18 , 1994 at.csh 11:00pm tomorrow at.csh now + 1 day at.csh 9pm Jan 13 at.csh 10pm Wed at.csh Если имя команды пропускается, утилита at выводит подсказку и ждет список команд, которые должны быть введены со стандартного входа. Для того чтобы закончить список команд, нажмите комбинацию клавиш <Ctrl>+<D>. Пример: $ at 8pm ...ввод команд для внесения в расписание с клавиатуры. at> echo at done > /dev/ttypl
at> ... конец ввода. job 2530 at Sat Jan 10 17:35:00 1998 $ _ Вы можете запрограммировать скрипт, чтобы он сам себя перепланировал путем вызова утилиты at внутри скрипта: $ cat at.csh ...скрипт, который перепланирует сам себя. #! /bin/csh date > /dev/ttyl # Reschedule script at now + 2 minutes at.csh $ _ Привилегированный пользователь (root) может создавать файлы at.allo и at.deny в каталоге /var/spool/cron, чтобы разрешить и запретить индивидуальным пользователем применять at-средства. Каждый файл состоит из списка пользовательских имен: один пользователь на одной строке. Если этих файлов нет, то только привилегированный пользователь может запускать утилиту at. Если файл at.den пуст, а файл at.allow не существует, все пользователи могут работать с утилитой at. Программируемая обработка текста: awk Утилита awk просматривает один или несколько файлов и исполняет действие на всех строках, которые соответствуют конкретному условию. Действия и условия описаны в программе awk и расположены от простого к сложному. Утилита awk получила свое имя от объединения первых символов фамилий его авторов: Ахо (Aho), Венбергер (Weinberger) и Керниган (Kernighan). Она заимствует свои структуры управления и синтаксис выражений из языка С. Если вы уже знаете С, то изучение awk не вызовет затруднений. awk — это комплексная утилита, настолько всеобъемлющая, что фактически существует книга, посвященная ей! Поэтому в данной главе описаны лишь главные особенности и опции awk. Однако хочется надеяться, что приведенный в этом разделе материал позволит вам писать большое количество полезных awk-приложений. Синтаксис awk -Fc [-f имяФайла] программа {переменная=значение} * {имяФайла}* awk — программируемая утилита обработки текста, которая просматривает строки своего входа и исполняет действия на каждой строке, соответст
вующей определенному критерию. Программа awk может быть включена в командную строку. В этом случае она должна быть окружена апострофами. Альтернативно она может быть задана с использованием опции -f, а ее вызов сохранен в файле. Начальные значения переменных могут быть заданы в командной строке. По умолчанию разделители полей — это символы табуляции и пробел. Чтобы переопределить значения по умолчанию, используйте опцию -г, сопровождаемую новым разделителем полей. Если имяФайла не определено, утилита awk читает со стандартного входа. awk-программы awk-программа может быть задана в командной строке. Но более общий способ состоит в том, чтобы поместить ее вызов в текстовый файл и специфицировать файл, используя опцию -г. Если вы решили разместить awk-программу в командной строке, окружите ее апострофами. Когда утилита awk читает строку, она разбивает ее на поля, которые разделены символами табуляции или пробелами. Разделитель полей может быть переопределен путем использования опции -f, как вы увидите позже в этом разделе, awk-программа — это список из одной или нескольких команд в формате [ условие ] [ \ {действие\} ] где условие — это: □ либо специальная лексема begin или end; П либо выражение, включающее любую комбинацию логических операторов, операторов отношений и регулярных выражений, а действие — это список из одного или большего количества С-подобных операторов, заканчивающихся точкой с запятой и имеющих следующий вид: П if (условие) оператор [else оператор] □ while (условие) оператор Л for (выражение; условие; выражение) оператор П break О continue П переменная=выражение О print [список выражений] [> выражение] П printf формат [,список выражений] [> выражение] □ next (перескакивает оставшиеся шаблоны на текущей строке ввода) П exit (перескакивает остаток текущей строки) П {список операторов}
Действие выполняется для строк, которые соответствует условию. Если условие отсутствует, действие выполняется на каждой строке. Если действие отсутствует, то все строки, соответствующие условию, просто посылаются на стандартный вывод. Операторы в awk-программе могут иметь отступ и быть отформатированы с использованием пробелов, символов табуляции и перевода строки. Доступ к отдельным полям К первому полю текущей строки можно обращаться, указав $1, второй $2, и т. д. $о обозначает строку целиком. Встроенная переменная nf равна числу полей в текущей строке. В следующем примере выполняется простая awk-программа на текстовом файле float, чтобы вставить число полей в каждую строку: $ cat float ...посмотреть оригинальный файл. Wish I was floating in blue across the sky, My imagination is strong, And I often visit the days When everything seemed so clear. Now I wonder what I’m doing here at all... $ awk '{print NF, $0}' float ...выполнить команду. 9 Wish I was floating in blue across the sky, 4 My imagination is strong, 6 And I often visit the days 5 When everything seemed so clear. 9 Now I wonder what I’m doing here at all... $ В EG IN и END Специальная конструкция begin вызывается прежде, чем читается первая строка, а специальная конструкция end — после того, как последняя строка была прочитана. Когда выражения перечисляются в операторе печати, между ними не выводятся пробелы, а по умолчанию печатается символ перевода строки. Встроенная переменная filename равна имени входного файла. В следующем примере представлено управление программой, которая выводила первое, третье и последнее поля каждой строки: $ cat awk2 ...посмотреть скрипт awk. BEGIN { print "Start of file:’’, FILENAME } { print $1 $3 $NF } ...печатать 1-е, 3-е и последнее поля. END { print "End of file" }
$ awk -f awk float ...выполнить скрипт. Start of file: float Wishwassky, Myisstrong, Andof tendays Whenseemedclear. Nowwonderall... End of file $ Операторы Когда в операторе печати между выражениями помещаются запятые, пробел печатается. Все обычные операторы С доступны в awk. Встроенная переменная nr содержит номер текущей строки. В следующем примере должна выполняться программа, которая выводит первые, третьи и последние поля строк 2 и 3 из файла float: $ cat awk3 ...посмотреть скрипт awk. NR > 1 && NR < 4 { print NR, $1, $3, $NF } $ awk -f awk3 float ...выполнить скрипт. My is strong, And often days $ Переменные Утилита awk поддерживает определенные пользователем переменные. Нет необходимости объявлять переменную. Ее начальное значение — это пустая строка или ноль, в зависимости от того, как вы используете переменную. В представленном ниже примере программа должна подсчитывать число строк и слов в файле по мере того, как она будет выводить строки на стандартный вывод: $ cat awk4 ...посмотреть скрипт awk. BEGIN { print "Scanning file" } printf "line %d: %s\n", NR, $0; lineCount++; wordCount += NF; END { printf "lines = %d, words = %d\n", lineCount, wordCount }
$ awk -f awk4 float ...выполнить скрипт. Scanning file line 1: Wish I was floating in blue across the sky, line 2: My imagination is strong, line 3: And I often visit the days line 4: When everything seemed so clear. line 5: Now I wonder what I’m doing here at all... lines = 5, words = 33 $ Структуры управления Утилита awk поддерживает большинство стандартных структур управления С. В следующем примере печатаются поля каждой строки в обратном порядке: $ cat awk5 ...посмотреть скрипт awk. { for (i = NF; i >= 1; i—) printf "%s ", $i; printf "\n"; } $ awk -f awk5 float ...выполнить скрипт, sky, the across blue in floating was I Wish strong, is imagination My days the visit often I And clear, so seemed everything When all... at here doing I’m what wonder I Now $ Расширенные регулярные выражения Условия для соответствия строкам могут быть расширенными регулярными выражениями, которые определяются в Приложении. Допустимые выражения должны быть помещены между косыми чертами (/). В следующем примере происходит вывод всех строк, которые содержат строки, начинающиеся с буквы t и заканчивающиеся буквой е, с любым числом символов между ними (для ясности последовательность символов выходных строк, которые удовлетворили условию, выделена курсивом): $ cat awk6 /t.*e/ { print $0 } ...посмотреть скрипт.
$ awk -f awk6 float ...выполнить скрипт. Wish I was floating in blue across the sky, And I often visit the days When everything seemed so clear. Now I wonder what I'm doing here at all... $ _ Цепочки условий Условием могут быть два выражения, разделенные запятой. В этом случае awk исполняет действие на каждой строке от первой строки, которая соответствует первому условию, до строки, удовлетворяющей второму условию, как показано в следующем примере: $ cat awk7 ...посмотреть скрипт. /strong/ , /clear/ { print $0 } $ awk -f awk7 float ...выполнить скрипт. My imagination is strong, And I often visit the days When everything seemed so clear. $ _ Разделители полей Если разделители полей — не пробелы, используют опцию -F, чтобы задать символ-разделитель. В следующем примере обрабатывается файл, поля которого разделялись двоеточиями: $ cat awk3 ...посмотреть скрипт awk. NR > 1 && NR < 4 {print $1, $3, $NF} $ cat float2 ...посмотреть входной файл. Wish:I:was:floating:in:blue:across:the:sky, My: imagination:is:strong, And:I:often:visit:the:days When:everything:seemed:so:clear. Now:I:wonder:what:I’m:doing:here:at:all... $ awk -F: -f awk3 float2 ...выполнить скрипт. My is strong, And often days $ 6 Зак. 786
Встроенные функции Утилита awk поддерживает несколько встроенных функций, включая expo, logo, sqrt о, into и substr о. Первые четыре функции работают точно так же как их стандартные двойники С. Функция substr (str, х, у) возвращает подстроку строки str, начиная от символа в позиции от х, длиной у символов. Ниже приведен пример работы и вывод этих функций: $ cat test ...посмотреть входной файл. 1.1 а 2.2 at 3.3 eat 4.4 beat $ cat awk8 ... посмотреть скрипт awk. printf "$1 = %g ", $1; printf "exp = %.2g ", exp ($1); printf "log = %.2g ", log ($1); printf "sqrt = %.2g ", sqrt ($1); printf "int = %d ", int ($1); printf "substr (%s, 1, 2) = %s\n", $2, substr($2, 1, 2); $ awk -f awk8 test ...выполнить скрипт. $1 = 1.1 exp = 3 log = = 0.095 sqrt = 1 int = 1 substr (a, 1, 2) = a $1 = 2.2 exp - 9 log = = 0.79 sqrt = 1.5 int = 2 substr (at, 1, 2} I = at $1 = 3.3 exp = 27 log =1.2 sqrt = 1.8 int = 3 substr (eat, It 2) = ea $1 = 4.4 exp =81 log =1.5 sqrt =2.1 int = 4 substr (beat, 1, 2) = be $ Жесткие и символические связи: In Утилита in позволяет создавать как жесткие связи, так и символические связи между файлами. Синтаксис In -sf оригинал [новаяСвязь] In -sf {оригинал} + каталог Утилита in позволяет создавать жесткие или символические связи к существующим файлам.
Чтобы создать жесткую связь между двумя нормальными файлами, определите существующую метку файла, как оригинал имени файла, и новую метку файла как новаяСвязь. Обе метки будут ссылаться на один физический файл, и эта структура отразится в счетчике жестких связей, показываемых утилитой is. Тогда к файлу можно обращаться через любую метку, а он удаляется из файловой системы, лишь когда все его ассоциированные метки будут удалены. Если аргумент новаяСвязь опущен, то предполагается последний компонент оригинала. Если последний аргумент — имя каталога, то жесткие связи создаются от этого каталога к указанным оригинальным именам файлов. Жесткие связи не могут охватывать файловые системы. Опция -s предписывает утилите in формировать символические связи, приводящие к созданию нового файла, который содержит указатель (по имени) на другой файл. Символическая связь может охватывать файловые системы, поскольку нет никакой явной связи с адресуемым файлом кроме имени. Обратите внимание, если указанный символической связью файл удален, а символический файл связи все еще существует, то обращение через него приведет к ошибке. Опция -f позволяет привилегированному пользователю (root) создавать жесткую связь к каталогу. Для дополнительной информации о том, как жесткие связи представлены в файловой системе, обсуждение файловой системы UNIX см. в главе 14. В представленном ниже примере добавляется новая метка hold к файлу, имеющему метку hold.3 (обратите внимание, что поле счетчика жестких связей было увеличено с 1 до 2, когда жесткая связь была добавлена, а затем значение снова вернулось к 1, когда жесткая связь была удалена): $ Is -1 ...посмотреть содержимое текущего каталога. total 3 -rw-r—г— 1 glass 124 Jan 12 17:32 hold.l -rw-r—г— 1 glass 89 Jan 12 17:34 hold.2 -rw-r—r— 1 glass 91 Jan 12 17:34 hold.3 $ In hold.3 hold ...создать новую жесткую связь. 3 Is -1 ...посмотреть новое содержимое каталога. total 4 -rw-r—r— 2 glass 91 Jan 12 17:34 hold -rw-r—r— 1 glass 124 Jan 12 17:32 hold.l -rw-r—r— 1 glass 89 Jan 12 17:34 hold.2 -rw-r—r— 2 glass 91 Jan 12 17:34 hold.3 3 rm hold ...удалить одну из связей.
$ Is -1 ...посмотреть обновленное содержимое каталога. total 3 -rw-r—r— 1 glass 124 Jan 12 17:32 hold.l -rw-r—r— 1 glass 89 Jan 12 17:34 hold.2 -rw-r—r— 1 glass 91 Jan 12 17:34 hold.3 Серия жестких связей может быть добавлена к существующему каталогу, если имя каталога определено в качестве второго аргумента утилиты in. В следующем примере создаются связи в каталоге tmp ко всем файлам, удовлетворяющим шаблону hold. *: $ mkdir tmp ...создать новый каталог. $ In hold.* tmp ...создать серию связей в tmp. $ Is -1 tmp ...посмотреть содержимое tmp. total 3 -rw-r—г— 2 glass 124 Jan 12 17:32 hold.l -rw-r—r— 2 glass 89 Jan 12 17:34 hold.2 -rw-r—r— 2 glass 91 Jan 12 17:34 hold.3 $ Жесткая связь не может быть создана от файла в одной файловой системе к файлу в другой файловой системе. Чтобы обойти эту проблему, создайте символическую связь, которая может охватывать файловые системы. Для этого используйте опцию -s утилиты in. В приведенном ниже примере осуществляется попытка создать жесткую связь из пользовательского домашнего каталога к файлу /usr/include/stdio.h. Но файл находится в другой файловой системе, по этой причине утилита in не создала связи. Однако запуск утилиты in с опцией -s достиг цели. Когда утилита is используется с опцией -F, символическим связям предшествует символ @. По умолчанию утилита is показывает содержимое символической связи. Для того чтобы получить информацию о файле, на который ссылается связь, используйте опцию -L. $ In /usr/include/stdio.h stdio.h ...жесткая связь. In: stdio.h: Cross-device link $ In -s /usr/include/stdio.h stdio.h ...символическая связь. $ Is -1 stdio.h ...проверить файл. Irwxrwxrwx 1 glass 20 Jan 12 17:58 stdio.h -> /usr/include/stdio.h $ Is -F ' ...@ указывает символическую связь. stdio.h@ $ Is -IL stdio.h ...посмотреть связь. -г—г—г— 1 root 1732 Oct 13 1998 stdio.h $ cat stdio.h ...посмотреть файл.
# ifndef FILE #define BUFSIZ ttdefine SBFSIZ 8 extern struct 1024 iobuf { $ Идентификация shell: whoami Предположим, что вы случайно натолкнулись на свободный терминал и на экране находится приглашение shell. Очевидно, что кто-то работал в системе UNIX и забыл выйти из нее. Вы задаетесь вопросом: любопытно, кто это был? Чтобы раскрыть тайну, можете использовать утилиту whoami, которая показывает имя владельца shell. Например, запустив whoami на терминале пользователя glass, можно увидеть: $ whoami glass $ Синтаксис whoami Показывает имя владельца shell. Замена пользователя: su Многие думают, что su — это сокращение от англ, superuser (привилегированный пользователь). Но это не так. Данная аббревиатура означает substitute user (заменить пользователя) и позволяет создать новый shell, принадлежащий другому пользователю. Синтаксис su [-] [имяПолъзователя] [аргументы] Утилита su создает временный shell с реальным и эффективным пользовательским и групповым идентификаторами. Если имя пользователя не определено, предполагается root, и новая подсказка shell как напомина
ние устанавливается на знак #. Работая в новом shell, вы воспринимаетесь как новый пользователь. Завершая сеанс shell нажатием комбинации клавиш <Ctrl>+<D>, вы возвращаетесь в свой первоначальный shell. Конечно, чтобы использовать эту утилиту, следует знать пароль другого пользователя. Переменные окружения shell и номе задаются из файла пароля имяПользователя. Если имяПользователя — не root, устанавливаются пользовательские переменные окружающей среды. Новый shell не проходит через последовательность регистрации, если не задана опция Все другие параметры передаются, как аргументы командной строки новому shell. Пример использования su: $ whoami ...выяснить текущий ID пользователя. glass $ SU ...заменить пользователя. Password: <ввел пароль привилегированного пользователя> $ whoami ...подтвердить, что мой текущий ID пользователя изменился. root $ ...выполнить задачи привилегированного пользователя здесь. $ ...прекратить дочерний shell. $ whoami ...подтвердить восстановление текущего ID пользователя. glass $ Проверка почты: biff Командные интерпретаторы UNIX периодически осуществляют проверку наличия поступающей почты. Это означает, что может пройти несколько минут между поступлением почты в ваш почтовый ящик и уведомлением shell на вашем терминале. Чтобы избежать подобной задержки, вы можете разрешить мгновенное уведомление о почте, используя утилиту biff. Синтаксис biff [у|п] Утилита biff разрешает или запрещает мгновенное уведомление о получении почты. Чтобы увидеть текущую установку biff, используйте утилиту без параметров. Укажите у, чтобы позволить вывод мгновенного уведомления, и п, чтобы запретить его. Почему эта утилита называется
biff? Разработчица из Калифорнийского университета в Беркли, которая написал утилиту для BSD UNIX, назвала ее в честь своей собаки Biff, которая всегда лаяла, когда почтальон приносил почту. Пример: $ biff biff is n $ biff у $ biff biff is у $ ...вывести текущую установку biff. ...разрешить моментальное уведомление о почте. ...подтвердить новую установку biff. Преобразование файлов Перечисленные ниже утилиты, в числе других, осуществляют преобразование над содержимым файлов. □ Утилиты compress/uncompress и gzip/gunzip конвертируют файл в пространственно-эффективный промежуточный формат и обратно. Полезны для экономии дискового пространства. □ Утилита crypt кодирует файл. □ Утилита sed — программируемый потоковый редактор общего назначения, который редактирует файл согласно предварительно подготовленному набору инструкций. □ Утилита tr связывает символы из одного набора с другим. Полезна для выполнения простого связывания типа преобразования файла из прописных букв в строчные. □ Утилита ul конвертирует встроенные подчеркнутые последовательности символов в файл подходящей для конкретного терминала формы. Сжатие файлов: compress/uncompress и gzip/gunzip Утилита compress кодирует файл в более компактный формат, который может быть декодирован позже с использованием утилиты uncompress. Синтаксис compress -cv {имяФайла}+ uncompress -cv {имяФайл}+
Утилита compress заменяет файл его сжатой версией, добавляя в конец суффикс ".Z". Опция -с посылает сжатую версию на стандартный вывод, а не перезаписывает первоначальный файл. Опция -v показывает величину сжатия, которое имеет место. Утилита uncompress инвертирует файл, созданный compress, воссоздавая первоначальный вид из его сжатой версии. Утилита compress полезна для уменьшения места на диске, занимаемого файлами,'и упаковки большего количества файлов в архивированный файл. Пример: $ Is -1 palindrome.с reverse.с ...проверить оригиналы. -rw-r—г— 1 glass 224 Jan 10 13:05 palindrome.с -rw-r—г— 1 glass 266 Jan 10 13:05 reverse.с $ compress -v palindrome.c reverse.с ...сжать их. palindrome.c: Compression: 20.08% — replaced with palindrome.c.Z reverse, c: Compression: 22.93% — replaced with reverse.c.Z $ Is -1 palindrome, c. Z reverse.c.Z -rw-r—r— 1 glass 179 Jan 10 13:05 palindrome.c.Z -rw-r—r— 1 glass 205 Jan 10 13:05 reverse.c.Z $ uncompress -v *.Z ...восстановить оригиналы. palindrome.c.Z: — replaced with palindrome.c reverse.c.Z: — replaced with reverse.c $ Is -1 palindrome.c reverse.с ...подтвердить. -rw-r—r— 1 glass 224 Jan 10 13:05 palindrome.c -rw-r—r— 1 glass 266 Jan 10 13:05 reverse.c $ _ Алгоритм, который реализуют утилиты compress и uncompress, был запатентован уже после их написания (хотя многие не знали этот факт). Когда их владелец начал процедуру получения патента, выяснилось, что либо должны были быть оплачены лицензионные отчисления за использование обеих утилит, либо они должны быть удалены из системы. Поэтому многие продавцы UNIX выбрали утилиту GNU, известную как gzip, которая работает почти таким же образом, как compress, но использует другой, не имеющий лицензионных ограничений алгоритм. Следующий пример их применения фактически Идентичен Показанному ранее ДЛЯ compress И uncompress: $ Is -1 palindrome.с reverse.с -rw-r—г— 1 ables 224 Jul 1 14:14 palindrome, с -rw-r—r— 1 ables 266 Jul 1 14:14 reverse, c $ gzip -v palindrome, c reverse, c
palindrome.с: 34.3% — replaced with palindrome.с.gz reverse.c: 39.4% — replaced with reverse.c.gz $ Is -1 palindrome.c.gz reverse.c.gz -rw-r—r— 1 ables 178 Jul 1 14:14 palindrome.c.gz -rw-r—r— 1 ables 189 Jul 1 14:14 reverse.c.gz $ gunzip -v ★ .gz palindrome.c.gz: 34.3% — replaced with palindrome.c reverse.c.gz: 39.4% — replaced with reverse.c $ Is -1 palindrome.c reverse.c -rw-r—r— 1 ables 224 Jul 1 14:14 palindrome, c -rw-r—r— 1 ables 266 Jul 1 14:14 reverse.c $ Синтаксис gzip -cv {имяФайла}+ gun z ip - cv {имяФайла} + Утилита gzip заменяет файл его сжатой версией, добавляя в конец суффикс ".gz". Опция -с посылает сжатую версию на стандартный вывод, а не перезаписывает первоначальный файл. Опция -v показывает величину сжатия, которое имеет место. Утилита gunzip может восстановить файл, созданный как gzip, так и compress. Утилиты gzip и gunzip доступны с Web-сайта GNU по адресу http://www.gnu.org/software/gzip/gzip.html. Вы можете загрузить их, если они отсутствуют в вашей системе. Шифрование файла: crypt Утилита crypt создает кодируемую ключом версию текстового файла. Единственный способ восстановить первоначальный текст из закодированного файла — выполнить crypt с тем же самым ключом. Синтаксис crypt [ ключ] Утилита crypt выполняет одно из действий: • если на стандартном входе текст допустимого вида, кодируемая версия текста посылается на стандартный вывод, используя параметр ключ как ключ шифрования;
• если на стандартном входе закодированный текст, декодированная версия текста посылается на стандартный вывод, используя параметр ключ как ключ для расшифровки. Если ключ не определен, утилита crypt напоминает, что вы должны ввести его со своего терминала. Вводимый ключ не отображается. Если ключ указан в командной строке, имейте в виду, что вывод листинга при помощи утилиты ps покажет значение ключа. Утилита crypt использует алгоритм кодирования, подобный тому, который использовался в немецкой шифровальной машине Enigma. Пример использования утилиты crypt: $ cat sample.txt Here’s a file that will be encrypted. $ crypt agatha < sample., txt > sample.crypt $ rm sample.txt $ crypt agatha < sample.crypt > sample.txt $ cat sample, txt Here’s a file that will be encrypted. $ ...вывести оригинал. ...agatha — это ключ. ...удалить оригинал. ...декодировать. ...вывести оригинал. Потоковое редактирование: sed Утилита потокового редактирования sed просматривает один или несколько файлов и осуществляет редактирование всех строк, которые соответствуют конкретному условию. Действия и условия могут быть заблаговременно сохранены в sed-скрипте. Утилита полезна для выполнения простых повторяющихся задач редактирования. Утилита sed имеет много параметров и опций. Из-за этого здесь описаны только ее главные особенности и опции. Однако хочется надеяться, что представленный в этом разделе материал позволит вам писать большое количество полезных sed-скриптов. Синтаксис sed [-е скрипт] [-f скриптФайл] {имяФайла}^ Утилита sed редактирует входной поток согласно скрипту, который содержит команды редактирования. Каждая команда редактирования отделяется символом перевода строки и содержит действие, строку или диапазон строк для выполнения действия, sed-скрипт может быть сохранен
в файле и выполнен с помощью указания опции -f. Если текст срипта помещается непосредственно в командную строку, он должен быть окружен апострофами. Если файлы не определены, утилита sed читает из стандартного входа. Команды sed sed-скрипт — это список из одной или более команд, разделенных символом перевода строки. Существуют правила применения команд утилиты sed (табл. 3.7). □ Адрес должен быть или номером строки или регулярным выражением. Регулярное выражение выбирает все строки, которые соответствуют выражению. Вы можете использовать символ $, чтобы выбрать последнюю строку. П диапазонАдреса может быть единственным адресом или парой адресов, отделенных запятыми. Если определены два адреса, то выбираются все строки между первой строкой, которая соответствует первому адресу, и первой строкой, которая соответствует второму адресу. □ Если адрес не определен, то команда применяется ко всем строкам. Таблица 3.7. Команды редактирования в sed Синтаксис команды Описание адрес а\ текст Добавляет текст после строки, указанной адресом диапазонАдреса с\ текст Заменяет текст, указанный диапазономАдреса текстом диапазонАдреса d Удаляет текст, указанный диапазономАдреса адрес i\ текст Вставляет текст после строки, укзанной адресом адрес г имя Добавляет содержимое файла имя после строки, указанной адресом диапазонАдреса s/выражение/строка/ Заменяет первое появление регулярного выражения выражение строкой строка диапазонАдреса а/выражение/строка/д Заменяет каждое появление регулярного выражения выражение строкой строка Замена текста В приведенном ниже примере sed-скрипт вводится в командной строке. Скрипт вставляет пару пробелов в начало каждой строки. $ cat arms .•.посмотреть оригинальный файл. People just like me,
Are all around the world, Waiting for the loved ones that they need. And with my heart, I make a simple wish, Plain enough for anyone to see. $ sed. 's/^/ /' arms > arms, indent ...отступ в файле. $ cat arms.indent ...посмотреть результат. People just like me, Are all around the world, Waiting for the loved ones that they need. And with my heart, I make a simple wish, Plain enough for anyone to see. $ _ Чтобы удалить все ведущие пробелы из файла, используйте оператор замены обратным способом, как показано в следующем примере: $ sed ’s/" *//' arms.indent ...удалить ведущие пробелы. People just like me, Are all around the world, Waiting for the loved ones that they need. And with my heart, I make a simple wish, Plain enough for anyone to see. $ _ Удаление текста Следующий пример иллюстрирует скрипт, который удаляет все строки, содержащие регулярное выражение ' а' в качестве отдельного символа: $ sed '/а/d' arms ...удалить все строки, содержащие ’а' People just like me, $ _ Чтобы удалить только те строки, которые содержат слово 'а1, пришлось окружить регулярное выражение скобками (\< и \>): $ sed '/\<a\>/d' arms People just like me, Are all around the world, Waiting for the loved ones that they need.
And with my heart, Plain enough for anyone to see. $ Вставка текста Рассмотрим пример, в котором вставлено предупреждение об авторском праве в начале файла с помощью команды вставки. Заметьте, sed-скрипт сохранен в файле и выполнялся с использованием опции -f: $ cat sed5 ...посмотреть sed-скрипт. Copyright 1992, 1998, & 2002 by Graham GlassX All rights reservedX $ sed. -f sed5 arms ...вставить предупреждение об авторском праве. Copyright 1992, 1998, & 2002 by Graham Glass All rights reserved People just like me, Are all around the world, Waiting for the loved ones that they need. And with my heart, I make a simple wish, Plain enough for anyone to see. $ Замена текста В приведенном ниже примере группа строк 1—3 заменяется сообщением о том, что они были подвергнуты цензуре: $ cat sed6 ...посмотреть sed-скрипт. 1,3с\ Lines 1-3 are censored. $ sed -f sed6 arms ...выполнить скрипт. Lines 1-3 are censored. And with my heart, I make a simple wish, Plain enough for anyone to see. $ _ Чтобы заменить сообщением отдельные строки, а не группу, создайте команды для каждой строки: $ cat sed7 ...посмотреть sed-скрипт. 1с\
Line 1 is censored. 2c\ Line 2 is censored. 3c\ Line 3 is censored. $ sed -f sed7 arms ...выполнить скрипт. Line 1 is censored. Line 2 is censored. Line 3 is censored. And with my heart, I make a simple wish, Plain enough for anyone to see. $ _ Вставка файлов В следующем примере вставлено сообщение за последней строкой файла: $ cat insert ...посмотреть файл, который будет вставлен. The End $ sed '$r insert' arms ...выполнить скрипт. People just like me, Are all around the world, Waiting for the loved ones that they need. And with my heart, I make a simple wish, Plain enough for anyone to see. The End $ Составные команды sed Приведенный ниже пример иллюстрирует использование составных команд sed. В начало каждой строки вставлена последовательность «, а вконец строк — последовательность >>: $ sed — 'з/л/« /' -е 's/$/ »/' arms « People just like me, » « Are all around the world, » « Waiting for the loved ones that they need. » « And with my heart, »
« I make a simple wish, » « Plain enough for anyone to see. » $ Преобразование символов: tr Утилита tr ставит в соответствие символы в файле из одного набора символам из другого набора.2 Синтаксис tr -cds строка! строка2 Утилита tr связывает все символы набора строка! на своем стандартном входе с набором символов строка2. Если длина строки2 меньше, чем длина строки!, строка2 дополняется, повторяя свой последний символ; другими словами, команда tr abc de является эквивалентной команде tr abc dee. Набор символов может быть определен с помощью системы обозначений shell: • чтобы определить набор из символов a, d, и f, просто пишут их, как отдельную строку: adf; • чтобы определить набор символов от а до z, отделяют начальный и конечный символ дефисом: a-z. По умолчанию утилита tr заменяет каждый символ стандартного входа строки! соответствующим ему СИМВОЛОМ строки2. Опция -с предписывает дополнить строку! прежде, чем будет выполнено связывание. Дополнение строки означает, что она заменяется строкой, которая содержит все символы ASCII кроме тех, которые имеются в первоначальной строке. В результате такого действия заменяется каждый символ стандартного входа, которого нет в строке!. Опция -d удаляет каждый символ, имеющийся в строке!, из стандартного входа. Опция -s заменяет последовательность повторяющихся символов единственным экземпляром символа. 2 Фактически создается таблица символов. — Ред.
Вот некоторые примеры утилиты tr в действии: $ cat до.cart go cart racing ...посмотреть пример входного файла. $ tr a-z A-Z < до.cart ...преобразовать прописные буквы в строчные. GO CART RACING $ tr a-c D-E < go.cart ...заменить abc на DEE. go EDrt rDEing $ tr -с a X < go. cart ...заменить все символы, кроме а, на X. ХХХХаХХХХХаХХХХХ$ ...даже последний перевод строки заменен. $ tr -с a-z *\012* < до.cart ...заменить не буквы go ...ASCII 12 (перевод строки). cart racing $ tr -cs a-z '\012* < go. cart ...повторить, но сжать go ...повторяющиеся переводы строк. cart racing $ tr -d a-c < go.cart ...удалить все символы a-c. go rt ring $ Преобразование подчеркнутых последовательностей: ul Утилита ul преобразует файл, который содержит подчеркнутые символы так, чтобы он правильно выводился на конкретном типе терминала. Она полезна с командами, подобными man, которая генерирует подчеркнутый текст. Синтаксис ul -t терминал {имяФайла}* Утилита ul преобразовывает подчеркнутые символы на своем входе так, чтобы они корректно отображались на указанном терминале. Если терминал не определен, то предполагается, что он определяется переменной
окружающей среды term. Файл /etc/termcap используется утилитой ui для определения правильной последовательности. Рассмотрим пример совместного использования утилит ui и man. Предположим, что вы хотите использовать утилиту man для создания документа, который желаете печатать на простом ASCII-принтере (принтер при этом не выводит подчеркнутых символов). Утилита man генерирует подчеркнутые символы для текущего терминала, поэтому нужно отфильтровать выходные данные, чтобы сделать их подходящими для "немого” принтера. Создадим конвейер с выхода-утилиты man через утилиту ui с установкой терминала dumb. $ man who | ul -tdumb > man.txt $ head man.txt ...посмотреть первые десять строк. WHO(l) USER COMMANDS WHO(l) NAME who — who is logged in on the system SYNOPSIS who [who-file] [am i] $ Просмотр необработанного содержимого файла: od Утилита восьмеричного дампа od позволяет увидеть содержимое нетекстового файла в различных форматах. Синтаксис od -acbcdfhilosx имяФайла [смещение[.][Ь]] Утилита od показывает содержимое имяФайла в форме, указанной одной из следующих опций. Опция Описание -а Интерпретирует байты, как символы, и печатает как имена ASCII (т. е. 0 соответствует NULL) -Ь Интерпретирует байты, как восьмеричное значение без знака -с Интерпретирует байты, как символы, и печатает в нотации С (т. е. 0 соответствует \0)
(окончание) Опция Описание -d Интерпретирует пары байтов, как десятичные без знака -f Интерпретирует пары байтов, как значения с плавающей запятой -h Интерпретирует пары байтов, как шестнадцатеричное без знака —i Интерпретирует пары байтов, как десятичные со знаком -1 Интерпретирует четыре байта, как десятичные со знаком -о Интерпретирует пары байтов, как восьмеричное без знака -s [n] Ищет строки минимальной длины п (по умолчанию 3), завершающиеся символом NULL -x Интерпретирует пары байтов, как шестнадцатеричное значение По умолчанию содержимое отображается как ряд восьмеричных чисел. Параметр смещение определяет, где должен начаться вывод. Если смещение заканчивается на ь, то это интерпретируется как число блоков. Иначе — как восьмеричное число. Чтобы определить шестнадцатеричное число, укажите перед ним х. Для определения десятичного числа поставьте в конце его точку. В следующем примере содержимое исполняемого файла /bin/od отображается в виде восьмеричных чисел, а затем в виде символов, начиная с позиции 1000 (восьмеричный): $ od /bin/od ...восьмеричный дамп । файла /bin/od. 0000000 100002 000410 000000 017250 000000 003630 000000 006320 0000020 000000 000000 000000 020000 000000 000000 000000 000000 0000040 046770 000000 022027 043757 000004 021002 162601 044763 0000060 014004 021714 000002 000410 045271 000000 020746 063400 0000100 000006 060400 000052 044124 044123 027402 047271 000000 0000120 021170 047271 000000 021200 157374 000014 027400 047271 0000140 000002 000150 054217 027400 047271 000002 000160 047126 $ od -с /bin/od 1000 ...символьный дамп файла /bin/od. 0001000 Н х \0 । 001 N @ \0 002 \0 \0 / u s г / 1 0001020 i b / 1 d s о \0 / d е v / z е 0001040 г о \0 ' \0 \0 \0 \0 030 с г t 0 : п о 0001060 / и S г / 1 i ь / 1 d . s о \п
0001100 \0 \0 \0 % c r t 0 • / u s r / 1 0001120 i b / 1 d • s о m a p P i n g 0001140 f a i 1 u r e \n \0 \0 \0 \o 023 c r 0001160 t 0 • n о / d e v / z e г о 0001200 \n \0 200 \0 \0 002 200 \0 \0 022 \0 \0 \0 007 \0 \0 $ _ Вы можете производить поиск строки минимальной длины, используя опцию -s. Любой ряд символов, сопровождаемых ASCII-символом NULL, рассматривается в качестве строки. Пример: $ od -s7 /bin/od ...поиск строк длиной от 7 символов. 0000665 \fNANu о 0001012 /usr/lib/ld. so 0001031 /dev/zero 0001050 crtO: no /usr/lib/ld.so\n 0001103 %crt0: /usr/lj.b/ld. so mapping failure\n Монтирование файловых систем: mount/umount Привилегированный пользователь (root) может расширять файловую систему, используя утилиту mount. Синтаксис mount -оопции [имяУстройства каталог] umount имяУстройства Утилита mount позволяет соединять файловую систему устройства с корневой иерархией. При использовании без аргументов утилита mount показывает список смонтированных в настоящее время устройств. Чтобы определить специальные опции, за опцией -о указывают список адекватных кодов, среди которых rw монтирует файловую систему для чте-ния/записи, а го монтирует файловую систему только для чтения. Утилита umount размонтирует предварительно смонтированную файловую систему. В следующем примере объединяются файловая система, содержащаяся на устройстве /dev/dsk2, с каталогом /usr. Обратите внимание, что перед вы
полнением монтирования каталог /usr был пуст. После монтирования файлы, хранящиеся на устройстве /dev/dsk2, появились внутри этого каталога. $ mount ‘...посмотреть текущие смонтированные устройства, /dev/dskl on / (rw) $ Is /usr .../usr is currently empty. $ mount /dev/dsk2 /usr ...монтировать устройство /dev/dsk2. $ mount ...посмотреть текущие смонтированные устройства, /dev/dskl on / (rw) /dev/dsk2 on /usr (rw) $ Is /usr ...посмотреть содержимое смонтированного устройства. bin/ etc/ .include/ lost+found/ sre/ ueb/ demo/ game lib/ pub/ sys/ ueblib/ diet/ hosts/ local/ spool/ tmp/ $ _ Чтобы размонтировать устройство, используйте утилиту umount. В следующем примере размонтируется устройство /dev/dsk2, а* затем происходит просмотр каталога /usr: $ umount /dev/dsk2 ...размонтировать устройство. $ mount ...посмотреть текущие смонтированные устройства, /dev/dskl on / (rw) $ Is /usr ...заметьте, что каталог /usr снова пустой. $ _ Файлы больше не доступны. Идентификация терминалов: tty Утилита tty идентифицирует имя рабочего терминала. Синтаксис tty Утилита tty показывает путевое имя вашего терминала. Возвращает О, если ее стандартный вход — терминал; иначе возвращает 1. В следующем примере входным терминалом является специальный файл /dev/ttypO: $ tty ...вывести путевое имя терминала. /dev/ttypO $
Форматирование текста: nroff/troff/style/spell Одной из первых применяемых функций UNIX должна была стать поддержка обработки текста в Bell Laboratories. Несколько утилит, включая nroff, troff, style и spell, были созданы специально для форматирования текста. В настоящее время они фактически устарели из-за использования более сложных инструментов, поддерживающих принцип "What You See Is What You Get" (WYSIWYG, "Что видите, то и получаете"). Например, утилита nroff требует ручного размещения специальных команд типа ,ра внутри текстового документа, чтобы отформатировать его правильно, тогда как современные инструменты позволяют вам делать это с помощью графического интерфейса. Для получения дополнительной информации об утилитах обработки текста в "старинном стиле" см. [Sobell 1994]. Измерение времени выполнения: time Иногда полезно знать, сколько потребуется времени для выполнения определенной команды или программы (или как долго они выполняются относительно времени выполнения чего-то еще). Утилита time может использоваться, чтобы получить отчет о времени выполнения любой указанной команды UNIX. Синтаксис time команднаяСтрока Утилита time служит для получения сведений о времени выполнения любой команды UNIX, указанной параметром команднаяСтрока. Время сообщается и в виде общего затраченного времени и времени ЦП. (Время ЦП выражается в виде двух значений: пользовательское время и системное время.) Пример: $ time sort allnames.txt >sortednames. txt real Om 4.18s user Om 1.85s sys Om 0.14s $ Эта команда сообщает нам, что требуется почти 4,2 секунды астрономического времени, чтобы отсортировать наш файл, тогда как общее время использования ЦП было 1,99 секунды.
Утилита time особенно полезна в определении времени работы программ или скриптов, обрабатывающих маленькие порции данных, когда невозможно "почувствовать" разницу в требуемом времени из-за слишком быстрого их выполнения. Создание собственных программ: Perl Мы выяснили, что при возникновении сложных задач обработки данных, когда требовалось объединение двух или нескольких утилит UNIX, необходимо было писать скрипты shell на одном из языков shell. Скрипты shell медленнее, чем С-программы, т. к. они интерпретируются вместо компиляции. С другой стороны, они намного более легки для написания и отладки. С-программы предоставляют возможность использования большего количества функций UNIX, но, как правило, требуют большего времени для написания и изменения. В 1986 г. Ларри Уолл (Larry Wall) выяснил, что shell-скриптов недостаточно, а С-программы оказались ’’убийственны” для многих намерений. Он собирался написать скриптовый язык, который должен был стать лучше shell-скриптов и С-программ. Результатом стал Perl (Practical Extraction Report Language, Практический Язык Извлечения Сообщений). Perl разрешил многие проблемы, с которыми Ларри Уолл сталкивался при генерации отчетов и применении других ориентируемых на текст функций, хотя язык также обеспечил свободный доступ к функциям UNIX, чего не делали shell-скрипты. Синтаксис языка Perl покажется знакомым для shell- и С-программистов, т. к. многое было заимствовано от элементов обоих языков. Хочется надеяться, что в этой книге нам удалось дать высокоуровневое представление о Perl. Подобно программе awk, о Perl написаны целые книги (см., например, [Medinets 1996] и [Wall 1996].) Детализация такого уровня находится за пределами возможностей этой книги, но, несмотря на первое знакомство, мы уверены, что вы захотите выяснить больше о Perl. Получение Perl Хотя Perl используется в большинстве сред UNIX, система программирования на языке часто не поставляется вместе с этой ОС, поскольку не является частью UNIX. Если ваш продавец UNIX не поставляет Perl, вы должны будете загрузить и установить его самостоятельно. Лучший источник — http://www.perl.com. Этот узел сети содержит рассылки Perl для различных платформ в секции downloads (загрузка), а также документацию и ссылки на многие другие полезные ресурсы.
Огромное преимущество реализации инструментов на Perl — это наличие языка на основных платформах, включая большинство версий UNIX, Windows и MacOS. Следует остерегаться несовместимости в системных запросах и системном расположении файлов данных. Однако ваш код потребует очень мало изменений для того, чтобы работать должным образом на различных платформах. Не менее важное преимущество Perl состоит в том, что он бесплатный. Perl лицензирован разновидностью GNU Public License (Общедоступной лицензией GNU), известной как Artistic License (Художественная лицензия). Она не затрагивает никакого кода, который вы пишете на Perl. Вы можете использовать и распространять собственный код любым пригодным для вас способом. Печать текста Без возможности печатать большинство программ не могли бы выполнить многого. Итак, согласно традиции UNIX, начнем наши примеры скриптов Perl с демонстрации печати единственной строки: print "hello world.\n”; Именно из этого простого примера вы можете сделать вывод, что каждая строка в Perl должна заканчиваться точкой с запятой (;). Также обратите внимание, что \п используется (так же как в языке программирования С) для печати символа перевода строки. Переменные, строки и целые Для программирования, конечно, необходимо иметь возможность присваивать и изменять значения. Perl поддерживает немало переменных, как, например, это делают интерпретаторы командной строки. Переменные могут иметь любой тип. Perl позволяет визуально определить тип переменных по специальным символам. Главное различие между Perl-переменными и переменными shell заключается в том, что в Perl символ $ сам по себе не используется. Он указывается в имени переменной: $i = 3; Это различие следует уяснить закаленным программистам shell. В дополнение ко всем "типичным" математическим операторам (сложение, вычитание и т. д.), целые числа также поддерживают оператор диапазона "..который служит для указания диапазона целых чисел. Он полезен для создания цикла на диапазоне значений.
Как и в большинстве языков программирования, строки в Perl определяются текстом в кавычках. Строки также поддерживают оператор конкатенации, который соединяет строки вместе. Пример: print 1, 2, 3..15, "\п"; # оператор диапазона print "А”, "В", "С”, "\п"; # строки $i = "А" . "В"; # оператор конкатенации print "$i", "\n"; Эти строки генерируют следующие выходные данные: 123456789101112131415 АВС АВ Вы можете видеть, что печатается каждое значение, предоставляя контроль над всеми промежутками. Массивы Большинство языков программирования поддерживает работу с массивами, которые являются списками значений данных. Массивы в Perl весьма просты в использовании, поскольку они распределяются динамически. (Программист не должен определять, насколько большими они будут. И если применяется больший по размеру массив, чем определен, Perl выделит больше памяти и увеличит массив.) Массив обозначается знаком @ следующим образом: @агг = (1, 2,3, 4,5) ; Эта строка объявляет массив агг и помещает в него пять значений. Можно определить тот же самый массив строкой кода @arr = (1..5); которая использует оператор диапазона с целыми. Доступ к отдельному индексированному элементу осуществляется так: print @arr[0],”\n"; Как в большинстве реализаций массивов, первый элемент нумеруется нулем. Таким образом, указанная строка печатала бы 1, т. к. это первое значение массива. Если указать массив без индексов, выведутся все определенные значения. Если используется имя массива без индекса в месте, где ожидается скалярное значение3, оно будет интерпретировано, как количество элементов этого массива. / 3 То есть имя переменной. — Ред.
Пример: @al = (1); # массив из 1 элемента @а2 - (1,2,3,4,5); # массив из 5 элементов @аЗ = (1..10); # массив из 10 элементов print @al, " ", @а2, " ", @аЗ, "\п"; print @al[0], " ", @а2[1], " ", @аЗ[2], "\п"; # Используется, как скаляр, выдаст число элементов print @а2 + @аЗ, "\п"; Когда код выполнится, пользователь увидит следующее: 1 12345 12345678910 123 15 В Perl предусмотрен специальный тип массива — ассоциативный массив. В обычном массиве индекс элемента определяется целым числом между 0 и максимальным размером массива. Ассоциативный массив может иметь индексы в произвольном порядке и любых значений. Рассмотрим, например, массив названий месяцев. Определим массив $month с 12 значениями "Январь", "Февраль" и т. д. (Так как массивы начинаются с индекса 0, вы или должны помнить о вычитании 1 из индекса, или определить множество из 13 значений и игнорировать $month[0], начиная вместо этого с $month[l] = "Январь"). Но что делать, если происходит чтение названия месяца со входа, и нужно посмотреть числовое значение. Можно использовать цикл для поиска в массиве, но это требует дополнительного кода. Было бы лучше, если бы можно было просто индексировать массив названием? В ассоциативном массиве вы можете это сделать следующим образом: @month{’January’} = 1; @month{’February'} = 2; Тогда можно читать название месяца и получать доступ к его числовому значению через код $monthnum = $month {$monthname) ; без применения цикла.
Вместо того чтобы вводить в массив элементы по одному, как мы делали в предыдущем примере, можно определить массив в начале Perl-программы: %month = ("January", 1, "February", 2, "March", 3, "April", 4, "May", 5, "June", 6, "July", 7, "August", 8, "September", 9, "October", 10, "November", 11, "December", 12); Набор значений, которые могут использоваться в ассоциативном массиве (также называемый ключами к массиву), возвращается, как и в нормальном массиве, вызовом функции Perl keys (): Omonthnames = keys (%month) ; Если вы попытаетесь использовать недопустимое значение как ключ, будет возвращен пустой указатель или ноль (в зависимости от того, как вы используете значение). Математические и логические операторы Следующий шаг после объявления переменной — изменение ее значения. Большинство действий над величинами знакомо из С-программирования. Типичные операторы для целых и вещественных чисел — это сложение (+), вычитание (-), умножение (*) и деление (/). Целые числа также поддерживают С-конструкции пре- и постинкремента и пре- и постдекремента. Кроме того, к ним применимы логические операторы and и or. В приведенном ниже примере указана обратная косая черта перед знаком $, используемым в качестве текста в операторе печати, поскольку в этом месте выводится не значение переменной, а ее имя с добавленным к нему символом $ как приставки: $п = 2; print ( "\$П=", $n, "\n"); $п = 2; print ("increment after \$n=", $n++, "\n"); $п = 2; print ("increment before \$n=", ++$n, "\n") $п = 2; print ("decrement after \$n=", $n—, "\n"); $п = 2; print ("decrement before \$n=", —$n, "\n") $п = 2; # сброс print ( "\$n+2=" , $n + 2, "\n"); print ( "\$n-2=" , $n - 2, "\n"); print ( "\$n*2=" , $n * 2, "\n"); print ( "\$n/2=" , $n / 2, "\n"); $г = 3.14; # вещественное число
print ("\$r=", $r, "\n"); print ("\$r*2=", $r * 2, "\n"); # удвоить print ("\$r/2=", $r / 2, "\n"); # поделить пополам print ("1 && 1 -> ”, 1 && 1, "\n"); print ("1 && 0 -> ", 1 && 0, "\n"); print ("1 || 1 -> ", 1 |I 1, "\n"); print ("1 j | 0 -> ", 1 | | 0, "\n"); Этот скрипт генерирует следующие выходные данные: $п=2 increment after $n=2 increment before $n=3 decrement after $n=2 decrement before $n=l $n+2=4 $n-2=0 $n*2=4 $n/2=l $r=3.14 $r*2=6.28 $r/2=1.57 1 && 1 -> 1 1 && 0 -> 0 1 II 1 -> 1 1 || 0 -> 1 Строковые операторы Действия над строковыми типами более сложны, чем над целыми числами, и обычно требуют использования строковых функций. Единственная простая операция, которая имеет смысл для строк, — это конкатенация. Строки конкатенируются оператором "Л Пример: $firstname = "Graham"; $lastname = "Glass"; $fullname = $firstname . " " . $lastname; print "$ fullname\n"; Результат работы кода: Graham Glass
Однако несколько простых операторов сопоставления доступны для строк: if ($value =~ /abc/) {print "contains 'abc'\n"}; $value =~ s/abc/def/; # изменить 'abc1 на 'def' $value =~ tr/a-z/A-Z/; # перевести в прописные Опытный пользователь UNIX признает синтаксис замены от редактора vi и утилиты sed, а также синтаксис перевода, основанный на UNIX-утилите tr. Операторы сравнения Наверняка вам потребуется выполнить сравнение значений. Операторы сравнения, как показано в табл. 3.8, являются обычными. В случае сравнения строк на "больше, чем" или "меньше, чем" эти операторы выясняют порядок сортировки строк. Таблица 3.8. Операторы сравнения в Perl Операция Числовые величины Строковые величины Равно eq Не равно ! = пе Больше, чем > gt Больше, чем или равно >= де Меньше, чем < It Меньше, чем или равно <= 1е Конструкции if, while, for и foreach Существенная часть любого языка программирования — это способность выполнять различные операторы в зависимости от значения переменной и организовывать циклы для повторяющихся задач или индексирования значений массива. Оператор if и цикл while в Perl подобны операторам в языке С. В конструкции if оператор сравнения используется для сопоставления двух значений, а различные наборы операторов исполняются в зависимости от результата сравнения (истина или ложь): $i = 0; if ( $i = 0 ) { print "it's true\n";
} else { print "it’s false\n”; } Этот скрипт выводит на печать строку it’s true Можно организовать цикл с помощью оператора while, например, для печати текста до тех пор, пока оператор сравнения возвращает истину: while ( $i = 0 ) { print "it’s true\n"; Конечно, сравнение возвратит ложь на следующем же цикле, т. к. $i был увеличен. Perl также работает, как с циклами for из С, так и с циклами foreach из С shell. Пример: for ($i = 0 ; $i < 10 ; $i++ ) { print $i, " ’’; print "\n"; Этот скрипт считает от 0 до 9, печатает значение (без символа перевода строки) и генерирует следующие выходные данные: 0123456789 Пример цикла foreach: foreach $n (1..15) { print $n, " "; } print ”\n"; Этот скрипт производит то, что вы могли ожидать: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Ввод/вывод файла Одно весомое усовершенствование Perl перед shell-скриптами — это способность направлять ввод и вывод к определенным файлам и использовать не только стандартный ввод, вывод или каналы ошибки. Вы можете получить доступ к стандартному вводу и выводу следующим образом: while (@line=<stdin>) { foreach $i (@line) {
print $i; # также читает символ EOL (конец строки) } } Этот скрипт будет читать каждую строку из стандартного входа и печатать ее. Но вы можете определить файл данных, из которого захотите выполнить чтение. Вот как это сделать: $FILE= "info.dat"; open (FILE); # имя переменной, не вычисляется @array = <FILE>; close (FILE); foreach $line (@array) { print "$line"; } Этот Perl-скрипт открывает файл info.dat и читает все его строки в массив array. (Остроумное имя, что вы скажете?) Затем он делает то же самое, что и предыдущий скрипт — распечатывает каждую строку. Функции Для реализации одних и тех же алгоритмов в разных программах язык должен предусмотреть возможности использования функций или подпрограмм. Korn shell обеспечивает слабый тип функций, реализованных через командный интерфейс, и это единственный основной shell, который предусматривает функции вообще. Конечно, язык С обеспечивает функции, но авторы скриптов имели много проблем с ним прежде, чем появился Perl. Функции Perl использовать достаточно просто, хотя синтаксис может показаться замысловатым. Следующий простой пример функции Perl даст вам интересную идею: sub pounds2dollars { $EXCHANGE_RATE =1.54; # изменить в случае необходимости $pounds = $_[0] ; return ($EXCHANGE_RATE * $pounds); ) Данная функция заменяет значение, указанное для фунта стерлингов на американские доллары (заданный обменный курс $1.54 к фунту, который может быть изменен по мере необходимости). Специальная переменная $_ [ о ] ссылается на первый аргумент функции.
Чтобы вызвать функцию, в Perl-скрипт надо добавить строки: $book =3.0; # цена в британских фунтах $value = pounds2dollars($book); print "Value in dollars = $value\n"; Когда мы запустим этот скрипт (который включает функцию Perl в конец), то получим Value in dollars = 4.62 Библиотека функций В противовес shell-скриптам Perl обладает способностью выполнять системные вызовы UNIX. Кроме того, Perl обеспечивает интерфейс ко многим системным вызовам UNIX. Интерфейс осуществляется через функции библиотеки Perl, а не прямо через библиотеку системных вызовов. Поэтому использование языка зависит от его реализации и версии, и вы должны обратиться к документации имеющейся у вас версии для получения конкретной информации. Когда интерфейс доступен, он обычно очень похож на библиотеку С. Мы уже рассмотрели несколько функций Perl в предыдущих разделах, когда видели применение системных вызовов open (), close () и print (). Другой простой пример полезной системной функции exit(1); использующейся для выхода из Perl-программы и передачи заданного кода возврата shell. Perl также имеет специальную функцию выхода для печати сообщения на стандартный выход stdout и выхода с текущим кодом ошибки: open(FILE) or die("Cannot open file."); Таким образом, если вызов функции open () завершается ошибкой, функция die о будет выполнена, генерирует вывод сообщения об ошибке на stdout, и Perl-программа завершается с кодом ошибки. Существуют некоторые строковые функции, позволяющие манипулировать значениями строк: length о, index о и split о . Команда $len = length($fullname); присваивает переменной $1еп длину текста, хранящегося в строковой переменной $fullname. Для того чтобы расположить одну строку внутри другой, используйте конструкцию $i = index($fullname, "Glass");
Значение $i будет равно 0, если строка начинается с текста, указанного в строке поиска во втором аргументе. Для того чтобы разбить строку на основе разделительного символа (например, если нужно разбить признаки из файла пароля UNIX на части), используйте ($username, $password, $uid, $gid, $name, $home, $shell) = split(/:/, $line) В этом случае функция split о возвращает массив значений, найденных в строке, обозначенной $iine и отделенной двоеточием. В данной строке кода определены отдельные переменные для сохранения каждого элемента в массиве так, чтобы можно было использовать их значения более простым способом, чем индексация в массиве. Следующая общая функция позволяет манипулировать в Perl-программе временем и датой: ($s, $m, $h, $dy, $mo, $yr, $wd, $yd, $dst) = gmtime(); $mo++; # отсчет месяцев начинается с нуля. $уг+= 1900; # Perl возвращает значение года, начиная с 1900. print "The date is $mo/$dy/$yr.\n"; print "The time is $h:$m:$s.\n"; Предыдущий код выдает результат: The date is 4/25/2002. The time is 13:40:27. Обратите внимание, что функция gmtimeO возвращает 9 значений. Синтаксис языка Perl обязывает определить эти величины в круглых скобках (как если бы вы присваивали многочисленные значения массиву). Аргументы командной строки Еще одна полезная возможность состоит в том, что можно передавать аргументы командной строки Perl-скрипту. Скрипты shell предусматривают очень простой интерфейс к аргументам командной строки, в то время как программы С обеспечивают более сложный (но и более гибкий) интерфейс. Perl-интерфейс находится где-то между ними, как показано в следующем примере: $n = $#ARGV+1; # количество аргументов (начиная с нуля). print $n, " args: \п" ; for ( $i = 0 ; $i < $n ; $i++ ) { print " @ARGV[$i]\n"; }
Этот Perl-скрипт выводит число аргументов, которые были переданы программе peri (после имени Perl-скрипта) и затем распечатывает каждый аргумент на отдельной строке. Мы можем изменить рассмотренный ранее скрипт "фунты в доллары", чтобы позволить значению британских фунтов быть определенным в командной строке следующим образом: if ( $#ARGV < 0 ) { # если аргументы не заданы. print "Specify value in to convert to dollars\n"; exit $poundvalue = @ARGV[0]; # получить значения из командной строки. $dollarvalue = pounds2doliars($poundvalue); print "Value in dollars = $dollarvalue\n"; sub pounds2doliars { $EXCHANGE_RATE =1.54; # из $pounds = $_[0]; return ($EXCHANGE_RATE * $pounds); # изменить, когда необходимо. Пример из реального мира Все краткие предыдущие примеры должны были дать вам представление о том, как работает Perl. Но пока мы не сделали ничего действительно очень полезного. Поэтому давайте, воспользовавшись уже имеющимися у нас сведениями, напишем Perl-скрипт для печати таблицы о ссуде. Определим еще одну команду. Синтаксис loan -а величина -р платеж -г норма Программа loan печатает таблицу, заданный размер ссуды, норму процента и платеж, который должен быть сделан ежемесячно. Таблица показывает необходимое количество месяцев для выплаты ссуды, а также сколько процентов и какая основная сумма будут оплачены каждый месяц. Все аргументы необходимы. 7 Зак. 786
Perl-скрипт loan.pl доступен в сети (см. Введение) и похож на код: # show loan interest $i=0; while ( $i < $#ARGV) { # аргументы процесса if ( @ARGV[$i] eq ”-r" ) { $RATE=@ARGV[++$i]; # норма процента } else { if ( @ARGV[$i] eq "-a” ) { $AMOUNT=@ARGV[++$i]; # размер ссуда } else { if ( @ARGV[$i] eq "-p" ) { $PAYMEOT=@ARGV[++$i]; # размер платежа } else { print "Unknown argument (@ARGV[$i])\n"; exit } } } if ($AMOUNT == 0 || $RATE == 0 || $PAYMENT ==0) { print "Specify -r rate -a amount -p payment\n"; exit } print "Original balance: \$$AMOUNT\n"; print "Interest rate: ${RATE}%\n"; print "Monthly payment : \$$PAYMENT\n"; print "\n"; print "Month\tPayment\tInterest\tPrincipal\tBalance\n\n"; $month=l; $rate=$RATE/12/100; # получить фактическую ежемесячную норму процента $balance=$AMOUNT; $payment=$ PAYMENT; while ($balance > 0) { # цикл для нормы процента $interest=roundUpAmount($rate * $balance); $principal=roundUpAmount($payment — $interest);
if ( $balance < $principal ) { # последний платеж $principdl=$balance; # не платить слишком много! $payment=$principal + $interest; } $balance = roundUpAmount($balance — $principal); print "$month\t\$$payment\t\$$interest\t\t\$$principal\t\t\$$balance\n"; $month++; } sub roundUpAmount { # # in: floating point monetary value # out: value rounded (and truncated) to the nearest cent # $value=$_[0] ; $newvalue = ( int ( ( $value * 100 ) + .5 ) ) / 100; return ($newvalue); Если предполагается платить по $30 в месяц при балансе кредитной карточки в $300 и учитывать норму процента, как 12.9% годовой процентной ставки, то график оплаты напоминает следующее: Original balance: $300 Interest rate: 12.5% Monthly payment: $30 9 Month Payment Interest Principal Balance 1 $30 $3.13 $26.87 $273.13 2 $30 $2.85 $27.15 $245.98 3 $30 $2.56 $27.44 $218.54 4 $30 $2.28 $27.72 $190.82 5 $30 $1.99 $28.01 $162.81 6 $30 $1.7 $28.3 $134.51 7 $30 $1.4 $28.6 $105.91 8 $30 $1.1 $28.9 $77.01 9 $30 $0.8 $29.2 $47.81 10 $30 $0.5 $29.5 $18.31 11 $18.5 $0.19 $18.31 $0
Таким образом, потребуется 11 месяцев, чтобы оплатить баланс при платеже по $30 в месяц, но последний платеж будет только $18.31. Если есть желание погасить ссуду быстрее, чем за 11 месяцев, нужно поднять ежемесячную оплату! Обзор главы Перечень тем В этой главе вы познакомились с утилитами, которые: □ фильтруют файлы; П сортируют файлы; □ сравнивают файлы; □ архивируют файлы; □ производят поиск файлов; □ планируют команды; □ поддерживают программируемую обработку текста; П создают жесткие и символические связи; □ заменяют пользователей; П проверяют наличие почты; □ изменяют файлы; □ просматривают необработанное содержимое файла; П монтируют файловые системы; П подготавливают документы. Также было рассмотрено написание скриптов Perl. Контрольные вопросы 1. При каких условиях вы архивировали бы файлы, используя утилиту tar? 2. Как вы преобразовали бы содержание файла к прописным буквам? 3. В чем разница между утилитами стр и diff? 4. Опишите что, означает "монтирование” файловой системы. 5. Какой процесс обслуживает утилита crontab? 6. Какие дополнительные функциональные возможности имеет расширенное регулярное выражение? 7. В чем состоят главные отличия между утилитами sed и awk?
8. Как утилита awk получила свое имя? 9. При каких условиях вы использовали бы символическую связь вместо жесткой связи? 10. В чем заключаются недостатки использования символической связи? 11. Для чего предназначено инкрементное резервное копирование, и как бы вы выполнили его? 12. Какими способами Perl делает программирование скриптов более легким по сравнению с обычными скриптами shell? Упражнения 3.1. Выполните тесты измерения времени для утилит grep и fgrep, чтобы определить преимущество использования быстродействия fgrep. [Уровень: легкий.] 3.2. Попросите системного администратора продемонстрировать использование утилиты tar для выполнения резервного копирования ваших файлов на ленту. [Уровень: легкий.] 3.3. Используйте утилиту crontab, чтобы включить в расписание скрипт, который в начале каждого дня удаляет ваши старые базовые файлы. [Уровень: средний.] Проекты 1. Напишите командный конвейер, который сжимает содержание файла и затем шифрует его, используя известный ключ, а результат записывает в новый файл. Какой соответствующий командный конвейер должен принимать этот зашифрованный файл для дешифрования и разархивирования его, чтобы получить первоначальный файл? [Уровень: легкий.] 2. Напишите командный конвейер, который находит и сжимает файлы в иерархии каталогов (например, в вашем домашнем каталоге), к которым не было обращений в течение 30 дней. [Уровень: средний.] 3. Измените Perl-скрипт loan так, чтобы вы могли передавать ему список платежей, а не использовать каждый месяц один и тот же размер платежа. [Уровень: средний.]

Глава 4 Командные интерпретаторы shell Мотивация Shell — это программа-посредник между пользователем и ядром операционной системы. Существуют четыре shell, которые, как правило, входят в стандартную поставку UNIX: Bourne shell (sh), Korn shell (ksh), C shell (csh) и Bourne Again shell (bash). Все перечисленные оболочки поддерживают базовый набор возможностей, позволяющих управлять UNIX. Так, например, все командные интерпретаторы позволяют сохранять вывод процесса в файл или перенаправлять его на вход другого. В этой главе мы рассмотрим базовые функции командной оболочки, а в главах 5—8 обсудим особенности каждой из них. Предпосылки Для понимания этой главы вам необходимо усвоить материал первых двух глав. Кроме того, некоторые из упоминаемых утилит были рассмотрены в главе 3. Поэтому, в случае необходимости, прочитайте ее еще раз. Большим преимуществом для изучения материала будет наличие UNIX на вашем компьютере. Задачи В данной главе мы научимся использовать базовые возможности shell, такие как перенаправление ввода/вывода, конвейеризацию, управление заданиями. Изложение по сложившейся уже традиции в этой главе проиллюстрировано примерами UNIX-сессий, которые вы можете повторить на своем компьютере для более глубокого усвоения материала.
Утилиты Нам предстоит познакомиться со следующими утилитами: chsh nohup tee echo ps kill sleep Команды shell Вы научитесь пользоваться следующими командами: echo kill umask eval login wait exec shift exit Общие сведения о shell Shell — это программа-интерфейс между пользователем и операционной системой. Она позволяет работать с файлами, осуществлять перенаправление данных и выполняет немало других полезных функций. Существуют четыре основных командных интерпретатора: П Bourne shell (sh); П Korn shell (ksh); □ C shell (csh); □ Bourne Again shell (bash). Выбор командной оболочки — это, прежде всего, вопрос вкуса. Хотя у каждой оболочки есть свои особенности, например, С shell лучше приспособлен для диалоговой работы, чем Bourne shell, но в некотором отношении хуже для написания скриптов. Korn shell совмещает в себе положительные черты Bourne shell и С shell, а также поддерживает дополнительные полезные функции. Bash впитал в себя лучшие черты остальных программных оболочек. Bourne shell имеется в каждой версии UNIX. Другие командные интерпретаторы не обязательно будут присутствовать в вашей ОС. В главе 8 содержится немало информации о том, что делать, если ваша любимая оболочка не входит в стандартную поставку системы.
Функциональные возможности shell Рассмотрим более подробно, какие функциональные возможности присущи всем командным оболочкам. На рис. 4.1 изображена взаимосвязь различных командных интерпретаторов, а на рис. 4.2 представлена иерархия базовых функциональных возможностей shell. Рис. 4.1. Взаимосвязь функциональных возможностей различных shell Локальные Окружения Условные Безусловные Рис. 4.2. Базовые функциональные возможности shell Выбор shell Когда вам предоставляют имя для регистрации в UNIX, системный администратор определяет программную оболочку, которой вы будете пользоваться. Для того чтобы узнать, какая оболочка вам досталась, просмотрите на приглашение shell. Если вы видите приглашение %, то, скорее всего, вы пользуетесь С shell. Другие оболочки используют символ $ в качестве приглашения по умолчанию. Для изменения установленного по умолча
нию shell воспользуйтесь утилитой chsh. При этом необходимо знать месторасположение всех оболочек. В табл. 4.1 указано стандартное местоположение различных оболочек. Таблица 4.1. Местоположение shell Командный интерпретатор Полное имя Bourne shell /bin/sh Korn shell /bln/ksh C shell /bin/csh Bash /bin/bash Синтаксис chsh Утилита chsh позволяет изменить установленный по умолчанию входной shell. Необходимо указать местоположение командного интерпретатора, который будет использоваться во время ваших последующих входов в систему. Рассмотрим пример замены установленного по умолчанию Bourne shell на Korn shell: % chsh ...изменить входной shell c sh на ksh. Changing login shell for glass ...замена входного shell для glass Old shell: /bin/sh ...вывод путевого имени старого shell. New shell: /bin/ksh ...ввод полного путевого имени нового shell. % "D ...завершить входной shell. login: glass Password: ...войти в систему снова. ...секрет $ . ...в этот раз я в Korn shell. Для того чтобы определить месторасположение shell, воспользуйтесь следующим способом: $ echo $SHELL /bin/ksh $ ...выводит имя моего входного shell. ...полное путевое имя Korn shell.
Эти строки с помощью команды echo выводят место расположения оболочки, указанное в переменной окружения shell. Вывод данных с помощью echo и использование переменных окружения будет рассматриваться чуть позже. Функционирование shell При вызове shell, который либо инициируется автоматически во время входа в систему, либо запускается вручную из скрипта или с клавиатуры, выполняется следующая последовательность действий: 1. Оболочка читает специальный файл запуска, обычно расположенный в домашнем каталоге пользователя. Этот файл содержит некоторую инициализирующую информацию. Последовательность запуска каждого shell различна, поэтому пока не будем останавливаться на конкретных деталях. 2. Выводится приглашение и ожидается пользовательская команда. 3. Если пользователь нажал комбинацию клавиш <Ctrl>+<D>, это интерпретируется shell, как "конец ввода", после чего командный интерпретатор завершает работу. Иначе shell выполняет команду пользователя и возвращается к шагу 2. Команды могут представлять собой как простой вызов отдельной утилиты, например: $ 1S так и сложный конвейер последовательно выполняющихся команд: $ ps -ef I sort ul -tdumb I Ip При необходимости ввести команду, которая превышает размер строки, воспользуйтесь символом обратной косой черты (\), а затем продолжите набор на следующей строке: $ echo это - очень длинная команда shell, и поэтому необходимо \ пользоваться специальным символом для продолжения набора \ на другой строке. \ Обратите внимание, что команда может занимать несколько строк. \ это - очень длинная команда shell, и поэтому необходимо пользоваться специальным символом для продолжения набора на другой строке. Обратите внимание, что команда может занимать несколько строк. $ _ Исполняемые файлы и встроенные команды Большинство команд UNIX вызывает утилиты, которые хранятся в каталогах. Код утилит находится в исполняемых файлах. Например, когда вы печатаете $ 1S
командная оболочка определяет местоположение исполняемой программы, по имени is, которая обычно находится в каталоге /bin, и выполняет ее. (Алгоритм поиска нужной утилиты будет рассмотрен чуть позже.) Кроме способности определять месторасположение утилиты shell имеет несколько встроенных команд, которые выполняются непосредственно оболочкой. Сейчас мы рассмотрим наиболее полезные из них — echo и cd. Вывод информации на экран: echo Встроенная команда echo выводит свои данные, указанные в качестве аргументов, на стандартный вывод. Все командные интерпретаторы имеют эту встроенную функцию, но кроме нее может быть использована и утилита echo (которая по умолчанию находится в каталоге /bin). Если вы пишете скрипты для различных shell, использование утилиты оправданно, т. к. в разных командных оболочках встроенные команды могут вести себя по-разному и требовать различных аргументов. Синтаксис echo {аргументы} ★ Встроенная команда shell echo (эхо) выводит следующие за ней аргументы на стандартный выход. По умолчанию форматирует данные, используя символ перевода строки. Смена каталогов: cd Со встроенной командой cd, изменяющей текущий рабочий каталог, мы познакомились в главе 2. Метасимволы Некоторые знаки обрабатываются shell специальным образом и называются метасимволами. Все командные интерпретаторы поддерживают основной набор метасимволов, представленных в табл. 4.2. После ввода команды shell анализирует ее на наличие метасимволов и обрабатывает их специальным образом. Таблица 4.2. Метасимволы shell Символ Описание Перенаправление вывода; записывает стандартный вывод в файл Перенаправление вывода; добавляет стандартный вывод в файл
Таблица 4.2 (окончание) Символ Описание < Перенаправление ввода; читает стандартный ввод из файла ★ Групповой символ замены файла; соответствует нулю или большему количеству символов 9 Групповой символ замены файла; соответствует любому единственному символу [. .] Групповой символ замены файла; соответствует любому символу между скобками 'команда' Замещение команды; заменяется выводом команды I Символ конвейера; посылает вывод одного процесса на ввод другого г Используется в последовательности команд I Условное выполнение; выполняет команду, если предыдущая завершилась с ошибкой && Условное выполнение; выполняет команду, если предыдущая команда была успешной ( • • ) Группирует команды & Выполняет команду в фоновом режиме # Все символы, которые следуют за #, до символа перевода строки, игнорируются shell и программами (т. е. символизирует комментарий) $ Раскрывает значение переменной \ Предотвращает специальную интерпретацию следующего символа << tok Переназначение ввода; читает стандартный ввод из скрипта до tok После обработки метасимволов выполняется сама команда. Для того чтобы отменить интерпретацию символа, как специального, используйте перед ним косую черту (\). Например: $ echo hi > file $ cat file hi $ echo hi \> file hi > file $ ...сохранить вывод echo в file. ...посмотреть содержимое file. ...запретить метасимвол >. ...> интерпретируется подобно другим символам. ...и вывод идет на терминал вместо файла.
Перенаправление ввода/вывода Средство перенаправления позволяет: □ сохранить вывод процесса в файл (перенаправление вывода)} П использовать содержимое файла как входные данные процесса (переназначение ввода). Рассмотрим эти задачи подробнее. Перенаправление вывода Перенаправление вывода удобно, т. к. дает возможность сохранить вывод процесса в файл, который может быть просмотрен, напечатан, отредактирован или использован как вход для другого процесса. Чтобы перенаправить вывод, используйте метасимволы > или ». Последовательность $ команда > имяФайла посылает стандартный вывод команды в файл с именем имяФайла. Командный интерпретатор создает файл с именем имяФайла, если тот еще не существует, или перезаписывает его предыдущее содержимое. Если файл существует и не имеет разрешения на запись, выводится сообщение об ошибке. Создадим файл alice.txt, в который перенаправим вывод утилиты cat. Вызываемая без параметров утилита cat просто записывает данные, полученные со стандартного входа (в данном случае с клавиатуры) на свой стандартный вывод: $ cat > alice.txt ...создать текстовый файл. In ту dreams that fill the night, I see your eyes, ... конец ввода. $ cat alice.txt ...посмотреть его содержимое. In my dreams that fill the night, I see your eyes, $ _ Последовательность $ команда » имяФайла добавляет стандартный ВЫВОД команды В файл С именем имяФайла. Командный интерпретатор создает файл с именем имяФайла, если тот еще не существует. Добавим какой-нибудь текст к созданному ранее файлу alice.txt: $ cat » alice.txt ...добавить к файлу. And I fall into them, Like Alice fell into Wonderland.
..конец ввода. $ cat alice.txt ...посмотреть новое содержимое. In my dreams that fill the night, I see your eyes, And I fall into them, Like Alice fell into Wonderland. $ _ По умолчанию обе формы перенаправления выводят на терминал сообщение об ошибке в случае сбоя. В С shell, Korn shell и Bash также предусмотрена защита против случайного переписывания файла из-за переназначения вывода. (Эти возможности подробно описаны в следующих главах.) Переназначение ввода Переназначение ввода полезно, поскольку исходные данные для выполнения команды можно взять из файла, созданного ранее. Чтобы переназначить ввод, используйте метасимволы < или «. Последовательность $ команда < имяФайла выполняет команду, используя содержимое файла имяФайла, как стандартный ввод. Если файл не существует или не имеет разрешения на чтение, выводится сообщение об ошибке. Для примера перешлем содержимое созданного нами файла alice.txt, используя УТИЛИТУ mail: $ mail glass < alice.txt ...послать почту самому себе. $ mail ...посмотреть свою почту. Mail version SMI 4.0 Sat Oct 13 20:32:29 PDT 1990 Type ? for help. >N 1 glass@utdallas.edu Mon Feb 2 13:29 17/550 & 1 ...прочитать сообщение #1. From: Graham Glass <glass@utdallas.edu> To: glass@utdallas.edu In my dreams that fill the night, I see your eyes, And I fall into them, Like Alice fell into Wonderland & q ... завершить работу c mail. $ _ Последовательность $ команда « слово
позволяет скопировать в буфер входные данные до слова, а затем выполняет команду, используя буфер. Чуть позже мы рассмотрим эту возможность, которая используется в основном для передачи потока данных другим командам. Групповые символы поиска файлов Все командные оболочки поддерживают возможность поиска по шаблону. Любое слово в командной строке, которое содержит, по крайней мере, один метасимвол, рассматривается как шаблон и заменяется отсортированным в алфавитном порядке списком всех соответствующих ему имен файлов. Эта процедура называется globbing (универсализация файловых имен). В табл. 4.3 приведены такие символы и их значения. Таблица 4.3. Символы поиска в shell Групповой символ Описание * Соответствует любой строке, включая пустую строку ? Соответствует любому единственному символу [ • - ] Соответствует любому из символов в скобках. Диапазон символов может быть определен, разделением пары символов чертой Вы можете запретить командному интерпретатору обрабатывать шаблоны в строке, заключив их в апострофы или двойные кавычки. Вот несколько примеров использования шаблонов: $ Is -FR ...рекурсивно выводит мой текущий каталог, а.с Ь.с сс.с dirl/ dir2/ dirl: d.c e.e dir2: f.d g. c $ 1s *. с ...любой текст с последующим .с. а. с Ь.с сс.с $ 1s ?.с . ...один символ с последующим .с. а. с Ь >. с $ Is /ас] * ...любая строка, начинающаяся с "а" или "с" а. с сс.с $ Is [A-Za-z]* ...любая строка, начинающаяся с буквы.
а. с Ь. с сс. с $ Is dir*/*.с ...все .с-файлы в каталоге dir*. dirl/d.c dir2/g.c $ Is */*.c ...все .с-файлы в любом подкаталоге. dirl/d.c dir2/g.c $ Is *2/?. ? ?.? а.с Ь.с dir2/f.d dir2/g.c $ _ Программные оболочки по-разному обрабатывают случаи, когда не найдено ни одного соответствия шаблону. Конвейеры Командный интерпретатор позволяет использовать стандартный вывод одного процесса как стандартный ввод другого путем их соединения через конвейер с помощью метасимвола |. Последовательность $ команда! | команда2 направляет стандартный вывод команды! на стандартный ввод команды2. Конвейером (pipeline) может быть связано любое количество команд. Конвейеры реализуют основную философию UNIX, исходя из которой большие задачи должны состоять из цепочки более мелких, решаемых предназначенными для этого утилитами. Стандартный канал вывода ошибок передается через стандартный конвейер, хотя некоторые shell поддерживают эту возможность. В приведенном ниже примере перенаправим вывод утилиты 1s на вход утилиты wc (рис. 4.3), чтобы посчитать количество файлов в текущем каталоге (сл/. главу 2 для получения информации о wc): $ Is . ..посмотреть текущий каталог. а.с Ь.с сс.с dirl dir2 $ Is I wc -w ... считать элементы 5 $ Рис. 4.3. Пример конвейера
В следующем примере перенаправим содержимое файла etc/passwd в утилиту awk для того, чтобы извлечь первое поле каждой строки. Вывод awk после этого перенаправим утилите sort, которая отсортирует строки в алфавитном порядке (рис. 4.4). В результате получим отсортированный список всех пользователей системы. $ head -4 /etc/passwd ...просмотреть файл password. root:eJ2S10rVe8mCg:0:1:Operator:/:/bin/csh nobody:*:65534:65534::/: daemon:*:1:1::/: sys:*:2:2::/:/bin/csh $ cat /etc/passwd | awk -F: '{print $1}' I sort audit bin daemon glass ingres news nobody root sync sys tim uucp $ Терминал Рис. 4.4. Сортирующий конвейер Есть очень удобная утилита tee, позволяющая копировать вывод конвейера в файл и в то же время передавать эти данные дальше по конвейеру. Название этой утилиты навеяно Т-соединением. Синтаксис tee -ia {имяФайла} + Утилита tee записывает данные, которые получает на стандартный вход, в файл и одновременно передает их по конвейеру другим утили
там. Опция -а дописывает данные в конец файла. Опция -i игнорирует прерывания. Скопируем вывод утилиты who в файл who.capture и одновременно передадим информацию дальше к утилите sort: $ who I tee who.capture I sort ables ttyp6 May 3 17:54 (gw.Waterloo.com) glass ttypO May 3 18:49 (bridgeOS.utdalla) posey ttyp2 Apr 23 17:44 (blackfoot.utdall) posey ttyp4 Apr 23 17:44 (blackfoot.utdall) $ cat who.capture ...посмотреть захваченные данные. glass ttypO May 3 18:49 (bridgeOS.utdalla) posey ttyp2 Apr 23 17:44 (blackfoot.utdall) posey ttyp4 Apr 23 17:44 (blackfoot.utdall) ables ttyp6 May 3 17:54 (gw.Waterloo.com) $ . Замещение команды Команда, окруженная обратными штрихами ('), выполняется, после чего замещается результатом своей работы. Любой перевод строки в выводе данных заменяется пробелами, как в следующих примерах: $ echo the date today is 'date' the date today is Mon Feb 2 00:41:55 CST 1998 $ _ Это дает возможность применять хитрости, передавая, таким образом, результат выполнения команды в конвейер. Например, утилита who (о которой рассказывается в главе 9) выводит список всех пользователей в системе, а утилита wc (описанная в главе 2) подсчитывает число слов или строк в данных со стандартного входа. Объединяя конвейером вывод утилиты who с утилитой wc, можно сосчитать количество пользователей на системе: $ who posey ttypO Jan 22 15:31 (blackfoot:0.0) glass ttyp3 Feb 3 00:41 (bridge05.utdalla) huynh ttyp5 Jan 10 10:39 (atlas.utdallas.e) $ echo there are who I wc -1' users on the system there are 3 users on the system $
Вывод результатов выполнения одной команды может использоваться, как часть другой. Например, утилита vi позволяет указать в командной строке список файлов, которые будут отредактированы. В этом случае они открываются редактором один за другим. Утилита grep, вызываемая с опцией -1, описанная в главе 3, выводит в командной строке список всех файлов, соответствующих указанному шаблону. С помощью замещения команды (вывод результатов ее работы) можно одной строкой заставить редактор vi отобразить список всех файлов, заканчивающихся на .с и соответствующих шаблону debug: $ vi 'grep -1 debug *.c' Последовательности Если пользователь вводит серию простых команд или конвейеров, отделенных точкой с запятой (;), shell будет выполнять их последовательно, слева направо. Эту возможность можно применять для ’’опережающего ввода с клавиатуры" тем пользователям, которые любят определять всю последовательность действий сразу. Например: $ date; pwd; Is ...выполнить три команды в последовательности. Mon Feb 2 00:11:10 CST 1998 /home/glass/wild а. с Ь.с сс.с dirl dir2 $ _ Каждая команда в представленной последовательности может иметь индивидуально переадресованный ввод/вывод, как показано в следующем примере: $ date > date.txt; Is; pwd > pwd. txt a.c b.c cc.c date.txt dirl dir2 $ cat date.txt ...посмотреть вывод date. Mon Feb 2 00:12:16 CST 1998 $ cat pwd. txt ...посмотреть вывод pwd. /home/glass $ _ Условные последовательности Каждый UNIX-процесс возвращает определенное значение при завершении. Если процесс возвращает 0, это значит, что ошибок не было, и процесс завершился успешно. В противном случае возвращается 1. Все встроенные
команды shell возвращают 1, если в процессе выполнения произошел сбой. Можно использовать эту информацию следующим образом: □ если вы задаете последовательность команд, отделенных символами &&, следующая команда выполняется только в случае, если предыдущая команда возвращает 0; П если вы задаете последовательность команд, отделенных символами | |, следующая команда выполняется в случае, если предыдущая команда возвращает 1. Метасимволы && и | | эквивалентны соответствующим операторам С и обозначают логическое and и or. Например, если в процессе компиляции программы в сс (С-компилятор) не обнаружено фатальных ошибок, создается выполняемая программа a.out и возвращается 0. В противном случае возвращается значение, отличное от 0. Приведенная ниже строка компилирует программу с именем myprog.c и выполняет файл a.out в случае, если компиляция прошла успешно: $ cc myprog.c && a.out Следующий пример в случае неудачи выводит сообщение об ошибке: $ cc myprog.c || echo compilation failed. Группирование команд Команды могут быть сгруппированы путем заключения их в круглые скобки, что заставляет дочерний shell (subshell) выполнить их. Этим командам доступны те же возможности, что и обычным: перенаправление и конвейеризация, использование стандартного ввода и вывода, канала сообщения об ошибках: $ date; Is; pwd > out. txt Mon Feb 2 00:33:12 CSTxl998 a. c b. c $ cat out.txt /home/glass $ (date; Is; pwd) > out. txt Background Processing 159 $ cat out.txt Mon Feb 2 00:33:28 CST 1998. ...выполнить последовательность ...вывод утилиты date. ...вывод утилиты Is. ...только pwd была перенаправлена. ...сгруппировать и затем перенаправить. ...все выводы были перенаправлены. а. с Ь. с /home/glass $
Фоновое выполнение Если вы завершаете простую команду, конвейер, последовательность конвейеров или группу команд метасимволом &, создается дочерний shell для выполнения команды в фоновом режиме, который работает одновременно с родительским shell и не взаимодействует с клавиатурой. Режим очень полезен для выполнения нескольких задач одновременно, если они не требуют ввода с клавиатуры. В системах с графическим интерфейсом принято выполнять каждую команду в отдельном окне, а не использовать для этого возможность фоновой обработки. Когда создается фоновый процесс, shell выводит информацию, которая может применяться в дальнейшем для управления этим процессом. Формат такой информации различен для каждого shell. Для того чтобы найти файл с именем а.с, вызовем утилиту find в приоритетном режиме. Она выполняется достаточно долго, поэтому запустим еще раз find в фоновом режиме. В этом случае shell выведет ID (уникальный номер) фонового процесса и приглашение, предлагая тем самым продолжить работу. Вывод результатов работы фонового процесса будет появляться на основном терминале, что, согласитесь, неудобно. Мы научимся использовать ID для управления процессом и подавления вывода результатов на основной терминал в следующих разделах этой главы. Приведенный ниже пример иллюстрирует поиск файла а.с: $ find . -пате а.с -print /wild/а.с /reverse/tmp/а.с $ find . -name Ь.с -print & 27174 $ date /wild/Ь.с Mon Feb 2 18:10:42 CST 1998 $ ./reverse/tmp/b.c ...появился после ...поиск а.с. ...поиск в фоновом режиме. ...ID процесса. ...запуск date в приоритетном режиме. вывод произведен фоновой утилитой find, вывод утилиты date. еще от фоновой утилиты find. того, как мы получили приглашение shell, ...поэтому мы не увидели нового приглашения. Вы можете задать несколько фоновых вызовов в одной строке, отделяя их друг от друга амперсантом, как показано в следующем примере: $ date & pwd & 27310 27311 Mon Feb 2 18:37:22 CST 1998 /home/glass $ ...создать два фоновых процесса. ...ID процесса для date. ...ID процесса для pwd. ...вывод утилиты date. . . . вывод утилиты pwd.
Перенаправление фоновых процессов Перенаправление вывода Чтобы воспрепятствовать отображению фонового процесса на терминале, перенаправьте его вывод в файл. В следующем примере показана переадресация стандартного вывода утилиты find в файл find.txt. По мере выполнения команды можно наблюдать увеличение занимаемого им объема, исполь зуя утилиту is: $ find. . -name а. с 27188 $ Is -1 find.txt -rw-r—r— 1 glass $ Is -1 find.txt -rw-r—r— 1 glass $ cat find.txt ./wild/а.c -print > find-, txt & ...ID процесса для find. ...посмотреть find.txt. 0 Feb 3 18:11 find.txt ...наблюдать его увеличение. 29 Feb 3 18:11 find.txt ...просмотреть find.txt. ./reverse/tmp/а.c $ Другой альтернативой является отправка вывода по почте самому себе, как показано в приведенном ниже примере: $ find . -name а. с -print I mail glass & 27193 $ сс program.с ...выполнять другую полезную работу. $ mail ...читать свою почту. Mail version SMI 4.0 Sat Oct 13 20:32:29 PDT 1990 Type ? for help. >N 1 glass@utdallas.edu Mon Feb 3 18:12 10/346 & 1 From: Graham Glass <glass@utdallas.edu> To: glass@utdallas.edu ./wild/a.c ...вывод утилиты find. ./reverse/tmp/а.c & g $ _ Некоторые утилиты также осуществляют вывод в стандартный канал ошибок, который должен быть перенаправлен в дополнение к стандартному выводу. В следующей главе подробно описано, как это делается, а сейчас просто приведем пример реализации этой возможности в Bourne Korn shell: $ man ps > ps.txt & ...сохранить документацию в фоновом режиме. 27203
$ Reformatting page. Wait done man ps > ps.txt 2>&1 & 27212 $ ...приглашение shell появилось здесь. ...стандартное сообщение об ошибке. ...перенаправление канала ошибок. ...все выводы перенаправлены. Перенаправление ввода Когда фоновый процесс пытается читать данные с терминала, последний автоматически посылает ему сигнал ошибки, который приводит к завершению процесса. Представленный ниже пример иллюстрирует запуск утилиты chsh в фоновом режиме. Сразу после запуска появляется сообщение, что терминал не был изменен. Если же мы запустим фоновое выполнение утилиты mail, на нашем экране отобразится строка "No message !?!" ("Нет сообщения!?!"): $ chsh & ...запуск chsh, как фонового процесса. 27201 $ Changing NTS login shell for glass on csservrl. Old shell: /bin/sh New shell: Login shell unchanged. ...не ожидает ввода. mail glass & ...запуск mail, как фонового процесса. 27202 $ No message !?! ...не ожидает ввода с клавиатуры. Программы shell: скрипты Любая последовательность команд shell может быть сохранена в обычном текстовом файле и выполнена позднее. Файл, который содержит команды shell, называется скриптом. Прежде чем выполнить скрипт, необходимо установить разрешение на его выполнение с помощью утилиты chmod. Далее для запуска скрипта потребуется лишь указать его имя. Скрипты полезны для хранения часто используемых последовательностей команд. Могут состоять как из одной, так и из нескольких сотен строк. Структуры управления, встроенные в shell, достаточно мощны, чтобы решать широкий диапазон задач. Скрипты значительно облегчают работу системного администратора, автоматизируя такие задачи, как предупреждение пользователей о превышении объема дискового пространства, мониторинг системы и т. д. При запуске скрипта система определяет, для какого командного интерпретатора он был написан, и затем запускает оболочку, используя содержимое файла скрипта как стандартный ввод. Чтобы определить оболочку, для ко
торой написан скрипт, программа анализирует его первую строку, используя следующие правила: П если первая строка содержит только символ #, то скрипт интерпретируется тем shell, из которого он был вызван; П если первая строка имеет формат ?#.' путевоеИмя, тогда для интерпретации используется программа, месторасположение которой указано в параметре путевоеИмя', □ если ни одно из двух первых правил не применимо, то скрипт интерпретируется Bourne shell. Если символ # появляется на любой строке кроме первой, все символы до конца этой строки рассматриваются как комментарий. Комментирование скриптов является хорошим тоном программирования, облегчающим работу с кодом. Для указания оболочки, интерпретирующей скрипт, лучше всего использовать формат #!, т. к. он понятен всем командным интерпретаторам. Приведенный ниже пример иллюстрирует содержимое и выполнение скриптов для С shell и Korn shell: $ cat > script.csh #l/bin/csh ...создать скрипт для C shell. # Это пример скрипта для С shell. echo -n the date today is date # в csh,, -n опускает перевод строки # вывести сегодняшнюю дату. ... конец ввода. $ cat > script.ksh ...создать скрипт для Korn shell. #/ /bin/ksh # Это пример скрипта для Korn shell. echo "the date today is \c" Ив ksh, \c опускает перевод строки date # вывести сегодняшнюю дату. ... конец ввода. $ chmod +х script.csh script.ksh ...сделать их исполняемыми. $ Is -IF script.csh script.ksh ...посмотреть атрибуты. -rwxr-xr-x 1 glass 138 Feb 1 19:46 script.csh* -rwxr-xr-x 1 glass 142 Feb 1 19:47 script.ksh* $ script.csh ...выполнить скрипт C shell. the date today is Sun Feb 1 19:50:00 CST 1998 $ script.ksh ... выполнить. скрипт Korn shell. the date today is Sun Feb 1 19:50:05 CST 1998
Расширения csh и ksh в данном примере используются только для ясности; скрипты могут называться как угодно и даже не нуждаются в расширении. Обратите внимание на использование \с и -п в качестве аргументов утилиты echo. Вид символа запрещения перевода строки способен варьироваться в зависимости от версии утилиты. Это может зависеть также от используемого shell: если оболочка имеет встроенную функцию echo, то специфические особенности утилиты не имеют значения. Дочерние shell Когда вы регистрируетесь в системе UNIX, запускается командный интерпретатор. Этот shell выполняет любые вводимые вами команды. Однако в некоторых случаях ваш текущий (родительский) shell создает новый (дочерний) для выполнения некоторых задач: □ родительский shell создает дочерний shell, чтобы выполнить сгруппированные утилиты, такие как is, pwd, date. Если команда выполняется не в фоновом режиме, то родительский shell бездействует до тех пор, пока дочерний не завершит работу; П при выполнении скрипта родительский shell создает дочерний, чтобы его выполнить; П если задание выполняется в фоновом режиме, родительский shell также создает дочерний shell и продолжает работать одновременно с ним. Дочерний shell называется subshell. Точно так же как любой другой процесс UNIX, subshell имеет свой собственный текущий рабочий каталог, так что перемещение при помощи команды cd, выполненное в subshell, не затрагивает родительской оболочки. Рассмотрим пример: $ pwd ...вывести текущий каталог моего входного shell. /home/glass $ (cd /; pwd) ...subshell перемещает и выполняет pwd. / ...вывод исходит от subshell. $ pwd ...мой входной shell никогда не перемещался, /home/glass $ _ Каждый shell содержит две области данных: пространство переменных окружающей среды и пространство локальных переменных. Дочерний shell наследует копию пространства переменных окружающей среды своего родителя и получает чистое пространство локальных переменных, как показано на рис. 4.5. '
Дочерний shell Рис. 4.5. Области данных subshell Переменные Командный интерпретатор поддерживает два вида переменных: переменные окружения и локальные. Оба типа являются строковыми. Основное различие между ними в том, что переменные окружения наследуются при запуске дочернего shell, в то время как локальные переменные не передаются. В переменных окружения хранится информация, передаваемая между родительским и дочерним shell. Каждый shell имеет набор предопределенных переменных окружения, которые обычно инициализируются файлами запуска, описанными в следующих главах. Аналогично, каждый shell имеет набор предустановленных локальных переменных. Однако и те, и другие могут быть созданы в случае необходимости, что очень удобно для написания скриптов. В табл. 4.4 перечислены предопределенные переменные окружения, общие для всех shell. Таблица 4.4. Предопределенные переменные окружения Имя Описание $НОМЕ Полное путевое имя пользовательского домашнего каталога $РАТН Список каталогов для поиска команд $MAIL Полное путевое имя пользовательского почтового ящика $USER Ваше имя пользователя $SHELL Полное путевое имя пользовательского входного shell $TERM Тип пользовательского терминала
Синтаксис назначения переменных отличается в разных shell, но способ, которым вы получаете доступ к переменным, один и тот же: при добавлении приставки $ к имени переменной эта символическая последовательность заменяется значением указанной переменной. Чтобы создать переменную, просто присваивают ей значение: variableName=value ...не ставьте пробелы вокруг =. В следующем примере выводятся значения некоторых общих переменных окружающей среды shell: $ echo HOME = $НСМЕ, PATH = $РАТН . . .вывести две переменных. НОМЕ = /home/glass, PATH = /bin:/usr/bin:/usr/sbin $ echo MAIL = $MAIL ...вывести другую. MAIL = /var/mail/glass $ echo USER = $USER, SHELL = $SHELL, TERM=$TERM USER = glass, SHELL = /bin/sh, TERM=vtlOO $ _ Приведенный ниже пример иллюстрирует различие между локальными переменными и переменными окружающей среды. Присвоим значение двум локальным переменным и затем сделаем одну из них переменной окружающей среды, используя команду export Bourne shell (подробно описанную главе 5). Затем создадим дочерний Bourne shell и выведем значения переменных, для которых было выполнено присвоение в родительском shell. Обратите внимание, что значение переменной окружающей среды было скопировано в дочерний shell, а значение локальной переменной нет. В конце нажмем комбинацию клавиш <Ctrl>+<D>, чтобы завершить работу дочернего shell и возобновить выполнение родительского, а потом выведем первоначальные переменные: $ firstname=Graham ...присвоить значение локальной переменной. $ lastname=Glass ...присвоить значение другой локальной переменной. $ echo $firstname $lastname ...вывести их значения. Graham Glass $ export lastname ...сделать lastname переменной окружающей среды. $ sh ...запустить дочерний shell; родительский приостановлен. $ echo $firstname $lastname ...вывести значения снова. Glass ...заметьте, что firstname не было скопировано. $ ...завершить дочерний; родительский возобновлен. $ echo $firstname $lastname ...они остались неизменными. Graham Glass $
В табл. 4.5 приведено несколько общих встроенных переменных, имеющих специальное значение. Первая специальная переменная особенно полезна для создания временных имен файлов, а остальные — для доступа к аргументам командной строки в скриптах shell. Ниже в примере были использованы все специальные переменные: $ cat script.sh ...вывести скрипт. echo the name of this script is $0 echo the first argument is $1 echo a list of all the arguments is $* echo this script places the date into a temporary file ecAo called $1.$$ date >$1.$$ # перенаправить вывод date. Is $1.$$ # посмотреть файл. rm $1.$$ # удалить файл. $ script.sh paul ringo george john ...выполнить его. the name of this script is script.sh the first argument is paul a list of all the arguments is paul ringo george john this script places the date into a temporary file called paul.24321 paul.24321 $ Таблица 4.5. Специальные встроенные переменные shell Имя Описание $$ ID процесса shell Имя скрипта shell (если применимо) $1. . $ 9 ссылается на n-й аргумент командной строки (если применимо) $ * Список всех аргументов командной строки Использование кавычек Часто бывает необходимо заместить групповой символ переменной или использовать механизмы замещения команд. Система расстановки кавычек shell позволяет сделать именно это.
Существуют правила их расстановки: □ одинарные кавычки1 (’) подавляют замещение группового символа, переменной и команды; П двойные кавычки (") подавляют замещение только группового символа; □ если кавычки вложены, то внешние кавычки не вызывают замещение. Следующий пример иллюстрирует различие между двумя видами кавычек: $ echo 3*4 =12 ...помните, * — это групповой символ. 3 а.с Ь.с с.с 4 = 12 $ echo "3 * 4 = 12" ...двойные кавычки подавляют групповые символы. 3 * 4 = 12 $ echo '3 * 4 = 12' ...одинарные кавычки подавляют групповые символы. 3 * 4 = 12 $ name=Graham Заключая текст в одинарные кавычки, мы подавляем подстановку группового символа, переменной и команды: $ echo 'ту name is $пате - date is 'date'' my name is $name - date is 'date' Заключая текст в двойные кавычки, мы подавляем подстановку группового символа, но разрешаем замещение переменной и команды: $ echo "ту name is $пате - date is 'date'" my name is Graham - date is Mon Feb 2 23:14:56 CST 1998 $ _ Перенаправление ввода в буфер shell Ранее в этой главе мы уже упомянули метасимвол «, и теперь пришло время поговорить о нем подробнее. Когда shell сталкивается с последовательностью вида $ команда « слово он копирует собственный стандартный ввод до строки, начинающейся со слова, в буфер shell и затем выполняет команду, используя содержимое буфера в качестве своего стандартного ввода. Если слово не встречается в тексте, Bourne shell и Korn shell прекращают копировать ввод, когда они достигают конца скрипта, а С shell выводит сообщение об ошибке. Все ссылки на переменные shell в скопированном тексте заменяются их значениями. 1 В книге также встречается название "апострофы". — Ред.
Чаще всего метасимвол « используется при передаче данных из скрипта на стандартный ввод других команд без применения вспомогательных файлов. Пример: $ cat here.sh ...посмотреть пример. mail $1 « ENDOFTEXT Dear $1, Please see me regarding some exciting news! - $USER ENDOFTEXT echo mail sent to $1 $ here.sh glass ...послать почту самому себе, используя скрипт. • I mail sent to glass $ mail ...посмотреть свою почту. Mail version SMI 4.0 Sat Oct 13 20:32:29 PDT 1990 Type ? for help. >N 1 glass@utdallas.edu Mon Feb 2 13:34 12/384 & 1 ...прочитать сообщение #1. From: Graham Glass <glass@utdallas.edu> To: glass@utdallas.edu Dear glass, Please see me regarding some exciting news! glass & q ...завершить работу утилиты mail. $ Управление работой Поддержка многозадачности — одна из привлекательнейших особенностей UNIX, и чтобы использовать ее в полной мере, важно иметь возможность управлять процессами. Помогут в этом деле две утилиты и одна встроенная команда: □ утилита ps генерирует список процессов и их атрибуты, такие как имена, ID процессов, управляющие терминалы и имена входа владельцев; □ утилита/команда kill завершает процесс, используя его ID; □ команда wait указывает shell на необходимость подождать завершения одного из дочерних процессов.
Статус процесса: ps Утилита ps позволяет осуществлять контроль статуса текущих процессов. Используем утилиту sleep, чтобы задержать работу команды echo и выполнить ее в фоновом режиме. Далее запустим утилиту ps, чтобы получить список текущих процессов. Все процессы принадлежат sh — Bourne shell: $ (sleep 10; echo done) & 27387 $ ps PID TTY TIME CMD 27355 pts/3 0:00 -sh 27387 pts/3 0:00 -sh 27388 pts/3 0:00 sleep 10 27389 pts/3 0:00 PS $ done ...задержать echo в фоновом режиме. ...ID процесса. ...получить список статуса процессов. ...входной shell. ...subshell. ...утилита sleep. ...сама команда ps! ...вывод из фонового процесса. Синтаксис ps -efl Утилита ps выводит информацию о статусе процесса. По умолчанию вывод ограничен процессами, созданными текущим пользовательским shell. Опция -е указывает ps на необходимость включить в список все процессы, которые в настоящее время выполняются. Опция -f выводит полный список. Опция -1 генерирует расширенный список. Синтаксис sleep секунды Утилита sleep выставляет таймер на указанное количество секунд, после чего завершается. В табл. 4.6 описаны значения полей утилиты ps. Таблица 4.6. Значения полей вывода утилиты ps Поле Описание S Состояние процесса UID Эффективный пользовательский ID процесса
Таблица 4.6 (окончание) Поле Описание PID ID процесса PPID ID родительского процесса С Процент времени ЦП, что использовал процесс в последнюю минуту PRI Приоритет процесса SZ Размер данных и стека процесса в килобайтах STIME Время создания процесса или дата, если процесс был создан не сегодня TTY Управляющий терминал TIME Время ЦП, использованное до настоящего момента (в формате ММ: ss) CMD Имя команды Табл. 4.7 иллюстрирует значения данных в поле s, описывающем состояние процесса. Таблица 4.7. Коды состояния процесса, сообщаемые утилитой ps Значение Описание 0 Работает на процессоре R Работоспособный S Спящий т Приостановленный Z "Убитый" процесс Значения большинства этих параметров будут описаны позже. Однако сейчас необходимо остановиться на значении двух полей - r и s: $ (sleep 10; echo done) & 27462 $ ps -f ...запрос ориентированного на пользователя вывода. UID PID PPID C STIME TTY TIME CMD glass 731 728 0 21:48:46 pts/5 0:01 -ksh glass 831 830 1 22:27:06 pts/5 0:00 sleep 10 glass 830 731 0 22:27:06 pts/5 0:00 -ksh $ done ...вывод предыдущей команды 8 Зак. 786
Если вас интересует работа других пользователей, воспользуйтесь опциями -е и -f утилиты ps: $ ps -ef ...вывести все пользовательские процессы. UID PID PPID С STIME TTY TIME CMD root 0 0 0 18:58:16 ? 0:01 sched root 1 0 0 18:58:19 9 0:01 /etc/init - root 2 0 0 18:58:19 9 0:00 pageout root 3 0 1 18:58:19 9 0:53 fsflush root 198 1 0 18:59:38 ? 0:00 /usr/sbin/nscd root 178 1 0 18:59:35 ? 0:00 /usr/sbin/syslogd root 302 1 0 18:59:58 9 0:00 /usr/lib/saf/sac root 125 1 0 18:59:14 9 0:00 /us r/sbin/rpcbind root 152 1 0 18:59:29 9 0:01 /usr/sbin/inetd -s root 115 1 0 18:59:13 9 0:00 /usr/sbin/in.routed -q root 127 1 0 18:59:15 9 0:00 /usr/sbin/keyserv root 174 1 0 18:59:34 9 0:00 /etc/automountd glass 731 728 0 21:48:46 p5 0:01 -ksh $ _ В главе 5 мы рассмотрим программу track, которая использует эти опции для осуществления текущего контроля за другими пользователями. Bourne shell и Korn shell автоматически заканчивают фоновые процессы, когда пользователь выходит из системы, в то время как С shell и Bash продолжают их выполнение. Если вы работаете с Bourne shell или Korn shell и хотите завершить фоновые процессы после выхода из системы, используйте утилиту nohup. Синтаксис nohup команда Утилита nohup выполняет команду и делает ее устойчивой к сигналу отбоя (hup) и завершения (term). Стандартный вывод и канал сообщений об ошибках автоматически переадресовываются в файл nohup.out, а значение приоритета процесса увеличивается на 5, таким образом снижая его приоритет2. Если команда выполняется с вызовом утилиты nohup, а затем происходит выход из системы, после чего пользователь вновь регистрируется, использо 2 Чем больше значение, тем ниже приоритет. — Ред.
ванная команда не появится в листинге утилиты ps. Это происходит потому, что при выходе из системы процесс теряет свой управляющий терминал и выполняется без него. Для включения в листинг всех процессов без управляющих терминалов следует запустить утилиту ps с опцией -х: $ nohup sleep 10000 & ...фоновый nohup-процесс 27406 Sending output to ’nohup.out ’ ... сообщение от nohup. $ ps ...посмотреть процессы. PID TT STAT TIME COMMAND 27399 p3 S 0:00 -sh (sh) 27406 p3 S N 0:00 sleep 10000 27407 p3 R 0:00 PS $ • • .выйти из системы. UNIX(r) System l V Release 4.0 login: < jlass • • .войти в систему. Password: • • .секрет. $ ps • • . фоновый процесс не виден. PID TT STAT TIME COMMAND 27409 p3 S 0:00 -sh (sh) 27411 p3 R 0:00 PS $ ps -x . . . фоновый процесс может быть виден. PID TT STAT TIME COMMAND 27406 IN 0:00 sleep 10000 27409 p3 S 0:00 -sh (sh) 27412 p3 R 0:00 ps -x $ _ Для дополнительной информации об управляющих терминалах см. главу 13. Процессы передачи сигналов: kill Если возникла необходимость завершить процесс прежде, чем он закончится, используйте команду kill. Korn shell и С shell содержат встроенную -команду kill, тогда как Bourne shell вместо этого вызывает стандартную утилиту kill. Представленный ниже пример иллюстрирует создание фонового процесса и завершение его командой kill: $ (sleep 10; echo done) && ...создать фоновый процесс. 27390 ...ID процесса. $ kill 27390 ..."убить" процесс.
$ ps ... он ушел PID тт STAT TIME COMMAND 27355 рЗ S 0:00 -sh (sh) 27394 рЗ R 0:00 PS $ Синтаксис kill [ -сигналЮ ] {Ю_процесса} + kill -1 Утилита/команда kill посылает сигнал с параметром сигнал_ю списку пронумерованных процессов, сигналю может быть номером или названием сигнала. По умолчанию kill посылает сигнал term (номер 15), получая который, процесс завершается. Чтобы вывести список допустимых названий сигналов, используйте опцию -1. Для передачи сигнала процессу пользователю необходимо быть его владельцем или привилегированным пользователем (для получения более подробной информации о сигналах см. главу 13). Процессы могут игнорировать все сигналы, кроме kill (номер которого 9). Поэтому, чтобы гарантированно завершить процесс, пошлите сигнал номер 9. (Обратите внимание, что отправка сигнала kill не позволяет процессу произвести очистку и закончиться обычным образом, как это делают многие программы при получении сигнала term.) Утилита kill (в противоположность встроенной команде в Korn shell и С shell) имеет параметр pid, равный 0. В главе 6 подробно описано использование команды kill. Представленный ниже пример иллюстрирует использование опции -1 и идентификаторов сигналов, которые выведены в порядке возрастания: $ kill -1 ...вывести имен^ сигналов. HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ VTALRM PROF WINCH LOST USR1 USR2 $ (sleep 10; echo done) & 27490 ...ID процесса. $ kill -KILL 27490 ..."убить" процесс с сигналом номер 9. $
Данный пример иллюстрирует завершение всех процессов, связанных с текущим терминалом: $ sleep 30 & sleep 30 & sleep 30 & ...создать три. 27429 27430 27431 $ kill 0 ..."убить" их все. 27431 Terminated 27430 Terminated 27429 Terminated $ Ожидание дочернего процесса: wait Командный интерпретатор shell может ожидать завершение дочерних процессов, выполняя встроенную команду wait. Синтаксис wait [ Ю_процесса ] Команда wait предписывает командному интерпретатору приостановить работу, пока дочерний процесс с указанным ID в качестве аргумента не завершится. Если аргумент не указан, shell ждет завершения всех дочерних процессов. В следующем примере shell перед продолжением своей работы ожидает завершения двух дочерних процессов, работающих в фоновом режиме: $ (sleep 30; echo done 1) & ...создать дочерний процесс. 24193 $ (sleep 30; echo done 2) & ...создать дочерний процесс. 24195 $ echo done 3; wait; echo done 4 ...ждать дочерние процессы. done 3 done 1 ... вывод первого дочернего процесса. done 2 ... вывод второго дочернего процесса. done 4
Поиск команды: $РАТН При обработке команды shell в первую очередь проверяет, не является ли она встроенной. Если это так, команда выполняется непосредственно shell: $ echo Некоторые команды выполняются shell непосредственно Некоторые команды выполняются shell непосредственно $ _ Если команда не встроенная, shell проверяет, начинается ли она с символа /. Если это так, shell интерпретирует его, как первый символ абсолютного пути, и пытается запустить соответствующий файл. Если система не находит данный файл по указанному адресу, возникает ошибка: $ /bin/Is ...полное путевое имя утилиты 1s. < script.csh script.ksh $ /bin/nsx ...несуществующее имя файла. /bin/nsx: not found $ /etc/passwd ...имя файла password. /etc/passwd: Permission denied ...он не исполняемый. $ _ Если введенная последовательность не является ни встроенной командой, ни путем к файлу, shell ищет каталоги, указанные в переменной path. После чего каждый каталог просматривается на предмет наличия в нем заданного файла, и если таковой находится, он выполняется. Если файл не является исполняемым или просто не найден, выводится сообщение об ошибке. Если переменная path не установлена или равна пустой строке, то просматривается только текущий каталог. Значение переменной path может быть изменено, но это будет обсуждаться в следующих главах. Первоначально путь поиска инициализируется файлом запуска shell и обычно включает все стандартные каталоги UNIX, содержащие выполняемые утилиты. Вот некоторые примеры: $ echo $РАТН /bin:/usr/bin:/usr/sbin ...просматриваемые каталоги. $ Is ...расположена в /bin. script.csh script.ksh $ nsx ...отсутствует. nsx: not found
Подмена стандартных утилит Пользователи часто создают каталог bin в своем домашнем каталоге и размещают его перед основным каталогом bin в переменной path. Такой способ позволяет им опередить загрузку установленных по умолчанию утилит UNIX собственными версиями, т. к. поиск в персональных каталогах будет производиться прежде, чем в стандартных. Однако необходимо использовать данную возможность с осторожностью, потому как shell может неправильно интерпретировать нестандартные утилиты. Рассмотрим пример. Создадим собственный каталог bin, пропишем его в переменной path, после чего поместим в него скрипт is: $ mkdir bin $ cd bin $ cat > Is echo my Is $ chmod +x Is $ echo $PATH . ..создать персональный каталог bin. ...перейти в новый каталог. ...создать скрипт 1s. ...конец ввода. ...сделать его исполняемым. ...посмотреть текущую установку PATH. /bin:/usr/bin:/usr/sbin $ echo $HOME ...получить путевое имя моего домашнего каталога, /home/glass $ РАТН= /home/glass/bin:$РАТН ...обновить. $ 1s ... вызов 1s. my Is ... версия подменяет /bin/Is. $ Внимание Только этот shell и его дочерние shell находятся под влиянием изменения path. Все другие shell при этом не затрагиваются. Завершение и коды выхода Завершаясь, каждый процесс возвращает числовое значение — код выхода. В соответствии с соглашением, значение кода выхода 0 означает, что процесс завершился успешно, в то время как любое значение, отличное от нуля, — выполнение закончилось неудачей. Все встроенные команды возвращают 1 в случае неудачи. В Bourne shell, Korn shell и Bash специальная переменная $? всегда содержит значение кода выхода предыдущей команды. В С shell это значение содержится в переменной $ status. В представленном
ниже примере утилита date завершилась успешно, тогда как утилиты сс и awk — С ошибкой: $ date ...date завершилась успешно. Sat Feb 2 22:13:38 CST 2002 $ echo $? ...вывести ее выходное значение. 0 ...указывает на успех. $ cc prog, с ...компилировать несуществующую программу. cpp: Unable to open source file ’prog.с’. $ echo $? ...вывести ее выходное значение. 1 ...указывает на неудачу. $ awk ...использовать awk неправильно. awk: Usage: awk . [-Fc] [-f source ’cmds’] [files] $ echo $? ...вывести ее выходное значение. 2 ...указывает на неудачу. $ _ Любой создаваемый пользователем скрипт должен возвращать код выхода. Чтобы завершить скрипт, используйте встроенную команду exit. Если оператор exit не указан в скрипте, по умолчанию возвращается значение выхода последней команды. Скрипт в представленном ниже примере возвратил значение 3: $ cat script, sh ...просмотреть скрипт. echo this script returns an exit code of 3 exit 3 $ script.sh ...выполнить скрипт. this script returns an exit code of 3 $ echo $? . ...посмотреть выходное значение. 3 $ Синтаксис exit номер Команда exit заканчивает выполнение и возвращает значение выхода родительскому процессу. Если аргумент номер опущен, то используется значение выхода предыдущей команды. В следующей главе представлено несколько примеров скриптов, использующих значения выхода.
Основные встроенные команды Большое число встроенных команд поддерживается четырьмя командными интерпретаторами, но только несколько команд являются общими для всех. Этот раздел описывает наиболее полезные общие команды. eval Команда eval выполняет вывод команды, как обычной команды shell. Синтаксис eval команда Команда eval выполняет вывод команды, как обычную команду shell. Она полезна при обработке вывода утилит, которые генерируют команды shell (например, tset). В представленном примере переменная х была установлена в результате выполнения команды echo: $ echo х=5 ...первое выполнение echo напрямую. х=5 $ eval 'echo х=5' ...выполнить результат echo. $ echo $х ...подтвердить, что х было присвоено 5. 5 $ ехес Команда ехес замещает заданной командой образ shell. Синтаксис ехес команда Команда ехес предписывать заместить образ shell командой в пространстве памяти процесса. Если команда выполнена успешно, выполняющий команду ехес shell завершается. Если этот shell был входным, то сессия работы с системой заканчивается после завершения выполнения команды.
В следующем примере командой ехес была запущена утилита date: $ exec date ...заместить процесс shell процессом date. Sun Feb 1 18:55:01 CDT 1998 ...вывод от date. login: _ ...входной shell завершился. shift Синтаксис встроенной команды указан ниже. Синтаксис shift Команда shift изменяет порядковые номера 2, 3, ..., п аргументов, указанных в командной строке на номера 1, 2, п — 1. При этом первый аргумент (с номером 1) теряется. Это особенно удобно в shell-скриптах, когда они циклически обрабатывают ряд параметров командной строки. Если нет никаких позиционных аргументов, оставленных для сдвига, выводится сообщение об ошибке. Следующий пример содержит скрипт С shell, в котором демонстрируются аргументы команды перед и после сдвига. $ cat shift.csh ...посмотреть скрипт. #!/bin/csh echo first argument is $1, all args* are $* shift echo first argument is $1, all args are $* $ shift, csh abed ...выполнить с четырьмя аргументами. first argument is a, all args are abed first argument is b, all args are bed $ shift, csh а ...выполнить с одним аргументом. . first argument is a, all args are a first argument is , all args are . $ shift.csh ' ...выполнить без аргументов. first argument is f all args are shift: No more words ...сообщение об ошибке.
umask Когда С-программа создает файл, первоначальная установка параметров полномочий задается как восьмеричное значение, передаваемое системному вызову open (). Например, чтобы создать файл с разрешением на чтение и запись для владельца, группы и остальных пользователей, программа выполнила бы следующий системный вызов: fd = open ("myFile", OCREAT | ORDWR, 0666); Для получения информации относительно шифрования параметров полномочий в виде восьмеричного числа см. главу 2. Системный вызов open () более подробно описан в главе 13. Когда shell выполняет перенаправление (с помощью символа >), он использует последовательность системного вызова, подобную показанной в предыдущем примере, чтобы создать файл с восьмеричным разрешением 666. Однако, если вы попробуете создать файл, указывая символ переназначения >, то скорее всего получите файл с установкой параметров полномочий 644 (восьмеричное), как показано в следующем примере: $ date > date.txt $ Is -1 date.txt -rw-r—r— 1 glass 29 May 3 18:56 date.txt $ _ Причина различия в установке параметров полномочий состоит в том, что каждый процесс UNIX содержит специальную величину umask, используемую для ограничения назначения параметров полномочий и запрашиваемую системой во время создания файла. По умолчанию величина umask равна восьмеричному значению 022. Набор битов значения umask маскирует набор битов требуемой установки параметров полномочий. В предыдущем примере требуемые параметры полномочий 666 были замаскированы значением 022, что в результате дало 644. В табл. 4.8 приведена расшифровка параметров umask и получаемых в результате наложения значений. Таблица 4.8. Побитовый пример эффекта установки umask rwx rwx rwx Начальный 110110110 Маска 000010010 Финальный 110100100
Если файл уже существует до перенаправления в него вывода, значения первоначальных параметров полномочий файла сохраняются, а значение umask игнорируется. Синтаксис umask [восьмеричнаяВеличина] Команда shell umask присваивает umask указанное восьмеричное число или, если аргумент опущен, показывает текущее значение umask. Значение umask сохраняется до следующего изменения. Дочерние процессы наследуют свое значение umask от родителей. В следующем примере значение umask изменено на 0, после чего был создан новый файл: $ umask ...вывести текущее значение umask. 22 ...маскирует разрешение записи для группы/других. $ umask 0 ...установить значение umask на 0. $ date > date2.txt ...создать новый файл. $ Is -1 date2.txt — rw—rw—rw— 1 glass 29 May 3 18:56 date2.txt Обзор главы Перечень тем В этой главе мы рассмотрели: □ общую функциональность четырех основных командных интерпретаторов; □ общие метасимволы shell; □ перенаправление ввода и вывода; □ подстановку имени файла; □ конвейеры; □ подстановку команды; □ командные последовательности; □ сгруппированные команды;
□ создание скриптов; □ различие между локальными переменными и переменными окружения; □ два различных типа кавычек; □ основы управления работой; □ механизм поиска команды; □ несколько основных встроенных команд. Контрольные вопросы 1. Можете ли вы изменить shell, установленный по умолчанию? 2. Какая команда UNIX используется для того, чтобы изменить текущий пользовательский каталог? 3. Как вы можете ввести команды, которые длиннее одной строки терминала? 4. В чем заключается различие между встроенной командой и утилитой? 5. Как сделать скрипт исполняемым? 6. Как иногда называется замещение имени файла? 7. Объясните использование замещения команды. 8. Объясните значения терминов "родительский shell", "дочерний shell" и "subshell". 9. Как вы думаете, почему команда kin получила свое имя? 10. Опишите способ подмены стандартной утилиты. 11. Чем удобна величина umask и почему? Упражнения 4.1. Напишите скрипт, который печатает текущую дату, ваше пользовательское имя и название вашего входного shell. [Уровень: легкий.] 4.2. Поэкспериментируйте с командой ехес путем создания серии из трех shell-скриптов a.sh, b.sh и c.sh, каждый из которых выводит свое имя, выполняет утилиту ps и затем вызывает при помощи ехес следующий скрипт этой последовательности. Пронаблюдайте, что происходит, когда вы запускаете первый скрипт, выполняя ехес а. sh. [Уровень: средний.] 4.3. Объясните, почему файл, который создан в следующей сессии, не затрагивается значением umask? [Уровень: средний.] $ Is -1 date.txt -rw-rw-rw- 1 glass 29 Aug 20 21:04 date.txt
$ umask 0077 $ date > date.txt $ Is -1 date.txt -rw-rw-rw- 1 glass 29 Aug 20 21:04 date.txt 4.4. Напишите скрипт, который создает три фоновых процесса, ждет их завершения, после чего выводит простое сообщение. [Уровень: средний.] Проект Сравните и противопоставьте особенности командного интерпретатора UNIX с графическими shell, доступными в Windows. [Уровень: средний.]
Глава 5 Bourne shell Мотивация Командный интерпретатор Bourne shell, написанный Стивеном Боурном (Stephen Bourne), был первым популярным UNIX shell и сейчас присутствует во всех UNIX-системах. Он поддерживает довольно универсальный язык программирования и похож на более мощный Кот shell, который описан в главе 6. Знание Bourne shell поможет вам понять работу многих скриптов, уже написанных для UNIX, а также подготовит к знакомству с Кот shell. Предпосылки Перед чтением данной главы вам необходимо ознакомиться с главой 4 и поэкспериментировать с основными функциями shell. Задачи В этой главе мы познакомимся со специфическими для Bourne shell функциями, включая использование локальных переменных и переменных окружения, встроенного языка программирования и средств перенаправления ввода/вывода. Изложение Как обычно, материал главы представлен в виде нескольких UNIX-сессий, что позволяет увидеть в работе обсуждаемое в теории. Утилиты В этой главе мы познакомимся с утилитами: expr test
Команды shell Ниже перечислены команды shell, которые будут рассмотрены в данной главе. break for...in...do...done set case...in...esac if...then...elif...fi trap continue read while...do...done export readonly Общие сведения о Bourne shell Bourne shell поддерживает все основные функции программной оболочки, описанные в главе 4, а также следующие специфические возможности (рис. 5.1): □ несколько способов задания значений и доступа к переменным; П встроенный язык программирования, который обеспечивает условные переходы, организацию циклов и обработку прерываний; □ усовершенствованные средства перенаправления и работы с последовательностью команд; П несколько новых встроенных команд. Рис. 5.1. Функции Bourne shell Запуск Bourne shell — это обычная С-программа, исполняемый файл которой хранится в каталоге /bin/sh. Если оболочка является установленной по умолчанию, то она запускается автоматически в процессе регистрации в системе. Можно также вызвать Bourne shell вручную из скрипта или с терминала, используя команду sh.
Когда Bourne shell стартует в диалоговом режиме, он ищет файл .profile в домашнем каталоге пользователя. Если файл найден, то выполняются все команды shell, содержащиеся в файле. Затем, независимо от того, был ли .profile найден, диалоговый режим Bourne shell выводит свое приглашение и ждет пользовательских команд. Стандартное приглашение Bourne shell — это символ $, хотя он может быть изменен путем установки локальной переменной psi, о которой мы поговорим чуть позже. Внимание Если Bourne shell запущен не в диалоговом режиме, файл не читается. Одно из обычных применений .profile — это инициализация переменных окружения типа term (содержит тип пользовательского терминала) и path (сообщает shell, где искать исполняемые файлы). Вот пример файла запуска .profile для Bourne shell: TERM=vtlOO # установка типа терминала. export TERM # скопировать в окружение. # Установить путь и метасимволы stty erase ,1Л?" kill "Ли" intr ”ЛС” eof РАТН='.:$HOME/bin:/bin:/usr/sbin:/usr/bin:/usr/local/Ып’ Переменные Bourne shell поддерживает следующие действия с переменными: П простое назначение и доступ; □ проверка переменной на существование; □ чтение переменной со стандартного ввода; □ создание неизменяемой переменной; □ экспорт локальной переменной в переменные окружения. Командный интерпретатор Bourne shell также определяет несколько локальных переменных и переменных окружения в дополнение к тем, которые были рассмотрены в главе 4. Создание и назначение переменной Bourne shell имеет следующий синтаксис для присвоения значения переменной: {имя=значение}+ Если переменная не существует, она неявно создается; иначе ее предыдущее значение переписывается. Недавно созданная переменная всегда локальна,
хотя может быть превращена в переменную окружения способом, о котором мы поговорим позднее. Чтобы присвоить значение, которое содержит пробелы, обрамите его кавычками. Например: $ firstName=Graham lastName-Glass аде=29 ...назначение переменных. $ echo $firstName $lastName is $age Graham Glass is 29 ...простой доступ. $ name=Graham Glass ...синтаксическая ошибка. Glass: not found $ name="Graham Glass" ...используйте кавычки для задания строк. $ echo $name ...теперь это работает. Graham Glass $ Доступ к переменной Bourne shell поддерживает методы доступа к переменным, перечисленные в табл. 5.1. Если доступ к переменной осуществляется прежде, чем ей присвоено значение, то возвращается пустая строка. Таблица 5.1. Специальные переменные Bourne shell Синтаксис Описание $имя Заменяется значением имя ${имя} Заменяется значением имя. Эта форма полезна, если выражение следует сразу за алфавитно-цифровым символом, который иначе интерпретировался бы как часть имени переменной ${имя-слово} Заменяется значением имя, если оно установлено, в противном случае — слово ${имя+слово} Заменяется слово, если имя установлено, в противном случае ничего не делает ${name=word} Присваивает слово переменной имя, если имя еще не установлено, и затем заменяется значением имя ${имя?слово} Заменяется имя, если имя установлено. Если имя не установлено, слово выводится в стандартный канал ошибки, и происходит выход из shell. Если слово опущено, то вместо этого выводится стандартное сообщение об ошибке Возможно, указанные методы несколько сложны, однако, даже если вы никогда не будете их использовать, то сможете понимать код, в котором они применяются. Представленные ниже примеры иллюстрируют каждый из
методов доступа. В первом примере мы будем использовать фигурные скобки, чтобы добавить строку к значению переменной: $ verb=sing ...определить переменную. $ echo I like $verbing ...нет переменной verbing. I like $ echo 1 like ${verb}ing ...теперь это работает. I like singing $ _ В следующем примере используется замещение команды для присвоения переменной startDate текущей даты, если переменная еще не установлена: $ startDate=${startDate-'date'} ...если не установлена, выполнить date. $ echo $startDate ...посмотреть ее значение. Tue Wed 4 06:56:51 GST 1998 $ _ Далее переменной х присваивается значение по умолчанию, после чего оно выводится на терминал: $ echo х - ${х=10) ...присвоить значение по умолчанию. х = 10 $ echo $х ...подтвердить, что значение переменной было установлено. 10 $ _ В данном примере в зависимости от того, установлены переменные или нет, выводятся те или иные сообщения: $ flag=l ...определить переменную. $ echo ${flag+'flag is set'} ...первое условное сообщение. flag is set $ echo ${flag2+'flag2 is set'} ...второе условное сообщение. ...результат нулевой. $ _ Следующий пример иллюстрирует доступ к неопределенной переменной grandTotal и вывод сообщения об ошибке: $ total=10 ...определить переменную. $ value=${total?'total not set'} ...доступ осуществлен. $ echo $value ...посмотреть ее значение. 10 $ value=${grandTotal?'grand total not set'} ...не установлена.
grandTotal: grand total not set $ _ В завершающем примере используется скрипт с тем же методом доступа, что и в предыдущем, в результате чего произошла ошибка: $ cat script.sh ...посмотреть скрипт. value=${grandTotal?’grand total is not set’} echo done # эта строка никогда не выполнится. - $ script.sh 4 ...выполнить скрипт. script.sh: grandTotal: grand total is not set $ _ Чтение переменной co стандартного ввода Команда read позволяет читать переменные со стандартного ввода. Если пользователь определяет только одну переменную, то вся строка сохраняется в переменной. Вот стандартный скрипт, напоминающий пользователю о необходимости ввести его полное имя: $ cat script.sh ...посмотреть скрипт. echo ’’Please enter your name: \c” read name # читать только одну переменную. echo your name is $name # вывести переменную. $ script.sh ...выполнить скрипт. Please enter your name: Graham Walker Glass your name is Graham Walker Glass ...была прочитана вся строка. $ Синтаксис read {переменная}+ Команда read читает одну строку со стандартного ввода и затем последовательно присваивает слова из строки указанным переменным. Оставшиеся слова присваиваются последней переменной. В представленном ниже примере была определена более чем одна переменная: $ cat.script.sh ...посмотреть скрипт. echo ’’Please enter your name: \c" read firstName lastName # читать две переменных.
echo your first name is $firstName echo your last name is $lastName $ script.sh ...выполнить скрипт. Please enter your name: Graham Walker Glass your first name is Graham ...первое слово. your last name is Walker Glass ...остаток. $ script.sh ...выполнить его снова. Please enter your name: Graham your first name is Graham ...первое слово. your last name is ...только одно. $ Экспорт переменных Команда export позволяет отмечать локальные переменные для экспорта в переменные окружения. Синтаксис export {переменная} + Команда export отмечает указанные переменные для экспорта в переменные окружения. Если переменные не определены, то выводится список всех переменных, отмеченных для экспорта в течение сессии shell. Как правило, для именования переменных окружения используются большие1 буквы. Однако вы можете использовать и строчные, если это вам удобно. Утилита env позволяет изменять и выводить такие переменные. Синтаксис env {переменная = значение} * [команда] Утилита env присваивает значения определенным переменным окружения и затем выполняет дополнительную команду, используя переопределенные таким образом переменные. Если переменные или команда не определены, выводится список текущих переменных окружения. 1 Прописные. — Ред.
Для примера создадим локальную переменную с именем database, которую затем пометим для экспорта. После этого создадим дочерний shell, который унаследует копию переменных окружения родителя: $ export ...вывести мой текущий экспорт. export TERM ...установлена в моем стартовом файле .profile. $ DATABASE=/dbase/db ...создать локальную переменную. $ export DATABASE ...отметить ее для экспорта. $ export export DATABASE ...заметьте, что она была добавлена. export TERM $ env DATABASE=/dbase/db . . .вывести переменные окружения. НОМЕ=/home/glass LOGNAME=glass PATH=:/usr/ucb:/bin:/usr/bin SHELL=/bin/sh TERM=vtlOO USER=glass $ sh ...создать дочерний shell. $ echo $DATABASE /dbase/db ...копия была унаследована. $ ...завершить subshell. $ _ Переменные только для чтения Команда readonly позволяет защищать переменные от модификации. Синтаксис readonly {переменная} * Команда readonly устанавливает статус "только для чтения" для указанных в качестве аргументов программы переменных, тем самым защищая их от изменений. Если переменные не определены, выводится список текущих переменных с таким статусом чтения. Копии экспортируемых переменных не наследуют статус "только для чтения".
В следующем примере переменная устанавливается в режим, доступный только для чтения, после чего экспортируется, а мы посмотрим на статус копии — она не унаследует разрешения только на чтение: $ password=Shazam. ...определить локальную переменную. $ echo $password Shazam ...вывести ее значение. $ readonly password ...защитить ее. $ readonly readonly password ...вывести все переменные со статусом readonly. $ password=Phoombah password: is read only ...попытаться модифицировать ее. $ export password ...экспортировать переменную. $ password=Phoombah ...попытаться модифицировать ее. password: is read only $ sh ...создать subshell. $ readonly $ echo $password Shazam ...экспортированная переменная не readonly. ... ее значение было скопировано корректно. $ password=Alacazar $ echo $password Alacazar . . .но ее величина может быть изменена. ...вывести ее значение. $ AD ...завершить subshell. $ echo $password Shazam ...вывести оригинальное значение. $ _ Предопределенные локальные переменные В дополнение к предопределенным локальным переменным ядра, Bourne shell определяет собственные локальные переменные, перечисленные в табл. 5.2. Таблица 5.2. Предопределенные локальные переменные Bourne shell Имя Описание $0 Индивидуально указанный список всех позиционных параметров $# Число позиционных параметров $ ? Код возврата последней команды
Таблица 5.2 (окончание) Имя Описание $ 1 ID процесса последней фоновой команды $- Текущие опции shell, назначенные в командной строке или встроенным набором команд $$ ID процесса, выполняемого shell Ниже приведен небольшой скрипт, в котором компилятор С (сс) вызывается с переданным в качестве параметра несуществующим файлом, вследствие чего операция терпит неудачи и возвращает код ошибки: $ cat script.sh ...посмотреть скрипт. echo there are $# command line arguments: $@ cc $1 # компилировать первый аргумент. echo the last exit value was $? # вывести код возврата. $ script.sh nofile tmpfile ...выполнить скрипт. there are 2 command line arguments: nofile tmpfile cc: Warning: File with unknown suffix (nofile) passed to Id Id: nofile: No such file or directory i the last exit value was 4 ...сообщение об ошибке от cc. $ _ Следующий пример иллюстрирует использование имени $! для принудительного завершения последнего фонового процесса: $ sleep 1 000 & ... создает фоновый процесс. 29455 ...ID фонового процесса. $ kill $! ..."убить” его! 29455 Terminated $ echo $! ...ID процесса все еще помнится. 29455 $ Предопределенные переменные окружения Базовые переменные окружения, о которых мы говорили в главе 4, дополняются перечисленными в табл. 5.3 переменными.
Таблица 5.3. Предопределенные переменные окружения Bourne shell Имя Описание $IFS Когда shell разбирает командную строку перед ее выполнением, он использует символы из этой переменной в качестве разделителей, ips обычно содержит пробел, символ табуляции и символ перевода строки $PS1 Переменная содержит значение приглашения командной строки (по умолчанию используется $). Чтобы заменить приглашение, просто присвойте PS1 новое значение $PS2 Переменная содержит значение вторичного приглашения командной строки (по умолчанию >), которое отображается, когда shell требует ввода дополнительной информации. Чтобы изменить приглашение, присвойте PS2 новое значение $SHENV Если эта переменная не установлена, shell ищет файл запуска .profile в домашнем каталоге пользователя, когда создается новый shell. Если переменная определена, то командный интерпретатор ищет каталог, указанный в shenv Ниже приведен небольшой пример, иллюстрирующий использование первых трех переменных. Здесь приглашение shell, сохраненное в переменной psi, устанавливается в отличное от заданного по умолчанию. Кроме того, разделитель полей задается равным двоеточию (:), а предыдущее значение сохраняется в локальной переменной. Далее производится проверка вывода приглашения: $ PSI = "sh?" sh? old.IFS=$IFS sh? IFS-":" sh? ls:*.c badguy.c number.c fact2.c number2.c sh? IFS=$oldIFS sh? string="a long\ > string" sh? echo $s tring a long string sh? PS2 = "??? " sh? string="a long\ ??? string" sh? echo $string a long string sh? ...установить новое первичное приглашение. ...запомнить старое значение IFS. ...заменить разделитель слов на двоеточие. ...это выполняется хорошо! open.с trunc.c writer.с reader.с who.с ...восстановить старое значение IFS. ...определить строку через две строки терминала ...> — это вторичное приглашение. ...посмотреть значение string. ...изменить вторичное приглашение. ...задать длинную строку. ...??? — это новое вторичное приглашение. ...посмотреть значение string. \
Арифметические действия Сам Bourne shell не поддерживает арифметических операций, однако утилита ехрг помогает устранить этот недостаток. Синтаксис ехрг выражение Утилита ехрг вычисляет выражение и посылает результат на стандартный вывод. Все компоненты выражения должны быть разделены пробелами, а все метасимволы shell должны завершаться символом \ (экранироваться). Выражение может давать результат в числовом или строчном виде в зависимости от операторов, которые оно содержит. Результат выражения может быть присвоен переменной shell соответствующим использованием команды замещения. Допустимо построение выражения с помощью перечисленных ниже бинарных операторов к целочисленным операндам, сгруппированным в порядке увеличения приоритета: Оператор Описание *, /, % Умножение, деление, остаток +> ~ Сложение, вычитание :=j i= Операторы сравнения & 1 Логическое И Логическое ИЛИ Круглые скобки могут использоваться для явного управления порядком вычисления (однако их также нужно экранировать). Утилита также поддерживает следующие строчные операторы: Оператор Описание строка: регулярноеВыражение Обе формы возвращают длину строки match строка регулярноеВыражение строка, если выражения совпадают, иначе возвращают ноль substr строка начало длина Возвращает подстроку строки, начинающейся с индекса на чало и состоящей из длина символов
(окончание) Оператор Описание index строка списокСимволов Возвращает индекс первого символа в строке. который появляется В спискеСимволов length строка Возвращает длину строки Формат регулярного выражения определен в Приложении. Следующий пример иллюстрирует некоторые функции ехрг и использует замещение команд: $ Х=1 $ х=' ехрг $х + 1' $ echo $х 2 $ х= 'ехрг 2 + 3 \* 5' $ echo $х 17 $ echo 'ехрг \(2 + 3\) \* 5' 25 $ echo 'ехрг length "cat”' 3 $ echo 'ехрг substr "donkey” 4 3' key $ echo 'expr index "donkey" "ke"' 4 $ echo 'expr match "Smalltalk" *. 9 $ echo 'expr match "transputer" 1 0 $ echo 'expr "transputer" : ’★.Ik 0 $ echo 'expr \ ( 4\ > 5 \ ) ' ...начальное значение x. ...увеличить x на единицу. ... * перед +. ...перегруппировать. ...найти длину cat. ...выделить подстроку. ...определить позицию подстроки. ★1k’' ...проверить ...на совпадение. ★.!&'' ...проверить ... на совпадение. г' ... проверить ...на совпадение. ...больше ли 4 > 5?
О $ echo 'ехрг \ ( 4 \ > 5\) \/ \ (6 \ < 7 \ ) ' .. .больше ли 4 > 5 или 6 < 7? 1 $ Условные выражения Структуры управления, подробно описанные в следующем разделе, часто используются для перехода в зависимости от значения логического выражения, возвращающего истину или ложь. Утилита test поддерживает достаточно большой набор UNIX-ориентированных выражений, которые являются подходящими для большинства случаев. Выражение test может иметь формы, показанные в табл. 5.4. Внимание Утилита очень требовательна к синтаксису выражений; пробелы, указанные в этой таблице, нельзя опускать. Синтаксис test выражение [выражение] Эквивалентной формой, используемой в некоторых UNIX-системах, является заключение выражения в круглые скобки. Утилита test возвращает 0, если выражение вычисляет истину; иначе возвращает код выхода, отличный от 0. Код возврата обычно используется структурами управления shell, выполняющими переходы по условию. Некоторые Bourne shell выполняют утилиту test, как встроенную команду. В таком случае они также поддерживают вторую форму вычисления. Однако чтобы эта форма работала, скобки должны быть отделены пробелами. Таблица 5.4. Выражения, используемые утилитой test Форма -Ь имяФайла -с имяФайла Описание Возвращает истину, если имяФайла существует в качестве специального блочного файла Возвращает истину, если имяФайла существует в качестве специального символьного файла
Таблица 5.4 (продолжение) Форма Описание -d имяФайла Возвращает истину, если имяФайла существует в качестве каталога -f имяФайла Возвращает истину, если имяФайла существует, как не каталог -g имяФайла Возвращает истину, если имяФайла существует в качестве файла "установки ID группы" -h имяФайла Возвращает истину, если имяФайла существует в качестве символической связи -к имяФайла Возвращает истину, если имяФайла существует и имеет установленный "промежуточный бит округления" -1 строка Длина строки -п строка Возвращает истину, если строка содержит, по крайней мере, один символ -р имяФайла Возвращает истину, если имяФайла существует в качестве именованного конвейера -г имяФайла Возвращает истину, если имяФайла существует в качестве читаемого файла -s имяФайла Возвращает истину, если имяФайла содержит, по крайней мере, один символ -t fd Возвращает истину, если дескриптор файла fd связан с терминалом * -и имяФайла Возвращает истину, если имяФайла существует в качестве файла "установки ID пользователя" -w имяФайла Возвращает истину, если имяФайла существует в качестве файла для записи -х имяФайла Возвращает истину, если имяФайла существует как исполняемый файл — z строка Возвращает истину, если строка не содержит никаких символов strl - str2 Возвращает истину, если strl равна str2 strll - str2 Возвращает истину, если strl не равна str2 строка Возвращает истину, если строка не нулевая intl-eq int2 Возвращает истину, если целое inti равно целому int2 inti -ne int2 Возвращает истину, если целое inti не равно целому int2 inti -gt int2 Возвращает истину, если целое inti больше целого int2 inti -ge int2 Возвращает истину, если целое inti больше или равно целому in t2
Таблица 5.4 (окончание) Форма Описание inti -It int2 Возвращает истину, если целое inti меньше целого int2 inti -le int2 Возвращает истину, если целое inti меньше или равно целому in t2 ! выражение Возвращает истину, если выражение ложно exprl -a expr2 Возвращает истину, если выражения exprl и ехрг2 оба истинны exprl —о expr2 Возвращает истину, если exprl или ехрг2 истинно \ (выражение \) Экранированные круглые скобки используются для группирования выражений Структуры управления Bourne shell поддерживает широкий диапазон структур управления, которые делают командный интерпретатор удобным инструментом программирования высокого уровня. Программы shell обычно хранятся в скриптах и, как правило, используются для автоматизации задачи инсталлирования и обслуживания. В следующих разделах в алфавитном порядке будут описаны структуры управления. Однако для их понимания желательно быть знакомым хотя бы с одним языком программирования высокого уровня. case... in... esac Команда case поддерживает многопозиционный переход, базирующийся на значении отдельной строки. Синтаксис case выражение in шаблон { | шаблон} *) списокКоманл esac выражение определяет строку, возможно, содержащую групповые символы, сравниваемую с шаблоном. списокКоманл включает одну или нескольких команд shell. Командный интерпретатор вычисляет выражение, а затем сравнивает его по порядку с каждым шаблоном. Когда первое соот
ветствие шаблону найдено, выполняется связанный с ним списокКоманд. Далее shell переходит на строку esac. Шаблоны в последовательности отделяются символом | ("или") и связаны с одним и тем же набором действий. Если shell не находит совпадения, выполняется esae. echo ’your choice? \c* read reply echo case $reply in "I") date r r "2”|”3") pwd r r ”4”) stop=l r r *) echo illegal choice Ниже приведен пример скрипта menu.sh, который использует структуру управления case (скрипт также доступен в сети; см. Введение для получения дополнительной информации): #! /bin/sh echo menu test program stop=0 # переустановить флаг прекращения цикла, while test $stop -eq 0 # цикл до выполнения, do cat « ENDOFMENU # вывести меню. 1 : print the date. 2 , 3: print the current working directory. 4 : exit ENDOFMENU echo # приглашение. # читать ответ. # отклик процесса. # вывести дату. # вывести рабочий каталог. # установить флаг прекращения цикла. # по умолчанию. # ошибка. esac done
Пример работы скрипта menu.sh: $ menu.sh menu test program 1 : print the date. 2r 3: print the current working directory. 4 : exit your choice? 1 Thu Feb 5 07:09:13 CST 1998 1 : print the'date. 2r 3: print the current working directory. 4 : exit your choice? 2 /home/glass 1 : print the date. 2 , 3: print the current working directory. 4 : exit your choice? 5 illegal choice 1 : print the date. 2 , 3: print the current working directory. 4 : exit your choice? 4 $ for... do... done Структура for позволяет выполнить несколько раз список команд, используя различные значения переменной цикла во время каждой итерации. Синтаксис for имяПеременной [ in {списокСлов} * ] do списокКоманл done Команда for циклически присваивает значению имяПеременной каждое из слов параметра списокСлов, выполняя команды из аргумента список-команд после каждой итерации. Если списокСлов не представлен, вместо
него используется $@. Команда break заставляет цикл завершиться, а команда continue переводит его к следующей итерации. Ниже приведен пример скрипта со структурой управления for: $ cat for.sh ...посмотреть скрипт. for color in red yellow green blue do echo one color is $color done $ for.sh ...выполнить скрипт. one color is red one color is yellow one color is green one color is blue $ _ if... then... fi Команда if поддерживает вложенные условные ветвления. Синтаксис if условие! then списокКоманд! elif условие2 ...необязательнаяг ...elif может повторяться несколько раз. then списокКома нд2 else ...необязательная, else может появляться один раз. списокКома ндЗ fi Выполняются команды из условие!. Если они завершаются удачно, выполняются команды ИЗ списокКоманд!. ЕСЛИ ПОСЛеДНЯЯ команда условие! завершается неудачно, и если указан один или более el if-компонентов (возвращающих истину), выполняются команды, указанные после then. Если команда возвращает сигнал об ошибке, и в скрипте задано условие else, выполняются команды, указанные после него. 9 Зак. 786
Вот пример скрипта, который использует структуру управления if: $ cat if.sh ...посмотреть скрипт, echo ’enter a number: \с’ read number if [ $number -It 0 ] then echo negative elif [ $number -eq 0 ] then echo zero else echo positive fi $ if.sh ...выполнение скрипта, enter a number: 1 positive $’ if.sh ...выполнить скрипт снова, enter a number: -1 negative $ trap trap позволяет задать команду, которая должна быть выполнена, когда командный интерпретатор получает сигнал конкретного значения. Синтаксис trap.[[команда ] {сигнал} + ] Команда trap предписывает shell выполнять команду всякий раз, когда получен любой из пронумерованных сигналов. Если поступило несколько сигналов, они перехватываются в числовом порядке. Если значение сигнала 0 определено, тогда команда выполняется при завершении shell. Если команда опущена, то значения пронумерованных сигналов сбрасываются в их первоначальное значение. Если команда — пустая строка, то пронумерованные сигналы игнорируются. Выполнение trap без аргументов приводит к выводу списка всех сигналов и их trap-установок. (Для получения дополнительной информации о сигналах и их действиях по умолчанию см. главу 13.)
Вот пример скрипта, который использует структуру управления trap: $ cat trap.sh ...посмотреть скрипт. trap ’echo Control-C; exit 1’ 2 # выполнить <Ctl>+<C> (сигнал 2) while 1 do echo infinite loop sleep 2 # приостановиться на две секунды, done $ trap.sh ...выполнить скрипт. infinite loop infinite loop ... я нажал <Ctrl>+<C>. Control-C ...выведено командой echo. $ _ Обратите внимание, что после нажатия комбинации клавиш <Ctrl>+<C> shell выполнил команду echo, сопровождаемую командой exit. until... do... done Команда until выполняет некоторую серию команд до тех пор, пока другая серия команд2 не станет истинной. Синтаксис until условие do списокКоманл done Команда until выполняет команды из списка условие и прекращается, если условие истинно. Иначе, выполняются команды из аргумента списокКоманл, и процесс повторяется. Если списокКоманл пуст, ключевое слово do должно быть опущено. Команда break вызывает немедленное завершение цикла, а команда continue переводит его к следующей итерации. Вот пример скрипта, который использует структуру управления until: $ cat until.sh ...вывести скрипт. 2 Иными словами, условие. — Ред.
until [ $х -gt 3 ] do echo x = $x x='expr $x + 1' done $ until.sh ...выполнить скрипт. x = 1 x = 2 x = 3 $ while... done Команда while выполняет серию команд до тех пор, пока другая серия3 выполняется успешно. Синтаксис while условие do списокКома нд done Команда while выполняет условие и прекращается, если последняя команда из него заканчивается неудачно. Иначе выполняется списокКо-манд, и процесс повторяется. Если списокКоманд пуст, ключевое СЛОВО do должно быть опущено. Команда break вызывает прерывание цикла, а команда continue переводит его к следующей итерации. Ниже приведен пример скрипта, который содержит структуру управления while и демонстрирует генерацию небольшой таблицы умножения: $ саt multi.sh ... вывести скрипт. if [ "$1" -eq "" ]; then echo "Usage: multi number" exit fi x=l # установка значения для внешнего цикла 3 Условие. — Ред.
while [ $x -le $1 ] # внешний цикл do y=l # установка значения для внутреннего цикла while [ $у -1е $1 ] do # генерирует один вход таблицы echo 'ехрг $х \* $у' " \с" у = 'ехрг $у + 1' # обновить счетчик внутреннего цикла done echo # пустая строка x='expr $х + 1' # обновить счетчик внешнего цикла done $ multi.sh 7 ...выполнить скрипт. 1 2 3 4 5 6 7 2 4 6 8 10 12 14 3 6 9 12 15 18 21 4 8 12 16 20 24 28 5 10 15 20 25 30 35 6 12 18 24 30 36 42 7 14 21 28 35 42 49 $ Пример проекта: track Для иллюстрации основной части возможностей Bourne shell воспользуемся небольшим скриптом track. Этот скрипт отслеживает регистрацию и выход из системы пользователей, генерируя простой отчет о пользовательских сессиях. Скрипт использует следующие утилиты: □ утилита who выводит список текущих пользователей системы; □ утилита grep фильтрует текстовые строки, которые соответствуют указанному шаблону; П утилита diff показывает различия между двумя файлами; □ утилита sort сортирует текстовый файл; □ утилита sed выполняет предварительно запрограммированное редактирование файла; □ утилита ехрг вычисляет значение выражения; П утилита cat выводит содержимое файла; □ утилита date отображает текущее время;
П утилита rm удаляет файл; П утилита mv перемещает файл; □ утилита sleep устанавливает паузу на несколько секунд. Утилиты grep, diff, sort и sed описаны в главе 3\ о другой утилите (who) рассказано в главе 9. Рассмотрим пример работы этого скрипта: $ track -пЗ Ivor -t200 ...отследить сессию Ивора. track report for ivor: ...первоначальный вывод. Login ivor ttyp3 Feb 5 06:53 track report for ivor: ...Ивор вышел из системы. Logout ivor ttyp3 Feb 5 06:55 ...завершить программу, используя комбинацию клавиш <Ctrl>+<C>. stop tracking ...сообщение прекращения программы. $ Синтаксис track [-псчетчик] [-tnaysa] Ю_пользователя Скрипт track отслеживает сессии входа и выхода указанного пользователя. С периодичностью в указанное паузой число секунд track просматривает систему и фиксирует зарегистрированных в системе. Если указанный пользователь зарегистрировался или вышел из системы за время последнего просмотра, эта информация передается на стандартный вывод. Скрипт работает, пока не сбросится счетчик количества просмотров. По умолчанию пауза равна 20 секундам, а счетчик 10 000 просмотров. Обычно этот скрипт выполняется в фоновом режиме с переадресацией стандартного вывода в файл. Программа track состоит из трех файлов: □ track — главный скрипт Bourne shell; □ Track.sed — sed-скрипт для редактирования вывода утилиты diff; П track.cleanup — маленький скрипт, который очищает временные файлы. Алгоритм работы описанного скрипта может быть разделен на три части. 1. Разбор командной строки и установка значений трех локальных переменных: user, pause и loopcount. Если произошли какие-нибудь ошибки, скрипт завершается выводом сообщения о них.
2. Затем устанавливается перехват двух событий: завершение скрипта и int-(<Ctrl>+<C>) или QUiT-сигналы (<Ctrl>+<\>). Если был получен сигнал выхода quit, инициализируется событие завершения скрипта. Скрипт очистки получает ID процесса главного скрипта в качестве единственного аргумента и удаляет временные файлы. 3. Далее цикл выполняется заданное количество раз, сохраняя отфильтрованный список текущих пользователей во временном файле .track.new.55, где 55 — ID процесса скрипта. Затем файл сравнивается с последней версией вывода, сохраненного в файле .track.old.55. Если строка находится в новом файле, а не в старом, это означает, что пользователь, скорее всего, зарегистрировался в системе. Если строка размещена в старом файле, пользователь покинул систему. Если после работы diff-файл вывода имеет длину, отличную от нуля, то информация передается утилите sed и выводится. После чего скрипт делает паузу на указанное количество секунд и продолжает выполнять цикл. Листинг двух пропущенных через утилиту diff наборов данных, переданных ей утилитой who, иллюстрируется следующей сессией: $ cat track.new.1112 ...новый вывод от who. glass ttypO Feb 4 23:04 glass ttyp2 Feb 4 23:04 $ cat track.old.1112 glass ttypO Feb 4 23:04 glass ttypl Feb 4 23:06 ...старый вывод от who. $ diff track.new.1112 track.old.1112 2c2 < glass ttyp2 Feb 4 23:04 > glass ttypl Feb 4 23:06 $ . ...изменения. track.sed Скрипт track.sed удаляет все строки, которые начинаются с цифры или ", а затем подставляет символ < вместо login и символ > вместо logout. /А[0-9].*/d /А---/d s/A</login/ s/A>/logout/
track.cleanup echo stop tracking rm -f .track.old.$1 .track.new.$1 .track.report.$1 track pause=20 # пауза по умолчанию между сканированиями loopCount=10000 # счетчик числа сканирований, по умолчанию error=0 # флаг ошибки for arg in $* # разбор аргументов командной строки do case $arg in \ -t*) # время pause='expr substr $arg 3 10' # извлечение числа -n*) # счетчик сканирований loopCount='ехрг substr $arg 3 10' # извлечение числа f г *) user=$arg # имя пользователя esac done if [ ! "$user" ] # проверка, был ли найден ID пользователя then error=l fi if [ $error -eq 1 ] # вывести ошибку, если ошибка найдена then cat « ENDOFERROR # вывести сообщение использования usage: track [-n#] [-t#] userid ENDOFERROR exit 1 # завершить shell fi trap ’track.cleanup $$; exit $exitCode’ 0 # trap в режиме выхода trap ’exitCode=l; exit’ 23 # trap в режиме INT/QUIT > .track.old.$$ # обнулить старый track-файл. count=0 # число сканирований так же while [ $count -It $loopCount ]
do who grep $user | sort > .track.new.$$ # сканировать систему diff .track.new.$$ .track.old.$$ | \ sed -f track.sed > .track.report.$$ if [ —s .track.report.$$ ] then echo track report for ${user): cat .track.report.$$ fi mv .track.new.$$ .track.old.$$ sleep $pause count='expr $count +1' done exitCode=0 # изменяется только отчет # вывести отчет # запомнить текущее состояние # подождать немного # обновить счетчик сканирования # установить код возврата Дополнительные встроенные команды Bourne shell поддерживает несколько специализированных встроенных команд. Некоторые, имеющие отношение к структурам управления, мы уже обсудили ранее. В следующих разделах в алфавитном порядке будут описаны остальные встроенные команды. Команда чтения: точка (.) Чтобы выполнить содержимое текстового файла, не запуская subshell, как это происходит при выполнении скрипта, командный интерпретатор использует точку (.), за которой следует имя файла. Файл не должен быть исполняемым. Команда удобна, если пользователь изменяет файл .profile и желает выполнить его повторно. Пример: $ cat .profile ...предположим, что .profile только что редактировался. TERM=vtlOO export TERM $ . .profile ...выполнить его повторно. Обратите внимание, что subshell не создан, любые локальные переменные являются локальными переменными текущего shell.
Команда null Команда null обычно используется в соединении со структурами управления, о которых мы уже поговорили ранее, и не выполняет никакого действия. Она часто указывается в структурах case, чтобы обозначить пустой набор операторов, связанных с конкретной веткой. Синтаксис null Команда null не выполняет никакого действия. Установка опций shell: set Команда зе1позволяет управлять некоторыми опциями shell (табл. 5.5). Таблица 5.5. Опции команды set Опция Описание е Если shell не выполняет команды с терминала или файла запуска, и команда терпит неудачу, тогда происходят системное прерывание err и выход п Принимает, но не выполняет команды. Опция не затрагивает диалоговые shell t Выполняет следующую команду и затем выходит и Генерирует ошибку, когда сталкивается с неустановленной переменной v Выводит команды shell, когда они читаются х Выводит команды shell, когда они выполняются Выключает опции х и v и интерпретирует дальнейшие символы, как аргументы Опции -х и -v полезны в отладке shell-скрипта, т. к. они выводят строки до и после обработки переменной, группового символа и метасимволов замещения команды. Синтаксис set -ekntuvx {аргументы}* Команда set позволяет пользователю выбирать опции shell. Любые указанные аргументы назначаются позиционным параметрам $1, $2 и т. д., переписывая их текущие значения.
В приведенном ниже примере проиллюстрировано использование различных опций команды set: $ cat script.sh ...посмотреть скрипт. set -vx а.с # установить режим отладки и перезаписать $1. Is $1 # получить доступ к первому позиционному параметру. set - # выключить трассировку. echo goodbye $USER echo $notset set -u # неустановленные переменные будут теперь генерировать ошибку. echo $notset # генерировать ошибку. $ script.sh Ь.с ...выполнить скрипт. Is $1 ...вывод от опции -v. + Is а.с ...ВЫВОД ОТ ОПЦИИ -X. а.с ...обычный вывод. set - . ...ВЫВОД ОТ ОПЦИИ -V. + set - ...ВЫВОД ОТ ОПЦИИ -X. goodbye glass ...обычный вывод. script.sh: notset: parameter not set ...доступ к неустановленной ...переменой. $ Усовершенствования В дополнение к новым функциями, которые уже были описаны ранее, в Bourne shell усовершенствованы следующие общие для всех командных интерпретаторов возможности: □ перенаправление ввода/вывода; П использование последовательностей команд. Перенаправление Помимо использования обычных возможностей перенаправления, Bourne shell позволяет дублировать, закрывать и переадресовывать произвольные каналы ввода/вывода. Вы можете связывать стандартный дескриптор ввода файла (0) с файловым дескриптором п, используя следующий синтаксис: $ команда <& п ...связать стандартный ввод. Точно так же можно связывать стандартный дескриптор вывода файла (1) с дескриптором файла п при помощи последовательности: $ команда >& п ...связать стандартный вывод.
Для закрытия каналов стандартного ввода и вывода служит синтаксис: $ команда <&- $ команда >&- ...закрывает стандартный ввод и выполняет команду. ...закрывает стандартный вывод и выполняет команду. Вы можете использовать файловый указатель перед любым метасимволом, который должен применяться вместо 0 (для переназначения ввода) или 1 (для переназначения вывода). Очень часто дескриптор файла 2 соответствует стандартному каналу ошибки. Приведенный ниже пример иллюстрирует использование описанных выше возможностей переназначения. Утилита man всегда пишет "Reformatting page. Wait" ("Переформатирование страницы. Ждите") в стандартный канал ошибки, когда начинает работать, и "done" ("завершено") — после завершения. Если вы переадресовываете только стандартный вывод, эти сообщения появляются на терминале. Для переадресации стандартного канала ошибки в отдельный файл используйте последовательность переназначения 2>. Для записи ошибок в файл стандартного входа указывайте последовательность: 2>*1. Пример: $ man Is > Is.txt Reformatting page. $ man Is > Is.txt $ cat err. txt Reformatting page. $ man Is > Is.txt $ head -1 Is.txt Reformatting page. ...направить стандартный вывод в Is.txt. Wait... done ...из стандартного канала ошибки. 2> err.txt ...направить канал ошибки в err.txt. ...посмотреть файл. Wait... done 2>&1 ...связать канал ошибки со стандартным . . .каналом вывода. ...посмотреть первую строку ls.txt. $ Последовательности команд Если группа команд помещена между круглыми скобками, они выполняются дочерним shell. Bourne shell также позволяет группировать команды, обрамляя их фигурными скобками, в которых разрешено указывать перенаправление ввода/вывода и конвейеризацию внутри группы, при этом команды выполняются непосредственно родительским shell. После открывающей фигурной скобки должен следовать пробел, а после закрывающей — точка с запятой. В представленном ниже примере первая команда cd не затрагивает текущий рабочий каталог shell, т. к. она работает внутри subshell, а вызванная второй раз выполняется shell: $ pwd ...вывести текущий рабочий каталог. /home/glass
$ (cd /; pwd; Is | wc -1) 22 $ pwd /home/glass $ { cd /;>pwd; Is I wc -1; } 22 $ pwd $ ...подсчитать файлы при помощи subshell. ...мой shell не перемещался. ...подсчитать файлы при помощи shell. ...мой shell переместился. Опции командной строки В табл. 5.6 описаны параметры командной строки, поддерживаемые Bourne shell. Таблица 5.6. Параметры командной строки Bourne shell Опция Описание -с строка Создает shell для выполнения командной строки строка -s Создает shell, который читает команды со стандартного ввода и посылает сообщения shell в стандартный канал ошибки -i Создает диалоговый shell; подобна опции -s, за исключением того, что все сигналы sigterm, sigint и sigquit игнорируются (для информации о сигналах см. главу 13) Обзор главы Перечень тем В данной главе мы рассмотрели: □ создание стартового файла Bourne shell; □ создание и доступ к переменным командного интерпретатора; □ арифметические операции; □ условные выражения;
□ шесть структур управления; □ пример проекта для отслеживания сессий пользователя с системой; □ несколько встроенных команд; □ усовершенствования базовых функций. Контрольные вопросы 1. Кто автор Bourne shell? 2. Опишите обычное использование встроенной переменной $$. 3. Как легче всего повторно выполнить пользовательский файл .profile? 4. Какой метод применяется для передачи значения переменной shell в subshell? 5. Какие возможности отладки предоставляет Bourne shell? Упражнения 5.1. Напишите утилиту shheip, которая работает так, как описано ниже. Синтаксис shheip [-к] {команда}* Утилита shheip выводит справку об указанной команде Bourne shell. Опция -к ВЫВОДИТ каждую команду, КОТОрая известна shheip. Вот пример shheip в действии: $ shheip null ...запрос помощи о null. The null command performs no operation. $ _ Удостоверьтесь, что ваша утилита показывает соответствующее сообщение об ошибке, если указана недопустимая команда. Желательно, чтобы текст сообщения справки о каждой команде хранился в отдельном файле, а не внутри скрипта shheip. Если вы решите справку внутри скрипта, пробуйте использовать функцию перенаправления документа в буфер.4 [Уровень: легкий.] 4 См. главу 4. — Ред.
5.2. Напишите утилиту junk. Синтаксис junk [-1] [-p] {имяФайла}* Утилита junk является аналогом утилиты rm. Вместо удаления файлов, она перемещает их в каталог junk, находящийся в домашнем каталоге пользователя. Если .junk не существует, он автоматически создается. Опция -1 выводит текущее содержимое каталога .junk, а опция -р производит очистку этого каталога. Вот пример работы скрипта: $ Is -1 reader.с ...вывести существующие файлы. -rw-r—г— 1 glass 2580 May 4 19:17 reader.с $ junk reader.с ...выполнить j unk! $ Is -1 reader.с reader.c not found ...подтвердить, что он был перемещен. $ junk badguy.c, $ junk -1 ...выполнить junk с другим файлом. ...вывести содержимое каталога .junk. -rw-r—r— 1 glass 57 Мау 4 19:17 badguy.c -rw-r—r— 1 glass 2580 Мау 4 19:17 reader.с $ junk -p ...очистить .junk. $ junk -1 $ ...вывести .junk. Не забывайте подробно комментировать ваш скрипт. [Уровень: средний.] 5.3. Измените скрипт junk так, чтобы он управлялся с помощью меню. [Уровень: легкий.] Проекты. 1. Напишите скрипт ghoul, который трудно "убить". Когда скрипт получает сигнал sigint (от нажатия комбинации клавиш <Ctrl>+<C>), он должен создать перед "смертью" свою копию. Таким образом, каждый раз, когда пользователь пытается ликвидировать ghoul, запускается его копия. Однако он может быть "убит" сигналом sigkill (—9). [Уровень: средний.]
2. Спроектируйте утилиту телефонной книги, которая позволяет просматривать и изменять алфавитный список имен, адресов и телефонных номеров. Используйте утилиты, описанные в главе 3, такие как awk и sed, для поддержки и редактирования информации в файле телефонной книги. [Уровень: высокий.] 3. Постройте утилиту управления процессом, которая позволяет ликвидировать его на основе информации об использовании процессора, ID пользователя, общего количества истекшего времени и т. д. Этот вид утилиты был бы особенно полезен для системных администраторов (см. главу 15). [Уровень: высокий.]
Глава 6 Korn shell Мотивация Korn shell, написанный Дэвидом Корном (David Korn), является более мощной версией Bourne shell и имеет усовершенствования в области управления заданиями, редактирования командной строки и программных возможностей. Это позволило командному интерпретатору Korn shell завоевать большую популярность в мире UNIX-систем. Предпосылки Для понимания этой главы вам необходимо прочитать предыдущую и поэкспериментировать с Bourne shell. Задачи В данной главе рассмотрим специфичные для Korn shell возможности. Изложение По сложившейся уже традиции материал главы представлен в виде нескольких примеров UNIX-сессий. Команды shell Мы рассмотрим следующие команды shell: alias jobs select bg kill typeset fc let unalias fg print function return
Общие сведения о Korn shell Korn shell поддерживает все функции Bourne shell, описанные в главе 5, и содержит следующие дополнительные функции (рис. 6.1): □ настройка команд с использованием псевдонимов; □ доступ к выполненным командам через механизм истории (vi- и emacs-подобные редактирующие возможности командной строки); □ функции; □ расширенное управление заданиями; □ несколько новых встроенных команд и усовершенствованные существующие. jobs bg fg <Ctrl>+<Z> Рис. 6.1. Функциональные возможности Korn shell Запуск Korn shell — это обычная С-программа, выполняемый файл которой хранится как /bin/ksh. Если вашим shell является /bin/ksh, его диалоговый режим активизируется автоматически, в процессе регистрации в UNIX. Можно также вызвать Korn shell вручную из скрипта или с терминала, используя команду ksh, которая имеет несколько опций командной строки. Последовательность запуска Korn shell различна для диалогового и недиалогового shell (табл. 6.1). Значение $env обычно устанавливается в скриптах $HOME/.kshrc и $HOME/.profile. После чтения файлов запуска диалоговый режим Korn shell отображает свое приглашение и ожидает ввода команды. Стандартное приглашение Korn shell — $, хотя оно может быть изменено путем установки локальной переменной psi, описанной в главе 5. Ниже
приведен пример Korn shell скрипта .profile, который выполняется один раз в начале каждой сессии: TERM=vtlOO; export TERM # тип моего терминала. ENV=-/.kshrc; export ENV # имя файла окружения. HISTSIZE=100; export HISTSIZE # помнить 100 команд. MAILCHECK=60; export MAILCHECK # секунды между проверками, set -о ignoreeof # не позволяет <Ctrl>+<D> завершить сессию, set -о trackall # ускорить поиск файла, stty erase ,ЛН* # установить символ возврата, tset # установить терминал. Таблица 6.1. Последовательность запуска Korn shell Шаг Тип командного интерпретатора Действие 1 Только диалоговый Выполняет команды из /etc/profile, если он существует 2 Только диалоговый Выполняет команды из $номе/. profile, если он существует 1 3 Оба Выполняет команды из $env, если он существует Некоторые из этих команд могут быть вам непонятны, но их значение станет ясным по мере чтения главы. Ниже показан пример Korn shell скрипта .kshrc, содержащего, как правило, полезную специфичную для этой оболочки информацию, используемую всеми shell, включая те, которые были созданы исключительно для выполнения скриптов: РАТН='.:-/bin:/bin:/usг/bin:/usг/local/bin:/gnuemacs’ PS1=’! $ export PSI # поместить номер команды в подсказку. alias h="fc -1” # установить полезные псевдонимы, alias ll="ls -1” alias rm=,,m -i" alias ccb^'ccLx" alias up="cdx .." alias dir="/bin/ls” alias ls="ls -aF" alias env="printenv|sort" # Функция, чтобы показывать путь и каталог при перемещении function cdx
{ if ’cd’ ’^G" then echo $PWD Is -aF fi } Каждый Korn shell, включая все subshell, выполняет этот скрипт при запуске. Псевдонимы Korn shell позволяет создавать собственные команды, используя псевдонимы (alias). Ниже проиллюстрирована работа команды alias: $ alias dir='ls -aF1 ...регистрировать псевдоним. $ dir ...то же самое, как напечатать Is -aF. ./ main2.c р.reverse.c reverse.h ../ main2.o palindrome.c reverse.old $ dir *.c ...то же самое, как напечатать Is -aF *.c. main2.c p.reverse.c palindrome.c $ alias dir ...определение dir. dir=ls -aF $ Синтаксис •alias [-tx] [слово [=строка]] Команда alias поддерживает простую форму настройки командной строки. Если слово псевдонима равно строке, а вы вводите команду, начинающуюся с этого словз^ первое появление словз заменяется строкой^ и команда обрабатывается повторно. Если слово или строка не определены, то выводится список всех текущих псевдонимов shell. Если пользователь указывает только слово, то выводится строка, в настоящее время связанная со словом псевдонима. Если слово и строка указаны, shell добавляет данный псевдоним в свою коллекцию. В случае, если псевдоним для слова уже существует, он заменяется. Если строка замены начинается со слова, оно не обрабатывается повторно для псевдонимов, предотвращая, таким образом, бесконечные циклы. Если строка замены заканчивается пробелом, то следующее слово обрабатывается, как псевдоним.
Пример: $ alias ls='ls -aFr ...нет проблем. $ Is *.c ...аналогично Is -aF *.c. main2.c p.reverse.c palindrome.c $ alias dir='ls' ...определить dir через Is. $ dir ...аналогично Is -aF. , / main2.c p.reverse.c reverse.h • • / main2.о palindrome.c reverse.old $ Создание псевдонимов встроенных команд Все встроенные команды за исключением case esac select { do fi then } done for time elif function until else if while могут иметь псевдонимы. Удаление псевдонимов Чтобы удалить псевдоним, используйте команду unalias. Синтаксис unalias {слово}+ Команда unalias удаляет все указанные псевдонимы. Пример: $ alias dir . . .посмотреть существующий псевдоним. dir=ls $ unalias dir ...удалить псевдоним. $ alias dir ...попытка посмотреть псевдоним снова, dir alias not found $
Предопределенные псевдонимы В табл. 6.2 представлены предопределенные псевдонимы Korn shell. Их использование станет понятным по мере изучения главы. Например, псевдоним г очень удобен для повторного вызова предыдущих команд вместо длинной последовательности fc -е * Таблица 6.2. Предопределенные псевдонимы Korn shell Псевдоним Значение false let 0 functions typeset -f history fc -1 integer typeset -i nohup nohup r fc -e - true type whence -v hash alias -t Некоторые полезные псевдонимы В табл. 6.3 перечислены полезные псевдонимы. Другие интересные псевдонимы рассмотрены в главе 7. Таблица 6.3. Некоторые полезные псевдонимы Псевдоним Значение Описание rm rm -i Указывает rm о необходимости запрашивать подтверждение mv mv -i Указывает mv о необходимости запрашивать подтверждение Is Is -aF Указывает 1s о необходимости выводить подробную информацию env printenv | sort Демонстрирует отсортированный список переменных окружающей среды 11 Is -1 Позволяет получить более удобный расширенный листинг каталога
Псевдонимы путей Обычное использование псевдонимов — это сокращение полного путевого имени. Чтобы ускорить процесс поиска, воспользуйтесь псевдонимами путей. Все псевдонимы с опцией -t интерпретируются, как псевдонимы путей, и для того чтобы установить их первоначальное значение служит механизм поиска. В этом случае псевдоним пути заменяется его значением, таким образом сводя время поиска к минимуму. Если за опцией -t не указано ни одного псевдонима, выводится весь список текущих псевдонимов путей. Например: $ alias -t page ...определить псевдоним пути для раде. $ alias -t ...посмотреть все псевдонимы путей. page=/usr/ucb/page ...сохранено его полное путевое имя. $ Опция -о trackaii команды set (которая будет описана чуть позже в этой главе) предписывает shell отслеживать все команды автоматически, как показано в следующем примере: $ set -о trackaii ...все команды теперь заносятся в список псевдонимов. $ date ...выполнить date. Fri Feb 6 00:54:44 CST 1998 $ alias -t ...посмотреть все псевдонимы путей. date=/bin/date ...date теперь в списке псевдонимов. page=/usr/ucb/page $ _ - Так как значение псевдонима пути зависит от переменной $ратн, все псевдонимы путей вычисляются повторно всякий раз, когда она изменяется. Если переменная path не определена, значения всех псевдонимов путей оказываются установленными на пустой указатель, но при этом остаются в списке псевдонимов. Экспортируемые псевдонимы Чтобы сделать псевдоним доступным дочернему shell, необходимо пометить его как экспортируемый псевдоним, указывая в команде alias после опции -х. Если псевдонимы опущены, выводится список всех текущих экспортируемых псевдонимов. Внимание Если значение псевдонима изменено в дочернем shell, это не затрагивает значение первоначального псевдонима в родительском.
Пример использования экспортированного псевдонима: $ alias -х mroe='morer ...добавить экспортируемый псевдоним. $ alias -х ...посмотреть экспортируемые псевдонимы. autoload=typeset -fu ...стандартный псевдоним. ... ...другие псевдонимы выводятся здесь. ls=ls -F mroe=more ...псевдоним, который я только что добавил. . . . ...другие псевдонимы выводятся здесь. type=whence -v vi=/usr/ucb/vi $ cat test.ksh mroe main2. c ...скрипт, использующий новый псевдоним. $ test.ksh /★ MAIN2.C */ #include #stdio.h, #include "palindrome.h" main () ...выполнить скрипт, mroe работает! printf ("palindrome (\"cat\") = %d\n", palindrome ("cat")); printf ("palindrome (\"noon\") = %d\n", palindrome ("noon")); } $ История Korn shell хранит список команд, введенных с клавиатуры для того, чтобы их можно было отредактировать и выполнить повторно позднее. Эта функция известна как механизм истории. Встроенная команда fc (от англ, fix command) дает к ней доступ. Есть две формы f с. Первая, более простая, разрешает выполнить повторно указанный набор команд; вторая, более сложная, позволяет редактировать их перед повторным выполнением. Пронумерованные команды При использовании истории очень удобно знать номер команды, которую вы собираетесь ввести. Для этого укажите в переменной приглашения psi символ $ PS1=’! $ ' 103 $ ...установка, чтобы PS1 содержала символ !. ...приглашение для команды с номером 103.
Сохранение команд Korn shell записывает выполненные команды в количестве $histsize в файл $histfile. Если переменная окружения histsize специально не задана, ее значение по умолчанию равно 128. Если имя файла histfile не установлено или указанный файл не имеет прав записи, то по умолчанию используется файл $HOME/.sh_history, единый для всех Korn shell. Поэтому пока пользователь не изменит значение $histfile во время сессии, введенные команды доступны, как история для применения в следующей сессии. Ниже приведен файл, в котором сохранена история выполнения команд: $ echo $HISTSIZE ...установлена в .profile. 100 $ echo $HISTFILE ...не установлена предварительно. $ tail -3 $НСМЕ/.sh_history ...вывести последние 3 строки. echo $HISTSIZE echo $HISTFILE tail -3 $НОМЕ/.sh_history $ Повторное выполнение команд Команда fc позволяет повторно выполнять отработанные команды. Синтаксис fc -е - [стараяСтрока=новаяСтрока] префикс Вариант команды fc, которая повторно выполняет предыдущую команду, начинающуюся с префикса после необязательной замены первого появления старойСтроки новойСтрокой. префикс МОЖеТ быть ЧИСЛОМ, В ЭТОМ случае повторно выполняется пронумерованное событие. Пример: 360 $ fc -е - ech ...последняя команда, начинающаяся с ech. echo $HISTFILE 361 $ fc -e - FILE=SIZE ech ...заменить FILE на SIZE, echo $HISTSIZE 100 362 $ fc -e - 360 echo $HISTFILE 363 $ ...выполнить команду с номером 360.
Символ г — предопределенный псевдоним для команды fc -е позволяющий более удобным способом повторно выполнить команды: 364 $ alias г ...посмотреть на псевдоним г. r=fc -е 365 365 $ г 364 ...выполнить команду с номером 364. alias г r=fc -е - 366 $ Редактирование команд Korn shell позволяет выполнить предварительное редактирование команд до их повторного выполнения. Пример: 371 $ whence vi ...найти расположение vi. /usr/ucb/vi 372 $ FCEDIT=/usr/ucb/vi ...присвоить FCEDIT значение полного пути. 373 $ fc 371 ...редактировать команду с номером 371. ...войти в vi, редактировать команду для замены ...на whence Is, сохранить, выйти из vi. whence Is ...вывести отредактированную команду. /bin/Is ...вывод от отредактированной команды. 374 $ fc 371 373 ...редактировать команды с номерами 371-373. ...войти в vi и редактировать список последних трех команд. ...предположим, что я удалил первую строку, ...изменил оставшиеся строки ...на echo -n hi и echo there, и затем вышел. echo —n "hi " ...вывести редактированные команды, echo there hi there ...вывод от отредактированных команд. 375 $ Синтаксис fc [-е редактор] [-nlr] [начало] [конец] Данный вариант синтаксиса команды fc позволяет вызвать редактор с указанным диапазоном команд. После выхода из него выполняется отре
дактированный набор команд. Если редактор не указан, то используется редактор, путь к которому указан в переменной окружения fcedit. По умолчанию значение $fcedit равно /bin/ed, но желательно не использовать это значение. Неплохим вариантом будет /usr/ucb/vi (либо другой полный путь до редактора vi в вашей системе), или путь до иного редактора, с которым вы хорошо знакомы. Если другие опции не определены, редактор вызывается для предыдущей команды. После выхода из редактора Кот shell автоматически повторяет и выполняет ее сохраненную версию. Чтобы определить конкретную команду по ее индексу или префиксу, укажите номер или префикс как значение начала, но не задавайте значение конца. Задание диапазона команд осуществляется так: установите значение начала, чтобы выбрать первую команду в серии, и значение конца, чтобы выбрать последнюю. Отрицательный номер интерпретируется, как смещение по отношению к текущей команде. Опция -1 показывает выбранные команды, но не выполняет их. Если команды не определены, выводятся 16 последних. Опция -г инвертирует порядок отобранных команд, а опция -п запрещает генерацию их номеров при выводе. Пример использования опции -1: 376 $ fc -1 371 373 ...вывести команды с номерами. 371 $ whence vi 372 $ FCEDIT= /usr/ucb/vi 373 $ fc 371 377 $ fc -6 ...редактировать команду с номером 371. . . . редактировать команду для замены на whence Is и затем выйти, whence Is ...вывод отредактированной команды. /bin/Is ...вывод в результате выполнения команды. 378 $ Редактирование команд Korn shell содержит упрощенные встроенные версии редакторов vi, gmacs и emacs для редактирования текущей или предыдущей команд. Чтобы выбрать один из встроенных редакторов, задайте в переменных visual или editor строку, которая заканчивается именем одного из них. Следующий пример иллюстрирует выбор редактора vi: 380 $ VISUAL=vi ...выбрать встроенный редактор vi. 381 $
Встроенный редактор vi Дальнейшее описание предполагает, что вы немного знакомы с редактором vi, который был описан в главе 2. Начните редактирование текущей строки нажатием клавиши <Esc> для входа в командный режим vi, а затем сделайте необходимые изменения. Для перехода из командного режима в режим добавления или вставки нажмите соответственно клавишу <а> или <i>. Возврат в командный режим осуществляется нажатием клавиши <Esc>. Для повторного выполнения команды служит клавиша <Enter>. Внимание Будьте внимательны: если вы нажмете комбинацию клавиш <Ctrl>+<D>, находясь в редакторе, то завершите работу shell, а не редактора. В командном режиме ключевые последовательности попадают в одну из следующих категорий: □ стандартные ключевые последовательности vi (см. главу 2)\ □ дополнительные перемещения; □ дополнительный поиск; □ маскирование имени файла; □ замещение псевдонима. Дополнительные перемещения Клавиши <Т> (<к> или <->) и <J^> (<j> или <+>) позволяют выбрать соответственно предыдущие и следующие команды в списке истории. Это облегчает доступ к ним из встроенного редактора. Чтобы загрузить команду с конкретным номером, перейдите в командный режим, а затем введите номер команды, сопровождаемый нажатием клавиши <G>. Пример: 125 $ echo line 1 line 1 126 $ echo line 2 line 2 127 $ . . . в этом месте я нажал клавишу <Esc> с последующим двойным ....нажатием клавиши <к>. Это действие загрузило команду . . .с номером 125 в командную строку, которую я затем выполнил ...путем нажатия клавиши <Enter>. line 1 128 $ ...здесь я нажал клавишу <Esc> с последующим 125 <G>.
...Это действие загрузило команду с номером 125 в командную строку, ...которую я затем выполнил путем нажатия клавиши <Enter>. line 1 129 $ _ Дополнительный поиск Стандартные конструкции /строка и ?строка производят поиск команды соответственно назад и вперед по списку истории. Например: 127 $ echo line 1 line 1 138 $ echo line 2 line 2 139 $ ...в этом месте я нажал клавишу <Esc> с последующим вводом /ech, ...что загрузило последнюю команду, содержащую строку ech, ... в командную строку. ...Затем я нажал клавишу <п> для продолжения поиска ...очередной команды, которая совпала с искомой. В завершение ...я нажал клавишу <Enter> для выполнения команды. line 1 $ _ Использование маски для имени файла Использование звездочке (*) в командном режиме добавляет ее к слову, на котором расположен курсор, вследствие чего слово будет обработано, как маска для замены имен файлов. Если совпадения не произойдет, пользователь услышит звуковой сигнал. В противном случае слово будет заменено списком в алфавитном порядке всех соответствующих имен файлов, и редактор автоматически перейдет в режим ввода. Пример: 114 $ Is m* m m3 main.c mbox ml madness . c main, о mon.out m2 main makefile myFile 115 $ Is ma ... в этом месте я нажал клавишу <Esc> . . . и клавишу <*>, а затем клавишу <Enter>. 115 $ Is madness.c main main.с main.о makefile madness.c main.с makefile main main.о 116 $
Если вы используете символ равно (=) в командном режиме, редактор выводит пронумерованный список всех файлов, которые содержат текущее слово как префикс, а затем повторно выводит командную строку: 116 $ 1s та ...в этом месте я нажал клавишу <Esc>, ...а затем клавишу <=>. 1) madness.с 2) main 3) main.с 4) main.о 5) makefile 116 $ Is та_ ...возврат к первоначальной командной строке. При использовании символа косой черты (/) в командном режиме редактор пытается интерпретировать имя текущего файла. Если оно соответствует каталогу, добавляется /; иначе добавляется пробел. Пример: 116 $ 1s та ...в этом месте я нажал клавишу <Esc>, а затем клавишу <\>. ...завершения не было выполнено, поскольку "та" — ...это префикс больше, чем одного файла. 116 $ 1s так ...в этом месте я нажал клавишу <Езс>, а затем клавишу <\>. ... Редактор дописал имя, и получилось "makefile". 116 $ Is makefile _ Замещение псевдонима Если, работая в редакторе, вы часто указываете один и тот же шаблон, можно воспользоваться механизмом замены псевдонимов. Если вы задаете _символ, как псевдоним слова, последовательность @ символ заменяется словом в командном режиме. В представленном ниже примере символ i в начале псевдонима переводит встроенный редактор в режим вставки, а нажатие клавиши <Esc> в конце строки заставляет его завершить сеанс vi: 123 $ alias _с= ' icommon text^[' . ..Л[ — это символ от комбинации ...клавиш <Ctrl>+<V>, который будет ...сгенерирован после <Esc> 124 $ echo ...в этом месте я нажал клавишу <Esc> за @с 124 $ echo common text Встроенный редактор emacs/gmacs Дальнейшее описание предполагает, что вы знакомы с редактором emacs, который был представлен в главе 2. Во встроенном редакторе поддерживается
большинство ключевых последовательностей emacs. Вы можете перемещать курсор и управлять текстом, используя стандартные ключевые последовательности редактора emacs. Для повторного выполнения команды следует нажать клавишу <Enter>. Главное различие между встроенным редактором и стандартным emacs в том, что ключевые последовательности перемещения курсора или вниз, поиск вперед и обратный поиск работают в списке истории. Например, последовательность <Т> <Ctrl>+<P> показывает предыдущую команду в командной строке. Точно так же ключевая последовательность обратного поиска <Ctrl>+<R> строка демонстрирует самую последнюю команду, содержащую строку. Арифметические действия Команда let позволяет выполнять арифметические вычисления. Синтаксис let выражение Команда let выполняет арифметические вычисления над целыми числами двойной точности и поддерживает все основные математические операторы, используя стандартные правила предшествования. Они приведены ниже в порядке уменьшения приоритета операций: Оператор Описание । Унарный минус Логическое отрицание * / 9- > /> ° Умножение, деление, остаток Сложение, вычитание < j > > о Операторы отношения == 1 = » • Равенство, неравенство = Присвоение Все операторы объединяются слева направо за исключением оператора присвоения. Выражения могут быть помещены между круглыми скобками для изменения порядка вычисления. Shell не проверяет переполнение, учитывайте это! Операнды могут быть целочисленными кон-
стантами или переменными. Когда встречается переменная, она заменяется значением, которое в свою очередь может содержать другие переменные. Вы можете явно заменить основание по умолчанию (10), используя формат основание#значение, Где основание — ЭТО ЧИСЛО между.2 и 36. Нельзя помещать пробелы или знаки табуляции между операндами или операторами. Недопустимо размещать $ перед переменными, которые являются частью выражения. Вот несколько примеров работы команды let: $ let х = 2 + 2 ksh: =: syntax error $ let x=2+2 $ echo $x 4 $ let y=x*4 $ echo' $y 16 $ let x=2#100+2#100 $ echo $x 4 $ ...выражение содержит пробелы. ...пробелы или табуляция недопустимы! ...хорошо. ...не помещайте $ перед переменными. ...сложить два двоичных числа. ...число выводится в десятичной системе. Предотвращение интерпретации метасимволов К сожалению, shell интерпретирует некоторые из стандартных операторов типа <, > и * как метасимволы. Поэтому они должны быть заключены в кавычки или им должна предшествовать обратная косая черта (\) для запрещения интерпретации их как специальных. Чтобы избежать этого неудобства, существует эквивалентная форма команды let, которая автоматически оперирует всеми символами, как будто они были окружены двойными кавычками, и позволяет использовать пробелы вокруг символов. Например, последовательность (( list )) эквивалентна let " list " Я лично всегда использую синтаксис ( о ) вместо let. Вот пример: $ (( х = 4 )) ... пробелы — это нормально. $ (( у = х * 4 ))
$ echo $y 16 $ _ Возвращаемые значения Если результат вычисления выражения равен 0, возвращается код 1; иначе — 0. Код возврата может использоваться структурами управления, например, оператором if, как в этом примере: $(( х = 4 )) ...4 присвоить х. $ if (( х > 0 )) ...использовать в структуре управления. > then > echo х is positive > fi x is positive ...вывод структуры управления. $ Для простых арифметических проверок стоит использовать ((..)) вместо test-выражений. Замена тильды Любое обозначение в формате -имя является объектом замены тильды. Командный интерпретатор проверяет файл пароля, чтобы определить, является ли имя именем пользователя и, если это так, заменяет последовательность -имя полным именем пути до домашнего каталога пользователя. В противном случае последовательность —имя остается неизменной. Замена тильды происходит после обработки псевдонимов. В табл. 6.4 приведен список замен тильды, включая специальные случаи -+ и —. Таблица 6.4. Замена тильды в Korn shell Последовательность тильды Заменяется на $НОМЕ -user домашний каталог пользователя — user -/путь $ НОМЕ/путь -+ $PWD (текущий рабочий каталог) .— $OLDPWD (предыдущий рабочий каталог) Ю Зак. 786
Предопределенные локальные переменные pwd и oldpwd будут описаны позже в этой главе. Ниже приведены некоторые примеры замены тильды: $ pwd /home/glass $ echo ~ /home/glass $ cd / $ echo $ echo — /home/glass $ echo ~dcox /home/de ox $ ...текущий рабочий каталог. ...мой домашний каталог. ...изменить на корневой каталог. ...текущий рабочий каталог. ...предыдущий рабочий каталог. ...домашний каталог другого пользователя. Меню: select Команда select позволяет создавать простые меню. Она выводит пронумерованный список слов, указанных в in-выражении в стандартном канале ошибок, после чего отображает приглашение, хранящееся в специальной переменной PS3, а затем ждет входную строку от пользователя. Когда пользователь вводит строку, та сохраняется в предопределенной переменной reply. Далее происходит одно из следующих действий: □ если пользователь ввел одно из предложенных чисел, имя устанавливается равным этому числу, команды из списка выполняются, посде чего вновь выводится приглашение, и система ожидает выбор очередной команды; П если пользователь ввел пустую строку, варианты выбора выводятся снова; П если пользователь указал отсутствующий вариант, имя устанавливается на пустой указатель, команды из списка выполняются, и выводится приглашение сделать другой выбор. Синтаксис select имя [in {слово}+] do списокКома нд done
Приведенный ниже пример заменяет цикл while более простой командой select! $ cat menu.ksh ...вывести скрипт, к echo menu test program select reply in "date" "pwd" "pwd" "exit" do case $reply in "date") date 11 "pwd") pwd "exit") break r r *) echo illegal choice z / esac done $ menu.ksh ...выполнить скрипт, menu test program 1) date 2) pwd 3) pwd 4) exit #? 1 Fri Feb 6 21:49:33 CST 1998 #? 5 illegal choice #? 4 $ _ Функции Korn shell позволяет определять функции, которые могут быть вызваны, как команды shell. Параметры, передаваемые функциям, доступны через стан
дартный механизм передачи параметров. Функции должны быть определены перед их использованием. Существуют два способа определить функцию (см. синтаксис). Рекомендуется использовать вторую форму, т. к. она похожа на конструкцию в языке С. Для вызова функции следует ввести ее имя, сопровождаемое соответствующими параметрами. По очевидным причинам shell не проверяет количество или тип параметров. Синтаксис function имя { списокКоманд Ключевое слово function может быть опущено: имя{) списокКоманд Ниже представлен пример скрипта, который определяет и использует функцию, которая не имеет параметров: $ cat fund. ksh ...вывести скрипт. message () # нет параметров функции. { echo hi echo there } i=l while (( i <= 3 )) do message ' # вызов функции. let i=i+l # увеличить на единицу счетчик цикла, done $ fund. ksh . . . выполнить скрипт. hi there hi /
there hi there $ Использование параметров в функциях Как уже было отмечено, параметры передаются функции стандартным способом. Пример: $ cat func2.ksh f О { echo parameter 1 = $1 echo parameter list = $* } # main program. f 1 f cat dog goat $ func2.ksh parameter parameter parameter parameter $ . . .вывести скрипт. # вывести первый параметр. # вывести входной список. # вызвать с одним параметром. # вызвать с тремя параметрами ...выполнить скрипт. 1 = 1 list = 1 1 = cat list = cat dog goat Возврат из функции Команда return возвращает значение. Синтаксис return [ значение ] Когда return используется без аргументов, передается код возврата последней выполненной в функции команды. Иначе, возвращается код, равный значению. Если команда return выполняется из главного скрипта, она эквивалентна команде exit. Код возврата доступен вызывающему процессу через переменную $?.
Ниже в качестве примера приведена функция, которая перемножает аргументы и возвращает результат: $ cat func3.ksh ...вывести скрипт, f () # функция с двумя параметрами. (( returnValue = $1 * $2 )) return $returnValue } # main program. f 3 4 # вызов функции. result=$? # сохранить код возврата, echo return value from function was $result $ func3.ksh ...выполнить скрипт, return value from function was 12 $ Контекст Функция выполняется в том же самом контексте, что и процесс, который вызывает ее. Это означает, что функция и процесс разделяют одни и те же переменные, текущий рабочий каталог и перехватчики событий. Единственное исключение — событие выхода, которое наступает, когда функция выполняет возврат. Локальные переменные Команда typeset (которая далее будет описана более подробно) имеет специальные ориентированные на функцию возможности. Созданная с использованием typeset переменная ограничивается областью действия функции, в которой она создана, и всех вызываемых функций. Если переменная с указанным именем уже существует, ее значение заменяется, когда функция выполняет возврат. Эта особенность напоминает понятие "область действия переменной" в языках программирования высокого уровня. В следующем примере используется команда typeset для объявления локальной переменной: $ cat func4.ksh f О typeset х (( х = $1 * $2 )) ...вывести скрипт. # функция с двумя параметрами. # объявить локальную переменную. # установить локальную переменную.
echo local x = $x return $x } # main program. x=l # установить глобальную переменную. echo global x = $x # вывести значение перед функциональным вызовом. f 3 4 # вызвать функцию. result=$? # сохранить код возврата. echo return value from function was $result echo global x = $x # значение после функции. $ func4.ksh ...выполнить скрипт. global х = 1 local x = 12 return value from function was 12 global x = 1 $ Рекурсия Как показывает практика, во многих случаях целесообразно использовать рекурсивные функции. Ниже приведены два скрипта, которые выполняют рекурсивное вычисление факториала — factorial о. Первый использует код возврата, чтобы вернуть результат, а второй — стандартный вывод для отображения результата. Внимание Эти скрипты доступны в сети (см. Введение для получения дополнительной информации). Рекурсивный факториал: использование кода возврата 4 factorial () # функция с одним параметром. if (( $1 <= 1 )) then return 1 # вернуть результат в коде возврата, else typeset tmp # объявить две локальных переменных, typeset result
( ( tmp = $1 - 1 ) ) factorial $tmp # вызвать рекурсивно. ( ( result = $?*$!)) return $result # передать результат в коде возврата, fi } # main program, factorial 5 # вызвать функцию echo factorial 5 = $? # вывести код возврата. Рекурсивный факториал: использование стандартного вывода factorial () # функция с одним параметром { if (( $1 <= 1 )) then echo 1 # вывести результат на стандартный вывод, else typeset tmp # объявить две локальных переменных, typeset result (( tmp = $1 - 1 )) (( result = 'factorial $tmp' * $1 ) ) echo $result # вывести результат на стандартный вывод, fi } # echo factorial 5 = 'factorial 5' # вывод результата. Использование кода функций в нескольких скриптах Для использования исходного кода функции в нескольких скриптах следует сохранить его в отдельном файле, а прочитать его можно с помощью встроенной команды в начале скрипта, содержащего функцию. Предположим, что исходный код одного из предыдущих скриптов вычисления факториала был сохранен в файле fact.ksh: $ cat.ksh ...вывести скрипт. . fact.ksh # читать исходный код функции, echo factorial 5 = 'factorial 5' # вызов функции.
$ func6.ksh ...выполнить скрипт, factorial 5 = 120 $ Расширенное управление заданиями Korn shell дополняет возможности управления заданиями Bourne shell и поддерживает команды, перечисленные в табл. 6.5. Они доступны только в UNIX-системах, которые поддерживают управление заданиями. Следующие несколько разделов содержат описание управления заданиями и примеры их использования. Особенности управления заданиями Korn shell идентичны средствам командного интерпретатора С shell, о котором речь пойдет в следующей главе. Таблица 6.5. Команды управления заданием Korn shell Команда Описание jobs Выводит задания пользователя bg • Помещает специфицированное задание в фоновый режим fg Помещает специфицированное задание в приоритетный режим kill Посылает произвольный сигнал процессу или заданию Задания Команда shell jobs выводит список всех заданий данного командного интерпретатора. Синтаксис jobs [-1] Команда jobs выводит список всех работ shell. В случае использования с опцией -1 к листингу добавляется ID процесса. Синтаксис каждой выводимой строки выглядит следующим образом: номерЗадания [+|~] PID Статус Команда Здесь + и - соответственно означают, что задание было последним или предпоследним, помещенным в фоновое выполнение. Статус может быть таким: • Running (Выполняется); • stopped (Остановлено (отложено));
• Terminated (Прекращено ("убито" сигналом)); • Done (Выполнено (нулевой код возврата)); • Exit (Выход (ненулевой код возврата)). Основное назначение символов + и - состоит в том, что они могут использоваться при определении задания в следующей команде (см. разд. "Спецификация задания " далее в этой главе). Пример: $ jobs ...нет заданий. $ sleep 1000 & ...запустить фоновое задание. [1] 27128 $ man Is | ul -tdumb > Is.txt & ...другое фоновое задание. [2] 27129 $ jobs -1 ...вывести текущие задания. [2] +27129 Running man Is 1 ul -tdumb > Is.txt & [1] - 27128 Running sleep 1000 & $ _ Спецификация задания Команды bg, fg, и kill позволяют специфицировать работу с помощью одного из форматов, перечисленных в табл. 6.6. Таблица 6.6. Спецификация заданий в Korn shell Форма Специфицирует %целоеЗначение Задание с номером целоеЗначение % префикс Задание, чье имя начинается с префикса 9- 4-Ъ Т Задание, которое было указано последним о. 9-о о То же самое, что и % + С о Задание, которое было указано предпоследним bg Команда shell bg возобновляет указанное задание как фоновый процесс. В следующем примере задание выполнялось в приоритетном режиме, а потом было приостановлено нажатием комбинации клавиш <Ctrl>+<Z> и переведено в фоновый.
Ниже приведены используемые для этого команды: $ man ksh | ul -tdumb > ksh.txt ...запустить в приоритетном режиме. AZ ...приостановить его. [1] + Stopped man ksh | ul -tdumb > ksh.txt $ bg %1 ... возобновить в фоновом режиме. [1] man ksh | ul -tdumb > ksh.txt & $ jobs ...вывести текущие задания. [1] + Running man ksh | ul -tdumb > ksh.txt $ Синтаксис bg [%задание] Команда shell bg возобновляет указанное задание, как фоновый процесс. Если никакое задание не определено, возобновляется последнее. fg Команда shell fg возобновляет указанное задание, как приоритетный процесс. Синтаксис fg [%задание] Команда shell fg возобновляет указанное задание, как приоритетный процесс. Если никакое задание не специфицировано, возобновляется последнее упомянутое задание. В представленном ниже примере фоновое задание было переведено в приоритетный режим: $ sleep 1000 & [1] 27143 $ man ksh | ul [2] 27144 $ jobs [2] + Running [ 1] - Running -tdumb > ksh.txt & ...запустить фоновое задание. ...запустить другое. ...вывести текущие задания. man ksh | ul -tdumb > ksh.txt & sleep 1000 &
$ fg %та . . .перевести задание в приоритетный режим, man ksh I ul -tdumb > ksh.txt ...команда выводится повторно. $ kill Команда shell kill посылает сигнал указанному заданию или процессам. Синтаксис kill [-1] [-сигнал] {процесс | %задание}+ Команда shell kill посылает сигнал указанному заданию или процессам. Процесс задается его номером PID. Сигнал может быть определен или его номером, или символически, путем удаления приставки "SIG" из символической константы в файле /usr/include/sys/signal.h. Чтобы получить список сигналов, используйте опцию -1. Если сигнал не определен, посылается сигнал term. Если сигнал term или hup был отправлен приостановленному процессу, посылается сигнал cont, который возобновляет его. Пример: $ kill -1 ...вывести все сигналы kill. 1) HUP 12) SYS 23) STOP 2) INT 13) PIPE 24) TSTP 3) QUIT 14) ALRM 25) CONT 4) ILL 15) TERM 26) TTIN 5) TRAP 16) USR1 27) TTOU 6) ABRT 17) USR2 28) VTALRM 7) EMT 18) CHLD 29) PROF 8) FPE 19) PWR 30) XCPU 9) KILL 20) WINCH 31) XFSZ Ю) BUS 21) URG 11) SEGV 22) POLL $ man ksh | ul -tdumb > ksh. txt & ...запустить фоновое задание. [1] 27160 $ kill -9 %1 ..."убить" его через спецификацию задания. [1] + Killed man ksh | ul -tdumb > ksh.txt & $ man ksh | ul -tdumb > ksh.txt & ...запустить другое. [1] 27164
$ kill -KILL 27164 [1] + Killed $ ..."убить” его через ID процесса, man ksh ul -tdumb > ksh.txt & Усовершенствования В дополнение к новым возможностям, которое уже были описаны, Кот shell содержит усовершенствования следующих возможностей: □ перенаправление; □ конвейеры; □ замещение команд; П доступ к переменной; □ дополнительные встроенные команды. Перенаправление Korn shell имеет некоторые усовершенствования перенаправления, а именно, способность удалять ведущие символы табуляции в документах, находящихся в буфере1. Если слову предшествует знак -, то ведущие символы табуляции удаляются из строки, введенной со стандартного входа. Синтаксис команда « [-] слово Пример: $ cat «- ENDOFTEXT this input contains > some leading tabs > '"D z this input contains some leading tabs $ _ Данный пример команды позволяет тексту, находящемуся в буфере, в скрипте размещаться с отступом, чтобы соответствовать близлежащим командам shell. 1 См. разд. "Перенаправление ввода в буфер shell" главы 4. — Ред.
Конвейеры Оператор | & поддерживает простую форму параллельной обработки. Когда команда сопровождается |&, она выполняется как фоновый процесс, стандартный ввод и вывод которого связаны с первоначальным родительским shell двусторонним конвейером. Когда первоначальный shell генерирует вывод, используя команду print -р, он посылается на стандартный канал ввода дочернего shell. Когда первоначальный shell читает ввод, используя команду read -р, он принимается со стандартного вывода дочернего shell. Вот пример: $ date |& ...начать дочерний процесс. [1] 8311 $ read -р theDate ...читать со стандартного вывода дочернего процесса. [1] + Done * date |& ... дочерний процесс прекратился. $ echo $theDate ...вывести результат. Sun Мау 10 21:36:57 CDT 1998 $ Замещение команды В дополнение к уже описанному методу замещения команды (окружения ее тупыми штрихами (')) Korn shell позволяет выполнять замену команды, используя следующий синтаксис: $ (команда) Обратите внимание, что символ $, который непосредственно предшествует открывающей круглой скобке, является частью синтаксиса, а не приглашением shell. Пример: $ echo there are $(who | wc -1) users on the system there are 6 users on the system $ _ Чтобы подставить содержимое файла в команду shell, используйте конструкцию $ (<имяФайла) вместо более длинной формы $(cat имяФайла}
Переменные Korn shell поддерживает следующие дополнительные средства работы с переменными: □ более гибкие методы доступа; □ предопределенные локальные переменные; □ предопределенные переменные окружения; □ простые массивы; □ команду typeset для форматирования вывода переменных. Гибкие методы доступа В дополнение к методам доступа к переменной, поддерживаемым Bourne shell, Korn shell реализует возможности работы с ними, перечисленные в табл. 6.7. Таблица 6.7. Методы доступа к переменной в Korn shell Синтаксис Действие ${#имя} Заменяется длиной значения имя ${#ИМЯ[* ] } Заменяется числом элементов в массиве имя ${имя:+слово} Работают подобно аналогам, которые не содержат :, за исклю- ${имя:=слово} чением того, что имя должно быть установлено и не равно нулю, а не просто определено ${имя:2 слово} ${имя:+слово} $ {имяЪша блон] Удаляет ведущий шаблон из имени. Выражение заменяется зна- $ {имя# #ша блон} чением имя, если оно не начинается с шаблона, и оставшимся суффиксом, если оно начинается с шаблона. Первая форма синтаксиса удаляет самый маленький шаблон соответствия, а вторая — большой $ {имя%та блон} Удаляет замыкающий шаблон из имени. Выражение заменяется $ {имя% % та блон} значением имя, если оно не заканчивается шаблоном, и оставшимся суффиксом, если оно завершается шаблоном. Первая форма синтаксиса удаляет самый маленький шаблон соответствия, вторая — самый большой Вот некоторые „примеры: $ fish='smoked salmon' ...установить переменную. $ echo ${#fish} ...вывести длину значения переменной.
13 $ cd dirl ...переместиться в каталог. $ echo $PWD ...вывести текущий рабочий каталог, /home/glass/dirl $ echo $HOME /home/glass $ echo ${PWD#$HOME/} ...удалить ведущее $НОМЕ/ dirl $ fileName=menu.ksh ...установить переменную. $ echo $ {fileName%. ksh} .bak ...удалить замыкающее .ksh menu.bak - ...и добавить .bak. $ Предопределенные локальные переменные В дополнение к стандартному набору предопределенных локальных переменных, Korn shell поддерживает переменные, перечисленные в табл. 6.8. Таблица 6.8. Предопределенные локальные переменные Korn shell Имя Описание $_ Последний параметр предыдущей команды $PPID ID процесса родительского shell $PWD Текущий рабочий каталог shell $OLDPWD Предыдущий рабочий каталог shell $RANDOM Случайное целое число $REPLY Устанавливается командой select $SECONDS Число секунд, прошедших с начала вызова shell $CDPATH Используется командой cd $COLUMNS Устанавливает ширину окна редактирования для встроенных редакторов $EDITOR Указывает тип встроенного редактора $ENV Содержит имя стартового файла Korn shell $FCEDIT Определяет редактор, который вызывается командой f с $HISTFILE Имя файла истории $HISTSIZE Количество строк истории для запоминания
Имя Таблица 6.8 (окончание) Описание $LINES Используется командой select для определения отображения вариантов выбора $MAILCHECK Указывает shell, сколько секунд ожидать перед проверкой почты. Значение по умолчанию 600 $MAILPATH Содержит список имен файлов, разделенных двоеточием. Shell проверяет эти файлы на изменение каждые $ MAILCHECK секунд $PS3 Приглашение, используемое командой select, по умолчанию #? $TMOUT Если переменная равна значению, большему, чем 0, и истекает больше, чем $тмоит секунд между командами, происходит завершение работы shell $VISUAL Содержит тип встроенного редактора Пример: $ echo hi there hi there ...вывести сообщение для демонстрации $ echo $_ ...вывести последний аргумент последней команды. there $ echo $PWD /home/glass ...вывести текущий рабочий каталог. $ echo $PPID 27709 ...вывести PID родительского shell. $ cd / ...переместиться в корневой каталог. $ echo $OLDPWD /home/glass ...вывести последний рабочий каталог. $ echo $PWD ...вывести текущий рабочий каталог. $ echo $RANDOM $ RANDOM . . . вывести два случайных числа. 32561 8323 $ echo $SECONDS 918 ...вывести секунды с начала работы shell. $ echo $TMOUT ...вывести значение тайм-аут 0 $ ...нет выбранного тайм-аута.
Одномерные массивы Korn shell поддерживает простые одномерные массивы. Синтаксис Для присвоения значения элементу массива используйте конструкцию: имяМассива[индекс]=значение Для доступа к значению элемента массива используйте конструкцию: ${имяМассива[индекс]} Для того чтобы сформировать массив, просто присвойте значение переменной, указывая в скобках список индексов между 0 и 511. Элементы массива создаются по мере необходимости. Если вы опускаете индекс, используется 0 в качестве значения по умолчанию. Вот пример, который использует скрипт для вывода квадратов чисел от 0 до 9: $ cat squares.ksh ...посмотреть скрипт. i=0 while ( ( i < 10 ) ) do (( squares[$i] = i * i )) ...присвоить отдельные элементы. (( i = i + 1 }) ...увеличить счетчик цикла. done echo 5 squared is ${squares[5]} ...вывести один элемент, echo list of all squares is ${squares[*]} ...вывести все. $ squares.ksh ...выполнить скрипт. 5 squared is 25 list of all squares is 0 1 4 9 16 25 36 49 64 81 $ typeset Команда typeset позволяет создавать и манипулировать переменными. В табл. 6.9 перечислены ее опции. Синтаксис typeset {-HLRZfilrtux [значение] [имя [=слово]]}* Команда typeset позволяет создавать и манипулировать переменными: форматировать, конвертировать их во внутреннее представление целых
чисел для более быстрого выполнения арифметических операций, присваивать им статус "только для чтения", переключаться между строчными и прописными буквами, экспортировать переменные. Каждая переменная имеет связанный с ней набор флагов, которые определяют ее свойства. Например, если переменной поставлен в соответствие флаг "заглавный", она будет всегда выводить свое содержимое прописными буквами, даже когда они строчные. Опции typeset используются путем установки и переустановки различных флагов, связанных с переменными. Предшествующий опции знак - активизирует заданный флаг. Для выключения флага и отмены действия опции поставьте перед ней + вместо -. % Таблица 6.9. Опции команды typeset Опция Описание L Включает флаг L и выключает флаг R. Выравнивает слово влево и удаляет ведущие пробелы. Если ширина слова меньше, чем ширина поля имени, то дополняет слово заключительными пробелами. Если ширина слова больше ширины, то усекает конец слова до размера поля. Если флаг Z установлен, ведущие нули также удаляются R Включает флаг R и выключает флаг L. Выравнивает слово вправо и удаляет завершающие пробелы. Если ширина слова меньше чем ширина поля имени, то дополняет слово ведущими пробелами. Если ширина слова больше, то усекает конец слова до размера поля z Выравнивает слово вправо и заполняет нулями, если первый непробельный символ — это цифра, и флаг L выключен Форматирование Во всех опциях форматирования ширина поля имени равна ширине значения, если оно установлено. В противном случае она установлена на ширину слова. Регистр С помощью опций изменения регистра значение может быть конвертировано в строчные и прописные буквы, как показано в табл. 6.10. Ниже приведен пример, который выравнивает влево все элементы в массиве, а затем отображает их прописными буквами: $ cat justify. ksh wordList[0] = ’j eff’ ...вывести скрипт. # установить три элемента.
wordList[1]=’John’ wordList[2]= ’ellen' typeset -uL7 wordList # форматировать все элементы массива. echo ${wordList [*]} # осторожно! shell удаляет пробелы без кавычек, echo "${wordList[*]}" # работает хорошо. $ justify.ksh ...выполнить скрипт. JEFF JOHN ELLEN JEFF JOHN ELLEN $ _ Таблица 6.10. Изменение регистра символов при помощи команды typeset Опция Описание 1 Включает флаг 1 и выключает флаг и. Конвертирует слово в регистр строчных букв и Включает флаг и и выключает флаг 1. Конвертирует слово в регистр прописных букв Тип Тип хранящегося значения может быть установлен с помощью опций, перечисленных в табл. 6.11. Изменим рассмотренный выше фрагмент кода, объявив массив квадратов, как массив целых чисел. Это ускорит его работу: $ cat squares.ksh ...посмотреть скрипт. typeset -i squares # объявить массив целых (для скорости). i=0 while (( i < 10 )) do (( squares[$i] = i * i )) (( i = i + 1 )) done echo 5 squared is ${squares[5]} echo list of all squares is ${squares[*]} $ squares.ksh ...выполнить скрипт. 5 squared is 25 list of all squares is 0 1 4 9 16 25 36 49 64 81 $
Таблица 6.11. Установка типа значениям при помощи команды typeset Опция Описание i Сохраняет имя, как целое число для ускорения арифметических вычислений. Задает основание вывода, равное значению, если оно установлено; иначе использует основание слова r Устанавливает атрибуты доступа к переменной равными "только для чтения" X Помечает переменную, как экспортируемую Разное В табл. 6.12 показаны две дополнительные опции команды typeset. Таблица 6.12. Дополнительные опции команды typeset Опция Описание f Единственные флаги, которые можно использовать с этой опцией, — t, устанавливающий трассировку функции, и х, выводящий все функции с установленным атрибутом х t Связывает имя с меткой слова Приведенный ниже пример демонстрирует трассировку функции factorial с применением опций -ft, после чего скрипт выполняется: $ cat func5.ksh ...посмотреть скрипт factorial () # функция одного параметра { if (( $1 <= 1 )) then return 1 else typeset tmp typeset result ( ( tmp = $1 - 1 )) factorial $tmp (( result =$?*$1)) return $result
# typeset -ft factorial ...выбрать трассировку функции, factorial 3 echo factorial 3 = $? $ func5.ksh ...выполнить скрипт. + let 3 <= 1 ...информация отладки. + typeset tmp + typeset result + let tmp =3-1 4- factorial 2 4- let 2 <= 1 4- typeset tmp 4- typeset result 4- let tmp =2-1 4- factorial 1 4- let 1 <= 1 4- return 1 4- let result =1*2 4- return 2 4- let result =2*3 4- return 6 factorial 3=6 $ typeset с не именованными переменными Если переменные не определены, выводятся имена всех переменных, имеющих указанный набор флагов. Если никакие флаги не указаны, то отображается список всех переменных и их параметров. Пример: $ typeset export NODEID export PATH ...вывести список всех переменных typeset. leftjust 7 t export integer MAILCHECK $ typeset -i ...вывести список целых переменных typeset. LINENO=1 MAILCHECK=60 $
Встроенные команды Korn shell использует усовершенствованные версии следующих встроенных команд: □ I cd; □ set; □ print (улучшенная версия команды echo из Bourne shell); □ read; □ test; О trap. cd Версия команды Korn shell cd содержит несколько новых возможностей. Синтаксис cd { имя } cd староеИмя новоеИмя Первая форма команды cd обрабатывается следующим образом: • если имя опущено, shell перемещается в домашний каталог, указанный в переменной $номе; • если имя равно -, shell перемещается в предыдущий рабочий каталог, который сохраняется в переменной $oldpwd; • если имя начинается с /, shell перемещается в каталог, полный путь которого — имя', • если имя начинается с других символов, командный интерпретатор производит поиск совпадений в каталогах, указанных в переменной $cdpath, после чего перемещает shell в соответствующий каталог. Значение $cdpath по умолчанию равно пустой строке, поэтому поиск производится только в текущем каталоге. Если используется вторая форма синтаксиса команды cd, shell заменяет первое найденное совпадение, указанное в параметре староеИмя, значением новоеИмя. Затем shell использует новое имя пути. Новое имя текущего каталога сохраняется в переменной pwd. Текущее значение $pwd может быть просмотрено с помощью встроенной команды pwd.
Ниже приведен пример cd в действии: $ CDPATH=.:/usr ...установить мой CDPATH. $ cd dirl $ pwd /home/glass/dirl ...переместиться в dirl, размещенный в $ cd include ...переместить ся в include, размещенный в /usr. $ pwd /usr/include ...вывести текущий рабочий каталог. $ cd - ...переместиться в мой предыдущий каталог. $ pwd /home/glass /dirl $ . ...вывести текущий рабочий каталог. * set Команда set позволяет устанавливать и снимать флаги, управляющие широким набором характеристик shell. В табл. 6.13 перечислены опции, доступные этой команде. Синтаксис set [ +-aefhkmnostuvx ] [ +-о опция] {аргумент} ★ Команда Korn shell set поддерживает все возможности аналогичной команды Bourne shell и несколько дополнительных усовершенствований. Опция, которой предшествует + вместо изменяет свое значение на противоположное. Таблица 6.13. Опции команды set Опция Описание а Все переменные автоматически получают флаг для экспорта f Запрещает замещение имени файла h Все невстроенные команды автоматически интерпретируются, как прослеженные псевдонимы ш Помещает все фоновые задания в уникальную процессную группу и выводит уведомление о завершении. Эта опция автоматически устанавливается для диалоговых shell п Принимает, но не выполняет команды. Опция не влияет на диалоговые shell
Таблица 6.13 (окончание) Опция Описание о Описание опции см. далее Р Присваивает $РАТН значение по умолчанию, заставляя последовательность запуска игнорировать файл $ номе/, profile, и читает /etc/suid_profile вместо файла $env. Этот флаг устанавливается автоматически, когда shell выполняется процессом в режиме "set user ID" или "set group ID". Для получения дополнительной информации о процессах "set user ID" см. главу 13 s Сортирует параметры Не изменяет никакие флаги. Если аргументов нет, все параметры возвращаются в исходное состояние Опция о Опция о команды set принимает аргумент, часто имеющий тот же самый эффект, что и один из флагов, используемых с set. Если аргумент не указан, выводятся текущие установки. В табл. 6.14 приведен список допустимых аргументов и их значения: Обратите внимание, что, установив опцию ignoreeof в .profile, можно защитить себя от случайного выхода из системы по нажатию комбинации клавиш <Ctrl>+<D>: set -о ignoreeof Таблица 6.14. Аргументы set -о Опция Описание allexport Эквивалентна флагу a errexit Эквивалентна флагу е bgnice Фоновые процессы выполняются с низшим приоритетом emacs Вызывает встроенный редактор emacs gmacs Вызывает встроенный редактор gmacs ignoreeof Не выходит по нажатию комбинации клавиш <Ctrl>+<D>. Вместо этого используется exit keyword Эквивалентна флагу к markdirs Добавляет окончание / к каталогам, сгенерированным путем замены имени файла monitor Эквивалентна флагу m
Таблица 6.14 (окончание) Опция Описание noclobber Предотвращает перенаправление усечения существующих файлов поехес Эквивалентна флагу п noglob Эквивалентна флагу f no log Не сохраняет определения функции в файле истории nounset Эквивалентна флагу и privileged То же самое, как -р verbose Эквивалентна флагу v trackall Эквивалентна флагу h vi Вызывает встроенный редактор vi viraw Символы обрабатываются во время печати в режиме vi xtrace Эквивалентна флагу х print Команда print — усовершенствованная версия встроенной команды Bourne shell echo, позволяющая посылать вывод в произвольный описатель файла. Синтаксис print -npsuR [п] {аргумент}* По умолчанию команда print выводит значения, указанные в качестве аргументов, на стандартный вывод, и сопровождает это переводом строки. Опция -п запрещает перевод строки, а опция -и позволяет определить описатель файла л из одной цифры для канала вывода. Опция -s добавляет вывод к файлу истории вместо направления его на стандартный выход. Опция -р посылает вывод в двунаправленный конвейерный канал shell. Опция -R интерпретирует все остающиеся слова как аргументы. Пример: 121 $ print -u2 hi there ...послать вывод в стандартный канал ошибок, hi there
122 $ print -s echo hi there 124 $ r 123 echo hi there hi there 125 $ print -R -s hi there -s hi there $ ...добавить в историю. ...вызвать снова команду с номером 123. ...рассматривает -s, как аргумент. read Команда Korn shell read — это расширенная версия одноименной команды Bourne shell. Синтаксис read -prsu [п] [имя?приглашение] {имя}* Команда Korn shell read работает точно так же, как одноименная команда Bourne shell, и имеет следующие дополнительные возможности: • опция -р читает входную строку с двунаправленного конвейера shell; • опция -и использует описатель файла п для ввода; • если первый аргумент содержит ?, остаток аргумента используется, как приглашение. Пример: $ read. 'name?Enter your name ' Enter your name Graham $ echo $name Graham $ _ test Команда Korn shell test поддерживает несколько новых операторов, описанных в табл. 6.15. Кроме того, она предлагает удобный синтаксис, позволяющий сделать программу более читаемой.
Таблица 6.15. Операторы проверки, уникальные для команды test Korn shell Оператор Описание -L имяФайла Возвращает истину, если имяФайла — символическая связь файл! -nt файл2 Возвращает истину, если файл1 новее, чем файл2 файл1 -ot файл2 Возвращает истину, если файл1 старше, чем файл 2 файл! -ef файл2 Возвращает истину, если файл! такой же, как файл2 Синтаксис [ [ testBbipaxeHHe] ] эквивалентно test textBMpaxceHne Ниже приведен пример использования нового синтаксиса команды test: $ cat test.ksh ...посмотреть скрипт. i=i ' while [[ i -le 4 ]] do echo $i (( i = i + 1 )) done $ test.ksh ... выполнить скрипт. 1 2 3 4 $ trap Команда Korn shell trap— это доработанная версия аналогичной команды Bourne shell.
Синтаксис trap [команда] [сигнал] Команда Korn shell trap работает аналогично одноименной команде из Bourne shell, но имеет некоторые особенности: • если аргумент то все указанные сигналы повторно устанавливаются в их начальные значения; • если сигнал exit или 0 устанавливается trap внутри функции, тогда команда выполняется после выхода из нее. В следующем примере установлен перехват события exit внутри функции: $ cat trap.ksh ...вывести скрипт. f О { echo ’enter f () ’ trap ’echo leaving f...’ EXIT # установить локальную ловушку, echo ’exit f ()’ } # main program. trap ’echo exit shell’ EXIT # установить глобальную ловушку. f # вызов функции f(). $ trap.ksh ...выполнить скрипт, enter f () exit f () leaving f... ...локальный EXIT перехвачен. exit shell ...глобальный EXIT перехвачен. $ Пример проекта: junk Чтобы проиллюстрировать некоторые возможности Korn shell, рассмотрим проект junk, упомянутый в главе 5. Скрипт Korn shell использует функцию, чтобы обработать сообщения об ошибке, и массив с именами файлов. Остальные действия будут понятны читателю из комментариев. (Этот скрипт доступен в сети. Для дополнительной информации см. Введение.)
Синтаксис junk -1р {имяФайла}* Утилита junk — это аналог утилиты rm. Вместо удаления файлов она перемещает их в каталог junk домашнего каталога пользователя. Если .junk не существует, он автоматически создается. Опция -1 выводит текущее содержимое каталога .junk, а опция -р производит его очистку. junk #! /bin/ksh # junk script # Korn shell version # author: Graham Glass # 9/25/91 # # Initialize variables fileCount=0 listFlag=O purgeFlag=O fileFlag=O # количество специфицированных файлов. # 1, если используется опция list (-). # 1, если используется опция очистки -р. # 1, если специфицирован хотя бы один фа если используется опция list (-). если специфицирован хотя бы один файл. junk=~/.junk # имя каталога junk. # error () ’ { # # Вывести сообщение об ошибке и выйти # cat « ENDOFTEXT Dear $USER, the usage of junk is as follows: junk -p means "purge all files" junk -1 means "list junked files" junk clist of files, to junk them ENDOFTEXT exit 1
# # Разбор командной строки. # for arg in $* do case $arg in "-P") purgeFlag=l r r "-I") listFlag=l r r -*) t r echo $arg is an illegal option • • r r *) fileFlag=l fileList[$fileCount]=$arg # добавить в список, let fileCount=fileCount+l r r esac done # # Проверка: не слишком ли много опций. # let total=$listFlag+$purgeFlag+$fileFlag if (( total != 1 )) then error fi # # Если каталог junk не существует, создать его. # if [[ ! (-d $junk) ]] then ’mkdir’ $junk # взято в кавычки на случай, если есть псевдоним.
# # Обработка опций. # if (( listFlag = 1 )) then ’Is1 -IgF $junk # вывод каталога junk, exit 0 fi # if (( purgeFlag == 1 ) ) then ’rm’ $junk/* # удалить файлы из каталога junk, exit О fi # if (( fileFlag == 1 )) then ’mv’ ${fileList [*] } $junk # поместить файлы в каталог junk, exit О fi # exit 0 Вот пример вывода результатов работы junk: $ Is *.ksh ...вывести некоторые для junk. fact.ksh* func5.ksh* test.ksh* trap.ksh* f unc4.ksh* squares.ksh* track.ksh* $ junk func5.ksh func4.ksh ...выполнить junk для пары файлов. $ junk -1 ...вывести мой junk-каталог, total 2 -rwxr-xr-x 1 gglass apollocl 205 Feb 6 22:44 func4.ksh* -rwxr-xr-x 1 gglass apollocl 274 Feb 7 21:02 func5.ksh* $ junk -p ...очистить мой junk-каталог. $ junk -z ...испытать глупую опцию. —z is an illegal option Dear glass, the usage of junk is as follows: junk -p means "purge all files"
junk -1 means "list junked files" junk <list of files> to junk them $ _ Ограниченный shell Существует разновидность Korn shell, называемая ограниченным Korn shell, который поддерживает все возможности командного интерпретатора за исключением того, что пользователь не может: П менять каталог; □ осуществлять перенаправление вывода, используя > или »; □ устанавливать переменные окружения shell, env или path; □ использовать абсолютные пути. Эти ограничения накладываются только после выполнения файлов .profile и $env. Любые скрипты, выполненные ограниченным Korn shell, интерпретируются их ассоциированными shell и не ограничены никаким образом. Ограниченный Korn shell — это обычная С-программа, исполняемый файл которой хранится в /bin/rksh. Если выбранный shell — /bin/rksh, при регистрации в системе автоматически вызывается ограниченный диалоговый Korn shell . Его также можно запустить вручную, из скрипта или с терминала, с ПОМОЩЬЮ КОМанДЫ rksh. Системные администраторы, как правило, используют такой shell, чтобы предоставить пользователям ограниченный доступ к системе следующим образом: 1. Пишут серии обычных скриптов Korn shell, позволяющих пользователям получить доступ к возможностям, которые они (пользователи) желают применять. 2. После чего размещают эти скрипты в каталоге с атрибутом "только для чтения", обычно находящемся в /usr/local/rbin. 3. Далее присваивают переменной $ратн пользовательского .profile значение /usr/local/rbin и несколько других каталогов на выбор. 4. Изменяют вход пользователя в файле пароля (см. главу 15), устанавливая shell равным /bin/rksh. Опции командной строки Если первый аргумент командной строки -, Korn shell стартует, как входной shell. Korn shell поддерживает опции командной строки Bourne shell, флаги 11 Зак. 786
встроенной команды set (включая -х и -v) и опции, перечисленные в табл. 6.16. Таблица 6.16. Опции командной строки Korn shell Опция Описание -г Делает Korn shell ограниченным Korn shell имяФайла Выполняет команды shell в имяФайла, если опция -s не используется. имяФайла — это $0 внутри имяФайла-скрипта Обзор главы Перечень тем В этой главе мы рассмотрели: П создание стартового файла Korn shell; П псевдонимы и механизм истории; □ встроенные строчные редакторы vi и emacs; П арифметические операции; П функции; П усовершенствованное управление заданиями; П несколько дополнительных возможностей команд. Контрольные вопросы 1. Кем был написан Korn shell? 2. Почему полезен механизм псевдонимов? 3. Каким образом вы можете повторно отредактировать и выполнить предыдущую команду? 4. Поддерживает ли Korn shell рекурсивные функции? 5. Опишите современный синтаксис команды test. Упражнения 6.1. Перепишите скрипт junk, представленный в данной главе, так, чтобы он управлялся с помощью меню. Используйте команду select. [Уровень: легкий.]
6.2. Напишите функцию dateToDays, которая принимает три параметра: название месяца, например, Sep; день, например, 18; год, например, 1962 — и возвращает количество дней, прошедших с 1 января 1900 г. до указанной даты. [Уровень: средний.] 6.3. Напишите набор функций, которые эмулируют каталожные стековые возможности С shell (будут описаны в главе 7). Используйте переменные окружающей среды, чтобы определить стек и его размер. [Уровень: средний\ 6.4. Напишите скрипт pulse, который принимает два параметра: имя скрипта и целое число. Скрипт должен выполняться указанное число секунд, потом приостановиться на то же самое время, а затем продолжить выполнение, пока условие будет истинно. [Уровень: высокий.] Проекты 1. Напишите скелетный скрипт, который автоматизирует администрирование системы, используя интерфейс меню. Скрипт должен выполнять следующие задачи: • автоматическое удаление файлов ядра; • автоматические предупреждения пользователям, захватывающим много процессорного времени или дискового пространства; • автоматическая архивация. [Уровень: легкий.] 2. Напишите скрипт-менеджер псевдонимов, который позволяет выбрать эмуляцию DOS, эмуляцию VMS или ее отсутствие. [Уровень: средний.]

Глава 7 С shell Мотивация С shell был написан после Bourne shell и придерживается синтаксиса и структур управления языка С. Первый С shell стал фаворитом разработчиков UNIX, поскольку был призван поддерживать расширенное управление заданиями. Многие пользователи С shell переходят к Korn shell и Bash shell из-за их дополнительных возможностей и эффективности, но С shell все еще остается популярным. Предпосылки Вам необходимо прочитать главу 4 и поэкспериментировать с некоторыми из основных возможностей командного интерпретатора. Задачи В данной главе объясняются и демонстрируются специфические для С shell возможности. Изложение Информация представлена в форме нескольких примеров UNIX-сессий и небольшого проекта. Команды shell В главе рассматриваются следующие команды shell: alias nice chdir nohup dirs notify source stop suspend
foreach...end onintr switch...case...endsw glob popd unalias goto pushd unhash hashstat rehash unset history repeat unsetenv if...then...else...endif set while...end logout setenv Общие сведения о С shell С shell поддерживает все основные возможности shell, описанные в главе 4. а также новые: П несколько способов установки и доступа к переменным; П встроенный язык программирования, который поддерживает условные переходы, циклы и обработку прерываний; □ настройку команд с помощью псевдонимов; □ доступ к предыдущим командам через механизм истории; П расширенное управление заданиями; □ несколько новых встроенных и усовершенствованные существующие команды. Эти новые возможности проиллюстрированы в иерархической диаграмме на рис. 7.1. программирования заданиями команды alias unalias J°^s S,OP repeat while notify nohup set /setenv \ .cshrc unsetenv unset Рис. 7.1. Функциональность С shell
Запуск С shell — это обычная С-программа, исполняемый файл которой хранится как /bin/csh. Если ваш выбранный shell — это /bin/csh, диалоговый режим С shell активизируется автоматически при регистрации пользователя в UNIX. Вы можете также вызвать С shell вручную из скрипта или с терминала, используя команду csh, которая имеет несколько опций командной строки, описанных в конце главы. Когда С shell стартует, как входной shell, существующий глобальный файл инициализации входа в систему, который применим ко всем пользователям, может также’быть выполнен. Этот файл полезен для установки переменных окружения (типа path) так, чтобы они содержали информацию о локальном окружении. Имя файла изменяется от одной версии UNIX к другой, но похоже на /.login или /etc/login. При вызове С shell последовательность запуска различна для входа в систему с регистрацией и входе без регистрации shell, как показано в табл. 7.1. Обратите внимание, что файл .cshrc выполняется при входе в систему перед любым типом файла инициализации. Это может показаться нелогичным и, в конце концов, стало причиной непредвиденного поведения пользователей, когда обрабатывались их файлы инициализации. Следует помнить, что С shell всегда выполняет собственный файл инициализации немедленно при старте, а затем определяет, является ли shell входным shell, который требует выполнения других файлов инициализации. Таблица 7.1. Последовательность запуска С shell Шаг Тип shell Описание 1 Оба Выполняет команды из $HOME/.cshrc, если он существует 2 Только при входе Выполняет команды из глобального файла инициализации входа в систему, если он существует 3 Только при входе Выполняет команды из $номе/.login, если он существует Как только диалоговый shell стартует и завершает выполнять все соответствующие файлы инициализации, он выводит приглашение и ждет команды пользователя. Стандартным приглашением С shell является символ %, хотя он может быть изменен путем установки локальной переменной $prompt, которая описывается далее. Файл .login обычно содержит команды, которые устанавливают переменные окружения, например, term, содержащую тип вашего терминала, и path, сообщающую shell, где искать исполняемые файлы. Помещайте в пользовательский .login-файл определения, которые должны быть установлены толь
ко однажды (переменные окружения, чьи значения наследуются другими shell), или то, что имеет смысл лишь для диалоговой сессии (такие, как определенные установки терминала). Вот пример .login-файла: echo -n "Enter your terminal type (default is vtlOO):" set termtype = $< set term - vtlOO if ("$termtype" != "") set term = "$termtype" unset termtype set path=(. /bin /usr/bin /usr/local/bin ) stty erase "/ч?" kill ,,/4U" intr "ЛС" eof , ,,/чр" crt crterase set cdpath = (~) set history = 40 set notify set prompt = "! % " set savehist = 32 Файл .cshrc в основном содержит команды, которые устанавливают общие псевдонимы, или что-нибудь еще, что применимо только к текущему shell. Суффикс "гс" является аббревиатурой от англ, run commands — выполняемые команды. Вот пример файла .cshrc: alias emacs /usr/local/emacs alias h history alias 11 Is -1 alias print prf -pr pbl-2236-lp3 alias Is Is -F alias rm rm -i alicts m more Переменные C shell поддерживает локальные переменные и переменные окружения. Локальная переменная может содержать одно значение, тогда она называется простой переменной, или больше, чем одно значение, тогда она называется списком. Создание и присвоение значений простым переменным Чтобы присвоить значение простой переменной, используйте встроенную команду set.
Синтаксис set {имя [-значение]}* Если аргументы не представлены, команда set выводит список всех локальных переменных. Если значение не указано, параметру имя присваивается пустая строка. Если переменная имя не существует, она создается неявно. Вот некоторые примеры: % set flag ...присвоить переменной flag пустую строку. % echo $flag ...ничего не печатается, т. к. она пустая. % set color = rec? ...присвоить color строку red. % echo $color red % set name = Graham Glass ...осторожно! должны использоваться кавычки. % echo $пате ...только первая строка была присвоена. Graham % set name = "Graham Glass" ...теперь это работает, как ожидалось. % echo $пате Graham Glass % set ...вывести список всех локальных переменных, argv () cdpath /home/glass color red cwd /home/glass flag name Graham Glass term vtlOO user glass % Доступ к простой переменной В дополнение к простому синтаксису доступа к переменной ($имя), С shell поддерживает сложные методы доступа, показанные в табл. 7.2.
Представленные ниже примеры иллюстрируют эти методы доступа. В первом примере использовались фигурные скобки, чтобы добавить строку к значению переменной: % set verb = sing % echo I like $verbing verbing: Undefined variable. % echo I like ${verb}ing I like singing Таблица 7.2. Доступ к переменным С shell Синтаксис Описание ${имя} ${2 имя} Заменяется значением имя. Этот формат полезен, если за выражением немедленно следует алфавитно-цифровой символ, который иначе интерпретировался бы, как часть имени переменной Заменяется 1, если имя существует, и 0 в противном случае В следующем примере переменная использовалась как простой флаг в условном выражении: % cat flag.csh ...посмотреть скрипт. # set flag ...присвоить переменной flag пустую строку. if (${?flag}) then ...перейти, если flag установлен. echo flag is set endif % flag.csh ...выполнить скрипт. flag is set Q, Q Создание списочных переменных Чтобы присвоить переменной список значений, используйте встроенную команду set. Синтаксис set {имя = ({слово}* )}* Если указанная переменная не существует, она создается неявно. Переменной присваивается копия списка слов.
Пример: % set colors = ( red yellow green ) ...задать строку. % echo $colors ...вывести введенный список, red yellow green % Доступ к списочной переменной С shell поддерживает пару способов для доступа к списочной переменной. Оба эти метода имеют две формы, вторая окружена фигурными скобками (табл. 7.3). Вторая форма полезна, если за выражением немедленно следует алфавитно-цифровой символ, который иначе интерпретировался бы как часть имени переменной. Таблица 7.3. Доступ к списочным переменным С shell Синтаксис Описание $имя[селектор] Обе формы заменяются элементом имя, чей индекс опреде- ${имя[селектор]} лен значением селектора, который может быть числом, диапазоном чисел в формате “начало-конец" или звездочкой (*). Если начало опущено, предполагается 1. Если конец опущен, предполагается индекс последнего элемента. Если представлена *, то выбираются все элементы. Первый элемент списка имеет индекс 1 $#имя $ {#имя} Обе формы заменяются числом элементов в имени Вот некоторые примеры: % set colors = ( red yellow green ) % echo $colors[1] red % echo $colors[2-3] yellow green % echo $colors[4] Subscript out of range. % echo $#colors 3 % ...задать список. ...вывести первый элемент. ...вывести второй и третий элементы. ...недопустимый доступ. ...вывести размер списка.
Построение списков Чтобы добавить элемент в конец списка, присвойте первоначальному списку собственное значение, следом через пробел укажите новый элемент и окружите эту конструкцию круглыми скобками. Если вы пробуете присвоить новому несуществующему элементу значение, то получите сообщение об ошибке. Следующий пример иллюстрирует некоторые манипуляции со списком: % set colors = ( red yellow green ) % set colors[4] = pink Subscript out of range. % set colors = ( $colors blue ) % echo $colors red yellow green blue •* % set colors[4] = pink % echo $colors red yellow green pink % set colors = $colors black % echo $colors red $ set girls - ( sally georgia ) $ set boys = ( harry blair ) $ set both = ( $girls $boys ) $ echo $both sally georgia harry blair g. о ...задать список. ...попытка установить четвертый. * ...добавить к списку. ...это работает! .хорошо, т. к. четвертый существует. .ч - I , .не забудьте использовать (). .только первый был установлен. • 9' .построить один список. .построить другой. .объединить списки. .вывести результат. Предопределенные локальные переменные В дополнение к обычным предопределенным локальным переменным С shell определяет переменные, перечисленные в табл. 7.4. Вот маленький скрипт, который использует переменную $<, чтобы получить ответ пользователя: % cat var5.csh ...вывести скрипт. # echo -n "please enter your name: " set name = $< # получить входную строку, echo hi $name, your current directory is $cwd % var5.csh ...выполнить скрипт. please enter your name: Graham hi Graham, your current directory is /home/glass g о
Таблица 7.4. Предопределенные локальные переменные С shell Имя Описание $?0 Равно 1, если shell выполняет команды из указанного файла; 0 — иначе $< Следующая строка стандартного входа приводится полностью $argv Список, содержащий все позиционные параметры: $argv[l], эквивалентен $1 $cdpath Список альтернативных каталогов, которые использует chdi г для целей поиска $cwd Текущий рабочий каталог $echo Установлена, если опция командной строки -х активна $histchars Может применяться, чтобы переопределить метасимволы истории по умолчанию. Первый символ используется вместо ! для замен истории, а второй — вместо 74 для быстрого повторного выполнения команды $history Размер списка истории $home Домашний каталог shell $ignoreeof Защищает shell от завершения, когда тот получает сигнал от нажатия комбинации клавиш <Ctrl>+<D> $mail Список файлов для проверки почты. По умолчанию shell проверяет почту каждые 600 секунд (10 минут). Если первое слово $mail — это число, shell использует его значение вместо значения по умолчанию $noclobber Защищает существующие файлы от подмены по символу > и не- $noglob существующие файлы от добавления по символу » Предотвращает расширение групповых символов $nonomatch Предотвращает появление ошибки, если нет файлов, соответствующих имени файла при расширении группового символа $notify По умолчанию shell уведомляет пользователя об изменениях в статусе работы как раз перед выводом нового приглашения. Если переменная $ not if у установлена, изменения в статусе отображаются немедленно, когда они происходят $path Используется shell для определения местоположения выполняемых файлов $prompt Приглашение shell $savehist Количество команд, сохраняемых в файле истории $shell Полное путевое имя входного shell
Таблица 7.4 (окончание) Имя Описание $status Код возврата последней команды $time Если переменная установлена, любой процесс, который берет больше указанного количества секунд, приводит к выводу сообщения, указывающего статистику процесса $verbose Установлена, если используется опция командной строки -v Создание и присвоение переменных окружения Чтобы назначать значение переменной окружения, используйте встроенную команду setenv. Синтаксис setenv имя значение Если указанная переменная не существует, она неявно создается; в противном случае переписывается. Обратите внимание, что переменные окружения всегда хранят одно значение. Для них нет такого понятия, как список окружения. Пример: % setenv TERM vt52 ...установить тип моего терминала. % echo $TERM ...подтвердить. vt52 о о Предопределенные переменные окружения В дополнение к общим предопределенным переменным окружения, С shell поддерживает переменную, показанную в табл. 7.5. Таблица 7.5. Предопределенная переменная окружения С shell Имя Описание $LOGNAME ID владельца shell
Выражения С shell поддерживает строковые, арифметические и ориентированные на файл выражения. Строковые выражения С shell поддерживает строковые операторы, показанные в табл. 7.6. Если операнд — это список, то первый элемент списка служит для сравнения. Скрипт в следующем примере использовал технику соответствия строки, чтобы сделать вывод об ответе пользователя: % cat exprl.csh # echo -n "do you like the C shell?" set reply = $< if ($reply == "yes") then echo you entered yes else if ($reply =~ y*) then echo I assume you mean yes endif % exprl.csh do you like the C shell? yeah I assume you mean yes % ...посмотреть скрипт. # приглашение. # получить строку со входа. # проверить на точное совпадение. # проверить на неточное совпадение. ...выполнить скрипт. Таблица 7.6. Строковые операторы С shell Оператор Описание == Возвращает истину, если строковые операнды абсолютно равны ! = Возвращает истину, если строковые операнды не равны =~ Подобен ==, за исключением того, что правый операнд может содержать групповые символы ! ~ Подобен ! =, за исключением того, что правый операнд может содержать групповые символы Арифметические выражения I С shell поддерживает арифметические операторы, перечисленные в табл. 7.7 в порядке убывания приоритета. Они работают точно так же, как их стандартные С-копии за исключением того, что обрабатывают только целые
числа. Выражения могут быть окружены круглыми скобками для управления порядком вычислений. Таблица 7.7. Арифметические операторы С shell Операторы Описание । Унарный минус Логическое отрицание * / 9- > / > ° Умножение, деление, остаток + > “ Сложение, вычитание Побитовый сдвиг влево, побитовый сдвиг вправо < , > , о Операторы отношения == I = ! • Равенство, неравенство А> 1 Битовое И, битовое исключающее ИЛИ, битовое ИЛИ 1 1 , & & Логическое ИЛИ, логическое И Когда вычисляется арифметическое выражение, пустая строка эквивалентна 0. Любое выражение, содержащее операторы &, &&, |, I I, <, >, « или >>, должно быть заключено в круглые скобки, чтобы помешать shell интерпретировать эти символы специальным образом. Вот типичный скрипт, который использует пару операторов: % cat ехргЗ.csh ...вывести скрипт. # set а = 3 set Ь = 5 if ($а >2 && $Ь > 4) then echo expression evaluation seems to work endif % ехргЗ.csh ...выполнить скрипт. expression evaluation seems to work о о Вы не можете применять команду set, чтобы назначить результат выражения переменной. Вместо этого используйте встроенную команду @, которая имеет форматы, показанные ниже.
Синтаксис Формат Описание Выводит все переменные shell @ переменная ор выражение Присваивает переменной выражение @ переменная [ индекс] ор выражение Присваивает выражение переменной с индексом Здесь ор — это =, += *= или /=. Вот некоторые примеры: % set <а=2*2 ...вы не можете использовать set для присвоения. set: Syntax error. о, о ...использование @ вместо. о, о echo $а 0 а = 2 * 2 % @ а = $а + $а ...сложить две переменные. % echo $а 8 % set flag = 1 % @ b = ($а && $flag) ...нужны скобки, потому что &&. % echo $b 1 % @ b = ($а && $flag) % echo $b О Вы можете также увеличить или уменьшить значение переменной на 1, используя ++ или —, как в следующих командах: % set value = 1 % @ value ++ % echo $value 2
Выражения, ориентированные на файл Чтобы сделать решение задач с использованием файлов немного легче для программирования, С shell поддерживает несколько файловых выражений. Описание каждой опции показано в табл. 7.8. Синтаксис -опция имяФайла Возвращается 1 (истина), если выбранная опция истинна, и 0 (ложь) в противном случае. Если имяФайла не существует или недоступно, все опции возвращаются 0. Таблица 7.8. Опции, используемые в файловых выражениях Опция Описание г Shell имеет разрешение чтения для имяФайла w Shell имеет разрешение записи для имяФайла X Shell имеет разрешение выполнения для имяФайла е имяФайла существует о имяФайла принадлежит тому же самому пользователю, что и процесс shell z имяФайла существует и имеет размер 0 байт f имяФайла — это обычный файл (не каталог или специальный файл) d имяФайла — это файл каталога (не обычный каталог или специальный файл) Вот пример скрипта, который использует опцию -w, чтобы определить, является ли файл перезаписываемым: % cat ехрг4.csh ...вывести скрипт. # echo -n "enter the name of the file you wish to erase:" set filename = $< # получить строку ввода. if (! (-w "$filename")) then # проверить, имею ли доступ. echo you do not have permission to erase that file. else rm $ filename
echo file erased endif % expr4.csh ...выполнить скрипт. enter the name of the file you wish to erase: / you do not have permission to erase that file. % Автоматическое дописывание имени файла Подобно Korn shell, С shell предусматривает способ избежать ввода длинных имен файлов в командной строке. (Эта функциональная возможность была введена в течение эволюции С shell, поэтому старые версии не могут обеспечивать ее.) Чтобы включать функцию дописывания имени файла, вы должны установить переменную fiiec: % set fiiec Теперь всякий раз, когда вы напечатаете часть имени файла, можете нажать клавишу <Esc>, и если часть имени файла, которую вы напечатали только что, уникально идентифицирует файл, остальная часть имени будет добавлена автоматически. Если текст уникально не идентифицирует файл, ничего изменено не будет, и вы услышите звуковой сигнал. Вы можете также ввести *, чтобы увидеть список имен файлов, которые в настоящее время соответствуют части напечатанного имени. Пример: % Is -al . log* .login .logout % Is -al .login ...нажал клавишу <Esc> — ничего, тогда напечатал * ...shell повторно вывел, я добавил i и нажал клавишу ...<Esc>, а затем была добавлена буква "п". Псевдонимы С shell позволяет создавать и настраивать собственные пользовательские команды с помощью встроенной команды alias. Пример: % alias dir 'Is -aF' ...регистрация псевдонима. % dir ...так же как печатание Is -aF. . / main2.c р.reverse.с reverse.h ../ main2.o palindrome.c reverse.old % dir *.c ...так же как печатание Is -aF *.с.
main2.с р.reverse.с palindrome.с % alias dir ...посмотреть значение, ассоциированное с dir. Is -aF $ Синтаксис alias [слово [строка]] Команда alias поддерживает простую форму настройки командной строки. Если ваше слово псевдонима равно строке и позже вы вводите команду, начинающуюся со слова, первое его появление зал^еняется строкой, и затем команда обрабатывается повторно. Если вы не вводите слово или строку, то отображается список всех текущих псевдонимов shell. Если вводится только слово, то выводится строка, в настоящее время связанная со словом псевдонима. Если вы снабжаете слово и строку, shell добавляет указанный псевдоним к его собранию псевдонимов. Если для слова псевдоним уже существует, он заменяется. Если строка замены начинается со слова, оно не обрабатывается повторно для псевдонимов, чтобы предотвратить бесконечные циклы. Если строка замены содержит слово где-нибудь в другом месте, появляется сообщение об ошибке во время выполнения псевдонима. В следующем примере создается псевдоним слова в терминах его самого: % alias Is 'Is -aF' % Is *.c main2.c p.reverse.c % alias dir 'Is' % dir ./ main2.c p.reverse.c ../ main2.o palindrome.c % alias who 'date; who' % who ...определить Is в терминах его самого. ...так же как печатание Is -aF *.с. palindrome.с ...определить dir в терминах 1s. ...так же как печатание Is -aF. reverse.h reverse.old ...идентифицирует проблему цикла. Alias loop. % alias who 'date; /bin/who' ...полный путь предотвращает ошибку. % who ...теперь работает прекрасно. Fri Feb 13 23:33:37 CST 1998 glass ttypO Feb 13 23:30 (xyplex2)
Удаление псевдонима Чтобы удалить псевдоним, используйте встроенную команду unalias. Синтаксис unalias шаблон Команда unalias удаляет все псевдонимы, которые соответствуют шаблону. Если в качестве шаблона указан символ *, то удаляются все псевдонимы. Полезные псевдонимы В табл. 7.9 предоставлен список полезных псевдонимов вместе с кратким описанием каждого. Я храню эти псевдонимы в моем файле .cshrc.1 Таблица 7.9. Полезные псевдонимы С shell Псевдоним Команда Описание cd cd \!*; set prompt = "$cwd \!>"; Is V Приглашение пользователя изменяется так, чтобы оно содержало и текущий рабочий каталог, и самый последний номер команды (см. разд. “История" далее в этой главе) Is Is -F Предписывает утилите 1s включить дополнительную информацию о файле rm rm -i Предписывает утилите rm запрашивать подтверждение rm mv\!* -/tomb Заставляет утилиту rm перемещать файл в специальный каталог tomb вместо удаления файла h history Отображает список предварительно используемых команд, печатая только один символ vi (mesg n; /bin/vi \!*; mesg y) Защищает от получения сообщений от других пользователей, в то время как вы находитесь в редакторе vi mroe more Исправляет обычную ошибку орфографии при использовании утилиты more 1 Очень часто повествование в книге ведется от имени одного автора, по видимому, Грехама Гласса. — Ред.
Таблица 7.9 (окончание) Псевдоним Команда Описание ls-11 1s -1 * Исправляет обычную ошибку орфографии при использовании утилиты 1s 11 1s -1 Позволяет получать расширенный листинг каталога Разделение псевдонимов Чтобы делать псевдоним доступным дочернему shell, разместите его определение в shell-файле .cshrc. Параметризованные псевдонимы Псевдоним может ссылаться на аргументы в оригинальной команде, используя механизм истории, описанный в следующем разделе. К первоначальной команде обращаются, как будто это была предыдущая команда. Полезный псевдоним для cd, который я упомянул, демонстрирует удачное использование этой возможности: часть псевдонима \! * заменяется всеми аргументами первоначальной команды. Символ \ предшествует !, чтобы запретить его специальное значение во время назначения псевдонима: alias cd ’cd \!*; set prompt = "$cwd \l > ”; Is’ История C shell хранит запись команд, введенных с клавиатуры так, чтобы их можно было отредактировать и позднее повторно выполнить. Эта функция известна как механизм историй. Метасимвол ! дает доступ к истории. Пронумерованные команды При использовании истории удобно принять меры, чтобы приглашение содержало номер команды, которую пользователь собирается ввести. Для этого добавьте последовательность \! в значение переменной приглашения: % set prompt = '\! % 1 ...включить номер события в приглашение. 1 % echo Genesis . . . эта команда — событие с номером 1. Genesis 2 % ... следующая команда будет событием с номером 2.
Сохранение команд С shell делает запись последних $history команд во время конкретной сессии. Если значение переменной $history не установлено принудительно, по умолчанию используется 1. Если вы хотите, чтобы следующая сессия могла иметь доступ к этим командам, установите переменную $savehist. Тогда последние $savehist команды поддерживаются в файле, указанном переменной histfile (который обычно определен по умолчанию в $HOME/.history, но может изменяться с версиями UNIX). Файл истории разделяется всеми диалоговыми С shell, созданными одним пользователем, если переменной histfile преднамеренно не присвоено никакое уникальное значение в каждом из shell. В представленном ниже примере я проинструктировал мой shell помнить последние 40 команд в списке истории и сохранять последние 32 команды между сессиями: 2 % set history = 40 . . .помнить последние 40 команд. 40 3 % set savehist = 32 ...хранить 32 команды между сессиями. 32 4 % Чтение истории Чтобы получить листинг истории shell, используйте встроенную команду history. Синтаксис history [-rh] [номер] Команда history позволяет получать доступ к списку истории shell. Если никакие параметры не указаны, команда выводит последние $history команд. Опция -г обеспечивает вывод списка истории в обратном порядке, а опция -h запрещает показ номеров событий. Команде history обычно назначается псевдоним h для скорости ее ввода. Вот пример: 4 % alias h history ...создать полезный псевдоним. 5 % h ' ...вывести текущую историю. 1 set prompt = ’\! % ’
2 set history = 40 3 set savehist =32 4 alias h history 5 h 6 % h -r 3 ... вывести последние 3 команды в обратном порядке. 6 h 3 5 h 4 alias h history % Повторное выполнение команды Чтобы повторно выполнить предыдущую команду, используйте метасимвол ! в одном из форматов, указанных в табл. 7.10. Эти последовательности могут появляться где угодно в командной строке, хотя обычно используются самостоятельно. Таблица 7.10. Повторное выполнение команд в С shell Формат Описание । । Заменяется текстом самой последней команды ! номер Заменяется текстом команды с указанным номером !префикс Заменяется текстом самой последней команды, которая начиналась с префикса !?подстрока? Заменяется текстом самой последней команды, которая содержит подстроку Вновь вызванная команда выводится на терминал до своего выполнения. Значение префикса или подстроки не может содержать пробел. Специальное значение ! не запрещается никаким видом кавычек, но может быть заблокировано предшествующим ему пробелом, символом табуляции, =, ( или \. Вот некоторые примеры: 41 % echo event 41 event 41 42 % echo event 42 event 42 43 % !! . ’ echo event 42 event 4 2 44 % !41 ...простое echo. ...другое простое echo. ...повторно выполнить последнюю команду. . . .вывод команды перед повторным выполнением. ...повторно выполнить команду с номером 41.
echo event 41 event 41 45 % /ec echo event 41 event 41 46 % . . .вывод команды перед повторным выполнением. ...повторно выполнить команду, начинающуюся с ес. . . .вывод команды перед повторным выполнением. Доступ к частям команд истории Вы можете получить доступ к части предыдущей команды с помощью модификаторов истории, которые являются набором опций. Эти опции могут немедленно следовать за спецификатором события. Каждый модификатор возвращает отдельную лексему или ряд лексем от указанного события. Табл. 7.11 предоставляет список модификаторов. Двоеточие перед опциями $ и * является необязательным. Таблица 7.11. Модификаторы истории С shell Модификатор Возвращаемые лексемы :0 Первая : номер (номер + 1) :начало-конец (начало + 1) до (конец + 1) . Первая :$ Последняя . * Предпоследняя Для использования одного из модификаторов на самой последней команде вы можете указать перед ним модификатор !! или только !. Вот некоторые примеры: 48 % echo I'like horseback riding ...оригинальная строка. I like horseback riding 49 % !!:0 !!:1 !!:2 !!:4 ...доступ к указанным аргументам. echo I like riding I like riding 50 % echo !48:l-$ ...доступ к ряду аргументов. echo I like horseback riding I like horseback riding 51 %
Доступ к частям имен файлов Если модификатор истории ссылается на имя файла, оно может быть далее изменено для получения доступа к конкретной части имени. Существующие модификаторы могут сопровождаться модификаторами имени файла, показанными в табл. 7.12. Таблица 7.12. Модификаторы имен файлов С shell Модификатор Часть файла Часть указанного имени файла, которая возвращается :h Заголовок Путь к файлу : г Корень Полное имя файла без расширения файла : е Расширение Расширение файла : t Остаток Имя и расширение файла Следующий пример демонстрирует обращение к различным частям оригинального имени файла с помощью вышеупомянутых средств доступа к имени файла: 53 % Is /usr/include/stdio.h ...оригинал. /usr/include/stdio.h 54 % echo 153:l:h ...доступ к заголовку. echo /usr/include /usr/include 55 % echo !53:l:r echo /usr/include/stdio /usr/include/stdio ...доступ к корню. 56 % echo !53:l:e echo h h ...доступ к расширению. 57 % echo !53:l:t echo stdio.h ...доступ к остатку. stdio.h Q. О Замена истории Модификатор замены замещает указанную часть команды в предыдущем событии.
Синтаксис !событие:s/часть1/часть2/ Эта последовательность заменяется указанным событием после замены первого Появления части1 частью2. Пример: 58 % Is /usr/include/stdio.h /usr/include/stdio.h 58 % echo 158:1:s/stdio/signal/ echo /usr/include/signal.h /usr/include/signal.h 59 % ...оригинал. ...выполнить замену. Структуры управления С shell поддерживает широкий диапазон структур управления, которые делают язык похожим на инструмент программирования высокого уровня. Программы shell обычно сохраняются в скриптах и, как правило, используются для автоматизации задачи установки и сопровождения. Некоторые структуры управления требуют, чтобы вводились несколько строк. Если такая структура управления вводится с клавиатуры, shell выводит приглашение символом ? для ввода каждой дополнительной строки, пока структура управления не закончится в точке, в которой она выполняется. Далее приводится описание каждой структуры управления в алфавитном порядке. Демонстрационные примеры С shell похожи на примеры Bourne shell, чтобы вы могли сравнить и противопоставить два командных интерпретатора. foreach... end Команда foreach позволяет списку команд выполняться многократно, каждый раз используя различное значение для указанной переменной. Синтаксис foreach имяПеременной (списокСлов) списокКоманл end
Команда foreach выполняет итерацию значения имяПеременной через каждое значение В спискеСлов, ВЫПОЛНЯЯ списокКоманд ПОСЛе КаЖДОГО присвоения. Команда break заставляет цикл немедленно завершиться, а команда continue — перейти к следующей итерации. Вот пример скрипта, который содержит команду foreach: % cat foreach.csh ...вывести скрипт. # foreach color (red yellow green blue) # четыре цвета echo one color is $color end % foreach. csh one color is red one color is yellow one color is green one color is blue ...выполнить скрипт. о, о goto Команда goto осуществляет безусловный переход к указанной метке. Для того чтобы объявить метку, просто начините строку с ее имени, а за именем поставьте двоеточие. Синтаксис goto метка Метка в виде метка: должна существовать далее в скрипте. Появляется утверждение goto, управление передается строке, следующей за меткой. Обратите внимание, что метка может как предшествовать, так и следовать за утверждением goto, даже если команда вводится с клавиатуры. Используйте goto осторожно, избегайте кода "похожего на спагетти"2 (даже если вы любите спагетти). Вот пример простой конструкции с командой goto: % cat goto.csh ...вывести скрипт. # echo gotta jump 2 To есть длинного кода. — Ped.
goto endOfScript # переход echo I will never echo this endOfScript: # метка echo the end % goto.csh ...выполнить скрипт, gotta jump the end о Ъ if... then... else... endif Есть два формата команды if. Первый поддерживает простой односторонний переход. Синтаксис i f (условие) команда \ Эта форма команды if вычисляет условие и, если оно истинно (не НОЛЬ), ВЫПОЛНЯет команду. Вот пример этого формата if: % if (5 > 3) echo five is greater than 3 five is greater than three о "6 Второй формат команды if поддерживает альтернативный переход. Синтаксис if {условие!) then списокКоманд! else if {условие2) then списокКоманд2 else списокКомандЗ endif
Части команды else и else if не обязательны, но завершающий endif должен обязательно присутствовать. Если условие1 истинно, выполняются команды из спискаКоманд1 и команда if завершается. Если условие! ложно и есть одно или больше составляющих else if, то истинное выражение, следующее после else if, вызывает выполнение команд после связанного then, и команда if заканчивается. Если не найдено ни одного истинного выражения и есть составляющая else, выполняются команды После else. Пример второй формы if: % cat if.csh ...вывести скрипт. # echo -n ’enter a number: ’ set number = $< if ($number < 0) then # приглашение пользователю. # читать строку ввода. echo negative else if ($number == 0) then echo zero else echo positive endif % if. csh ...выполнить скрипт. enter a number: -1 negative g. о onintr Команда onintr позволяет определить метку, на которую должен быть осуществлен переход, когда shell получает сигнал sigint. Этот сигнал обычно производится нажатием комбинации клавиш <Ctrl>+<C> и описан более подробно в главе 13. Синтаксис onintr [- | метка] Команда onintr инструктирует shell перейти на метку, когда получен сигнал sigint. Если используется опция сигнал sigint игнорируется. Если опции не указаны, восстанавливается оригинальная shell-программа обработки SIGINT.
Пример: % cat onintr.csh ...вывести скрипт. onintr controlC # установить ловушку на <Ctrl>+<C>. while (1) echo infinite loop sleep 2 end controlC: echo control C detected % onintr.csh ...выполнить скрипт, infinite loop infinite loop ЛС ...нажал <Ctrl>+<C>. control C detected repeat Команда repeat позволяет выполнять отдельную команду указанное количество раз. Синтаксис repeat выражение команда Команда repeat вычисляет выражение И ВЫПОЛНЯет команду необходимое количество раз. Пример использования repeat: % repeat 2 echo hi there ...вывести две строки. hi there hi there switch... case... endsw Команда switch поддерживает многопозиционный переход, основанный на значении единственного выражения.
Синтаксис switch (выражение) case шаблон1: списокКоманд! breaksw case шаблон2: списокКомандЗ breaksw case шаблонЗ: списокКомандЗ breaksw default: списокКоманД—defa ul t endsw В выражении определяется строка., шаблон1, шаблон2, шаблонЗ могут включать Групповые СИМВОЛЫ, а списокКоманд 1, списокКоманд2, списокКомандЗ, списокКоманд_(1еfault — списки из одной или более команд shell. Командный интерретатор вычисляет выражение и сравнивает его с каждым шаблоном сверху вниз. Как только шаблон соответствия найден, выполняется его ассоциированный список команд, и затем shell переходит на строку endsw. Если никакого совпадения не найдено и условие по умолчанию присутствует, то выполняется списокКоманд-default. Если ника-кого совпадения не найдено, а условие по умолчанию отсутствует, то выполнение продолжается с команды после endsw. Ниже приведен пример скрипта menu.csh, который использует структуру управления switch: # echo menu test program set stop =0 # переустановить флаг завершения цикла while ($stop ==0) # пока не выполнится цикл cat « ENDOFMENU # вывести меню 1 : print the date 2r 3: print the current working directory 4 : exit ENDOFMENU echo
echo -n ’your choice? ' set reply = $< echo "" switch ($reply) case "1": date breaksw case ”2H: case ”3”: pwd breaksw case "4": set stop = 1 breaksw default: echo illegal choice breaksw endsw end # приглашение # читать ответ # процесс ответа # вывести дату # вывести рабочий каталог # установить флаг завершения цикла # по умолчанию # ошибка \ Вот вывод скрипта menu.csh: % menu.csh menu test program 1 : print the date 2f 3: print the current working directory 4 : exit your choice? 1 Sat Feb 14 00:50:26 CST 1998 1 : print the date. 2 , 3: print 'the current working directory 4 : exit your choice? 2 /home/glass 1 : print the date. 2 , 3: print the current working directory 4 : exit your choice? 5 illegal choice 12 Зак. 786
1 : print the date. 2 , 3: print the current working directory 4 : exit your choice? 4 while... end Встроенная команда while позволяет многократно выполнять список команд, пока указанное выражение истинно (не ноль). Синтаксис while (выражение) списокКоманл end Команда while вычисляет выражение и, если оно истинно, выполняет каждую команду в спискеКоманд, а затем повторяет процесс. Если выражение ложно, цикл while заканчивается и скрипт продолжает выполнять команды, следующие за end. Команда break немедленно завершает цикл, команда continue предписывает перейти к следующей итерации. Вот пример скрипта, который содержит структуру управления while, чтобы вывести маленькую таблицу умножения: % cat multi.csh ...вывести скрипт. # set x = 1 # установить значение внешнего цикла while ($x <= $1) # внешний цикл set у = 1 # установить значение внутреннего цикла while ($y <= $1) # внутренний цикл @ v = $x * $y # вычисление входа echo -n $v " " # вывести вход @ у ++ # обновить счетчик внутреннего цикла end echo "" # новая строка @ x ++ # обновить счетчик внешнего цикла end % multi. csh 7 ...выполнить скрипт.
т 2 3 4 5 6 7 2 4 6 8 10 12 14 3 6 9 12 15 18 21 4 8 12 16 20 24 28 5 10 15 20 25 30 35 6 12 18 24 30 36 42 7 14 21 28 35 42 49 % _ Пример проекта: junk Для иллюстрации некоторых возможностей С shell, которые мы обсудили, ниже представлена версия скрипта С shell junk, который был предложен в конце главы 6. Синтаксис junk -1р {имяФайла}★ junk заменяет утилиту rm. Вместо удаления файлов она перемещает их в каталог .junk домашнего каталога пользователя. Если каталог .junk не существует, он автоматически создается. Опция -1 выводит текущее содержимое каталога .junk, а опция -р производит его очистку. Скрипт С shell, который представлен ниже, использует списочную переменную для хранения имен файлов. Остальная часть функциональных возможностей очевидна из представленных комментариев. (Скрипт shell доступен в сети. Для дополнительной информации см. Введение.) junk #! /bin/csh #junk script #author: Graham Glass #9/25/91 # #Initialize variables # set fileList = () # список всех специфицированных файлов. set listFlag =0 # установлена в 1, если Опция -1 указана.
set purgeFlag =0 # 1, если опция -р указана. set fileFlag =0 # 1, если хотя бы один файл специфицирован, set junk = ~/.junk # каталог .junk. # # Разбор командной строки. # foreach arg ($*) switch ($arg) case "-p": set purgeFlag = 1 breaksw case "-1": set listFlag = 1 breaksw case echo $arg is an illegal option goto error breaksw default: set fileFlag = 1 set fileList = ($fileList $arg) # добавить в список, breaksw endsw end # # Проверка: не слишком ли много опций. # 0 total = $listFlag + $purgeFlag + $fileFlag if ($total != 1) goto error # # Если каталог .junk не существует, создать его. # if (!(-е $junk)) then 'mkdir' $junk endif # # Обработка опций. # if ($listFlag) then
'Is’ -IgF $junk # вывести каталог .junk, exit 0 endif # if ($purgeFlag) then 'rm' $junk/* # удалить содержимое каталога .junk, exit 0 endif # if ($fileFlag) then ’mv' $fileList $junk # переместить файлы в каталог .junk, exit О endif # exit 0 # # Вывести сообщение, об ошибке и выйти. # error: cat « ENDOFTEXT Dear $USER, the usage of junk is as follows: junk -p means "purge all files" junk -1 means "list junked files" junk <list of files> to junk them ENDOFTEXT exit 1 Усовершенствования В дополнение к новым возможностям, которые уже были описаны, С shell имеет усовершенствования в следующих областях: □ клавиатурные комбинации быстрого вызова для повторного выполнения команды; □ метасимволы {, }; □ замещение имени файла; □ перенаправление; □ конвейеризация; П управление заданием.
Повторное выполнение команды: клавиатурная комбинация быстрого вызова Обычно возникает желание повторно выполнить предыдущую команду с небольшой модификацией. Например, скажем, вы напечатали с ошибкой имя файла. Вместо fil.txt вы хотели ввести file.txt. Есть удобный стенографический способ исправить такую ошибку. Если вы напечатаете команду Л часть1^часть2 то предыдущая команда повторно выполнится после того, как первое вхождение части! заменится частыо2. Эта сокращенная процедура применима только к предыдущей команде. Вот пример: % Is -1 fil.txt ...on! Is: File or directory "fil.txt" is not found. % ^fil^file ...быстрая коррекция. Is -1 file.txt ...хорошо. -rw-r-xr-x 1 ables 410 Jun 6 23:58 file.txt % _ Метасимволы: {} Вы можете использовать фигурные скобки вокруг имен файлов, чтобы сэкономить ввод обычных приставок и суффиксов. Нотация a{b, c}d заменяется abd acd В следующем примере я скопировал С-файлы заголовков /usr/include/stdio.h и /usr/include/signal.h (которые имеют обычную приставку и суффикс) в мой домашний каталог: % ср /usr/include/(stdio,signal).h . ...копировать два файла. О Замещение имен файлов В дополнение к обычным возможностям замещения имени файла С shell поддерживает две новых особенности: способность запретить замещение имени файла и способность определить, какое действие должно быть предпринято, если шаблон ни с чем не совпадает.
Запрещение замещения имени файла Чтобы запретить замещение имени файла, установите переменную $nogiob. Тогда групповые символы теряют их специальное значение. Переменная $nogiob по умолчанию не установлена. Вот пример: % echo а* р* ...один групповой символ совпадает: р* progl.с • prog2.с ргодЗ.с ргод4.с % set noglob ...скрыть обработку группового символа. % echo а* р* а* р* о О Ситуации несовпадения Если несколько шаблонов присутствуют в команде и, по крайней мере, один из них имеет совпадение, то ошибка не возникает. Однако, если ни один из шаблонов не имеет совпадения, по умолчанию shell выводит сообщение об ошибке. Если переменная $nonomatch установлена, и совпадения не происходит, то первоначальные образцы используются без изменения. Переменная по умолчанию $nonomatch не установлена. Пример: % echo а* р* ...один групповой символ совпадает: р* progl.с prog2.с ргодЗ.с ргод4.с % echo а* b* ...ни один групповой символ не совпадает. echo: No match. ...по умолчанию произошла ошибка. % set nonomatch ...установить специальную переменную nonomatch. % echo a* b* ...групповые символы теряют их специальные значения. a* b* ...нет ошибок. Перенаправление В дополнение к обычным возможностям перенаправления С shell поддерживает пару усовершенствований: способность переадресовывать стандартный канал ошибки и способность защитить файлы против случайного переписывания. Перенаправление стандартного канала ошибки Для того чтобы переадресовать стандартный канал ошибки в дополнение к стандартному каналу вывода, просто добавляют амперсант (&) к оператору переназначения > или », как показано в следующем примере: % Is -1 a.txt b.txt >list.out ...Is посылает ошибки ...в стандартный канал ошибок.
Is: File or directory "b.txt" is not found. % Is -1 a.txt b.txt >& list.out ...так же переадресовывает . . .и стандартный канал ошибок, о. о Хотя не существует простого способа переадресовать только канал ошибки, это может быть сделано с помощью следующей хитрости: (процесс! > файл!) >& файл2 Это работает путем переадресации стандартного вывода процесс! в файл! (который может быть /dev/null, если вы не хотите сохранять вывод), позволяя только стандартным ошибкам покидать командную группу. Вывод командной группы и каналы ошибки тогда переадресованы в файл2. Защита файлов от случайного переписывания Вы можете защитить существующие файлы от случайного переписывания и несуществующие файлы от случайного добавления путем установки переменной $nociobber. Если команда shell пробует выполнить то или другое действие, она терпит неудачу и выводит сообщение об ошибке. Внимание Обычные системные вызовы типа write () не затрагиваются. По умолчанию $nociobber не установлена. Вот пример: % Is -1 errors ...посмотреть существующие файлы, -rw-r-хг-х 1 glass 225 Feb 14 10:59 errors % set noclobber ...защитить файлы. % Is a.txt >& errors ...не может быть переписан, errors: File exists, о о Чтобы временно заблокировать эффект от $nociobber, добавьте символ « к оператору переназначения: % Is a.txt >&! errors ...существующий файл переписан. о, о Конвейеризация В дополнение к поддержке обычных возможностей конвейеризации С shell позволяет направлять стандартный вывод и стандартный канал ошибки от процесса! К процессу2.
Синтаксис процесс! |& процесс2 В приведенном ниже примере организован конвейер стандартного вывода и канала ошибки от утилиты is к утилите more: % Is -1 a.txt b.txt |& тоге ...конвейеризация стандартных каналов ...вывода и ошибки. Is: File or directory "b.txt" is not found, -rw-r-xr-x 1 ables 988 Dec 7 06:27 a.txt о о Хотя нет никакого прямого пути конвейеризации только канала ошибки, это может быть сделано, если прибегнуть к хитрости, подобной описанной ранее, для того чтобы конвейеризировать в файл только канал ошибки: (процесс! > файл) |& процесс2 Эта хитрость работает путем переадресации стандартного вывода процесс! в файл! (который может быть /dev/null, если вы не хотите сохранять вывод), позволяя только стандартным ошибкам покидать командную группу. Вывод командной группы и канал ошибки тогда конвейеризированы в процесс2. Но поскольку стандартный вывод теперь пуст, результатом является только стандартный вывод ошибки. Управление заданием Возможности управления заданиями С shell те же самые, как и в Korn shell, с дополнительными встроенными командами: О stop; О nice; О suspend; О nohup; О notify. stop Чтобы приостановить указанную работу, используйте команду stop. Синтаксис stop {%задание}* Команда stop приостанавливает указанное задание, используя стандартный формат спецификатора задания, описанный в главе 6. Если аргументы отсутствуют, приостанавливается последнее упомянутое задание.
suspend Чтобы приостановить командный интерпретатор, вызовите команду suspend. Синтаксис suspend Команда suspend приостанавливает shell, который ее вызвал. Имеет смысл применять данную команду тогда, когда shell является дочерним по отношению к входному shell, и это обычно делается, чтобы приостановить shell, вызванный утилитами su или script. nice Чтобы установить уровень приоритета shell или команды, используйте команду nice. Для дополнительной информации о приоритетах процесса см. главу 13. Синтаксис nice [+|- число] [команда] Команда nice запускает команду с уровнем приоритета число. Вообще, чем выше приоритет, тем медленнее будет выполняться процесс. Только привилегированный пользователь (root) может определить отрицательный уровень приоритета. Если уровень приоритета опущен, уровень приоритета предполагается равным 4. Если аргументы не специфицированы, устанавливается уровень приоритета shell. nohup Чтобы защищать команду от зависания, используйте встроенную команду nohup. Синтаксис nohup [команда ] Команда nohup выполняет команду и защищает ее от зависания. Если аргументы не специфицированы, то все дальнейшие команды работают под уровнем защиты shell. Обратите внимание, что все фоновые команды в С shell автоматически защищены от зависания.
notify Обычно shell уведомляет пользователя относительно изменения в состоянии задания прямо перед выводом нового приглашения. Если пользователь желает получить немедленное (асинхронное) уведомление об изменении состояния задания, используется встроенная команда notify. Синтаксис notify {%задание}* Команда notify инструктирует shell немедленно сообщать пользователю, когда указанные задания изменяют состояние. Задания должны быть определены в соответствии со стандартным форматом спецификатора задания, описанным в главе 6. Если никакое задание не определено, используется самое позднее упомянутое задание. Для разрешения передачи немедленного уведомления обо всех заданиях установите переменную $notify. Завершение входного shell Команда logout заканчивает работу входного shell. В отличие от команды exit рассматриваемая команда не может использоваться, чтобы завершить работу диалогового дочернего shell. Поэтому можно закончить работу входного С shell одним из следующих трех способов: □ на отдельной строке нажать комбинацию клавиш <Ctrl>+<D> (пока не установлена переменная $ignoreeof); □ использовать встроенную команду exit; □ использовать встроенную команду logout. Вот пример: % set ignoreeof ...установить для предотвращения выхода по <Ctrl>+<D>. % ... теперь не хочет работать. Use "logout" to logout. % logout ...лучший способ выхода из системы. login: Когда входной С shell заканчивает работу, он ищет "завершающие" файлы. Если команды в каждом таком файле найдены, то они последовательно выполняются. Завершающий файл пользователя — это $HOME/.logout. После данного файла выполняется любой глобальный завершающий файл. Имя этого файла может быть /etc/logout или /.logout, в зависимости от вашей версии UNIX.
Обычно файл завершения содержит команды для очистки временных каталогов, выполнение других подобных действий по очистке и вывода прощального сообщения. Если заканчивает работу невходной С shell с помощью команды exit или комбинации клавиш <Ctrl>+<D>, завершающие файлы не выполняются. Встроенные команды С shell поддерживает следующие дополнительные встроенные команды: О chdir; П glob; П source. chdir Команда shell chdir описана ниже. Синтаксис chdir [путь] Команда chdir работает таким же образом, как и команда cd, изменяя текущий рабочий каталог пользователя на указанный в параметре путь. glob Синтаксис и описание команды shell glob представлены ниже. Синтаксис glob {аргументы} Команда glob работает таким же образом, как и команда echo, печатая список аргументов после обработки механизмом метасимволов shell. Различие состоит в том, что аргументы в списке отделены друг от друга нулями (ASCII 0) вместо пробелов в заключительном выводе. Это делает вывод идеально пригодным для использования С-программами, которые принимают строки, завершающиеся пустыми символами. source Когда скрипт выполняется, он интерпретируется subshell. Поэтому любые назначения псевдонимов или локальных переменных, выполненные скрип
том, не имеют никакого эффекта для родительского shell. Если вы хотите, чтобы скрипт интерпретировался текущим shell и, таким образом, оказал влияние на этот shell, следует использовать встроенную команду source. Синтаксис source [-h] имяФайла Команда source заставляет shell выполнять каждую команду в скрипте имяФайла без вызова subshell. Команды в скрипте помещаются в список истории, только если используется опция -h. Это в полной мере применимо для файла имяФайла, чтобы содержать дальнейшие source-команды. Если происходит ошибка во время выполнения файла имяФайла, управление возвращается к первоначальному shell. В представленном ниже примере применяется команда source для повторного выполнения отредактированного .login-файла: % vi .login ...редактировать мой файл .login. % source .login ...повторно выполнить его. Enter your terminal type (default is vtlOO): vt52 о о Иной способ повторно выполнить файл заключался бы в выходе из системы и затем входе в систему снова. Стек каталогов С shell позволяет создавать и управлять стеком каталогов, который облегчает жизнь, когда пользователь переключается между рабочими каталогами. Для помещения каталога в стек каталогов используйте команду pushd. Синтаксис pushd [+число | имя] Команда pushd проталкивает указанный каталог в стек каталогов и работает следующим образом: • когда имя указано, текущий рабочий каталог проталкивается в стек и shell перемещается в указанный каталог;
• когда нет аргументов, два верхних элемента стека каталогов переставляются; • когда задано число, указанный по номеру элемент стека каталогов циклически сдвигается к вершине стека и становится текущим рабочим каталогом. Элементы стека нумеруются в возрастающем порядке, номер вершины стека — 0. Чтобы вытолкнуть каталог из стека каталогов, следует вызвать команду popd. Синтаксис popd [+число] Команда popd выталкивает каталог из стека каталогов и работает так: • когда нет аргументов, shell перемещается в каталог, находящийся на вершине стека каталогов, и затем выталкивает его; • когда задано число, shell перемещается в пронумерованный каталог стека и сбрасывает стек. Команда dirs позволяет видеть содержимое стека каталогов. Синтаксис dirs Команда dirs выводит текущий стек каталогов. Вот некоторые примеры манипуляций со стеком каталогов: % pwd /home/glass % pushd / % pushd /usr/include /usr/include / ~ . . .я в моем домашнем каталоге. ...переход в корневой каталог, протолкнуть ...домашний каталог. ...автоматически вывести стек каталогов. ...протолкнуть другой каталог.
% pushd / /usr/include ~ % pushd /usr/include / ~ % popd % popd ...переставить два элемента стека, ...перейти обратно в корневой каталог. ...переставить их опять, ...перейти обратно в /usr/include. ...вытолкнуть каталог, ...перейти обратно в корневой каталог. ...вытолкнуть каталог, ...перейти обратно в домашний каталог. Хеш-таблица Как описано в главе 4, переменная path используется, когда ищется исполняемый файл. Чтобы ускорять этот процесс, С shell хранит внутреннюю структуру данных, называемую хеш-таблицей, которая позволяет быстрее осуществлять поиск в иерархии каталогов. Хеш-таблица автоматически строится всякий раз, когда читается файл .cshrc. Однако для того, чтобы хеш-таблица работала правильно, она должна восстанавливаться всякий раз, когда изменяется $ратн, или всякий раз, когда новый исполняемый файл добавляется к любому каталогу в последовательности $ратн. С shell автоматически обслуживает первый случай, но вы должны заботиться о втором. Если вы добавляете или переименовываете исполняемый файл в любом из справочников в последовательности $ратн, исключая ваш текущий каталог, то должны использовать команду rehash, чтобы заставить С shell восстановить хеш-таблицу. Если желаете, вы можете использовать команду unhash для запрета функционирования хеш-таблицы, таким образом замедляя процесс поиска. Команда hashstat может применяться для исследования эффективности системы хеширования. Однако вывод этой команды ничего для вас не означает, если вы не знакомы с алгоритмами хеширования. В приведенном ниже примере добавлен новый исполняемый файл в каталог ~/bin, который был в пользовательском пути поиска. Командный интерпретатор не мог найти файл до тех пор, пока пользователь не выполнил команду rehash. % pwd ... я в моем домашнем каталоге. /home/glass
% echo $РАТН . . .вывести мою переменную PATH. .:/home/glass/bin:/usr/bin:/usr/local/bin:/bin: % cat > bin/script.csh ...создать новый скрипт. # / echo found the script ''D ... конец ввода. % chmod +x bin/script. csh ...сделать исполняемым. % script.csh ...попытаться выполнить его. script.csh: Command not found. % rehash ...выполнить rehash. % script.csh ...попытаться выполнить его снова, found the script ...успех! % hashstat ...вывести хеш-статистику. 5 hits, 6 misses, 45% Опции командной строки Если первый аргумент командной строки — это символ С shell запускается как входной shell. В дополнение к этой особенности, С shell поддерживает опции командной строки, перечисленные в табл. 7.13. Таблица 7.13. Опции командной строки С shell Опция Описание -с строка Создает shell для выполнения командной строки -е Завершает shell, если любая команда возвращает ненулевой код возврата -f Запускает shell, но не ищет или читает команды из .cshrc -i Создает диалоговый shell, как опция -s, за исключением того, что игнорируются все сообщения SIGTERM, SIGINT И SIGQUIT —n Разбирает команды, но не выполняет их; применяется только для отладки -s Создает shell, который читает команды со стандартного ввода и посылает сообщения shell в стандартный канал ошибки -t Читает и выполняет отдельную строку со стандартного ввода -v Вызывает установку $ verbose
Таблица 7.13 (окончание) Опция Описание -V Подобно -v за исключением того, что перед установкой $ verbose выполняется .cshrc -х Вызывает установку переменой $echo -X Подобно -х за исключением того, что перед установкой $echo читается .cshrc имяФайла Выполняет команды shell в файле имяФайла, если не используются ОПЦИИ с, -i, -s, ИЛИ -t. имяФайла — ЭТО $0 В скрипте имяФайла Обзор главы Перечень тем В этой главе было описано: □ создание стартового файла С shell; □ простые и списочные переменные; □ выражения, включая целочисленную арифметику; □ псевдонимы и механизм истории; □ несколько структур управления; □ усовершенствованное управление заданием; □ несколько новых встроенных команд. Контрольные вопросы 1. Почему целочисленные выражения нужно предварять знаком @? 2. В чем состоит удобный способ исправления простой ошибки в предыдущей команде? 3. В чем заключается функция метасимволов {}? 4. Опишите разницу между встроенными командами set и setenv. 5. Как вы защищаете файлы от случайной перезаписи? 6. Как вы защищаете скрипты от прерываний от комбинации клавиш <Ctrl>+<C>?
Упражнения 7.1. Напишите С shell версию скрипта track, который был описан в главе 5. [Уровень: легкий.] 7.2. Напишите утилиту hunt, которая действует, как препроцессор для утилиты find, hunt берет имя файла, как свой единственный параметр, и показывает полное путевое имя каждого соответствия имени файла. Поиск производится вниз от текущего каталога. [Уровень: средний.] Проект Изучите текущие тенденции в объектно-ориентированном программировании и затем спроектируйте объектно-ориентированный shell (C++ shell?). [Уровень: высокий.]
Глава 8 Bourne Again shell Мотивация Bash, известный под именем Bourne Again Shell, изначально написанный Брайеном Фоксом (Brian Fox) из Free Software Foundation (Фонд открытых программных средств), является самым новым из командных интерпретаторов и быстро становится популярным UNIX shell. Bash возник в результате Стремления создать ’’наилучший изо всех shell”, который не только бы обеспечивал обратную совместимость с Bourne shell, но и включал наиболее полезные возможности как С shell, так и Korne shell. Другое преимущество состоит в том, что Bash как продукт Открытых программных систем (Open Software product) является свободно распространяемым, может быть найден на всех Linux-узлах сети, загружен и установлен почти в любой версии UNIX, если он там еще не существует. (Для получения дополнительной информации об открытом программном обеспечении см. главу 16.) Предпосылки Советуем вам прочитать главу 4 и поэкспериментировать с частью основных возможностей UNIX shell. Рекомендуется также изучить главу 5, потому что вся информация из нее также применима к Bash. Задачи В данной главе объясняются и демонстрируются возможности Bash, которые являются уникальными или отличают этот командный интерпретатор от ранее обсужденных shell. Все, изложенное в главе 5, работает в Bash, и многое из материала глав би 7также применимо к Bash.
Изложение Информация в главе представлена в форме нескольких примеров UNIX-сессий. Команды shell Описываются следующие команды shell: alias if...then...elif...then...else...fi set builtin jobs source case...in...esac kill unalias declare local unset ' dirs popd until...do...done export pushd while...do...done for...do...done readonly history select Общие сведения о Bourne Again shell Bourne Again shell (или Bash) — это shell, выбранный для системы Linux. (Для получения дополнительной информации о Linux см. главу 16.) Bash поддерживает все основные возможности, описанные в главе 4, и совместим с Bourne shell, рассмотренным в главе 5 (таким образом, скрипты Bourne shell выполняются в Bash). Bash соответствует стандарту команд POSIX1 для shell (IEEE 1003.2). Хотя Bash пытается соответствовать функциональным возможностям и синтаксису sh и csh, в случае конфликта будет преобладать sh. Следующие особенности Bash являются новыми или немного отличаются от тех, что обсуждались в предыдущих главах: П манипуляция переменной; □ обработка командной строки, псевдонимы и история; □ арифметические действия, условные выражения, структуры управления; □ стек каталогов; / □ управление заданием; □ функции shell. 1 Portable Operating System Interface for Computing Environment, переносимый интерфейс операционной системы для вычислительной среды. — Ред.
Получение Bash Bash доступен почти для каждой версии UNIX и даже для Windows-платформ. Узел сети GNU Bash имеет адрес http://www.gnu.org/ software/bash/bash.html. Исходный код Bash можно найти на сайте ftp://ftp.gnu.org/pub/gnu/bash. Bash для Windows может быть найден на2 http://www.cygwin.com. Справочное руководство по Bash также доступно в сети: http://www.gnu.org/manual/bash. Если ваша UNIX-система еще не имеет Bash, вы должны скачать надлежащий пакет для конкретной UNIX-платформы, скомпоновать его и установить в каталог /bin. Внимание Вы должны иметь права привилегированного пользователя (root), чтобы установить программу в каталог системы. Однако вы можете инсталлировать ее в свой собственный каталог и запускать вручную. (Тем не менее, если вы сделаете это, могут возникнуть проблемы, связанные с безопасностью, которые воспрепятствуют в создании программы вашего входного shell.) Запуск Подобно другим shell, Bash — это UNIX-программа. Когда новый Bash-интерпретатор стартует, он выполняет команды из файла .bashrc в домашнем каталоге пользователя, выполняющего shell. Существует одна особенность: когда Bash запускается, как входной shell, вместо файла .bashrc он выполняет команды из файла .bash_profile в домашнем каталоге пользователя. Поэтому если необходимо, чтобы .bashrc-файл также был выполнен в вашем входном shell, вы должны добавить следующий код в свой файл .bashprofile: if [ -f -/.bashrc ]; then . -/.bashrc fi Позже в данной главе мы увидим, как и почему работает этот код. По умолчанию код часто находится в системе в файлах .bash_profile. В дополнение к файлам .bashrc и .bash-profile системный администратор может поместить команды инициализации, подходящие всем пользователям, 2 Чтобы установить любой или все компоненты инструментальной системы Cygwin, щелкните на значке install, который загрузит и выполнит файл setup.exe. Установка проведет вас через процесс, где вы сможете выбрать Bash и другие части системы Cygwin, которые захотите установить.
в файле /etc/profile, который Bash также будет читать и выполнять. Обратите внимание, что Bash будет читать файл /etc/profile перед выполнением любых файлов инициализации, принадлежащих пользователю. Переменные Bash поддерживает создание и использование переменных shell для следующих целей: □ присвоение и доступ к значению; □ определение и использование списков значений; □ проверка значения или проверка на существование переменной; □ чтение или запись значения переменной. Создание и назначение простой переменной Синтаксис Bash для присвоения значения простой переменной подобен синтаксису других shell. Синтаксис {имя=значение}+ Вот некоторые примеры: $ teamnате= "Denver Broncos" $ gameswon=12 $ gameslost=3 Встроенная команда set может использоваться для задания и отображения установок shell и вывода значений переменных shell. Встроенная команда set имеет много аргументов и применений; мы обсудим их по мере необходимости. Ее самое простое использование — это демонстрация переменных shell и их значений. Синтаксис set Встроенная команда set показывает весь набор переменных в shell.
Пример: $ set gameslost=3 gameswon=12 teamname-" Denver Broncos" $ _ Другие действия, которые может осуществлять команда set, сейчас не рассматриваются. Позже мы увидим другие способы вызова set для выяснения поведение shell. Доступ к простой переменной Доступ к значению простых переменных такой же, как и в других shell (табл. 8.1). Таблица 8.1. Доступ к значению простой переменной Синтаксис Описание $имя Используется значение переменной имя $ {имя} Значение переменной имя указывается рядом с другими символами в тех случаях, когда имя переменной может быть неверно истолковано С переменными, установленными так, как показано в разд. "Создание и назначение простой переменной" ранее в этой главе, можно использовать следующую команду, чтобы резюмировать командные сезонные факты: $ echo "The $teamname went ${gameswon}-$(gameslost) last year”. The Denver Broncos went 12-3 last year. Создание списочных переменных Списочные переменные или массивы подобны аналогичным структурам данных в С shell. Bash, однако, предусматривает более явный метод для определения массива при помощи встроенной команды declare, хотя использование переменной в форме массива также будет работать. Синтаксис declare [-ах] [имя] Если переменная имя еще не существует, она создается. Если имя массива опущено, когда используется опция -а, команда declare отобразит все
определенные в настоящее время массивы и их значения. Если указывается опция -х, переменная экспортируется в дочерний shell. Команда пишет свой вывод в формате, пригодном для повторного использования в качестве команд ввода. Это полезно, когда пользователь хочет создать задающий переменные скрипт, т. к. они уже установлены в пользовательском текущем окружении. Вот пример создания списка команд: $ declare -a teamnames $ teamnames[0]="Dallas Cowboys" $ teamnames[1] = "Nashington Redskins" $ teamnames [2] = "New York Giants" На практике, если команду declare опущена, другие строки все же будут работать, как и ожидается. Доступ к списочным переменным После формирования списка значений логично использовать его для каких-либо манипуляций. При доступе к значениям массива допустимо добавлять фигурные скобки вокруг имени переменной, чтобы явно отличить ее от другого текста, который может располагаться вокруг нее (чтобы воспрепятствовать shell использовать иной текст, как часть имени переменной). Это соглашение показано в табл. 8.2. Если применяют массивы, то для распознавания их среди других операторов shell требуются фигурные скобки. Таблица 8.2. Доступ к значениям списочной переменной Синтаксис Описание $ {имя [индекс] } Доступ к индексированному (индекс) элементу массива $имя $ {имя [ * ] } или $ {имя [ @ ] } Доступ ко всем элементам массива $имя $ {#имя [ * ] } или $ {% имя [ 0 ] } Доступ к количеству элементов в массиве $имя Предположим, что существует список из 32 команд NFL (Национальной футбольной лиги), сохраненный как $teamname [0], ..., $ teamname [31]. Можно было бы использовать эту информацию следующим образом: $ echo "There are ${tfteamnames[*]} teams in the NFL" There are 32 teams in the NFL $ echo "They are: ${teamnames[*]}"
Построение списков Легко построить массив или список переменных одним из двух способов. Если известно необходимое количество элементов, то можно использовать встроенную команду declare, чтобы определить пространство и присвоить значения определенным элементам в списке. Если заранее неизвестно или нет необходимости заботиться о количестве элементов в списке, то можно просто перечислить их, и они будут добавлены в определенном вами порядке. Например, чтобы задать наш список команд NFL, количество которых (по крайней мере, сегодня) равно 32, можно определить его: $ declare -a teamnames $ teamnames[0]^"Dallas Cowboys" $ teamnames[1]^"Washington Redskins" $ teamnames[2]="New York Giants" $ teamnames[31] = "Houston Texans" Или в следующей единственной (хотя и длинной) команде: $ declare -a teamnames $ teamnames=([0] = "Dalias Cowboys" \ [l]="Washington Redskins" \ [31] = "Houston Texans") Обратная косая черта (\) применяется для сообщения shell, что команда продолжается на следующей строке. Даже притом, что мы знаем количество команд заранее, в действительности в этом нет необходимости для определения массива. Мы могли бы сделать так: $ teamnames — ("Dallas Cowboys" "Washington Redskins" \ "New York Giants" "New York Jets" \ ... "Houston Texans") Обратите внимание, что, если массив заполнен не подряд (т. е. если некоторым элементам не присвоены значения, а просто пропущены), то при запросе количества значений в массиве оно будет фактическим числом внесенных элементов, а не самым большим определенным индексом. Пример: $ туlist[0]=27 $ mylist[5]=30 $ echo ${#mylist [★]} ...количество элементов в mylist[]
2 $ declare -а declare -a mylist='{[0]="27" [5]=”30”)1 $ _ Удаление списков Списочные переменные могут быть освобождены или удалены с помощью встроенной команды unset. После окончания работы с массивом можно освободить используемое им пространство, уничтожив его полностью. Однако более вероятно, что вы захотите удалить конкретный элемент из массива. Синтаксис unset имя unset имя[индекс] Команда unset освобождает конкретную переменную или элемент списочной переменной (массива). Например: $ unset teainn ames [17] Теперь массив содержит 31 элемент вместо 32. Экспорт переменных Во всех shell переменные являются локальными к определенному командному интерпретатору и, если иначе не определено, не передаются дочернему shell. Вы должны экспортировать переменную shell в соответствующий Bourne shell или Korn shell. Встроенная команда export поддерживается в Bash, так же как и в Bourne shell, а опция -х во встроенной команде declare, как мы видели ранее, экспортирует переменную в subshell. В С shell переменная должна быть создана, как переменная окружения, чтобы быть доступной в subshell. Bash поддерживает опцию shell, которая позволяет определить выполнение по умолчанию экспорта переменных shell в любые созданные дочерние shell. Опции shell задаются во встроенной командой set.
Синтаксис set -o allexport Команда set сообщает shell об экспорте всех переменных subshell. Предопределенные переменные Подобно большинству shell, Bash определяет некоторые переменные во время запуска. В дополнение к обычным предопределенным переменным Bash поддерживает переменные, перечисленные в табл. 8.3. Таблица 8.3. Предопределенные переменные Bash Имя Описание BASH Полное путевое имя исполняемого файла Bash BASH_ENV Расположение стартового файла Bash (по умолчанию это ~/. bashrc) BASH_VERSION Строка версии BASH_VERSINFO Массив "только для чтения" информации о версии DIRSTACK Массив, определяющий стек каталогов EUID Значение "только для чтения" эффективного ID пользователя, выполняющего Bash (только для UNIX) HISTFILE Расположение файла, содержащего историю shell (по умолчанию ~/. bash_history) HISTSIZE Максимальное количество команд в истории (по умолчанию 500) HISTFILESIZE Максимальное количество строк, разрешенное в истории (по умолчанию 500) HOSTNAME Имя главной машины, на которой работает Bash HOSTTYPE Тип главной машины, на которой выполняется Bash MAILCHECK Период (секунды) проверки новой почты OSTYPE Операционная система машины, на которой выполняется Bash PPID Только для чтения ID родительского процесса Bash SHLVL Уровень shell (увеличивается на единицу каждый раз, когда процесс Bash стартует; показывает, как глубоко shell вложен) DID Только для чтения. ID пользователя, выполняющего Bash (только для UNIX)
Клавиатурные комбинации быстрого вызова команд Bash предусматривает несколько способов сокращения команд и аргументов, которые пользователь вводит с клавиатуры. Псевдонимы Как С shell и Korn shell, Bash позволяет определять собственные пользовательские команды при помощи встроенной команды alias. Псевдонимы Bash работают подобно псевдонимам Korn shell, как показано в следующем примере: $ alias dir="ls -aF" $ dir ./ main2.c ../ main2.o $ dir *.c main2.c p.reverse.c p.reverse.c palindrome.c palindrome.c reverse.h reverse.old $ Синтаксис alias [-p] [cuzobo[=строка] ] Если пользователь ставит в соответствие новое слово команды строке, то при вводе слова команды с клавиатуры строка будет подставлена на его место (и любые последующие аргументы будут добавлены к строке. Если указать alias слово, то будет напечатан любой псевдоним, определенный для слова. Самое простое использование команды alias (без аргументов) будет печатать все определенные псевдонимы. Если указана опция -р, псевдонимы печатаются в формате, подходящем для ввода shell. (Так, если вы вручную установили свои любимые псевдонимы, то можете записать их в файл, чтобы включить в свой файл .bashrc.) Для отказа от специального определения псевдонима служит встроенная команда unalias. Можно отменить определение псевдонима, если вы хотите возвратиться к нормальному поведению команды, когда ваш обычный псевдоним приводит к иному поведению. (Скажем, может случиться, что вы
больше не захотите, чтобы псевдоним dir использовал утилиту is в предыдущем примере, потому чт. е. другая команда dir в системе.) Синтаксис unalias [-а] {слово}+ Команда unalias удаляет конкретный псевдоним. Если используется опция -а, удаляются все псевдонимы. История команд Подобно С shell и Korn shell, Bash поддерживает историческую запись команд, которые вы вводите с клавиатуры. Команды, поддерживаемые в истории, можно выборочно повторно выполнять или изменить, а затем выполнить их с внесенными изменениями. Хранение команд Вводимые с клавиатуры команды сохраняются в файле истории, определенном переменной shell $histfile. По умолчанию значение задает файл .bash history в домашнем каталоге пользователя. Этот файл может содержать максимум $histfilesize элементов; значение по умолчанию равно 500. Чтение истории команд Чтобы увидеть историю shell, используйте встроенную команду history. Синтаксис history [-с] [п] Встроенная команда history распечатывает текущую историю команд shell. Если числовое значение п определено, показывает только последние п элементов списка истории. Если используется опция -с, очищает список истории. Повторное выполнение команд Bash принимает метасимвол !, чтобы повторно выполнять команды из списка истории таким же образом, как это делает С shell. Метасимволы повторного выполнения Bash показаны в табл. 8.4.
Таблица 8.4. Метасимволы повторного выполнения команды в Bash Формат Описание । । Заменяется текстом самой последней команды ’ номер Заменяется командой с номером из списка истории ! -номер Заменяется текстом команды с номером команды, определяемым от конца списка.(! -1 эквивалентно ! ’) !префикс Заменяется текстом самой последней команды, которая начиналась с префикса !?подстрока? Заменяется текстом самой последней команды, которая содержит подстроку Замена истории Иногда возникает желание сделать больше, чем просто повторно выполнить уже отработанную команду. Например, вы можете немного изменить команду (чтобы заменить имя файла или отдельный аргумент длинной команды). Самая простая форма замены истории такая же, как в С shell. Синтаксис строка 1А строка2* Заменяет строку1 на строку2 в предыдущей команде и выполняет ее. Эта форма полезна, когда пользователь делает незначительную ошибку в команде и не хочет повторно печатать команду целиком. Пример: $ 1р financial_report_july_2001.txt Ip: File not found. $ ^2001^2002^ Ip f inancial_report_july_2002.txt request id is lwcs-37 (1 file) $ _ Или, возможно, пользователь хочет заменить кое-что в более ранней команде. Вот пример: $ 1р financial_report_july_2001.txt Ip: file not found. $ Is financial- report_july_2002. txt financial_report_may_2002. txt
financial_report_june_2002. txt $ ! Ip:s/2002/2001/ request id is lwcs-37 (1 file) $ Синтаксис !команда:s/строка 1/строка2/ Строка2 заменяется строкой1 в недавно выполненной команде, начинающейся с текста, специфицированного командой. Редактирование команды Bash обеспечивает возможность редактирования команды, во многом подобную таковой в Korn shell. Поддерживаются emacs- и vi-стили редактирования; emacs — по умолчанию. Так как в emacs пользователь всегда находится в режиме ввода текста, то можно перемещать курсор ввода в любое время, когда происходит ввод команды. Так, например, если вы пропустили слово, то можете вернуться назад при помощи комбинации клавиш <Ctrl>+<B> и вставить его. Чтобы получить доступ к своему списку истории предыдущих команд, можете нажать комбинацию клавиш <Ctrl>+<P> для перемещения "вверх”, как будто бы ваш список истории — это файл. Большинство других команд перемещения в редакторе emacs поддерживается в Bash. (См. разд. "Редактирование файла: emacs" в главе 2.) Bash также позволяет vi-пользователям применять подобные действия, но этот редактор должен быть установлен с помощью встроенной команды set, описанной ниже. Поскольку редактор vi имеет два режима (командный и режим ввода текста), в то время как вы печатаете обычную команду, Bash обращается с вами, будто вы находитесь в режиме входа текста. Поэтому, чтобы получить доступ к vi-возможностям редактирования команды, вы должны нажать клавишу <Esc> так, как если бы вы работали с этим редактором, чтобы вернуться в командный режим. Затем можно передвигаться тем же самым способом, как если бы вы были в vi (используя клавишу <h> для возврата к команде и клавишу <к> для перемещения к предыдущим командам в списке истории). (См. разд. "Редактирование файла: vi" в главе 2.) Синтаксис set -о vi Команда set предписывает shell использовать vi-стиль редактирования команды. Если вы когда-либо захотите вернуться к стилю по умолчанию редактора emacs, замените vi на emacs в команде.
Автозаполнение Bash может закончить имя файла, название команды, имя пользователя или имя переменной shell, которое вводится с клавиатуры, если уже введено достаточно текста для уникальной идентификации.. Чтобы попытаться закончить текущий аргумент команды, нажмите клавишу <ТаЬ>. Если соответствующие имена файлов доступны, но введенный текст не идентифицирует полностью ни одного из них, общий текст, который имеют возможные имена, будет дописан до символа, начиная с которого, они больше не имеют совпадений. Это предоставляет пользователю возможность заполнять длинные имена файлов, у которых лишь несколько символов в конце различны (подобно числовой последовательности или дате). Тогда вы можете печатать только уникальную часть имени файла, к которому желаете получить доступ. Арифметические действия Арифметические действия более просты в Bash, чем в Bourne shell. Все, что можно выполнить с помощью утилиты ехрг, доступно через встроенные команды в Bash. Это не только требует меньше набора текста, но и быстрее выполняет скрипты shell в Bash. Чтобы выполнить арифметическое действие в B^sh, следует поместить оператор внутри двойных круглых скобок. Синтаксис ( ( оператор )) Обычные числовые действия включают действия, перечисленные в табл. 8.5. Таблица 8.5. Арифметические операторы Оператор Описание +> - Сложение, вычитание ++, — Инкремент, декремент *, /, % Умножение, деление, остаток ★ * Возведение в степень Арифметические действия над целыми числами выполняются быстрее, чем над числами с плавающей запятой. Если известно, что переменная будет всегда целочисленной (например, счетчик или индекс массива), то можно использовать встроенную команду declare для объявления ее целой.
Синтаксис declare -i имя Этот формат команды declare определяет переменную с именем, как целое. После того как будут рассмотрены некоторые простые условные выражения, мы объединим их в простой математический скрипт. Условные выражения Вы можете сравнивать значения (обычно хранящиеся в переменных shell) друг с другом и переходить на различные команды в зависимости от результата сравнения. (В разд. "Структуры управления" далее в этой главе будет показано, как управлять и что делать после сравнения.) Подобно арифметическим действиям, арифметические выражения заключаются в двойные круглые скобки. Типы сравнений, которые можно осуществлять, показаны в табл. 8.6. Таблица 8.6. Арифметические условные операторы Оператор Описание Л JI V JI A V Сравнения: меньше, чем или равно; больше, чем или равно; меньше, чем; больше, чем — — 1 — > • I Равно, не равно Логическое НЕ & & Логическое И 1 1 Логическое ИЛИ Арифметические выражения Сравнения с использованием арифметических выражений позволяют просто выполнять математические вычисления в скрипте Bash. Мы досчитаем до 20 и проверим, какие числа делят 20 нацело (не волнуйтесь слишком много о конструкции while; мы рассмотрим ее позже): $ cat divisors.bash #!/bin/bash
declare -i testval=20 declare -i count=2 # начать c 2, 1 всегда работает while (( $count <= $testval )); do (( result = $testval % $count )) if (( $result == 0 )); then # делится нацело echo " $testval is evenly divisible by $count" . fi ( ( count++ ) ) done $ bash divisors .bash 20 is evenly divisible by 2 20 is evenly divisible by 4 20 is evenly divisible by 5 20 is evenly divisible by 10 20 is evenly divisible by 20 $ _ Сравнение строк Строковые условные операторы Bash показаны в табл. 8.7. Таблица 8.7. Строковые условные операторы Оператор Описание -п строка Истина, если длина строки не ноль -z строка Истина, если длина строки ноль строка 1 == строка2 Истина, если строки равны строка! ’= строка2 Истина, если строки не равны Выражения, ориентированные на файл В табл. 8.8 перечислены ориентируемые на файл условные операторы Bash. Таблица 8.8. Ориентированные на файл условные операторы Оператор Возвращает -а файл истину, если файл существует —b файл истину, если файл существует и является специальным блочным файлом
Таблица 8.8 (окончание) Оператор Возвращает -с файл истину, если файл существует и является специальным символьным файлом -d файл истину, если файл существует и является каталогом -е файл истину, если файл существует -f файл истину, если файл существует и является обычным файлом -g файл истину, если файл существует и его бит установки группового ID установлен -k файл истину, если файл существует и его атрибут sticky bit установлен "P файл истину, если файл существует и является поименованным конвейером — r файл истину, если файл существует и читаемый -s файл истину, если файл существует и имеет размер больше нуля -t дескрипторФайла истину, если дескрипторФайла открыт и ссылается на терминал -u файл истину, если файл существует и его бит SUID3 установлен -w файл истину, если файл записываемый -x файл истину, если файл существует и исполняемый -0 файл истину, если файл существует и принадлежит эффективному ID пользователя -G файл истину, если файл существует и принадлежит эффективному групповому ID пользователя -L файл истину, если файл существует и является символической связью -N файл 4 истину, если файл существует и модифицировался со времени последнего чтения -s файл истину, если файл существует и является сокетом файл1 -nt файл 2 истину, если файл1 более новый, чем файл2 файл1-ot файл 2 истину, если файл1 более старый, чем файл2 файл! -ef файл2 истину, если файл1 и файл2 имеют то же самое устройство и номера индексных дескрипторов (inode) 3 Set User ID, бит установки пользовательского ID. — Ped.
Простым примером применения операций над файлами может быть следующий код: $ cat owner.bash #!/bin/bash # if [ -О /etc/passwd ]; then echo "you are the owner of /etc/passwd." else echo "you are NOT the owner of /etc/passwd." fi $ bash owner.bash you are NOT the owner of /etc/passwd. $_ Структуры управления Вы можете применять различные управляющие структуры, чтобы указывать shell, какая команда должна выполниться следующей. Эти структуры управления подобны структурам, поддерживаемым Bourne shell, Korn shell и С shell. Хотя они могут использоваться в диалоговом режиме Bash, но чаще всего используются при написании скриптов Bash. case... in... esac Оператор case позволяет специфицировать многократные действия, которые будут приняты, когда значение переменной соответствует одному или большему числу значений. Фрагмент скрипта для Bash shell, который использует оператор case, чтобы распечатать местожительство команд NFL, перечисленных в нашем более раннем примере, мог бы напоминать следующее: case ${teamname[$index]} in "Dallas Cowboys") echo "Dallas, TX";; "Denver Broncos") echo "Denver, CO";; "New York Giants"|"New York Jets") echo "New York, NY";; *) echo "Unknown location";; esac
Синтаксис case слово in шаблон {| шаблон }*) команды ;; esac Оператор case выполняет команды, когда значение слова соответствует шаблону. Символ ) означает конец списка шаблонов для совпадения. Символ ;; требуется, чтобы указать конец команд, которые будут выполняться. Обратите внимание на специальное использование шаблона * в качестве последнего. Если скрипт проходит через все шаблоны и не находит совпадения, шаблон * отловит эту ситуацию. Если нет соответствия никаким шаблонам, то ни одна из команд не будет выполнена. if... then... elif... then... else... fi Команда if покажется знакомой пользователям Korn shell и Bourne shell. Данная конструкция позволяет сравнить два или большее количество значений и перейти к блоку команд в зависимости от результата сравнения. Синтаксис if условие!; then команды!; [elif условие2; then команды2; ] [else командыЗ; ] fi Если условное выражение условие! ИСТИННО, ТО ВЫПОЛНЯЮТСЯ команды!. Если проверки условия! ложные, то, если структура elif присутствует, выполняются следующее проверки в условии2 (else if). Если условие2 истинно, то выполняются команды2. Конструкция else используется, когда нужно выполнять команды после того, как условие в результате дало ложь. В качестве примера рассмотрим случай для пары наших команд NFL, когда мы печатаем информацию о них. # $index был установлен на некоторую произвольную команду в списке #
if [ "${teamname[$index]}" == "Minnesota Vikings" ]; then cat "vikings.txt" # печатать "специальную" информацию elif [ "${teamname[$index]}" == "Chicago Bears" ]; then cat "bears.txt" # повторить else cat "nfl.txt" # для кого-нибудь еще, печатать стандартный файл fi for... do... done Конструкцию for лучше использовать, если существует известный набор значений, по которым нужно выполнить цикл (например, список машин, имен файлов или чего-то похожего). Синтаксис for имя in слово {слово}★ ; do команды ; done Команда for выполняет команды для каждого слова в списке с параметром $имя, содержащим значение текущего слова. Можно использовать сравнение, чтобы выйти из такого цикла, или просто последовательно обработать каждый пункт списка. Самый простой пример — печать всех файлов текста в текущем каталоге: $ for file in *.txt do Ip $file done request id is lwcs-37 (1 file) request id is lwcs-37 (1 file) request id is lwcs-37 (1 file) $ while/until ...do... done Конструкции while и until работают похожим образом, выполняя цикл, "пока" или "пока не" проверочное условие встречено (while в случае, если условие первоначально истинно, и нужно организовать цикл, пока оно не станет ложным; until в случае, если условие первоначально ложно, и необходимо создать цикл, пока оно не станет истинным).
Синтаксис while условие; do команды; done until условие; do команды; done В конструкции while исполняются команды, пока выражение проверки условие вычисляет ИСТИНУ. В конструкции until ВЫПОЛНЯЮТСЯ команды, пока выражение проверки условие вычисляет ложь. Обе конструкции полезны, если заранее неизвестно, когда изменится условие проверки. Стек каталогов Bash поддерживает стек каталогов подобно предлагаемому в С shell, с несколькими усовершенствованиями. Одно из них заключается в том, что полный стек сохраняется в массиве строк $ dirstack, обеспечивая простой доступ к любому элементу стека из скрипта Bash. Для того чтобы протолкнуть текущий каталог в стеке каталогов и заменить его на новый каталог, применяется встроенная команда pushd. Когда приходит время возвратиться к предыдущему каталогу, используется встроенная команда popd для восстановления предыдущего местоположения каталога и переключения на него. В дополнение к доступу к содержимому стека при помощи переменной shell $dirstack встроенная команда dirs позволяет выводить (или очищать) содержимое стека каталогов. Синтаксис pushd [-п] [каталог] t Команда pushd сохраняет текущий каталог как самое последнее дополнение (т. е. в вершине) к стеку каталогов. Последующая команда popd восстановит этот каталог. К тому же pushd заменяет каталог на специфицированный. Если никакой новый каталог не определен, текущий каталог и каталог на вершине стека переставляются (т. е. вы проталкиваете текущую вершину стека и меняете каталоги, а затем проталкиваете текущий каталог в стек). Если присутствует опция -п, переключение на новый каталог не происходит, а просто текущий каталог проталкивается в стек.
Синтаксис popd [—п] Команда popd восстанавливает последний каталог, который был помещен в стек, и переходит в этот каталог. Запись удаляется из стека. Если присутствует опция —п, переход в новый каталог не осуществляется, он просто удаляется из вершины стека. Синтаксис dirs [-ср] Если аргументов нет, команда dirs просто распечатывает содержимое стека каталогов. Опция -р выводит каталоги по одному на строке. Опция -с очищает стек каталогов. Управление заданием Bash добавляет несколько возможностей к управлению заданием, кроме описанных в главе 6. Если вы пропустили изучение Korn shell, предлагаем вернуться и прочитать разд. "Расширенное управление заданиями ". Управление заданием позволяет пользователю приостанавливать и возобновлять выполнение процесса, начатого при помощи командной строки Bash. Как и в Korn shell или С shell, нажатие комбинации клавиш <Ctrl>+<Z> во время выполнения процесса приостановит его. Затем вы можете использовать команды shell fg или bg, чтобы возобновить процесс соответственно как фоновый или приоритетный. Спецификация задания та же самая, как и в Korn shell, с двумя дополнительными опциями, показанными в табл. 8.9. Спецификатор должен уникально идентифицировать работу. Если больше, чем одно задание соответствует спецификатору, Bash сообщает об ошибке. Иначе команды fg, bg и wait работают так же, как в Korn shell. Встроенные команды shell jobs и kill в Bash работают немного по-другому. Таблица 8.9. Дополнительные спецификаторы задания в Bash Формат Описание % имя Ссылается на Процесс, чье имя начинается с имя % ?имя Ссылается на процесс, в котором имя возникает где-нибудь в командной строке
Синтаксис jobs [-Irs] Команда jobs отображает список всех заданий shell. Когда jobs используется с опцией -1, ID процессов включаются в листинг. Если указана опция -г, выводятся только задания, работающие в настоящее время. Опция -s позволяет напечатать только остановленные в настоящее время задания. Синтаксис kill [-s signame ] [-n signum] специфика цияЗадания или 1с1_процесса Команда kill посылает специфицированный сигнал конкретному процессу. Требуется указать или спецификациюзадания (например, %1), или ID процесса. Если используется опция -s, signame — это допустимое имя сигнала (например, sigint). Если приводится опция -n, signum — это номер сигнала. Если ни одна из опций не указана, процессу посылается сигнал SIGTERM. Функции Функции Bash синтаксически похожи на функции в Korn shell. Если вы пропустили изучение Korn shell, возвратитесь и прочитайте разд. "Функции" главы 6. Bash имеет пару ценных дополнений к существующим в Korn shell функциям, которые мы и рассмотрим в данной главе. Функции в Bash могут экспортироваться в subshell с помощью встроенной команды export. Командный интерпретатор также поддерживает встроенную команду local, которая ограничивает переменную, делая ее локальной только в текущей функции (значение переменной нельзя передать в дочерний shell). Синтаксис export -f имяФункции Встроенная команда export, используемая с опцией -f, экспортирует функцию в subshell подобно экспорту значения переменной.
Синтаксис local имя[=значение] Встроенная команда local определяет переменную так, чтобы она была локальной только в текущей функции, имя переменной может быть выведено, или ей может быть присвоено значение в той же самой конструкции. Существует еще одна полезная команда для написания функций — builtin. Синтаксис builtin [команда [ аргументы] ] Встроенная команда shell builtin выполняет указанную встроенную команду shell и передает ее аргументы, если они представлены. Команда полезна, когда пользователь пишет функцию для shell, которая имеет аналогичное имя, что и существующая встроенная команда, но в функции нужно выполнять встроенную команду, а не вызывать рекурсивно функцию. Разные встроенные команды Bash включает много других встроенных команд, некоторые из них он заимствует из других shell для совместимости. Синтаксис readonly Команда readonly применяется, чтобы не допустить изменения значения переменной shell. В Bash подобное действие может быть выполнено при помощи команды declare -г. Использование readonly то же самое, что и в Bourne shell и Korn shell (см. главу 5). Синтаксис select Команда select служит для создания приглашения меню. Данная встроенная команда аналогична одноименной команде в Korn shell (см. главу 6).
Синтаксис source file . file Команды source и могут применяться для выполнения скрипта shell в текущем командном интерпретаторе. Это полезно для повторного выполнения файлов .profile или .bashrc после их модификации. Встроенная команда source аналогична одноименной команде С shell. Использование то же самое, что и в Bourne shell и Korn shell. Встроенная команда set имеет несколько опций, которые мы прежде не обсуждали. В табл. 8.10 перечислены наиболее полезные опции. Таблица 8.10. Некоторые опции встроенной команды set Опция Описание -о allexport | -а Экспортирует все созданные или модифицированные переменные и функции -о emacs Устанавливает стиль редактирования команд для того, чтобы поведение было подобно редактору emacs -о ignoreeof Диалоговый shell не будет выходить на EOF — конец файла (например, если вы случайно нажали комбинацию клавиш <Ctrl>+<D>) -о noclobber | -С Предохраняет перенаправление вывода от переписывания существующих файлов -о noglob | -f Запрещает замещение имени файла (известна под именем globbing) -о posix Предписывает Bash четко придерживаться стандарта POSIX 1003.2 -о verbose | -v Отображает строки ввода shell по мере их чтения (полезна для отладки скриптов) -о vi Устанавливает стиль редактирования команд для того, чтобы поведение было подобно редактору vi Опции командной строки Bash поддерживает много опций командной строки. Некоторые из наиболее полезных показаны в табл. 8.11.
Таблица 8.11. Некоторые опции командной строки Bash Опция Описание -с строка Выполняет строку, как команду shell —s Читает команды со стандартного ввода —login Делает Bash входным shell. Полезна, если нельзя заставить Bash быть входным shell из команды chsh --noprofile Игнорирует Bash-файлы профиля (в масштабе всей системы и пользовательских версий) --norc Игнорирует rc-файлы Bash (~/.bashrc) — posix Выполняется в соответствии со стандартом POSIX (тр же самое, что и set -о posix) --verbose | -v Отображает строки ввода shell, когда они читаются (то же самое, как set -о verbose) Обзор главы Перечень тем В этой главе мы рассмотрели: □ стартовые файлы Bash; П использование переменных shell; □ применение псевдонимов, механизма истории и редактирование командной строки; □ арифметические действия в Bash, условные операторы и структуры управления; □ использование стека каталогов; □ реализацию в Bash управления заданием; □ функции Bash. Контрольные вопросы 1. Почему обеспечение собственных арифметических действий в Bash существенно улучшает время выполнения скриптов shell? 2. Почему требуются фигурные скобки ({}) вокруг списочных переменных? 3. Все ли скрипты Bourne shell работают в Bash?
4. Все ли Bash-скрипты работают в Korn shell? 5. Какая переменная shell содержит стек каталогов? Упражнение Скрипт track, представленный в главе 5, выполняется и в Bash. Однако он использует утилиту UNIX ехрг, чтобы выполнить свои арифметические вычисления. Измените track так, чтобы он использовал арифметику Bash, и сравните его скорость выполнения со скоростью в первоначальной версии. Можете использовать утилиту UNIX time (описанную в главе 5), чтобы измерить различия, если совсем не "почувствуете" значительных различий. [Уровень: легкий.] Проект Напишите скрипт Bash mv (заменяющий утилиту UNIX mv), который пробует переименовать указанный файл (используя утилиту UNIX mv). Но если файл назначения уже существует, вместо него создается файл с индексом — своего рода номер версии. Так, если ввести $ mv а.txt b.txt но файл b.txt уже существует, mv переместит файл в b.txt.I. Обратите внимание, если b.txt. 1 уже существует, вы должны переименовать файл в b.txt.2 и т. д., пока не возникнет возможность успешно переименовать файл и получить имя, которого не существует. [Уровень: средний.]

Глава 9 Организация сети Мотивация Одно из наиболее существенных преимуществ UNIX перед другими конкурирующими операционными системами во время ее возникновения заключалось в том, что она была одной из первых ОС, которые обеспечивали доступ к широко распространенным локальным сетям, а также к Интернету. Сегодня миллионы пользователей и программ разделяют информацию в этих сетях по разным причинам, от распределения больших вычислительных задач до обмена рецептами для приготовления лазаньи. Для того чтобы наилучшим образом использовать ресурсы сети, вы должны понимать работу утилит, управляющих обменом информацией. Данная глава описывает наиболее полезные сетевые утилиты. Несмотря на то, что они применимы и к локальным сетям, и к Интернету, акцент будет сделан на утилитах, связанных с Интернетом. Предпосылки Для понимания материала данной главы необходимо изучить главы 7 и 2 Не будет лишним иметь доступ к системе UNIX, чтобы вы могли испытать различные обсуждаемые утилиты. Задачи В настоящей главе я покажу вам, как определить, что находится на сети, общаться с другими пользователями, копировать файлы при помощи сети и выполнять процессы на других компьютерах в сети. Изложение Глава начинается с краткого обзора концепций сети и терминологии, а затем описываются сетевые утилиты UNIX.
Утилиты Будут рассмотрены следующие утилиты: finger rsh w ftp rusers wall hostname rwho who mesg talk whois rep telnet write rlogin users Общие сведения о сетях Сеть — связанная система взаимодействующих компьютеров. С помощью сети вы можете разделять ресурсы с другими пользователями через постоянно увеличивающееся количество сетевых приложений, типа Web-браузеров и систем сообщений электронной почты. В 1990-х годах был огромный взрыв использования сети UNIX. Например, парадигма, "клиент-сервер" описанная в главе 7, была принята многими из ведущих компьютерных корпораций и в большой степени основывается на способности сетевых операционных систем распределять рабочую нагрузку между сервером и его клиентами. Чтобы иметь представления о сети, важно знать: □ общую сетевую технологию; □ как строятся сети; □ как общаться с людьми через сеть; □ как использовать другие компьютеры сети. Эта глава охватывает перечисленные темы и даже больше. Построение сети Один из лучших способов понять, как выглядит работа современной сети, — это выяснить эволюцию ее развития. Вообразите, что два человека в офисе хотят соединить свои компьютеры вместе так, чтобы они могли разделять данные. Самый легкий способ — соединить их через последовательные порты. Получится самая простая форма локальной сети (local area network, LAN), и она не требует фактически никаких специальных программ или аппаратуры. Когда один компьютер хочет передать информацию другому, он
просто посылает данные со своего последовательного порта.. Эта организация показана на рис. 9.1. Последовательное соединение Рис. 9.1. Простейшая LAN Сети Ethernet Теперь давайте предположим, что другой человек хочет подключиться к существующей сети этих двух парней. При трех компьютерах в сети мы нуждаемся в схеме адресации для того, чтобы можно было различить компьютеры. Мы также хотели бы иметь минимальное число связей. Самое обычное выполнение этого вида LAN называется Ethernet. Ethernet — это стандарт аппаратного подключения, определяющий кабельную сеть, передачу сигналов и поведение, которые позволяют данным распространяться по проводам. Формат данных назначается в соответствии с протоколами сети, которые мы рассмотрим немного позже. Ethernet-стандарт был первоначально разработан корпорацией Xerox и работает следующим образом. П Каждый компьютер содержит Ethernet-карту, которая является специальной частью аппаратуры ЭВМ и имеет уникальный адрес Ethernet. □ Ethernet-карта каждого компьютера соединена с ним единственным проводом. П Когда компьютер хочет послать сообщение другому компьютеру с конкретным адресом Ethernet, он передает в широковещательном режиме сообщение в Ethernet вместе с Ethernet-заголовком и последующей информацией, которая содержит Ethernet-адрес назначения. Принимает сообщение только Ethernet-карта, чей адрес соответствует адресу назначения. □ Если два компьютера одновременно пробуют передавать данные в Ethernet в широковещательном режиме, то в результате возникает ситуация, называемая коллизией (collision). При коллизии оба компьютера
ожидают случайный промежуток времени и затем пробуют снова передать данные. Рис. 9.2 демонстрирует диаграмму Ethernet. Ethernet-сети могут передавать данные со скоростью порядка десятков или сотен мегабайт в секунду. Ethernet-кабель Ethernet-карта Рис. 9.2. Ethernet Мосты Давайте предположим, что Ethernet в офисе работает настолько хорошо, что сотрудники соседнего офиса также строят себе Ethernet. Как один компьютер в одном Ethernet взаимодействует с компьютером в другом Ethernet? Одним из решений могло бы быть соединение сетей при помощи специальной аппаратуры, называемой мостом (bridge). Мост передает Ethernet-сообщение между различными сегментами сети, как будто бы оба сегмента — это единственный Ethernet-кабель сети (рис. 9.3). Мост используется, когда требуется расширить сеть за пределы разрешенного размера единственной секции провода. (Длина ограничивается затуханием сигнала при распространении.) Рис. 9.3. Мост
Маршрутизаторы Использование мостов облегчает построение маленьких последовательно связанных секций Ethernet. Но это довольно неэффективный способ связать вместе большое количество сетей. Например, предположим, что корпорация имеет четыре LAN, которые желательно связать эффективным способом. Соединение их вместе при помощи мостов заставило бы данные проходить через "средние" секции, чтобы достигнуть концов, в то время как владельцы этих средних секций не заинтересованы в получении передаваемых данных. Чтобы передать данные непосредственно от исходящей сети до сети назначения, может использоваться маршрутизатор (router). Маршрутизатор.— это устройство, которое соединяет вместе две или более сетей и автоматически направляет входные сообщения в соответствующую сеть (рис. 9.4). Ethernet-кабель Ethernet-кабель Шлюзы Заключительная стадия в развитии сети происходит, когда много корпораций хотят соединить свои локальные сети в единую, глобальную сеть (wide area network, WAN). Чтобы сделать это, несколько высокопроизводительных маршрутизаторовь называемых шлюзами (gateway), помещаются по всей стране, и каждая корпорация связывает свою LAN с самым близким шлюзом (рис. 9.5).
Рис. 9.5. Шлюзы Объединение сетей Для того чтобы бы LAN и WAN надлежащим образом были способны к передаче информации между собой, они должны согласовать в масштабе сети схему адресации и схему маршрутов. Эта крупномасштабная взаимосвязь различных сетей известна как объединение сетей. Любая группа двух или нескольких сетей, связанных вместе, может по существу называться интерсе-тъю. Однако самая большая и хорошо известная интерсеть стала известна как Интернет. Университеты, большие корпорации, правительственные учреждения и военные организации — все имеют компьютеры, которые являются частью Интернета. Компьютеры вообще связываются вместе быстродействующими каналами передачи данных. Самые большие из ,этих компьютерных систем соединяются вместе для того, чтобы сформировать так называемую магистраль сети (backbone) Интернета. Другие, менее крупные организации связывают свои LAN с магистралью через шлюзы. » Коммутация пакетов Сегодняшние цифровые компьютерные сети — это сети пакетной коммутации. Когда один узел сети посылает сообщение другому узлу, сообщение разбивается на маленькие пакеты, каждый из которых может быть передан независимо (т. е. направлен) через сеть.
Пакеты содержат специальную информацию, которая позволяет им объединиться в пункте назначения. Они также содержат информацию для целей маршрутизации, включая адрес узла назначения и узла источника. Объединенный набор протоколов называется комплектом протокола управления передачей (Transmission Control Protocol, TCP) и протокола Интернета (Internet Protocol, IP). Связь между процессами UNIX (interprocess communication, IPC) использует протокол TCP/IP, чтобы позволить им на различных машинах ’’взаимодействовать" друг с другом. Адреса Интернета Узлы Интернета, так же как многие частные интерсети, используют протокол TCP/IP для передачи данных. Несмотря на то, что данный протокол обычно реализуется в сетях Ethernet, он может также использоваться в сетях других типов. Это делает его полезным для соединения различных сетей, поскольку не все компьютеры связаны Ethernet. Например, некоторые LAN могут использовать систему маркерного кольца IBM (Token Ring). Поэтому система адресации IP применяет независимую от аппаратуры схему маркирования: мосты, маршрутизаторы и шлюзы передают сообщения, основанные исключительно на их IP-адресе назначения, который связывается с физическим адресом аппаратуры только, когда сообщение достигает узла LAN-назначения. Таким образом, компьютер, посылающий сообщение, не должен знать конкретную информацию об аппаратуре компьютера, которому передается сообщение. Механизм IP-адресации работает одинаково независимо от того, подключили ли вы фактически ваши компьютеры к Интернету или нет. Когда организация устанавливает LAN, которая должна быть частью глобальной сети, она должна получить уникальный диапазон адресов, предназначенных для ее компьютеров (см. главу 10). Пока будем предполагать, что мы используем локальную 1Р-сеть. Адрес IP — это 32-битовое значение, которое записывается, как четыре отделенных точкой числа. Каждое число представлено 8 битами из 32 битов адреса. Поскольку каждая часть является 8-битовой, максимальное значение, которое она может иметь, равно 255. Из-за стремительного роста Интернета, по-видимому, кажущиеся бесконечными ресурсы 32-битовых адресов могут быстро исчерпаться. Текущий IP-стандарт протокола (IPv4) модифицируется, чтобы позволить большие адреса. IPv6 определит 128-битовый IP-адрес так, чтобы можно было назначить существенно больше адресов. (Для получения дополнительной информации о IPv6 см. главу 76.)
Именование Числовые IP-адреса не очень удобны для людей, чтобы использовать их для доступа к отдаленным компьютерам. Пользователи гораздо больше применяют именование объектов (например, людей, домашних животных и автомобилей). Поэтому мы взялись также и за именование наших компьютеров. Когда имя ведущего узла назначено конкретному компьютеру, может быть установлено взаимоотношение между именем и числовым IP-адресом компьютера. Тогда' пользователь способен указать имя, чтобы сослаться на компьютер, а программное обеспечение автоматически переведет имя в адрес IP. Связь IP-адресов и локальных имен хост-машин хранится администратором системы LAN в файле /etc/hosts. Продемонстрируем маленький фрагмент файла Техасского университета в Далласе (University of Texas at Dallas): 129.110.41.1 129.110.42.1 129.110.43.2 129.110.43.128 129.110.43.129 129.110.66.8 129.110.102.10 ташпахОЗ csservr2 ncubeOl vanguard jupiter neocortex corvette Маршрутизация Протокол IP выполняет два вида маршрутизации: статическую и динамическую. Статическая информация маршрутизации хранится в файле /etc/route и имеет вид "You may get to the destination DEST via the gateway GATE with X hops" ("Вы можете получить назначение DEST через шлюз GATE с X-ретрансляциями"). Когда маршрутизатор должен переслать сообщение, он может использовать информацию из этого файла, чтобы определить лучший маршрут. Динамическая информация маршрутизации разделяется между хост-машинами через демоны1 (daemons) /etc/routed или /etc/gated. Эти программы постоянно модернизируют свои локальные маршрутные таблицы на основе информации, собираемой из сетевого трафика, и периодически делятся своей информацией с другими соседними демонами. Безопасность Несколько сетевых утилит UNIX дают возможность пользователю с учетными записями на нескольких компьютерах выполнять команду на одном из 1 Демон — это причудливый термин для постоянно работающего фонового процесса, который обычно начинается, когда система загружается.
них, запуская ее с другого компьютера. Например, я зарегистрирован на машинах csservr2 и vanguard в Техасском университете в Далласе (University of Texas at Dallas). Для того чтобы выполнить утилиту date на машине vanguard с машины csservr2, я могу использовать утилиту rsh (обсуждаемую позже в этой главе2) следующим образом: $ rsh vanguard date ...выполнить date на vanguard. Любопытно, что rsh и несколько других утилит способны запустить shell на отдаленной хост-машине без необходимого пароля. Они могут делать это из-за возможности UNIX, называемой эквивалентностью машин. Если в своем домашнем каталоге вы создаете файл .rhosts, который содержит список имен хост-машин, то любой пользователь с тем же самым пользовательским ID, как ваш собственный, может зарегистрироваться с этих хост-машин без предоставления пароля. В моих домашних каталогах на csservr2 и vanguard есть файл .rhosts, который содержит следующие строки: csservr2.utdallas.edu vanguard.utdallas.edu Мы должны использовать "официальное" имя хост-машины, которое включает интернет-домен, в .rhosts-файле (см. главу 10). Эта способность получения shell без необходимости указывать пароль обеспечивает выполнение удаленных команд с любого компьютера без всяких препятствий. UNIX также позволяет системному администратору регистрировать глобально эквивалентные машины в файле /etc/hosts.equiv. Глобальная эквивалентность означает, что любой пользователь, зарегистрировавшийся на одной из перечисленных машин, может получить доступ к локальной хост-машине без пароля. Например, если файл vanguard /etc/hosts.equiv содержит строки csservr2.utdallas.edu vanguard.utdallas.edu то любой пользователь на csservr2 может войти в vanguard или выполнить удаленную команду на нем без упоминания пароля. Глобальная эквивалентность должна использоваться с большой осторожностью (как всегда). Порты и общие сервисы Один хост-компьютер3 сети взаимодействует с другим через набор пронумерованных портов. Каждый хост поддерживает некоторое количество стандартных портов для обычного использования и позволяет прикладным программам создавать другие порты для транзитных коммуникаций. Файл 2 См. разд. "Выполнение удаленных команд: rsh " далее в этой главе. — Ред. 3 Далее может использоваться термин ’’хост". — Ред.
/etc/services содержит список стандартных портов. Вот отрывок из файла Техасского университета в Далласе (University of Texas at Dallas): echo 7/tcp discard 9/top sink null systat 11/tcp users daytime 13 /tcp ftp-data 20/tcp ftp 21/tcp telnet 23/tcp smtp 25/tcp mail time 37/tcp timeserver rip 39/udp resource whois 43/tcp finger 79/tcp sunrpc 111/tcp exec 512/tcp login 513/tcp Описание утилиты telnet, представленное далее в этой главе4, содержит примеры соединений с некоторыми из этих стандартных портов. Сетевое программирование Коммуникации между процессами UNIX позволяют вам связываться с другими программами с известным IP-адресом и портом. Данная возможность описана в конце главы 13, вместе с полным исходным кодом для проекта Internet shell, который может конвейеризировать и переадресовывать данные к другим интернет-shell на различных хост-машинах. Пользователи Работая в сети UNIX, следует знать все о перемещении по сети и взаимодействии с другими пользователями. Поэтому один из наиболее важных аспектов заключается в том, чтобы уметь выяснять, кто работает на конкретном хосте. Существует несколько утилит, которые это делают: □ утилита users перечисляет всех пользователей на вашем локальном хосте; □ утилита rusers выводит список всех пользователей в вашей локальной сети; 4 См. разд. "Удаленные соединения: telnet" далее в этой главе. — Ред.
□ утилита who подобна утилите users, за исключением того, что предоставляет больше информации; □ утилита rwho похожа на утилиту rusers, но выводит больше информации; □ утилита w подобна утилите who, за исключением того, что предоставляет еще больше сведений; □ утилита whois позволяет получить информацию о главных интернет-узлах; □ утилита hostname выводит локальное имя вашего хоста. Распечатка списка пользователей: users/rusers Утилиты users и rusers выводят список текущих пользователей вашего локального хоста и локальной сети соответственно. Синтаксис users Утилита users выводит простой, краткий список пользователей на вашем локальном хосте. Синтаксис rusers -а {хост}* Утилита rusers отображает список пользователей вашей локальной сети. По умолчанию опрашиваются все машины сети, хотя можно отменить это умолчание, предоставив список имен хостов, rusers работает, передавая широковещательный запрос об информации всем хостам, а затем показывает поступившие ответы. Для того чтобы хост ответил, должен работать rusersd-демон. (Дополнительную информацию о демонах см. в главе 15.) Например: $ users ...вывести пользователей на локальном хосте, glass posey $ rusers -al ...вывести пользователей в локальной сети. csservr4.utd posey
vanguard.utd huynh posey datta venky csservr2.utd posey glass $ Расширенные сведения о пользователях: who/rwho/w Утилиты who и rwho предоставляют немного больше информации, чем это делают утилиты users И rusers. Синтаксис who ^ЬоФайл] [am 1] По умолчанию утилита who показывает список всех пользователей на вашем локальном хосте. Если вы представите аргументы "ат 1", утилита опишет только вас. Всякий раз, когда пользователь регистрируется в системе или выходит из нее, файл /var/adm/wtmp обновляется информацией о сессии пользователя. Вы можете указать имя этого файла (или файла в том же самом формате), как аргумент who Файл. В этом случае утилита расшифрует информацию в файле и представит ее в типичном формате who. Вот пример использования утилиты who: $ who ...вывести всех текущих пользователей на локальном хосте, posey ttypO May 15 16:31 (blackfoot.utdall) glass ttyp2 May 17 17:00 (bridge05.utdalla) $ who am i ... вывести себя. csservr2!glass ttyp2 May 17 17:00 (bridgeOS.utdalla) $ who /var/adm/wtmp ...проверить файл who. leui ttyp2 May 17 12:48 (bridgeOS.utdalla) juang ttyp3 May 17 12:49 (annex.utdallas.e) ttyp3 May 17 12:52 ttyp2 May 17 12:57 weidman ttyp2 May 17 16:25 (annex.utdallas.e) ttyp2 May 17 16:33 glass $ ttyp2 May 17 17:00 (bridgeOS.utdalla)
Синтаксис rwho Утилита rwho аналогична утилите who, за исключением того, что она выводит список пользователей, зарегистрированных на всех отдаленных хостах вашей локальной сети. Синтаксис w {ID—Пользователя} * Утилита w отображает список, в котором зафиксировано, что делает каждый указанный пользователь. Другими словами, w похожа на утилиту who. Утилита w столь же удобна, как и утилита who. Вот пример: $ w ...получить более детальную информацию, чем who. 5:27pm up 11 days, 11 ruins, 3 users , load average: 0.08, 0.03, 0.01 User tty login® idle JCPU PCPU what posey ttypO Fri 4pm 2 days 1 -csh glass ttyp2 5:00pm 1 13 1 w $ w glass ...проверить только самого себя. 5:27pm up 11 days, 11 mins, 3 users, load average: 0.08, 0.03, 0.01 User tty login® idle JCPU PCPU what glass ttyp2 5:00pm 13 1 w glass Собственное имя хоста: hostname Чтобы выяснить имя вашей локальной хост-машины, используйте утилиту hostname. Синтаксис hostname [имяХоста] Если утилита hostname используется без параметров, то показывает имя вашей локальной хост-машины. Привилегированный пользователь может изменить его, предоставляя новое имя хоста, как аргумент, что обычно
делается автоматически в файле /etc/rc.local. (Дополнительную информацию об этом файле см. в главе 15.) Пример: $ hostname csservr2 $ . . .вывести имя моей хост-машины. Персональные данные: finger Как только вы получили список пользователей вашей системы, удобно получить о них расширенную информацию. Утилита finger позволяет сделать это. Рекомендуется создавать собственные файлы .plan и .project в своем домашнем каталоге так, чтобы другие пользователи тоже могли вас "вычислить". Веселитесь! Синтаксис f inger { Ю_пользова теля} * Утилита finger выводит информацию о пользователях, которая собирается из следующих источников: • домашний каталог пользователя, shell запуска и полное имя читаются из файла пароля /etc/passwd; • если пользователь имеет файл .plan в своем домашнем каталоге, содержимое файла показывается, как plan пользователя; • если пользователь имеет файл .project в своем домашнем каталоге, содержимое файла показывается, как project пользователя. Если пользовательский ID не указан, утилита finger отображает сведения о каждом пользователе, который в настоящее время зарегистрирован в системе. Вы можете указать пользователя на отдаленном хосте с ромо-щью @. В этом случае запускается finger-демон удаленного хоста для ответа на локальный finger-запрос. В представленном ниже примере сначала выводится информация обо всех пользователях системы, а затем собственные сведения: $ finger ...указать на каждого в системе. Login Name TTY Idle When Where posey John Posey pO 2d Fri 16:31 blackfoot.utdall
glass Graham Glass p2 Sun 17:00 bridgeOS.utdalla $ finger glass ...указать самого себя. Login name: glass In real life: Graham Glass Directory: /home/glass Shell: /bin/ksh On since May 17 17:00:47 on ttyp2 from bridgeOS.utdalla No unread mail Project: To earn an enjoyable, honest living. Plan: To work hard and have fun and not notice the difference. $ _ В следующем примере выводятся три источника информации finger о себе: $ cat .plan ...вывести файл .plan. То work hard and have fun and not notice any difference. $ cat .project ...вывести файл .project. To earn an enjoyable, honest living. $ grep glass /etc/passwd ...посмотреть файл password, glass:##glass:496:62:Graham Glass:/home/glass:/bin/ksh $ _ В заключительном примере используется утилита rusers для получения листинга удаленных пользователей, а затем запускается удаленная утилита finger, чтобы выяснить все о Сьюзен: $ rusers ...посмотреть на удаленных пользователей. csservr4.utd posey vanguard.utd huynh posey datta venky centaur.utda susan csservr2.utd posey posey leui glass $ finger susanOcentaur ...выполнить удаленную утилиту finger, [centaur.utdallas.edu] Login name: susan In real life: Susan Marsh Directory: /home/susan Shell: /bin/csh On since May 11 11:00:55 on console 1 day Idle Time New mail received Fri May 15 19:24:01 1998; unread since Fri May 15 16:40:28 1998 No Plan. $
Взаимодействие с пользователями Перечисленные далее утилиты позволяют вам взаимодействовать с пользователем: □ утилита write предоставляет возможность посылать пользователю отдельную строку за один раз; □ утилита talk позволяет иметь диалоговый полиэкран для двустороннего разговора; □ утилита wall помогает посылать сообщение всем пользователям на локальном хосте; □ утилита mail обеспечивает отправку почтовых сообщений. Утилита mail была описана в главе 2. Она поддерживает полную стандартную адресную схему Интернета. Другие утилиты описаны далее вместе с простой утилитой mesg, которая позволяет защитить себя от сообщений других пользователей. Защита от общения: mesg Утилиты write, talk и wall связываются с другими пользователями путем передачи данных непосредственно на их терминалы. Вы можете блокировать способность других пользователей писать на ваш терминал с помощью УТИЛИТЫ mesg. Синтаксис mesg [n | у] Утилита mesg запрещает другим пользователям писать на ваш терминал. Она работает, изменяя разрешение записи вашего устройства tty. Аргументы п и у соответственно запрещают и разрешают запись. Если аргументы не указаны, выводится ваш текущий статус. В следующем примере утилита mesg предохраняет от получения сообщения ОТ утилиты write: $ mesg n ... защитить терминал. $ write glass ...попытаться использовать write для себя самого. write: You have write permission turned off $
Передача строки за один раз: write write является простой утилитой, которая позволяет посылать одну строку за один раз указанному пользователю. Синтаксис write Ю_пользователя [ tty] Утилита write копирует свой стандартный ввод, одну строку за один раз, на терминал, связанный с ю_пользователя. Если пользователь зарегистрирован на нескольких терминалах, можно определить конкретное устройство tty при помощи дополнительного аргумента tty. Первая строка ввода, которую вы посылаете пользователю при помощи утилиты write, предваряется сообщением Message from yourHost!yourld on yourTty таким образом, чтобы получатель мог инициировать write для взаимодействия с вами. Для выхода из утилиты следует нажать в отдельной строке комбинацию клавиш <Ctrl>+<D>. Можно запретить вывод от write на ваш терминал С ПОМОЩЬЮ утилиты mesg. В представленном ниже примере получено write-сообщение от другого пользователя, Тима, а затем инициировалась собственная утилита write для передачи ему ответа. Для синхронизации работы использовалось соглашение: -о- (перейти на прием) и -оо- (перейти на прием и отключится): $ Message from tim@csservr2 on ttyp2 at 18:04 hi Graham -о-$ wri te tim ... от Тима. ...инициировать ответ. hi Tim —o— ...от меня. don't forget the movie later -oo- ... от Тима. OK -oo- ... от меня. ...конец моего ввода. $ _ Хотя можно вести беседу с помощью утилиты write, это ужасно неудобно. Лучше использовать утилиту talk.
Интерактивные диалоги: talk Утилита talk позволяет вести беседу через сеть. Это забавная утилита, которая заслуживает проведения исследований с другом. Синтаксис talk 10_полъзователя [tty] Утилита talk позволяет общаться с другим пользователем в сети через многоэкранный интерфейс. Если пользователь зарегистрирован на нескольких терминалах, можно выбрать конкретный терминал, указывая конкретное имя устройства tty. Чтобы говорить с кем-нибудь, введите с вашего терминала: $ talk theirUserId@theirHost Это вызовет появление на экране получателя сообщения: Message from TalkDaemon@ their Host ... talk: connection requested by yourUserId@yourHost talk: respond with: talk yourUserId@yourHost Если получатель соглашается на ваше приглашение, он напечатает следующее в ответ на приглашение shell: $ talk yourUserldGyourHost Далее ваш экран разделится на две части: одна будет отображать вводимый с клавиатуры текст, другая — сообщения собеседника. Все, что вы печатаете, будет отображаться на терминале другой персоны, и наоборот. Для того чтобы повторно вывести экран, если возникнет неполадка, следует нажать комбинацию клавиш <Ctrl>+<L>. Для завершения работы утилиты talk существует комбинация клавиш <Ctrl>+<C>. Чтобы запретить другим пользователям вести с вами диалог, используйте УТИЛИТУ mesg. Сообщения всем пользователям: wall Если у вас есть нечто важное сообщить миру (или, по крайней мере, каждому на вашем локальном хосте), утилита wail может вам помочь. Название утилиты wail составлено из слов "write all" — "пишут все". Она позволяет передавать широковещательное сообщение. •
Синтаксис wall [имяФайла] Утилита wall копирует СВОЙ стандартный ВВОД (или содержимое имяФайла. если он представлен) на терминал каждого пользователя локального хоста, предваряя ввод сообщением "Broadcast Message..." ("Широковещательное сообщение..."). Если пользователь запретил связь с терминалом утилитой mesg, сообщение не будет получено, исключая привилегированного пользователя wall. В следующем примере показано, как каждому пользователю на локальном хосте (в том числе, автору сообщения) посылается шутка: $ wall ...написать всем. this is a test of the broadcast system ... конец ввода. Broadcast Message from glass@csservr2 (ttyp2) at 18:04 ... this is a test of the broadcast system $ _ Утилита wall чаще всего используется системными администраторами, чтобы послать пользователям важную, своевременную информацию (например, "Система будет выключена через пять минут!"). Распределенные данные Основополагающий вид дистанционных операций —• это передача файлов, и UNIX имеет для этого несколько утилит: П утилита гср (удаленное копирование) позволяет копировать файлы между локальным и удаленным UNIX-хостами; □ утилита ftp (протокол передачи файлов или программа) обеспечивает копирование файлов между локальным UNIX-хостом и любым другим хостом (включая не-UNIX-хост), который поддерживает протокол FTP; таким образом, утилита ftp мощнее утилиты гср; □ утилита uucp (копирование с UNIX на UNIX) подобна утилите гср и позволяет копировать файлы между любыми двумя UNIX-хостами. 14 Зак. 786
Копирование файлов между двумя UNIX-хостами: гср Утилита гср позволяет копировать файлы между UNIX-хостами. Синтаксис гср -р оригинальныиФайл новыиФайл гср -рг {имяФайла}+каталог Утилита гср позволяет копировать файлы между UNIX-хостами. И локальный хост, и удаленный хост должны быть зарегистрированы как эквивалентные машины (см. разд. "Безопасность"ранее в этой главе). Чтобы определить удаленный файл на хосте, используйте синтаксис хост:путь Если указан относительный путь, он интерпретируется, как путь относительно домашнего каталога пользователя на удаленном хосте. Опция -р пробует сохранить последнее время модификации, последнее время доступа и флаги разрешения во время копирования. Опция -г заставляет рекурсивно копироваться любой файл каталога. В приведенном ниже примере файл original.txt скопирован с удаленного хоста vanguard в файл new.txt локального хоста csservr2, а затем скопирован файл original2.txt с локального хоста в файл new2.txt на удаленном хосте: $ гср vanguard:original.txt new.txt ...удаленный в локальный. $ гср original2.txt vanguard:new2.txt ...локальный в удаленный. $ _ Копирование файлов между He-UNIX-хостами: ftp Протокол FTP (Протокол передачи файлов) — это общий протокол для передачи файлов, который поддерживается многими машинами. Вы можете использовать для передачи файлов со своего локального UNIX-хоста на удаленный хост любого другого вида, если вы знаете интернет-адрес удаленного FTP-сервера. Пользователи He-UNIX-компьютеров часто запускают утилиту ftp для передачи файлов между UNIX и их собственной системой. В табл. 91.1 представлен список наиболее полезных ftp-команд, которые являются доступными из режима команд этой утилиты.
Синтаксис ftp -п [имяХоста] Утилита ftp позволяет управлять файлами и каталогами, как на локальном, так и удаленном хосте. Если указано имя удаленного хоста, утилита ищет файл .netrc, чтобы определить, имеет ли удаленный хост анонимный вход ftp (т. е. вход без пароля). Если имеет, то этот вход используется для регистрации пользователя на удаленном хосте. В противном случае предполагается, что пользователь имеет свою учетную запись на удаленном хосте, и выводится приглашение для ввода пользовательского ID и пароля. Если регистрация в удаленной системе прошла успешно, утилита ftp входит в свой режим команд и показывает приглашение ftp >. Если имя удаленного хоста не указано, утилита входит в свой командный режим немедленно, и следует использовать команду открытия, чтобы соединиться с удаленным хостом. Опция -п не допускает осуществление начальной автоматической последовательности входа в систему. Вы можете прервать передачу файла без выхода из утилиты ftp, нажимая комбинацию клавиш <Ctrl>+<C>. Таблица 9.1. Команды утилиты ftp Команда Описание ! команда Выполняет команду на локальном хосте append локальныйФайл удаленныйФа йл Добавляет локальныйФайл к удаленномуФайлу > bell Вызывает звуковой сигнал после каждой передачи файла bye Закрывает сеанс текущей удаленной связи с хостом и выходит из ftp cd удаленныйКаталог Изменяет ваш текущий удаленный рабочий каталог на уда ленныйКа талог close Закрывает текущую удаленную хост-связь delete удаленныйФайл Удаляет удаленныйФайл на удаленном хосте get удаленныйФайл [локальныйФайл] Копирует удаленныйФайл в локальныйФайл. Если локальныйФайл опущен, ему присваивается то же самое имя, что и у удаленного файла
Таблица 9.1 (окончание) Команда Описание help [команда] Выводит справку о команде. Если команда опущена, отображается список всех команд утилиты ftp led локальныйКатало Изменяет ваш текущий локальный рабочий каталог на локальныйКатало 1s удаленныйКаталог Выводит содержимое вашего текущего удаленного рабочего каталога удаленныйКа талог mkdir удаленныйКаталог Создает удаленныйКа талог на удаленном хосте open имяХоста [порт] Делает попытку связи с хостом с именем имяХоста. Если пользователь определяет дополнительный номер порта, ftp предполагает, что это порт FTP-сервера put локальныйФайл [удаленныйФайл] Копирует локальныйФайл в удаленныйФайл. Если удаленныйФайл опущен, ему присваивается то же самое имя, что и у локального файла pwd Показывает текущий удаленный рабочий каталог пользователя quit Аналогично bye rename удаленныйФайл1 удаленныйФайл2 Переименовывает удаленный файл с удаленныйФайл 1 на удаленныйФайл2 rmdi г удаленныйКаталог Удаляет удаленныйКа та лог В следующем примере копируется файл writer.c с удаленного хоста vanguard на локальный хост, а затем копируется файл who.c с локального хоста на удаленный хост: $ ftp vanguard ...открыть FTP-соединение с vanguard. Connected to vanguard.utdallas.edu. vanguard FTP server (SunOS 5.4) ready. Name (vanguard:glass): glass ...вход в ситстему Password required for glass. «Password: ...секрет! User glass logged in. ftp> Is ...получить каталог удаленного хоста. PORT command successful. ASCII data connection for /bin/ls (129.110.42.1,4919) (0 bytes). ... ...много файлов было выведено здесь.
uniq upgrade who. с writer.с ASCII Transfer complete. 1469 bytes received in 0.53 seconds (2.7 Kbytes/s) ftp> get writer.с ...скопировать с удаленного хоста. PORT command successful. ASCII data connection for writer.c (129.110.42.1,4920) (1276 bytes). ASCII Transfer complete. local: writer.c remote: writer.c 1300 bytes received in 0.012 seconds (le+02 Kbytes/s) ftp> !ls ...получить каталог локального хоста. reader.с who.с writer.с ftp> put who.с ...копировать файл на удаленный хост. PORT command successful. ASCII data connection for who.c (129.110.42.1,4922). ASCII Transfer complete. ftp> quit ...отключиться. Goodbye. $ _ Распределенная обработка Мощность распределенных систем становится поразительной, когда вы начинаете перемещаться по сети и регистрироваться на различных хостах. Некоторые хосты обеспечивают ограниченные беспарольные учетные записи с пользовательским ID, подобно имени guest. Так что исследователи могут бродить по сети без причинения вреда, хотя эта практика исчезает, поскольку все больше пользователей злоупотребляют данной привилегией. В наши дни пользователь почти всегда должен иметь учетную запись на удаленном компьютере, чтобы войти в сеть. Существуют три утилиты для распределенного доступа: □ утилита г login позволяет войти на удаленный хост; □ утилита rsh предоставляет возможность выполнения команды на удаленном UNIX-хосте;
□ утилита telnet поддерживает выполнение команд на любом удаленном хосте, который имеет Telnet-сервер. Из перечисленных утилит telnet наиболее гибкая, т. к. есть другие системы в дополнение к UNIX, которые поддерживают Telnet-серверы. Удаленный вход в систему: rlogin Чтобы зарегистрироваться на удаленном хосте, используйте утилиту rlogin. Синтаксис rlogin -ес [-1 Ю_пользователя] имяХоста Утилита rlogin пытается зарегистрировать пользователя на удаленном хосте имяХоста. Если не указан 10_пользователя с помощью опции -1, во время входа в систему берется ваш локальный пользовательский ID. Если удаленный хост не установлен как эквивалент локального хоста в пользовательском файле $HOME/.rhost, появится запрос о пароле на удаленном хосте. Как только пользователь соединится, его локальный shell приостанавливается, а удаленный shell начинает выполняться. После окончания работы с удаленным входным shell, завершая его нормальным способом (обычно нажатием комбинации клавиш <Ctrl>+<D>), возобновляется локальный shell. Существует несколько специальных ’’команд перехода", вводимых с клавиатуры и имеющих специальное значение. Каждой предшествует символ перехода, которым по умолчанию является тильда (-). Вы можете заменить этот символ путем указания в опции -е привилегированного символа перехода. Вот список команд перехода: Последовательность Описание Отключается немедленно от удаленного хоста -susp Приостанавливает удаленную входную сессию. Для повторного старта удаленной входной сессии используется команда f g ~dsusp t Приостанавливает ввод половины удаленной входной сессии, но все еще выводит информацию локальной входной сессии на локальный терминал пользователя. Для повторного старта удаленной входной сессии используется команда fg
В представленном ниже примере пользователь регистрируется на удаленном хосте vanguard с собственного локального хоста csservr2, выполняет утилиту date, а затем разъединяется: $ гlogin vanguard ...удаленный вход. Last login: Tue May 19 17:23:51 from csservr2.utdallas vanguards date ...выполнить команду на vanguard. Wed May 20 18:50:47 CDT 1998 vanguard% AD ...завершить удаленный входной shell. Connection closed. $ _ ...вернуться домой на csservr2! Выполнение удаленных команд: rsh Если вы хотите выполнить только единственную команду на удаленном хосте, утилита rsh намного удобнее, чем riogin. Синтаксис rsh [-1 1р_пользователя] имяХоста [команда] Утилита rsh пытается создать удаленный shell на хосте имяхста, чтобы выполнить команду. Утилита копирует свой стандартный ввод команде и копирует стандартный вывод и канал ошибок от команды в свои аналогичные каналы. Сигналы прерывания, выхода и завершения отправляются команде, так что пользователь может нажимать комбинацию клавиш <Ctrl>+<C> для удаленной команды, rsh заканчивается сразу же по завершении работы команды. Если Не указан 19_пользователя с помощью опции -1, для связи берется локальный пользовательский ID. Если никакая команда не определена, rsh предоставляет удаленный shell, вызывая утилиту riogin. Метасимволы в кавычках обрабатываются удаленным хостом; все другие — локальным shell. В следующем примере выполняется утилита hostname как на локальном хосте csservr2, так и на удаленном хосте vanguard: $ hostname ...выполнить на локальном хосте. csservr2 $ rsh vanguard hostname ...выполнить на удаленном хосте, vanguard $
Удаленные соединения: telnet Утилита telnet позволяет связываться с любым удаленным хостом в Интернете, который имеет Telnet-сервер. Синтаксис telnet [хост [порт]] Утилита .telnet устанавливает двустороннюю связь с отдаленным портом. Если указано имя хоста, но не специфицируется порт, пользователь автоматически связывается с Telnet-сервером на указанном хосте, который обычно позволяет зарегистрироваться на удаленной машине. Даже если не задано имя хоста, telnet переходит непосредственно в командный режим (таким же образом, как и утилита ftp). Что происходит далее и как устанавливается связь, зависит от функциональных возможностей порта, с которым произошла связь. Например, порт 13 из любой интер-нет-машины отправит время дня и затем отключится, тогда как порт 7 вернет (ping) назад введенный с клавиатуры текст. Для того чтобы войти в командный режим после установки связи, следует нажать комбинацию клавиш <Ctrl>+<]>, которая является последовательностью перехода telnet. Это вызывает вывод приглашения командного режима. Допускаются также следующие команды: Команда Описание close Закрывает текущее соединение open хост [порт] Соединяет с хостом с необязательным спецификатором порта quit Выходит из telnet z Приостанавливает telnet ? Печатает сводку команд telnet Чтобы закончить telnet-связь, следует нажать комбинацию клавиш <Ctrl>+<]>, сопровождая ее командой quit. В представленном ниже примере telnet используется, чтобы эмулировать функциональные возможности утилиты riogin, опуская явный номер порта в команде open: $ telnet ...стартовать telnet. telnet> ? ...получить help.
Commands may be abbreviated. Commands are: close close current connection display display operating parameters mode try to enter line-by-line or character-at-a-time mode open connect to a site quit exit telnet send transmit special characters (’send ?’ for more) set set operating parameters (’set ?’ for more) status print status information toggle toggle operating parameters (’toggle ?’ for more) z suspend telnet ? print help information telnet> open vanguard ...получить входной shell на vanguard. Trying 129.110.43.128... Connected to vanguard.utdallas.edu. Escape character is ,Л]’. SunOS 5.4 (vanguard) login: glass ...ввод пользовательского ID. Password: ...секрет! Last login: Tue May 19 17:22:45 from csservr2.utdalla *** For assistance, send mail to UNIXINFO. Tue May 19 17:23:21 CDT 1998 Erase is Backspace vanguard% date ...выполнить команду. Tue May 19 17:23:24 CDT 1998 vanguard% ...отключиться от удаленного хоста. Connection closed by foreign host. $ _ ...утилита telnet завершилась. Если хотите, можете определить имя хоста непосредственно в командной строке, следующим образом: $ telnet vanguard ...спецификация имени хоста в командной строке. Trying 129.110.43.128... Connected to vanguard.utdallas.edu. Escape character is ,Л]’. SunOS 5.4 (vanguard) login: glass ...ввод пользовательского ID и т. д.
Вы можете использовать утилиту telnet, чтобы испытать некоторые из стандартных услуг портов, которые описаны ранее в этой главе. Например, порт 13 печатает день и время на удаленном хосте, а затем немедленно отключается: $ telnet vanguard 13 ...какое время и день на удаленном хосте? Trying 129.110.43.128 ... Connected to vanguard.utdallas.edu.' Escape character is ,A]’. Tue May 19 17:26:32 1998 Connection closed by foreign host ...telnet-связь завершилась. $ _ Аналогично, порт 79 позволяет ввести имя удаленного пользователя и получать информацию ОТ утилиты finger: $ telnet vanguard 79 ...вручную выполнить удаленную утилиту finger. Trying 129.110.43.128 ... Connected to vanguard.utdallas.edu. Escape character is ,Л]’. glass ...ввод пользовательского ID. Login name: glass In real life: Graham Glass Directory: /home/glass Shell: /bin/csh Last login Tue May 19 17:23 on from csservr2.utdalla No unread mail No Plan. Connection closed by foreign host. ... telnet-связь завершилась. $ _ Когда системные администраторы проверяют сеть, они часто используют порт 7 для проверки связи хоста. Порт 7 возвращает назад на пользовательский терминал все, что пользователь напечатает, и иногда известен как ping-порт. Вот пример: $ telnet vanguard 7 ...проверить ping. Trying 129.110.43.128 ... Connected to vanguard.utdallas.edu. Escape character is ’Л]’. hi ...моя строка. hi ... эхо. there
there л] . . .переключиться в командный режим. telnet> quit ...завершить соединение. Connection closed. $ _ Утилита telnet принимает числовые интернет-адреса так же, как символические имена. Например: $ telnet 129.110.43,128 7 ...числовой адрес vanguard. Trying 129.110.43.128... Connected to 129.110.43.128. Escape character is ,Л]’. hi ...моя строка. hi ... эхо. *] ...переключиться в командный режим. telnet> quit ...отключиться. Connection closed. $ Сетевая файловая система: NFS В порядке выработки хорошего использования сетевых возможностей UNIX, Sun Microsystems представила свободно копируемую спецификацию для сетевой файловой системы (Network File System, NFS). NFS поддерживает перечисленные ниже полезные возможности. □ Позволяет нескольким локальным файловым системам быть смонтированными в отдельную сетевую файловую иерархию, к которой можно открыто обращаться с любого хоста. Чтобы поддержать эту способность, NFS включает удаленную возможность монтировки. П Удаленный вызов процедуры (Remote Procedure Call, RPC) используется NFS, чтобы позволить одной машине делать вызов процедуры на другой машине, таким образом обеспечивая распределенные вычисления. □ NFS поддерживает нейтральную по отношению к хосту схему внешнего представления данных (external data representation, XDR), которая позволяет программистам создавать структуры данных, которые могут быть разделены хостами с различным порядком байтов и длиной слова.
NFS очень популярна и используется на большинстве компьютеров, где работает UNIX. (См. [Nemeth 2000] для дополнительной информации о NFS.) Для дополнительной информации Если вы насладились представленным кратким обзором организации сети UNIX и желаете узнать больше, то [Stevens 1998], [Anderson 1995] и раздел о сети в [Nemeth 2000] являются превосходными источниками. Обзор главы Перечень тем В этой главе мы рассмотрели: □ основные сетевые концепции и терминологию UNIX; □ утилиты просмотра пользователей и взаимодействия с ними; □ утилиты для манипулирования удаленными файлами; □ утилиты для получения удаленного входного shell и выполнения удаленных команд. Контрольные вопросы 1. В чем заключается различие между мостом, маршрутизатором и шлюзом? 2. В чем заключается способ для системного администратора сообщать пользователям о важных событиях? 3. Почему утилита ftp более мощная, чем утилита гср? 4. Опишите некоторые варианты использования общих портов. 5. Что означает эквивалентность машин и как вы можете ее использовать? Упражнения 9.1. Испытайте утилиты гср и rsh следующим образом: • скопируйте отдельный файл с вашего локального хоста на удаленный хост, используя гср; • запустив rsh, получите shell на удаленном хосте и отредактируйте файл, который вы только что скопировали;
• покиньте удаленный shell с помощью команды exit; • используя гср, скопируйте файл с удаленного хоста обратно на локальный хост. [Уровень: легкий.] 9.2. Используйте утилиту telnet, чтобы получить время дня на нескольких удаленных хост-узлах. Действительно ли их время точное относительно друг друга? [Уровень: средний.] Проект Напишите shell-скрипт, который работает в фоновом режиме на двух машинах и гарантирует, что содержимое указанного каталога на одной машине всегда является зеркальным отображением каталога на другой машине. [Уровень: высокий.]

Глава 10 Интернет Мотивация Пожалуй, Интернет — наиболее популярное применение вычислительной техники. Возникнув как сетевой инструмент для соединения пользователей в университетских и правительственных учреждениях, он превратился в огромное самостоятельное образование, используемое миллионами людей во всем мире. Даже самый компьютерно-неграмотный человек в Соединенных Штатах, по крайней мере, слышал об Интернете. В большой степени привлекательность наличия домашнего компьютера в настоящее время определяется возможностью иметь доступ к информации в Интернете. В данной главе вы узнаете, что собой представляет Интернет, и что пользователь может с ним делать. Предпосылки Можно не разбираться в тонкостях механизма работы протокола IP (см. главу 9), чтобы с пользой прочитать данную главу. Но чем лучше вы знакомы с организацией сети UNIX, тем полезнее окажется для вас эта глава. Задачи Познакомившись с главой, вы лучше станете понимать сущность Интернета, историю его возникновения, принцип работы -и назначение. Изложение Для настоящего изучения любой темы необходимо знать исторические факты. Сначала мы опишем возникновение Интернета и сегодняшнее его состояние, учитывая его неблагоприятное начало. После обзора инструментов, используемых для доступа к информации в Интернете, рассмотрим перспективы, которые могут ожидать пользователей глобальной сети.
Эволюция Интернета Эволюция началась с увеличением локальных сетей, которые мы рассмотрели в главе 9. Сначала некоторые компании связали свои собственные LAN через индивидуальные соединения. Другие передавали данные через сеть, реализованную на базе общедоступной телефонной сети. В конечном счете, сетевые исследования, финансируемые американским правительством, объединили все вместе. Пусть кажется невероятным, но можно сделать вывод, что Интернет был почти неизбежен. Хоть прототип Интернета и возник в "компьютерной лаборатории", и в то время большинство думало, что лишь активные компьютерные ученые будут когда-нибудь использовать его, способ хранения и применения информации почти продиктовал, что будет найден лучший способ передачи информации из одного места в другое. Сейчас, когда я смотрю телевизор и вижу адреса страниц сети в конце видеорекламы для широко распространенных продуктов, знаю, что Интернет действительно достиг широкого применения. Мало того, что высокотехнологичные корпорации поддерживают страницы сети, но даже зерновые компании имеют узлы во всемирной сети. Можно спорить о полноценности некоторых из этих узлов, но факт их существования свидетельствует, что общество восприняло новую технологию. Наверняка у вас возник вопрос: как мы добились этого и куда можно двигаться дальше? В начале:1960-е В 1960-х люди побывали на Луне, общество прошло через потрясения на нескольких фронтах, а технология изменялась быстрее, чем когда-либо прежде. Агентство Исследований Министерства обороны США Advanced Research Projects Agency (ARPA) пыталось разработать компьютерную сеть, чтобы соединить вместе правительственные компьютеры (и компьютеры некоторых правительственных подрядчиков). Как и во многих достижениях в нашем обществе, инициатива (и часть финансирования) поступила от правительства, которое надеялось усилить поступательное движение в военных или оборонных целях. Быстродействующая связь данных могла бы потребоваться, чтобы помочь в некоторый момент выиграть войну.. Кстати, причины возникновения существующей американской системы дорог между штатами (другой вид сети) почти аналогичны. В 1960-х ЭВМ в основном использовались для вычислений, и будут еще доминировать в течение некоторого времени. Сменные диски, маленькие ленты и технология компактных дискет были еще в будущем. Перемещение данных с одного большого компьютера на другой обычно требовало записи
данных на относительно большое ленточное устройство или на некоторый большой диск, физического переноса этого носителя на другой большой компьютер и загрузки данных на этот компьютер. Хотя такая процедура была обычной, конечно, все-таки оказывалась чрезвычайно неудобной. Сетевая связь В то время организация компьютерной сети была все еще в младенчестве, локальные сети также существовали, но были прообразом Интернета. В течение 1968 и 1969 годов ARPA экспериментировало с соединениями между несколькими правительственными компьютерами. Основной архитектурой был 50-килбайтный выделенный телефонный канал, связанный с каждой стороны с машиной, называемой Interface Message Processor (IMP). Концептуально это мало чем отличается от вашей личной интернет-связи сегодня, если предположить, что модем пользователя выполняет работу IMP (Конечно, IMP была по сравнению с модемом гораздо более сложным устройством.) На каждой стороне IMP затем соединялась с компьютером или компьютерами, которые нуждались в доступе к сети. ARPANET Сеть ARPANET появилась в сентябре 1969 года, когда первые четыре IMP были установлены в Южно-калифорнийском университете (University of Southern California), Стэнфордском исследовательском институте (Stanford Research Institute), Калифорнийском университете в Санта-Барбаре (University of California at Santa Barbara) и Университете Юты (University of Utah). Перечисленные узлы имели существенное число ARPA-контрагентов. Успех первых экспериментов на этих четырех узлах вызвал большой интерес со стороны ARPA, так же как и в академическом сообществе. Вычисления никогда больше не станут такими, какими были тогда. Стандартизация Интернета: 1970-е Проблема с первыми соединениями в ARPANET состояла в том, что каждая IMP была до некоторой степени сделанной на заказ для каждого узла в зависимости от операционных систем и сетевой конфигурации других компьютеров. Много времени и усилий было израсходовано, чтобы получить сеть из четырех узлов. Сотни узлов требовали бы в сотню раз больше времени и очень много заказной работы, если бы это пришлось делать тем же самым способом. Стало ясно, что если все компьютеры связать в сеть таким же образом и использовать одинаковые протоколы, они могли бы соединиться друг с другом более эффективно и с гораздо меньшими усилиями в каждом узле. Но в то время различные компьютерные продавцы снабжали собственные операци
онные системы собственной аппаратурой и были лишь в начале пути стандартизации, которая облегчает взаимодействие. Требовался набор стандартов, которые могли быть реализованы в программном обеспечении на различных системах, чтобы существовала возможность разделения данных в форме, понятной различным компьютерам. Несмотря на то, что возникновение стандартных сетевых протоколов началось в 1970-х, реализоваться они смогли не раньше 1983 года, когда все члены ARPANET стали использовать исключительно их. Семейство протокола IP В начале 1970-х исследователи начали проектировать протокол IP. Слово "интернет" использовалось в более общем смысле, чем ARPANET, которое означало конкретную сеть. Слово "интернет" указывало на общую организацию сети компьютеров, чтобы позволить им взаимодействовать. Протокол IP (Internet Protocol, Протокол Интернета) — фундаментальный программный механизм, который перемещает данные из одного места в другое через сеть. Данные, которые должны быть переданы, разделяются на пакеты, которые являются основными элементами данных, используемыми в цифровой компьютерной сети. IP не гарантирует, что любой отдельный пакет достигнет другого конца или порядок прибытия пакетов окажется последовательным. Но он гарантирует неизменность прибывшего пакета по сравнению с исходным пакетом в источнике. Эта особенность может показаться сначала не очень полезной, но подождите немного. TCP/IP Раз можно передать пакет на другой компьютер и известно, что прибывший пакет будет верным, то другие протоколы могут быть добавлены "сверху" основного протокола IP для обеспечения большего количества функциональных возможностей. Протокол TCP (Transmission Control Protocol, Протокол управления передачей) чаще всего используется с IP. (Вместе они упоминаются, как TCP/IP.) Как подразумевает имя, TCP управляет фактической передачей потока пакетов данных. Протокол добавляет информацию последовательности и подтверждения к каждому пакету, и стороны ТСР-"беседы" сотрудничают для выяснения, что полученный поток данных восстановлен в том же самом порядке, как в оригинале. Когда отдельный пакет будет не в состоянии достичь другого конца из-за некоторых неполадок в сети, получающая TCP-программа узнает это, поскольку порядковый номер пакета отсутствует. Программа может контактировать с отправителем и сделать так, чтобы он снова послал пакет. С другой стороны, отправитель, не имея квитанции подтверждения о получении рассматриваемого пакета, будет в конечном счете повторно передавать пакет, предполагая, что тот не
был получен. Если пакет был получен, а потерялось только подтверждение, TCP-программа при получении второй копии пропустит ее, т. к. первая уже достигла цели. Приемник будет все же посылать подтверждение, которое ожидает посылающая ТСР-программа. TCP — это протокол, ориентированный на связь. Прикладная программа открывает TCP-связь с другой программой на другом компьютере, и оба компьютера пересылают данные друг другу. Когда они заканчивают свою работу, то закрывают сеанс связи. Если один конец (или поломка сети) неожиданно закрывает сеанс, это на другом конце рассматривается, как ошибка. UDP/IP Другой полезный протокол, который сотрудничает с IP, — это UDP (User Datagram Protocol, Пользовательский дейтаграммный протокол), иногда полунежно называемый Ненадежным (Unreliable) дейтаграммным протоколом. UDP реализует низкоуровневый сигнальный метод для поставки коротких сообщений по IP, но не гарантирует их прибытие. В некоторых случаях приложение должно послать информацию о статусе другому приложению (например, агент управления, посылающий информацию о статусе в сеть или приложению управления системами), но информация не является критически важной. Если она не прибывает, или будет послана снова позже, или в ней нет необходимости, то данные должны быть получены приложением. Конечно, такой подход предполагает, что любой отказ происходит из-за некоторого переходного состояния и метод "следующий раз" будет работать. Если он терпит неудачу все время, подразумевается, что существует проблема сети, которая не может стать явной. В подобном случае служебные протокольные сигналы требуют открыть и поддержать TCP-связь. Это, правда, больше, чем действительно необходимо. Ведь вы хотите всего лишь послать короткое сообщение о статусе. В действительности вас не заботит, получит ли его компьютер на другом конце (т. к. если он это не сделает, то, вероятно, получит следующее сообщение), и вы, конечно, не хотите ждать подтверждение о получении. Так что "ненадежный протокол" точно соответствует названию. Интернет-адресация Когда организация монтирует LAN, которая должна быть частью Интернета, она запрашивает уникальный интернет-адрес в Центре Информации Сети (Network Information Center, NIC). Число, которое назначается, зависит от размера организации. □ Огромной организации или стране назначается адрес класса А — число, которое является первыми 8 битами из 32-битового IP-адреса. Организация свободна использовать остающиеся 24 бита, чтобы обозначать свои
локальные хосты. NIC редко выдает адреса класса А, т. к. каждое число расходует полное 32-битовое пространство. □ Организации среднего размера назначается адрес класса В — число, которое является первыми 16 битами 32-битового IP-адреса. Организация может тогда использовать оставшиеся 16 битов, чтобы обозначать свои локальные хосты. □ Маленьким организациям назначается адрес класса С, который является первыми 24 битами 32-битового IP-адреса. Например, Техасский университет в Далласе (University of Texas at Dallas) классифицируется, как организация среднего размера, и ее LAN был назначен 16-битовый номер 33 134. IP-адреса записываются как серия четырех 8-битовых чисел. Старший байт указывается в начале этой серии. Все компьютеры LAN Техасского университета в Далласе поэтому имеют IP-адрес вида 129.110. XXX.YYY1, где XXX и YYY - числа между 0 и 255. Интернет-приложения Раз существовало семейство протоколов, которое позволяло легкую передачу данных удаленному хосту сети, следующий шаг должен был обеспечить поддержку прикладных программ, которые воспользовались бы преимуществом этих протоколов. Первыми приложениями, работающими с TCP/IP, были две программы, которые раньше TCP/IP возникли для ARPANET: telnet и ftp (см. главу 9). Программа telnet служила (до сих пор) для соединения с другим компьютером в сети, чтобы пользователь мог войти и работать с удаленным компьютером со своего локального компьютера или терминала. Эта особенность была весьма полезной во времена дорогостоящих вычислительных ресурсов. Организация могла и не иметь собственный суперкомпьютер, но пользователи получали к нему доступ с другого узла, telnet позволяет входить в систему удаленно без необходимости физически приближаться к другому узлу. Программа ftp использовалась для передачи файлов между узлами. Несмотря на то, что ftp все еще доступен сегодня, большинство пользователей для этой цели применяют сетевые браузеры или сетевые файловые системы. Изменение архитектуры и переименование Интернета: 1980-е Пока университеты и правительственные агентства использовали ARPANET, распространилось известие о пользе сети. Вскоре к ней подклю 1 129 х 256 + ПО = 33 134.
чились и корпорации. Сначала из-за правительственного финансирования корпорация обязана была заключать некоторый правительственный контракт, чтобы получить право доступа к сети. Через некоторое время это требование стало выполняться все реже и реже. С ростом сети появились проблемы. Как и с локальной сетью, чем меньше любая сеть и количество связанных узлов, тем легче ее администрировать. С ростом сети увеличивается сложность управления ею. Стало ясно, что рост ARPANET скоро опередит способность Отдела защиты (Defense Department) управлять сетью. Степень прибавления новых хостов теперь требовала ежедневных модификации в таблице хостов сети. Кроме того, каждый узел ARPANET должен был загружать новые таблицы хостов каждый день, если он хотел иметь современные таблицы. С другой стороны, число доступных имен хостов истощалось, т. к. каждое имя должно было быть уникальным во всей сети. Служба доменных имен Появилась служба доменных имен (Domain Name Service, DNS). Вместе с Berkeley Internet Name Daemon (BIND), служба DNS предложила иерархию обозначения доменов хостов сети и метод для поддержки требуемой адресной информацией любого пользователя в сети. В новой системе были установлены имена домена верхнего уровня, под которым каждый участок сети мог устанавливать подчиненные домены. Министерство обороны (Department of Defense, DOD) управляло бы доменами верхнего уровня и делегировало управление каждым подчиненным доменом подразделению или организации, которая зарегистрировала домен. Программы DNS/BIND обеспечили метод поиска сетевой адресной информации конкретного хоста для любого участка сети. Давайте посмотрим на реальном примере, как имя хоста превращается в адрес. Один из наиболее популярных доменов верхнего уровня — это сот, так что мы будем использовать его в нашем примере, поскольку большинство знакомо с ним. DOD поддерживает сервер для домена сот. Все подобласти, зарегистрированные в com-домене, были "известны" серверу DOD. Когда некоторый хост сети нуждался в адресе для имени хоста под сот-доменом, он запрашивал сервер имен сот. Если бы вы попытались связаться с snoopy.hp.com, ваш компьютер не знал бы IP-адрес, потому что нет никакой информации в вашей локальной таблице хоста для snoopy.hp.com. Ваш компьютер вошел бы в контакт с сервером доменных имен для сот-домена, чтобы выяснить адрес у него. Этот сервер, однако, "знает" только адрес сервера имен hp.com; нет необходимости знать все в домене. Так как hp.com зарегистрирован в сервере имен сот,
он (сервер сот) может запросить hp.com-сервер имен для получения адреса2. Как только входят в контакт с сервером имен, который имеет полномочия для домена hp.com, адрес для snoopy.hp.com (или сообщение, что хоста не существует) возвращается инициатору запроса. До того момента каждое имя хоста в ARPANET было только именем, подобно utexas для хоста ARPANET в Техасском университете. При новой системе эта машина была бы переименована, чтобы стать членом домена utexas.edu. Однако такое изменение не могло быть сделано всюду и быстро. Поэтому какое-то время был установлен домен по умолчанию .агра. По умолчанию все хосты стали известны под этим доменом (следовательно, utexas изменил свое имя на utexas.arpa). Как только был сделан этот шаг, узлу стало проще войти в сообщество своего "реального” домена, поскольку большинство вовлеченных программ 'понимали" систему доменных имен. Как только сообщество ARPANET приняло эту систему, все виды проблем были решены. Имя хоста должно было быть уникально только в пределах подчиненного домена. Таким образом, если HP имел машину snoopy, то это еще не означало, что никто в Техасском университете уже не мог использовать данное имя, поскольку snoopy.hp.com и snoopy.utexas.edu были различными именами. Еще не было проблемы, пока с сетью были связаны только большие компьютеры, но быстро приближалась эра взрывного роста рабочих станций. Другое преимущество состояло в том, что больше не было нужды поддерживать и обновлять каждый день отдельную охватывающую всю сеть таблицу хоста. Каждый узел хранил его собственные актуальные локальные таблицы хостов и просто запрашивал сервер имен, когда был необходим адрес хоста на другом участке. Путем запроса сервера имен гарантировалось получение наиболее актуальной информации. Домены верхнего уровня, с которыми чаше всего приходится сталкиваться пользователям, перечислены в табл. 10.1. Таблица 10.1. Общие имена областей верхнего уровня Имя Категория biz Деловая сот Коммерческая 2 В протоколах доступны две опции. Первая, когда запрашивающая машина переад-ресуется к ’’наиболее осведомленному" хосту и может делать последующие запросы, пока не получит информацию, которая ей необходима. Другая состоит в том, что исходная машина может сделать единственный запрос, и каждая следующая машина, не имеющая адреса, может делать очередной запрос к наиболее осведомленному хосту от имени первоначального хоста. Это опция конфигурации решающего программного обеспечения домена, и не имеет значения, сколько запросов сделано или какова эффективность запросов.
Таблица 10.1 (окончание) Имя Категория edu Образовательная gov mil Правительственная Военная net Поставщик сетевых услуг org Некоммерческая организация XX Две буквы кода страны Например, LAN в Техасском университете в Далласе было назначено имя utdallas.edu. Поскольку организация получила свой уникальный IP-адрес и имя домена, она может использовать остальную часть IP-номера, чтобы назначать адреса другим хостам в LAN. Вы можете увидеть, какие адреса ваш локальный DNS-сервер возвращает для конкретных имен хостов по вызову утилиты nsiookup, имеющейся в большинстве UNIX-систем. Утилита наиболее полезна для получения адресов компьютеров в вашей собственной сети. Компьютеры в Интернете часто защищены программами межсетевой защиты (брандмауэрами, firewalls), так что полученный адрес не может использоваться непосредственно. Синтаксис nsiookup [имяХоста или адрес] Утилита nsiookup контактирует с локальной службой имен (Name Serv-icce) и запрашивает IP-адрес для данного имени хоста. Некоторые версии также позволяют осуществлять обратный поиск, в котором задается IP-адрес, а пользователь получает имя хоста для него. Если nsiookup запускается без аргументов, происходит вход в диалоговую сессию, в которой можно посылать многократные команды. (Для выхода применяется комбинация клавиш <Ctrl>+<D>.) Однако nsiookup хороша для выяснения, допустимы ли в пределах домена имена доменов или Web-серверы (компьютеры, которые могли бы находиться вне брандмауэров для общего доступа). Вы могли бы увидеть следующий тип вывода от nsiookup: $ nsiookup www.hp.com Server: localhost Address: 127.0.0.1
Non-authoritative answer: Name: www.hp.com Addresses: 192.151.52.217,. 192.151.52.187, 192.151.53.86, 192.6.118.97, 192.6.118.128, 192.6.234.8 $ nslookup www.linux.org Server: localhost Address: 127.0.0.1 Non-authoritative answer: Name: www.linux.org Address: 198.182.196.56 $_ Сначала nsiookup сообщает имя и IP-адрес DNS-сервера, используемого для запроса. В этом случае компьютер, на котором выполняется утилита, также запускает bind. После этого нам предоставляется текущий IP-адрес для имени хоста, который и запрашивался. Если имя хоста не существует или DNS-сервер не может (или не хочет) предоставить адрес, появится что-то вроде этого: $ nsiookup Server: localhost Address: 127.0.0.1 > xyzzy Server: localhost Address: 127.0.0.1 *** localhost can’t find xyzzy: Non-existent host/domain > AD $ _ Дальнейшее развитие Подобно родителю, чей ребенок вырос и нуждается в независимости, Министерство обороны (Department of Defense, DOD) США "уяснило, что его ребенок, ARPANET, должен уйти из дома и быть предоставлен самому себе". Министерство обороны инициировало сеть, как научно-исследовательскую работу для доказательства своей концепции. Сеть стала полезной, так что Министерство продолжило работать над ней и управлять ею. Но по
скольку количество членов сети росло, управление ею забирало все большие ресурсы и приносило меньше и меньше дохода, поскольку к сети подключалось все большее число клиентов, не связанных с DOD. Для Министерства обороны настало время покинуть бизнес управления сетью. В конце 1980-х годов Национальный научный фонд (National Science Foundation, NSF) начал создавать сеть NSFNET. Взяв за основу принцип крупномасштабной организации сети, в то время уникальной и конструируемой, как "магистральная” сеть, к которой могли подключаться другие региональные сети. NSFNET была первоначально предназначена для связи суперкомпьютерных центров. Используя те же самые типы оборудования и протоколы, которые формировали ARPANET, NSFNET обеспечила альтернативную среду с более свободным и легким доступом, чем управляемая государством ARPANET. Большинству людей, кроме вовлеченных программистов и менеджеров, кажется, что ARPANET сегодня мутировала в Интернет. В действительности были созданы подключения к NSFNET (и его региональной сети), а связи ARPANET разорваны. Но из-за разделения соглашений о наименовании и внешнего вида изменение было гораздо менее очевидно для случайного пользователя. Результатом оказалась сеть, которая работала (с точки зрения пользователя) так же, как ARPANET, но она была собрана из намного большего количества корпораций и неправительственных агентств. Очень важно, что эта новая сеть не снабжалась правительственными деньгами, а скорее выживала на частном финансировании от своего использования. Web: 1990-е В 1990-х Интернет вошел во всеобщий обиход. Хотя, соответственно, он вырос, но это был все еще мир, преимущественно принадлежащий компьютерным пользователям и программистам. Нужно было произойти двум событиям, чтобы Интернет появился для ничего не подозревающей публики: продолжающееся быстрое увеличение количества домашних персональных компьютеров (ПК) и одна удивительно хорошая идея. “Убийственное приложение" Опять выбор времени сыграл роль в истории Интернета. Сама сеть росла и использовалась миллионами людей, но все еще не воспринималась широко распространенной. Более искушенные пользователи домашних ПК соединялись с Интернетом через подключение к сети их работодателя или присоединялись к компании, которая обеспечивала доступ в Интернет. Эти компании стали называться поставщиками услуг Интернета (Internet Service Provider, ISP). В начале 1990-х годов существовала только горстка этих по
ставщиков, т. к. лишь немногие признавали, что предоставление доступа к Интернету любому желающему было коммерческим предприятием. Затем появился Mosaic. Это был первый браузер, задуманный проектировщиками программного обеспечения в Национальном центре разработки приложений для супер-ЭВМ (National Center for Supercomputing Applications, NCSA) в Иллинойском университете в Урбана-Чэмпинг (University of Illinois at Urbana-Champaing). C Mosaic пользователь мог получить доступ к информации на других участках Интернета без необходимости использования сложных и неинтуитивных инструментов, которые были популярны в то время (например, telnet, ftp). Браузер Mosaic был (как и вообще все браузеры) приложением, показывающим страницу данных как в текстовом, так и в графическом виде. Он демонстрировал информацию, описанную языком гипертекстовой разметки документа (HyperText Markup Language, HTML). Наиболее революционным аспектом HTML стала гиперсвязь. Это способ связать информацию в одном месте документа с информацией в другой части документа (или вообще в другом документе). Разрабатывая страницу в HTML, можно было показывать информацию и добавлять ссылки на другие части страницы или страницы на других участках, которые содержали родственную информацию. Этот подход создавал документ, по которому можно было "осуществить навигацию", чтобы позволить пользователям получать конкретную информацию без необходимости читать или искать документ последовательно, как это было принято в то время. Почти сразу серверы осуществляли "прыжки" по Интернету и предоставляли информацию, которая могла быть представлена в Mosaic. Теперь вместо поддержки анонимного FTP-узла узел мог выдавать свою общедоступную информацию в гораздо более презентабельном формате. Анонимные FTP-участки обычно требовали, чтобы обращающиеся к ним пользователи знали, что хотят найти или, в лучшем случае, получали README-файл, который помогал им отыскать желаемое. Взаимодействуя с сервером, который предоставлял HTML, пользователи могли просто наводить указатель, щелкать мышью и выбирать страницу, содержащую искомую информацию. Конечно, описанное "волшебство" произошло не автоматически: на каждом узле, который поддерживал произвольную информацию для внешних пользователей, необходимо было установить сервер и форматировать информацию. Но это не требовало значительно большей работы, чем предоставление информации через анонимный FTP. Вначале, поскольку люди переключались с предоставления информации через FTP-инструменты на использование Web-инструментов, обе альтернативы были сопоставимы в терминах требующихся усилий, для того чтобы сделать доступными данные. Ввиду
того, что узлы стали более сложными, выполняемой работы прибавилось, но выигрыш от представления также увеличился. Некоторые разработчики, вовлеченные в создание ранних версий Mosaic, позже сформировали компанию Netscape Communications, Inc., где они использовали опыт, накопленный во время предыдущей разработки браузера, и создали Netscape — следующее поколение браузеров. С тех пор браузеры во главе с Netscape и Microsoft Internet Explorer стали изощренными приложениями, которые каждый год открывают новые возможности как просматривающему, так и публикующему. Web против Интернета Смысл слова "Web" различен для людей в разных контекстах и вызывает так много путаницы. Перед Mosaic и другими браузерами был только Интернет — просто всемирная сеть компьютеров. Она сама по себе может быть изображена схематически как паутина (web) связей сети. Когда Mosaic, используя HTML, обеспечивал возможность перепрыгивать из одного места Интернета в другое, возникла иная концептуальная "сеть" — "Web". Мало того, что мой компьютер связан с несколькими другими, формирующими сеть (Web), но теперь мой HTML-документ также связан с несколькими другими (через гиперсвязи), создавая виртуальную паутинную сеть информации. Эта паутина (web) подарила термины "Web-страница" и "Web-просмотр" (Web browsing). Когда кто-то говорит сегодня о Web, они обычно подразумевают непосредственно Интернет. С другой стороны (особенно, когда термин представлен строчными буквами), они могут подразумевать сеть информации, доступной в Интернете. Хотя первоначально этот способ не имелся в виду, термины "Web" и "Интернет" в настоящее время часто используются как взаимозаменяемые. Однако изначально написание "web" строчными буквами относится к информации, которая доступа в инфраструктуре Интернета. Достижимость Несколько ISP возникли в то время, когда Web лишь появлялся. Как только концепция Web получила ясное представление, оказалось, что, возможно, каждый захочет войти в Интернет. В то время, как электронная почта была и остается одной из наиболее известных услуг, предоставляемых доступом к Интернету, Web-просмотр стал более "визуальным", что привлекло к нему широкую публику. Неожиданно обычный пользователь обнаружил пользу (или, по крайней мере, забаву) от подключения к Интернету; в основном это не были компьютерные идиоты. Что бы там ни было, Интернет изменялся быстро. Больше людей, больше информации и больший спрос заставили Интернет расцве
тать в применении и доступности. Конечно, с увеличением количества пользователей появляются более неопытные люди, и чаще возникает перегрузка сети; популярность — это всегда палка о двух концах. Другим фактором, позволяющим обычной публике иметь доступ к Интернету, было геометрическое увеличение скоростей модема. В то время как крупные компании имеют прямое подключение к Интернету, большинство индивидуальных соединений — это коммутируемые связи по домашним телефонным линиям, требующим модемов. Когда высшая скорость модема стала 2400 байт/с (bps), которая была еще совсем недавно предельной, загрузка Web-страницы выполнялась невыносимо медленно. Поскольку скорости модема увеличились до 100 Кбайт/с, и быстродействующие цифровые линии стали экономичными для домашнего использования, оказалось гораздо рациональнее иметь больше одного терминала, используя коммутируемую связь. В среднем плата за индивидуальные соединения составляет от $10 до $60 в месяц, в зависимости от скорости передачи данных и использования служб. Счет за интернет-обслуживание, которое является сопоставимым со счетом кабельного телевидения или телефонным счетом, является приемлемым; обычная публика, вероятно, не принимала бы счет, который был бы на порядок выше других счетов. Изменения в Интернете По мере того как общественность играла все большую и большую роль в развитии Интернета, часть первоначального духа сети изменилась. Интернет изначально был разработан “только чтобы доказать, что это могло быть сделано", а не как объект создания прибыли. Предполагалось, особенно в дни ARPANET, что информация и программное обеспечение должны быть свободно доступными. Многое из раннего кода, который управлял Интернетом (комплект IP-протокола и инструментарий типа ftp и telnet), было отдано его авторами, изменено сторонними разработчиками и возвращено назад для "большей пользы". Это было необходимо для роста и процветания Интернета. Однако сегодня бизнес присутствует и в Интернете, и немало информации доступно за дополнительную плату. Нельзя сказать, что все зарабатывают деньги или это плохо. Но такой поворот принес существенное изменение в культуру Интернета. Интернет нуждался "в свободном духе" истоков, которые он когда-то имел. Но теперь общество в основном использует ресурсы сети, и естественно, что Интернет изменяется в соответствии с увеличивающимся влиянием на него экономики. Размещение рекламы в сети стало обычным делом. К тому же некоторые узлы предлагают пользователям подписаться на конкретные ресурсы (за дополнительную плату), чтобы иметь возможность подключения и
получения информации. Торговля через Интернет3 (например, заказ товаров и услуг, включая диалоговую информацию), как ожидается, продолжит бурно развиваться и в будущем. Безопасность Существует масса книг по безопасности Интернета (например, [Cheswick 1994]). Поскольку в Интернете существует коммерческая деятельность, в будущем потребность в безопасности и опасения по поводу безопасности действий станут только увеличиваться. Вообще, отдельная передача данных предусматривает обеспечение собственной безопасности. Другими словами, если вы делаете закупку, продавец, вероятно, будет использовать безопасные протоколы, чтобы получить подходящую информацию от вас (например, номер кредитной карточки). Существуют четыре главных рискованных действия при использовании Web-сервера: копирование информации, модификация информации, заимствование прав и отказ от обслуживания. Услуги шифрования могут предотвращать копирование или изменение информации. Пользовательская осведомленность может помочь минимизировать заимствование прав. Наиболее "страшное" для пользователей рискованное действие (и, кстати, реже всего с ним возникают проблемы) — это копирование информации, которая путешествует через сеть. Интернет — общественная сеть, и поэтому информация, которая послана "в явном" (не зашифрованном) виде, в теории, может быть скопирована кем-то между отправителем и получателем. Но поскольку данные разделяются на пакеты, которые могут прийти по одному или разным маршрутам к месту назначения, зачастую нецелесообразно пытаться захватить (подслушать) полезную информацию. Модификация информации, которая находится в процессе доставки, имеет ту же самую проблему, как подслушивание, с дополнительной опасностью фактического осуществления модификации. Хотя это можно сделать, но данная задача очень сложна и обычно не стоит усилий. Заимствование прав пользователя через интерфейс входа в систему или сообщение электронной почты является, вероятно, самым обычным типом нарушения безопасности. Пользователи часто не защищают свои пароли. Как только некто узнает чье-нибудь имя пользователя и пароль, он может войти в систему и иметь те же права и привилегии, как и законный владелец. К сожалению, тривиально послать сообщение электронной почты с подделанными заголовками, чтобы казалось, что сообщение пришло от другого пользователя. Обычно внимательное изучение способно подтвердить подлинность заголовка, но может и привести к неразберихе, особенно если сообщение получает неопытный пользователь. Можно также играть роль 3 Так называемая электронная коммерция. — Ред.
другого хоста сети, требуя использовать тот же самый сетевой адрес. Ситуация известна, как spoofing (имитация соединения)’, это не тривиальное упражнение, но опытный сетевой программист или администратор могут это осуществить. Нападение для отказа обслуживания происходит, когда внешний источник посылает огромное количество информации серверу с целью его перегрузки и подрыва способности выполнять свою работу. Столкнувшись с такими трудностями, сервер становится непригодным для использования так, что никто не может его использовать. Авторское право Одна из самых больших проблем в развитии .информационного обмена в Интернете — сохранение авторского права (copyright). В традиционных печатных средствах информации требуется время, чтобы воспроизвести информацию, и доказательство этого воспроизводства будет существовать. Другими словами, если бы я переиздал чей-то текст без разрешения автора, созданная копия доказала бы противозаконное действие. В Интернете информация может быть воспроизведена буквально со скоростью света. За время, требуемое для копирования файла, авторское право может быть нарушено совершенно незаметно. Цензура В любой среде, где может быть распределена информация, будут существовать люди, желающие ограничить доступ к данным. Если информация принадлежит мне, и я хочу ограничить доступ к ней посторонних, это называется моим правом на секретность. Если информация принадлежит кому-то другому, а я хочу ограничить доступ к ней, это называется цензурой. Нельзя сказать, что цензура плоха. Как и во многом, проблема заключается не в идее, а в ее интерпретации. Цензура в Интернете, мягко говоря, — сложная проблема. Правительства и организации могут пробовать ограничить отдельные виды доступа к некоторым видам материалов (часто из лучших намерений). Но поскольку Интернет является всемирным ресурсом, такие действия не всегда правомерны. Как, например, закон в Нэшвилле (Nashville) может применяться к Web-серверу в Сиднее (Sydney)? Кроме того, даже если Web-сервер делает что-нибудь противозаконное, кто будет преследовать по суду корпус (сервера)? Дезинформация Безусловно, неприятна ситуация с нарушением авторских прав или публикацией оскорбительного материала, но гораздо больше раздражения вызывает заведомо ложная информация. Так как никто не имеет полномочий на одобрение пли подтверждение правильности информации, размещаемой в
сети, любой может опубликовать все, что угодно. Это прекрасно для свободы слова, но люди имеют тенденцию верить информации, которую они видят в печати. Не могу сказать, сколько я слышал историй о людях, действующих на основе информации, найденной в сети, которая вводила в заблуждение или была неправильной. Смогли бы вы доверять слухам? Это равносильно доверию информации, которую вы находите в сети, по крайней мере, когда вы не уверены в источнике. Приемлемое использование Многие ISP проводят политику приемлемого использования, которой вы должны придерживаться, чтобы получать их услуги. В будущем эта политика поможет решить многие из интернет-проблем, начиная с момента его создания. Большинство политик приемлемого использования требуют, чтобы пользователи вели себя достойно и не делали ничего противозаконного или оскорбительного в отношении других пользователей, в том числе отправку "бесконечной" электронной почты, копирование чужих файлов и т. д. Существует осознанная анонимность4 пользователей Интернета. Если вы посылаете мне электронную почту, с которой я не согласен, возможно, для меня окажется затруднительным выяснять с вами отношения лично. Я мог бы успокоиться, ВОПЯ НА ВАС ПО ЭЛЕКТРОННОЙ ПОЧТЕ5. Благодаря этому люди склонны вести себя так, как никогда бы не вели при личном общении. Поскольку Интернет и его пользователи растут, эта проблема должна уменьшиться. Современный Интернет В прошлом сеанс связи в Интернете предполагал хранение выполненных команд и обращений к FTP-узлам. Нужно было хранить сведения о ресурсах и методы организации доступа к ним. Сегодня почти все, к чему вы получаете доступ в Интернете, базируется на Web, т. е. доступно через Web-браузер. Существует много Web-браузеров, но самый обычный для UNIX-компьютеров — это Netscape, написанный компанией Netscape Communications. Подобно любой другой программе с оконным интерфейсом, Web-браузер имеет кнопки, меню, область управления и область вывода. Пользо- 4 Я пишу здесь "осознанная", потому что фактически можно найти нужного человека, если вы приложите для поиска достаточно усилий. Даже люди, посылавшие сообщения с угрозами через услуги "анонимной электронной почты", были найдены с помощью правоохранительных органов. ISP сотрудничают с властями, когда выписываются ордера на арест! 5 Текст прописными буквами обычно интерпретируется, как крик. Это не относится к тем немногим пользователям, которые все еще используют компьютеры или терминалы, способные генерировать лишь прописные символы.
ватель печатает или выбирает Web-адрес, а браузер посылает запрос на указанный компьютер сети (локальной сети или Интернета) и отображает информацию в окне. Не будем подробно останавливаться на том, как использовать Netscape или любой другой браузер, поскольку самостоятельное испытание его возможностей — лучший способ научиться работать с Web-обозревателем. Вообще, все браузеры имеют место для ввода Web-адреса, способ посмотреть историю обозревателя, Web-адреса, которые вы посетили раньше, и кнопки, чтобы помочь вам перемещаться назад или вперед по этому списку. Большинство браузеров позволяет запоминать или печатать информацию и сохранять Web-адреса в списке закладок так, чтобы пользователь смог возвратиться к узлу в будущем, без необходимости помнить и повторно печатать его адрес. URL Web-страница отображается в окне браузера после того, как пользователь напечатает определенный Web-адрес. Адрес называется универсальным локатором ресурсов (Uniform Resource Locator, URL). Например, URL для узла сети Prentice Hall это http://www.prenhall.com Компонентами URL являются протокол, использующийся для получения Web-страницы, интернет-адрес или имя компьютера, на котором находится страница, необязательный номер порта и необязательное имя файла. В случае URL для Prentice Hall номер порта и имя файла были опущены, и браузер предположил порт 80 и затребовал корневой (/) файл в рамках дерева документов Web-сервера (не то же самое, что корень файловой системы UNIX). Чаще всего указывается протокол HTTP (HyperText Transport Protocol, Протокол транспортировки гипертекста), который является протоколом для организации доступа к HTML-информации. Зашифрованный канал (защищенный HTTP, HTTPS) используется для страниц или деловых операций, вовлекающих конфиденциальную информацию (например, номера кредитных карточек). Большинство Web-браузеров также поддерживает FTP-протокол, который дает базирующийся на GUI способ доступа к анонимным FTP-узлам через браузер пользователя (см. главу И). Если протокол не определен, большинство браузеров предположат, что перед URL идет http://, так что вы можете опустить это при ручном наборе адреса. Как правило, при загрузке определенной Web-страницы в браузер появляется приятно отформатированная страница, содержащая информацию и некоторый выделенный текст или значки (гиперсвязи), по которым можно щелкать для перехода на другие связанные Web-страницы, возможно, принад
лежащие иной части данного узла или другой организации. Гиперсвязь — фундаментальная концепция в сердце Всемирной паутины. Она "плетет" сеть информации, где каждая страница содержит ссылки на другие страницы. Так как много содержащих связанную информацию Web-страниц имеют связи друг с другом, результат — "сеть" связей через весь Интернет. Web-поиск Итак, имея браузер, можно получить доступ к Web-узлам. Но как обнаружить необходимую информацию? Я мог бы назвать некоторые из тысяч известных мне узлов, содержащих интересные сведения, но к моменту издания этой книги многие из перечисленных узлов могут быть больше недоступны. Вместо того чтобы дать вам рыбу, я лучше покажу, как эту рыбу ловить, чтобы вы могли найти самостоятельно то, в чем, возможно, нуждаетесь. В Интернете существует достаточное количество поисковых Web-машин. Это узлы, которые строят и непрерывно обновляют свою базу данных Web-страниц и ключевых слов, относящихся к этим страницам. Обычно их услуги бесплатны; страницы, которые. показывают результаты поиска, как правило, содержат рекламу, являющуюся источником финансирования для поддержки работы узла. Вот некоторые поисковые машины: □ www.altavista.com; □ www.excite.com; □ www.google.com; □ www.infoseek.com; □ www.lycos.com; □ www.webcrawler.com; □ www.yahoo.com. У каждого есть собственные предпочтения. Ваш фаворит может зависеть от скорости ответа, расположения информации, качества результата поиска, легкости, с которой вы можете строить запрос, или некоторых других услуг, которые может предлагать узел. Сегодня в Интернете существует так много узлов, что самая большая проблема использования поисковой машины состоит в построении достаточно конкретного запроса таким образом, чтобы пользователь не получил тысячи ссылок, большинство из которых будут не нужны. Если вы пытаетесь найти "обыкновенную компанию", можно попытаться угадать адрес ее узла. URL-форма вроде http://wwxompanynamexxm работает чаще, чем не работает. 15 Зак. 786
Поиск пользователей и доменов NIC обеспечивает доступ к базе данных зарегистрированных интернет-пользователей и интернет-доменов. Пользовательская база данных не содержит список всех интернет-пользователей; точнее сказать, она перечисляет только пользователей, которые зарегистрировались в NIC. Обычно это системные администраторы и администраторы сети, которые управляют информацией домена для узла. Web-узел NIC — это http://www.internic.net, а его Web-страница направит вас на ресурсы, которые можно использовать, чтобы выполнить все виды поиска для домена и интернет-информации. Факторы, влияющие на будущее использование Продолжит ли Интернет расти, как это происходило до настоящего времени? Раньше единственный фактор, ограничивающий его использование, состоял в его недоступности. Эта проблема была решена ISP и быстродействующими коммутируемыми и широкополосными линиями. Сегодня миллионы людей получают доступ к Интернету каждый день. Одни используют его, чтобы посылать и получать электронную почту и читать информационные бюллетени. Другие обращаются к нему для поиска информации всех видов на корпоративных и образовательных Web-серверах. Многим нужен Интернет для заказа товаров. Ввиду того, что удобство становится приоритетом в беспокойной жизни людей, доступ в глобальной сети может оказаться наиболее эффективным путем обнаружения информации даже на локальном уровне. Благодаря наличию миллионной аудитории рекламодатели стремятся разместить свои сообщения на новом рынке. Поскольку Web-серверы и приложения стали "серьезнее”, рекламодатели будут направлять свои сообщения только заинтересованным пользователям. Это является преимуществом и для торговца, и для клиента, т. к. помогает уменьшить информационную перегрузку, от которой мы уже страдаем в наши дни. Обзор главы Перечень тем В этой главе мы рассмотрели: □ историю Интернета; □ протоколы, используемые в Интернете; □ приложения, которые предоставляют доступ к Интернету; □ службу DNS, используемую в Интернете; □ Всемирную Паутину, Web-просмотр и Web-поиск.
Контрольные вопросы 1. Почему NIC выделяет очень мало адресов класса А? 2. В чем заключается различие между HTTP- и HTTPS-протоколами? 3. Если бы вы искали Web-страницу Sun Microsystems, какой адрес вы ввели бы сначала? 4. Каковы два наиболее существенных различия между TCP- и UDP-протоколами? Упражнения 10.1. Выберите некоторые известные вам компании и попробуйте получить доступ к их Web-страницам с URL, которые вы составляете в формате www.company.com. [Уровень: легкий.] 10.2. Соединитесь с Web-узлом www.intemic.net и исследуйте его, чтобы выяснить, какие виды услуг обеспечивает NIC. Посмотрите информацию об имени вашего домена (или имени домена вашего ISP). [Уровень: средний.] Проект Сделайте вид, что вы хотите купить самый последний компакт-диск вашей любимой группы, но не знаете об интернет-узле, который продает их. (Есть несколько.) Проведите Web-поиск с несколькими ключевыми словами (например, музыка, компакт-диск, закупка и цмя группы). Посмотрите, сможете ли вы найти способ приобрести компакт-^иск, и исследуйте другие узлы, которые попали в результаты поиска, чтобы выяснить, почему они удовлетворили ваш запрос. Полезно знать, как в следующий раз сделать лучший поиск. [Уровень: средний.]

Глава 11 Пользовательские интерфейсы Мотивация Фактически все UNIX-компьютеры используют графические оболочки, поддерживающие оконный интерфейс. Большинство этих систем основано на X Window System, разработанной в Массачусетском технологическом институте (Massachusetts Institute of Technology, MIT). Дружественный интерфейс X Window System облегчает работу в любой оконной системе UNIX. Предпосылки Так как X Window System пользуется преимуществами некоторых возможностей сетевой организации UNIX, до чтения данной главы изучите главу 9. Задачи В настоящей главе будут представлены краткий обзор X Window System и методы использования некоторых ее наиболее общих функций. Вы узнаете, как система может улучшить вашу производительность. В других книгах (например, [Quercia 1993]; [OSF 1992] и [Fountain 2000]) тщательнее рассматриваются детали этой системы. Наша цель состоит в предоставлении информации в достаточном количестве, чтобы пользователь мог начать работать с X Window System. Изложение Сначала будет представлена краткая история систем с оконным интерфейсом. Затем мы исследуем X Window System и выясним, что она напоминает, как работает, рассмотрим команды UNIX, применяемые в ней.
Утилиты В данной главе рассматриваются следующие утилиты: xbiff xhost xterm xclock xrdb Общие сведения На заре развития UNIX-систем символьный терминал был единственным интерфейсом к системе. Пользователь регистрировался в системе и выполнял всю работу в отдельной, основанной на обмене символами, сессии. В лучшем случае пользователь имел терминал с "разумными” способностями курсора, обеспечивающего полноэкранные манипуляции (т. е. курсор позволял экранное редактирование текста или отладку). Но чаще всего в его распоряжении был просто строковый терминал, с которого можно было вводить строку текста (команду) и получать обратно несколько строк текста в ответ. И пользователь был счастлив иметь его вместо перфокарт, которые применялись ранее! Графические пользовательские интерфейсы По мере того как компьютерные системы становились более сложными, растровые дисплеи (где каждая частица на экране может быть засвечена или затемнена, а не просто отображается символ в определенном месте) позволили пользовательским интерфейсам стать более изощренными. Появился графический пользовательский интерфейс (Graphical User Interface, GUI), часто произносимый как "gooey". Первым компьютером с полуизвестным'GUI был Xerox STAR. Он был исключительно системой обработки текста и впервые позволил использовать напоминающие страницу с завернутым уголком пиктограммы (значки), представляющие документы. Xerox STAR имел пиктограммы для папок, документов и принтеров на рабочем столе (экране), а не интерфейс командной строки, который был нормой в то время. Возможность щелкнуть на значке документа для его редактирования и перетащить его на значок принтера для распечатывания вместо необходимости помнить, какие команды соответствуют этим действиям, была в то время революционной. Некоторые из инженеров Xerox перешли в Apple Computer и работали над компьютером Apple Lisa, что привело к разработке Macintosh. UNIX также "вошел в игру" GUI. Сотрудники Sun Microsystems внедрили Suntools в ранней версии SunOS (предшественника Solaris, текущей версии Sun UNIX). Suntools предоставляла многотерминальные окна на одном эк
ране с функциями вырезания (копирования) и вставки. Сначала было несколько приложений, которые предоставляли реальные функциональные возможности GUI. Измеритель производительности мог графически демонстрировать статистику производительности системы вместо того, чтобы показывать ее в виде чисел в таблице. Программа mailtool предоставляла возможность читать электронную почту при помощи иной программы, нежели традиционная программа /bin/maii. Но в традиционных системах с оконным интерфейсом приложение могло показывать информацию только на экране того компьютера, на котором приложение работало. Очередной шаг в развитии был еще впереди. X Window System В 1984 году Массачусетский технологический институт (Massachusetts Institute of Technology, MIT) выпустил X Window System. Понимая пользу систем с оконным интерфейсом, но будучи неудовлетворенными тем, что предоставляли UNIX-продавцы, студенты в MIT с активностью, сопоставимой с движением компании BSD1 из Беркли (Berkeley), намеревались написать собственную систему с оконным интерфейсом. Первоначально компания Digital Equipment Corporation помогла финансировать проект Athena, откуда X начала свое шествие. Революционная идея, стоящая за X Window System, которая и по сей день является конкурентоспособной, — это различие между функциями клиента и сервера в процессе рисования изображения на компьютерном экране. В отличие от большинства систем с оконным интерфейсом, X определяется в соответствии с протоколом сети, заменяющим традиционный интерфейс вызова процедуры. Таким образом, вместо простого наличия приложения, рисующего изображение непосредственно на экране, как делали предыдущие системы, X Window System разделяет две функции на части. Х-сервер заботится о рисовании и управлении содержимым компьютерного растрового дисплея, а также связью со всеми клиентами, которые желают рисовать на экране. Х-клиент не рисует непосредственно на экране, но связывается с Х-сервером, работающим на компьютере, где находится экран для графического вывода. Позволяя этому взаимодействию между двумя процессами существовать на одном компьютере или через сетевое соединение между двумя процессами на различных машинах, неожиданно пользователь получает возможность вывода графики на другом экране. Это открывает дверь к новым возможностям (также как решение проблемы безопасности). X Window System часто упоминается просто как ”Х" или "XI Г, что относится к ее самой современной версии. Пока авторы писали эту книгу, XII находилась в своей шестой версии; следовательно, полное название — это 1 Berkeley Software Distribution. — Ред.
ходилась в своей шестой версии; следовательно, полное название — это X11R62. Для самой последней информации о X Window System см. Web-узел Open Group's X.Org Consortium http://www-x.org. Х-серверы X-cepeep стартует и "занимает" растровый дисплей на компьютерной системе. Это может случиться автоматически, когда пользователь входит в систему или выполняет команду запуска Х-сервера, в зависимости от реализации. Обычно во время старта Х-сервера один или два Х-клиента также запускаются. Х-клиенты — это программы, которые взаимодействуют с пользователем через один или несколько Х-серверов. Должна стартовать некоторая программа, что обеспечит пользовательский доступ к системе. (Экран, управляемый Х-сервером, но не выполняющий ни какое приложение, ничего не позволит делать на нем.) Терминальное окно и диспетчер окон — типовые программы, которые можно запустить. В системах, которые не запускают Х-сервер во время входа, обычно существует команда, подобная xinit или xstart, которую пользователь может либо ввести вручную, либо добавить в свой входной скрипт или скрипт профиля так, чтобы он запускался автоматически при входе в систему. Г еометрия экрана Разметка экрана называется геометрией. Растровый дисплей имеет определенный размер, измеряемый в пикселах — точках, которые могут быть установлены в состояние "включено" или "выключено" (белый или черный) или в некоторое цветовое значение. Маленький экран может быть 600x480 пикселов (типичный PC-монитор с низкой разрешающей способностью). Больший экран может быть 1280x1000 пикселов или даже больше для графических экранов с очень высоким разрешением. Геометрия экрана определяется либо путем ссылки на определенную позицию на экране (например, 500x200 пикселов), либо путем ссылки на позицию относительно угла экрана. Позиция +0+0 — это верхний левый угол экрана, а —0—0 — это нижний правый угол (—0+0 — это верхний правый, +0—0 — нижний левый). Поэтому, +500+500 означает 500 пикселов от верхнего левого угла экрана в обоих направлениях по осям X и Y. Мы рассмотрим примеры, когда будем обсуждать Х-клиентов. 2 XII Release 6. — Ред.
Безопасность и авторизация Возможно, вы думаете, что ввод текста и отображение его на экране компьютера, подключенного к вашей сети, вряд ли могут привести к возникновению проблем безопасности. Ведь ввод/вывод на Х-сервер — это всего лишь ввод и вывод. Разрешение на запись на Х-сервере также предоставляет возможность запросить у системы текущую копию экрана или даже от входа с клавиатуры. X Window System имеет систему безопасности, встроенную в Х-сервер. Это не высококлассная система, но она достаточна, чтобы воспрепятствовать случайному сыщику в получении неправомочного доступа. По умолчанию Х-сервер, работающий на любой компьютерной системе, позволяет только Х-клиентам на этой же самой системе общаться с ним. Х-сервер не принимает подключений от ’’иностранных” Х-клиентов без осведомления, кто они. Это заставляет конфигурацию по умолчанию Х-сервера быть очень похожей на обычную систему с графическим оконным интерфейсом, в которой лишь приложения, выполняющиеся на данном компьютере, могут писать на его экран. Чтобы воспользоваться преимуществом сетевых функций X Window System, вы должны разрешить внешний доступ. Для этого служит утилита xhost (X-клиент). • Синтаксис xhost [+|-][имяХоста] Утилита xhost разрешает или запрещает доступ к Х-серверу в системе. Без аргументов утилита печатает свои текущие установки и список хостов (если есть), которые имеют доступ. Специфицируя только +, можно разрешить доступ всем хостам; определяя только -, можно запретить доступ всем хостам. Когда имя хоста определено после + или -, соответственно предоставляется или запрещается доступ этому хосту. Например, xhost +bluenote позволит Х-клиентам, работающим на компьютере bluenote, писать на дисплей системы, в которой работала утилита xhost. Позже после выполнения работы можно запретить доступ с командой xhost -bluenote В безопасной среде, где нет опасений по поводу записи с другой системы на пользовательский дисплей, можно позволить любому Х-клиенту в сети писать на ваш дисплей командой xhost +
А закрыть доступ от всех Х-клиентов при помощи xhost Диспетчеры окон Возможность выводить на экран в действительности не очень полезна, если пользователь лишь наблюдает появление и пропадание окон, но не может с ними ничего делать. Как раз в такой ситуации нужны диспетчеры окон. Диспетчер окна — программа (Х-клиент), которая взаимодействует с X-сервером, клавиатурой и мышью в системе. Он обеспечивает интерфейс для пользователя, чтобы давать инструкции Х-серверу об операциях с окнами. Хотя диспетчеры окон обычно работают на том же самом компьютере, где существует дисплей, которым они управляют, но это необязательно. Если вы имеете специальный диспетчер окна X, который работает только на одном определенном типе компьютера, допустимо установить его для управления вашей рабочей станцией с удаленного компьютера. Конечно, есть внутренние проблемы. Например, что произойдет, если удаленный компьютер, выполняющий диспетчер окна, или сеть испортились? Вашим X-сервером нельзя было бы управлять, потому что он не мог бы взаимодействовать с диспетчером окна, и ваши клавиатура и мышь, вероятно, не будут отвечать (по крайней мере, должным образом) на ваш ввод. Но факт, что система могла бы все еще работать, является гарантией гибкости архитектуры X Window System. Одна из важных особенностей, предоставляемых диспетчером окна, —- это "облик и ощущение" рабочего стола. Облик и ощущение интерфейса окна зависят от приложения, создающего окно. В то время как все диспетчеры окон предоставляют похожую функциональность, вид каждого может сильно варьировать. Фокус Самая важная работа диспетчера окна — это поддержка фокуса окна. Термин используется, чтобы характеризовать, какое окно в настоящее время выбрано или активно. Если пользователь осуществляет ввод с клавиатуры, окно с фокусом — это место, куда данные будут посланы. Фокус позволяет перемещаться из одного окна в другое и выполнять разнообразные действия в различных окнах. Вообще, окно с фокусом имеет отличную от других окон границу, хотя его интерфейс может быть сконфигурирован так, чтобы не отличаться в этом отношении. Фокус окна настраивается в зависимости от предпочтения пользователя: выбрана граница окна или строка заголовка или указатель мыши перемещен на окно.
Запуск программы Большинство диспетчеров окон предусматривает наличие выпадающего меню, которое может быть настроено для разного запуска часто применяемых приложений. Например, если указатель мыши находится на корневом окне (рабочий стол непосредственно, а не прикладное окно) и пользователь щелкает и удерживает кнопку мыши, то большинство диспетчеров окон выведет меню. Список команд меню обычно включает функции, подобные старту нового терминального окна или выходу из диспетчера окна. Набор команд изменяется от одного диспетчера окна к другому и может быть в большой степени настроен. Кнопки мыши могут быть настроены на выводы различных списков. На рис. 11.1 приведен пример окна. Рис. 11.1. Окно и выпадающее меню корневого окна Открытие и закрытие окон Диспетчер окна также заботится об отображении активных окон и расположении значков. Если вы открываете новое терминальное окно и заканчиваете с ним работать, но не хотите удалять его, потому что, возможно, оно понадобится позже, то можете закрыть3 терминальное окно. Диспетчер окна создаст значок на рабочем столе, ассоциированный с программой 3 Это соответствует сворачиванию окна в Windows. — Ред.
терминального окна, который не займет много места. Вы будете видеть его и можете щелкнуть (или щелкнуть дважды, в зависимости от используемого диспетчера окна) по нему позже, чтобы восстановить окно (повторно открыть). Сама программа будет работать, в то время как ее окно представлено значком. Можно убедиться, что сам Х-сервер ничего не знает об этой функции, если в какой-либо ситуации вы удалите диспетчер окна. (Если вы сделаете это, все ваши значки снова откроют окна на всем пространстве вашего экрана!). На рис. 11.2 представлен рабочий стол с открытым окном и значками. Рис, 11,2. Рабочий стол с открытым окном и значками Выбор диспетчера окна Для Х-серверов доступно много различных диспетчеров окон. Большинство основано на стандарте графической оболочки Motif, разработанной Open Software Foundation (OSF). Некоторые диспетчеры окон имеют общее происхождение от первоначального диспетчера окна Motif. Многие реализуют виртуальный рабочий стол, который расширяет область рабочего стола за пределы области реального участка на экране. Диспетчер окна в этом случае помогает управлять отображением определенной области большого виртуального рабочего стола.
К часто встречающимся диспетчерам окон относятся: О dtwm диспетчер окон рабочего стола, часть общего окружения рабочего стола (Common Desktop Environment, CDE), подобен vuewm, но поддерживает виртуальный рабочий стол; О fvwm бесплатный виртуальный диспетчер окна, написанный Робертом Нэйше-ном (Robert Nation), который стал очень популярным в сообществе Linux; О kwm диспетчер К Window, используемый с KDE (К Desktop Environment); □ mwm диспетчер окна Motif, исходный диспетчер окна; □ olwm/olvwm диспетчер окна OpenLook фирмы Sun Microsystems и диспетчер виртуального окна OpenLook; Л twin/tvtwm диспетчер" окна Тома и диспетчер виртуального окна Тома, написанные Томом ла Стрэнджем (Tom LaStrange), чтобы исправить некоторые из функций, которые он не любил в Motif; □ vuewm диспетчер окна VUE фирмы Hewlett-Packard. Ради простоты будем рассматривать диспетчер окон Motif в качестве образца с общими характеристиками для всех этих диспетчеров. Motif предусматривает дополнительные компоненты окна, о котором приложение (X-клиент) не должно волноваться. Например, Motif рисует границу вокруг окна, которая может быть указана мышью, чтобы изменить фокус окна, перемещает (тянет) окно или изменяет его размер. Граница окна содержит заголовок и кнопки, позволяющие перемещать окно, устанавливать размеры, минимизировать (заменять значком), разворачивать окно на полный экран или удалять. Виджеты Виджет (widget) — термин, используемый для того, чтобы описать каждый отдельный компонент Х-окна. Кнопки, границы и полосы прокрутки — все это виджеты. Каждый Х-инструментарий может определять свой собственный набор виджетов. Так как мы фокусируемся на окружении Motif, то бу
дем интересоваться лишь его набором виджетов, который предоставляется прикладной программе через программный интерфейс приложения (Application Programming Interface, API) Motif. Меню Команды меню обеспечивают GUI доступ к функциям, предоставляемым приложением. Часто эти функции не связаны непосредственно с содержимым конкретного окна. (Скорее, они выполняют действия, подобные открытию файлов, установке опций и выходу из программы). Команды меню находятся наверху окна, как показано на рис. 11.3. Рис, 11,3, Выпадающее меню Командные кнопки Командные кнопки могут быть размещены любым способом, удобным для приложения. Типичный пример — кнопки ОК и Cancel диалогового окна (дополнительное окно, которое появляется с новой информацией или запросом к пользователю). На рис. 11.4 представлено типичное диалоговое окно.
Рис, 11,4, Диалоговое окно с командными кнопками Флажки и переключатели Флажки и переключатели — это генерирующие ввод виджеты. Флажки служат для установки или сброса выбора. Если флажок отмечен, он указывает "истина", "да" или "присутствует" в зависимости от контекста приложения. Переключатели —- это набор взаимно исключающих альтернатив. Когда одна выбрана, любые другие, выбранные ранее, сбрасывают отметки (подобно кнопкам в автомобильном радио). Оба типа виджетов показаны на рис. 11.5. Рис, 11,5, Флажки и переключатели Полосы прокрутки Полосы прокрутки позволяют пролистывать назад и вперед содержимое окна или его части. Прокрутка полезна, когда отображается много текста, но до
ступна маленькая область экрана так, что будет помещаться не весь текст. Полосы прокрутки могут быть или горизонтальными, или вертикальными. Вертикальные полосы обычно расположены справа в окне (рис. 11.6). Горизонтальные полосы обычно — внизу окна. Рис. 11.6. Терминальное окно с вертикальной полосой прокрутки Функции диспетчера окна Motif Операции, выполняемые с окнами и значками на рабочем столе под управлением диспетчера окон Motif, подобны функциям других диспетчеров X Window,System, хотя некоторые детали могут слегка изменяться. Вызов корневого меню Корневое меню (root menu) содержит основные операции, необходимые для управления Х-сессией. По умолчанию список включает команды запуска терминального окна, перемещения фокуса на другое окно и выход из диспетчера окон. Корневое меню можно настроить, чтобы добавить в него часто используемые Х-приложения, чтобы их легко было запускать. Допустимо вызвать различные меню для разных кнопок мыши.
Открытие окна Чтобы открыть (или максимизировать) окно, дважды щелкните на значке, представляющем закрытое окно. Закрытие окна Чтобы закрыть (или минимизировать) окно, щелкните на кнопке закрытия в заголовке окна. Можно также вызвать меню с командой Close (Закрыть), выбирая границу окна. Кроме того, большинство диспетчеров окон предусматривают возможность задания комбинации клавиш, которая может использоваться, когда фокус находится на окне. Перемещение окна Чтобы переместить окно, наведите указатель мыши на его границу, нажмите среднюю кнопку мыши и, удерживая ее нажатой, перетаскивайте окно. Выпадающее меню границы окна также обычно имеет команду Move (Переместить). Изменение размера окна Чтобы изменить размер окна, наведите указатель мыши на его границу, нажмите и удерживайте левую кнопку мыши и тяните границу в нужном направлении. Когда тянут угол, изменяются размеры и X, и Y. При выборе верхней, нижней или боковой границы и перемещении ее размер окна изменяется только в одном направлении. Размещение поверх других окон Окно может быть размещено поверх остальных окон (над другими окнами) путем выбора его границы. Это действие также устанавливает фокус на данном окне. Вызов меню окна Диспетчер окна может снабдить меню каждое окно. Меню вообще содержит одну или несколько функций, описанных ранее, и может быть настроено. Чтобы вызвать меню окна, щелкните на кнопке меню в верхнем левом углу окна или нажмите правую кнопку мыши где-нибудь на границе окна или в области заголовка.
Приложение-клиент Каждая программа, которая осуществляет вывод на экран Х-сервера, называется Х-клиентом. X Window System содержит много полезных Х-клиентов. Далее мы рассмотрим несколько самых простых Х-клиентов, которые используют новички, изучая X Window System. Чтобы узнать о дополнительных аргументах, которые могут использоваться для настройки программы клиента, обратитесь к утилите man. xclock Утилита xclock, являющаяся Х-клиентом (рис. 11.7), может быть запущена вручную или через пользовательский файл инициализации. Синтаксис xclock [-digital] Утилита xclock отображает часы на рабочем столе пользователя. По умолчанию появляется циферблат с широкими стрелками. Если определен аргумент -digital, демонстрируются цифровые часы. Рис, 11.7, xclock-клиент xbiff Утилита xbiff, Х-клиент, является в основном версией для X Window System biff-программы от Berkeley UNIX, которая сообщает пользователю о поступлении новой почты.
Синтаксис xbiff Утилита xbiff показывает изображение почтового ящика на рабочем столе. Если пользователь, запустивший xbiff, не получал новой почты, в окне клиента будет изображен стеллаж с пустыми полками (рис. 11.8, а). Когда почта доставлена, появляются изображения конвертов, и значок может изменить цвет (рис. 11.8, б). Как упоминалось ранее, по легенде один из разработчиков Калифорнийского университета в Беркли (University of California at Berkeley) имел собаку по имени Biff, которая всегда лаяла, когда почтальон доставлял почту. Много более сложных программ было написано, но xbiff — это оригинал. а Рис, 11,8, Два вида xbiff-клиента: а — почты нет; б — почта пришла
xterm Утилита xterm — вероятно, самый популярный Х-клиент у пользователей UNIX. Она обеспечивает оконный интерфейс терминала в системе. Самые первые пользователи системы с оконным интерфейсом использовали свои Х-терминалы главным образом, чтобы организовать многотерминальный интерфейс и объединить мониторы на рабочем столе. По мере того как X-клиенты становились более сложными, xterm применялась все реже и реже. Но до сих пор все еще весьма полезна, если вы используете в UNIX shell-интерфейс. Утилита имеет несметное количество аргументов, позволяющих определять в командной строке размер окна, цвет и шрифт. (Для дополнительной информации СМ. страницу man ДЛЯ xterm.) Синтаксис xterm [-С] Утилита xterm запускает терминальное окно на рабочем столе. Если указана опция -с, терминальное окно получит сообщения консоли. Эта особенность полезна для предотвращения получения сообщений консоли, чтобы производить вывод поперек экрана системы, в которой растровый монитор является также консольным устройством. Стандартные аргументы Х-клиента Большинство Х-клиентов принимают стандартные аргументы, которые позволяют настраивать размер Х-клиента и положение при запуске. Г еометрия Х-геометрия клиента определяется аргументом -geometry. Можно задать не только размер клиента, но и позицию смещения для отображения его на экране. Общий формат для указания размера — xxy, для смещения — +х+/. Например, чтобы запустить утилиту xciock, окно которой сдвинуто на 10 пикселов в каждом направлений от верхнего правого угла экрана и размер составляет по 100 пикселов в ширину и высоту, следует использовать команду $ xciock -geometry 100x100-10+10 Обратите внимание на значение аргумента -geometry: отсутствуют пробелы.
Цвет переднего и заднего плана Цвета переднего плана и заднего плана могут быть установлены аргументами -foreground и -background. Следующая команда xterm создаст терми-нальное окно с символами цвета циан (легкий оттенок синего) на черном фоне: $ xterm -foreground cyan -background black Эта комбинация цветов очень удобна для работы, но каждый имеет собственные предпочтения. Заголовок Аргумент -title выводит строку в заголовке окна. Данная функция полезна для пометки одного из многих терминальных окон, используемых во время сессии на удаленном компьютере. Например: $ xterm -title "Remote access to mail server" Значок Аргумент -iconic применяется, чтобы запустить Х-клиента в минимизированном окне так, чтобы на рабочем столе появился лишь значок. Это полезно для приложений, которые будут использоваться, но не обязательно в настоящий момент при старте. Дополнительные темы Некоторые из рассматриваемых далее тем можно отнести к одному или нескольким предыдущим разделам. Но авторы считают, что читатель нуждается в предварительной подготовке перед обсуждением более сложных возможностей X Window System. Копирование и вставка Операция копирования и вставки — одна из наиболее полезных особенностей X Window System. Способность выделять текст в одном окне и копировать его в другое окно без необходимости повторно печатать — это большая экономия времени. Обсудим тему вне возможностей диспетчера окон, т. к. она фактически обеспечивается непосредственно Х-сервером. Вы можете удостовериться в этом, используя копирование и вставку, даже когда ни один диспетчер окон не работает. Чтобы скопировать текст в буфер копирования и вставки, установите указатель мыши на начало выделяемого фрагмента, щелкните левой кнопкой мы
ши и, не отпуская ее, тяните указатель к концу фрагмента. (Выделение можно выполнить как вперед, так и назад.) Когда вы отпустите кнопку мыши, выделенный текст скопируется в буфер (в отличие от PC4, который требует дополнительного действия для копирования выделенного текста в буфер). Далее перейдите к окну, в котором следует вставить текст, щелкните средней кнопкой мыши (или обеими кнопками мыши сразу, если их только две), и текст будет вставлен. Некоторые приложения вставляют текст в текущую позицию текстового курсора, в то время как другие — в точке, где вы щелкаете средней кнопкой мыши. Эта особенность определяется приложением. На рис. 11.9 представлен пример, в котором выполнялась утилита who для выяснения зарегистрированных в системе пользователя. Для копирования и вставки строки, показывающей подключение Graham, указатель был размещен на начале строки и после нажатия кнопки мыши передвинут к концу строки. Так был помечен текст и помещен в буфер копирования и вставки. Затем после отправки сообщения электронной почты Graham был выполнен щелчок средней кнопкой мыши, чтобы вставить текст в сообщение почты в текущей точке. $ mail No mail. $ who glass console May 6 18:45И ables ttypl May 6 18:55 ables ttyp3 May 6 18:59 dee ttyp4 May 6 18:44 pJones ttyp5 May 6 18:57 mktg ttyp5 May 6 14:35 pjones ttyp5 May 6 12:14 ksm ttyp5 $ mail glass May 6 17:32 Subject: are you in Graham, Is this you?: the computer room? glass console May 6 18:45 IT it is. could you come to the south Thanks. -king door of the computer room and let me in? I forgot my.access card. Рис. 11.9. Пример копирования и вставки 4 Авторы здесь не правы. Эта функция зависит не от компьютера, а от программного обеспечения, реализующего операцию копирования и вставки. — Ред.
Сетевые возможности Ранее упоминалось, что X Window System является сетевой системой с оконным интерфейсом, и существует возможность передать информацию от Х-клиента, работающего на одном компьютере, Х-серверу, работающему на другом. Эта способность фундаментальна для Х-конструкции, и ее весьма просто использовать с любого Х-клиента, определяя аргумент -display. Аргумент сообщает Х-клиенту, с каким Х-сервером войти в контакт, чтобы показать его виджеты. По умолчанию дисплей относится к локальной машине, на которой работает клиент. Чтобы запустить утилиту xterm на сервере savoy, укажите конструкцию типа $ xterm -display savoy: 0. О Спецификация : о. о — это способ уникально идентифицировать дисплей и Х-сервер, работающий на компьютере. Несмотря на то, что можно управлять множественными Х-серверами, также как иметь множественные мониторы, связанные с отдельным компьютером, при обычном использовании каждый компьютер будет иметь только один монитор и управлять лишь одним Х-сервером. Так что значение : о. о будет почти всегда обозначать эту конфигурацию. По умолчанию имя дисплея для локальной системы должно быть unix:о.о или, возможно, :0.0 без имени сервера. Имя unix имеет специальное значение "локальный компьютер" и поэтому не будет работать удаленно, если фактически существует локальный сервер unix. Если вводится команда $ xterm -display bluenote, utexas. edu: 0. 0 и пользователь на bluenote запустил утилиту xhost, чтобы разрешить доступ машине, на которой вводилась указанная команда, тогда создаваемое терминальное Х-окно будет показано на bluenote.utexas.edu. Ресурсы приложения Прикладные ресурсы являются и ящиком Пандоры, и одним из наиболее революционных аспектов X Window System. Х-ресурсы позволяют пользователям настраивать "облик и ощущение" их рабочих столов и приложений, которыми они управляют в той мере, в которой не обеспечивает никакая другая система. Мы кратко рассмотрим самые основные части использования Х-ресурсов. Я настоятельно рекомендую прочитать главу "Setting Resources" ("Установка ресурсов") в [Quercia 1993] для получения дополнительной информации.
Как работают ресурсы Каждое приложение, включая диспетчера окон, непосредственно может воспользоваться преимуществом Х-ресурсов. Х-ресурс — это строка текста, подобно имени переменной и значению, которое устанавливается как часть Х-сервера. Значение ресурса привязано к определенному виджету. Когда X-сервер рисует конкретный виджет, он ищет значение, связанное с ним в своем списке ресурсов и соответственно устанавливает описанный признак. Например, рассмотрим гипотетическое приложение xask. Это приложение представляет диалоговое окно, содержащее текстовое сообщение (вопрос) и две кнопки: Yes и No. Окно можно определить (по крайней мере) следующими ресурсами: xask.Button.yes.text xask.Button.no.text Приложение может иметь значения по умолчанию в случае, если эти ресурсы не установлены. При отсутствии набора ресурсов во время выполнения xask пользователь увидел бы окно, подобное показанному на рис. 11.10. Рис. 11.10. Вымышленное приложение Xask Но что, если пользователь предпочтет иметь более интересные надписи на кнопках, чем Yes и No? Возможно, следует изменить язык, используемый приложением, но вряд ли пользователь пожелает иметь отдельную версию программы для каждого языка. Существует другой выход — настроить значения Х-ресурсов. В нашем примере мы установим следующие ресурсы для Xask: xask.Button.yes.text: Sure xask.Button.no.text: No way Конечно, это тривиальный пример, но зато видна мощь Х-ресурсов. Путем присвоения вышеупомянутых значений этим ресурсам на Х-сервере в следующий раз выполняется xask и отображает окно, показанное на рис. 11.11. Если один пользователь установит значения ресурсов и выполнит xask, а
другой не установит и тоже запустит эту программу, то они увидят разные результаты! Х-ресурсы используются, чтобы настроить характеристики окна, например, размер, цвет, шрифт и значения строк текста. Хотя они типичны, в действительности нет никаких ограничений в том, что пользователь может настроить в приложении. Каждое Х-приложение использует некоторое количество Х-ресурсов, и они должны быть описаны в документации для приложения. Чем больше (сложнее) приложение, тем больше ресурсов оно будет требовать. Например, Motif имеет почти бесконечное количество ресурсов, которые позволяют настраивать в нем почти все. Рис. 11.11. Приложение Xask с настроенными Х-ресурсами Определение ресурсов Как настроить необходимые ресурсы? Х-ресурсы — это ресурсы Х-сервера, поэтому они устанавливаются на машине, на которой Х-сервер работает. Есть много способов загрузить ресурсы в Х-сервер. Ручной способ доступа к ресурсной базе данных Х-сервера — это утилита xrdb. Синтаксис xrdb [-query|-load|-merge|-remove] [имяФайла] Утилита xrdb обеспечивает доступ к базе данных Х-ресурса для X-сервера. Использованная с аргументом -query, xrdb печатает ресурсы, определенные в Х-сервере. Аргумент -load заставляет загружать новую ресурсную информацию в базу данных ресурса, при этом заменяя предыдущую информацию. Если имяФайла определено, информация ресурса загружается из этого файла, иначе читается стандартный канал ввода. Аргумент -merge загружает- новый информационный ресурс, действует^ подобно аргументу -load, за исключением того, что существующая ин-
формация не удаляется. (Информация дублирующихся ресурсов перепи-* сывается.) Наконец, аргумент -remove очищает базу данных ресурса X-сервера. Вы можете добавлять или удалять индивидуальный ресурс или группу ресурсов в любое время. Однако ручное добавление и удаление Х-ресурсов очень быстро становится утомительным. Хотелось бы иметь способ спецификации ресурса, который будет применяться каждый раз, когда выполняется некоторое приложение. Делается путем создания файл ресурса по умолчанию .Xdefaults. Это файл инициализации, распознающийся Х-программой сервера, когда та выполняется пользователем. После запуска Х-сервер загружает все ресурсы, перечисленные в файле. Например, если мы хотим, чтобы наша программа xask всегда использовала более легкомысленный текст на кнопках, мы можем ввести соответствующие ресурсы в наш .Xdefaults-файл, и следующий раз при запуске Х-сервера эти ресурсы будут установлены. Внимание Мы могли бы также непосредственно запустить утилиту xrdb с нашим .Xdefaults-файлом, чтобы загрузить или перезагрузить вышеупомянутые ресурсы в любое время. Если пользователь настраивает большое количество ресурсов во многих приложениях, помещая все эти строки настройки ресурса в свой .Xdefaults-файл, через какое-то время обнаружится, что файл стал чрезвычайно большим. Файл неудобно читать. Также некоторые приложения приходят с набором ресурсов по умолчанию, которые определяют признаки виджетов используемого приложения вместо задания значений по умолчанию непосредственно в коде. В этом случае возникает необходимость в способе загрузки ресурсов в Х-сервер при выполнении приложения. Определение каталога, где сохраняются Х-ресурсы, позволяет Х-серверу загружать множественные файлы ресурса. Списки ресурсов для приложения сохраняются в файле, названном так, чтобы приложение нашло его. (Это обычно имя приложения, но оно определяется непосредственно приложением.) Таким образом, ресурсами легче управлять, т. к. каждый файл будет содержать ресурсы для одного приложения. Можно обнаружить десятки или даже сотни файлов в этом каталоге, но каждый файл будет иметь разумный размер. В предыдущем примере Xask мы могли бы помещать наши ресурсы в файл Xask, который, как известно, используется приложением. Тогда единственное, в чем мы нуждаемся, — это в способе сообщить приложению, где искать данный файл. Определим shell-переменную xapplresdir (Х-каталог ресурса приложения). В С shell она должна быть переменной окружения
так, чтобы дочерние shell унаследовали ее значение. В Bourne-подобых shell эта переменная должна экспортироваться, когда будет установлена. Конфигурация и запуск Может показаться странным, что данный раздел до сих пор оставался без внимания. Но попытка выполнять X Window System без понимания, как она работает, иногда создает препятствие. Читатель уже знаком с отношениями между Х-сервером и Х-клиентом, а также как используется диспетчер окон, и поэтому представленные ниже разделы окажутся для него простыми. Следует учесть, что особенности запуска X Window System изменяются от платформы к платформе и от реализации к реализации. xinit и .xinitre При выполнении исходной X Window System, распространяемой от Массачусетского технологического института, пользователь регистрируется на UNIX-компьютере, как обычно. Можно затем ввести команду xinit, чтобы запустить Х-сервер, или указать ее в своем файле инициализации shell. xinit запускает Х-сервер и выполняет команды, найденные в файле shell-скрипта .xinitre. В этом файле можно определить любое приложение, которое пользователь хочет запустить при старте Х-сервера. Типичные команды, находящиеся в .xinitrc-файле, — это вызовы утилиты xterms и почтовых программ или, возможно, любимого Web-браузера пользователя. Здесь же разрешено запускать предпочтительный диспетчер окон. Следует обратить внимание на то, что Х-сервер выполняет команды из .xinitrc-файла подобно скрипту shell, и когда скрипт завершается (т. е. все команды выполнены), Х-сервер прекращает работу. Это ключевой момент. Часто пользователи помещают команды в свои .xinitrc-файлы и используют символ &, чтобы распараллелить процессы (выполнять их в фоновом режиме). Но они делают это в последней команде списка. В данном случае .xinitre выходит из скрипта и Х-сервер прекращает работу. Проявляется это так: Х-сервер стартует, запускается приложение, а затем резко прекращается. Причина в том, что последнее приложение, начатое в .xinitrc-файле, не должно быть запущено в фоне с символом & на конце команды! Когда это конкретное приложение завершает работу, сам Х-сервер тоже останавливается. Некоторые пользователи предпочитают делать "последним приложением" диспетчер окон (mwm). Продвинутые пользователи иногда ставят последней командой специальное "console" терминальное окно, потому что хотят останавливать и запускать свой диспетчер окон без прекращения работы X-сервера. В этом случае, чтобы заставить Х-сервер выйти, следует просто за
вершить работу (печатать exit или нажать комбинацию клавиш <Ctrl>+ +<D>) в консольном окне. В приведенном ниже примере .xinitrc-файла диспетчер окон mwm стартовал последним и не был помещен в фоновый режим, так что выполнение скрипта приостановлено до тех пор, пока выполняется mwm. После выхода из вашего диспетчера окон выполнение .xiiiitrc-скрипта продолжается до конца, и когда скрипт заканчивается, Х-сервер также завершает работу: $ cat .xinitrc xbiff & mailtool & xterm -C & mwm $ _ Приведенное обсуждение может выглядеть простым, но отказ от этой идеи может вызвать огорчение у пользователя-новичка X Window System! mwm и .mwmrc Так же как .xinitrc — файл запуска для xinit, .mwmrc — файл инициализации для mwm. При старте Motif диспетчер окон должен быть проинструктирован, какие действия назначить кнопкам мыши, что делать, когда пользователь щелкает на различных виджетах, и, вообще, как вести себя. Обычно регистрационная запись пользователя будет обеспечиваться по умолчанию копией .mwmrc-файла, который разрешено редактировать. В .mwmrc-файле определяются все выпадающие меню, которые будут доступны пользователю. Это один путь, с помощью которого можно осуществлять некоторые действия, когда Х-сервер выполняется без запуска других приложений (в пользовательском .xinitrc-файле). Через какое-то время некоторые приложения могут оказаться настолько полезными, что пользователь добавит команду для их запуска в выпадающие меню. (Подробности см. в [OSF 1992].) Обзор других Х-совместимых рабочих столов В настоящее время применяются три основных рабочих стола UNIX: CDE, KDE и Gnome. Старые рабочие столы типа VUE и OpenWindows фигурируют в истории рабочего стола UNIX, но мы лишь упоминаем их, поскольку они больше не используются во многих окружениях UNIX.
CDE С развитием компаниями своих реализаций Х-серверов и добавлением собственных "звонков и свистков" к ним стало ясно, что Х-окружения усложнятся и разнообразятся. Hewlett-Packard, IBM, SunSoft и Novell объединились, чтобы определить новую Х-реализацию, которая могла быть разделена и использоваться всеми UNIX-платформами. Результатом стал Common Desktop Environment (CDE). Интерфейс CDE похож на VUE, хотя есть также повсюду очевидные влияния и OpenWindows. CDE также базируется на Motif и имеет диспетчера регистрации и сессии. CDE не представляет никакого большого технологического прыжка по отношению к OpenWindows, VUE или даже непосредственно к X Window System. Он обеспечивает общее окружение, разделяемое главными UNIX-продавцами, и доступность UNIX-пользователям на различных UNIX-платформах. CDE стал первым рабочим столом UNIX, который был принят несколькими UNIX-продавцами. Gnome GNU Network Object Model Environment (Gnome) — это вклад GNU-проектов в рабочий стол UNIX. Как с другим программным обеспечением GNU, он бесплатно доступен и работает на большинстве платформ UNIX. Несмотря на то, что рабочий стол поставляется теперь со многими коммерческими дистрибутивами UNIX и Linux, он может быть также найден на сайте http://www.gnome.org. Предназначение Gnome состоит в том, чтобы обеспечить удобный в работе рабочий стол для начинающих пользователей и не связать руки опытным. KDE Во многом подобный Gnome, KDE — К Desktop Environment, является бесплатным рабочим столом UNIX, разработанным свободной группой программистов со всего мира. KDE также доступен почти для каждой версии UNIX и Linux и может быть найден по адресу http://www.kde.org. KDE пытается обеспечивать интерфейс, подобный рабочим столам MacOS и Windows, чтобы содействовать установке UNIX или Linux на домашние компьютеры и в офисе (где MacOS и Windows традиционно доминируют). OpenWindows Сознавая, что его оригинальная система управление окнами, Suntools, не была правильным решением, корпорация Sun отказалась от нее и разработала собственный, базирующийся на X Window System, рабочий стол OpenWindows. Хотя OpenWindows способен к выполнению Х-клиентов, он
обеспечил новый "облик и ощущение" и новый набор виджетов (и, конечно, новый набор приложений). Новый стиль называли OpenLook. Несмотря на то, что пользователи Sun и Open Software Foundation воспользовались Open-Look и OpenWindows, он никогда в действительности не копировался в остальной части Х-сообщества. VUE Hewlett-Packard также хотела улучшить оригинальную Х-концепцию наряду с тем, чтобы сохранилась совместимость с общими Х-клиентами. Visual User Environment (VUE) реализовало Х-сервер, новый дизайн рабочего стола и новую парадигму запуска. VUE обеспечил экран регистрации так, чтобы пользователь никогда не видел общий диалог регистрации UNIX или приглашение shell на терминале. VUE выполнялся, когда включался компьютер, и это переместило контроль над файлами инициализации пользователя на момент, когда тот регистрировался в системе. VUE также предоставил полезный набор таких файлов по умолчанию, чтобы окружение изначально хорошо работало, но мог все еще настраиваться опытным Х-пользователем. В тех случаях, когда в обычной X Window System пользователь должен был редактировать файлы инициализации, чтобы изменить поведение системы, VUE предложил много методов для изменения поведения через GUI, что было гораздо проще для среднего пользователя. Обзор главы Перечень тем В этой главе мы рассмотрели: □ что делает графический пользовательский интерфейс; □ X Window System Массачусетского технологического института; □ Х-серверы, Х-клиенты и виджеты; □ диспетчер окон Motif; □ ресурсы X-приложения. i Контрольные вопросы 1. Какая команда используется для изменения прав доступа к Х-серверу? 2. Какой Motif-виджет вы использовали бы в окне, в котором хотите предоставить пользователям выбор одной из нескольких опций?
3. Какой Х-аргумент приложения используется, чтобы отобразить окно приложения на экране другого компьютера? 4. Какой признак Х-сервера позволяет изменить вид Х-приложения без необходимости изменять программу? Упражнения 11.1. Объясните, почему диспетчер окон является Х-клиентом. [Уровень: легкий.] 11.2. Предположим, что ваш диспетчер окон завершился, и вы не можете получить фокус в окне и напечатать команду для вызова нового диспетчера окна. (Предположим также, что у вас нет команды в корневом меню, которая запускает новый диспетчер окон.) Объясните, как вы могли бы использовать копирование и вставку в своих существующих окнах для выполнения команды. [Уровень: высокий.] Проект Используйте утилиту xrdb для печати базы данных ресурсов работающего X-сервера. Изучите вывод, чтобы узнать, какие типы ресурсов какими типами приложений используются. [Уровень: средний.]

Глава 12 Инструментальные средства программирования на С Мотивация Чаще всего в системах UNIX применяются языки программирования С и C++. Это не удивительно, т. к. ОС UNIX была написана на С, и многие версии поставляются со стандартным С-компилятором. Большинство утилит UNIX и масса коммерческих продуктов написаны на С или C++. Поэтому надеемся, знакомство с написанием, компилированием и выполнением С-программ окажется для читателя очень полезным. Конечно, UNIX поддерживает много других популярных языков программирования, но данная глава предлагает описание прежде всего на языке С и инструментов его поддержки, поскольку они фундаментальны для окружения UNIX. Предпосылки Глава предполагает, что вы уже знаете С и компилировали программы, по крайней мере, на одной платформе. Например, много читателей, возможно, использовали С-компиляторы от Borland или Microsoft. Задачи В этой главе описываются инструментальные средства, которые поддерживают некоторые стадии разработки программы: компиляция, отладка, поддержка библиотек, профилирование и управление исходным кодом. Изложение Программное окружение С представлено естественным способом, со множеством примеров и маленьких программ. 16 Зак. 786
Утилиты В данной главе будут рассмотрены следующие утилиты: admin Id sact аг lint strip сс lorder touch comb make tsort dbx prof unget get prs help ranlib Язык С Язык С может быть обнаружен в двух главных формах: K&R С и ANSI С. K&R, названый в честь авторов первого популярного программного С-текста Брайана Кернигана (Brian Kernighan) и Денниса Ритчи (Dennis Ritchie), определяет С, как это было в дни юности UNIX. Некоторые ссылаются на него, как на "классический С". Американский национальный институт стандартов (American National Standards Institute, ANSI) разработал собственный стандарт С, добавив некоторые полезные особенности и конкретизировав синтаксис для существующих, но плохо определенных, возможностей. Большинство компиляторов поддерживают оба стандарта. Прежде чем мы перейдем к исходным кодам, следует уточнить важный момент: исходный код в этой книге не соответствует стандарту ANSI С. Приходится лишь сожалеть, потому что ANSI С содержит несколько хороших синтаксических возможностей и проверку типа, которые позволяют создавать сопровождаемые и удобочитаемые программы. Причина, по которой я не использовал1 указанные особенности, заключается в том, что несколько ведущих корпораций и университетов, которые я знаю, непосредственно поддерживают только K&R С. Чтобы сделать мой исходный код как переносимым, так и полезным насколько возможно, я подогнал его к наиболее разумному наименьшему общему знаменателю. Я делал это без удовольствия, поскольку я профессиональный разработчик программного обеспечения, так же как и автор, и это действительно идет против моей воли. Фактически яч предпочел бы написать весь код на C++, но это другая история. 1 Здесь опять ведется повествование от имени Грэхэма Гласса. — Ред.
Компиляторы С До недавнего времени С-компилятор был стандартным компонентом в UNIX, особенно в версиях, которые поставлялись с исходным кодом. (Как еще вы могли бы изменить код и создать новое ядро?) К сожалению, некоторые продавцы захотели "оторвать" компиляторы С от своих дистрибутивов UNIX и продают их отдельно. В зависимости от потребностей, пользователь должен проверить его для каждой версии UNIX, которую собирется использовать. Однако даже если некоторая версия UNIX больше не поставляет компилятор С, существует альтернатива. Спасибо проекту GNU: GNU С (gcc) и GNU C++ (g++). Эти компиляторы С и C++, соответственно, свободно доступны для большинства UNIX-платформ на Web-узле GNU по адресу http://www.gnu.org/software/gcc/gcc.html. Одномодульные программы Исследуем С-программу2, которая выполняет простую задачу: перестановки в обратном порядке символов строки (будем называть это короче: реверсирование строки). Для начала посмотрим, как писать, компилировать, компоновать модули и выполнять программу, используя единственный исходный файл. Затем выясним, почему лучше разделить программу на несколько независимых модулей, и рассмотрим, как это сделать. Вот исходный листинг КОДа первой ВерСИИ Программы reverse: 1 /* REVERSE.C */ 2 3 #include <stdio.h> 4 5 /* Function Prototype */ 6 reverse (); 7 g I ★ ★ ★ к * к k-k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk / 9 10 main () 11 12 { char str [100]; /* Буфер для хранения реверсированной строки */ 14 15 reverse ("cat", str); /* Реверсировать строку "cat" */ 2 Большинство примеров, используемых в этой главе, доступно в сети. (Для дополнительной информации см. Введение.)
16 printf ("reverse ("cat") = %s\n", str); /* Вывести результат */ 17 reverse ("noon", str); /* Реверсировать строку "noon" */ 18 printf ("reverse ("noon") = %s\n", str); /* Вывести результат */ 19 } 20 2 у****************************************************************J 22 23 reverse (before, after) 24 ' 25 char *before; /* Указатель на исходную строку */ 26 char *after; /* Указатель на реверсированную строку */ 27 28 { 2 9 int i; 30 int j; 31 int len; 32 33 len = strlen (before); 34 35 for (j = len — 1; i = 0; j >= 0; j—; i++) /* Цикл реверсирования */ 36 after[I] =before[j]; 37 38 after[len] = NULL; /* NULL прекращает реверсирование строки */ 39 } Компиляция С-программы Чтобы написать и выполнить программу reverse, я сначала создал каталог называемый reverse, внутри своего домашнего каталога, а затем — файл reverse.c, используя UNIX-редактор emacs. Далее я компилировал Сопрограмму при помощи утилиты сс. Чтобы подготовить выполняемую версию единственной, самодостаточной программы, следует ввести за сс имя файла с исходным кодом, который должен закончиться суффиксом ”.с”. Компилятор сс ничего не выводит, когда компиляция проходит без ошибок. По умолчанию сс создает исполняемый файл a.out в текущем каталоге. Чтобы выполнить программу, необходимо ввести: a.out. Любые встречающиеся ошибки передаются в стандартный канал ошибки, который связан по умолчанию с экраном пользовательского терминала.
Вот что получилось при компиляции программы: $ mkdir reverse ...создать подкаталог для исходного кода. $ cd reverse $ ...я создал файл reverse.c, используя emacs. $ сс reverse.с ... компилировать источник. "reverse.c", line 16: syntax error at or near variable name "cat" "reverse.c", line 18: syntax error at or near variable name "noon" "reverse.с", line 35: syntax error at or near symbol ; "reverse.c”, line 35: syntax error at or near symbol ) $ _ Как вы видите, компилятор сс нашел ошибки и вывел их список: □ ошибки на строках 16 и 18 были из-за несоответствующего использования двойных кавычек внутри двойных кавычек; □ ошибки на строке 35 возникли из-за неправильного использования точки с запятой (;). Так как эти ошибки было легко исправить, я скопировал отягощенный ошибками файл reverse.c в файл reverse.oldl.c, а затем удалил ошибки времени компиляции при помощи редактора emacs. Я оставил исходный файл в каталоге для того, чтобы можно было видеть эволюцию моего программирования. Листинг скорректированной программы reverse Ниже представлена вторая, исправленная, версия программы reverse. Строки, ранее содержащие ошибки, которые я исправил, выделены курсивом: 1 /* REVERSE.C */ 2 3 #include <stdio.h> 4 5 /* Function Prototype */ 6 reverse (); 7 g у****************************************************************/ 9 10 main () 11 12 { 13 char str [100]; /* Буфер для хранения реверсированной строки */
14 15 reverse ("cat", str); /* Реверсировать строку "cat" */ 16 printf ("reverse (\"cat\") = %s\n", str); /* -Вывести */ 17 reverse ("noon", str); /* Реверсировать строку "noon" */ 18 printf ("reverse (\"noon\") = %s\n", str); /* Вывести ★/ 19 } 20 у****************************************************************/ 22 23 reverse (before, after) 24 25 char *before; /* Указатель на исходную строку */ 26 char *after; /* Указатель на реверсированную строку */ 27 28 { 2 9 int i; 30 int j; 31 int len; 32 33 len = strlen (before); 34 35 for (j = len — 1, i = 0; j >= 0; j —, i ++) /* Цикл реверсирования ★/ 3 6 after[I] =before[j]; 37 38 after[len] = NULL; /* NULL прекращает реверсирование строки */ 39 } Выполнение С-программы После компиляции второй версии reverse.c я запустил программу, введя имя исполняемого файла a.out. Как видно, ошибок нет: $ сс reverse.с ... скомпилировать источник. $ Is -1 reverse.c a.out ...вывести информацию о файлах. -rwxr-xr-x 1 glass 24576 Jan 5 16:16 a.out* -rw-r—r— 1 glass 439 Jan 5 16:15 reverse.c $ a.out ...выполнить программу. reverse ("cat") = tac reverse ("noon") = noon $
Переопределение имени исполняемого файла По умолчанию имя исполняемого файла a.out является довольно загадочным, и очередной файл a.out, созданный следующей компиляцией, переписал бы старый файл. Чтобы избежать таких проблем, лучше использовать опцию -о с сс, которая позволяет задать имя исполняемого файла: $ сс reverse.с -о reverse ...назвать исполняемый файл reverse. $ Is -1 reverse -rwxr-xr-x 1 glass 24576 Jan 5 16:19 reverse* $ reverse ...запустить исполняемый файл reverse. reverse ("cat") = tac reverse ("noon") = noon $ _ Многомодульные программы Неудобство способа, которым я строил программу reverse, состоит в том, что функция reverse не может легко использоваться в других программах. Например, необходимо написать функцию, которая возвращает 1, если строка является палиндромом, и 0 в противном случае. (Палиндром — это строка, которая читается одинаково справа налево и слева направо. Например, "noon” — это палиндром, но "попо" — нет.) Можно использовать функцию reverse, чтобы реализовать функцию палиндрома. Один способ: вырезать и вставить reverse о в программу палиндрома, но это убогий метод, по крайней мере, по трем причинам: □ вырезание и вставка выполняются медленно; □ если будет создан улучшенный код функции reverse о, необходимо заменить каждую копию старой версии новой версией, что является кошмаром сопровождения; П каждая копия reverse о поглощает дисковое пространство. Уверен, вы понимаете, что есть лучший способ’ разделить использование функции. Многократно используемые функции Лучшая стратегия для разделения reverse о состоит в том, чтобы удалить ее из программы reverse, скомпилировать отдельно, а затем скомпоновать результирующий объектный модуль с какой угодно программой. Данный способ позволяет обойти перечисленные в предыдущем разделе проблемы и дает возможность использовать функцию в различных программах. Такие функции назваются многократно используемыми.
Подготовка многократно используемой функции Чтобы подготовить функцию многократного использования, следует создать модуль с исходным кодом функции вместе с файлом заголовка, который содержит прототип функции. Затем необходимо скомпилировать исходный модуль с кодом в объектный модуль, используя опцию -с компилятора сс. Объектный модуль содержит машинный код вместе с информацией в форме таблицы символов, которая позволяет объединить модуль с другими объектными модулями, когда создается исполняемый файл. Ниже представлены листинги новых файлов reverse.c и reverse.h. reverse.h 1 /* REVERSE.H 2 3 reverse (); /* Объявляет, но не определяет эту функцию */ reverse.c 1 /* REVERSE.C */ 2 3 #include <stdio.h> 4 ftinclude "reverse.h" 5 g /************************************************************/ 7 8 reverse (before, after) 9 10 char *before; /* Указатель на исходную строку */ 11 char *after; /* Указатель на реверсированную строку */ 12 13 { 14 int i; 15 int j; 16 int len; 17 18 len = strlen (before); 19 20 for (j = len — 1, i = 0; j >= 0; j—, i++) /* Цикл реверсирования */ 21 after[i] = beforefj];
22 23 after[len] = NULL; /* NULL прекращает реверсирование строки */ 24 } mainl.c Вот листинг основной программы, которая использует reverse (): 1 /* MAIN1.C */ 2 3 #include <stdio.h> 4 #include "reverse.h" /* Содержит прототип reverse() */ 5 /**w*w*****w*+*w*M*iirw******:w*w*****:w**:w**w*'k****** 7 8 main () 9 10 { 11 char str [100]; 12 13 reverse ("cat", str); /* Вызвать внешнюю функцию */ 14 printf ("reverse (\"cat\") = %s\n", str); 15 reverse ("noon", str); \ /* Вызвать внешнюю функцию */ 16 printf ("reverse (\"noon\") = %s\n", str); 17 } Отдельное компилирование и компоновка модулей Для компиляции каждого исходного файла кода отдельно используйте опцию -с компилятора сс. При этом создается отдельный объектный модуль для каждого исходного файла кода с суффиксами ".о”. Например: $ сс -с reverse.c ...скомпилировать reverse.c в reverse.о. $ сс -с mainl.c ...скомпилировать mainl.c в mainl.o. $ Is -1 reverse.c mainl.o -rw-r—r— 1 glass 311 Jan 5 18:24 mainl.o 1 glass 181 Jan 5 18:08 reverse.о -rw-r—r—
В качестве альтернативы можно перечислить все исходные файлы кодов в одной строке: $ сс -с reverse.с mainl.с ...скомпилировать каждый .с-файл в .о-файл. $ _ Чтобы скомпоновать их все вместе в исполняемый файл mainl, следует перечислить имена всех объектных модулей после команды компилятора сс: $ сс reverse.о mainl.о -о mainl ...скомпоновать объектные модули. $ Is -1 mainl ...проверить исполняемый. -rwxr-xr-x 1 glass 24576 Jan 5 18:25 mainl* $ mainl ...запустить исполняемый. reverse (’’cat") = tac reverse ("noon") = noon $ Автономный загрузчик: Id Когда используется компилятор сс для компоновки нескольких объектных модулей, он, очевидно, вызывает автономный загрузчик UNIX id. Загрузчик известен как linker (компоновщик). Хотя большинство С-программистов непосредственно никогда не вызывают утилиту id, все-таки разумно кое-что знать о ней. Синтаксис Id —п {-Ьлуть}* {объектныйМодуль}* {библиотека}★ {-1х}* [-о имяФайла] Утилита id компонует вместе указанный объектный (объектныйМодуль) и библиотечный (библиотека) модули, чтобы создать исполняемый файл. Можно отменить имя выполняемого файла по умолчанию a.out, используя опцию -о. Если пользователь хочет создать автономный исполняемый файл (в противоположность динамически подключаемому модулю, который находится вне сферы описания этой книги), то следует задать опцию -п. Когда утилита id сталкивается с опцией в виде -1х, она ищет стандартные каталоги /lib, /usr/lib и /usr/local/lib для библиотеки libx.a. Чтобы добавить путь каталога в этот путь поиска, необходимо применить опцию -ъпуть. Утилита id (в большей степени, чем другие утилиты UNIX) меняется от версии к версии. Следует обратиться за дополнительной информацией к документации конкретной версии UNIX при использовании Id.
Если пользователь компонует С-программу вручную, важно определить объектный С-модуль времени выполнения, /lib/crtO.o как первый объектный модуль, и стандартную С-библиотеку, /lib/libc.a как библиотечный модуль. Пример: $ Id -n /lib/crtO.o mainl.о reverse.c -1с -о mainl ...ручная компоновка. $ mainl ...выполнить программу. reverse ("cat") - tac reverse ("noon") = noon $ _ Повторное использование функции reverse Теперь, когда вы узнали, что исходная программа reverse может содержать пару модулей, используем модуль reverse снова для построения программы палиндрома. Приведем листинги заголовка и исходного кода функции палиндрома. palindrome.h 1 /* PALINDROME.H */ 2 3 int palindrome О; /* Объявляет, но не определяет */ palindrome.с 1 /* PALINDROME.С */ 2 3 #include "palindrome.h" 4 #include "reverse.h" 5 #include <string.h> 6 *7 /★★★★*★★★*★**★*★★★★★★★*★★**★★*****★★*★**★★★*******★★★*•*••*•★*★★•*•★•*••*•★ / 8 9 int palindrome (str) 10 11 char *str; 12 13 { 14 char reversedStr [100]; 15 reverse (str, reversedStr); /* Реверсировать оригинал */ 16 return (strcmp (str, reversedStr) == 0); /* Сравнить оба */ 17 }
main2.c Вот исходный код программы main2.c, которая проверяет функцию palindrome () ’. 1 /* MAIN2.C */ 2 3 #include <stdio.h> 4 ttinclude "palindrome.h" 5 g /***-**'11г*****'<г*^'А'****^^т1гЛ'*Л***Л'*'** + ^*'<г***********Лт>гЛ,>1г*******'1<'**'<г** / 7 8 main () 9 10 { 11 printf ("palindrome (\'cat\) = %d\n", palindrome ("cat")); 12 printf ("palindrome (\'noon\) = %d\n", palindrome (."noon")); 13 } Способ объединить модули reverse, palindrome И main2 ТДКОЙ Же, как И ранее использованный: скомпилируйте объектные модули, а затем скомпонуйте их. Не нужно повторно компилировать reverse.c, поскольку он не изменился с момента создания объектного файла reverse.о. $ сс -с palindrome.с ...скомпилировать palindrome.с в palindrome.о. $ сс -с main2.c ...скомпилировать main2.c в main2.o. $ сс reverse.о palindrome.о main2.o -о main2 ...скомпоновать все. $ Is -1 reverse.о palindrome.о main2.o main2 -rwxr-xr-x 1 glass 24576 Jan 5 19:09 main2* -rw-r—г— 1 glass 306 Jan 5 19:00 main2.о -rw-r—г— 1 glass 189 Jan 5 18:59 palindrome.о -rw-r—г— 1 glass 181 Jan 5 18:08 reverse.о $ main2 ...выполнить программу, palindrome ("cat") = О palindrome ("noon") = 1 $ _ Поддержка многомодульных программ При поддержке многомодульных систем нужно рассмотреть несколько различных вопросов: 1. Что гарантирует сохранение последних версий объектных модулей и исполняемых файлов?
2. Что содержат объектные модули? 3. Кто отслеживает каждую версию исходных файлов и файлов заголовков? К счастью, есть утилиты UNIX, которые решают каждую проблему. Вот ответ на каждый вопрос: 1. Утилита make, система зависимости файлов UNIX. 2. Утилита аг, архивная система UNIX. 3. Система управления исходным кодом SCCS. Система зависимости файлов UNIX: make Теперь понятно, как несколько независимых объектных модулей могут быть скомпонованы в единственный исполняемый файл. Вдобавок, один и тот же объектный модуль может быть связан с несколькими различными исполняемыми файлами. Хотя многомодульные программы эффективны в терминах возможности многократного использования и дискового пространства, они должны тщательно поддерживаться. Например, предположим, что мы изменяем исходный код reverse.c так, чтобы он использовал указатели вместо массива. Это привело бы к более быстрой обратной функции. Чтобы модернизировать исполняемые файлы двух главных программ, mainl и main2, вручную, мы должны проделать следующие шаги: 1. Повторно скомпилировать reverse.c. 2. Связать reverse.o и mainl.о для того, чтобы создать новую версию mainl. 3. Связать reverse.o и main2.o для того, чтобы создать новую версию main2. Вообразите также ситуацию, что предложение #define в файле заголовка изменено. Все исходные файлы кодов, которые непосредственно или косвенно включают файл заголовка, должны повторно компилироваться, а затем все исполняемые модули, которые обращаются к измененным объектным модулям, должны быть повторно скомпонованы. Представьте систему с 1000 объектными модулями и 50 выполняемыми программами. Помнить все отношения среди заголовков, файлов исходных кодов, объектных модулей и исполняемых файлов было бы кошмаром. Один из способов избежать этой проблемы состоит в том, чтобы использовать утилиту UNIX make, которая позволяет создавать make-файл, который содержит список всех взаимозависимостей для каждого исполняемого файла. Как только такой файл создан, повторно создать исполняемый файл легко: просто запускается утилита make: $ make -f makefile
Синтаксис make [-f такеФайл] Утилита make модернизирует файл на основе ряда правил зависимостей, сохраняемых в специальном формате, make-файл. Опция -f позволяет определить собственное имя make-файла. Если оно не указано, принимается имя makefile. Маке-файлы Чтобы использовать утилиту таке для поддержки исполняемого файла, следует сначала создать make-файл. Этот файл содержит список всех взаимоза-висимостей, которые существуют между файлами и используются для создания исполняемого файла. Make-файл может иметь любое имя. Рекомендуется называть его, добавляя к имени исполняемого файла суффикс ".make". Таким образом, имя make-файла для mainl могло бы быть mainl.make. В своей самой простой форме make-файл имеет формат, представленный ниже. Синтаксис списокЦелей: списокЗависимости списокКоманд Здесь списокЦелей — СПИСОК целевых файлов, списокЗависимости — список файлов, ОТ которых зависят файлы В спискеЦелей. списокКоманд — список команд (команды вообще могут отсутствовать), разделенных символом перевода строки, которые реконструируют целевые файлы из файлов зависимостей. Каждая строка в спискекоманд должна начинаться с символа табуляции. Правила следует разделить, по крайней мере, одной пустой строкой. Например, давайте подумаем о взаимозависимостях файла, связанных с исполняемым файлом mainl. Этот файл построен из двух объектных модулей: main 1.о и reverse.©. Если любой файлов изменен, mainl может быть восстановлен путем компоновки файлов через утилиту сс. Поэтому одно правило в mainl.make было бы mainl: mainl.о reverse.о сс mainl.о reverse.о -о mainl
Эта строка должна теперь приводить к двум объектным файлам. Файл mainl.o построен из двух файлов: mainl.c и reverse.h. (Помните, что любой файл, который является непосредственно или косвенно включенным через директиву #included в исходный файл, является эффективной частью этого файла.) Если один из файлов изменен, то mainl.o может быть восстановлен компиляцией mainl.c. Здесь, следовательно, появляются дополнительные правила в mainl.make: mainl.o: mainl.c reverse.h сс -c mainl.c reverse.©: reverse.c reverse.h cc -c reverse.c Порядок make-правил Порядок make-правил важен. Утилита make создает "дерево” взаимозависи-мостей, первоначально исследуя первое правило. Каждый целевой файл в первом правиле — корневой узел дерева зависимости, и каждый файл в его списке зависимости добавляется, как лист корневого узла. В нашем примере начальное дерево может иметь такой вид, как представлено на рис. 12.1. mainl mainl.o reverse, о Рис. 12.1. Начальное дерево зависимости make Далее утилита make просматривает каждое правило, связанное с файлами в списке зависимости, и выполняет те же самые действия. В нашем примере заключительное дерево выглядит так, как показано на рис. 12.2. mainl mainl.o reverse, о mainl.c reverse.h reverse.c reverse.h Рис. 12.2. Заключительное дерево зависимости make
Наконец, утилита make обрабатывает дерево от нижних узлов (листьев) до коревого узла, определяя, является ли последнее время модификации каждого дочернего узла более поздним, чем последнее время модификации его непосредственного родительского узла. Для каждого случая, где это условие соблюдается, выполняется правило, связанное с родителем. Если файл не существует, его правило выполняется без учета времени последних модификаций его дочерних узлов. Чтобы проиллюстрировать порядок, в котором узлы могли быть исследованы, на рис. 12.3 представлена пронумерованная диаграмма. main17 mainl.о reverse, о reverse, h reverse. с3 Рис. 12.3. Упорядочение make mainl. с1 reverse. h4 Выполнение make Поскольку make-файл создан, можно выполнить утилиту make для создания исполняемого файла, чьи информационные зависимости определены make-файлом. Для демонстрации ее работы были удалены все объектные модули и исполняемый файл, чтобы выполнился каждый список команд. Когда затем была запущена утилита make, на экране появилось: $ таке -f mainl.make ...сделать новейший исполнимый файл. сс -с mainl.с сс -с reverse.с сс mainl.о reverse.о -о mainl $ _ Заметьте, что каждое make-правило было выполнено в точном порядке, показанном на рис. 12.3. Так как был создан второй исполняемый файл после программы palindrome, также был сформован второй make-файл main2.make. Вот он: main2: main2.o reverse.о palindrome.о сс main2.o reverse.о palindrome.о -о main2
main2.o: main2.c palindrome.h сс -с main2.с reverse.o: reverse.c reverse.h сс -c reverse.c palindrome.о: palindrome.c palindrome.h reverse.h cc -c palindrome.c После запуска make с использованием этого файла получен вывод: $ make -f main2.make ...создать более поздний исполняемый файл, сс -с main2.с сс -с palindrome.с сс main2.o reverse.o palindrome.о -о main2 $ _ Заметьте, что файл reverse.c повторно не компилировался, потому что предыдущий запуск make уже СОЗДВЛ обновленный объектный модуль, a make повторно компилирует файлы, только когда это необходимо. Маке-правила Маке-файлы, продемонстрированные ранее, на самом деле гораздо больше, чем должны быть. Это потому, что некоторые добавленные шаке-правила уже известны утилите таке в более общем виде. Например, обратите внимание, что некоторые из правил имеют форму ххх.о: reverse.c reverse.h сс -с ххх.с где ххх изменяется среди правил. Утилита таке содержит предопределенное правило, подобное . с. о: /bin/сс -с -0 $< Это выглядящее загадочно правило сообщает утилите таке, как создать объектный модуль из исходного файла кода С. Существование такого общего правила позволяет отбросить правило перекомпиляции С. Поэтому представим улучшенную версию main2.make: main2: main2.o reverse.o palindrome.о cc main2.o reverse.o palindrome.о -о main2 main2.o: main2.c palindrome.h reverse.o: reverse.c reverse.h palindrome.о: palindrome.c palindrome.h reverse.h
Утилита make также включает другие правила вывода. Например, она "знает", что имена объектного модуля и его соответствующего исходного файла кода обычно связаны. Утилита использует эту информацию, чтобы вывести стандартные зависимости. Например, она выводит, что main2.o зависит от main2.c и, таким образом, можно убрать эту информацию из списка зависимости. Приведем еще раз переработанную версию main2.make: main2: main2.0 reverse.0 palindrome.0 cc main2.o reverse.0 palindrome.0 -0 main2 main2.0: palindrome.h reverse.0: reverse.h palindrome.о: palindrome.h reverse.h Написание собственных пользовательских правил К сожалению, метод для написания собственных пользовательских правил или даже описание тех, что уже существуют, находится вне рамок данной книги. Для получения дополнительной информации консультируйтесь с одним из источников, указанных в разд. "Другие возможности таке" далее в этой главе. touch Чтобы удостовериться, что новая версия make-файла работала, была запущена утилита make и получен следующий вывод: $ make -f main2.make 'main2' is up to date. $ _ Поскольку make успешно выполнлась ранее, другой запуск этой утилиты не собирался вызывать никакие правила! Чтобы заставить make работать с целью проверки, следует воспользоваться удобной утилитой touch, которая делает последнее время модификации всех указанных файлов равным текущему времени системы. Синтаксис touch -с {имяФайла}+ Утилита touch обновляет время последней модификации и доступа указанных файлов, чтобы сделать его равным текущему времени. По умолчанию, если указанный файл не существует, он создается с нулевым размером. Чтобы предотвратить это, используйте опцию -с.
В представленном ниже примере утилита touch использоваась для файла reverse.h, который впоследствии вызвал перекомпиляцию нескольких исходных файлов: $ touch reverse.h ...полная сборка. $ make -f main2.make /bin/сс -с —о reverse.c /bin/сс —с —о palindrome.c cc main2.o reverse.© palindrome.© -o main2 $ Макросы Утилита make поддерживает примитивные макросы. Если вы определяете строку в формате, представленном ниже, в начале make-файла, каждое появление $ (символ) в make-файле заменяется такстомЗамены. Синтаксис символ = текстЗамены В дополнение к существующим правилам стандартный файл правил содержит определения макросов по умолчанию, типа cflags, которые используются некоторыми из встроенных правил. Например, правило, которое сообщает утилите make, как модернизировать объектный файл из исходного С-файла, напоминает это: .с.о: /bin/сс -с $(CFLAGS) $< , Стандартный файл правил содержит строку в формате CFLAGS = -О 4 Если пользователь желает повторно откомпилировать набор программ, используя опцию -р компилятора сс, следует отменить значение по умолчанию cflags в начале make-файла и указать опцию -р в заключительном запросе к сс в правиле main2, подобно этому: CFLAGS = -Р main-2: main2.o reverse.© palindrome.0 cc -p main2,.o reverse.0 palindrome.0 -0 main2 main2.о: palindrome.h reverse.о: reverse.h palindrome.0: palindrome.h reverse.h
Чтобы повторно скомпилировать набор программ, запускается утилита touch: $ touch *.с ...заставить make повторно компилировать все. $ таке -f main2.make /bin/сс -с -р main2.с /bin/сс -с -р palindrome.с /bin/сс -с -р reverse.c сс -р main2.o reverse.o palindrome.о -о main2 $ Другие возможности таке make — довольно сложная утилита, которая обеспечивает обработку правил вывода и библиотек. Сведения почти обо всех возможностях, кроме возможностей поддержки библиотек, включены в эту книгу, в других книгах содержится меньше данных о make. Поэтому обратитесь к страницам man UNIX для получения дополнительной информации. Система поддержки архивов UNIX: аг С-проект среднего размера обычно содержит несколько сотен объектных модулей. Описание такого большого числа объектных модулей в правилах make-файла может оказаться довольно утомительным. Поэтому рекомендуется изучить применение утилиты поддержки архивов аг, чтобы организовывать и группировать объектные модули. Данная утилита, иногда известная как библиотекарь, позволяет выполнять следующие задачи: □ создание специального файла формата архива, заканчивающегося суффиксом ’’.а”; □ добавление, удаление, замену и присоединение любого вида файлов к архиву; □ получение оглавления архива. Синтаксис аг ключ имяАрхива {имяФайла} ★ Утилита аг позволяет создавать и управлять архивом. имяАрхива — имя файла архива, к которому пользователь желает получить доступ, должно заканчиваться суффиксом ".а". Аргумент ключ может принимать одно из значений: • d — удаляет файл из архива; • q — добавляет файл в конец архива, даже если он уже существует;
• г — добавляет файл к архиву, если его там нет, или заменяет текущую версию, если он есть; • t — посылает оглавление архива на стандартный вывод; • х — копирует список файлов из архива в текущий каталог; • v — производит многословный вывод. Когда набор объектных модулей сохраняется в файле архива, к нему можно просто обращаться из компилятора сс и загрузчика id, указывая имя файла архива в качестве аргумента. Любые объектные модули, которые необходимо взять из файла архива, автоматически компонуются. Это сильно уменьшает количество параметров, которые требуют данные утилиты при объединении большого числа объектных модулей. Создание архива Архив автоматически создается, когда добавляется первый файл. Поэтому, чтобы узнать, как создается архив, см. разд. "Добавление файла" далее. Добавление файла Чтобы добавить (или заменить) файл к указанному архиву, используйте утилиту аг с опцией г. Эта опция добавляет все указанные файлы к файлу архива имяАрхива, заменяя уже существующие файлы. Если файлАрхива не существует, он автоматически создается. Имя архива должно иметь суффикс ".а". Синтаксис аг г имяАрхива {имяФайла}+ Присоединение файла Чтобы присоединить файл к указанному архиву, используйте утилиту аг с опцией q. Эта опция добавляет все указанные файлы к файлу архива имяАрхива независимо от того, существуют они или нет. Если файл архива не существует, то автоматически создается. Опция q удобна, если известно, что файл уже существует, поскольку она позволяет утилите избежать поиска в архиве. Синтаксис ar q имяАрхива {имяФайла}+
Получение оглавления Чтобы получить оглавление архива, используйте утилиту аг с опцией t. Синтаксис ar t имя Арх ив а Удаление файла Для удаления списка файлов из архива используйте утилиту аг с опцией а. Синтаксис ar d имяАрхива {имяФайла} + Извлечение файла Для копирования списка файлов из архива в текущий каталог применяйте утилиту аг с опцией х. Если список файлов не определен, то копируются все файлы из архива. Синтаксис аг х имяАрхива {имяФайла}+ Обработка архива из командной строки Приведенный ниже пример иллюстрирует, как можно создать архив и управлять им из командной строки с помощью объектных модулей, описанных ранее в главе. Позже будет показано, как библиотека способна поддерживаться автоматически из make-файла. Сначала был создан файл архива string.a, чтобы хранить все связанные со строкой объектные модули. Затем по очереди добавлялся каждый модуль с помощью опции г. И, наконец, продемонстрированы различные опции аг. $ сс -с reverse.c palindrome.с main2.c ...создать объектный файл. $ 1s ★.о ...подтвердить. main2.o palindrome.о reverse.o $ ar г string.a reverse.o palindrome.о ...добавить в архив. ar: creating string.a $ ar t string.a ...получить оглавление.
reverse.© palindrome.о $ сс main2.o string.а -о main2 $ main2 palindrome ("cat") = О palindrome ("noon") = 1 $ ar d string.a $ ar t string.a palindrome.о $ ar r string.a $ ar t string.a palindrome.о reverse.© $ rm palindrome.о reverse.о $ Is *.o main2.о $ ar x string.a reverse.о $ Is *.o main2.©reverse.о $ reverse.о reverse.о ...скомпоновать объектные модули. ...выполнить программу. ...удалить модуль. ...подтвердить удаление. ...поместить обратно. ...подтвердить добавление ...удалить оригиналы. ...подтвердить. ...скопировать их обратно. ...подтвердить. Обработка архива при помощи утилиты таке Хотя архив может быть создан и обработан из командной строки, намного лучше применять утилиту make. Чтобы обратиться к объектному файлу в архиве, поместите после имени архива имя объектного файла внутри круглых скобок. Утилита make имеет встроенные правила, которые автоматически заботятся об операциях над архивом. Ниже приведен обновленный файл main2.make, который использует архив вместо простых объектных файлов: main2: main2.o string.a(reverse.©) string.a(palindrome.о) cc main2.o string.a -o main2 main2.o: palindrome.h string.a(reverse.o): reverse.h string.a(palindrome.o): palindrome.h reverse.h Вот вывод утилиты make, запущенной с предыдущим файлом: $ гт *.о ...удалить все объектные модули. $ make -f main2.make ...выполнить make. сс -с main2.с
ar rv string.a reverse.© ...объектный модуль сохранен. a — reverse.© ar: creating string.a rm -f reverse.c ...оригинал удален. сс -c palindrome.с ar rv string.a palindrome.о a — palindrome.о rm -f palindrome.© cc main2.o string.a -o main2 ...доступ к архивированным . . .объектным модулям. $ _ Заметьте, что встроенные make-правила автоматически удалили первоначальный объектный файл, как только он был скопирован в архив. Упорядочение архивов Встроенные make-правила не поддерживают никакого специфического порядка в файле архива. В большинстве систем это нормально, т. к. утилиты сс и id способны извлечь объектные модули и разрешить внешние ссылки независимо от порядка. Однако в некоторых старых системах такая ситуация невозможна. Вместо этого, если объектный модуль А содержит функцию, которая вызывает функцию в объектном модуле В, то В должен быть перед А в последовательности компоновки. Если А и В находятся в одной и той же библиотеке, то В должен появиться перед А в библиотеке. Если пользовательская система является одной из старых, то, вероятно, появится следующая ошибка в конце make, показанного в предыдущем примере: Id: Undefined symbol _reverse *** Error code 2 make: Fatal error: Command failed for target 'main2' Данная загадочная ошибка происходит, потому что reverse.o содержит запрос к функции palindrome о в palindrome.o. Это означает, что reverse.o должен быть, после palindrome.o в архиве. Чтобы устранить ошибку, следует либо реорганизовать модули в архиве с помощью утилит lorder и tsort, либо использовать утилиту raniib, как описано в следующем разделе. В приведенном ниже примере создана новая упорядоченная версия старого архива, а затем переименована, чтобы заменить оригинал: $ ar cr string2.a lorder string, а | tsort' $ ar t string, а ...упорядочить архив. ...старый порядок.
reverse.о palindrome.о $ ar t string2.а palindrome.о ...новый порядок. reverse.о $ mv string2.a string.a $ make -f main2d.make ...заменить старый архив. ...попытка выполнить make снова. сс main2.o string.a -о main2 $ _ Make-файл тогда отработает правильно. ДЛЯ ПОЛучеНИЯ ДОПОЛНИТеЛЬНОЙ информации Об уТИЛИТах lorder И tsort используйте ВОЗМОЖНОСТЬ man. Создание оглавления: ranlib В старых системах, в которых упорядочение является проблемой, можно помочь компоновщику обработать неупорядоченные объектные модули путем добавления оглавления к каждому архиву через утилиту ranlib. (Если ranlib не существует в системе, то не стоит волноваться о порядке.) Синтаксис ranlib {имяАрхива} + Утилита ranlib добавляет оглавление к каждому указанному архиву. (Она делает это, вставляя вход .SYMDEF в архив.) В приведенном далее примере ошибка ссылки возникла из-за неправильного порядка следования объектных модулей в архиве string.a. Компилятор сс напомнил, что следует добавить оглавление. Как вы можете видеть, компоновка прошла успешно: $ ar г string.a reverse.o palindrome.о ...этот порядок вызывает .. . проблемы. ar: creating string.a $ сс main2.o string.a -о main2 ...компилировать файлы. Id: string.a: warning: archive has no table of contents; add one using ranlib(1) Id: Undefined symbol reverse
$ ranlib string.а $ cc main2.o string.a -o main2 $ main2 ...добавить оглавление. ...нет проблемы. ...программа работает прекрасно. palindrome (’’cat’’) = 0 palindrome ("noon") = 1 $ _ Разделяемые библиотеки Статические библиотеки работают, безусловно, прекрасно для многих приложений. Однако поскольку скорость процессоров увеличилась и стоимость микросхем памяти снизилась, код стал более сложным. Таким образом, программы, связанные с большими архивами библиотек, теперь производят очень большие выполняемые файлы. (Маленькая программа, которая создает единственное Х-окно, может иметь мегабайтный размер, когда связана с требуемыми Х-библиотеками.) Для того чтобы уменьшить размер созданного объектного кода, разумно связать программу с разделяемой библиотекой (shared library). Разделяемая (или динамическая) библиотека связана с компилируемой программой, но ее функции загружаются динамически, по мере необходимости, а не все сразу во время загрузки. Результирующий объектный код меньше, потому что он не включает текст библиотеки, как это делается, когда осуществляется компоновка со статической библиотекой. Одно неудобство использования разделяемой библиотеки состоит в том, что объектный код будет написан для определенной версии библиотеки. Если код изменен, но интерфейсы не менялись, то пользовательской программе полезнее взаимодействовать с новой библиотекой, которая работает лучше. Однако если изменения сделаны в интерфейсе библиотеки, то при компоновке программы с обновленной версией библиотеки во время выполнения могут возникнуть проблемы (и, вероятно, они появятся). Поэтому важно быть осведомленным об изменениях в поддерживаемых библиотеках при написании приложения. Утилита id содержит аргументы, предписывающие ей построить разделяемую библиотеку (в системах, которые поддерживают такие библиотеки) вместо статической библиотеки. Эти аргументы изменяются в разных версиях UNIX, и необходимо свериться с документацией к ОС. Наиболее общая форма — это -shared. Можно также проинструктировать утилиту id связываться либо со статическими, либо с динамическими библиотеками, используя аргумент -В (-Bstatic ИЛИ -Bdynamic). В заВИСИМОСТИ ОТ пользовательского С-компилятора утилита сс может иметь один из указанных (или других) аргументов, чтобы создать разделяемую библиотеку во время компиляции.
Система управления исходным кодом UNIX: SCCS Чтобы должным образом поддерживать большой проект, важно уметь хранить, получать доступ и защищать все версии файлов исходных кодов. Например, предположим, что я решил изменить функцию reverse о для использования указателей ради эффективности. Было бы хорошо, если бы я мог легко вернуться и посмотреть, как исходный файл выглядел прежде, чем были сделаны изменения. Точно так же важно запретить другим пользователям вносить изменения в файл, в то время как вы активно его редактируете. Ниже представлены принципы работы системы управления исходным кодом UNIX (Source code control system, SCCS). □ Когда программист создает первоначальную версию функции, то преобразует ее в файл формата SCCS, используя утилиту admin. Файл формата SCCS сохраняется специальным способом и не может быть отредактирован или скомпилирован, как обычно. Он содержит информацию о времени и пользователе, создавшем его. Новые модификации будут содержать информацию об изменениях, которые были сделаны по сравнению с предыдущей версией. □ Всякий раз, когда необходимо отредактировать файл формата SCCS, следует сначала "оформить выдачу" (check out) самой последней версии файла, применяя утилиту get. Она создает текстовый файл стандартного формата, который можно редактировать и компилировать. Утилита get также позволяет получать предыдущую версию файла. □ Когда работа над новой версией файла завершена, следует возвратить ее в SCCS-файл посредством утилиты delta. Она оптимизирует хранение SCCS-файла, запоминая лишь различия между старой и новой версиями. Утилита get не позволяет кому-либо еще выбрать файл, пока пользователь не вернет его. □ Утилита sact предоставляет возможность наблюдать за процессом редактирования конкретного SCCS-файла. SCCS также содержит утилиты help, prs, comb, what и unget. Прежде чем мы исследуем более продвинутые опции SCCS, давайте рассмотрим типовую сессию. Учтите, что некоторые системы имеют отличный от SCCS способ работы. Поэтому следует проконсультироваться с man, чтобы определить, соответствуют ли SCCS-примеры, приведенные в данной книге, вашей версии системы SCCS. Обратите внимание, что SCCS обсуждается потому, что она поставляется с большинством версий UNIX. Другое программное обеспечение управления исходным кодом, которое может лучше удовлетворять вашим потребностям, доступно через Интернет (например, Система управ
ления модификациями GNU (Revision Control System, RCS)) и за определенную плату. Создание SCCS-файла Чтобы создать SCCS-файл, используйте утилиту admin. Синтаксис admin -1имя -flсписок -dlсписок -еимя -аимя зссз_Файл Утилита SCCS admin позволяет создавать и управлять файлом формата SCCS. Опция -i создает файл зссз^Файл из файла имя. Имя sccs-Файла должно иметь приставку ”.s". Опции -fi и -di позволяют закрывать и открывать набор перечисленных версий соответственно. Опции -а и -е предоставляют возможность добавлять и удалять указанных пользователей из списка пользователей, которым разрешается получать редактируемую версию SCCS-файла. Как только SCCS-файл будет создан, можно удалить оригинал. Если появляется ошибка от любой утилиты SCCS, необходимо вызвать SCCS-утилиту help с кодом сообщения в качестве ее аргумента. В представленном ниже примере создается SCCS-версия исходного файла reverse.c. $ Is -1 reverse.c ...посмотреть оригинал. -rw-r—г— 1 gglass 266 Jan 7 16:37 reverse.c $ admin -ireverse.c s.reverse.c ...создать SCCS-файл. No id keywords (cm7) $ help cm7 ...получить помощь о cm7. cm7: "No id keywords" No SCCS identification keywords were substituted for. You may not have any keywords in the file, in which case you can ignore this warning. If this message came from delta then you just made a delta without any keywords. If this message came from get then the last time you made a delta you changed the lines on which they appeared. It’s a little late to be telling you that you messed up the last time you made a delta, but this is the best we can do for now, and it’s better than nothing. This isn’t an error, only a warning. $ Is -1 s.reverse.c ...посмотреть SCCS-файл.
-г—г—г— 1 gglass 411 $ rm reverse.с $ Jan 7 17:39 s.reverse.с ...удалить оригинал. Синтаксис help {сообщение}+ Утилита SCCS help выводит объяснение указанных ключевых сообщений, которые, в свою очередь, генерируются другими SCCS-утилитами в случаях предупреждений или фатальных ошибок. Выборка файла Чтобы вызвать копию SCCS-файла только для чтения, используйте утилиту get. Синтаксис get -е -р -гредакция sccS—Файл SCCS-утилита get проверяет редакцию файла из его SCCS-дубликата. Если номер версии не задан, проверяется самая последняя версия. Если указана опция -е, файл подвергся изменениям и должен быть возвращен в SCCS-файл с помощью утилиты delta. Иначе он только читается и не должен возвращатся. Опция -р отправляет копию файла только для чтения на стандартный вывод; никакой файл не создается. В следующем примере проверяется копия только для чтения самой последней версии файла reverse.c: $ get s.reverse.с 1.1 29 lines No id keywords (cm7) $ Is -1 reverse.c -r—r—r— 1 gglass ...проверить копию только для чтения. ...номер версии. ...количество строк в файле. ...посмотреть копию. 266 Jan 7 18:04 reverse.c $ Утилита get показывает номер версии файла, который проверяется. В данном случае это номер версии по умолчанию — 1.1. Номер версии имеет
формат релиз.дельта. Обратите внимание, что каждый раз, когда сохраняется изменение в SCCS-файл, номер дельты увеличивается автоматически. Номер релиза изменяется явно лишь с использованием утилиты get. Пример создания нового релиза будет приведен немного позже. Когда пользователь получает версию SCCS-файла только для чтения, она не может быть отредактирована, и ничего не делается для установки запрета на доступ к файлу. Чтобы получить редактируемую версию SCCS-файла, используйте опцию -е. Она создает перезаписываемый файл и предотвращает многократные "вызовы". Следующие команды иллюстрируют использование -е: $ get -е s.reverse.с 1.1 new delta 1.2 29 lines $ Is -1 reverse.c -rw-r-xr-x 1 gglass $ get -e s.reverse.c ...получить записываемую версию. ...редактируемая версия это 1.2. ...посмотреть ее. 266 Jan 7 18:05 reverse.с, ...версия закрыта. ERROR [s.reverse.с]: writable ereverse.с* exists (ge4) $ Наблюдение SCCS-активности Утилита sact демонстрирует список процессов редактирования, связанных с указанным файлом. Синтаксис sact {sccS—Файл} + Утилита SCCS sact показывает текущий процесс редактирования по указанным SCCS-файлам. Вывод содержит версию существующей дельты, версию новой дельты, имя пользователя, который проверил файл с помощью get -е, дату и время, когда была выполнена команда get -е. Вот вывод sact от предыдущего примера: $ sact s.reverse.с ...наблюдать активность на reverse.c. 1.1 1.2 gglass 98/01/07 18:05:11 $
Отмена выборки и возврат файла Если пользователь выполняет утилиту get, а затем хочет отменить результат ее работы, следует воспользоваться утилитой unget. Синтаксис unget -гредакция -п {зссз_Файл} + Утилита SCCS unget полностью отменяет результат предыдущей работы утилиты get. Данная утилита восстанавливает SCCS-файл в его прежнее состояние, удаляет He-SCCS-версию файла и открывает файл для использования другими людьми. Если в настоящее время редактируются несколько редакций, следует использовать опцию -г для определения номера редакции, кторая подлежит отмене. По умолчанию unget поместит обратно файл в SCCS-файл. Опция -п предписывает unget копировать файл, оставляя выбранную версию на месте. В следующем примере предположим, что сначала была запущена утилита get с файлом reverse.c, а потом было решено отменить результат ее работы: $ Is -1 reverse.с -rw-r-xr-x 1 gglass $ unget s.reverse.c 1.2 $ Is -1 reverse.c reverse.c not found $ sact s.reverse.c No outstanding deltas for: $ ...посмотреть выбранный файл. 266 Jan 7 18:05 reverse.c ...вернуть его. ...версия возвращенного файла. ...оригинал ушел. ...активность над оригиналом ушла. s.reverse.с Создание новой дельты Предположим, что пользователь выбирает редактируемую версию файла reverse.c и изменяете ее так, чтобы она использовала указатели вместо индексов массива. Далее приведен листинг новой версии: 1 /* REVERSE.C */ 2 3 #include <stdio.h> 4 #include "reverse.h"
5 6 7 reverse (before, after) 8 9 char *before; 10 ch^r *after; 11 12 { 13 char* p; 14 15 p = before + strlen (before); 16 17 while (p— != before) 18 *after++ = *p; 19 20 *after = NULL; 21 } Когда новая версия файла сохранена, пользователь должен возвратить ее в SCCS-файл, используя утилиту delta. Синтаксис delta -гредакция -п {зссз_Файл} + Утилита SCCS delta возвращает проверенный файл назад в указанный SCCS-файл. Номер новой версии дельты равен старому номеру дельты плюс один. Вдобавок, delta описывает изменения, которые пользователь сделал в файле. Утилита напоминает о комментарии перед возвращением файла. Если тот же самый пользователь имеет две невыполненные версии и желает возвратить одну из них, должна использоваться опция -г, чтобы определить номер редакции. По умолчанию файл удаляется после возвращения. Опция -п предотвращает это. Пример: $ delta s.reverse.с ... возврат модифицированной версии. comments? converted the function to use pointers ...комментарий. No id keywords (cm7) 1.2 ...номер новой версии. 5 inserted ...описание модификаций.
7 deleted 16 unchanged $ Is -1 reverse.c ...оригинал был удален, reverse.c not found $ _ Получение истории файла Чтобы получить листинг истории модификации SCCS-файла, используйте утил иту prs. Синтаксис prs -гper акция {зссз_Файл} + SCCS утилита prs выводит историю, связанную с указанными SCCS-файлами. По умолчанию демонстрируется история всего файла. Можно ограничить вывод конкретной версией, используя опцию -г. Числа в правой колонке вывода относятся к номеру строк, вставленных, удаленных и сохраненных неизменными соответственно. Пример: $ prs s.reverse.с ...вывести историю, s.reverse.c: D 1.2 98/01/07 18:45:47 gglass 2 1 00005/00007/00016 MRs: COMMENTS: converted the function to use pointers D 1.1 98/01/07 18:28:53 gglass 1 0 00023/00000/00000 MRs: COMMENTS: date and time created 98/01/07 18:28:53 by gglass $ Идентифицирующие ключевые слова SCCS В исходный файл могут быть помещены и обработаны утилитой get несколько специальных последовательностей символов, когда получаются версии копий только для чтения. В табл. 12.1 перечислены наиболее общие прел едовательности. Удобно помещать эти последовательности в комментарий 17 Зак. 786
в начале исходного файла. Комментарий не затронет исходный код программы и будет виден при чтении файла. Таблица 12.1. Идентифицирующие ключевые последовательности SCCS Последовательность Заменяется на %м% имя файла исходного кода %i% номер релиз. дельта. ветвь, последовательность %D% текущую дату %Н% текущий час о гр о. ъ 1 ъ текущее время Создание нового релиза Чтобы создать новый релиз SCCS-файла, следует определить его номер через опцию -г утилиты get. Новый номер релиза основан на самой современной версии предыдущего релиза. В представленном ниже примере создается релиз 2 из файла reverse.c и добавляются идентификационные ключевые слова SCCS, описанные в предыдущем разделе: $ get -е ~r2 s. reverse.c ...выбрать версию 2. 1.2 ...номер предыдущей версии. new delta 2.1 ...номер новой версии. 21 lines $ vi reverse.c ...редактировать перезаписываемую копию. . . .Я добавил следующие строки в начале программы: /* Module: %М% SCCS Id: %I% Time: %D% %T% * / ...и затем сохранил файл. $ delta s.reverse.с ...вернуть новую версию, comments? added SCCS identification keywords 2.-1 6 inserted 0 deleted 21 unchanged $ get -p s.reverse.c ...вывести файл на стандартный вывод. 2.1 /* REVERSE.Н */
/* Module: reverse.с SCCS Id: 2.1 Time: 98/01/07 22:32:38 */ ...остаток файла $ _ Обратите внимание, что ключевые слова были замещены, когда была получена копия версии 2 только для чтения. Выборка копий только для чтения предыдущих версий Чтобы выбрать другую, а не самую последнюю версию, используйте опцию -г утилиты get для задания номера версии. Например, следует получить копию только для чтения версии 1.1 файла reverse.c. Вот как это сделано: $ get -г 1.1 s.reverse.с ...выборка версии 1.1. 1.1 23 lines $ Выборка редактируемых копий предыдущих версий Если пользователь желает получить редактируемую копию предыдущей версии, следует использовать опции -е или -г утилиты get. Предположим, что необходимо получить редактируемую копию версии 1.1 файла reverse.c. Номер версии редактируемой копии не может быть 1.2, т. к. эта версия уже существует. Вместо этого утилита get создает новую "ветку" из версии 1.1, пронумерованную 1.1.1.1 (рис. 12.4). Дельты, добавленные к этой ветке, нумеруются 1.1.1.2, 1.1.1.3 и т. д. 1.1 ------------► 1.2 Ветка 1.1.1.1 -----------► 1.1.1.2 Рис, 12,4, "Ветвление" дельты
Пример: $ get -е -г1.1 s.reverse.с ...создать ветку версии 1.1. 1.1 . new delta 1.1.1.1 23 lines $ _ Редактирование различных версий Допустимо одновременное редактирование различных редакций файла. Однако следует определить номер редакции файла, который возвращается, когда выполняется утилита delta. Необходимо также переименовать копию, когда пользователь получает другую копию, т. к. всем копиям присваивается одно и то же имя. В приведенном ниже примере возвращаются копии версий 1.1 и 2.1 для редактирования, а затем обе сохраняются: $ get -е -г1.1 s.reverse.с ...редактировать новую версию на основе 1.1. 1.1 new delta 1.1.1.1 23 lines $ mv reverse.c reverse2.c ... переименовать версию 1.1.1.1. $ get -e -r2.1 s. reverse.c ...редактировать новую версию на основе 2.1. 2.1 new delta 2.2 27 lines $ sact s.reverse.c ...показать SCCS-активность. 1.1 1.1.1.1 gglass 98/01/07 22:42:26 2.1 2.2 gglass 98/01/07 22:42:49 $ delta s.reverse .c ... неясный возврат. comments? try it ERROR [s.reverse.c]: missing -r argument (del) $ delta -r2.1 s.reverse.c ...возврат модифицированной версии 2.1. comments? try again 2.2 0 inserted 0 deleted 27 unchanged $ mv reverse2.c reverse.c ...переименовать другую версию.
$ delta s.reverse.с ...однозначный возврат. comments? save it No id keywords (cm7) 1.1.1.1 0 inserted 0 deleted 23 unchanged $ sact s.reverse.c No outstanding deltas for: s.reverse.c $ Удаление версий Можно удалять дельту из SCCS-файла, когда она является листом в дереве SCCS-версий, с помощью утилиты rmdei. В примере, изображенном на рис. 12.5, не было разрешено удалить версию 1.1, поскольку она не является листом, а версия 1.1.1.1 удалилась успешно. Команды таковы: $ rmdei ~rl.1 s.reverse.с ...попытка удалить 1.1. ERROR [s.reverse.с]: not a ’leaf delta(rc5) $ rmdei -rl.1.1.1 s.reverse.c ...удалить 1.1.1.1. $ Синтаксис rmdei -гредакция зссз_Файл Утилита rmdei удаляет указанную версию из SCCS-файла, когда она является листом. Не может быть удалена Л.1 > 1.2 Ветка 1.1.1.1 Рис. 12.5. Только листья могут быть удалены
Сжатие SCCS-файлов Существует возможность сжатия SCCS-файла и удаления любых ненужных дельт с помощью утилиты comb. Синтаксис comb {s ссз_Файл} + Утилита comb сжимает SCCS-файл так, чтобы он содержал лишь самую последнюю исходную версию. Остаются самая последняя дельта и дельты, которые имеют ветви. Утилита работает, генерируя скрипт Bourne shell, который должен быть впоследствии запущен, чтобы выполнить фактическое сжатие. Скрипт посылается в стандартный вывод. Поэтому его следует сохранить и затем выполнить. Рассмотрим пример. Предположим, что файл s.reverse.c содержал несколько различных версий. Ниже осуществляется его сжатие в меньший файл, содержащий только самую последнюю версию: $ comb s.reverse.c > comb.out ...создать скрипт. $ cat comb.out ...посмотреть скрипт. trap "rm -f COMB$$ comb$$ s.COMB$$; exit 2" 1 2 3 15 get —s -k -r2.3 -p s.reverse.c > COMB$$ ...другие строки идут здесь. rm comb$$ rm -f s.reverse.c mv s.COMB$$ s.reverse.c admin -dv s.reverse.c $ chmod +x comb.out ...сделать скрипт исполняемым. $ comb.out ...выполнить скрипт. $ prs s.reverse.c ...посмотреть историю, s.reverse.c: D 2.3 98/01/08 15:35:53 gglass 1 0 00028/00000/00000 MRs: COMMENTS: This was COMBined
Ограничение доступа к SCCS-файлам По умолчанию файл из SCCS-файла способен выбрать любой пользователь. Однако можно ограничить доступ одному или нескольким пользователям (включая ГРУППЫ Пользователей) С ПОМОЩЬЮ ОПЦИЙ -а И -е УТИЛИТЫ admin. Опция -а может сопровождаться: П пользовательским именем; в этом случае пользователь добавляется к списку пользователей, которые могут выбирать файл; □ номером группы; в этом случае любой пользователь в группе может выбирать файл. Если значению предшествует символ 1, тогда указанный пользователь лишается права выбора. Если применяется С shell, следует убедиться, что введен символ ! для предотвращения случайной ссылки на список истории. Для удаления пользователя из списка применяется опция -е. Повторяющиеся Ьпции -а и -е могут появляться в отдельной командной строке. В приведенном далее примере устанавливается запрет на собственные права доступа пользователя, а затем они восстанавливаются. $ admin -а\!glass s.reverse.с ...удалить права от пользователя glass. $ get -е s.reverse.с ...попытка доступа. 2.3 ERROR Гs.reverse.c]: not authorized to make deltas (col4) $ admin -aglass s.reverse.c $ get -e s.reverse.c 2.3 new delta 2.4 28 lines $ unget s.reverse.c 2.4 $ admin -atim s.reverse.c $ admin -eglass s.reverse.c $ get -e s.reverse.c 2.3 ...восстановить доступ. ...нет проблемы. ...вернуть файл. ...добавить tim в список пользователей. ...лишить glass прав доступа. ...попытка доступа. ERROR [s.reverse.с]: not authorized to make deltas (col4) $ admin -aglass s.reverse.c $ admin -al82 s.reverse.c $ ...восстановить права glass. ...дать права группе 182.
Блокировка релизов Можно защищать отдельный релиз или все релизы от редактирования с помощью опций -fl и-dl утилиты admin. Для блокировки конкретного релиза следует указать за опцией -fl номер релиза. Для блокировки всех релизов — за опцией -fl символ а. Чтобы разблокировать, необходимо использовать те же самые правила, но с опцией -di. Пример: $ admin -fla s.reverse.c ...блокировать все релизы. $ get -е -r2.1.s.reverse.с ...попытка доступа. 2.1 ERROR [s.reverse.с]: SCCS file locked against editing (co23) $ admin -dla s.reverse.c ...разблокировать все. $ get -e -rl.l s.reverse.c ...нет проблемы. 1.1 new delta 1.1.1.1 21 lines $ _ Профайлер UNIX: prof Часто необходимо знать, как программа тратит время на свое выполнение. Например, если на работу конкретной функции тратится времени больше, чем ожидается, тогда, вероятно, стоит оптимизировать функцию вручную для улучшения производительности. Утилита prof позволяет получать профиль программы. Синтаксис prof -In [ исполняемыйФайл [ профайл ] ] prof — стандартный UNIX-профайлер. Он генерирует таблицу, которая указывает время, потраченное каждой функцией на обработку, и число вызовов функции в исполняемом файле исполняе^шйФайл, основываясь на трассеровке эффективности, сохраняемой в файле профайл. Если аргумент профайл опущен, предполагается файл mon.out. Если аргумент исполняемыйФайл отсутствует, используется файл a.out. Исполняемый файл должен быть скомпилирован с помощью опции -р утилиты сс. Опция позволяет генерировать специальный код, который пишет файл mon.out во время работы программы. Далее утилита prof разбирает выходной файл после окончания работы программы и отображает содержа
щуюся в нем информацию. По умолчанию информация профиля выводится в убывающем порядке времени. Опция -1 определяет вывод информации по имени, опция -п — по совокупному времени. Пример prof в действии: $ main2 ...выполнить программу. palindrome ("cat") = 0 palindrome ("noon") = 1 • ..вывод программы. $ Is -1 mon.out -rw-r-xr-x 1 gglass • • .посмотреть 1472 Jan вывод монитора. 8 17:19 mon.out $ prof main2 mon.out • ..получить профиль программы. %Time Seconds Cumsecs 42.9 0.05 0.05 #Calls msec/call Name rdpcs - 42.9 0.05 0.10 2002 0.025 reverse 14.3 0.02 0.12 2002 0.008 palindrome 0.0 0.00 0.12 1 0. main $ prof -1 main2 ..порядок по именам. %Time Seconds Cumsecs #Calls msec/call Name 0.0 0.00 0.05 1 0. main 14.3 0.02 0.07 42.9 0.05 0.05 2002 0.008 palindrome rdpcs 42.9 0.05 0.12 $ . 2002 0.025 reverse После того как профиль будет просмотрен, можно выполнить некоторую ручную настройку, а затем получить другой профиль. Перепроверка программ: lint Язык С имеет удобную утилиту lint, которая проверяет пользовательскую программу более тщательно, чем это делает компилятор сс. Если программа состоит из нескольких исходных модулей, лучше всего специфицировать их в одной командной строке так, чтобы lint могла проверить взаимодействие между модулями. Синтаксис lint {имяФайла}* Утилита lint просматривает указанные исходные файлы и выводит любые найденные потенциальные ошибки.
Пример, который демонстрирует различие между проверкой единственного модуля и проверкой множества модулей, приведен ниже: $ lint reverse.c ...проверить reverse.c. reverse defined( reverse.с(12) ), but never used $ lint palindrome.с ...проверить palindrome.c. palindrome defined( palindrome.c(12) ), but never used reverse used( palindrome.c(14) ), but not defined $ lint main2.c ...проверить main2.c. main2.c(ll): warning: main() returns random value to invocation environment printf returns value which is always ignored palindrome used( main2.c(9) ), but not defined $ lint main2.c reverse.c palindrome.с ...проверить все модули вместе. main2.c: main2.c(ll): warning: main() returns random value to invocation environment reverse.c: palindrome.c: Lint pass2: printf returns value which is always ignored $ _ Отладчик UNIX: dbx UNIX-отладчик dbx позволяет отлаживать программы символически. Несмотря на то, что этот отладчик не может конкурировать с большинством профессиональных отладчиков, dbx поставляется как удобная стандартная утилита в большинстве версий UNIX. Она обладает следующими возможностями: □ выполнение в пошаговом режиме; □ точки останова; □ редактирование из отладчика; □ доступ и изменение переменных; □ поиск функций; □ трассировка.
Синтаксис dbx исполняемыйФайл Утилита dbx — ЭТО стандартный отладчик UNIX. Указанный исполняемыйФайл загружается в отладчик, и появляется приглашение для пользователя. Чтобы получить информацию о различных командах dbx, введите help в ответ на приглашение. Чтобы продемонстрировать работу утилиты dbx, рассмотрим пример, в котором выполняется отладка следующей рекурсивной версии palindrome о: 1 /* PALINDROME.С */ 2 3 tfinclude "palindrome.h" 4 #include <string.h> 5 6 7 enum { FALSE, TRUE }; 8 9 10 int palindrome (str) 11 12 char *str; 13 14 { 15 return (palinAux (str, 1, strlen (str))); 16 } 17 18 /★★*★★***★********★**★**★*********************************/ 19 20 int palinAux (str, start, stop) 21 22 char *str; 23 int start; 24 int stop; 25 26 } 27 if (start >= stop)
28 return (TRUE); 29 else if (str[start] != strfstop]) 30 return (FALSE); 31 else 32 return (palinAux (str, start + 1, stop — 1)); 33 } Подготовка программы для отладки Для отладки программу необходимо скомпилировать с пОмощью опции -д компилятора сс, которая помещает отладочную информацию в объектный модуль. Вход в режим отладки Если программа скомпилировалась правильно, можно вызвать dbx с именем исполняемого файла в качестве первого аргумента, dbx покажет приглашение. Рекомендуется в ответ на приглашение ввести help, чтобы увидеть список всех команд dbx3: $ dbx main2 ...вызвать отладчик. dbx version srl0.3(4) of 7/6/90 17:52 reading symbolic information Туре 'help' for help. (dbx) help ...получить помощь. run [args] - начинает выполнение программы stop at <line> - приостанавливает выполнение на строке stop in <func> - приостанавливает выполнение, когда вызывается <func> stop i f <cond> - приостанавливает выполнение, когда <cond> истинно trace <line#> - трассировка выполнения строки trace <func> - трассировка вызовов функции trace <var> - трассировка изменений переменных trace <exp> at <line#> - печатает <ехр>, когда <line> достигнута status - печатает пронумерованный список трассировок и останавливается delete <#> [<#> ...] - отменяет трассировку или остановку каждого указанного номера 3 Ниже приводится перевод. — Ред.
cont step next return call <func>(<params>) print <exp> [, <exp> ...] where whatis <name> assign <var> = <exp> dump <func> list [<line#>], <line#>> use <directory-list> sh <connmand-line> quit (dbx) - продолжает выполнение с места остановки - выполняет одну исходную строку, входит в функции - выполняет одну исходную строку, игнорирует вызовы - продолжает до возврата из текущей функции - выполняет данный вызов функции - печатает значения выражений - печатает текущие активные процедуры - выводит описание имени - присваивает переменной программы значение <ехр> - печатает все переменные активной функции — выводит исходные строки - устанавливает путь поиска для исходных файлов - передает командную строку shell - выходит из dbx Выполнение программы Для выполнения программы следует ввести команду run, которая выполняет программу до завершения: (dbx) run . . .выполнить программу. palindrome ("cat") = О palindrome ("noon") = О program exited (dbx) _ On! Строка "noon" — это палиндром, но функция palindrome () выдает иной результат. Пришло время тщательно исследовать dbx. Трассировка программы Чтобы получить трассировочный список, строка за строкой, необходимо ввести команду trace. Когда требуется любой вид трассировки, dbx возвращает числовой указатель, который может использоваться командой delete, чтобы выключить трассировку.
Продолжим рассмотрение примера: программа запускается повторно с помощью команды rerun: (dbx) trace ...запрос трассы. [1] trace ...запрос 1. (dbx) rerun ...выполнить программу с начала. trace: 9 printf ("palindrome (\"cat\") = %d\n", palindrome ("cat")); trace: 10 int palindrome (str) trace: 15 return (palinAux (str, 1, strlen (str))); trace: 20 int palinAux (str, start, stop) trace: 27 if (start >= stop) trace: 29 else if (str[start] != str[stop]) trace: 30 return (FALSE); trace: 33 } trace: 33 } trace: 16 } palindrome ("cat") = 0 trace: 10 printf ("palindrome (\"noon\") = %d\n", palindrome ("noon") trace: 10 int palindrome (str) trace: 15 return (palinAux (str, 1, strlen (str))); trace: 20 int palinAux (str, start, stop) trace: 27 if (start >= stop) trace: 29 else if (str[start] != str[stop]) trace: 30 return (FALSE); trace: 33 } trace: 33 } trace: 16 } palindrome ("noon") = 0 trace: 11 } trace: 11 } program exited (dbx) _ Трассировка переменных и вызовов функции Трассировка может быть выполнена для значения переменной или вызова конкретной функции путем добавления параметров к команде trace.
Синтаксис trace переменная in функция Для отслеживания вызовов указанной функции используйте синтаксис: trace функция Ниже представлен вывод dbx после того, как были выполнены три новых трассировки, а затем программа была повторно запущена: (dbx) trace start in palinAux ...трассировка переменной start. [2] trace start in palinAux (dbx) trace stop in palinAux ...трассировка переменной stop. [3] trace stop in palinAux (dbx) trace palinAux ...трассировка функции palinAux. [4] trace palinAux (dbx) rerun ...запуск программы с начала. trace: 9 printf ("palindrome (\"cat\") = %d\n", palindrome ("cat")); trace: 10 int palindrome (str) trace: 15 return (palinAux (str, 1, strlen (str))); trace: 20 int palinAux (str, start, stop) calling palinAux (str = "cat", start = 1, stop = 3) from function palindrome.palindrome trace: 27 if (start >= stop) initially (at line 27 in "/home/glass/reverse/palindrome.c"): start = 1 initially (at line 27 in "/home/glass/reverse/palindrome.c"): stop = 3 trace: 29 else if (str[start] != str[stop]) trace: 30 return (FALSE); trace: 33 ) trace: 33 } trace: 16 } palindrome ("cat") = 0 trace: 10 printf ("palindrome (\"noon\") = %d\n", palindrome ("noon")); trace: 10 int palindrome (str) trace: 15 return (palinAux (str, 1, strlen (str))); trace: 20 int palinAux (str, start, stop) after line 20 in "/home/glass/reverse/palindrome.c": stop = 4 calling palinAux(str = "noon", start = 1, stop = 4) from function
palindrome, palindrome trace: 27 if (start , = stop) trace: 29 else if (strfstart] != str[stop]) trace: ' 30 return (FALSE); trace: 33 } trace: 33 } trace: 16 } palindrome ("noon") = 0 trace: 11 } trace: 11 } program exited (dbx) Ошибки Теперь природа ошибок достаточно ясна: значения start и stop неправильны, каждое больше чем оно должно быть. Это весьма обычная ошибка, когда программист забывает, что индексирование элементов массива в языке С начинается с нуля, а не с единицы. Можно вызвать редактор, указанный переменной окружения $editor, используя команду edit. Это удобно для исправления ошибок налету, хотя следует помнить, что нужно повторно скомпилировать программу перед ее повторной отладкой. Вот правильная версия функции palindrome () : int palindrome (str) char *str; 4 return (palinAux (str, 0, strlen (str) — 1)); } Далее рассматриваются некоторые полезные команды dbx для задания точек останова, пошагового выполнения, организации доступа к переменным и вывода частей программы. Точки останова Для остановки отладчика dbx при обнаружении вызова конкретной функции служит команда stop. Она позволяет выполнять программу с полной скоростью до той функции, которую необходимо проверить более тщательно. Например: (dbx) stop in palinAux ...добавить точку останова. [7] stop in palinAux (dbx) rerun ...запустить программу сначала.
trace: 9 printf (’’palindrome (\’’cat\") = %d\n", palindrome (’’cat”)) ; trace: 10 int palindrome (str) trace: 15 return (palinAux (str, 1, strlen (str))); trace: 20 int palinAux (str, start, stop) calling palinAux(str = "cat", start = 1, stop = 3) from function palindrome.palindrome [7] stopped in palinAux at line 27 in file ’’/home/glass/reverse/pal indrome. c" 27 if (start >= stop) (dbx) Пошаговое выполнение Чтобы выполнить программу по одной строке за один шаг, используется команда step. Она предписывает отладчику dbx повторно показывать свое приглашение сразу после того, как очередная строка программы будет выполнена. В следующем примере выполняется команда step после того, как программа остановилась на строке 27: (dbx) step ...выполнить строку после 27-й и остановиться. trace: 29 else if (str[start] != str[stop]) initially (at line 29 in ’’/home/glass/reverse/palindrome.c"): start = 1 initially (at line 29 in ’’/home/glass/reverse/palindrome.c"): stop = 3 stopped in palinAux at line 29 in file "/home/glass/reverse/palindrome.c" 29 else if (str[start] != strfstop]) (dbx) Организация доступа к переменным Для вывода значения конкретной переменной применяется команда print. Команда whatis показывает объявление переменной, а команда which сообщает, где переменная объявлена. Команда where показывает полную трассу стека, а команда whereis сообщает, где расположена конкретная функция. Перечисленные команды демонстрируются в следующем примере: (dbx) print start ...вывести текущее значение start. 1 (dbx) whatis start ...получить информацию о типё.
int start; (dbx) which start . ..найти ее расположение, palindrome.palinAux.start (dbx) where ...получить трассу стека. palinAux(str = "cat", start = 1, stop = 3), line 29 in "/home/glass/reverse/palindrome.c" palindrome.palindrome(str = "cat"), line 15 in "/home/glass/reverse/palindrome.c" main(), line 9 in "/home/glass/reverse/main2.c" unix_$main() at 0x3b4e7al4 _start(), line 137 in ”//garcon/unix_src/lang/sgs/src/crtO/crtO.c" (dbx) whereis palinAux ...определить место функции, palindrome.palinAux (dbx) whereis start ...определить место переменной, palindrome.palinAux.start (dbx) _ Листинг программы Команда list позволяет вывести первые несколько строк функции, а команды / и ? предоставляют возможность осуществить поиск, соответственно, вперед и назад по тексту, как показано в следующем примере: (dbx) list palindrome ...вывести десять строк. 5 6 7 enum { FALSE, TRUE }; х 8 9 10 int palindrome (str) 11 12 char* str; 13 14 { 15 return (palinAux (str, 1, strlen (str))); (dbx) list 10,20 ...вывести строки с 10 по 20. 10 int palindrome (str) 11 12 char* str; 13 14 }
15 return (palinAux (str, 1, strlen (str))); 16 { 17 18 /**************************^***i************************** 19 20 int palinAux (str, start, stop) 21 (dbx) ?palinAux ...поиск назад строки "palinAux". 20 int palinAux (str, start, stop) (dbx) /palinAux ...поиск вперед строки "palinAux". 32 return (palinAux (str, start + 1, stop — 1)); (dbx) _ r Выход из отладчика Для выхода из отладчика dbx служит команда quit: (dbx) quit ...покинуть отладчик. $ _ Возврат осуществляется в командный интерпретатор shell. Резюме В разд. "Отладчик UNIX: dbx"я представил краткое изложение часто используемых команд dbx. Они могут дать полезные подсказки об ошибках в разработанной программе. По моему собственному мнению, dbx— довольно бедный отладчик по сравнению с некоторыми популярными РС-отладчиками, типа Turbo Debugger от Borland, и развитых системных отладчиков UNIX, типа ObjectWorks\C ++. С другой стороны, dbx доступен на большинстве UNIX-машин, что делает элементарное понимание его действий полезным. Удаление лишнего кода: strip Утилиты отладчика и профайлера требуют, чтобы компиляция программы выполнялась с помощью специальных опций, каждая из которых добавляет код к исполняемому файлу. Для того чтобы удалить этот дополнительный код после завершения отладки и профилирования, можно применять утилиту strip.
Синтаксис strip {имяФайла} + Утилита strip удаляет все: таблицу символов, информацию перераспределения, отладки и профилирования из указанных файлов. Пример экономии места: $ Is -1 main2 ...посмотреть оригинальный файл, -rwxr-xr-x 1 gglass 5904 Jan 8 22:18 main2* $ strip main2 ...убрать побочную информацию. $ Is -1 main2 ...помотреть очищенную версию. -rwxr-xr-x 1 gglass 3^73 Jan 8 23:17 main2* $ Обзор главы Перечень тем В этой главе вы познакомились с утилитами, которые: □ компилируют С-программы; П управляют компиляцией программ, состоящих из многих модулей; □ поддерживают архивы; □ поддерживают множественные версии исходного кода; П профилируют исполняемые файлы; □ отлаживают исполняемые файлы. Контрольные воросы 1. Почему полезно иметь историю своего исходного кода? 2. Каково определение концевой вершины на SCCS-дереве дельты? 3. Каково преимущество опции -q утилиты аг? 4. Может ли утилита make использовать объектные модули, сохраненные в файле архива? 5. Что означает термин "функция многократного использования"? 6. Назовите причины профилирования исполняемого файла. 7. Опишите кратко, что делает утилита strip.
Упражнения 12.1. Создайте разделяемую библиотеку с простой функцией, которая возвращает значение целого числа. Затем напишите программу, чтобы вызвать функцию и напечатать ее возвращаемое значение. После скомпилируйте и выполните программу, внесите изменение в библиотечную функцию, перестройте библиотеку и выполните программу снова (без повторной компиляции главной программы). Что происходит, если вы переименовываете функцию в разделяемой библиотеке? [Уровень: легкий.] 12.2. Скомпилируйте файлы reverse.c и palindrome.c и поместите их в архив string.a. Напишите главную программу в файле prompt.c, которая выводит приглашение пользователю для ввода строки, а затем выводит 1, если строка является палиндромом, и 0 — в противном случае. Создайте make-файл, который связывает prompt.о с функциями reverse о и palindrome о, сохраненными в файле string.a. Если необходимо, используйте утилиту dbx для отладки кода. [Уровень: средний.] 12.3. Попробуйте поработать с современным отладчиком типа отладчика исходного кода Borland C++. Каков он по сравнению с dbx? [Уровень: средний.]. Проекты 1. Напишите документ, в котором укажите, как бы вы использовали утилиты, представленные в этой главе, чтобы помочь управлять командой из 10 человек, работающей с вычислительной техникой. [Уровень: средний.] 2. Замените первоначальную версию palindrome о, сохраненную в программе palindrome, версией на основе указателя. Используйте SCCS для управления изменениями исходного кода и утилиту аг для замены старой версии в файле string.a. [Уровень: средний.]

Глава 13 Системное программирование Мотивация Если вы С-программист, и хотите воспользоваться преимуществом возможностей многозадачности и взаимодействия процессов UNIX, весьма важно, чтобы вы хорошо знали системные вызовы UNIX. Предпосылки Чтобы понять материал данной главы, необходимо иметь хороший опыт программирования на С. Для получения дополнительных сведений об Интернете обратитесь к главам 9 и 10. Задачи В этой главе объясняется и демонстрируется большинство системных вызовов UNIX, в том числе поддерживающих ввод/вывод, управление процессами и взаимодействие процессов. Изложение Информация представлена в форме нескольких примеров программ, включая пример командного интерпретатора shell, разработанного для Интернета. Большинство кодов доступно в сети (для дополнительной информации см. Введение).
Системные вызовы и библиотечные процедуры i В главе представлены следующие системные вызовы и библиотечные процедуры: accept fchown inet__addr perror alarm fcntl inet_ntoa pipe bind fork ioctl read bzero f stat kill- setegid chdir ftruncate 1chown seteuid chmod getdents link setgid chown getegid listen setpgid close geteuid Iseek setuid connect getgid Istat signal dup gethostbyname memset socket dup2 gethostname mknod stat execl getpgid nice sync execlp getpid ntohl truncate execv getppid ntohs unlink execvp getuid open wait exit htonl pause write f chmod htons Общие сведения Чтобы использовать такие возможности, как создание файла, дублирование процесса и взаимодействие между процессами, прикладные программы должны "взаимодействовать" с операционной системой. Они могут делать это через совокупность процедур, называемых системными вызовами, которые являются функциональным интерфейсом программиста к ядру UNIX. Системные вызовы аналогичны библиотечным процедурам, за исключением того, что первые выполняют вызов подпрограммы непосредственно в сердце UNIX. Системные вызовы UNIX могут быть объединены в следующие условные главные категории: □ управление файлами; □ управление процессами; □ обработка ошибок.
Взаимодействие процессов (Interprocess communication, IPC) фактически является подмножеством управления файлами, поскольку UNIX обрабатывает механизмы IPC, как специальные файлы. На рис. 13.1 представлена диаграмма, которая иллюстрирует иерархию системных вызовов управления файлами. Рис. 13.1. Иерархия системных вызовов управления файлами Иерархия системных вызовов управления процессами включает процедуры для дублирования, отделения и завершения процессов, как показано на рис. 13.2. Единственный системный вызов, поддерживающий обработку ошибки, реггого, я поместил в иерархию только, чтобы быть последовательным (рис. 13.3). Рис. 13.2. Иерархия системных вызовов управления процессами
Обработка ошибки реггог Рис. 13.3. Иерархия обработки ошибки Далее системные вызовы, представленные на этих диаграммах иерархии, будут обсуждаться в следующем порядке. □ Обработка ошибки. Я начинаю главу с описания реггог (). П Обычное управление файлами. Оно включает сведения о создании, открытии, закрытии, чтении и записи обычных файлов. Мы также увидим краткий обзор STREAMS. П Управление процессами. Здесь существенны дублирование, отделение, приостановка и прекращение процессов. Кратко обсуждаются многопр-точные процессы. □ Сигналы. Хотя возможности сигналов можно рассматривать, как часть либо управления процессами, либо взаимодействия между процессами, это достаточно серьезная тема, чтобы выделить ее в специальный раздел. □ IPC. Взаимодействие процессов осуществляется через конвейеры (как именованные, так и не имеющие имени) и сокеты (включая информацию об интернет-сокетах). Представлен краткий обзор двух механизмов IPC, встречающихся в некоторых версиях UNIX: разделяемая память и семафоры. Глава заканчивается листингом исходного кода и обсуждением проекта Internet shell, который является командным интерпретатором, поддерживающим конвейеризацию и перенаправление на другие Internet shell на удаленных хостах. Программа Internet shell использует большинство возможностей, описанных в этой главе. Обработка ошибки: реггогО Большинство системных вызовов способно определенным образом завершиться с ошибкой. Например, системный вызов open о будет неудачным, если вы пробуете открыть для чтения несуществующий файл. В соответствии с соглашением, все системные вызовы возвращают ~ 1, если происходит ошибка. Однако трудно понять, почему произошла ошибка; системный вызов open () может потерпеть неудачу по нескольким различным причи
нам. Если вы хотите детализировать ошибки системных вызовов, то должны знать две вещи: □ существует глобальная переменная errno, которая хранит числовой код * последней ошибки системного вызова; □ подпрограмма реггог о описывает ошибки системного вызова. Каждый процесс содержит глобальную переменную errno, которая первоначально равна нулю при создании процесса. Когда происходит ошибка системного вызова, в errno устанавливается числовой код, связанный с причиной ошибки. Например, если пользователь пробует открыть для чтения файл, который не существует, errno будет равна 2. Файл /usr/include/sys/errno.h содержит список предопределенных кодов ошибки. Вот отрывок этого файла: #define #define EPERM ENOENT 1 2 /* Нет владельца */ /* Нет такого файла или каталога */ #define ESRCH 3 /* Нет такого процесса */ #define EINTR 4 /* Прерванный системный вызов */ #define EIO 5 /* Ошибка ввода/вывода */ Успешный системный вызов никогда не затрагивает текущее значение errno, а неудачный системный вызов всегда переписывает ее значение. Для того чтобы из программы получить доступ к переменной errno, включите заголовок <errno.h>. Подпрограмма реггого разъясяет по-английски те кущее значение errno. Синтаксис void реггог (char* str) Библиотечная процедура реггог о показывает строку str, снабженную двоеточием и сопровождаемую описанием последней ошибки системного вызова. Если ошибки нет, реггог о выводит строку "Error 0". Фактически реггог () — это не системный вызов, а стандартная процедура библиотеки С. Ваша программа должна проверять системные вызовы на возвращаемое значение —1, а затем немедленно обработать ситуацию. Для начала необходимо вызвать реггог о для описания ошибки, особенно во время отладки. В представленном ниже примере намеренно создана пара ошибок системного вызова, чтобы продемонстрировать работу реггого, а затем показано, что переменная errno сохранила последний код ошибки системного вызова
даже после того, как был сделан успешный вызов: единственный способ повторно установить errno — это вручную назначить ей 0. $ cat showErrno.c #include < stdio.h> ttinclude <sys/file.h> #include <errno.h> main () { int fd; /* Открыть несуществующий файл, чтобы вызвать ошибку */ fd - open ("nonexist.txt", O_RDONLY); v if (fd — -1) /* Ошибка произошла */ { printf ("errno = %d\n", errno); perror '("main"); } fd = open ("/", O—WRONLY); /* Вызвать другую ошибку */ if (fd = -1) printf ("errno = %d\n", errno); perror ("main"); } /* Выполнить успешный системный вызов */ fd = open ("nonexist.txt", O_RDONLY | O_CREAT, 0644); printf ("errno = %d\n", errno);/* Вывести после успешного вызова */ perror ("main"); errno =0; /* Вручную установить переменную ошибки */ perror ("main"); } Не волнуйтесь о том, как работает системный вызов open (). Он будет описан позже в данной главе *. Вот вывод программы: $ showErrno ...выполнить программу, errno = 2 main: No such file or directory errno = 21 ...даже после успешного вызова main: Is a directory 1 См. разд. "Открытие файла: open() " далее в этой главе. — Ред.
errno = 21 main: Is a directory main: Error 0 $ _ Управление обычными файлами Мое описание системных вызовов управления файлами разбивается на четыре главных части: П вводная часть, которая представляет главные концепции файлов UNIX и дескрипторов файлов; □ описание основных системных вызовов управления файлами с применением демонстрационной программы reverse, которая переставляет символы в строках и сами строки файла; □ объяснение нескольких расширенных системных вызовов с использованием демонстрационной программы monitor, которая периодически просматривает каталоги и выводит имена файлов, которые изменились со времени последнего просмотра; □ описание оставшихся системных вызовов управления файлами с помощью различных отрывков исходного кода. Общие сведения об управлении файлами Системные вызовы управления файлами позволяют манипулировать полной совокупностью обычных файлов, каталогами и специальными файлами, включая: □ файлы на дисках; □ терминалы; П принтеры; □ файлы взаимодействия между процессами, такими как конвейеры и сокеты. В большинстве случаев системный вызов open () служит для первоначального доступа или создания файла. Если open о выполняется успешно, он возвращает маленькое целое число, называемое дескриптором файла, которое используется в последующих операциях ввода/вывода с этим файлом. Если open о завершается с ошибкой, возвращается —1. Ниже представлен отрывок кода, который иллюстрирует типичную последовательность событий: int fd; /* Дескриптор файла */
fd = open (fileName, Открыть файл, возвращает дескриптор файла */ if (fd == -1) { /* Обработать условия ошибки */ } fcntl (fd, ...); /* Установить некоторые флаги ввода/вывода, */ /* если необходимо */ read (fd, /* Читать из файла */ write (fd, ...); /* Писать в файл */ Iseek (fd, ...); /* Поиск в файле */ close (fd); /* Закрыть файл, освобождает дескриптор файла */ Когда процесс больше не нуждается в доступе к открытому файлу, он должен закрыть его, используя системный вызов close о. Все открытые файлы процесса автоматически закрываются, когда процесс заканчивается. Хотя это означает, что можно опустить явный вызов close о, лучшая практика программирования состоит в том, чтобы закрыть файлы явно. Дескрипторы файла нумеруются последовательно, начиная с 0. В соответствии с соглашением, первые три значения дескриптора файла имеют специальное значение (табл. 13.1). Таблица 13.1. Значения дескрипторов файлов для стандартных каналов ввода/вывода Значение Описание 0 Стандартный ввод (stdin) 1 Стандартный вывод (stdout) 2 Стандартный канал ошибки (stderr) Например, библиотечная функция printf о всегда посылает свой вывод с помощью дескриптора файла 1, a scanf () всегда читает свой ввод через дескриптор файла 0. Когда ссылка на файл закрыта, его дескриптор освобождается и может быть повторно назначен последующим вызовом open (). Большинство системных вызовов ввода/вывода требуют дескриптор файла в качестве своего первого параметра, чтобы знать, с каким файлом работать. Отдельный файл может быть открыт несколько раз и, таким образом, иметь несколько дескрипторов, связанных с ним (рис. 13.4).
Рис. 13.4. Много дескрипторов файла — один файл Каждый дескриптор файла имеет собственный частный набор свойств, которые не имеют никакого отношения к файлу. П Указатель файла, который записывает смещение в файле, когда тот читается или пишется. Когда дескриптор файла создан, его указатель файла помещается по умолчанию на смещение 0 в файле (первый символ). По мере того как процесс читает или пишет, указатель файла соответственно обновляется. Например, если бы процесс открыл файл и затем прочитал 10 байтов из него, указатель файла достиг бы позиции на смещении Ю. Если бы процесс затем написал 20 байтов, байты со смещением 10—29 в файле были бы переписаны, и указатель файла достиг бы смещения 30. □ Флаг, который указывает, должен ли дескриптор автоматически быть закрыт, если процесс выполняет ехес (). □ Флаг, который указывает, должен ли весь вывод файла быть добавлен к концу файла. В дополнение к перечисленным свойствам, следующие свойства являются значащими, если только это специальный файл типа конвейера или сокета. О Флаг, который указывает, должен ли процесс блокировать ввод из файла, если файл в настоящее время не содержит никакого входа. □ Число, которое указывает ID процесса или группу процесса, которой должен быть послан сигнал sigio, если вход становится доступным на файле. (Сигналы и группы процесса обсуждаются далее.) Системные вызовы ореп() и fcntio позволяют управлять этими флагами. Первый пример: reverse В качестве первого примера опишем основные системные вызовы ввода/вывода. В табл. 13.2 представлен их список вместе с кратким описанием.
Таблица 13.2. Системные вызовы для базовых операций ввода/вывода Имя системного вызова Описание open Открывает/создает файл read Читает байты из файла в буфер write Пишет байты из буфера в файл Iseek Перемещает указатель на конкретное смещение в файле close Закрывает файл unlink Удаляет файл Для иллюстрации применения этих системных вызовов будет использована маленькая сервисная программа reverse.c. Она может служить хорошим примером того, как писать утилиты UNIX. Синтаксис reverse -с [имяФайла] Утилита reverse переставляет символы строки из ввода в обратном порядке и посылает их на стандартный вывод. Если имя Фа ил а не определено, reverse переворачивает свой стандартный ввод. Когда используется опция —с, reverse также переворачивает символы в каждой строке. Пример: $ сс reverse.с -о reverse $ cat test Christmas is coming, The days that grow shorter, Remind me of seasons I knew $ reverse test Remind me of seasons I knew The days that grow shorter, Christmas is coming, $ reverse -c test .tsap eht ni wenk I snosaes ...компилировать программу. ...вывести тестовый файл. in the past. ...переставить строки, in the past. i ...переставить символы в строках. fo em dnimeR
,retrohs worg taht syad ehT , gnimoc si samtsirhC $ cat test I reverse ...направить вывод в reverse. Remind me of seasons I knew in the past. The days that grow shorter, Christmas is coming, $_ Как работает reverse Утилита reverse работает, выполняя два прохода над своим вводом. Во время первого прохода она обращает внимание на стартовое смещение каждой строки в файле и сохраняет эту информацию в массиве. Во время второго прохода она переходит к началу каждой строки в обратном порядке, копируя ее из первоначального файла ввода на свой стандартный вывод. Если имя файла не определено в командной строке, reverse читает со сво-‘его стандартного ввода во время первого прохода и копирует это во временный файл для второго прохода. Когда программа заканчивается, временный файл удаляется. В табл. 13.3 представлен краткий обзор потока программы вместе с переч-. нем функций, которые связаны с каждым действием, и списком системных вызовов, используемых каждым шагом. Затем следует полный листинг reverse.c, исходный код reverse. Смотрите код и читайте описание системных вызовов. Код также доступен в сети (см. Введение для дополнительной информации). Таблица 13.3. Описание алгоритма, используемого в reverse.c Шаг Действие Функции Системные вызовы 1 Разбирает командную строку parseCommandLine, processoptions open 2 Если читает со стандартного ввода, создает временный файл для хранения ввода, в противном случае открывает входной файл для чтения passl open 3 Читает из файла по кускам, сохраняя смещение начала каждой строки в массиве. При чтении со стандартного ввода копирует каждый кусок во временный файл passl, trackLines read, write 18 Зак. 786
Таблица 13.3 (окончание) Шаг Действие Функции Системные вызовы 4 Читает входной файл снова, на сей раз назад, копируя каждую строку в стандартный вывод. Полностью переворачивает строку, если была указана опция -с \ pass2, processLine, reverseLine Iseek 5 Закрывает файл. Удаляет его, если это временный файл pass2 close Листинг: reverse.c 1 #include <fcntl.h> /* Для определения режима файла */ 2 #include <stdio.h> 5 #include <stdlib.h> 4 5 6 /* Энумератор */ 7 enum { FALSE, TRUE }; /* Стандартные значения: истина и ложь */ 8 enum { STDIN, STDOUT, STDERR}; /* Стандартные индексы каналов */ 9 /* ввода/вывода */ 10 11 /* Операторы #define */ 12 #define BUFFER_SIZE 4096 /* Размер буфера копирования */ 13 ttdefine NAME_SIZE 12 14 #define MAX_LINES 100000 /* Максимальное число строк в файле */ 15 16 17 /* Глобальные */ 18 19 char *fileName = NULL; /* Указывает на имя файла */ char tmpName [NAME_SIZE]; 20 int charOption = FALSE; /* Установить true, если есть опция -с */ 21 int standardinput = FALSE; /* Установить true, если читается stdin * 22 int lineCount =0; /* Общее число строк во вводе */ 23 int lineStart [MAX_LINES]; /* Хранит смещение каждой строки */ / 24 int fileOffset =0; /* Текущая позиция во вводе */ 25 int fd; /* Дескриптор файла ввода */
26 27 /*****************★******★*★***★****★*******★**********★*****/ 28 29 main (argc, argv) 30 31 int argc; 32 char* argv []; 33 34 { 35 parseCommandLine (argc,argv); /* Разбирает командную строку */ 36 passl(); /* Выполняет первый проход по вводу */ 37 pass2(); /* Выполняет второй проход по вводу */ 38 return (/* выход успешный */ 0); /* Выполнено */ 39 } 40 /********★★★**★★***★**************★★**★**★★***★*★★***★***★*★****★/ ‘42 43 parseCommandLine (argc, argv) 44 45 int argc; 46 char* argv [ ]; 47 48 /* Разбор аргументов командной строки */ 49 50 { 51 int i; 52 53 for (i= 1; i < argc; i++) 54 { 55 if(argv[i] [0] = ’-') 56 processoptions (argv[i]); 57 else if (fileName == NULL) 58 fileName= argv[i]; 59 else 60 usageError(); /* Произошла ошибка */ 61 } 62 63 standardinput = (fileName == NULL); 64 } 65
520 Глава 13 66 67 68 processoptions (str) 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 char* str; /* Разбор опций */ { int j; for (j= 1; str[j] != NULL; j++) { switch(str[j]) /* Переключиться по флагу командной строки */ { case' с': charOption = TRUE; break; default: usageError(); break; } } } usageError() { fprrntf (stderr, "Usage: reverse -c [filename]\n"); exit (/* Выход при ошибке */ 1); ^•к-к-к'к^^'к’к-к-к'к-к-к'к-к-к'к-к'к'к-к'к-к'к'к-к-к-к'к-к-к-к-к-к-к^-к'к-к-к-к-к-'к-к'к'к-к-^-к-к-к'-к'к^'к'к-к'к'к'/с'к'к'к I passl ()
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 /* Выполнить первый просмотр файла */ { int tmpfd, charsRead, charsWritten; char buffer [BUFFERJSIZE]; if (standardinput) /* Читать co стандартного ввода */ { fd = STDIN; sprintf (tmpName, ’’.rev.%d",getpid()); /* Случайное имя */ /* Создать временный файл для хранения копии ввода */ tmpfd = open (tmpName, O_CREAT | O_RDWR, 0600); if (tmpfd == -1) fatalError(); } else /* Открыть именованный файл для чтения */ { fd = open (fileName, O_RDONLY); if (fd == -1) fatalError(); } lineStart[0] =0; /* Смещение первой строки */ while (TRUE) /* Читать весь ввод */ { /* Заполнить буфер */ charsRead = read (fd, buffer, BUFFER_SIZE); if (charsRead == 0) break; /* EOF */ if (charsRead == -1) fatalError(); /* Ошибка */ trackLines (buffer, charsRead); /* Обработать строку */ /* Копировать строку во временный файл, если чтение из stdin ★/ if (standardinput) { charsWritten = write (tmpfd, buffer, charsRead); if(charsWritten ’= charsRead) fatalError(); } } 1 /* Сохранить смещение замыкающей строки, если существует */ lineStart[lineCount +1] = fileOffset;
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 /★ Если читается стандартный ввод, подготовить fd для прохода 2 */ if (standardinput) fd = tmpfd; } trackLines (buffer, charsRead) char* buffer; int charsRead; /* Сохранить смещение каждой строки в буфере */ { int i; for (i = 0; i < charsRead; i++) { ++fileOffset; /* Обновить текущую позицию файла */ ;if (buffer[i] == ’\n’) lineStart[++lineCount] = fileoffset; } } /**★*★★★*****★★★*★★★******★**★***★★*★★★*****★*****★★★★*★★**★★★★★*/ int pass2() /* Просмотр входного файла снова, вывод строк в обратном порядке */ { int i; for (i = lineCount — 1; i >= 0; i—) processLine (i); close (fd); /* Закрыть входной файл */ if (standardinput) unlink (tmpName); /* Удалить временный файл */ }
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 processLine (i) int i; /* Читать строку и вывести ее */ { int charsRead; char buffer [BUFFER_SIZE]; Iseek (fd, lineStart[i], SEEK_SET); /* Найти строку и читать ее */ charsRead = read (fd, buffer, lineStart[i+1] — lineStart[i]); /* Перевернуть строку, если была выбрана опция -с */ if (charOption) reverseLine (buffer, charsRead); write (1, buffer, charsRead); /* Писать ее на стандартный вывод */ } у***************************************************************/ reverseLine (buffer, size) char* buffer; int size; /* Перевернуть все символы в буфере */ { int start = 0, end — size — 1; W char tmp; if (buffer[end] == ’\n') —end; /* Переставить символы попарно */ while (start < end) { tmp = buffer[start];
222 buffer[start] = buffer[end]; 223 buffer[end] = tmp; 224 ++start; /* Увеличить на единицу индекс start */ 225 —end; /* Уменьшить на единицу индекс end */ 226 } 227 } 228 229 /*************************************************************** 230 231 fatalError() 232 233 { 234 perror ("reverse: "); /* Описать ошибку */ 235 exit (1); 236 } Открытие файла: ореп() Утилита reverse начинается С выполнения функции parseCommandLine () (строка 43), которая устанавливает различные флаги в зависимости от выбранных опций. Если имя файла определено, задается переменная fileName, чтобы указать на имя, и standardinput сбрасывается в false; иначе fileName получает значение null, a standardinput присваивается true. Затем выполняется функция passio (строка 103), которая исполняет одно из описанных ниже действий. □ Если reverse читает со стандартного ввода, создается временный файл с разрешением чтения и записи для владельца и без разрешений для кого-либо еще (восьмеричный код 600). Файл открывается в режиме чтения/ записи и используется для сохранения копии стандартного ввода, которая нужна во время прохода 2. Во время прохода 1 вход принимается со стандартного ввода, так что дескриптор файла fd установлен на stdin, определен 0 вверху программы. Вспомните, что стандартный ввод всегда имеет нулевой дескриптор файла. П Если reverse читает из указанного файла, файл открывается в режиме только для чтения так, чтобы его содержимое могло читаться во время прохода 1, используя дескриптор файла fd. Каждое действие нуждается в системном вызове open (). Первое действие использует его, чтобы создать файл, второе — чтобы получить доступ к существующему файлу.
Синтаксис int open (char* fileName, int mode [, int permissions]) Системный вызов open () позволяет открыть или создать файл для чтения или записи. fileName — абсолютное или относительное имя пути, mode — поразрядное or флага чтения/записи, с учетом или без учета некоторых смешанных флагов, permissions — число, которое кодирует значение флагов доступа к файлу, необходимо только, когда файл создается. Оно обычно записывается с помощью восьмеричной схемы шифрования, описанной в главе 2. На значение доступа влияет значение процесса umask, описанного в главе 4. Значения предопределенного флага чтения/записи и смешанных флагов заданы в файле /usr/include/fcntl.h. Флаги чтения/записи: Флаг Описание o_rdonly Открывает только для чтения O_wronly Открывает только для записи O_RDWR z Открывает для чтения и записи Смешанные флаги: Флаг Описание O_APPEND Позиция указателя в конце файла перед каждым write () O_CREAT Если файл не существует, создает его и устанавливает ID владельца на эффективный UID процесса. Значение umask используется при определении начальных значений флага доступа O_EXCL Если O_CREAT установлен и файл существует, тогда open () завершается с ошибкой O_NONBLOCK (Называется O_NDELAY на некоторых системах). Флаг работает только для указанных конвейеров. Если флаг установлен, open () для файла в режиме "только для чтения" немедленно вернет управление, независимо от того, является ли конец записи открытым. open () для файла в режиме "только для записи" завершится с ошибкой, если конец для чтения не открыт, open () для файла в режиме "только для чтения" или "только для записи" блокируется, пока другой конец не будет открыт O_TRUNC Если файл существует, он усекается до нулевой длины, open () возвращает неотрицательный дескриптор файла, если завершится успешно; иначе возвращается -1
Создание файла Чтобы создать файл, используйте флаг o_creat в параметре mode и добавьте восьмеричное значение для начальной установки флага. Например, строки 114—117 создают временный файл с доступом на чтение и запись для владельца и затем открывают файл для чтения и записи: 114 sprintf (tmpName, ”.rev.%d", getpidO); /* Случайное имя */ 115 /* Создать временный файл для хранения копии ввода */ 116 tmpfd = open (timpName, O_CREAT | O_RDWR, 0600) ; 117 'if (tmpfd == -1) fatalError(); Функция getpidO является системным вызовом, который возвращает ID процесса (PID) — номер, который будет уникальным. Это удобный способ сформировать уникальное временное имя файла. (Для дополнительной информации о getpidO см. разд. "Управление процессами" далее в этой главе.) Обратите внимание, что имя временного файла начинается с точки так, чтобы оно не появлялось в листинге утилиты is. Файлы, которые начинаются с точки, иногда называют скрытыми файлами. Открытие существующего файла Чтобы открыть существующий файл, задайте только флаги параметра mode. Строки 121—122 демонстрируют открытие файла в режиме "только для чтения": 121 fd = open (fileNamer O_RDONLY); 122 if (fd — -1) fatalError(); Другие флаги открытия Другие более сложные установки флага системного вызова open (), типа o_nonblock, используются с конвейерами, сокетами и STREAMS. Вероятно, флаг o_creat — пока единственный смешанный флаг, в котором читатель будет нуждаться. Чтение из файла: read() Поскольку утилита reverse имеет инициализированный дескриптор файла fd для ввода, она читает фрагменты входа и обрабатывает их, пока не будет достигнут конец файла. Чтобы читать байты из файла, reverse использует системный вызов read (). Он выполняет ввод низкого уровня и не имеет ни одной из способностей форматирования scanf (). Преимущество read о состоит в том, что он обходит дополнительный этап буферирования, обеспечиваемый библиотечными функциями С, и поэтому является очень быстрым. Можно читать один символ входа за раз, что закончилось бы большим количеством системных вызовов и привело к значительному замедлению
выполнения программы. Вместо этого был использован системный вызов read о для чтения по buffer_size символов одновременно. BUFFER_SIZE был выбран кратным размеру блока диска для эффективного копирования. Внимание Здесь приведено краткое описание работы системного вызова read () при чтении обычного файла. Сведения о чтении специальных файлов см. далее в данной главе. Синтаксис ssize_t read (int fd, void* buf, size_t count) Систмный вызов read о копирует count байтов из файла, заданного дескриптором файла fd в буфер buf. Байты читаются, начиная с текущей позиции файлового указателя, которая затем обновляется. read () копирует столько байтов из файла, сколько может, до значения count и возвращает количество фактически скопированных байтов. Если read о предпринимается после того, как последний байт уже считался, он возвращает 0, который указывает на конец файла. Если системный вызов завершился успешно, read о возвращает количество прочитанных байтов; иначе возвращает —1. Строки 130—132, выполняют чтение и проверяют возвращаемый результат: 130 charsRead = read (fd, buffer, BUFFER_SIZE); 131 if (charsRead == 0) break; /* EOF */ 132 if (charsRead == -1) fatalError(); /* Ошибка */ По мере чтения каждый фрагмент входа передается функции trackLines о. Эта функция просматривает буфер входа до символа перевода строки и сохраняет смещение первого символа в каждой строке в массиве linestart. Переменная fileoffset служит для хранения текущего смещения файла. Содержание linestart используется во время второго прохода. Запись в файл: write() Когда утилита reverse читает со стандартного входа, она создает копию входа для использования во время второго прохода. Чтобы сделать это, она заставляет дескриптор файла tmpfd ссылаться на временный файл и затем пишет каждый фрагмент входа в файл во время цикла чтения. Для записи байтов в файл она использует системный вызов write о, который выполни-
ет вывод низкого уровня и не имеет ни одной из способностей форматирования библиотечной функции printf о. Преимущество write о состоит в том, что он обходит дополнительный этап буферирования, обеспечиваемый библиотечными функциями С, и поэтому является очень быстрым. Внимание Здесь приведено краткое описание работы системного вызова write () при записи в обычный файл. Сведения о записи в специальные файлы см. далее в данной главе. Синтаксис ssize_t write (int fdr void* buf, size_t count) Системный вызов write о копирует count байтов из буфера buf в файл, заданный дескриптором файла fd. Байты записываются, начиная с текущей позиции файлового указателя, которая затем обновляется. Если флаг o_append был установлен для fd, позиция файлового указателя перемещается на конец файла перед каждой записью. write () копирует столько байтов из буфера, сколько может, до значения count и возвращает количество фактически скопированных байтов. Пользовательский процесс должен всегда проверять возвращаемое значение. Если возвращаемое значение не равно count, то диск, вероятно, заполнен, и пространства не осталось. Если системный вызов завершается без ошибки, write () возвращает количество записанных байтов; иначе возвращает —1. Строки 134—139 демонстрируют выполнение записи: 134 /* Копировать строку во временный файл, если чтение из stdin */ 135 if (standardinput) 136 { 137 charsWritten = write (tmpfd, buffer, charsRead); 138 if(charsWritten != charsRead) fatalError(); 139 } Перемещение в файле: IseekO После первого прохода массив linestart содержит смещение первого символа каждой строки входного файла. Во время второго прохода строки читаются в обратном порядке и выводятся на стандартный вывод. Чтобы чи
тать строки из последовательности, программа использует системный вызов iseek о, который позволяет изменять файловый указатель. Синтаксис off_t Iseek (int fd, off_t offset, int mode) Системный вызов iseek о позволяет изменять текущую позицию дескриптора файла, fd — дескриптор файла, offset — смещение, длинное целое число. Параметр mode описывает, как смещение должно интерпретироваться. Три возможных значения режима * определены в файле /usr/include/stdio.h и имеют следующие значения: Величина Описание SEEKJSET Смещение offset относительно начала файла SEEK__CUR Смещение offset относительно текущей позиции файла SEEK_END Смещение offset относительно конца файла Системный вызов iseek о завершается с ошибкой, если будет осуществлена попытка перемещения за начало файла. При нормальном завершении iseek о возвращает текущую позицию файла; иначе возвращает —1. В некоторых системах режимы определены в файле /usr/include/unistd.h. Строки 196—197 демонстрируют выполнение поиска до начала строки и чтения всех символов в строке. Обратите внимание, что количество символов для чтения рассчитывается путем вычитания смещения начала следующей строки от смещения начала текущей строки: 196 Iseek (fd, lineStart[i], SEEK_SET); /* Найти строку и читать ее */ 197 charsRead = read (fd, buffer, lineStart[i+1] — lineStart[i]); Если необходимо выяснить текущее местоположение без перемещения, следует указать значение смещения 0 относительно текущей позиции: currentOffset = Iseek (fd, 0, SEEK__CUR) ; Если произошло перемещение за конец файла, а затем выполняется системный вызов write о , ядро автоматически расширяет размер файла и обращается с промежуточной областью файла, как будто она была заполнена сим
волами null (ASCII 0). Интересно, что оно не выделяет пространство диска для промежуточной области. Это подтверждает следующий пример: $ cat sparse.с ...посмотреть тестовый файл. # include <fcntl.h> # include <stdio.h> # include <stdlib.h> /***************************************************************/ main () { int i, fd; /* Создать разбросанный файл */ fd = open ("sparse.txt", O_CREAT | O_RDWR, 0600); write (fd, "sparse", 6); Iseek (fd, 60006, SEEKJSET); write (fd, "file", 4); close (fd); /* Создать нормальный файл */ fd = open ("normal.txt", O_CREAT I O_RDWR, 0600); write (fd, "normal", 6); for (i = .1; i <= 60000; i++) write (fd, "/О", 1); write (fd, "file", 4); close (fd); } $ sparse ...выполнить файл. $ Is -1 ★.txt ...посмотреть файлы. -rw-r—r— 1 glass 60010 Feb 14 15:06 normal.txt -rw-r—r— 1 glass 60010 Feb 14 15:06 sparse.txt $ Is -s ★.txt ...вывести их использованные блоки. 60 normal.txt* ...использует 60 блоков целиком. 8 sparse.txt* ...использует только 8 блоков. $ Файлы, которые содержат промежутки, называются разбросанными файлами (sparse file). (Для дополнительной информации см. главу 14). Закрытие файла: closeO После второго прохода утилита reverse использует системный вызов close (), чтобы освободить дескриптор входного файла.
Синтаксис int close (int fd) Системный вызов close о освобождает дескриптор файла fd. Если fd — последний дескриптор файла, связанный с конкретным открытым файлом, ресурсы ядра, отведенные для файла, освобождаются. Когда процесс заканчивается, все его дескрипторы автоматически закрываются, но лучший стиль программирования состоит в том, чтобы закрыть файл явно. Если осуществляется попытка закрытия дескриптора файла, который уже закрыт, происходит ошибка. При нормальном завершении системный вызов close о возвращает 0; иначе возвращает —1. Строка 180 содержит вызов close (): 180 close (fd); /* Закрыть входной файл */ Закрытие файла не гарантирует, что буфер файла немедленно сбрасывается на диск. Для получения дополнительной информации о буферизации файлов см. главу 14. Удаление файла: unlinkf) Если утилита reverse читает со стандартного ввода, она сохраняет копию входа во временном файле. В конце второго прохода она удаляет этот файл, ИСПОЛЬЗУЯ СИСТемНЫЙ ВЫЗОВ unlink (). Синтаксис int unlink (const char* fileName) Системный ВЫЗОВ unlink о удаляет жесткую СВЯЗЬ между fileName и его файлом. Если fileName — последняя связь к файлу, ресурсы файла освобождаются. В этом случае, если дескрипторы файла любого процесса в настоящее время связаны с файлом, вход каталога удаляется немедленно, но файл освобождается только тогда, когда все дескрипторы файла будут закрыты. Это означает, что исполняемый файл может откреплять себя во время выполнения и все еще продолжать работать до завершения. При нормальном завершении unlink о возвращает 0; иначе возвращает —1. Строка 181 содержит вызов unlink о: 181 if (standardinput) unlink (tmpName); /* Удалить временный файл */ Дополнительные сведения о жестких связях см. в главе 14.
Второй пример: monitor Данный раздел содержит описание несколько дополнительных системных вызовов, перечисленных в табл. 13.4. Их* использование демонстрируется в контексте программы monitor, которая позволяет контролировать ряд указанных файлов и получать информацию всякий раз, когда любой из них изменяется. Таблица 13.4. Углубленные системные вызовы ввода/вывода UNIX Имя Функция stat Получает статусную информацию о файле f stat Работает точно так же, как stat getdents Получает вхождения каталога Синтаксис monitor [-t delay] [-1 count] {имяФайла}+ Утилита monitor просматривает все указанные файлы каждые delay секунд и показывает информацию о любом из указанных файлов, которые были изменены с последнего просмотра. Если имяФайла является каталогом, просматриваются все файлы внутри этого каталога. Модификация файла обозначается одним из трех способов: Обозначение Описание ADDED Указывает, что файл был создан после последнего просмотра. Каждому файлу в списке файлов дают эту метку во время первого просмотра CHANGED Указывает, что файл был изменен после последнего просмотра DELETED Указывает, что файл был удален после последнего просмотра По умолчанию monitor будет осуществлять просмотр в любом случае, хотя вы можете определить общее количество просмотров с помощью опции -1. По умолчанию время задержки между просмотрами составляет 10 секунд, хотя оно может быть переопределено через опцию -t.
В приведенном ниже примере проводится мониторинг индивидуального файла и каталога с сохранением вывод monitor во временном файле: % 1s ...посмотреть домашний каталог. monitor.с monitor tmp/ % Is tmp ...посмотреть каталог tmp. ь % monitor trap myFile.txt >& monitor.out & [1] 12841 ...запустить. % cat > tmp/a ...создать файл в ~/tmp. hi there "D % cat > myFile.txt ...создать myFile.txt. hi there % cat > myFile.txt hi again ...изменить myFile.txt. % rm tmp/a ...удалить tmp/a. % jobs ...посмотреть задания. [ 1] + Running monitor tmp myFile.txt , & monitor.out % kill %1 ..."убить" задание monitor. [1] Terminated monitor tmp myFile.txt , & monitor.out % cat monitor.out ...посмотреть вывод. ADDED tmp/b size 9 bytes, mod. time = Sun Jan 18 00:38:55 1998 ADDED tmp/a size 9 bytes, mod. time = Fri Feb 13 18:51:09 1998 ADDED myFile.txt size 9 bytes, mod. time = Fri Feb 13 18:51:21 1998 CHANGED myFile.txt size 18 bytes, mod. time = Fri Feb 13 18:51:49 1998 DELETED tmp/a Q. О Заметьте, как содержимое файла monitor.out отразило дополнение, модификацию и удаление проверяемого файла и каталога. Как работает monitor Утилита monitor непрерывно просматривает указанные файлы и каталоги на модификацию. Она использует системный вызов stat () для получения информации о статусе заданных файлов, включая их тип и самое последнее время модификации, и системный вызов getdentsO, чтобы просмотреть
каталоги, monitor поддерживает таблицу статуса, называемую stats, которая хранит следующую информацию о каждом найденном файле: □ имя файла; □ информацию статуса, полученную stat о; □ запись о том, существовал ли файл во время текущего и предыдущего просмотра. Во время просмотра monitor обрабатывает каждый файл таким образом: □ если файл в настоящее время не находится в таблице просмотра, он добавляется, и выводится сообщение "ADDED"; □ если файл уже размещен в таблице просмотра и был изменен после последнего просмотра, выводится сообщение "CHANGED". В конце просмотра все вхождения, которые существовали во время предыдущего, но не во время текущего просмотра, удаляются из таблицы, и выводится сообщение "DELETED". Ниже представлен полный листинг файла monitor.c, исходный код программы monitor. Просмотрите его, а затем прочитайте описание системных вызовов. Листинг: monitor.c 1 #include <stdio.h> /* Для printf, fprintf */ 2 #include <string.h> /* Для strcmp */ 3 #include <ctype.h> /* Для isdigit */ 4 #include <fcntl.h> /* Для O_RDONLY */ 5 ttinclude <sys/dirent.h> /* Для getdents */ 6 #include <sys/stat.h> /* Для IS macros */ 7 #include <sys/types.h> /* Для modet */ 8 #include <time.h> /* Для локального и актуального времени */ 9 10 11 /* # define-операторы */ 12 #define MAX_FILES 100 13 #define MAX_FILENAME 50 14 #define NOT^FOUND -1 15 #define FOREVER -1 16 #define DEFAULT_DELAY_TIME 10 17 #define DEFAULT_LOOP_COUNT FOREVER 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 /★ Логические */ enum { FALSE, TRUE } /* Структура Status, одна на файл */ struct statStruct { char fileName [MAX_FILENAME]; /* Имя файла */ int lastCycle, thisCycle; /* Для определения изменений */ struct stat status; /* Информация от stat() */ }; /* Глобальные */ char* fileNames [MAX_FILES]; /* Один на файл в командной строке */ int fileCount; /* Счетчик файлов в командной строке */ struct statStruct stats [MAX_FILES]; /* Один на совпадающий файл */ int loopCount = DEFAULT_LOOP_COUNT; /* Количество циклов */ int delayTime = DEFAULT_DELAY_TIME; /* Секунд между циклами */ Jr***************************************************************/ main (argc, argv) int argc; char* argv []; parseCommandLine (argc, argv); /* Разобрать командную строку */ monitorLoop(); /* Выполнить главный цикл monitor */ return (/* ВЫХОД_УСПЕХ */ 0) ; ****************************************************************/ parseCommandLine (argc, argv) int argc; char* argv [];
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 /★ Разобрать аргументы командной строки */ { int i; for (i = 1; ( (i < argc) && (i < MAKEFILES) ); i++) { if (argv[i] [0] == ) processoptions (argv[i]); else fileNames[fileCount++] = argv[i]; } if (fileCount == 0) usageError(); } у****************************************************************/ processoptions (str) char* str; /* Разобрать опции */ { int j; for (j = 1; str[j] != NULL; j++) X { switch(str[j]) /* Переключатель по символу опции */ { case ’t’: delayTime — getNumber (str, &j); break; case 111: loopCount = getNumber (str, &j); break;
98 } 99 } 100 } 101 ]_ Q2 /★**★**★**★*★**★★★**★***★**★*★★★★★★★*****★*****★+★★★★★*****★****+ / 103 104 getNumber (str, i) 105 106 char* str; 107 int* i; 108 109 /* Преобразовать числовую ASCII-опцию в число */ 110 111 { ' 112 int number = 0; 113 int digits =0; /* Счетчик цифр в числе */ 114 115 while (isdigit (str[(*i) + 1]))/Преобразовать символы в целое*/ 116 { 117 number = number *10+ str[++(*i)] — ’O’; 118 ++digits; 119 } 120 121 if (digits == 0) usageError(); /* Здесь должно быть число */ 122 return (number); 123 } 124 2 5 ^•к'к'к'к-к~к--к-к'к’-к-к-к-к-к^'к-к-к^к-к-к-)с'к'к'к'к'к'к'к-к-к-к'к-к-к^к-к-к-)с-к-к--к'к'к'к'к-к-к-к-к-)г--к-к-)г-^-к-'к'к--к-к-к-к-к'к- I 126 127 usageError() 128 129 { 130 fprintf (stderr, "Usage: monitor -t<seconds> -l<loops> (filename}+\n"); 131 exit (/* ВЫХОД_НЕУСПЕХ */ 1); 132 } 133 1 4 /****************************************************************/ 135
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 163 164 165 166 167 168 169 170 171 172 173 174 175 monitorLoop() /* Главный цикл monitor */ { do { monitorFiles(); /* Просмотреть все файлы */ fflush (stdout); /* Вывести стандартный вывод */ fflush (stderr); /* Вывести стандартный канал ошибки */ sleep (delayTime); /* ЭКдать следующего цикла */ } while (loopCount == FOREVER || —loopCount > 0); /★★★★★**^*****************************************т1гт1г*****'/г*т1гт1г*** monitorFiles() /* Обработать все файлы */ int i; for (i = 0; i < fileCount; i++) monitorFile (fileNames[i]); 162 for (i = 0; i< MAX_FILES; i++) /* Обновить массив stat */ { if (stats[i].lastCycle && !stats[i].thisCycle) printf ("DELETED %s\n", stats[i].fileName); stats[i].lastCycle = stats[i].thisCycle; stats[i].thisCycle = FALSE; } } ★★*★★★★★*******★★**★★★★★★★*★★★★★★★★★★***★********************** monitorFile (fileName)
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 char* fileName; /* Обработать одиночный файл/каталог */ { struct stat statBuf; mode_t mode; int result; result = stat (fileName, &statBuf); /* Получить статус файла */ if (result == -1) /* Статус был недоступен */ { fprintf (stderr, "Cannot stat %s\n", fileName); return; } mode = statBuf.st_mode; /* Вид файла */ if(S__ISDIR (mode)) /* Каталог *•/ processDirectory (fileName); else if (S-ISREG (mode) || S_ISCHR (mode) || S_ISBLK (mode)) updateStat (fileName, &statBuf); /* Обычный файл */ } ********************★*****************************************/ /* processDirectory (dirName) char* dirName; /* Обработать все файлы в указанном каталоге */ int fd, charsRead; struct dirent dirEntry; char fileName [MAX_FILENAME];
215 fd = open (dirName, O_RDONLY); /* Открыть для чтения */ 216 if (fd == -1) fatalError(); 217 218 while (TRUE) . /* Читать все элементы каталога */ 219 { 220 charsRead = getdents(fd, &dirEntry, sizeof (struct dirent)); 221 if (charsRead == -1) fatalError(); 222 if (charsRead == 0) break; /* EOF */ 223 if (strcmp (dirEntry.d_name, ”.") != 0&& 224 strcmp (dirEntry.d_name, != 0) /* Пропустить . и .. */ 225 { 226 sprintf (fileName, "%s/%s", dirName, dirEntry.d_name); 227 monitorFile (fileName); /* Рекурсивный вызов */ 228 } 229 230 Iseek (fd, dirEntry.d_off, SEEK_SET); /* Искать следующий элемент */ 231 } 232 233 close (fd); /* Закрыть каталог */ 234 } 235 у****************************************************************/ 237 238 updateStat (fileName, statBuf) 239 240 char* fileName; 241 struct stat* statBuf; 242 243 /* Добавить элемент status, если необходимо */ 244 245 { 246 int entryindex; 247 248 entryindex = findEntry (fileName);/* Найти существующий элемент */ 249 250 if (entгуIndex == NOT_FOUND) 251 entryindex = addEntry (fileName, statBuf); /* Добавить новый элемент */ / 252 else
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 284 285 286 287 288 289 290 291 updateEntry (entгуIndex, statBuf); /* Обновить существующий элемент */ if (entryIndex != NOT_FOUND) stats[entryindex].thisCycle = TRUE; /* Обновить массив status */ } ★ ***★****★**★★★*★*******★*****★***★*★★** + **★*** + ***★************ I findEntry (fileName) char* fileName; /* Определить индекс указанного файла в массиве status */ { int i; for (i = 0; i < MAX_FILES; i++) if (stats[i].lastCycle && strcmp (stats[i].fileName, fileName) == 0) return (i); return (NOT_FOUND) ; } /**************************************************************** j addEntry (fileName, statBuf) char* fileName; struct stat* statBuf; 283 /* Добавить новый элемент в массив status */ { int index; index = nextFreeO; /* Найти следующий свободный элемент */ if (index == NOT_FOUND) return (NOT_FOUND); /* He осталось */ strcpy (stats[index].fileName, fileName); /* Добавить имя файла */ stats[index].status = *statBuf; /* Добавить информацию статуса */
292 293 294 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 printf ("ADDED "); printEntry (index); return (index); /* Известить стандартный вывод */ /* Вывести информацию статуса */ ***•*•**•*•********★****★★*•*******★★*•★★★★★★*★*★*•*•*★★★*★****•★*•**•*•*★** nextFree() /* Вернуть следующий свободный индекс в массив status */ int i; for (i = 0; i < MAX_FILES; i++) if (!stats[i].lastCycle && !stats[i].thisCycle) return (i); return (NOT_FOUND); updateEntry (index, statBuf) int index; struct stat* statBuf; /* Вывести информацию, если файл был модифицирован */ if (stats[index].status.st_intime != statBuf->st_mtime) stats[index].status = *statBuf;/* Сохранить информацию статуса */ printf ("CHANGED "); printEntry (index); /* Известить стандартный вывод */ if***************************************************************/
332 333 printEntry (index) 334 335 int index; 336 337 /* Вывести элемент массива status */ 338 339 { 340 printf ("%s ", stats[index].fileName) ; 341 printstat (&stats[index].status); 342 } 343 244 у**********************************************'****'*’*************/ 345 ' 346 printStat (statBuf) 347 348 struct stat* statBuf; 349 350 /* Вывести буфер status */ 351 352 { 353 printf ("size %lu bytes, mod. time = %s", statBuf->st_size, 354 asctime (localtime (&statBuf->st_mtime))); 355 } 356 357 y**************************************************************y 358 359 fatalError() 360 361 { 362 perror ("monitor: ") ; 363 exit (/* ВЫХОД_СБОЙ */ 1); 364 } Получение информации о файле: stat() Утилита monitor получает свою информацию о файле через системный вызов stat о из monitorFile о (строка 175) на строке 186: 186 result = stat (fileName, &statBuf); /* Получить статус файла */
Синтаксис int stat ( const char* name, struct stat* buf) int Istat (const char* name, struct stat* buf) int fstat (int fdr struct stat* buf) Системный вызов stat о заполняет буфер buf информацией о файле пате. Структура stat определена в файле /usr/include/sys/stat.h. istat о возвращает информацию непосредственно о символической связи, а не о файле, на который она ссылается, fstat о исполняет ту же самую функцию, что и stat (), но требуется дескриптор файла, который должен быть задан в качестве его первого параметра. Структура stat содержит следующие поля; Имя Описание st_dev st_ino Номер устройства Номер узла st_mode st_nlink st_uid Флаги разрешения доступа Счетчик жестких связей ID пользователя st_gid ID группы st_size st_atime st_mtime st_ctime Размер файла Последнее время доступа Последнее время модификации Последнее время изменения статуса Есть некоторые предопределенные макросы, заданные в файле /usr/include/sys/stat.h, которые берут поле st_mode в качестве аргумента и возвращают истину (1) для следующих типов файла: Макрос Возвращает истину для файла типа S_IFDIR Каталог S_IFCHR Специальное символьное устройство S_IFBLK Специальное блочное устройство S_IFREG Обычный файл S_IFFIFO Конвейер
Поля времени могут быть декодированы стандартными подпрограммами библиотеки С asctime () И localtime (). stat о и f stat о возвращают 0, если завершились без ошибок, и —1 — в противном случае. Утилита monitor проверяет режим файла с помощью макросов s isdir, s_isreg, s ischr и s_isblk, обрабатывая каталоги и другие файлы следующим образом. □ Если файл — ЭТО каталог, monitor вызывает процедуру processDirectory () (строка 204), которая применяет monitorFile о рекурсивно к каждому из элементов каталога. О Если файл — это обычный файл, символьный файл или блочный файл, monitor вызывает процедуру updatestat () (строка 238), которая либо добавляет, либо модернизирует элемент статуса файла. Если статус изменяется любым способом, вызывается процедура updateEntry() (строка 315), чтобы показать новый статус файла. Расшифровка полей времени выполняется подпрограммами localtime о И asctime о В printstat о (строка 346). Чтение информации каталога: getdentsO Подпрограмма processDirectory о (строка 204) открывает файл каталога для чтения и использует системный вызов getdents о, чтобы получить каждый элемент в каталоге. processDirectory о осторожничает, чтобы не пропустить каталоги и и применяет lseek(), чтобы перескочить с одного элемента каталога на следующий. Когда каталог полностью просмотрен, он закрывается. Синтаксис int getdents (int fd, struct dirent* buf, int structSize) Системный вызов getdents () читает файл каталога с дескриптором fd с его текущей позиции и заполняет структуру, указанную buf, следующим элементом. Структура dirent определена в файле /usr/include/sys/dirent.h и содержит поля: Имя Описание d_ino Номер узла d_of f Смещение следующего элемента каталога
(окончание) Имя Описание d_reclen Длина структуры элемента каталога d_nam Длина имени файла Системный вызов getdents () возвращает длину элемента каталога при нормальном завершении; 0, когда последний элемент каталога уже читался; —1 — в случае ошибки. Некоторые старые системы используют системный вызов getdirentriesо вместо getdents(). getdirentries () несколько отличается от getdents(). (См. соответствующую страницу руководства используемой системы.) Смешанные вызовы системы управления файлами В табл. 13.5 приведено краткое описание некоторых смешанных вызовов системы управления файлами UNIX. Таблица 13.5. Вызовы системы управления файлами UNIX Имя Описание chown Заменяет владельца файла или группы chmod Изменяет установки прав доступа файла dup dup2 Дублирует дескриптор файла Подобен dup fchown Работает точно так же, как chown fchmod Работает точно так же, как chmod fcntl Дает доступ к разным характеристикам файла ftruncate Работает точно так же, как truncate ioctl Управляет устройством link Создает жесткую связь mknod Создает специальный файл sync Планирует выгрузку всех буферов файла, которые должны быть сброшены на диск truncate Укорачивает файл
Замена владельца файла или группы: chownQ и fchownQ Системные вызовы chown о и fchown о изменяют владельца или группу файла. Синтаксис int chown (const char* fileName, uid_t ownerId, gid_t groupld) int Ichown (const char* fileName, uid__t owner Id, gid__t groupld) int fchown (int fd, uid_t ownerld, gid_t groupld) Системный вызов chown о заставляет ID владельца и группы fileName измениться соответственно на owner id и groupld. Значение —1 в специфическом поле означает, что ассоциированное с ним значение должно остаться неизменным. Системный вызов ichown о изменяет непосредственно собственность символической связи, а не файла, на который связь ссылается. Только привилегированный пользователь (root) может изменять собственность файла, а обычный пользователь способен изменять только одну группу на другую группу, членом которой он является. Если fileName — символическая связь, изменяется владелец и группа связи вместо файла, на который связь ссылается. Системный вызов fchownо точно такой же, как chownо, но требует открытый дескриптор в качестве аргумента вместо filename. Все функции возвращают —1, если завершились с ошибкой, и 0 — в противном случае. В приведенном далее примере происходит замена группы файла test.txt с music на cs, который имеет ID группы, равный 62 (для дополнительной информации о ID группы и его локализации см. главу 15^: $ саt mychown.с ... посмотреть файл. main() int flag; flag = chown ("test.txt", -1, 62); /* Оставить ID пользователя неизменным */ if (flag == -1) perror("mychown.c");
$ Is -lg test.txt ...проверить файл перед.. -rw-r—г— 1 glass music 3 May 25 11:42 test.txt $ mychown ...выполнить программу. $ Is -lg test.txt ...проверить файл после. -rw-r—г— 1 glass cs 3 May 25 11:42 test.txt $ Изменение прав доступа файла: chmod() и fchmodO Системные вызовы chmod () и f chmod () изменяют флаги прав доступа файла. Синтаксис int chmod (const char* fileName, int mode) int fchmod (int fd, mode_t mode) Системный ВЫЗОВ chmodj) изменяет режим fileName на mode, обычно восьмеричное число, как описано в главе 2. Флаги "установка ID пользователя" и "установка ID группы" имеют, соответственно, восьмеричные значения 4000 и 2000. Чтобы изменить режим файла, вы должны быть либо его владельцем, либо привилегированным пользователем. Системный вызов fchmod () работает аналогично chmod (), но требует открытый дескриптор файла в качестве аргумент вместо filename. Оба вызова возвращают —1, если завершились с ошибкой, и 0 — в противном случае. В следующем примере изменяются флаги прав доступа файла test.txt на восьмеричное 600, которое соответствует разрешению чтения и записи только для владельца: $ cat ту chmod. с ...посмотреть файл, main() { int flag; flag = chmod ("test.txt", 0600);/*Использовать восьмеричную кодировку*/ if (flag == -1) perror ("mychmod.c"); } $ Is -1 test.txt ...проверить файл перед. -rw-r—г— 1 glass 3 May 25 11:42 test.txt
$ mychmod ...выполнить программу. $ Is -1 test.txt ...проверить файл после. -rw------- 1 glass 3 May 25 11:42 test.txt $ Дублирование дескриптора файла: dup() и dup2() Системные вызовы dup () и dup2 () позволяют дублировать дескрипторы файла. Shell использует dup2(), чтобы выполнить перенаправление и конвейеризацию. Примеры см. в разд. "Управление процессами" далее в этой главе и изучите Internet shell в конце главы. Синтаксис int dup (int oldFd} int dup2 (int oldFdf int newFd) Системный вызов dup о находит самый маленький свободный элемент дескриптора файла и направляет его на тот файл, с которым связан дескриптор oldFd. Системный вызов dup2 () закрывает дескриптор newFd, если он в настоящее время активен, и затем направляет его на файл, с которым связан Дескриптор oldFd. В обоих случаях первоначальные и скопированные дескрипторы файла разделяют один и тот же указатель файла и режим доступа. Оба вызова возвращают индекс нового дескриптора файла, если завершились без ошибки, и —1 — в противном случае. В представленном ниже примере создается файл st.txt и производится запись в него через четыре различных дескриптора файла: П первый дескриптор файла — первоначальный дескриптор; П второй дескриптор — копия первого, размещенная в области 4; П третий дескриптор — копия первого, размещенная в области 0, которая была освобождена оператором close (0) (стандартный канал ввода); П четвертый дескриптор — копия дескриптора 3, продублированная на место существующего дескриптора в области 2 (стандартный канал ошибки). $ cat mydup.c ...посмотреть файл. ftinclude < stdio.h> 19 Зак. 786
ttinclude <fcntl.h> main () { int fdl, fd2, fd3; fdl = open ("test.txt", O_RDWR | O_TRUNC); printf ("fdl — %d\n", fdl); write (fdl, "what’s", 6); fd2 = dup (fdl) ; /* Сделать копию fdl */ printf ("fd2 = %d\n", fd2); write (fd2, " up", 3); close (0); /* Закрыть стандартный ввод */ fd3 = dup (fdl); /* Сделать другую копию fdl */ printf ("fd3 - %d\n", fd3); write (0, " doc", 4); dup2 (3, 2); /* Дублировать канал 3 в канал 2 */ write (2, "?\n", 2); $ mydup ...выполнить программу. fdl = 3 fd2 = 4 fd3 = 0 $ cat test.txt .посмотреть выходной файл, what’s up doc? $ _ Действия с дескриптором файла; fcnttf) Системный вызов fcnti () непосредственно управляет установкой флагов, связанных с дескриптором файла. Синтаксис int fcntl (int fd, int cmdf int arg) Системный вызов fcntl () исполняет действие, заданное командой cmd, на файле, связанном с дескриптором файла fd. arg — дополнительный аргумент для cmd.
Обычные значения cmd: Значение Описание F_SETFD Устанавливает флаг "закрыть при выходе" в младшем бите агд (0 или 1) F_GETFD Возвращает число, чей младший бит — 1, если флаг "закрыть при выходе" установлен, и 0 — в противном случае F-GETFL Возвращает число, соответствующее текущим флагам статуса файла и режимам доступа F_SETFL Устанавливает текущие флаги статуса файла в агд F_GETOWN Возвращает ID процесса или группу процесса, которая в настоящее время установлена, чтобы получить сигналы SIGIO/SIGURG. Если возвращенное значение положительно, оно относится к ID процесса. Если оно отрицательно, его абсолютное значение относится к группе процесса F-SETOWN Устанавливает ID процесса или группу процесса, которая должна получить сигналы SIGIO/SIGURG в агд. Схема кодирования аналогична F_GETOWN Системный вызов fcnti() возвращает -1, если завершается с ошибкой. В следующем примере открывается существующий файл для записи, и начальные символы заменяются фразой "hi there". Затем используется fcnti (), чтобы установить флаг append в дескрипторе файла, который предписывает добавление всех дальнейших записей. Это стало причиной размещения слова "guys" в конце файла, даже притом, что файловый указатель был перемещен обратно на начало при помощи iseeko. Код следующий: $ cat myfcnti.с ...посмотреть программу. #include <stdio.h> #include <fcntl.h> main() { int fd; fd = open ("test.txt", O_WRONLY); /* Открыть файл для записи */ write (fd, "hi there\n", 9); Iseek (fd, 0, SEEK_SET); • /* Поиск начала файла */ fcnti (fd, FJSETFL, O_WRONLY | O_APPEND); /* Установить^флаг APPEND */ write (fd, " guys\n", 6);
close (fd); $ cat test.txt ...посмотреть оригинальный файл. here are the contents of the original file. $ my fcntl $ cat test.txt ...выполнить программу. ...посмотреть новое содержимое. hi there the contents of the original file. guys $ ...обратите внимание, что "guys” — в конце. Управление устройствами: ioctlO Ниже приведено описание системного вызова ioctl О. Синтаксис int ioctl (int fdf int cmdr int arg) Системный вызов ioctl() исполняет действие, заданное командой cmd, на файле, связанном с дескриптором файла fd. arg — дополнительный аргумент для cmd. Имеющие силу значения cmd зависят от устройства, на которое ссылается fd, и обычно документируются в инструкциях производителя. Поэтому здесь примеры не приводятся для данного системного вызова, ioctl () возвращает ~1, если завершается с ошибкой. Создание жестких связей: Ппк() Системный вызов link о создает жесткую связь к существующему файлу. Синтаксис int link (const char* oldPath, const char* newPath) Системный вызов link о создает новую метку newPath и связывает ее с тем же самым файлом, что и метка oldPath. Счетчик жестких связей связанного файла увеличивается на единицу. Если oldPath и newPath находятся на различных физических устройствах, жесткая связь не может
быть создана, и link о завершается с ошибкой. Для дополнительной информации о жестких связях см. описание enbkbns in в главе 3. Системный вызов link о возвращает -1, если завершается с ошибкой, и 0 — в противном случае. В приведенном далее примере создается файл another.txt, который связывается с файлом original.txt. Затем демонстрируется, что обе метки были связаны с одним и тем же файлом. Код следующий: $ cat mylink.с ...посмотреть программу. main () link ("original.txt", "another.txt"); } $ cat original.txt ...посмотреть оригинальный файл, this is a file. $ Is -1 original.txt another.txt ...проверить файлы перед. another.txt not found -rw-r—r— 1 glass 16 May 25 12:18 original.txt $ my link ...выполнить программу. $ Is -1 original.txt another.txt ...проверить файлы после. -rw-r—г— 2 glass 16 May 25 12:18 another.txt -rw-r—r— 2 glass 16 May 25 12:18 original.txt $ cat » another.txt ...изменить another.txt. hi "D $ Is -1 original.txt another.txt ...обе метки отражают изменение. -rw-r—г— 2 glass 20 May 25 12:19 another.txt -rw-r—r— 2 glass 20 May 25 12:19 original.txt $ rm original.txt ...удалить оригинальную метку. $ Is -1 original.txt another.txt ...проверить метки. original.txt not found -rw-r—r—‘ 1 glass 20 May 25 12:19 another.txt $ cat another.txt ...посмотреть содержимое через другую метку, this is a file. hi $
Создание специальных файлов: mknod() Системный вызов mknod () позволяет создавать специальный файл. В качестве примера работы mknod () рассмотрите разд. "Конвейеры" далее в этой главе. Синтаксис int mknod (const char* fileName, mode_t type, dev_t device] Системный вызов mknod о создает новый обычный файл, каталог или специальный файл с именем fileName, чей тип может быть одним из следующих: Значение Описание S_IFDIR Каталог S_IFCHR Символьный файл устройства S_IFBLK Блочный файл устройства S_IFREG Обычный файл S_IFIFO Именованный конвейер Если файл является символьным или блочным, то байт младшего разряда device должен определять младший номер устройства, а старший байт — старший номер устройства. (Это может изменяться в различных версиях UNIX.) В других случаях значение device игнорируется. (Для получения дополнительной информации о специальных файлах см. главу 14.) Только привилегированный пользователь (root) может вызывать mknodj), чтобы создавать каталоги, ориентируемые на специальные символьные или блочные файлы. mknod () возвращает —1, если завершается с ошибкой, и 0 — в противном случае. Сбрасывание на диск буферов файловой системы: sync() Системный вызов sync () сбрасывает на диск буферы файловой системы. Синтаксис void sync() Системный вызов sync () планирует запись на диск всех буферов файловой системы. (Для получения дополнительной информации о буферной
системе см. главу 14.) sync() должен выполняться любыми программами, которые обходят буферы файловой системы и исследуют файловую систему. sync о всегда завершается нормально. Усечение файла: truncateO и ftruncateO Системные вызовы truncate () и ftruncate () устанавливают длину файла. Синтаксис int truncate (const char* fileName, off_t length) int ftruncate (int fd, off_t length) Системный вызов truncateO устанавливает длину файла filename, равную length байтов. Если файл длиннее, чем length, он обрезается. Если короче, чем length, то дополняется ASCII-нулями. Системный ВЫЗОВ ftruncateO работает аналогично truncateO, за ИС-. ключением того, что требует открытый дескриптор файла в качестве своего аргумента вместо filename. Обе функции возвращают —1, если завершаются с ошибкой, и 0 — в противном случае. В приведенном ниже примере устанавливается длина двух файлов, равная 10 байтам; один из файлов был первоначально короче, а другой длиннее. Вот код: $ cat truncate, с ...посмотреть программу. main() { truncate ("filel.txt", 10); truncate ("file2.txt", 10); $ cat filel.txt ...посмотреть filel.txt. short $ cat file2.txt ...посмотреть file2.txt. long file with lots of letters $ Is -1 file*.txt ...проверить оба файла. -rw-r—г— 1 glass 6 May 25 12:16 filel.txt -rw-r—r— 1 glass 32 May 25 12:17 file2.txt $ truncate ...выполнить программу. $ Is -1 file*.txt ...проверить оба файла снова.
-rw-r—г— 1 glass 10 May 25 12:16 filel.txt -rw-r—r— 1 glass 10 May 25 12:17 file2.txt $ cat filel.txt ...filel.txt длиннее, short $ cat file2.txt ...file2.txt короче, long file $ STREAMS STREAMS2 — более новая и обобщенная возможность ввода/вывода, которая была представлена в System V UNIX. Потоки чаще всего используются для добавления драйвера устройства к ядру и обеспечения интерфейса к драйверам сети. Первоначально разработанная Деннисом Ритчи (Dennis Ritchie), одним из создателей UNIX, подсистема ввода/вывода STREAMS обеспечивает двусторонний путь между пространством ядра и пользовательским процессным пространством. Реализация STREAMS более обобщена, чем предыдущие механизмы ввода/вывода, делая их, таким образом, более легкими для реализации новых драйверов устройств. Одной из первоначальных мотиваций для создания STREAMS была очистка и улучшение традиционного символьного ввода/вывода UNIX, посылаемого терминальным устройствам. Базирующиеся на System V версии UNIX также включают Transport Layer Interface (TLI) — сетевой интерфейс к драйверам STREAMS, подобный интерфейсу сокетов, позволяющий драйверам сети на основе STREAMS связаться с другими программами на основе сокетов. Усовершенствования по сравнению с традиционным вводом/выводом UNIX Традиционный символьный ввод/вывод UNIX развивался с момента возникновения этой ОС. Как и в любой сложной программной подсистеме, через какое-то время незапланированные и плохо спроектированные изменения привели к большим сложностям. Часть преимуществ STREAMS исходит из факта, что посистема является более новой и может реализовать знания, полученные за прошдшие годы. Это приводит к более прозрачному интерфейсу, чем раньше. STREAMS также позволяет легче добавлять протоколы сети, чем создание целого драйвера и всех его необходимых частей на пустом месте. Зависимый от устройства код был выделен в модули так, чтобы только значимая часть 2 Потоки. — Ред.
переписывалась для каждого нового устройства. Общий код действий по обслуживанию ввода/вывода (например, распределение буферов и управление) был стандартизирован, и каждый модуль смог усилить услуги, предоставляемые потоком. Работа STREAMS производит посылку и получение потоковых сообщений, а не просто выполнение грубого ввода/вывода символа за символом. STREAMS также добавила управление потоком и приоритетную обработку. Анатомия STREAMS Каждый поток имеет три части: □ заголовок потока — точка доступа для пользовательского приложения, функций и структур данных, представляющих поток; □ модули — код для обработки читаемых или записываемых данных; □ драйвер потока — прикладная часть кода, который связывается с определенным устройством. Все три работают в пространстве ядра, хотя модули могут быть добавлены из пользовательского пространства. Заголовок потока обеспечивает интерфейс системного вызова для пользовательского приложения. Заголовок потока создается с помощью системного вызова open () . Ядро заведует любым требуемым распределением памяти, восходящим и нисходящим потоком данных, планированием очереди, управлением потока и регистрацией ошибок. Данные, записанные в заголовок потока из прикладной программы, находятся в форме сообщения, которое передается к первому модулю для обработки. Этот модуль обрабатывает сообщение и передает результат второму модулю. Обработка и передача продолжаются, пока последний модуль не передаст сообщение драйверу потока, который пишет данные на соответствующее устройство. Данные, поступающие от устройства, проходят тот же самый путь в обратном направлении. Системные вызовы STREAMS В дополнение к системным вызовам ввода/вывода, которые были описаны ранее в этой главе— ioctl О, open о, close о, read () и write (),— следующие системные вызовы работают с потоком: □ getmsg () — получает сообщение из потока; □ putmsgo — помещает сообщение в поток; П poll о — опрашивает один или более потоков для деятельности; □ isastreamo — выясняет, является ли данный дескриптор файла потоком.
Управление процессами Процесс UNIX — это уникальный экземпляр работающей или работоспособной программы. Каждый процесс в системе UNIX имеет следующие атрибуты: □ некоторый код (известный также как текст); □ некоторые данные; □ стек; □ уникальный ID процесса (Process ID, PID). Когда UNIX стартует, в системе существует только один видимый процесс. Этот процесс называется init и имеет PID 1. Единственный способ создать новый процесс в UNIX состоит в том, чтобы продублировать существующий процесс, так что init — предок всех последующих процессов. Когда процесс дублируется, родительский и дочерний3 процессы фактически идентичны (если бы не вещи, подобные PID, PPID (Parent PID), и времена выполнения); код порожденного процесса, данные и стек — это копия родителя, и он даже продолжает выполнять тот же самый код. Дочерний процесс может, однако, заменить свой код кодом другого выполняемого файла, таким образом, отделяя себя от родителя. Например, когда процесс init начинает выполняться, он быстро дублируется несколько раз. Каждый из дубликатов дочерних процессов затем заменяет свой код из исполняемого файла getty, который является ответственным за обработку пользовательского входа в систему. Поэтому иерархия процессов напоминает схему, представленную на рис. 13.5. Дочерний getty (PID 4) управляет входом в систему Дочерний getty (PID 5) управляет входом в систему Дочерний getty (PID 6) управляет входом в систему Рис. 13.5. Начальная иерархия процесса 3 Дочерний процесс также называется порожденным. — Ред.
Когда дочерний процесс заканчивается, этот факт сообщается его .родителю, чтобы родитель мог предпринять некоторое соответствующее действие. Обычное действие для родительского процесса — приостановка, пока один из порожденных процессов не закончится. Например, когда shell выполняет утилиту в приоритетном режиме, он дублируется в два процесса shell; дочерний процесс shell заменяет свой код кодом утилиты, тогда как родительский shell ждет завершения дочернего процесса. Когда порожденный процесс заканчивается, первоначальный родительский процесс ’’пробуждается" и представляет пользователю следующее приглашение shell. Рис. 13.6 иллюстрирует способ, которым shell выполняет утилиту. Указаны запросы системы, которые являются ответственными за каждую стадию выполнения. Internet shell, представленный позже в данной главе, имеет базовые возможности управления процессами классических UNIX shell и является хорошим проектом, чтобы рассмотреть некоторые удачные примеры кодирования, которые используют ориентируемые на процесс системные вызовы. А пока давайте рассмотрим некоторые простые программы, которые представляют эти системные вызовы один за другим. Следующие немногие подразделы описывают системные вызовы перечисленные в табл. 13.6. Родительский процесс PID 34 выполняет shell -----------------^Дублируется: fork() Родительский процесс PID 34 Дочерний процесс PID 35 выполняет shell, выполняет shell ждет дочерний Ждет дочерний: wait() - Заменяет код: ехес() Дочерний процесс PID 35 запускает утилиту Сигнал Родительский процесс PID 34 выполняет shell, пробуждается Завершить: ехес() Дочерний процесс PID 35 завершается
Таблица 13.6. Ориентированные на процесс системные вызовы UNIX Имя Описание fork Дублирует процесс getpid Получает ID процесса getppid Получает ID родительского процесса exit Прекращает процесс wait Ожидает дочерний процесс exec Замещает код, данные и стек процесса Создание нового процесса: fork() Процесс может дублировать себя, используя fork о. fork о — это странный системный вызов, потому что один процесс (оригинал) вызывает его, а два процесса (оригинал и его порожденный процесс) возвращаются из него. Оба процесса продолжают выполнять один и тот же код одновременно, но имеют полностью отдельные стеки и пространство данных. Синтаксис pid_t fork (void) Системный вызов fork() заставляет процесс дублироваться. Дочерний процесс — почти точная копия первоначального родительского процесса; он наследует копию кода своего родителя, данные, стек, открытые дескрипторы файла и таблицу сигналов. Однако родитель и порожденный процесс имеют различные ID порожденного процесса и ID родительского процесса. Если fork о завершается нормально, он возвращает PID порожденного процесса родительскому и 0 дочернему процессу. Если fork о завершается с ошибкой, он возвращает —1 родительскому процессу, а дочерний процесс не создается. Теперь это напоминает мне о великолепном научно-фантастическом рассказе, который я однажды читал, о человеке, который проходил через очаровательную кабину в цирке. Продавец кабины сообщает человеку, что кабина является, в сущности, репликатором: любой, кто проходит через кабину, дублируется. Но это не все: "исходный" человек выходит из кабины невредимым, а его дубль оказывается на Марсе в качестве раба Марсианских строительных команд. Продавец затем сообщает человеку, что ему дадут
миллион долларов, если он позволит себя скопировать, и человек соглашается. Он счастливо идет через машину, ожидая получения миллиона долларов... и выходит на поверхность Марса. Тем временем на Земле его дубликат уходит с запасом наличных денег. Вопрос такой: если бы вы прошли через кабину, что делали бы вы? Процесс может получить его собственный ID процесса и номер ID родительского процесса, используя, соответственно, системные вызовы getpido И getppid(). Синтаксис pid_t getpid (void) pid_t getppid (void) Систмные вызовы getpido и getppidо возвращают, соответственно, ID процесса и ID родительского процесса. Они всегда завершаются нормально. ID родительского процесса с PID 1 равен 1. Для иллюстрации действия fork.o ниже приведена небольшая программа, которая дублируется и затем ветвится, базируясь на значении, возвращаемом fork(): $ cat myfork.с ...посмотреть программу. #include <stdio.h> main() { int pid; printf ("I’m the original process with PID %d and PPID %d.\n", getpid(), getppid()); pid = fork(); /* Дублироваться. Порожденный и родительский процессы */ /* продолжаются отсюда */ if (pid != 0) /* PID не ноль, тогда я должен быть родителем */ printf ("I’m the parent process with PID %d and PPID %d.\n", getpid(), getppid()); printf ("My child's PID is %d\n", pid); } else /* PID ноль, тогда я должен быть порожденным */ printf ("I'm the child process with PID %d and PPID %d.\n", getpid ()., getppid () );
printf ("PID %d terminates.\n”, getpid() ); /* Оба процесса */ /* выполняются здесь */ } $ ту fork . . .выполнить программу. I’m the original process with PID 13292 and PPID 13273. I’m the parent process with PID 13292 and PPID 13273. My child’s PID is 13293. I’m the child process with PID 13293 and PPID 13292. PID 13293 terminates. ...порожденный процесс завершился. PID 13292 terminates. ...родительский процесс завершился. $ PID родителя ссылается на PID shell, который выполнял программу myfork. Внимание Для родительского процесса опасно заканчиваться без ожидания "смерти” своего порожденного процесса. Единственная причина, по которой в приведенном выше примере родитель не ждет, чтобы его дочерний процесс "умер", — отсутствие описания системного вызова wait (). Осиротевшие процессы Если родитель умирает прежде своего дочернего процесса, порожденный процесс автоматически усыновляется первоначальным процессом init с PID 1. Чтобы проиллюстрировать эту особенность, предыдущая программа была изменена, в коде порожденного процесса остался оператор sleep. Это гарантирует, что родительский процесс закончится прежде, чем ребенок. Вот программа й результирующий вывод: $ cat orphan.с ...посмотреть программу. #include- <stdio.h> main() { int pid; printf ("I’m the original process with PID %d and PPID %d.\n", getpid(), getppid()); pid = fork(); /* Дублироваться. Порожденный и родительский процессы */ /* продолжаются отсюда */ if (pid != 0) /* Ветвление, базирующееся */ /* на возвращаемом значении fork() */ { /* PID не ноль, тогда я должен быть родителем */ printf ("I’m the parent process with PID %d and PPID %d.\n",
getpid(), getppid() ); printf ("My child’s PID is %d\n", pid); } else { /* PID ноль, тогда я должен быть ребенком */ sleep (5); /★ Для уверенности, что родитель заканчивается первым */ printf ("I’m the child process with PID %d and PPID %d.\n", • getpid(), getppid()); } printf ("PID %d terminates.\n", getpidO); /* Оба процесса */ /* выполняются здесь */ } $ orphan ...выполнить программу. I’m the original process with PID 13364 and PPID 13346. I’m the parent process with PID 13364 and PPID 13346. PID 13364 terminates. I’m the child process with PID 13365 and PPID 1 ...осиротел! PID 13365 terminates. $ _ X Рис. 13.7 иллюстрирует эффект усыновления. init Родительский "умирает" первым "Усыновление дочернего Дочерний переживает родительский
Завершение процесса: exit() Процесс может закончиться в любое время, выполняя системный вызов exit (). Код завершения дочернего процесса может использоваться родительским процессом для различных целей. Синтаксис void exit (int status) Системный вызов exit о закрывает все дескрипторы файла процесса, освобождает его код, данные и стек, а затем заканчивает процесс. Когда дочерний процесс завершается, он посылает своему родителю сигнал sigchld и ждет его код статуса — status завершения для того, чтобы принять его. Используются только младшие восемь битов статуса, так что значения ограничены диапазоном 0—255. Процесс, который ожидает своего родителя, чтобы принять от него код возврата, называется зомби-процессом. Родитель принимает код завершения дочернего процесса, выполняя wait (). Ядро гарантирует, что все осиротевшие дети завершающегося процесса усыновляются процессом init путем установки их PPID в 1. Процесс init всегда принимает коды завершения своих порожденных процессов. exit () не возвращает никакого результата. Командные интерпретаторы могут получить доступ к коду завершения их последнего дочернего процесса через одну из их специальных переменных. Например, в представленном ниже коде С shell сохраняет код завершения последней команды в переменной $ status: % cat myexit.c ...посмотреть программу. #include <stdio.h> main () { printf ("I’m going to exit with return code 42\n"); exit(42); % myexit ... выполнить программу. I’m going to exit with return code 42
% echo $status 42 ...вывести код завершения. Во всех других shell значение возвращается в специальной переменной shell $?. Зомби-процессы Процесс, который заканчивается, не может оставить систему, пока его родитель не примет его код возврата. Если родительский процесс к этому моменту мертв, код возврата будет принят уже процессом init, который всегда принимает коды возврата своих порожденных процессов. Однако если родитель процесса функционирует, но никогда не выполняет системный вызов wait о, код возврата дочернего процесса никогда не будет принят, и процесс останется в состоянии "зомби". Зомби-процесс не имеет кода, данных или стека, так что он не расходует много системных ресурсов, но он продолжает существовать в системной таблице процессов, которая имеет фиксированный размер. Слишком большое количество зомби-процессов может потребовать вмешательства системного администратора. (Для дополнительной информации см. главу 15.) Представленная далее программа создает зомби-процесс, который был показан в выводе утилиты ps. Когда был "убит" родительский процесс, порожденный процесс был усыновлен процессом init, и ему было позволено "успокоиться". Вот код: $ cat zombie.с ...посмотреть программу, ^include <stdio.h> main () { int pid; pid = fork(); /* Дублироваться */ if (pid != 0) /* Ветвление, базирующееся на возвращаемом */ /* значении fork() */ while (1) /* Никогда не заканчивается */ /* и никогда не выполняет wait() */ sleep (1000); else
exit (42); /* Выход с небольшим номером */ $ zombie & ... выполнить программу в фоновом режиме. [1] 13545 $ ps PID ТТ STAT 13535 р2 S 13545 р2 S ...получить статус TIME COMMAND 0:00 -ksh (ksh) 0:00 zombie 13546 р2 Z 0:00 <defunct> 13547 р2 R 0:00 ps $ kill 13545 [1] Terminated zombie $ ps PID TT STAT TIME COMMAND 13535 p2 S 0:00 -ksh (ksh) 13548 p2 R 0:00 ps $ процессов. ...shell. ...родительский процесс. ...порожденный зомби-процесс. ..."убить" родительский процесс. ...заметьте, что теперь зомби пропал. Ожидание дочернего процесса: wait() Родительский процесс может ждать завершения одного из своих дочерних процессов, и затем принять его код завершения, выполняя системных вызов wait(). Синтаксис pid_t wait (int* status} Системный вызов wait () заставляет процесс приостановиться, пока один из его дочерних процессов не закончит выполнение. Успешный вызов wait () возвращает PID завершившегося порожденного процесса и помещает код статуса в status, который кодируется следующим образом: • если правый байт status — 0, левый байт содержит восемь битов значения, возвращенного exit () или return (); • если правый байт отличен от 0, самые правые семь битов равны номеру сигнала, который заставил дочерний процесс завершить работу, и оставшийся бит самого правого байта установлен в 1, если порожденный процесс произвел дамп памяти
Если процесс выполняет системный вызов wait о и не имеет дочерних процессов, wait о возвращается немедленно с —1. Если процесс выполняет wait о и один или большее его порожденных процессов уже зомби, wait о возвращается немедленно со статусом одного из зомби. В приведенном ниже примере дочерний процесс заканчивается прежде завершения программы, выполняя exit () с кодом возврата 42. Между тем, родительский процесс выполнил wait о и приостановился, пока не получил код завершения своего порожденного процесса. В этой точке родитель вывел информацию о кончине порожденного процесса и выполнил остальную часть программы. $ cat mywait.с ...посмотреть программу. #include <stdio.h> main () int pid, status, childPid; printf ("I’m the parent process and my PID is %d\n", getpidO); pid = fork(); /* Дублироваться */ if (pid != 0) /* Ветвление, базирующееся */ /* на возвращаемом значении fork() */ { printf ("I’m the parent process with PID %d and PPID %d\n", getpid (), getppid ()); childPid = wait (^status); /* Ждать завершения дочернего процесса */ printf ("A child with PID %d terminated with exit code %d\n", childPid, status » 8) ; } else { printf ("I’m the child process with PID %d and PPID %d\n", getpid (), getppid ()); exit (42); /* Выход с небольшим числом */ } printf ("PID %d terminates \n ", getpidO); } $ my wait ...выполнить программу. I’m the parent process and my PID is 13464 I’m the child process with PID 13465 and PPID 13464
I’m the parent process with PID 13464 and PPID 13409 A child with PID 13465 terminated with exit code 42 PID 13-465 terminates $ Замена кода процесса: exec() Процесс может заменить свой текущий код, данные и стек с таковыми другого исполняемого файла, используя один из системных вызовов семейства ехес(). Когда процесс выполняет ехес(), его номера PID и PPID остаются теми же самыми — заменяется лишь код, выполняемый процессом. Члены семейства ехес(), перечисленные ниже, не являются действительными системными вызовами: скорее, они функции библиотеки С, которые вызывают системный вызов execve(). execve () непосредственно почти не применяется, поскольку содержит некоторые редко используемые опции. Синтаксис int excecl (const const char* char* pa th, argl, const char* ..., const argOr char* argn, NULL) int execv (const char* pa th, const char* argv[]} int execlp (const const char* char* pa th, argl, const char* argO, ..., const char* argn, NULL) int execvp (const char* pa th, const char* argv[]) Библиотечные процедуры семейства ехес() заменяют код вызывающего процесса, данные и стек из исполняемого файла, чье путевое имя хранится В path. execl() идентична execipO, a execv() идентична execvpO, за исключением того, что execio и execvо требуют абсолютного или относительного путевого имени исполняемого файла, который должен быть предоставлен. execlpo и execvpO для поиска пути используют переменную окружения $ратн. Если исполняемый файл не найден, системный вызов возвращает — 1; иначе вызывающий процесс заменяет свой код, данные и стек из исполняемого файла и начинает выполнять новый код. Нормально завершенный системный вызов ехес () никогда не возвращается. ехесК) и execipO вызывают исполняемый файл со строковыми аргументами, указанными в argi, argn. Аргумент агдО должен содержать имя исполняемого файла, а список аргументов обязан заканчиваться NULL.
execv() и execvp () вызывают исполняемый файл со строковыми аргументами, указанными В argv[l], argv[n], где argv[n+l] — ЭТО NULL. Аргумент argv[0] должен содержать имя исполняемого файла. В приведенном ниже примере программа выводит сообщение, а затем заменяет свой код кодом исполняемого файла is: $ cat myexec.c ...посмотреть программу. #include <stdio.h> main() printf ("I’m process %d and I’m about to exec an Is -l\n", getpidO); execl (’’/bin/ls’’,. "Is", "-I", NULL); /* Выполнить Is */ printf ("This line should never be executed\n"}; } $ myexec ...выполнить программу. I’m process 13623 and I’m about to exec an Is -1 total 125 -rw-r—r— 1 glass 277 Feb 15 00:47 myexec.c -rwxr-xr-x 1 glass 24576 Feb 15 00:48 myexec $ _ Обратите внимание, что execl () завершился нормально и поэтому ничего не вернул. Смена каталогов: chdir() Каждый процесс имеет текущий рабочий каталог, который используется при обработке относительного путевого имени. Дочерний процесс наследует текущий рабочий каталог от своего родителя. Например, когда утилита выполняется из shell, ее процесс наследует текущий рабочий каталог shell. Для изменения текущего рабочего каталога процесса служит системный вызов chdir (). Синтаксис int chdir (const char* pathname) Системный вызов chdir () изменяет текущий рабочий каталог процесса на каталог pathname. Процесс должен иметь, разрешение выполнения каталога, чтобы завершиться без ошибки, chdir () возвращает 0, если закончился нормально, иначе возвращает —1.
В следующем примере процесс вводит название текущего рабочего каталога перед и после выполнения системного вызова chdir о'путем выполнения команды pwd, используя библиотечную процедуру system о: $ cat туchdir.с ...посмотреть исходный код. #include <stdio.h> main () { system ("pwd"); /* Вывести текущий рабочий каталог */ chdir ("/"); /* Изменить рабочий каталог на корневой каталог */ system ("pwd"); /* Вывести новый рабочий каталог */ chdir ("/home/glass"); /* Изменить */ system ("pwd"); /* Вывести */ } $ туchdir ...выполнить программу. /home/glass / /home/glass $ Смена приоритетов: nice() Каждый процесс имеет значение приоритета между —20 и +19, влияющее на количество времени ЦП, которое выделяется процессу. Вообще, чем меньше значение приоритета, тем быстрее будет выполняться процесс. Только процессы привилегированного пользователя (root) и ядра (описанные в главе 14) могут иметь отрицательное значение приоритета, а приоритеты shell, выполняющих вход в систему, начинаются с 0. Дочерний процесс наследует значение приоритета от родителя и может изменять его с помощью библиотечной процедуры nice о. Синтаксис int nice (int delta) Библиотечная процедура nice о добавляет delta к текущему значению приоритета процесса. Только привилегированный пользователь (root) может определить значение delta, которое приводит к отрицательному значению приоритета. Допустимые значения приоритета находятся между —20 и +19. Если значение delta определено таким образом, что выводит значение приоритета вне предела, значение усекается до предела. Ес
ли библиотечная процедура nice () завершается нормально, она возвра-щает новое значение, иначе возвращает —1. Обратите внимание, что это может вызвать проблемы, т. к. значение —1 является допустимым. В приведенном ниже примере процесс выполняет утилиты ps перед и после пары вызовов nice (). $ cat mynice.c ...посмотреть исходный код. #include <stdio.h> main () { printf ("original priority\n"); system ("ps"); /* Выполнить ps */ nice (0); /* Добавит 0 к моему приоритету */ printf ("running at priority 0\n"); system ("ps"); /★ Выполнить другой ps */ nice (10); /* Добавит 10 к моему приоритету */ printf ("running at priority 10\n"); system ("ps"); /* Выполнить последний ps */ $ mynice original priority ...выполнить программу. PID TT STAT TIME COMMAND 15099 p2 S 15206 p2 S 15207 p2 S 15208 p2 R 0:00 -sh (sh) 0:00 a.out 0:00 sh —c ps 0:00 ps running at priority 0 PID TT STAT TIME COMMAND ...добавление 0 не меняет его. 15099 р2 S 15206 р2 S 15209 р2 S 15210 р2 R 0:00 -sh (sh) 0:00 a.out 0:00 sh -c ps 0:00 ps running at priority 10 добавление 10 заставляет их работать медленнее. PID TT STAT TIME COMMAND 15099 p2 S 0:00 -sh (sh) 15206 p2 S N 0:00 a.out
15211 р2 S N 0:00 sh -с ps 15212 p2 RN 0:00 ps $ Обратите внимание, что при значении приоритета процесса, отличного от нуля, оно отмечается утилитой ps, как N, вместе с командами sh и ps, которые были созданы благодаря библиотечному вызову system о . Доступ к ID пользователя и ID группы Ниже приводится описание системных вызовов и библиотечных процедур, которые позволяют читать и устанавливать реальный и эффективный ID процесса. Синтаксис uid__t getuidQ uid_t geteuidO gid_t getgid() gid_t getegidO Системные вызовы getuido и geteuidO возвращают, соответственно, реальный и эффективный пользовательский ID запрашивающего процесса. Системные вызовы getgido и getegidO возвращают, соответствен-но, реальный и эффективный ID группы запрашивающего процесса. Номера ID, соответствующие пользовательскому ID и ID группы, перечислены в файлах /etc/passwd и /etc/group. Эти вызовы всегда завершаются без ошибок. Синтаксис int setuid (uid_t id) int seteuid (uid_t id) int setgid (gid_t icfj int setegid (gid_t id) Библиотечные процедуры seteuido и setegid о устанавливают эффективный пользовательский (групповой) ID запрашивающего процесса. Библиотечные процедуры setuid о и setgid о устанавливают на указанное значение эффективный и реальный пользовательский (групповой) ID запрашивающего процесса.
Эти вызовы завершаются нормально, если выполняются привилегированным пользователем или если id — это реальный или эффективный пользовательский (групповой) ID запрашивающего процесса. Они возвращают 0, если заканчиваются без ошибок; иначе возвращают —1. Пример программы: фоновая обработка Далее мы исследуем код, который использует системные вызовы fork о и ехесо, чтобы запустить программу в фоновом режиме. Первоначальный процесс создает дочерний процесс для выполнения указанного файла, а затем заканчивается. Осиротевший порожденный процесс автоматически усыновляется процессом init. $ cat background, с ...посмотреть программу. #include <stdio.h> main (argc, argv) int argc; char* argv []; { if (fork() == 0) /* Дочерний процесс */ execvp (argv[l], &argv[l]); /* Выполнить другую программу */ fprintf (stderr, "Could not execute %s\n", argv[l]); } } $ background cc mywait.c ...выполнить программу. $ ps ...подтвредить, что cc работает в фоне. PID ТТ STAT TIME COMMAND 13664 рО S 0:00 -csh (csh) 13716 рО R 0:00 ps 13717 рО D 0:00 сс mywait. с $ . Обратите внимание, как необчно передан список аргументов от main о к execvp(), указывая &argv[i] в качестве второго аргумента execvp(). Заметьте также, что использован вызов execvp () вместо execv (), чтобы программа для поиска исполняемого файла могла использовать $ратн. Пример программы: использование диска Следующий пример программирования применяет новую технику для подсчета файлов, котоые не являются каталогами, в иерархии. Когда программа
стартует, ее первый аргумент должен быть именем каталога для поиска. Программа исследует элемент за элементом в каталоге, порождая новый процесс для каждого. Любой дочерний процесс либо выходит с результатом 1, если его связанный файл — не каталог, либо повторяет процесс, суммируя коды выхода своих потомков и выходя с общим счетом. Этот алгоритм интересен, но бессмыслен: мало того, что он создает большое количество процессов, что не особенно эффективно, но еще и используется код завершения, чтобы возвратить количество файлов, а он ограничен восьмибитовым значением. Код следующий: $ cat count.с ...посмотреть программу. #include <stdio.h> #include <fcntl.h> #include <sys/dirent.h> #include <sys/stat.h> long processFile(); long processDirectory(); main (argc, argv) int argc; char* argv []; { long count; count = processFile (argv[l]); printf ("Total number of non-directory files is %ld\n"/ count); return (/* ВЫХОД-УСПЕШНО */ 0) ; } long processFile (name) char* name; { struct stat statBuf; /* Чтобы хранить возвращаемые данные от stat() */ mode_t mode; int result; result = stat (name, &statBuf); /* Считать указанный файл */ if (result == -1) return (0); /* Ошибка */ mode = statBuf.st_mode; /* Посмотреть вид файла */ if (S_ISDIR (mode)) /* Каталог */ return (processDirectory (name)); else return (1); /* Был обработан не каталог */
long processDirectory (dirName) char * dirName; { int fd, children, i, charsRead, childPid, status; long count, totalCount; char fileName [100]; struct dirent dirEntry; fd = open (dirName, O_RDONLY); /* Открыть каталог для чтения */ children =0; /* Инициализировать счетчик дочерних процессов */ while (1) /* Просмотреть каталог */ { charsRead = getdents (fd, &dirEntry, sizeof (struct dirent)); if (charsRead == 0) break; /* Конец каталога */ if (strcmp (dirEntry.d_name, ”.”) != 0 && strcmp (dirEntry.d_name, "..") != 0) { if (fork() == 0) /* Создать дочерний процесс */ /* для обработки каталога */ { sprintf (fileName, "%s/%s’’, dirName, dirEntry.d_name) ; count = processFile (fileName); exit (count); } else ++children; /* Увеличить счетчик дочерних процессов */ } Iseek (fd, dirEntry.d_off, SEEK_SET);/* Перейти на следующий.каталог */ } close (fd); /* Закрыть каталог */ totalCount =0; /* Инициализировать счетчик файлов */ for (i=l; i <= children; i++) /* Ждать завершения дочерних процессов */ { childPid = wait (&status); /* Принять код завершения дочернего */ totalCount += (status » 8); /* Обновить счетчик файлов */ } return (totalCount); /* Вернуть количество файлов в каталоге */ } $ Is -F ...посмотреть текущий каталог. a.out* disk.с fork tmp/ zombie*
background myexec.c myfork.c mywait.c background.с myexit.c orphan.c mywait* count* myexit* orphan* zombie.c $ Is tmp ...вывести только подкаталоги. a. out* disk.c myexit.c orphan.с background.c zombie.c myexec.c myfork.c mywait.c $ count . ...считать обычные файлы с ".". Total number of non-directory files is 25 $ Подпроцессы-нити Множественные процессы дороги для того, чтобы их создавать либо заново, либо путем копирования существующего процесса системным вызовом fork о. Часто целиком новое пространство процесса не является необходимостью для маленькой, но все же независимой, задачи в программе. Фактически можно пожелать, чтобы отдельные задачи были способны разделить некоторые ресурсы в процессе, такие как пространство памяти или открытое устройство. Когда стали доступны мультипроцессорные системы, выяснилось, что UNIX нуждался в лучшем способе воспользоваться преимуществом многочисленных процессоров без того, чтобы запускать новый процесс на отдельном процессоре. Нить (thread) — абстракция, которая делает возможным множественные "нити управления" в единственном пространстве процесса. О ней можно думать почти как о процессе в пределах процесса (почти). Во многих отношениях модель нити подобна модели процесса UNIX. Терминология некоторых из реализаций нитей бывает путанной. Вы можете найти термин "легкие процессы" (lightweight processes), используемый поочередно с "нитью", или найти места, где применяются оба термина, чтобы характеризовать тонкие различия. В большинстве случаев идея относительно более легкого веса (т. е. менее дорогие издержки) — это то, что планировалось. Мы будем попросту ссылаться на нити. Так как реализация функциональных возможностей нити варьируется в различных версиях UNIX, исследование любой незаконно игнорировало бы другие, а полное изучение всех текущих реализаций — вне возможностей данного обзора. Поэтому будут представлены функциональные возможности нити UNIX на высоком уровне, который является общим для всех реализаций. Рекомендуется обратиться за справкой к документации используемой версии UNIX для получения информации относительно конкретных системных вызовов.
Управление нитями Четыре главных функции поддерживают общие возможности управления нитями в большинстве реализаций: □ create — создать нить; П join — приостановиться и ждать завершения созданной нити (подобно системному вызову wait о между родительскими и дочерними процессами); □ detach — позволить нити передать ее ресурсы системе, когда она заканчивается, и не требовать объединения (в этом случае значение выхода недоступно); □ terminate — вернуть ресурсы процессу. Синхронизация нитей В многопоточном окружении одна или несколько нитей могут быть созданы, чтобы справиться с определенными задачами. Если задачи не связаны, нити могут быть запущены и выполняться до завершения. Если какая-нибудь часть задачи требует информации от другой задачи, взаимодействие между нитями должно быть синхронизировано. Синхронизация способна часто осуществляться через стандартные IPC-механизмы UNIX, но большинство библиотек нитей также предусматривают синхронизирующие примитивы, характерные для использования нитями. Объект mutex может использоваться, чтобы управлять взаимным исключением между нитями. Mutex-объекты могут быть созданы, разрушены, блокированы и разблокированы. Признаки mutex-объекта разделяются между нитями и применяются, чтобы позволить другим нитям знать состояние нити, которую mutex-объект описывает. Mutex-объекты могут также использоваться в соединении с условными переменными, которые поддерживают значение (такое как порог), чтобы обеспечить более точное управление синхронизацией нитей. Безопасность нитей Итак, теперь вы синхронизировали различные нити управления в вашей собственной программе. Но как быть с библиотечными функциями, которые они вызывают? Должен ли ваш код синхронизировать (например) использование графической библиотеки, чтобы гарантировать запрет одновременного вывода нитями в одну и ту же часть экрана? Как насчет двух нитей, которые используют математическую библиотеку, чтобы модернизировать разделяемые данные? Вы синхронизировали применение ваших переменных, но используют ли математические функции какие-нибудь разделяемые
переменные? Являются ли функции повторно входимыми (т. е. может ли одновременно больше, чем одна точка управления использоваться в пространстве памяти функции)? Задавая эти вопросы, вы спрашиваете, является ли библиотека нитей безопасной? Безопасно ли вызывать функции этих библиотек из многопоточной программы? Вероятно, не трудно вообразить разновидности непредвиденных проблем, которые могут возникнуть при таких обстоятельствах. Если продавец или автор библиотеки заявляет, что она является нитебезопасной, вы должны предположить, что это не так, и писать ваш код соответственно (управляя взаимным исключительным доступом к библиотеке между различными нитями в своей программе). Другие связанные с процессом системные вызовы, которые описаны ранее, могут быть затронуты реализацией нитей. Например, каждая нить поддерживает свой собственный стек, маску сигнала и локальную область хранения. Поэтому, не всегда очевидно, в какой момент системный вызов применяется только к нити или к полному процессу, управляющему нитью. Для вас будет важно определить, как ваша реализация нитей может оказывать влияние на другие системные вызовы UNIX. Перенаправление Когда процесс разветвляется, дочерний процесс наследует копию дескрипторов файла своего родителя. Когда процесс выполняет системный вызов ехес (), все незакрытые после выполнения дескрипторы файла остаются незатронутыми, включая стандартный ввод, вывод и каналы ошибки. UNIX shell использует эту информацию, чтобы осуществить перенаправление. Например, предположим, что вы напечатаете на терминале команду: $ 1s > Is.out чтобы выполнить перенаправление. Shell производит следующий ряд действий: 1. Родительский shell ветвится и ждет завершения дочернего shell. 2. Дочерний shell открывает файл Is.out, создавая или усекая его по мере необходимости. 3. Дочерний shell дублирует дескриптор файла Is.out в стандартный дескриптор файла вывода с номером 1 и закрывает первоначальный дескриптор Is.out. Поэтому весь стандартный вывод перенаправляется в Is.out. 4. Дочерний shell выполняет системный вызов ехес() с утилитой 1s. Так как дескрипторы файла унаследованы во время выполнения ехес (), весь стандартный вывод 1s идет в Is.out.
5. Когда дочерний shell завершается, родительский возобновляется. Дескрипторы файла-родителя не затронуты действиями дочернего процесса, поскольку каждый процесс поддерживает собственную персональную таблицу дескрипторов. Чтобы перенаправить стандартный канал ошибки в дополнение к стандартному выводу, shell мог бы просто дублировать дескриптор файла Is.out дважды — один раз в дескриптор 1 и другой раз в дескриптор 2. Представленная ниже программа делает приблизительно тот же самый вид перенаправления, как и UNIX shell. При вызове с именем файла в качестве первого параметра и последовательностью команд в качестве остальных параметров программа redirect осуществляет перенаправление стандартного вывода команды в указанный файл. $ cat redirect, с #include <stdio.h> ...посмотреть программу. #include <fcntl.h> main (argc, argv) int argc; char* argv []; { int fd; /* Открыть файл для переназначения */ fd = open (argv[l], O_CREAT | O_TRUNC | O_WRONLY, 0600); dup2 (fd, 1); /* Дублировать дескриптор на стандартный вывод */ close (fd); /* Закрыть оригинальный дескриптор */ /* для сохранения пространства дескрипторов */ execvp (argv[2], &argv[2]); /* Вызвать программу; унаследует stdout */ реггог ("main"); /* Не должна выполняться никогда */ } $ redirect Is.out Is -1 ...переназначить Is -1 на Is.out. $ cat Is.out ...посмотреть выходной файл, total 5 -rw-r-xr-x 1 gglass 0 Feb 15 10:35 Is.out -rw-r-xr-x 1 gglass 449 Feb 15 10:35 redirect.c -rwxr-xr-x 1 gglass 3697 Feb 15 10:33 redirect $ _ Internet shell, описанный в конце этой главы, имеет лучшие возможности переназначения, чем стандартный shell UNIX; он может даже переадресовывать вывод на другой Internet shell на удаленном хосте.
Сигналы Программы должны иногда иметь дело с неожиданными или непредсказуемыми событиями, например, такими: □ ошибка плавающей запятой; □ отказ питания; □ ’’звонок” будильника; □ ’’смерть" дочернего процесса; □ завершение, запрашиваемое от пользователя (т. е. нажатие комбинации клавиш <Ctrl>+<C>); □ запрос приостановки от пользователя (т. е. нажатие комбинации клавиш <Ctrl>+<Z>). Эти виды событий иногда называются прерываниями, т. к. они должны прервать обычный поток программы, чтобы быть обработанным. Когда UNIX распознает, что сложилась такая ситуация, ОС посылает соответствующему процессу сигнал. Существует уникальный пронумерованный сигнал для каждого возможного случая. Например, если процесс становится причиной ошибки плавающей запятой, ядро посылает сигнал номер 8 процессу, нарушающему нормальную работу (рис. 13.8). Ядро — это не единственный источник, который может посылать сигнал; любой процесс способен отправить сигнал произвольному процессу, когда он имеет права на это. (Правила, касающиеся прав, обсуждаются далее.) Сигнал № 8 Процесс Рис. 13.8. Сигнал ошибки плавающей запятой Через специальную часть кода, называемую обработчиком сигнала, программист может принять меры, чтобы конкретный сигнал был проигнорирован или обработан. В последнем случае получающий сигнал процесс приостанавливает свой текущий поток управления, выполняет обработчик сигнала и возобновляет первоначальный поток управления, когда обработчик сигнала заканчивает свою работу. Путем изучения сигналов вы можете "защитить" свои программы от нажатия комбинации клавиш <Ctrl>+<C>, организовывать завершение программы
по сигналу будильника, если она слишком долго выполняет задачу, и узнать, как UNIX использует сигналы во время ежедневных операций. Определенные сигналы Сигналы определены в файле /usr/include/sys/signal.h. Программист может выбрать для конкретного сигнала либо обработчик сигнала, определенный пользователем, либо вызывать обработчик сигнала, по умолчанию предоставляемый ядром, либо проигнорировать его. По умолчанию обработчик обычно выполняет одно из следующих действий: □ завершает процесс и генерирует файл-дубликат содержимого оперативной памяти (dump); □ завершает процесс без генерации файла дубликата содержимого оперативной памяти (quit); □ игнорирует и отказывается от сигнала (ignore); □ приостанавливает процесс (suspend); □ возобновляет процесс. Список сигналов В табл. 13.7 перечислены предопределенные сигналы System V наряду с их макроопределениями, числовыми значениями, действиями по умолчанию и кратким описанием каждого. Таблица 13.7. Сигналы Макро Номер По умолчанию Описание SIGHUP 1 quit Зависание SIGINT 2 quit Сигнал прерывания SIGQUIT 3 dump Завершить SIGILL 4 dump Неправильная команда SIGTRAP 5 dump Системное прерывание трассировки (используется отладчиком) SIGABRT 6 dump Аварийное прекращение SIGEMT 7 dump Команда эмуляции системного прерывания SIGFPE 8 dump Арифметическое исключение SIGKILL 9 quit "Убить” (не может быть пойман, блокирован или игнорирован) 20 Зак. 786
Таблица 13.7 (окончание) Макро Номер По умолчанию Описание SIGBUS 10 dump Ошибка шины — bus error (плохой формат , адреса) SIGSEGV 11 dump Нарушение сегментации (выход за диапазон адреса) SIGSYS 12 dump Плохой аргумент системного вызова SIGPIPE 13 quit Запись в конвейер или сокет, который никто не читает SIGALRM 14 quit Будильник SIGTERM 15 quit Сигнал завершения программы (сигнал по умолчанию, посылается kill) SIGUSR1 16 quit Пользовательский сигнал 1 SIGUSR2 17 quit Пользовательский сигнал 2 SIGCHLD 18 ignore Статус дочернего процесса изменился SIGPWR 19 ignore Отказ питания или повторный старт SIGWINCH 20 ignore Изменение размера окна SIGURG 21 ignore Срочная ситуация сокета SIGPOLL 22 exit Опрашиваемое событие SIGSTOP 23 quit Остановленный (сигнал) SIGSTP 24 quit Остановленный (пользователь) SIGCONT 25 ignore Продолженный SIGTTIN 26 quit Остановленный (tty-ввод) SIGTTOU 27 quit Остановленный (tty-вывод) SIGVTALRM 28 quit Действительное время истекло SIGPROF 29 quit Время профилирования истекло SIGXCPU 30 dump Превышен лимит времени ЦП SIGXFSZ 31 dump Превышен лимит размера файла Сигналы терминала Самый легкий способ послать сигнал приоритетному процессу — это нажать комбинацию клавиш <Ctrl>+<C> или <Ctrl>+<Z>. Когда драйвер терминала (часть программного обеспечения, которое поддерживает терминал) рас
познает комбинацию клавиш <Ctrl>+<C>, он посылает сигнал sigint всем текущим приоритетным процессам. Точно так же комбинация клавиш <Ctrl>+<Z> заставляет драйвер посылать сигнал sigtstp всем текущим приоритетным процессам. По умолчанию sigint заканчивает, a sigtstp приостанавливает процесс. Далее будет продемонстрировано, как выполнить подобные действия из С-программы. Запрос аварийного сигнала: alarmf) Один из самых простых способов увидеть сигнал в действии состоит в том, чтобы организовать процесс для получения сигнала будильника, sigalrm, с помощью библиотечной процедуры alarm(). Обработчик по умолчанию для этого сигнала выводит сообщение "Alarm clock" и заканчивает процесс. Синтаксис unsigned int alarm (unsigned int count) Библиотечная процедура alarm о предписывает ядру послать сигнал sigalrm запрашивающему процессу после count секунд. Если сигнал был уже намечен, он переопределяется. Если count равно 0, Любые незаконченные запросы отменяются, alarm о возвращает количество секунд, которые остаются до передачи сигнала. Ниже приведена программа, которая использует alarm (), вместе с ее выводом: $ cat alarm.с ...посмотреть программу. #include <stdio.h> main () { alarm (3); /* Запланировать аварийный сигнал через 3 секунды */ printf ("Looping forever.\п"); while (1); printf ("This line should never be executed\n"); } $ alarm ...выполнить программу. Looping forever... Alam clock . . .происходит через 3 секунды. $
Обработка сигналов: signalO Последний пример программы реагировал на сигнал sigalrm в манере, заданной по умолчанию. Библиотечная процедура signal о может использоваться для подмены действия по умолчанию. Синтаксис void (*signal (int sigCode, void (★func) (int))) (int) Библиотечная процедура signal о позволяет процессу определять выполняемое действие, когда конкретный сигнал будет получен. Параметр sigCode задает номер сигнала, который должен быть перепрограммирован, a fund может быть одним из значений: • siG-iGN указывает, что сигнал должен быть проигнорирован и отвергнут; • sig dfl означает, что должен использоваться подразумеваемый обработчик ядра; • адрес определенной пользователем функции, который указывает, что функция должна быть выполнена, когда поступает сигнал. Имеющие силу номера сигналов хранятся в файле /usr/include/signal.h. Сигналы sigkill и sigstp не могут быть перепрограммированы. Дочерний процесс наследует назначения сигнала от своего родителя во время выполнения системного вызова fork о. Когда процесс выполняет системный вызов ехес (), предварительно игнорируемые сигналы остаются игнорируемыми, но обработчики заменяются на обработчики по умолчанию. За исключением sigchld, сигналы не помещаются в стек. Это означает, что, если процесс спит, и ему посланы три идентичных сигнала, фактически обрабатывается только один из сигналов, signal о возвращает предыдущее значение func, связанное с sigCode, если завершается нормально; иначе возвращает —1. В представленный выше пример были внесены изменения так, чтобы был пойман и эффективно обработан сигнал sigalrm: □ установлен обработчик сигнала aiarmHandier(), использующий signal(); □ организован цикл while, эксплуатирующий в меньшей степени ресурсы системы разделения времени, с помощью библиотечной процедуры pause о. Старая версия цикла while имела пустое тело, которое заставляло его выполнять цикл очень быстро и поглощать ресурсы ЦП. Новая версия цикла while каждый раз приостанавливается до получения сигнала.
Вот обновленная версия программы: $ cat handler.с ...посмотреть программу. #include <stdio.h> К ’ • - #include <signal.h> int alarmFlag =0; /* Глобальный флаг alarm */ void alarmHandler(); /* Опережающее описание обработчика alarm */ /*i*^i***^**Jr*****i***Jr*************i^****JrJr**********Jr***Jr**** / main() { signal (SIGALRM, alarmHandler); /* Установка обработчика сигнала */ alarm (3); /* Назначить сигнал alarm через 3 секунды */ printf ("Looping...\n"); while (! alarmFlag) /* Цикл, пока флаг установлен */ { pause(); /* Ждать сигнала */ } printf ("Loop ends due to alarm signal\n"); } у**************************************************************/ void alarmHandler() printf ("An alarm clock signal was received\n"); alarmFlag = 1; } $ handler ...выполнить программу. Looping... An alarm clock signal was received ...происходит через 3 секунды. Loop ends due to alarm signal $ Синтаксис int pause (void) Библиотечная процедура pause о приостанавливает запрашивающий процесс и возвращается, когда вызывающий процесс получает сигнал, pause () чаще всего применяется для эффективного ожидания сигнала alarm. Она не возвращает ничего полезного.
Защита критического кода и формирование цепочки обработчиков прерываний Описанные выше методы могут применяться для защиты критических участков кода против нажатия комбинации клавиш <Ctrl>+<C> и других сигналов. В этих случаях обычно сохраняют предыдущее значение обработчика, чтобы оно могло быть восстановлено после выполнения критического участка кода. Ниже приведен исходный код программы, которая защищает себя против сигналов sigint: $ cat critical.с ...посмотреть программу, ftinclude <stdio.h> ftinclude <signal.h> main() void (*oldHandler)(); /* Для хранения старого значения обработчика */ printf ("I can be <Ctrl>+<C>\n"); sleep (3); oldHandler = signal (SIGINT, SIG_IGN); /* Игнорировать <Ctrl>+<C> */ printf ("I’m protected from <Ctrl>+<C> now\n"); sleep (3); signal (SIGINT, oldHandler); /* Восстановить старый обработчик */ printf ("I can be <Ctrl>+<C> again\n”); sleep (3); printf ("Bye!\n"); } $ critical ...выполнить программу. I can be <Ctrl>+<C> ЛС ...<Ctrl>+<C> работает здесь. $ critical ...выполнить программу снова. I can be <Ctrl>+<C> I’m protected from <Ctrl>+<C> now AC ...<Ctrl>+<C> игнорируется. I can be <Ctrl>+<C> again Bye! $
Посылка сигналов: kill() Процесс может посылать сигнал другому процессу, используя системный вызов kill о. Для системного вызова kill о его название не характеризует выполняемые им действия, поскольку многие из сигналов, которые он способен посылать, не заканчивают ("убивают") процесс. Вызов получил свое название вследствие исторических причин: когда UNIX была разработана, основным использованием сигналов оказалось завершение процессов. Синтаксис int kill (pid_t pidr int sigCode) Системный вызов kill о посылает сигнал со значением sigCode процессу с PID pid. kill о завершается нормально, а сигнал посылается до тех пор, пока, по крайней мере, одно из следующих условий удовлетворяется: • посылающий и получающий процессы имеют одного и того же владельца; • посылающий процесс принадлежит привилегированному пользователю (root). Есть несколько вариантов работы kill о: • если pid равен 0, сигнал посылается всем процессам в группе процесса-отправителя; • если pid равен —1 и отправитель принадлежит привилегированному пользователю (root), сигнал посылается всем процессам, включая отправителя; • если pid равен —1 и отправитель непривилегированный пользователь, сигнал посылается всем процессам, принадлежащим тому же самому владельцу, что и отправитель, исключая процесс посылки; • если pid отрицателен и не равен — 1, сигнал посылается всем процессам в группе процесса. (Группы процесса обсуждаются позже в данной главе.) Если kill о ухитрится послать сигнал без ошибки, по крайней мере, один сигнал, он возвращает 0; иначе возвращает —1. "Смерть" дочерних процессов Когда дочерний процесс заканчивается, он посылает своему родителю сигнал siGCHLD. Родительский процесс часто устанавливает обработчик, чтобы взаимодействовать с этим сигналом, и обычно выполняет системный вызов
wait о для принятия кода завершения дочернего процесса и не позволить ему перейти в состояние "зомби”.4 Альтернативно родитель может выбрать игнорирование сигналов sigchld, тогда порожденный процесс автоматически перестает быть зомби. Одна из программ сокета, .которая представлена позже в данной главе, использует эту особенность. Приведенный ниже пример иллюстрирует обработчик сигнала sigchld и позволяет пользователю ограничить время для выполнения команды. Первый параметр limit — это максимальное количество секунд, которое отводится для работы, а остальные параметры — это сама команда. Программа работает, выполняя следующие шаги: 1. Родительский процесс устанавливает обработчик сигнала sigchld, который выполняется, когда его дочерний процесс заканчивается. 2. Родительский процесс разветвляется и, чтобы выполнить команду, порождает дочерний процесс. 3. Родительский процесс "спит" указанное количество секунд. Когда он пробуждается, то посылает своему дочернему процессу сигнал sigint, чтобы "убить" его. 4. Если порожденный процесс заканчивается прежде, чем его родитель выйдет из состояния сна, выполняется обработчик сигнала sigchld родителя, заставляя родителя немедленно завершиться. Вот исходный код и пример вывода программы: $ cat limit.с ...посмотреть программу. ttinclude <stdio.h> #include <sigrial.h> int delay; void childHandler(); /******•*•★********•************•*•*********************•*•***************/ main (argc, argv) int argc; char* argv[]; { int pid; signal (SIGCHLD, childHandler); /* Установить обработчик "смерти” */ /* ребенка */ pid = fork().; /* Дублироваться */ 4 Это означает, что дочерний процесс полностью прекратил работу, чтобы не оставаться больше зомби.
if (pid == 0) /* Дочерний процесс */ { execvp (argv[2], &argv[2]); /* Выполнить команду */ реггог ("limit"); /* Не должно выполняться никогда */ } else /* Родитель */ { sscanf (argv[l], "%d", &delay); /* Прочитать задержку */ /* из командной строки */ sleep (delay); /* Заснуть на указанное количество секунд */ printf ("Child %d exceeded limit and is being killed\n", pid); kill (pid, SIGINT); /* "Убить" дочерний процесс */ } } /jr****'Jt***i**********^**'^*******^*****^*4r**Jr^*****'^-Jr*^4-*****Jr**Jr****** / void childHandler() /* Выполняется, если дочерний процесс "умирает" */ /* раньше родителя */ { int childPid, childstatus; childPid = wait (&childStatus); /* Принять код завершения дочернего */ printf ("Child %d terminated within %d seconds\n", childPid, delay); exit (/* ВЫВОД_УСПЕШНО */ 0); } $ limit 5 Is ...выполнить программу; команда завершилась хорошо, a.out alarm critical handler limit alarm.c critical.c handler.c limit.c Child 4030 terminated within 5 seconds $ limit 4 sleep 100 ...выполнить ее снова; ...команда выполняется слишком долго. Child 4032 exceeded limit and is being killed $ Приостановка и возобновление процессов Сигналы sigstop и sigcont, соответственно, приостанавливают и возобновляют процесс. Они используются UNIX shell (большинством shell, кроме Bourne shell), поддерживающими управление заданиями, чтобы реализовать встроенные команды типа stop, fg и bg.
В приведенном далее примере главная программа создает два дочерних про-цеса, которые входят в бесконечный цикл и выводят сообщение каждую секунду. Главная программа ждет в течение трех секунд, а затем приостанавливает первый порожденный процесс. Второй процесс продолжает выполняться, как обычно. После следующих трех секунд родитель возобновляет первый дочерний процесс, ожидает некоторое, более продолжительное, время и завершает оба порожденных процесса. Вот код: $ cat pulse.с ...вывести программу. #include <signal.h> ftinclude <stdio.h> main() { int pidl; int pid2; pidl = fork(); if (pidl == 0) /* Первый дочерний процесс */ { while (1) /* Бесконечный цикл */ { printf ("pidl is alive\n"); sleep (1); } } pid2 = fork(); /* Второй дочерний процесс ★/ if (pid2 == 0) while (1) /★ Бесконечный цикл */ { printf (”pid2 is alive\n”); sleep (1); } } sleep (3); kill (pidl, SIGSTOP); /★ Приостановить первый дочерний процесс */ sleep (3); kill (pidl, SIGCONT); /* Возобновить первый дочерний процесс ★/ sleep (3); kill (pidl, SIGINT); /* ’’Убить” первый дочерний процесс ★/ kill (pid2, SIGINT); /* ’’Убить" второй дочерний процесс */
$ pulse ...выполнить программу. pidl is alive ...оба работают в первые три секунды. • pid2 is alive pidl is alive pid2 is alive pidl is alive pid2 is alive pid2 is alive ...только второй дочерний процесс сейчас работает. pid2 is alive pid2 is alive pidl is alive ...первый дочерний процесс возобновился. pid2 is alive pidl is alive pid2 is alive pidl is alive pid2 $ . is alive Группы процесса и терминалы управления Когда пользователь находится в shell и выполняет программу, которая создает несколько дочерних процессов, единственное нажатие комбинации клавиш <Ctrl>+<C> обычно заканчивает программу и ее порожденные процессы, а затем возвращает пользователя в shell. Чтобы поддержать этот вид поведения, в UNIX введено несколько новых концепций. □ В дополнение к уникальному номеру ID процесса каждый процесс является членом группы процесса. Несколько процессов могут быть членами одной и той же группы процесса. Когда процесс осуществляет ветвление, дочерний процесс наследует свою группу от родителя. Процесс может заменить свою группу на новое значение с помощью системного вызова setpgidO. Когда процесс выполняется, его группа процесса остается неизменной. □ Каждый процесс может иметь связанный с ним терминал управления — обычный терминал, с которого стартовал процесс. Когда процесс осуществляет ветвление, дочерний процесс наследует свой терминал управления от родителя. Когда процесс выполняется, его терминал управления остается тем же самым. х □ Каждый терминал может быть связан с единственным процессом управления. Когда обнаруживается метасимвол, подобный генерируемому комбинацией клавиш <Ctrl>+<C>, терминал посылает соответствующий сигнал всем процессам в группе своего процесса управления.
□ Если процесс пытается читать со своего терминала управления и не является членом той же группы, что и процесс управления терминалом, процессу посылается сигнал sigttin, который обычно приостанавливает его. Shell использует эти особенности описанным ниже образом. □ Когда начинается диалоговый shell, он является процессом управления терминала и воспринимает этот терминал в качестве своего терминала управления. Описание, как это происходит, лежит за рамками данной книги. □ Когда shell выполняет приоритетный процесс, дочерний shell занимает место в другой группе процесса перед выполнением команды и берет под свой контроль терминал. Любые сигналы, генерируемые терминалом, таким образом, идут в приоритетную команду, а не в первоначальный родительский shell. Когда приоритетная команда заканчивается, первоначальный родительский shell забирает назад управление терминалом. □ Когда shell выполняет фоновый процесс, дочерний shell занимает место в другой группе процесса пред выполнением, но не берет под свой контроль терминал. Любые сигналы, генерируемые терминалом, продолжают идти в shell. Если фоновый процесс пробует читать со своего терминала управления, он приостанавливается сигналом sigttin. Диаграмма на рис. 13.9 иллюстрирует типичную схему. Предположим, что процессы 145 и 230 — лидеры фоновых работ, а процесс 171 является лидером приоритетных работ. Системный вызов setpgido заменяет группу процесса. Процесс может определить свой текущий ID группы процесса через системный вызов getpgidO . Синтаксис pid_t setpgid (pid_t pidf pid_t pgrpid) Системный вызов setpgido устанавливает групповой ID процесса для процесса с pid на pgrpid. Если pid равен 0, групповой ID вызывающего процесса устанавливается на pgrpid. Для того чтобы setpgido завершился нормально и установил групповой ID процесса, по крайней мере, должно быть выполнено одно из условий: • вызывающий и указанный процессы должны иметь одного и того же владельца; • вызывающий процесс должен принадлежать привилегированному пользователю (root).
Когда процесс хочет запустить собственную уникальную группу процесса,* он обычно передает свой ID процесса в качестве второго параметра в системный вызов setpgido. Если setpgido завершается с ошибкой, то возвращает — 1. Процесс, управляющий терминалом —171 Рис. 13.9. Управляющие терминалы и группы процесса Синтаксис pid_t getpgid (pid_t pid) Системный вызов getpgid о возвращает групповой ID процесса с pid. Если pid равен 0, возвращается групповой ID вызывающего процесса. Очередной пример иллюстрирует случай, когда терминал распределяет сигналы всем процессам процессной группы своего процесса управления. Поскольку дочерний процесс унаследовал группу процесса от родителя, то и родитель, и порожденный процесс ловят сигнал sigint. Код следующий: $ cat pgrpl.c ...посмотреть программу, ftinclude <signal.h>
#include<stdio.h> void sigintHandler(); main() { signal (SIGINT, sigintHandler); /* Обработчик <Ctrl>+<C> */ if (fork() == 0) printf ("Child PID %d PGRP %d waits\n", getpidO, getpgid (0)); else printf ("Parent PID %d PGRP %dwaits\n", getpid(), getpgid (0)); pause(); /* Ждать сигнал */ } void sigintHandler() { printf ("Process %d got a SIGINT\n", getpidO); } $ pgrpl . . . выполнить программу. Parent PID 24583 PGRP 24583 waits Child PID 24584 PGRP 24583 waits ЛС ... нажать комбинацию клавиш <Ctrl>+<C>. Process 24584 got a SIGINT Process 24583 got a SIGINT $ _ Если процесс помещает себя в разные группы, он больше не связан с процессом управления терминалом и не получает сигналы с терминала. В приведенном ниже примере на дочерний процесс не воздействует нажатие комбинации клавиш <Ctrl>+<C>: $ cat pgrp2 .с ... посмотреть программу. ttinclude <signal.h> #include <stdio.h> void sigintHandler(); main() { int i; signal (SIGINT, sigintHandler); /* Установить обработчик сигнала */ if (fork() == 0) setpgid (0, getpidO); /* Поместить дочерний процесс */ /* в его собственную группу процесса ★/ printf ("Process PID %d PGRP %dwaits\n", getpidO, getpgid (0));
for (i = 1; i <= 3; i++) /* Цикл три раза */ printf ("Process %d is alive\n", getpidO); sleep(1); void sigintHandler() printf ("Process %d got a SIGINT\n", getpidO); exit (1); $ pgrp2 . . . выполнить программу. Process PID 24591 PGRP 24591 waits Process PID 24592 PGRP 24592 waits . <Ctrl>+<C> Process 24591 got a SIGINT Process 24592 is alive ...родитель получает сигнал. ...дочерний процесс продолжается. Process 24592 is alive Process 24592 is alive $ _ Если процесс пытается читать со своего терминала управления после того, как он непосредственно отключается от процесса управления терминалом, ему посылается сигнал sigttin, который по умолчанию приостанавливает приемник. Далее в примере отлавливается сигнал sigttin собственным обработчиком: $ cat рдгрЗ.с ...посмотреть программу. '#include <signal.h> #include <stdio.h> #include <sys/terraio. h> #include <fcntl.h> void sigttinHandler(); main () { int status; char str [100]; if (fork() ==0} /* Дочерний процесс */ signal (SIGTTIN, sigttinHandler); /★ Установить обработчик */
setpgid (0, getpidO); /* Поместить себя в новую группу процесса */ printf ("Enter a string: "); scant ("%s", str); /* Пытаться читать с управляющего терминала */ printf ("You entered %s\n", str); else /* Родитель */ { wait (&status); /* Ждать завершения дочернего процесса */ } } void sigttinHandler() { printf ("Attempted inappropriate read fpom control terminal\n"); exit (1); } $ рдгрЗ ...выполнить программу. Enter a string: Attempted inappropriate read from control terminal $ IPC Взаимодействие процессов (Interprocess communication, IPC) — это общий термин, описывающий, как два процесса могут обмениваться информацией друг с другом. Вообще, два процесса могут работать как на одной и той же машине, так и на разных, хотя некоторые механизмы IPC могут поддерживать только локальное использование (например, сигналы и конвейеры). IPC может быть обменом данными, где два или больше процессов совместно обрабатывают данные или другую синхронизированную информацию с целью помочь двум независимым, но связанным процессам запланировать работу так, чтобы они не перекрывались. Конвейеры Конвейеры (pipes) — механизм межпроцессного взаимодействия, который позволяет двум или более процессам посылать информацию друг другу. Они обычно используются внутри shell, чтобы соединить стандартный вывод одной утилиты со стандартным вводом другой. Например, ниже приведена простая команда shell, которая определяет, сколько пользователей находится в системе: $ who | wc -1
Утилита who генерирует одну строку вывода для одного пользователя. Этот вывод затем "конвейеризируется” в утилиту wc, которая при вызове с опцией -1 выводит общее количество строк на своем входе. Итак, конвейеризированная команда особым образом вычисляет общее количество пользователей, считая число строк, которые генерирует утилита who. На рис. 13.10 представлена схема конвейера. Байты от who следуют через конвейер к wc Рис. 13.10. Простой конвейер Важно понять, что и пишушии, и читающий процессы конвейера выполняются одновременно; конвейер автоматически буферирует вывод "писателя" и приостанавливает его, если конвейер становится слишком заполненным. Аналогично, если конвейер освобождается, "читатель" временно приостанавливается до тех пор, пока некоторый вывод не станет доступным. Все версии UNIX поддерживают безымянные конвейеры, которые являются видом конвейеров, применяемых shell. System V также поддерживает более мощный вид конвейера, называемый именованным конвейером. В следующих разделах будет показано, как построить конвейер каждого вида. Безымянные конвейеры: pipe() Безымянный конвейер (unnamed pipe) — это однонаправленная коммуникационная связь, которая автоматически буферирует свой вход (максимальный размер изменяется в различных версиях UNIX, но приблизительно равен 5 Кбайт) и может быть создан с помощью системного вызова pipe (). Каждый конец конвейера имеет ассоциированный дескриптор файла. "Пишущий" конец конвейера может быть записан через системный вызов write о , а "читаемый" конец может читаться с помощью системного вызова read о. Когда процесс завершает работу с конвейерным дескриптором файла, он должен закрыть его, используя системный вызов close (). Синтаксис int pipe (int fd[2]) Системный вызов pipe() создает безымянный конвейер и возвращает два дескриптора файла; дескриптор, связанный с "читаемым" концом
конвейера сохраняется в fd[0], а дескриптор, связанный с "пишущим" концом конвейера, сохраняется в fd[l]. Перечисленные далее правила применимы к процессам, которые читают из конвейера: • если процесс читает из конвейера, чей пишущий конец был закрыт, системный вызов read () возвращает 0, указывая конец ввода; • если процесс читает из пустого конвейера, чей пишущий конец все еще открыт, он засыпает до тех пор, пока некоторый ввод не становится доступным; • если процесс пробует читать большее количество байтов из конвейера, чем там присутствует, возвращается текущее содержимое всего конвейера, и системный вызов read () возвращает количество фактически прочитанных байтов. Следующие правила применимы к процессам, которые пишут в конвейер: • если процесс пишет в конвейер, чей читаемый конец был закрыт, пишущий завершается с ошибкой, а писателю посылается сигнал sigpipe. По умолчанию действие этого сигнала должен завершить приемник; • если процесс пишет в конвейер меньшее количество байтов, чем тот может держать, системный вызов write о, как гарантируется, будет атомарным; т. е. процесс-писатель закончит свой системный вызов без прерывания другим процессом. Если процесс пишет в конвейер большее количество байтов, чем тот может держать, гарантия атомарности отсутствует. Так как доступ к безымянному конвейеру происходит через механизм дескриптора файла, обычно только процесс, который создает конвейер, и его потомки могут использовать конвейер5. Системный вызов -iseek () не имеет никакого значения, когда применяется к конвейеру. Если ядро не может выделить достаточно пространства для нового конвейера, pipe () возвращает — 1; иначе возвращается 0. Если код выполняется, то будут созданы структуры данных, показанные на рис. 13.11. Безымянные конвейеры обычно используются для связи между родительским и дочерним процессами с одним процессом записи и другим процессом чтения, int fd[2]; pipe (fd); 5 В продвинутых ситуациях фактически возможно передать дескрипторы файла несвязанным процессам через конвейер.
Типичная последовательность событий следующая: 1. Родительский процесс создает безымянный конвейер с помощью системного ВЫЗОВа pipe (). 2. Родительский процесс ветвится. 3. Писатель закрывает свой читаемый конец конвейера и указывает читателю закрыть его пишущий конец конвейера. 4. Процессы связываются через системные вызовы write () и read (). 5. Каждый процесс закрывает свой активный дескриптор конвейера, когда работа с конвейером завершается. Рис. 13.11. Безымянный конвейер Двунаправленная связь возможна только при использовании двух конвейеров. Вот маленькая программа, которая применяет конвейер, чтобы позволить родителю читать сообщение от своего дочернего процесса: $ cat talk.с ...вывести программу. #include <stdio.h> #define READ 0 /* Индекс читающего конца конвейера ★/ #define WRITE 1 /* Индекс пишущего конца конвейера */ char* phrase = ’’Stuff this in your pipe and smoke it"; main() int fd[2], bytesRead; char message [100]; /* Буфер сообщений родительского процесса */ pipe (fd); /* Создать безымянный конвейер */ if (fork() == 0) /* Дочерний, пишущий */ { close(fd[READ]); /* Закрыть неиспользуемый конец */ write (fd[WRITE], phrase, strlen (phrase) + 1); /* Включая NULL*/ close (fd[WRITE]); /* Закрыть используемый конец */
else /* Родитель, читающий */ { close (fd[WRITE]); /* Закрыть неиспользуемый конец */ bytesRead = read (fd[READ], message, 100); printf ("Read %d bytes: %s\n", bytesRead, message); /* Послать */ close (fd[READ]); /* Закрыть используемый конец */ } } $ talk ...выполнить программу. Read 37 bytes: Stuff this in your pipe and smoke it $ Заметьте, что дочерний процесс добавил null — признак конца фразы, как части сообщения, чтобы родитель мог легко показать сообщение. Когда процесс писателя посылает больше, чем одно сообщение переменной длины в конвейер, он должен использовать протокол для индикации конца сообщения читателю. Методы для выполнения этого включают: □ посылку длины сообщения (в байтах) перед посылкой непосредственно сообщения; □ завершение сообщения специальным символом, например, символом НОВОЙ строки ИЛИ NULL. UNIX shell использует безымянные конвейеры, чтобы строить конвейерные связи. Для этого он использует ухищрение, подобное механизму переназначения, описанному ранее, чтобы соединить стандартный вывод одного процесса со стандартным вводом другого. Для иллюстрации такого подхода рассмотрим программу, которая выполняет две указанных программы, соединяя стандартный вывод первой со стандартным вводом второй. Программа, осуществляющая соединение, предполагает, что никакая программа не вызывается с опциями и что имена программ перечислены в командной строке. $ cat connect.с ...посмотреть программу. #include <stdio.h> #define READ 0 #define WRITE 1 main (argc, argv) int argc; char* argv[]; { int fd [2]; pipe (fd); if (fork() != 0) /* Создать безымянный конвейер */ /* Родитель, пишущий */
{ close (fd[READ]); /* Закрыть неиспользуемый конец */ dup2 (fd[WRITE], 1); /* Дублировать используемый конец на stdout */ close (fd[WRITE]); /* Закрыть оригинальный используемый конец */ execlp (argv[l], argv[l], NULL); /* Выполнить программу-писатель */ perror ("connect’'); /* He должен никогда выполняться ★/ } , else /* Дочерний, читающий */ close (fd[WRITE]); /* Закрыть неиспользуемый конец */ dup2 (fd[READ], 0); /* Дублировать используемый конец на stdin */ close (fd[READ]); /* Закрыть оригинальный используемый конец */ execlp (argv[2], argv[2], NULL); /* Выполнить программу-читатель */ perror ("connect"); /* He должен никогда выполняться */ } } $ who ...выполнить who сам по себе. gglass ttypO Feb 15 18:45 (xyplex_3) $ connect who wc ...конвейеризировать who на wc. 1 6 57 ...1 строка, 6 слов, 57 символов. $ Позже в данной главе будет рассмотрен более сложный пример использования безымянных конвейеров. Также в конце главы представлено интересное упражнение, которое затрагивает создание кольца конвейеров. Именованные конвейеры Именованные конвейеры (named pipes), часто упоминаемые как очереди FIFO (First Input First Output, первым пришел, первым вышел), меньше ограничены, чем безымянные конвейеры, и обладают следующими преимуществами: □ имеют имя, которое существует в файловой системе; □ могут использоваться несвязанными процессами; □ существуют, пока не удалены явно. К сожалению, именованные конвейеры поддерживаются только System V. Все правила, упомянутые в предыдущем разделе относительно безымянных конвейеров, применимы к именованным конвейерам за исключением того, что именованные конвейеры обладают большей вместимостью буфера — обычно, приблизительно 40 Кбайт.
Именованные конвейеры в файловой системе существуют как специальные файлы и могут быть созданы одним из двух способов: □ путем запуска утилиты UNIX mknod; □ путем использования системного вызова mknod (). Чтобы создать именованный конвейер с помощью цтилиты mknod, следует указать опцию р. (Дополнительную информацию о mknod см. в главе 15.) Режим именованного конвейера может быть установлен через утилиту chmod для того, чтобы позволить другим получить доступ к создаваемому конвейеру. Ниже представлен пример этой процедуры, выполненной в Korn shell: $ mknod туPipe р ... создать конвейер. $ chmod ug+rw туPipe ...обновить права. $ Is -1д ту Pipe ...посмотреть атрибуты. prw-rw--- 1 glass cs 0 Feb 27 12:38 myPipe $ _ Обратите внимание, что тип именованного конвейера — это символ р в первой позиции листинга утилиты is. Чтобы создать именованный конвейер через системный вызов mknod (), следует определить s_ififo в качестве режима файла. Режим конвейера затем может быть изменен путем использования системного вызова chmod (). Вот отрывок С-кода, который создает именованный конвейер с разрешением чтения и записи для владельца и группы: mknod ("myPipe", S_IFIFO, 0); /* Создать именованный конвейер */ chmod ("myPipe", 0660); /* Изменить его флаги разрешения */ Независимо от того, как создается именованный конвейер, результат один и тот же: специальный файл добавляется в файловую систему. Как только именованный конвейер открыт через системный вызов open (), другой системный вызов — write о — добавляет данные в начало очереди FIFO, и read () удаляет данные с конца FIFO-очереди. Когда процесс закончил использовать именованный конвейер, он должен закрыть его с помощью системного вызова close о, а когда именованный конвейер перестает быть нужным, его необходимо удалить из файловой системы через системный ВЫЗОВ unlink о . Подобно безымянному конвейеру, именованный конвейер предназначен только для использования в качестве однонаправленной связи. Процесс-писатель должен открыть именованный конвейер только для записи, а процесс-читатель — только для чтения. Хотя процесс мог бы открыть именованный конвейер и для записи, и для чтения, это не имеет большого практического смысла. Прежде чем будет представлен пример программы,
которая использует именованные конвейеры, приведем специальные правила их применения: □ если процесс пробует открыть именованный конвейер только для чтения, и нет процесса, который в настоящее время открыл этот файл для записи, читатель будет ждать, пока процесс не откроет файл для записи; но если установлен флаг o_nonblock/o_ndelay, тогда системный вызов open () сразу завершится нормально; □ если процесс пробует открыть именованный конвейер только для записи, и нет процесса, который в настоящее время открыл этот файл для чтения, писатель будет ждать, пока процесс не откроет файл для чтения; но если установлен флаг o_nonblock/o_ndelay, тогда системный вызов open () сразу звершится с ошибкой; □ именованные конвейеры не работают через сеть. Очередной пример использует две программы — "читатель" и "писатель" — и работает так, как описано ниже. □ Единственный читающий процесс выполняется, создавая именованный конвейер aPipe. Затем процесс читает и выводит заканчивающиеся символом null строки из конвейера, пока конвейер не закрывается всеми пишущими процессами. □ Один или несколько пишущих процессов выполняются, каждый из них открывает именованный конвейер aPipe и посылает ему три сообщения. Если конвейер не существует, когда писатель пытается открыть его, писатель повторяет попытку каждую секунду, пока не завершится нормально. Когда все сообщения писателя посланы, он закрывает конвейер и выходит. Далее следует исходный код для каждого файла и некоторый примерный вывод. Программа-читатель ftinclude <stdio.h> ftinclude <sys/types.h> #include <sys/stat.h> /* Для S_IFIFO */ #include <fcntl.h> main() { int fd; » char str[100]; unlink("aPipe"); /★ Удалить именованный конвейер, если он существует */
mknod ("aPipe", S_IFIFO, 0); /* Создать именованный конвейер */ chmod ("aPipe", 0660); /* Изменить его права */ fd = open ("aPipe”, O_RDONLY); /* Открыть его для чтения */ while (readLine (fd, str)) /* Вывести получаемые сообщения */ printf ("%s\n", str); close (fd); /* Закрыть конвейер */ readLine (fd, str) int fd; char* str; /* Читает отдельную, заканчивающуюся NULL, строку в str из fd */ /* Возвращает 0, когда достигнут конец ввода, и 1 — иначе */ { int п; do /* Читать символы до NULL или конца ввода */ { n = read (fd, str, 1); /* Читать один символ */ while (п > 0 && *str++ != NULL); return (n > 0); /* Возвращает ложь, если конец ввода */ Программа-писатель ttinclude <stdio.h> #include <fcntl.h> main() { int fd, messageLen, i; char message [100]; /* Подготовить сообщение */ sprintf (message, "Hello from PID %d", getpidO); messageLen = strlen (message) + 1; do /* Пытаться открыть файл до успеха */ { > fd = open ("aPipe", O_WRONLY); /* Открыть именованный конвейер */ /* для записи */
if (fd == -1) sleep (1); /* Пытаться снова через 1 секунду */ while (fd == -1); for (i = 1; i <= 3; i++) /* Послать три сообщения */ write (fd, message, message-Len); /* Записать сообщение */ /* в конвейер */ sleep (3); /* Подождать немного */ } close (fd); /* Закрыть дескриптор конвейера */ Примерный вывод $ reader & writer & writer & ...старт одного читателя, двух писателей. [1] 4698 ...читающий процесс. [2] 4699 ...первый пишущий процесс. [3] 4700 ...второй пишущий процесс. Hello from PID 4699 Hello from PID 4700 Hello from PID 4699 Hello from PID 4700 Hello from PID 4699 Hello from PID 4700 [2] Done writer ...первый писатель вышел. [3] Done writer ...второй писатель вышел. [1] Done reader ...читатель вышел. $ Сокеты Сокеты являются традиционным механизмом связи между процессами в UNIX, который позволяет процессам взаимодействовать друг с другом, даже если они находятся на различных машинах. Такая сетевая возможность делает сокеты очень полезными. Например, утилита riogin, которая позволяет пользователю, работающему на одной машине, регистрироваться на удаленном хосте, реализована через сокеты. Другие общие применения сокетов включают: П печать файла на одной машине с другой машины; П передача файлов с одной машины на другую.
Связь процессов через сокеты основана на клиент-серверной модели. Один процесс, называемый серверным процессом, создает сокет, чье имя известно другим клиентским процессам. Эти процессы клиентов могут взаимодействовать с серверным процессом через подключение к его именованному сокету. Чтобы это сделать, процесс клиента сначала создает безымянный сокет и требует, чтобы он был связан с именованным сокетом сервера. Успешное соединение возвращает один дескриптор файла клиенту и один — серверу, которые могут использоваться для чтения и записи. Обратите внимание, что, в отличие от конвейеров, связи через сокеты двунаправлены. Рис. 13.12 иллюстрирует процесс. 1. Сервер создает именованный сокет 2. Клиент создает безымянный сокет и требует соединения 3. Клиент создает соединение. Сервер поддерживает начальный именованный сокет Завершенное соединение Рис. 13.12. Соединение через сокет Как только подключение через сокет организовано, обычно серверный процесс осуществляет ответвление дочернего процесса, чтобы общаться с клиентом в то время, как первоначальный родительский процесс продолжает принимать другие подключения клиентов. Типичный пример — удаленный сервер печати: серверный процесс сначала принимает клиента, который хочет послать файл на печать, а затем ответвляет дочерний процесс, чтобы выполнить передачу файла. Родительский процесс тем временем ждет другие запросы клиентов на печать.
Рассмотрим следующие темы: □ виды сокетов; □ как сервер создает именованный сокет и ждет подключений; □ как клиент создает безымянный сокет и запрашивает подключения к серверу; □ как сервер и клиент взаимодействуют после выполнения подключения через сокет; □ как закрывается сокетное соединение; □ как сервер может создать дочерний процесс, чтобы общаться с клиентом. Виды сокетов Различные виды сокетов могут классифицироваться по трем признакам: □ домен; □ тип; □ протокол. Домены Домен сокета указывает, где могут находиться сокеты сервера и клиента. В настоящее время поддерживаются следующие домены: □ AFJJNIX (клиенты и сервер должны быть на одной машине); □ AF_INET (клиенты и сервер могут быть где угодно в Интернете); □ AF_NS (клиенты и сервер могут быть в сети XEROX). "AF” является аббревиатурой "Address Family" (семейство адреса). Есть подобный набор констант, которые начинаются с буквы "Р" (например, PFJJNIX и PF_INET), которая означает "Protocol Family" (семейство протокола). Может использоваться любой набор, т. к. они эквивалентны. Эта книга содержит сведения об AF_UNIX и AF_INET-coKeTax, но не об AF_NS-coKeTax. Типы Тип гнезда определяет тип связи, которая может существовать между клиентом и сервером. В настоящее время поддерживаются два главных типа: □ SOCK_STREAM — последовательный, надежный, с двусторонней связью, переменной длины поток байтов; □ SOCK_DGRAM — подобно телеграммам: без соединения, ненадежный, сообщения фиксированной длины.
Другие типы, которые находятся либо в стадии планирования, либо реализованы только в некоторых доменах, таковы: П SOCK_SEQPACKET — последовательный, надежный, с двусторонней связью, фиксированной длины пакеты байтов; □ SOCK_RAW — обеспечивает доступ к внутренним сетевым протоколам и интерфейсам. Данная книга содержит информацию только об использовании SOCK STREAM-сокетов, которые являются наиболее общими. Эти сокеты интуитивно понятны и удобны. Протоколы Значение протокола определяет средства низкого уровня, которыми реализован тип сокета. Системные вызовы, ожидающие параметр протокола, воспринимают 0, как "правильный протокол". Другими словами, значение протокола вообще не должно волновать пользователя. Большинство систем в качестве дополнительных услуг поддерживают только протоколы, отличные от тех, что имеют нулевые параметры. Поэтому во всех примерах будем применять протокол по умолчанию. Написание сокетных программ Любая программа, использующая сокеты, должна включать файлы /usr/include/sys/types.h и /usr/include/sys/socket.h. Дополнительные файлы заголовков должны быть добавлены в базовый компонент необходимого со-кетного домена. Наиболее общие домены показаны в табл. 13.8. Другие со-кетные домены определены в файле socket.h. Таблица 13.8. Общие сокетные домены и соответствующие файлы заголовков Домен Дополнительные файлы заголовка AF_UNIX /usr/include/sys/un.h AFJNET /usr/include/netinet/in.h /usr/include/arpa/inet. h /urs/include/netdb.h Для иллюстрации способа, которым написана программа, использующая сокеты, построим описание ориентируемых на сокеты системных вызовов вокруг маленького клиент-серверного примера, который содержит AF-UNIX-сокеты. Затем будет приведен другой пример, который использует AF INET-сокеты.
AF-UNIX-пример состоит из следующих двух программ: П chef — сервер, который создает именованный сокет recipe и пишет рецепт любым клиентам, которые его запрашивают; рецепт — набор строк переменной длины, заканчивающихся символом null; П cook — клиент, который соединяется с именованным сокетом recipe и читает рецепт с сервера, cook выводит рецепт на стандартный вывод по мере того, как читает его, а затем завершает работу. Серверный процесс программы chef работает в фоновом режиме. Любые клиентские процессы, которые соединяются с сервером, заставляют его разветвиться, дублируя сервер для передачи рецепта и позволяя первоначальному серверу принимать другие поступающие подключения. Вот образец вывода из примера "Chef-Cook": $ chef & ... запустить сервер в фоновом режиме. [1] 5684 $ cook ...запустить клиента для вывода рецепта. spam, spam, spam, spam, spam, and spam. $ cook ...запустить другого клиента для вывода рецепта. spam, spam, spam, spam, spam, and spam. $ kill %1 ..."Убить" сервер. [1] Terminated chef $ Листинг примера "Chef-Cook" В данном разделе представлены полные листинги программ chef и cook. Предлагается сначала просмотреть код, а затем прочитать следующие разделы для выяснения деталей работы обеих программ. В целях экономии места преднамеренно пропущена большая часть проверки ошибок. Сервер chef 1 ftinclude <stdio.h> 2 #include <signal.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <sys/un.h> /* Для AF_UNIX-сокетов */ 6 7 ttdefine DEFAULT-PROTOCOL 0 8
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 main() int serverFd, clientFd, serverLen, clientLen; struct sockaddr_un serverUNIXAddress; /* Адрес сервера */ struct sockaddr_un clientUNIXAddress; /* Адрес клиента */ struct sockaddr* serverSockAddrPtr; /* Указатель на адрес сервера */ struct sockaddr* clientSockAddrPtr; /* Указатель на адрес клиента */ /* Игнорировать сигнал "смерти” дочернего для предотвращения зомби */ signal (SIGCHLD, SIG_IGN); serverSockAddrPtr = (struct sockaddr*) ^serverUNIXAddress; serverLen = sizeof (serverUNIXAddress); clientSockAddrPtr = (struct sockaddr*) &clientUNIXAddress; clientLen = sizeof (clientUNIXAddress); /* Создать UNIX-сокет, двунаправленный, подразумеваемый протокол */ serverFd = socket (AF_UNIX, SOCK_STREAM, DEFAULT-PROTOCOL); serverUNIXAddress.sun_family = AF_UNIX; /* Установка типа домена */ strcpy (serverUNIXAddress.sun_path, "recipe”); /* Установить имя */ unlink ("recipe"); /* Удалить файл, если он уже существует */ bind (serverFd, serverSockAddrPtr, serverLen); /* Создать файл */ listen (serverFd, 5);/*Максимальное число незаконченных соединений*/ while (1) /* Бесконечный цикл */ /* Принять клиентское соединение */ clientFd = accept (serverFd, clientSockAddrPtr, &clientLen); if (fork() == 0) /* Создать дочерний, чтобы послать рецепт */ writeRecipe (clientFd); /* Послать рецепт */ close (clientFd); /* Закрыть сокет */ exit (/* ВЫХОД_УСПЕХ */ 0); /* Завершиться */
48 else 49 close (clientFd); /* Закрыть дескриптор клиента */ 50 } 51 } 52 ^2 /**^*******************************************************^*****/ 54 55 writeRecipe (fd) 56 57 int fd; 58 59 ‘ { 60 static char* linel = "spam, spam, spam, spam,"; 61 static char* line2 = "spam, and spam."; 62 write (fd, linel, strlen (linel) + 1); /* Писать первую строку */ 63 write (fd, line2, strlen (line2) + 1); /* Писать вторую строку */ 64 } Клиент cook 1 #include <stdio.h> 2 #include <signal.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <sys/un.h> /* Для AF—UNIX-сокетов */ 6 1 7 #define DEFAULT_PROTOCOL. 0 8 Q /**★★**★★★★******★*★*★★*★******★**★****★*•*****★*★*********★ / 10 11 main() 12 13 { 14 int clientFd, serverLen, result; 15 struct sockaddr_un serverUNIXAddress; 16 struct sockaddr* serverSockAddrPtr; 17 18 serverSockAddrPtr = (struct sockaddr*) &serverUNIXAddress; 19 serverLen = sizeof (serverUNIXAddress); 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 58 59 60 61 /* Создать UNIX-сокет, двунаправленный, подразумеваемый протокол */ clientFd = socket (AF_UNIX, SOCK_STREAM, DEFAULT_PROTOCOL); , serverUNIXAddress.sun_f ami1у = AF_UNIX; /* Домен сервера */ strcpy (serverUNIXAddress.sun_path, "recipe"); /* Имя сервера */ do /* Цикл до создания соединения с сервером */ { result = connect (clientFd, serverSockAddrPtr, serverLen); if (result == -1) sleep (1); /* Ждать и пробовать снова */ } while (result = -1); readRecipe (clientFd); /* Читать рецепт */ close (clientFd); '/* Закрыть сокет */ exit (/* ВЫХОД_УСПЕХ */ 0); /* Сделано */ readRecipe (fd) int fd; { char str[200]; while (readLine (fd, str)) /* Читать строку до конца ввода */ printf ("%s\n", str); /* Вывести строку через сокет */ } I к'к-к'к-к'к-к'к'к-к-к'к'к'к'к-к-к-к'к'к'к'к-к'к'к'к-к'к'к'к'к'к'к'к'к'к'к'к-к'к'к-к-к-к-к-к-к'к'к'к'к'к'к'к'к'к'к-к'к / readLine (fd, str) int fd; char* str; 57 /* Читать одиночную строку, завершающуюся символом NULL */ { int n;
62 63 do /* Читать символы до NULL или конца ввода */ 64 ( 65 n = read (fd, str, 1); /* Читать один символ */ 66 } 67 while (п > 0 && *str++ != NULL); 68 return (n > 0); /* Вернуть ложь, если конец ввода */ 69 } Анализ исходного кода Теперь, когда вы просмотрели программы, пришло время вернуться и проанализировать их. Начинаем с сервера. Сервер Сервер — это процесс, который является ответственным за создание именованного сокета и установку связи с ним. Для того чтобы выполнить это, сервер должен использовать системные вызовы, перечисленные в табл. 13.9. Таблица 13.9. Системные вызовы, используемые типичным процессом-демоном UNIX Системный вызов Описание socket Создает именованный сокет bind Дает сокету имя listen Специфицирует максимальное количество отложенных соединений accept Принимает сокетное соединение от клиента Создание сокета: socketf) Процесс может создавать сокет через системный вызов socket о. Сервер chef создает свой безымянный сокет на строке 30 программы: 30 serverFd = socket (AF_UNIX, SOCK_STREAM,; DEFAULT_PROTOCOL); Синтаксис int socket (int domain, int type, int protocol) Системный вызов socket о создает безымянный сокет указанного домена (dmain), типа (type) и протокола (protocol). Допустимые значения этих параметров были описаны ранее. 21 Зак. 786
Если socket о завершается нормально, то возвращает дескриптор файла, связанный с недавно созданным сокетом; иначе возвращает —1. t ♦ Именование сокета: bind() Как только сервер создал безымянный сокет, он должен связать его с именем через системный вызов bind (). Сервер chef назначает поля sockaddr_un и исполняет bind () в строках 31—34: 31 serverUNIXAddress. sun_family = AF__UNIX; /* Установка типа домена */ 32 strcpy (serverUNIXAddress.sunjpath, "recipe"); /* Установить имя */ 33 unlink ("recipe"); /* Удалить файл, если он уже существует */ 34 bind (serverFd, serverSockAddrPtr, serverLen); /* Создать файл */ Синтаксис int bind (int fd, const struct sockaddr* address, size_t addressLen) Системный вызов bind () связывает безымянный сокет, представленный дескриптором файла fd, с адресом сокета, сохраненным в address. addressLen должен содержать длину структуры адреса. Тип и значение поступающего адреса зависят от домена сокета. Если сокет находится в AF-UNIX-домене, указатель на структуру sockaddr un должен быть направлен к sockaddr* и передан, как address. Эта структура имеет два поля, которые должны быть установлены следующим образом: Поле Назначить значение sun_family AFUNIX sun_path Полное путевое UNIX-имя сокета (абсолютное или относительное), до 108 символов длиной Если именованный AF-UNIX-сокет уже существует, возникает ошибка, поэтому лучше вызвать unlink о перед попыткой связать его. Если сокет находится в АЕ_1\ЕТ-домене, указатель на структуру sockaddr_in должен быть направлен к sockaddr* и передан, как address. Эта структура имеет четыре поля, которые должны быть установлены следующим образом: Поле Назначить значение s i n_ f ami 1 у AF_I N ЕТ sin port Номер порта интер нет-co кета
(окончание) Поле Назначить значение sin_addr Структура типа in_addr, которая держит интернет-адрес । sin_zero Оставить пустым Ддя дополнительной информации об интернет-портах и адресах см. далее разделы, посвященные Интернету. Если bind о завершается нормально, он возвращает 0; иначе возвращает — 1. Создание очереди сокета: listenO В то время как серверный процесс обслуживает связь клиента, возможно, что другой клиент также будет пытаться осуществить связь. Системный вызов listenO позволяет процессу определить количество возможных связей, которые могут быть с очередями. Сервер chef слушает свой именованный сокет в строке 35: 35 listen (serverFd, 5);/*Максимальное число незаконченных соединений*/ Синтаксис int listen (int fd, int queueLength) Системный вызов listenO позволяет определить максимальное число ожидаемых связей на сокете. Максимальная длина очереди равна 5. Если клиент пытается соединиться с сокетом, чья очередь полна, ему отказывается в доступе. Прием клиента: acceptf) Раз сокет создан и имеет имя, а также размер его очереди определен, заключительным шагом является прием запросов связи клиентов. Чтобы это сделать, сервер должен использовать системный вызов accept (). Сервер chef принимает связь в строке 40: 40 clientFd = accept (serverFd, clientSockAddrPtr, &clientLen); Синтаксис int accept (int fd, struct sockaddr* address, int* addressLen) Системный вызов accept о слушает именованный сокет сервера, указанный дескриптором файла fd, и ждет, пока запрос связи клиента не
будет получен. Когда это происходит, accept о создает безымянный сокет с теми же самыми признаками, как у оригинального именованного сокета сервера, соединяет безымянный сокет с сокетом клиента и возвращает новый дескриптор файла, который может использоваться для связи с клиентом. Оригинальный именованный сокет сервера может применяться для принятия добавочных связей. Структура address заполняется адресом клиента и обычно выступает только в соединении с интернет-связями. Поле addressLen должно быть первоначально установлено, чтобы указывать на целое число, содержащее размер структуры, указанной address. Когда связь создана, целое число, на которое оно указывает, изменяется на реальный размер в байтах результирующего address. Если accept () завершается нормально, он возвращает новый дескриптор файла, который может использоваться для взаимодействия с клиентом; иначе возвращает — 1. Обслуживание клиента Когда связь с клиентом завершается без ошибк, самая обычная последовательность событий такова: 1. Серверный процесс разветвляется. 2. Родительский процесс закрывает недавно сформированный дескриптор файла клиента и возвращается к системному вызову accept (), готовый обслужить новые требования о связи. 3. Используя системные вызовы read () и write (), дочерний процесс взаимодействует с клиентом. Когда "беседа" закончена, дочерний процесс закрывает дескриптор файла клиента и завершается. Процесс сервера chef определяет этот ряд действий в строках 37—50: 37 while (1) /* Бесконечный цикл */ 38 { 39 /* Принять клиентское соединение */ 40 clientFd = accept (serverFd, clientSockAddrPtr, SclientLen); 41 42 if (fork() == 0) /* Создать дочерний, чтобы послать рецепт */ 43 { 44 writeRecipe (clientFd); /* Послать рецепт */ 45 close (clientFd); /* Закрыть сокет */ 46 exit (/* ВЫХОД УСПЕХ */ 0); /* Завершиться */ 47 }
48 else 49 close (clientFd); /★ Закрыть дескриптор клиента */ 50 } Обратите внимание, что сервер выбирает игнорирование сигналов sigchld в строке 21 так, чтобы его потомки могли "умирать” немедленно без требования у родителя приема своих кодов возврата. Если бы сервер не сделал это, потребовалось бы установить обработчик сигнала sigchld, что было бы более утомительно. Клиент Теперь, когда стало понятно, как написана программа сервера, рассмотрим структуру программы клиента. Клиент — это процесс, который является ответственным за создание безымянного сокета и подключение его к именованному сокету сервера. Для этого он должен использовать системные вызовы, перечисленные в табл. 13.10. Таблица 13.10. Системные вызовы, используемые типичным клиентским процессом UNIX Системный вызов Описание socket Создает безымянный сокет connect Присоединяет безымянный сокет клиента к именованному сокету сервера Способ, которым клиент применяет системный вызов socket о для создания безымянного сокета, аналогичен способу, которым пользуется его сервер. Домен, тип и протокол клиентского сокета должны соответствовать таковым в целевом сокете сервера. Клиентский процесс cook создает свое безымянное гнездо в строке 22: 22 clientFd = socket (AF_UNIX, SOCK_STREAM, DEFAULT_PROTOCOL); Создание соединения: connect() Чтобы соединяться с сокетом сервера, процесс клиента должен заполнить структуру адресом сокета и затем воспользоваться системным вызовом connect (). В строках 26—31 процесс клиента cook вызывает connect о до тех пор, пока не будет организована успешная связь: 26 do /* Цикл до создания соединения с сервером */ 27 { 28 result = connect (clientFd, serverSockAddrPtr, serverLen); 29 if (result === -1) sleep (1); /* Ждать и пробовать снова */
30 31 while (result == -1); Синтаксис int connect (int fd, struct sockaddr* address, int addressLen) Системный вызов connect () пытается соединяться с сокетом сервера, чей адрес содержится в структуре, указанной address. Если все проходит успешно, дескриптор файла fd может служить для связи с сокетом сервера. Тип структуры, которая указывает address, должен следовать тем же самым правилам, какие указаны в описании bind (): • если сокет находится в AF-UNIX-домене, указатель на структуру sockaddr un должен быть помещен в sockaddr* и передан, как address’, • если сокет находится в АР_1НЕТ-домене, указатель на структуру sockaddr_in должен быть помещен в sockaddr* и передан, как address. addressLen должен быть равен размеру адресной структуры. (Образцы интернет-клиентов см. в примере connect () сокета и программы Internet shell в конце главы.) Если связь произведена, connect () возвращает 0. Если сокет сервера не существует или его очередь в настоящее время заполнена, connect о возвращает —1. Взаимодействие через сокеты Как только сокет сервера и сокет клиента соединились, их дескрипторы файлов могут использоваться системными вызовами write () и read (). В программе примера сервер использует write () в строках 55—64: 55 writeRecipe (fd) 56 57 int fd; 58 59 { 60 static char* linel = "spam, spam, spam, spam,"; 61 static char* line2 = "spam, and spam."; 62 write (fd, linel, strlen (linel) + 1); /* Писать первую строку */ 63 write (fd, line2, strlen (line2) + 1); /* Писать вторую строку */ 64 }
Клиент использует системный вызов read () в строках 53—69: 53 readLine (fd, str) 54 55 int fd; 56 char* str; 57 58 /* Читать одиночную строку, завершающуюся символом NULL */ 59 60 { 61 int n; 62 63 do /* Читать символы до NULL или конца ввода */ 64 { 65 n = read (fd,str, 1); /* Читать один символ */ 66 } 67 while (п > 0 && *str++ != NULL); 68 return (n > 0); /* Вернуть ложь, если конец ввода */ 69 } Сервер и клиент должны быть внимательны, чтобы закрыть свои дескрипторы файлов сокетов, когда в них больше нет необходимости. Интернет-сокеты AF_UNIX-coKeTbi, которые были представлены выше, прекрасны для изучения сокетов, но они не являются центром активности. Большинство полезного материала вовлечено во взаимодействие между машинами в Интернете, так что остальная часть данной главы посвящена AF-INET-сокетам. Если вы еще не читали об организации сети в главе 9, теперь подходящее время, чтобы это сделать. Интернет-сокет определяется двумя значениями: 32-битовым IP-адресом, который определяет отдельный уникальный интернет-хост, и 16-битовым номером порта, задающим конкретный порт на хосте. Это означает, что интернет-клиент должен знать не только IP-адрес сервера, но и номера порта сервера. Как было упомянуто в главе 9, несколько стандартных номеров портов зарезервированы для системного использования. Например, порт 13 всегда обслуживается процессом, который передает время дня хоста любому клиенту, который в этом заинтересован. Первый интернет-пример сокета позволяет соединяться с портом 13 с любого интернет-хоста в мире и выяснять ’’удаленное’’ время дня.
Это осуществляют три вида интернет-адресов: □ если пользователь вводит "s", это автоматически означает локальный хост; □ если вводится значение, начинающееся с цифры, предполагается А.В.С.D-формат IP-адреса, который преобразуется программным обеспечением в 32-битовый 1Р-адрес; □ если пользователь вводит строку, предполагается, что это символическое имя хоста, и оно преобразуется программным обеспечением в 32-битовый 1Р-адрес. Далее приведен пример вывода программы internet time. Третий адрес, который я ввел, — это IP-адрес ddn.nic.mil национального интернет-сервера базы данных. Заметьте разницу в один час между временем моего локального хоста и временем хоста сервера базы данных. Пример вывода $ inettime ...выполнить программу. Host name (q = quit, s = self): s ...какое мое время? Self host name is csservr2 Internet Address = 129.110.42.1 The time on the target port is Fri Mar 27 17:03:50 1998 Host name (q = quit, s = self): wotan .-..какое время на wotan? Internet Address = 129.110.2.1 The time on the target port is Fri Mar 27 17:0*3:55 1998 Host name (q = quit, s = self): 192.112.36.5 ...пробовать ddn.nic.mil. The time on the target port is Fri Mar 27 18:02:02 1998 Host name (q = quit, s = self): q ...покинуть программу. $ Листинг программы Internet time Этот раздел содержит полный листинг клиентской программы internet time. Предполагается, что читатель просмотрит код, а затем изучит следующие за ним разделы, чтобы выяснить детали ее работы. 1 #include <stdio.h> 2 #include <signal.h> 3 #include <ctype.h> 4 #include <sys/types,h> 5 #include <sys/socket.h> 6 #includ’e <netinet/in.h> 7 #include <arpa/inet.h> /* Для AF_INET-сокетов */
8 9 10 11 12 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <netdb.h> #define DAYTIME PORT #define DAYTIME_PORT 13 /* Стандартный порт о */ #define DEFAULT PROTOCOL 0 unsigned long promptForINETAddress(); unsigned long nameToAddr () ; у*********************************************************** main() int clientFd; /* Дескриптор файла клиентского сокета */ int serverLen; /* Длина структуры адреса сервера */ int result; /* От вызова connect() */ struct sockaddr_in serverINETAddress; /* Адрес сервера */ struct sockaddr* serverSockAddrPtr; /* Указатель на адрес */ unsigned long inetAddress; /* 32-битовый IP-адрес */ /* Установить две переменные сервера */ serverSockAddrPtr = (struct sockaddr*) &serverINETAddress; serverLen = sizeof (serverINETAddress); /* Длина адреса */ while (1) /* Цикл, пока не будет прерван */ inetAddress = promptForINETAddress(); /* Получить 32-бита IP */ if (inetAddress == 0) break; /* Сделано */ /* Начать обнуление входной адресной структуры */ bzero ((char*)&serverINETAddress, sizeof(serverINETAddress)); serverINETAddress.sin_family = AF_INET; /* Интернет */ serverINETAddress.sin_addr.s_addr = inetAddress; /* IP */ serverINETAddress.sin_port = htons (DAYTIME_PORT); /* Теперь создать сокет клиента */ clientFd = socket (AF_INET, SOCK_STREAM, DEFAULT-PROTOCOL); do /* Цикл до соединения с сервером */ result = connect (clientFd, serverSockAddrPtr, serverLen);
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 if (result == -1) sleep (1);/* Пытаться снова через 1 сек*/ while (result == -1); и readTime (clientFd); /* Читать время с сервера */ close (clientFd); /* Закрыть сокет */ } exit (/* ВЫХОД-УСПЕШЕН */ 0); } unsigned long promptForINETAddress () { char hostName [100]; /* Имя от пользователя: числовое или символьное */ unsigned long inetAddress; /* 32-бита IP-формат */ /* Цикл до выхода или ввода правильного имени */ /* Если выход, вернуть 0; иначе вернуть IP-адрес хоста */ do { printf ("Host name (q = quit, s = self): "); scanf ("%s", hostName); /* Получить имя с клавиатуры */ if (strcmp (hostName, "q") == 0) return (0); /* Выход */ inetAddress = nameToAddr (hostName); /* Преобразовать в IP */ if (inetAddress == 0) printf ("Host name not found\n"); while (inetAddress == 0); return (inetAddress); } /***★★•*•★*★•*•*★★★★★★★★*★*★★★****★**★★**★***★★*★★*★★**★★★★****★/ unsigned long nameToAddr (name) char* name;
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 { char hostName [100]; struct hostent* hostStruct; struct in_addr* hostNode; /* Преобразовать имя в 32-битовый IP-адрес */ /* Если имя начинается с цифры, допустить, что это числовой */ /* интернет-адрес в формате A.B.C.D, и преобразовать его */ if (isdigit (name[0])) return (inet_addr (name)); if (strcmp (name, "s") == 0)/* Получить имя хоста из базы данных */ { gethostname (hostName, 100); printf ("Self host name is %s\n", hostName); else /* Принять, что это имя — правильное символьное имя хоста */ strcpy (hostName, name); /* Теперь получить адресную информацию из базы данных */ hostStruct = gethostbyname (hostName); if (hostStruct == NULL) return (0); /* He найден */ /* Выделить IP-адрес из структуры */ hostNode = (struct in_addr*) hoststruet->h_addr; /* Вывести читаемую версию ради шутки */ printf ("Internet Address = %s\n", inet_ntoa (*hostNode)); return (hostNode->s_addr); /* Вернуть IP-адрес */ } readTime (fd) int fd; { char str [200]; /* Строковый буфер */ printf ("The time on the target port is ");
123 while (readLine (fd, str)) /* Читать строку до конца ввода */ 124 printf ("%s\n", str); /* Вывести строку с сервера пользователю */ 125 } 126 2 7 /***>H****O****ilri************i*ilt*i**fc**:llriii**:lr****i*JH*:IH*y 128 129 readLine (fd, str) 130 131 int fd; 132 char* str; 133 134 /* Читать отдельную NEWLINE - заключительную строку */ 135 136 { 137 int n; 138 139 do /* Читать символы до NULL или конца ввода */ 140 { 141 n = read (fd, str, 1); /* Читать один символ */ 142 } 143* while (п > 0 && *str++ != '\п'); 144 return (п > 0); /* Вернуть ложь, если конец ввода */ 145 } Анализ исходного кода Теперь, когда вы просмотрели исходный код интернет-сокета, пришло время исследовать его интересные части. Программа фокусируется главным образом на клиентскую сторону интернет-связи, поэтому сначала приведено описание этой части. Интернет-клиенты Процедура для создания интернет-клиента та же самая, как и для создания AF-UNIX-клиента, за исключением адреса инициализации сокета. Ранее было упомянуто, что структура интернет-адреса сокета имеет тип struct sockaddr_in и содержит четыре поля: □ sin_famiiy — домен сокета, который должен быть установлен на AFJNET; □ sin_port — номер порта, который в данном случае равен 13; □ sin addr — 32-битовый IP-номер клиента-сервера; □ sin_zero — поле, дополненное пробелами и не установленное.
В создании сокета клиента единственная хитрость — это определение 32-битового IP-адреса сервера. promptForiNETAddressо (строка 59) получает ИМЯ хоста ОТ пользователя И затем вызывает nameToAddr о (строка 80), чтобы преобразовать это в адрес IP. Если пользователь вводит строку, начинающуюся с цифры, для выполнения преобразования вызывается библиотечная процедура inet_addr (). Обратите внимание, что порядок "байтов сети" — это хост-нейтральный порядок байтов в адресе IP. Этот порядок необходим потому, что обычный порядок байтов может отличаться от машины к машине, что сделало бы адреса IP непереносимыми. Синтаксис in_addr_t inet_addr (const char* string) Библиотечная процедура %inet_addr() возвращает 32-битовый IP-адрес, который соответствует string формата A.B.C.D. Адрес IP имеет сетевой порядок байтов. Если string не начинается с цифры, следующий шаг заключается в том, чтобы определить, является ли первый символ "s", означающий локальный хост. Имя локального хоста получается при помощи системного вызова gethostname() (строка 97). Как только символическое имя хоста определено, программа может искать его в файле хостов сети /etc/hosts. Это выполняется при ПОМОЩИ библиотечнй Процедуры gethostbyname () (строка 104). Синтаксис int gethostname (char* name, int nameLen) Системный вызов gethostname () устанавливает символьный массив, указанный name ДЛИНОЙ nameLen, На Строку, заканчивающуюся NULL, рав-ным имени локального хоста. Синтаксис struct hostent* gethostbyname (const char* name) Библиотечная процедура gethostbyname о ищет файл /etc/hosts и возвращает указатель на структуру host ent, которая описывает элемент файла, связанный со строкой name. Если name не найдено в файле /etc/hosts, возвращается null.
Структура hostent имеет несколько полей, но единственное интересующее нас поле — это поле типа struct in_addr*, называемое h addr. Оно содержит связанный с хостом IP-номер в подобласти s addr. Перед возвращением IP-номера программа выводит строковое описание адреса IP, вызывая библиотечную процедуру inet_ntoa () (строка 109). Синтаксис char* inet_ntoa (struct in_addr address) Библиотечная процедура inet_ntoa () берет структуру типа in_addr в качестве своего аргумента и возвращает указатель на строку, которая описывает адрес в формате A.B.C.D. Заключительный 32-битовый адрес возвращается в строке ПО. Как только IP-адрес inetAddress определен, поля адреса сокета клиента заполняются в строках 37—40: 37 38 39 40 bzero ((char*)&serverINETAddress, sizeof(serverINETAddress)); serverINETAddress.sin_family = AF_INET; /* Интернет */ serverlNETAddress.sin_addr.s_addr = inetAddress; /* IP */ serverlNETAddress.sin_port = htons (DAYTIME_PORT); Библиотечная процедура bzero о очищает содержимое адресной структуры сокета прежде, чем назначаются его поля, bzero () вызов происходит от Беркли-версии UNIX. Эквивалент в System V — это библиотечная процедура memset (). Подобно адресу IP, номер порта имеет сетевой порядок байтов, который создается при помощи библиотечной процедуры htons (). Синтаксис void bzero (void* buffer, size_t length) Библиотечная процедура bzero о заполняет массив buffer размером length НУЛЯМИ (ASCII-СИМВОЛ NULL). Синтаксис void memset (void* buffer, int value, size_t length) Библиотечная процедура memset () заполняет массив buffer размером length значением value.
Синтаксис in_addr_t htonl in_port_t htons in_addr_t ntohl in_port_t ntohs (in_addr_t (in_port_t (in_addr_t (in_port_t host Long) hostShort) network Long) networkshort) Каждая из этих библиотечных процедур осуществяет преобразование из числового формата хоста в числовой формат сети. Например, htonio возвращает эквивалент формата сети формату хоста в длинном целом без знака hostLong, a ntohsO — эквивалент хост-формата сети в коротком целом без знака networkshort. Заключительным шагом следует создать сокет клиента и сделать попытку связи. Код для этого почти аналогичен AF_UNIX-coKeTy: 42 clientFd = socket (AF_INET, SOCK_STREAM, DEFAULTJPROTOCOL); 43 do /* Цикл до соединения с сервером */ 44 { 45 result = connect (clientFd, serverSockAddrPtr, serverLen); 46 if (result == -1) sleep (1);/* Пытаться снова через 1 сек*/ 47 } 48 while (result == -1); Остальная часть программы не содержит ничего нового. Интернет-серверы Создание интернет-сервера — фактически довольно простое дело. Должны быть заполнены поля структуры адреса сокета sin family, sin port и sin zero, как это было сделано в примере клиента. Единственное различие заключается в том, что поле s addr должно иметь сетевой порядок байтов, который устанавливается константой inaddr_any, означающей "принимать любые поступающие запросы клиента". Приведенный ниже пример процедуры, использованной для создания адреса сокета сервера, — слегка измененная версия некоторого кода, взятого из программы Internet shell: int serverFd; /* Сокет сервера */ struct sockaddr_in serverINETAddress; /* Интернет-адрес сервера */ struct sockaddr* serverSockAddrPtr; /* Указатель на адрес сервера */ struct sockaddr_in clientINETAddress; /* Интернет-адрес клиента */ struct sockaddr* clientSockAddrPtr; /* Указатель на адрес клиента */
int port =13; /* Установить на порт, который вы хотите обслуживать */ int serverLen; /* Длина адресной структуры ★/ serverFd = socket (AFJENET, SOCK_STREAMZ DEFAULT-PROTOCOL); /* Создать */ serverLen = sizeof (serverINETAddress); /* Длина структуры */ bzero ((char*) &serverINETAddress, serverLen); /* Очистить структуру */ serverINETAddress . sin_family = AF_INET; /* Интернет-домен */ serverINETAddress.sin_addr.s_addr = htonl (INADDR_ANY); /* Принять все */ serverINETAddress.sin_port = htons (port); /* Номер порта сервера */ Когда адрес создан, сокет связывается с ним, и размер очереди сокета определяется обычным способом. serverSockAddrPtr = (struct sockaddr*) &serverINETAddress; bind (serverFd, serverSockAddrPtr, serverLen); listen (serverFd, 5); Заключительный шаг — это прием связей клиентов. Когда организуется успешная связь, адрес сокета клиента заполняется IP-адресом клиента, и возвращается новый дескриптор файла. clientLen = sizeof (clientINETAddress); clientSockAddrPtr = (struct sockaddr*) clientINETAddress; clientFd = accept (serverFd, clientSockAddrPtr, &clientLen); Как видно, код интернет-сервера подобен коду AFUNIX-сервера. Разделяемая память Разделение сегмента памяти — это прямой и интуитивный метод, который позволяет двум процессам на одной и той же машине разделять данные. Процесс, который размещает разделяемый сегмент памяти, при вызове получает назад ID, предполагая, что создание разделяемого сегмента памяти прошло успешно. Другие процессы могут использовать этот ID, чтобы получить доступ к разделяемому сегменту памяти. Доступ к разделяемому сегменту памяти — самая быстрая форма IPC, т. к. никакие данные не должны копироваться или посылаться куда-нибудь еще. Однако поскольку существует только одна копия данных, если несколько процессов обновляют данные, они должны синхронизировать свои действия для предотвращения искажения данных. Нужны некоторые общие системные вызовы, необходимые для размещения и использования разделяемых сегментов памяти в системах, базирующихся на System V: □ shmgeto — размещает разделяемый сегмент памяти и возвращает ID сегмента;
□ shmato — присоединяет разделяемый сегмент памяти к виртуальному адресному пространству вызывающего процесса; □ shmdt () — отсоединяет присоединенный сегмент от адресного пространства; □ shmctio — позволяет изменить атрибуты (например, разрешение на доступ), связанные с разделяемым сегментом памяти. После успешного вызова shmgeto разделяемый сегмент памяти существует и может быть доступен через ID, возвращенный в вызове. Обратите внимание, что любой другой процесс для использования того же самого сегмента должен также знать этот ID. ID можно сделать доступным другим процессам через другой механизм IPC, или конкретный ID можно передать shmgeto, чтобы иициировать применение конкретного известного ID (с пониманием, что вызов может быть неудачным, если этот ID уже использовался с другим разделяемым сегментом памяти). Как только пользователь получит эффективный ID для разделяемого сегмента памяти, вызов shmat () возвратит указатель на адрес в виртуальном пространстве памяти локального процесса, куда был присоединен разделяемый сегмент памяти. Затем можно изменять этот указатель на индекс блока памяти аналогично использованию любого другого блока памяти. (Это предполагает, что пользователю известен формат данных, содержащихся в разделяемом сегменте памяти.) Если разделяемая память больше не нужна, можно освободить (отсоединить) ее вызовом shmdt () (в который передается указатель на ID неразделяемого сегмента памяти). Когда последний процесс, имеющий присоединенный разделяемый сегмент памяти (не обязательно процесс, который создавал его), отсоединяет его, освобождается пространство, выделенное сегменту. Семафоры Семафор — это не механизм связи, который мы видели в конвейерах, сокетах и разделяемой памяти. Фактические данные не пересылаются при помощи семафора. Скорее, семафор — это счетчик, который описывает доступность ресурса (который может быть разделяемым сегментом памяти). Семафор создается и получает значение, обозначающее разрешенное количество параллельного использования ресурса. Каждый раз, когда процесс готовится использовать некоторый ресурс, он проверяет семафор, чтобы узнать, является ли ресурс доступным. Если значение семафора большее 0, то ресурс доступен. Процесс выделяет "единицу ресурса", и семафор уменьшается на единицу. Если значение семафора 0, процесс засыпает, пока значение не станет большее 0 (т. е. другой процесс не "вернет" ресурс). Семафоры могут применяться для блокировки ресурса путем создания семафора со значением 1 (как только один процесс использует его, значение
семафора будет 0). Он известен как бинарный семафор. Семафоры могут помочь в установке максимального числа параллельного использования специфического ресурса. Семафор System V немного более сложен, чем только что описанный. Семафоры управляются, как список или набор семафоров, а не индивидуально. Это обеспечивает метод определения составных семафоров для сложного блокирующего механизма, но требует ненужных издержек, когда пользователю нужен только один семафор. Связанные с семафором системные вызовы таковы: □ semget о — создает набор (массив) семафоров; □ semopO — манипулирует с набором семафоров; □ semcti о — изменяет атрибуты набора семафоров. Internet shell Вы когда-нибудь задавались вопросом, что собой представляет внутренняя часть shell? Хорошо, вот замечательная возможность узнать, как он работает, и получить некоторый исходный код, который поможет создать ваш собственный shell. Я разработал Internet shell, во многом похожий на стандартный UNIX shell в том смысле, что он обеспечивает конвейеризацию и работу в фоновом режиме. Но я также добавил некоторые специфичные для Интернета возможности, в которых другие shell испытывают недостаток. Ограничения Чтобы уместить функциональные возможности Internet shell в разумных пределах, имеются ограничения. □ Все символы должны быть отделены явным пробелом (символ табуляции или пробелы). Это означает, что вместо записи Is; date вы должна писать Is ; date В результате этого лексический анализатор стал очень простым. □ Замена имени файла (универсализация файловых имен) не поддерживается. Это означает что стандартные символы *, ? и метасимволы о не истолковываются. Эти ограничения хороши для реализации их в повседневном shell.
Синтаксис команды Синтаксис команды Internet shell подобен синтаксису команд стандартного UNIX shell. Здесь комады описываются формально на основе нотации BNF (обратите внимание, что символы перенаправления < и > отделяются \, чтобы предотвратить двусмысленность; см. Приложение): <internetShellcommand> = <sequence> [ & ] <sequence> = <pipeline> {; <pipeline> }* <pipeline> = <simple> { I <simple> } <simple> = { <token> }* { <redirection>)* <redirection> = <fileRedireqtion> | <socketRedirection> <fileRedirection> = \> <file> > <file> I \< <file> <socketRedirection> = <clientRedirection> | <serverRedirection> <clientRedirection> = @\>c <socket> | @\<c <socket> <serverDirection> = @\>s <socket> | @\<s <socket> <token> = строка_ символов <file> = допустимый—путь <socket> = либо путь (сокет UNIX-домена) , либо имя интернет-сокета в формате именя_хоста. номер_порта Запуск Internet shell Исполнимым файлом Internet shell назван ish. Приглашение Internet shell — знак вопроса (?). Когда ish стартует, он наследует переменную окружающей среды $ратн от shell, который вызывает его. Значение $ратн может быть изменено путем использования встроенной команды setenv. Чтобы выйти из Internet shell, следует нажать комбинацию клавиш <Ctrl>+ +<D> в отдельной строке. Встроенные команды Internet shell выполняет большинство команд, создавая дочерний shell, который запускает указанную утилиту, в то время как родительский shell ждет порожденный shell. Однако некоторые команды включены в shell и выполняются непосредственно. В табл. 13.11 перечислены встроенные команды. Они могут быть перенаправлены.
Таблица 13.11. Встроенные команды Internet shell Встроенная Функция echo {<token>}* Выводит символы на терминал cd path Изменяет рабочий каталог shell на path getenv name Выводит значение переменной окружения пате setenv name value Устанавливает значение переменной окружения name на value Перед описанием Internet shell рассмотрим несколько примеров обычных команд и команд, специфичных для Интернета. Примеры обычных команд Ниже приведены примеры, которые иллюстрируют последовательности, перенаправление и возможности конвейера Internet shell: $ ish ...старт shell. Internet Shell. ? Is ..простая команда. ish.с ish.cs ish.van who. socket who.sort ? Is | wc • ..конвейер. 5 5 41 ? who I sort > who.sort & ..конвейер, перенаправление, фон. [4356] ..PID фонового процесса. ? cat who.sort • ..показать работу перенаправления glass ttyp2 May 28 18:33 (bridge05.utdalla) posey ttypO May 22 10:19 (blackfoot.utdall) posey ttypl May 22 10:19 (blackfoot.utdall) ? date ; whoami ..последовательность команд. Thu Mar 26 18:36:24 CDT 1998 glass ? echo hi there ...выполнить встроенную команду. hi there ? getenv PATH ...посмотреть путь переменной окружения. :.:/usr/local/bin:/usr/ucb:/usr/bin:/bin:/usr/etc ? mail glass < who.sort ...перенаправление входа также работает. ? ...выйти из shell. $
Интернет-примеры Internet shell становится довольно интересным, если исследовать особенности его сокета. Вот пример, который использует сокет домена UNIX для передачи информации: $ ish ...старт Internet shell. Internet Shell. ? who @>s who.sck & ...сервер посылает вывод в сокет who.sck. [2678] ? Is ...выполнить команду для смеха, ish.с ish.van who.sock who.sort ish.cs who.sck who.socket ? sort @<c who.sck ...клиент читает ввод с сокета who.sck. glass ttyp2 May 28 18:33 (bridgeOS.utdalla) posey ttypO May 22 10:19 (blackfoot.utdall) posey ttypl May 22 10:19 (blackfoot.utdall) veerasam ttyp3 May 28 18:39 (129.110.70.139) ? ...покинуть shell. $ Действительно, забавные вещи происходят, когда выполняется внедрение интернет-сокетов. Первый shell в следующем примере работает на хосте csservr2, а второй shell — на хосте vanguard: $ ish Internet Shell. ? who @>s 5000 & [7221] ? "D $ rlogin vanguard % ish Internet Shell. ...запустить Internet shell на csservr2. ...фоновый сервер посылает вывод в порт 5000. ...покинуть shell. ...войти на хост vanguard. ...выполнить Internet shell на vanguard. ? sort @<с csservr2.5000 ...клиент читает ввод с csservr2. ...порт 5000. IP address = 129.110.42.1 ...выведен Internet shell. glass ttyp2 May 28 18:42 (bridgeOS.utdalla) ...вывод c posey ttypO May 22 10:19 (blackfoot.utdall) ...c who posey ttypl May 22 10:19 (blackfoot.utdall) ...на csservr2 veerasam ttyp3 May 28 18:39 (129.110.70.139) ? ...покинуть shell.
% logout $ ...выйти c vanguard. ...вернуться на csservr2 На рис. 13.13 приведена иллюстрация сокетного соединения. Рис. 13.13. Перенаправление Internet shell Следующий пример более интересен: первый shell использует один сокет, чтобы взаимодействовать со вторым shell, а второй shell — другой сокет, чтобы "говорить" с третьим: $ ish Internet Shell. ? who @>s 5001 & [2001] ? $ rlogin vanguard % ish Internet Shell. ...запустить shell на csservr2. ...фоновый сервер посылает вывод в порт 5001. ...покинуть shell. ...войти на vanguard. ...запустить shell на vanguard. ? sort @<с csservr2.5001 @>s 5002 & ...фоновый процесс читает [3756] ...ввод с порта 5001 ...на csservr2 и посылает его ...в локальный порт 5002. IP address = 129.110.42.1 - ...выводится shell. ? ...покинуть shell. % ...выйти из vanguard, logout $ ish ...стартовать другой shell на csservr2. Internet Shell. ? cat @<c vanguard.5002 ...читать ввод с порта 5002 на vanguard.
IP address = 129.110.43.128 ...выводится shell. glass ttyp2 May 28 18:42 (bridge05.utdalla) posey ttypO May 22 10:19 (blackfoot.utdall) posey ttypl May 22 10:19 (blackfoot.utdall) veerasam ttyp3 May 28 18:39 (129.110.70.139) ? ...покинуть shell. $ На рис. 13.14 предсталена иллюстрация соединения с двумя сокетами. Рис. 13.14. Еще одно перенаправление Internet shell Как работает Internet shell Работа Internet shell может быть разделена на несколько главных частей: □ основной командный цикл; □ анализ; □ выполнение встроенных команд; □ выполнение конвейеров; □ выполнение последовательностей; □ фоновая обработка; □ работа с сигналами; □ выполнение перенаправления файлов; □ выполнение перенаправления сокетов.
Далее приводится описание каждого действия вместе с фрагментами кода и указанием номера строки, когда необходимо. Перед чтением объснений следует просмотреть листинг исходного кода. Основной командный цикл Когда shell стартует, он инициализирует обработчик сигнала для перехвата прерывания от клавиатуры и сбрасывает флаг ошибки. Затем он входит в функцию commandLoop () (строка 167), которая выводит приглашение пользователю для ввода строки, разбирает ввод и выполняет команду. commandLoop() осуществляет цикл до нажатия пользователем комбинации клавиш <Ctrl>+<D>, после чего shell заканчивается. Анализ Чтобы проверить командную строку на ошибки, она сначала разбивается на отдельные символы процедурой tokenizeO (строка 321), которая расположена в секции лексического анализатора исходного кода. tokenizeO вызывается процедурой commandLoop () И заполняет ГЛОбалЬНЫЙ массив tokens указателями на каждый индивидуальный символ. Например, если строка ввода была is -1, tokens [0] указал бы на строку is, a tokens [1] — на строку -1. После разбора строки глобальный указатель на символы tindex устанавливается на 0 (строка 350) в подготовке к разбору. Разбор выполняется нисходящим способом. Главный анализатор parseSequence () (строка 194) вызывается ИЗ функции commandLoop () . parsesequence () разбирает каждый конвейер в последовательности, вызывая parsepipeline (), и записывает информацию, которую возвращает parsepipeline о. Наконец, он проверяет, должна ли последовательность быть выполнена в фоновом режиме. Точно так же функция parsepipeline о (строка 222) разбирает каждую простую команду в конвейере, вызывая parseSimpleO, и записывает информацию, возвращаемую parseSimpleO. parseSimpleO (строка 242) записывает символы простой команды и обрабатывает любые замыкающие метасимволы типа >, » и @>s. Информация, которую каждая из этих разбирающих функций собирает, сохраняется в структурах для дальнейшего использования процедурами выполнения. Структура sequence (строка 75) может хранить детали до пяти (max_pipes) конвейеров вместе с флагом, указывающим, действительно ли последовательность должна быть выполнена в фоновом режиме. Каждый конвейер зарегистрирован в структуре pipeline (строка 67), которая может записывать детали до пяти (max_simple) простых команд. Наконец, структура simple (строка 52), может содержать до 100 (max_tokens) символов вме
сте с несколькими полями, которые записывают информацию, связанную с перенаправлением ввода/вывода. Если команда разобрана без ошибок, локальной переменной sequence (строка 182) присваивается значение структуры sequence, которая содержит проанализированную версию команды. Обратите внимание, что можно было использовать указатели для более эффективного возвращения структуры, но был выбран простой способ программирования, чтобы фокусироваться на специфичных для UNIX аспектах. Выполнение командной последовательности Основной командный цикл выполняет успешно разобранную команду, вызывая процедуру executesequence () (строка 444), которая делает следующее. □ Если команда должна быть запущена в фоновом режиме, она создает дочерний процесс, чтобы выполнить конвейеры в последовательности; первоначальный родительский shell не ждет порожденный. Перед выполнением конвейера дочерний процесс восстанавливает его оригинальный обработчик прерываний и помещает себя в новую группу процесса, чтобы освободиться от зависаний и других сйгналов. Это гарантирует, что фоновый процесс продолжит выполняться, даже когда shell закончится, и пользователь выйдет из системы. □ Если команда должна быть выполнена в приоритетном режиме, родительский shell выполняет конвейеры в последовательности. В обоих случаях вызывается executepipeline () (строка 472), чтобы выполнить каждый компонент конвейера последовательности команды. Выполнение конвейеров Процедура executepipeline () выполняет одно из двух действий. □ Если конвейер — ЭТО простая встроенная команда, executepipeline () выполняет простую команду непосредственно, без создания дочернего процесса. Это очень важно. Например, встроенная команда cd выполняет chdirо, чтобы изменить текущий рабочий каталог shell. Если бы создавался дочерний shell, чтобы выполнить эту встроенную команду, рабочий каталог первоначального родительского shell был бы не затронут, что будет неправильно. □ Если конвейер — это больше, чем простая встроенная команда, executepipeline о создает дочерний shell, чтобы выполнить конвейер; первоначальный родительский shell ждет порожденный процесс, чтобы закончить свою обработку. Заметьте, что родитель ждет определенный PID, вызывая процедуру waitForPidO (строка 503), потому что роди
тельский shell мог создать некоторых предыдущих "потомков", чтобы выполнить фоновые процессы, и будет неправильным для родителя возобновиться, когда один из этих фоновых процессов закончится. Если конвейер содержит только одну простую команду, то никакие конвейеры создаваться не должны, и вызывается процедура executesimple о (строка 569). Иначе, вызывается процедура executepipes о (строка 516), которая соединяет каждую команду с ее собственным конвейером. executepipes () — довольно сложная процедура. Если конвейер содержит п простых команд, то executepipes о создает п дочерних процессов, один на команду, и п — 1 конвейер, чтобы соединить порожденные процессы. Каждый дочерний процесс повторно присоединяет свой стандартный ввод или каналы вывода к соответствующему конвейеру и затем закрывает все первоначальные дескрипторы конвейерных файлов. Далее каждый "потомок" выполняет свою ассоциированную простую команду. Тем временем первоначальный процесс, который вызвал процедуру executepipes о , ждет все свои порожденные процессы, чтобы получить возможность завершиться. Выполнение простой команды Процедура executesimple о переадресовывает стандартный ввод или каналы вывода по мере необходимости, а затем выполняет либо процедуру executeBuiltinO (строка 635), либо процедуру executeprimitive о (строка 596). В зависимости от категории команды builtin о (строка 624) возвращает истину, если лексема — имя встроенной команды. Если команда встроенная, возможно, что она выполняется непосредственно shell. Чтобы не допустить изменение переадресации каналов ввода/вывода shell, первоначальный стандартный ввод и каналы вывода записываются для более позднего восстановления. Процедура executeprimitive о (строка 596) работает, вызывая execvpO. К счастью (но не по совпадению), p->token уже находится в форме, необходимой ДЛЯ execvpO. Встроенные функции ВЫПОЛНЯЮТСЯ executeBuiltinO, используя простой switch-оператор. Переключение redirect о (строка 761) выполняет всю предварительную обработку, необходимую для переключения как сокета, так и файла. Основная техника заключается в том, чтобы переадресовывать стандартные каналы ввода/вывода, и аналогична той, которая была описана ранее в главе. Если требуется переназначение файла, вызывается функция dupFdO (строка 806) для создания файла с соответствующим режимом и дублирования стандартного дескриптора файла. Если требуется переназначение сокета, вызывается либо функция server о (строка 950), либо функция client о (строка 879),
чтобы создать соответствующий тип связи сокета. Эти функции манипулируют и UNIX-доменом и доменом интернет-сокетов тем же самый способом, как это происходило в более ранних примерах сокетов. Расширение возможностей Я думаю, что можно добавить некоторые новые возможности к Internet shell. Если вы заинтересованы в этом, см. разд. "Проекты" в конце главы для получения некоторых советов. Листинг исходного кода Internet shell 1 #include <stdio.h> 2 '# include <stdlib.h> 3 #include <string.h> 4 #include <signal.h> 5 #include <ctype.h> 6 #include <sys/types.h> 7 #include <fcntl.h> 8 #include <sys/ioctl.h> 9 #include <sys/socket.h> 10 #include <sys/un.h> 11 #include <netinet/in.h> 12 ttinclude <arpa/inet. h> 13 #include <netdb.h> 14 14 15 16 /* Макросы */ 17 ttdefine MAX_STRING_LENGTH 200 18 ttdefine MAX_TOKENS 100 19 tfdefine MAX_TOKEN_LENGTH 30 20 ttdefine MAX_SIMPLE 5 21 ttdefine MAX_PIPES 5 22 ttdefine NOT_FOUND -1 23 tfdefine REGULAR -1 24 ttdefine DEFAULT-PERMISSION 0660 25 tfdefine DEFAULT-PROTOCOL 0 26 ttdefine DEFAULT-QUEUE_LENGTH 5 27 tfdefine SOCKET_SLEEP 1 28
29 30 /* Энумераторы */ 31 enum { FALSE, TRUE }; 32 enum metacharacterEnum 33 { 34 SEMICOLON, BACKGROUND, END_OF_LINE, REDIRECT_QUTPUT, 35 REDIRECT_INPUT, APPEND_OUTPUT, PIPE, 36 REDIRECT_OUTPUT_SERVER, REDIRECT_OUTPUT__CLIENT, 37 REDIRECT_INPUT_SERVER, REDIRECT_INPUT_CLIENT 38 }; 39 enum builtInEnum { ECHO_BUILTIN, SETENV, GETENV, CD }; 40 enum descriptorEnum { STDIN, STDOUT, STDERR }; 41 enum pipeEnum {. READ, WRITE }; 42 enum lOEnum 43 { 44 NO-REDIRECT, FILE_REDIRECT, 45 SERVER_REDIRECT, CLIENT_REDIRECT 46 }; 47 enum socketEnum { CLIENT, SERVER }; 48 enum { INPUTJSOCKET, OUTPUT_SOCKET }; 49 50 51 /* Каждая простая команда имеет одну из структур */ 52 struct simple * 53 54 char* token [MAXJTOKENSJ; / '* Лексемы команды */ 55 int count; / Число лексем */ 56 int outputRedirect; / z* Установить на lOEnum */ 57 int inputRedirect; / z* Установить на lOEnum */ 58 int append; /* Присвоить истину для режима добавления */ 59 char *outputFile; /* Имя выходного файла или NULL, если нет */ 60 char *inputFile; /* Имя входного файла или NULL, если нет */ 61 char *outputSocket; /* Имя выходного сокета или NULL, если нет */ 62 char *inputSocket; /* Имя входного сокета или NULL, если нет */ 63 }; 64 65 66 /* Каждый конвейер имеет одну из структур, ассоциированную с ним */ 67 struct pipeline
68 { 69 struct simple simple [MAX_SIMPLE]; /* Команд в конвейере */ 70 int count; /* Число простых команд */ 71 }; 72 73 74 /* Каждая последовательность команд имеет одну из структур */ 75 struct sequence 76 { 77 struct pipeline pipeline [MAX_PIPES]; /* Конвейеров в последовательности */ 78 int count; /* Число конвейеров */ 79 int background; /* Истина, если это фоновая последовательность * 80 * }; 81 82 83 /* Прототипы */ 84 struct sequence parseSequence(); 85 struct pipeline parsePipeline(); 86 struct simple parseSimple(); 87 char *nextToken(); 88 char *peekToken(); 89 char *lastToken(); 90 char* getTokenO; 91 92 93 /* Глобальные */ 94 char* metacharacters [] = 95 & "@>s", "@>c", "@<s", "@<c"/ ’”’ 96 char* builtlns [] = { ’’echo”, ’’setenv", "getenv", "cd", ’”’ }; 97 char line [MAX_STRING_LENGTH]; /* Текущая строка */ 98 char tokens [MAX_TOKENS][MAX_TOKEN_LENGTH]; /* Лексем в строке */ 99 int tokenCount; /* Число лексем в текущей строке */ \ 100 int tIndex; /* Индекс в строке для лексического анализатора */ 101 int errorFlag; /* Имеет значение "истина", когда происходит ошибка */ 102 103 104 /* Несколько предварительных объявлений */ 105 void (*originalQuitHandler) О;
106 void quitHandler() ; 107 108 109 /* Внешние */ 110 char **environ; /* Указатель на окружение */ 111 /*************************************************************** / 113 114 main (argc, argv) 115 116 int argc; 117 char* argv []; 118 119 { 120 initialize(); /* Инициализировать некоторые глобальные */ 121 commandLoop(); /* Принять и обработать команды */ 122 return (/* ВЫХОД_УСПЕШНО */ 0) ; 123 } 124 126 127 initialize() 128 129 { 130 printf ("Internet Shell.\n"); /* Введение */ 131 /* Установить обработчик <Ctrl>+<C>, чтобы ловить прерывания от клавиатуры */ 132 originalQuitHandler = signal (SIGINT, quitHandler); 133 } 134 j_35 у****************************************************************j 136 137 void quitHandler() 138 139 { 140 /* Обработчик <Ctrl>+<C> */ 141 printf ("\n"); 142 displayPrompt(); 143 }
144 145 146 •147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 error (str) char* str; { /* Вывести строку, когда ошибка будет в стандартный канал ошибки */ fprintf (stderr, ”%s", str); errorFlag = TRUE; /* Установить флаг ошибки */ /****************************************************************j displayPrompt() printf (”? ”); } commandLoop () { struct sequence sequence; /* Принимать и обрабатывать команды до появления <Ctrl>+<D> */ while (TRUE) { displayPrompt(); if (gets (line) = NULL) break; /* Получить входную строку */ tokenizeO; /* Разбить входную строку на лексемы */ errorFlag = FALSE; /* Сбросить флаг ошибки */ if (tokenCount >1) /* Обработать любую непустую строку */ sequence = parseSequence(); /* Разобрать строку */
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 /* Если нет ошибок во время разбора, */ /* выполнить команду */ if (’errorFlag) executesequence (&sequence); ПРОЦЕДУРА РАЗБОРА **********★*****************************************************/ struct sequence parseSequence() { struct sequence q; /★ Разобрать командную последовательность и вернуть указатель на структуру */ q.count =0; /* Число конвейеров в последовательности */ q.background = FALSE; /* По умолчанию не в фоне */ while (TRUE) /* Цикл до найденной точки с запятой */ q.pipeline[q.count++] = parsePipeline(); /* Разобрать */ if (peekCode() != SEMICOLON) break; nextToken(); /* Выключить разделитель "точка с запятой" */ if (peekCode() == BACKGROUND) /* Последовательность в фоне */ q.background = TRUE; nextToken(); /* Выключить амперсанд */ getToken (END_OF_LINE); /* Проверить достижение конца строки */ return (q);
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 22 struct pipeline parsePipeline() { struct pipeline p; /* Разобрать конвейер и вернуть дескриптор структуры на него */ р.count =0; /* Число простых команд в конвейере */ while (TRUE) /* Цикл до появления разделителя конвейера */ { р.simple[р.count++] = parseSimple(); /* Разобрать команду */ if (peekCode() != PIPE) break; nextTokenO; /* Выключить разделитель конвейера */ } return (р); } struct simple parseSimple() { struct simple s; int code; int done; /* Разобрать простую команду и вернуть дескриптор структуры */ s.count =0; /* Число лексем в простой команде */ s.outputFile = s.inputFile = NULL; s.inputsocket = s.outputsocket = NULL; s.outputRedirect = s.inputRedirect = NO_REDIRECT;/*По умолчанию*/ s.append = FALSE; while (peekCode() == REGULAR) /* Сохранить все обычные лексемы */ s.token[s.count++] = nextToken(); s.token[s.count] = NULL; /* Заканчивающийся NULL список лексем */ Зак. 786
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 done = FALSE; /* Разобрать специальные метасимволы, подобные > и » ★/ do { code = peekCode(); /* Считать следующую лексему */ switch (code) { case REDIRECT_INPUT: /* < */ nextToken(); s.inputFile = getToken (REGULAR); s.inputRedirect = FILE_REDIRECT; break; case REDIRECT—OUTPUT: /* > */ case APPEND_OUTPUT: /* > */ nextToken(); s.outputFile = getToken (REGULAR); s.outputRedirect = FILE_REDIRECT; s.append = (code = APPEND_OUTPUT); break; case REDIRECT—OUTPUT—SERVER: /* @>s */ nextToken(); s.outputsocket = getToken (REGULAR); s.outputRedirect = SERVER_REDIRECT; break; case REDIRECT—OUTPUT—CLIENT: /* @>c */ nextToken(); s.outputsocket = getToken (REGULAR); s.outputRedirect = CLIENT—REDIRECT; break; I I a case REDIRECT—INPUT—SERVER: /* @<s */ nextToken(); s.inputsocket = getToken (REGULAR); s.inputRedirect = SERVER_REDIRECT;
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 break; case REDIRECT_INPUT_CLIENT: /* @<c */ nextToken(); s.inputsocket = getToken (REGULAR); s.inputRedirect = CLIENT_REDIRECT; break; default: done = TRUE; break; } while (’done); return (s); } /* ПРОЦЕДУРА ЛЕКСИЧЕСКОГО АНАЛИЗАТОРА */ ^•к-к'к-к-к^-к-к-к-к-к-к-к-к-к-к^’к'к-к-к-к-к-к-к-к-к’к-к-к-к’к'к-к-к-к-к-к-к-к-к-к-к-к-к'к-к-к-к-к'к-к-к-к-к-к-к-к-к-^-^-^-к-^ / tokenize() char* ptг = line; /* Указывает на входной буфер */ char token [MAX_TOKEN_LENGTH]; /* Держит текущую лексему */ char* tptr; /* Указатель на текущий символ */ tIndex =0; /* Глобальный: указывает на текущую лексему */ /* Разбить текущую входную строку на лексемы */ while (TRUE) { tptr = token; while (*ptr == ’) ++ptr; /* Пропустить ведущие пробелы */ if (*ptr =- NULL) break; /* Конец строки */ do
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 { *tptr++ = *ptr++; } while (*ptr != ’ ’ && *ptr != NULL); *tptr = NULL; strcpy (tokens[tlndex++], token); /* Хранит символ */ } /* Поместить лексему "конец-строки" в конец списка лексем */ strcpy (tokens[tlndex++], "\n"); tokenCount = tlndex; /* Запомнить общее число лексем */ tIndex =0; /* Установить индекс лексем на начало списка лексем */ } ^•к-к-кк-кк-к'к-к'к'к'к'к-к'к'к'к'к-к'кк-'к-к-к'к'к'к-к-к-к'к'к'к-к'ккк-к'к'к'к-к-кк-к-кккк-к-к-кк-к-к-к-к-к-ккк / char* nextToken() return (tokens[tlndex++]); /*Вернуть следующую лексему из списка*/ кк'к-кк’к-кк-к'к-к-к'к-кк-к-к-к'к-к'к'к'к'к'к'к'к'к'кк-кк-к * -к к-ккк-к-к-к-к-к-к-к-к-к-к-к-к-кк-к-кккк-к char *lastToken() { return (tokens[tIndex — 1]); /* Вернуть предыдущую лексему из списка */ к’кк’к'к-к-к'к-к-кк'к-кк'кк-к-к-к-к-к'к-кк-кк-кк-кк-к-кк-ккккк-к-кк-к-к'кк-к-к-кк-к-кккккк-к-к-к-к-кккк peekCode() { /* Вернуть указатель кода следующей лексемы в списке */ return (tokenCode (peekToken()));
Системное программирование 649 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 char* реекТокепО { /* Вернуть указатель следующей лексемы в списке */ return (tokens[tlndex]); } ^•k'k-k-k-ir-k-k-ir-k'k-ie-k-k-k'k-k-k-k-k'k-k-ic-k'k-k'k'k-k'kic-k'k'ic'ie'k-k’k'k-k’k'k'k-ic-k-k-k'k-ir-k-k-k'k'k-k-k-k-ic-k-k-k-k-k-k'k-k I char *getToken (code) int code; { char str [MAXJSTRING_LENGTH]; /* Генерировать ошибку, если код следующей лексемы — не code, */ /* иначе вернуть лексему */ if (peekCode() != code) { sprintf (str, "Expected %s\n", metacharacters[code]); error (str); return (NULL); } else return (nextToken()); /Jt****Jr*f********Jrir*i*ilr****5lr*i**ilr*************^Jri*****************/ tokenCode (token) char* token; {
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 421 443 444 445 446 447 448 449 450 451 452 453 /★ Вернуть индекс лексемы в массиве метасимволов */ return (findString (metacharacters, token)); findString (strs, str) char* strs []; char* str; int i = 0; /* Вернуть индекс строки в строковом массиве strs */ /* или NOT_FOUND, если строки там нет */ while (strcmp (strs[i], "") !=0) if (strcmp (strs[i], str) == 0) return (i); else return (NOT_FOUND) ; /* He найдена */ ПРОЦЕДУРА ВЫПОЛНЕНИЯ КОМАНДЫ executeSecqence (р) struct sequence* p; int i, result; /* Выполнить последовательность операторов (возможно, один) */ if (p->background) /* Выполнить в фоне */
454 455 456 457 458 459 4 60 461 462 463 464 4 65 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 if (fork() == 0) { printf (”[%d]\n"/ getpidO); /* Вывести дочерний PID */ /* Дочерний процесс */ signal (SIGQUIT, originalQuitHandler);/* Старый обработчик */ setpgid (0, getpidO); /★ Изменить группу процесса */ for (i = 0; i < p->count; i++) /* Выполнить конвейеры */ executepipeline (&p->pipeline[i]); exit (/* ВЫХОД-УСПЕШНО */ 0) ; } } else /* Выполнить в фоне */ for (i = 0; i < p->count; i++) /* Выполнить каждый конвейер */ executepipeline (&p->pipeline[i]); } *****★*********★*****★+*^**^**************,111*****^*********^*^** / executepipeline (p) struct pipeline *p; { int pid, processGroup, result; /* Выполнить каждую простую команду в конвейере (возможно, одну) */ if (p->count == 1 && builtin (p->simple[0].token[0])) executeSimple (&p->simple[0]); /* Выполнить ее непосредственно */ else { if ((pid = fork()) == 0) { /* Дочерний shell выполняет простую команду */ if (p->count == 1) executeSimple (&p->simple[0]); /* Выполнить команду */ else executePipes (p) ; /* Выполнить больше, чем одну команду */ exit ( /* ВЫХОД—УСПЕШНО */ 0) ; }
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528- 529 530 531 else { /* Родительский shell ждет завершения "потомка" */ waitForPID (pid); } } } ★ ★fr^O********************************************************* waitForPID (pid) int pid; { int status; /* Возврат, когда дочерний процесс с PID завершится */ while (wait (&status) ! = pid); } ***i**************i*******Jr******Jr****Jr*****Jr**** + *****Jf**** executePipes (p) struct pipeline *p; { int pipes, status, i; int pipefd [MAX_PIPES][2]; /* Выполнить две или больше простых команды в конвейере */ pipes = p->count — 1; /* Число строящихся конвейеров */ for (i = 0; i < pipes; i++) /* Построить конвейеры */ » pipe (pipefdfi]); for (i =0; i < p->count; i++)/* Создать 1 процесс на конвейер */ { t if (fork() != 0) continue; /★ Дочерний процесс */
532 */ 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 /^Первое: соединить stdin с конвейером, если нет первой команды if (i != 0) dup2 (pipefd[i-1][READ], STDIN); /^Второе: соединить с конвейером, если нет последней команды */ if (i != p->count — 1) dup2 (pipefd[i][WRITE], STDOUT); /★ Третье: закрыть все файловые дескрипторы каналов ★/ closeAllPipes (pipefd, pipes); /★ Последнее: выполнить простую команду */ executesimple (&p->simple[i]); exit (/* ВНХОД_УСПЕШНО */0); } /* Родительский shell приходит сюда после отделения "потомка" ★/ closeAllPipes (pipefd, pipes); for (i = 0; i < p->count; i++) /* Ждать завершения "потомков" */ wait (^status); /****************** + ************* + *********-*••********★*****★**★*** / closeAllPipes (pipefd, pipes) int pipefd [] [2]; int pipes; int i ; /* Закрыть каждый дескриптор файла конвейера */ for (i = 0; i < pipes; i++) { close (pipefd[i][READ]); close (pipefd[i][WRITE]); } } **************************************************************** executeSimple (p)
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 struct simple* p; { int copyStdin, copyStdout; /* Выполнить простую команду */ if (builtin (p->token[0])) /* Встроенная */ /* Родительский shell выполняет это, так что помните */ /* stdin и stdout в случае встроенной переадресации */ copyStdin = dup (STDIN); copyStdout = dup (STDOUT); if (redirect (p)) executeBuiltln (p); /* Выполнить встроенный */ /* Восстановить stdin и stdout */ dup2 (copyStdin, STDIN); dup2 (copyStdout, STDOUT); close (copyStdin); close (copyStdout); } else if (redirect (p)) /* Переадресовать, если необходимо */ executePrimitive (p); /* Выполнить простую команду */ } у*^***********************^*^********^************************^**/ executePrimitive (р) struct simple* р; /* Выполнить команду путем вызова ехес */ if (execvp (p->token[0], p->token) == -1) { perror (”ish”); exit (/* ВЫХОД_НЕУДАЧА */ 1); }
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 ВСТРОЕННЫЕ КОМАНДЫ ********************************************************************** builtlnCode (token) char* token; , { /* Вернуть индекс лексемы в массиве встроенных */ return (findString (builtlns, token)); } ^'к'к’к'к'к'к'к'к'к’к'к'к'к’к'к'к'к’к-к’к'к'к-к’к'к’к-к-к-к'к'к'к'к-к-к^ск'к-к'к-к-к'к-к’к'к'к-ктк-к'к-к'к-к^-к-к^^'к'к’к-к'к^ builtln (token) » char* token; { /* Вернуть истину, если лексема встроенная */ return (builtlnCode (token) != NOT_FOUND); } **★★****★*★★****★***★★********★****★*★**★*****★★*★**★***********/ executeBuiltln (p) struct simple* p; { /* Выполнить отдельную встроенную команду */ switch (builtlnCode (p->token[0])) { case CD: executeCd (p); break; case ECHO BUILTIN:
648 executeEcho (р); 649 break; 650 651 case GETENV: 652 executeGetenv (p); 653 break; 654 655 case SETENV: 656 executeSetenv (p); 657 break; 658 } 659 } 660 g /******Jr********* + **^**** + ^ik****** + ****************** + + ***** + **i*/ 662 663 executeEcho (p) 664 665 struct simple* p; 666 667 { 668 int i; 669 670 /* Вывести лексемы в этой команде */ 671 for (i = 1; i < p->count; i++) 672 printf ("%s "> p->token[i]); 673 674 printf ("\n"); 675 } 676 677 у****************************************************************/ 678 679 executeGetenv (p) 680 681 struct simple* p; 682 683 { 684 char* valuel- ess 686 /* Вывести значение переменной окружения */
657 if (p->count != 2) 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 error ("Usage: getenv variable\n"); return; value = getenv (p->token[1]); if (value == NULL) printf ("Environment variable is not currently set\n"); else printf ("%s\n", value); executeSetenv (p) struct simple* p; /* Установить значение переменной окружения */ if (p->count != 3) error ("Usage: setenv variable value\n"); else setenv (p->token[1], p->token[2]); setenv (envName, newValue) char* envName; char* newValue; int i = 0; char newStr [MAX_STRING_LENGTH]; int len;
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 /* Установить переменную окружения envName на newValue */ sprintf (newStr, "%s=%s", envName, newValue); len = strlen (envName) + 1; while (environ[i] != NULL) if (strncmp (environ[i], newStr, len) == 0) break; } if (environ[i] == NULL) environ[i+1] = NULL; environ[i] = (char*) malloc (strlen (newStr) + 1); strcpy (environ[i], newStr); } executeCd (p) struct simple* p; { /* Изменить каталог */ if (p->count != 2) error ("Usage: cd path\n"); else if (chdir (p->token[1]) == -1) perror ("ish"); } ***★★★*★**★★★★*★*★★******★★*****★★★*★*★*★******★★★★★**★+**★*•*•*★* / ПЕРЕАДРЕСАЦИЯ ********************************************★*******************/ redirect (p) struct simple *p;
765 766 767 768 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 { int mask; 767 /* Выполнить переадресацию ввода */ switch (p->inputRedirect) { case FILE_REDIRECT: /* Переадресовать от файла */ if (IdupFd (p->inputFile, O_RDONLY, STDIN)) return(FALSE); break; case SERVER_REDIRECT: /* Переадресовать от сокета сервера */ if (!server (p->inputSocket, INPUT_SOCKET) ) return(FALSE); break; case CLIENT_REDIRECT: /* Переадресовать от сокета клиента */ if (!client (p->inputSocket, INPUT_SOCKET)) return(FALSE); break; } /* Выполнить переадресацию вывода */ switch (p->outputRedirect) { case FILE—REDIRECT: /* Переадресовать в файл */ mask = O_CREAT | O_WRONLY | (p->append?O_AP.PEND:O_TRUNC) ; if (IdupFd (p->outputFile, mask, STDOUT)) return (FALSE); break; case SERVER—REDIRECT: /* Переадресовать в сокет сервера^ */ if (1 server(p->outputSocket, OUTPUT_SOCKET)) return(FALSE); break; case CLIENT—REDIRECT: /* Переадресовать в сокет клиента */ if (1 client(p->outputSocket, OUTPUT_SOCKET)) return(FALSE); break; } return (TRUE); /* Если я попал сюда, тогда все прошло хорошо */ } **★★*★★*★★★*★★★★*****★****★******★ ***★***★★★*******•*★****!
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 dupFd (name, mask, stdFd) char* name; int mask, stdFd; int fd; /* Дублировать новый дескриптор файла поверх stdin/stdout */ fd = open (name, mask, DEFAULT_PERMISSION); if (fd == -1) { . error ("Cannot redirect\n"); return (FALSE); dup2 (fd, stdFd);^Копировать по стандартному дескриптору файла */ close (fd); /* Закрыть другой дескриптор */ return (TRUE); УПРАВЛЕНИЕ СОКЕТОМ internetAddress (name) char* name; /* Если имя содержит цифры, предположить, что это интернет-адрес */ return (strpbrk (name, "01234567890") != NULL); ****************************************************************/ socketRedirect (type)
844 845 int type; 846 847 { 848 return (type == SERVER_REDIRECT || type == CLIENT_REDIRECT); 849 } 850 /************************************************************/ 852 853 getHostAndPort (str, name, port) 854 855 char *str, *name; 856 int* port; 857 858 { 859 char *tokl, *tok2; 860 861 /* Декодировать имя и номер порта из входной строки */ 862 ’ /* формы ИМЯ.ПОРТ */ 863 tokl = strtok (str, "."); 864 tok2 = strtok (NULL, "."); 865 if (tok2 == NULL) /* Имя отсутствует, тогда предположить локальный хост */ 866 { 867 strcpy (name, "") ; 868 sscanf (tokl, "%d", port); 869 } 870 else 871 { 872 strcpy (name, tokl); 873 sscanf (tok2, "%d", port); 874 } 875 } 876 g'y'y у************************************************************/ 878 879 client (name, type) 880 881 char* name;
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 , 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 int type; int clientFd, result, internet, domain, serverLen, port; char hostName [100]; struct sockaddr_un serverUNIXAddress; struct sockaddr_in serverINETAddress; struct sockaddr* serverSockAddrPtr; struct hostent* hostStruct; struct in_addr* hostNode; /* Открыть сокет клиента с указанным именем и типом */ internet = internetAddress (name); /* Интернет-сокет? */ domain = internet ? AF_INET : AF_UNIX; /* Выбрать домен */ /* Создать сокет клиента */ clientFd = socket (domain, SOCK_STREAM, DEFAULT_PROTOCOL); if (clientFd == -1) { perror ("ish"); return (FALSE); } if (internet) /* Интернет-сокет */ getHostAndPort (name, hostName, &port); /* Получить имя, порт */ if (hostName [0] == NULL) gethostname (hostName, 100); serverINETAddress. sin_family = AF_INET; /* Интернет */ hostStruct = gethostbyname (hostName); /* Найти хост */ if (hostStruct == NULL) perror ("ish"); return (FALSE); } hostNode = (struct in_addr*) hostStruct->h_addr; printf ("IP address = %s\n", inet_ntoa (*hostNode)); serverINETAddress.sin_addr = *hostNode; /*Установить IP-адрес */
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 serverINETAddress.sin_port = port; /* Установить порт */ serverSockAddrPtr = (struct sockaddr*) &serverINETAddress; serverLen = sizeof (serverINETAddress); else /* Домен сокет UNIX */ serverUNIXAddress.sun_family — AF_UNIX; /* Домен */ strcpy (serverUNIXAddress.sun_path, name); /* Имя файла */ ♦ serverSockAddrPtr = (struct sockaddr*) &serverUNIXAddress; serverLen = sizeof (serverUNIXAddress); do /* Соединится с сервером */ ( result = connect (clientFd, serverSockAddrPtr, serverLen); if (result == -1) sleep (SOCKET_SLEEP);/*Попробовать снова*/ while (result == -1); /* Выполнить переадресацию */ if (type == OUTPUT_SOCKET) dup2 (clientFd, STDOUT); if (/type == INPUT_SOCKET) dup2 (clientFd, STDIN); close (clientFd);/*3акрыть оригинальный дискриптор файла клиента*/ return (TRUE); } у****************************************************************/ server (name, type) char* name; int type; int serverFd, clientFd, serverLen, clientLen; int domain, internet, port; struct sockaddr un serverUNIXAddress;
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 struct sockaddr_un clientUNIXAddress; struct sockaddr_in serverlNETAddress; struct sockaddr_in clientINETAddress; struct sockaddr* serverSockAddrPtr; struct sockaddr* clientSockAddrPtr; /★ Подготовить сокет сервера */ internet = internetAddress (name); /* Интернет? */ domain = internet ? AF_INe£ : AF_UNIX; /* Выбрать домен */ /* Создать сокет сервера */ serverFd = socket (domain, SOCK_STREAM, DEFAULT-PROTOCOL); if (serverFd == -1) { perror ("ish"); return (FALSE); } if (internet) /* Интернет-сокет */ sscanf (name, "%d", &port); /* Получить номер порта */ /*'Заполнить поля адреса сокета сервера */ serverLen = sizeof (serverlNETAddress); bzero ((char*) &serverlNETAddress, serverLen); serverlNETAddress. sin_family = AF_INET; /* Домен */ serverlNETAddress.sin_addr.s_addr = htonl (INADDR_ANY); serverlNETAddress.sin_port = htons (port); /* Порт */ serverSockAddrPtr = (struct sockaddr*) &serverINETAddress; } else /* домен сокета UNIX */ serverUNIXAddress.sun_family = AF_UNIX; /* Домен */ strcpy (serverUNIXAddress.sun_path, name); /* Имя файла */ serverSockAddrPtr — (struct sockaddr*) &serverUNIXAddress; serverLen = sizeof (serverUNIXAddress); unlink (name); /* Удалить сокет, если все готово */ } /* Связаться с адресом сокета */
998 if (bind (serverFd, serverSockAddrPtr, serverLen) == -1) 999 { 1000 perror ("ish"); 1001 return (FALSE); 1002 } 1003 1004 /* Установить максимальную длину очереди ожидающих соединений */ 1005 if (listen (serverFd, DEFAULT_QUEUE_LENGTH) == -1) 1006 { 1007 perror ("ish"); 1008 return (FALSE); 1009 } 1010 1011 if (internet) /* Интернет-сокет */ 1012 { 1013 clientLen = sizeof (clientINETAddress); 1014 clientSockAddrPtr = (struct sockaddr*) &clientINETAddress; 1015 } 1016 else /* Домен сокета UNIX */ 1017 { 1018 clientLen = sizeof (clientUNIXAddress); 1019 clientSockAddrPtr = (struct sockaddr*) &clientUNIXAddress; 1020 } 1021 1022 /* Принять соединение */ 1023 clientFd = accept (serverFd, clientSockAddrPtr, sclientLen); 1024 1025 close (serverFd); /* Закрыть оригинальный сокет сервера */ 1026 1027 if (clientFd == -1) 1028 { 1029 perror ("ish"); 1030 return (FALSE); 1031 } 1032 1033 /* Выполнить переадресацию */ 1034 if (type == OUTPUT_SOCKET) dup2 (clientFd, STDOUT); 1035 if (type == INPUT_SOCKET) dup2 (clientFd, STDIN);
1036 close (clientFd); /* Закрыть оригинальный сокет клиента */ 1037 1038 return (TRUE); 1039 } 1040 Обзор главы Перечень тем В этой главе были описаны: О общие запросы системы управления файлами; □ системные вызовы для дублирования, завершения и отделения процессов; □ ожидание родительским процессом своих "потомков"; □ термины "сирота” и "зомби"; □ процессы-нити; □ перехват и игнорирование сигналов; □ способ "убивать” процессы; □ приостановка и возобновление процесса; □ механизмы IPC: безымянные конвейеры, именованные конвейеры, разделяемая память и семафоры; □ клиент-серверная парадигма; □ сокеты домена UNIX и сокеты домена Интернета; □ разработка и функционирование Internet shell. Контрольные вопросы 1. Как можно выяснить, что достигнут конец файла? 2 цто такое "дискриптор файла"? 3. Каков самый быстрый способ перемещения на конец файла? 4. Опишите путь, которым shell осуществляет перенаправление ввода/вывода. 5. Что такое "осиротевший процесс"? 6. Чем задание, работающее в двух процессах, отличается от задания, работающего в двух нитях? 7. При каких обстоятельствах накапливаются зомби?
8. Как родитель может выяснять, каким образом "умерли" его "потомки"? 9. В чем различие между библиотечными процедурами execv () и execvp () ? 10. Почему имя системного вызова kill о не отражает его назначения? 11. Как можно защищать критический код? 12. Какова цель групп процесса? 13. Что происходит, когда писатель пробует переполнить конвейер? 14. Как можно создать именованный конвейер? 15. Опишите парадигму клиент-сервера. 16. Опишите стадии, которые клиент и сервер проходят, чтобы установить связь. Упражнения 13.1. Напишите программу, которая ловит все посланные' ей сигналы и распечатывает сведения об этих сигналах. Затем пошлите команду kill -9 процессу. Как сигнал sigkill отличается от других сигналов? [Уровень: легкий.] 13.2. Напишите программу, которая берет единственный целочисленный аргумент п из командной строки и создает двоичное дерево процессов глубины п. Когда дерево создано, каждый процесс должен вывести фразу "I am process х" ("Я процесс х") и завершиться. Узлы дерева процесса должны перечисляться согласно обходу в ширину. Например, если пользователь вводит $ tree 4 ... построить дерево глубиной 4. то дерево процессов могло бы выглядеть подобно представленному на рис. 13.15. Рис. 13.15. Дерево глубиной 4
Вывод был бы таким: I am process 1 I am process 2 I am process 15 Удостоверьтесь, чтобы первоначальный родительский процесс не закончился, пока все его дочерние процессы не "умрут". Сделайте так, чтобы вы могли завершить родителя и его "потомков" со своего терминала нажатием комбинации клавиш <Ctrl>+<C>. [Уровень: средний.] 13.3. Напишите программу, которая создает кольцо из трех процессов, связанных конвейерами. Первый процесс должен предложить (prompt) пользователю ввести строку, а затем послать ее второму процессу. Второй процесс должен реверсировать (reverse) строку и передать ее третьему процессу. Третий процесс обязан конвертировать строку в прописные буквы (uppercase) и послать ее назад первому процессу. Когда первый процесс получит обработанную строку, он должен вывести (display) ее на терминал. Когда это сделано, все три процесса должны закончиться. Иллюстрация кольца процессов приведена на рис. 13.16. Рис. 13.16. Кольцо процессов Вот пример программы в действии: $ ring ...выполнить программу. Please enter a string: ole Processed string is: ELO $ _ [Уровень: средний.]
13.4. Перепишите упражнение 5.1, используя язык С. [Уровень: средний.] 13.5. Напишите программу, которая использует библиотечную процедуру setuido, чтобы разрешить пользователю получать доступ к файлу, к которому он обычно доступа не имел. [Уровень: средний.] Проекты 1. Напишите набор программ для игры "Камень, ножницы, бумага", которые взаимодействуют и работают параллельно. В этой игре каждый из двух игроков тайно выбирает бумагу, ножницы или камень. Затем они показывают свой выбор. Арбитр решает, кто победил следующим образом: • бумага побеждает камень (покрывая его); • камень побеждает ножницы (затупляя их); • ножницы побеждают бумагу (разрезая ее); • совпадение выборов — ничья. Победивший игрок получает очко. При ничьей очки не присуждаются. Ваша программа должна смоделировать такую игру, разрешая пользователю выбрать количество повторений, предоставляя возможность наблюдать за игрой и видеть заключительный счет. Вот пример игры: $ play 3 ...играть три раза. Paper, Scissors, Rock: 3 iterations Player 1: ready Player 2: ready Go Players [1] Player 1: Scissors Player 2: Rock Player 2 wins Go Players [2] Player 1: Paper Player 2: Rock Player 1 wins Go Players [3] Player 1: Paper Player 2: Paper Players draw. Final score: Player 1: 1 Player 2: 1 Players Draw $
Вы должны написать три программы, которые работают следующим образом. • Одна программа — главная, разветвляется и выполняет один процесс арбитра и два процесса игрока. Затем она ждет, когда все три закончатся. Главная программа должна проверять, что параметр командной строки, который определяет число повторений, — правильный, и должна передать число процессу арбитра в качестве параметра к библиотечной процедуре ехес (). • Одна программа — программа-арбитр, играет роль сервера. Она должна подготовить сокет и ждать от обоих игроков для вывода строки "READY", означающей, что они готовы сделать выбор. Арбитр должен приказать каждому игроку сделать выбор, посылая им строку "GO". Их ответы читаются, а счет вычисляется и обновляется. Описанный процесс должен повторяться до тех пор, пока все партии не будут завершены. Тогда арбитр должен послать обоим игрокам строку "STOP", которая заставляет партии закончиться. • Одна программа — программа-игрок, выполняет роль клиента. Она запускается дважды главной программой и должна стартовать, соединяясь с сокетом арбитра. Затем она должна послать сообщение "READY". Когда программа получает сообщение от арбитра "GO", игрок должен сделать выбор и передать его арбитру в качестве строки. Когда игрок получает строку "STOP", он должен "убить" себя. Эти три программы будут, конечно, разделять некоторые функции. Создайте make-файл, который собирает отдельные общие функции и компонует с использующими их выполняемыми файлами. Не пренебрегайте посылать строки, зашифровывая их, как однобайтовые числа — это только часть проблемы. [Уровень: средний.] 2. Перепишите упражнение 1, используя безымянные конвейеры вместо сокетов. Какая программа проще для написания? Какая программа легче для понимания? [Уровень: средний.] 3. Перепишите упражнение 1, чтобы позволить игрокам находиться на различных интернет-машинах. Организуйте возможность запуска каждого компонента игры отдельно. [Уровень: высокий.] ...выполните эту команду на vanguard. $ referee 5000 ...использовать локальный порт 5000. ...выполните эту команду на csservr2. $ player vanguard.5000 ...игрок на удаленном порту. ...выполните эту команду на wotan. $ player vanguard.5000 ...игрок на удаленном порту.
4. Internet shell готов для улучшений. Ниже приведен список особенностей, которые были бы перспективны для добавления. • Возможность указывать интернет-адрес в формате A.B.C.D. Это фактически легко добавить, т. к. мой первый интернет-пример уже имеет эту способность. [Уровень: легкий.] • Возможности управления заданиями, подобные командам shell fg, bg и jobs. [Уровень: средний.] • Замещение имени файла, использующее *, ? и (). [Уровень: высокий.] • Поддержка двунаправленных сокетов, которая соединяет каналы стандартного ввода и вывода, клавиатуры или указанного процесса с интер-нет-сокетом. Эта особенность позволила бы вам соединяться со стандартными услугами без помощи утилиты telnet. [Уровень: высокий.] • Простой встроенный язык программирования. [Уровень: средний.] • Способность обращаться к любому интернет-адресу символически. Например, было бы хорошо иметь возможность переадресации на vanguard.utdalIas.edu.3000. [Уровень: средний.]

Глава 14 UNIX изнутри Мотивация Операционная система UNIX — одна из лучших разработанных операционных систем своего времени. Многие из основных базовых концепций ОС, заложенных в UNIX, продолжают в некоторой форме использоваться и в настоящее время. Например, способ, которым UNIX разделяет ЦП среди конкурирующих процессов, применяется во многих операционных системах типа Microsoft Windows. Знание принципов работы системы может помочь в проектировании быстродействующих приложений UNIX. Например, сведения о структуре системы виртуальной памяти позволяют организовать структуры данных так, чтобы количество информации, передаваемой между главной и вторичной памятью, было минимизировано. В результате знание UNIX важно как источник полезной информации, которая поможет при проектировании других подобных систем, и как помощь при программировании быстродействующих приложений UNIX. Предпосылки Для изучения материала данной главы следует прочитать главу 13. Также поможет хорошее знание структур данных, указателей и связанных списков. Задачи В этой главе описываются используемые UNIX механизмы, предназначенные для поддержки процессов, управления памятью, ввода/вывода и файловой системы. Также объясняются главные структуры данных ядра и алгоритмы. Изложение Различные части UNIX системы описаны по очереди.
Общие сведения Система UNIX — довольно сложная ОС, которая все больше и больше "обрастает" новыми возможностями. Чтобы хорошо в ней разбираться, необходимо разделить систему на поддающиеся управлению части и заняться изучением каждой части, используя многоуровневый подход. Следовательно, мы обсудим следующие темы: □ основы ядра', системные вызовы и прерывания; □ файловая система*, управление иерархией каталогов, обычными файлами, периферийными устройствами и сложной файловой системой; □ управление процессами*, разделение ЦП и памяти процессами и реализация сигналов; □ ввод/вывод*. организация доступа к файлам, специальное внимание уделено терминальному вводу/выводу. □ взаимодействие процессов (IPC)*. механизмы, которые позволяют процессам связываться друг с другом, даже если они находятся на различных машинах. Есть некоторые различия в способах, которыми проектировщики BSD и System V реализовали части перечисленных подсистем. Всякие существенные различия в подходе будет указаны. Основы ядра Ядро UNIX — это часть операционной системы UNIX, которая содержит код для: □ разделения ЦП и ОЗУ между конкурирующими процессами; □ обработки всех системных вызовов; □ управления периферийными устройствами. Ядро — программа, которая загружается с диска в ОЗУ, когда компьютер впервые включается. Оно всегда остается в ОЗУ и работает, пока система не выключена или не случится аварийный отказ. Хотя ядро написано главным образом на языке С, некоторые части, по причинам эффективности, запрограммированы на ассемблере. Пользовательские программы взаимодействуют с ядром через интерфейс системных вызовов. Подсистемы ядра Возможности ядра могут быть разделены на следующие подсистемы: □ управления памятью; □ управления процессами;
□ взаимодействия процессов (IPC); □ ввода/вывода; □ управления файлами. Эти подсистемы взаимодействуют по четким иерархическим правилам. Рис. 14.1 иллюстрирует иерархическое представление. Рис. 14.1. Подсистемы UNIX Процессы и файлы Ядро (kernel) UNIX поддерживает концепции процессов и файлов. Процессы — это "живые формы", которые существуют в компьютере и принимают решения. Файлы — это контейнеры информации, которые подвержены чтению и записи. Кроме того, процессы могут взаимодействовать друг с другом, используя несколько различных типов механизмов межпроцессной связи, включая сигналы, конвейеры и сокеты (рис. 14.2). Взаимодействие с ядром Обрабатывающие возможности ядра доступны через интерфейс системных вызовов, а периферия (специальные файлы) связывается с ядром через аппаратные прерывания. Системные вызовы и аппаратные прерывания — это единственные способы, которыми внешний мир может взаимодействовать с ядром, как показано на рис. 14.3.
Рис. 14.2. Поддержка процессов и файлов в UNIX I Системные вызовы Ядро Прерывания аппаратуры Внешнее устройство Внешнее устройство Внешнее устройство Рис. 14.3. Взаимодействие с ядром Так как системные вызовы и прерывания, очевидно, очень важны, начнем обсуждение структуры UNIX с описания каждого из этих механизмов. Системные вызовы Системные вызовы — это функциональный интерфейс программиста к ядру. Они представляют собой процедуры, которые находятся внутри ядра UNIX и поддерживают основные функции системы, подобно перечисленным в табл. 14.1. Системные вызовы могут быть сгруппированы в три главных категории, как показано на рис. 14.4.
Таблица 14.1. Общие системные вызовы UNIX Системный вызов Описание open() Открыть файл close() Закрыть файл read () /write () Выполнить ввод/вывод kill О Послать сигнал pipe() Создать конвейер socket() Создать сокет fork() Дублировать процесс exec() Перекрыть процесс exit() Завершить процесс Рис. 14.4. Основные подсистемы системного вызова Пользовательский режим и режим ядра Ядро содержит несколько структур данных, которые являются существенными для функционирования системы, включая: □ таблицу процессов, которая содержит один элемент для каждого процесса в системе; □ таблицу открытых файлов, которая содержит, по меньшей мере, один элемент для каждого открытого файла в системе. 23 Зак. 786
Указанные структуры данных находятся в пространстве памяти ядра, защищенном от пользовательских процессов системой управления памяти, которая будет описана позже. Поэтому пользовательские процессы не могут случайно разрушить эти важные структуры данных ядра. Процедуры системных вызовов отличаются от обычных функций, потому что они могут, хотя и в тщательно контролируемой манере, непосредственно управлять структурами данных ядра. Пользовательский процесс работает в специальном режиме машины, называемом пользовательским режимом. Этот режим предотвращает процесс от выполнения некоторых привилегированных машинных команд, включая те, которые позволили бы ему получать доступ к структурам данных ядра. Другой режим машины называется режимом ядра. Процесс в режиме ядра может выполнять любую машинную команду. Единственный способ для пользовательского процесса войти в режим ядра состоит в том, чтобы выполнить системный вызов. Каждому системному вызову назначен кодовый номер, начиная с 1. Например, системному вызову open о может быть назначен кодовый номер 1, а системному вызову close о — кодовый номер 2. Когда процесс осуществляет системный вызов, С-библиотека времени выполнения системного вызова помещает параметры системного вызова и его кодовый номер в некоторые регистры машины, а затем выполняет команду trap. Команда trap переключает машину в режим ядра и использует кодовый номер системного вызова а в качестве индекса в таблице векторов системных вызовов, расположенной в младших адресах памяти ядра. Таблица векторов системных вызовов — это массив указателей на код ядра для каждого системного вызова. Код, соответствующий индексированной функции, выполняется в режиме ядра, изменяя по мере необходимости структуры данных ядра, а затем исполняет специальную команду return, которая переключает машину назад в пользовательский режим и возвращается к коду пользовательского процесса. Когда я впервые изучал UNIX, то не понимал, почему был принят этот подход. Почему просто не используют клиент-серверную модель с серверным процессом ядра, который обслуживает системные вызовы от клиентских пользовательских процессов? Это позволило бы пользовательским процессам не выполнять непосредственно код ядра. Причина ясная и простая: скорость. В текущей архитектуре издержки обмена между процессами слишком велики, чтобы на практике сделать клиент-серверный подход приемлемым. Однако интересно обратить внимание, что некоторые из современных микроядерных систем принимают этот подход. С точки зрения программиста, использование системного вызова удобно: вы вызываете С-функцию с допустимыми параметрами, и функция возвращает управление, когда заканчивает обработку. Если происходит ошибка, функция возвращает — 1, и устанавливается глобальная переменная errno, чтобы
указать причину ошибки. Рис. 14.5 содержит диаграмму, которая иллюстрирует поток управления во время системного вызова. Пользовательский процесс Пользовательский result = open (7home/glass/file.txt", O_RDONLY)rn ► ... код — ►open (char* name, int mode) { «Поместить параметры в регистры» «Выполнить команду trap, переключиться на код ядра и режим ►ядра» «Вернуть результаты системного вызова» } Библиотека С времени выполнени Адрес в ядре closeQ ►Адрес в ядре ореп() — Адрес в ядре write() Таблица векторов системных вызовов ►Код ядра для ореп() { 1 «Манипулировать структурами данных ядра» «Вернуться к пользовательскому коду и режиму» Рис. 14.5. Пользовательский режим и режим ядра Код ядра системного вызова Синхронная обработка против асинхронной Когда процесс выполняет системный вызов, обычно он не может быть выгружен. Это означает, что планировщик не будет назначать ЦП другому процессу во время действия системного вызова. Однако некоторые системные вызовы требуют действий ввода/вывода от устройства, и эти действия могут потребовать определенное время для выполнения. Чтобы избежать простоя ЦП во время ожидания завершения операции ввода/вывода, ядро переводит ожидающий процесс в состояние сна и будит его снова только тогда, когда получит аппаратный сигнал прерывания о завершении вво-
да/вывода. Планировщик назначает ЦП не спящему процессу, а другим процессам, в то время как аппаратура обслуживает запрос ввода/вывода. Интересный результат применения способа, каким UNIX обрабатывает системные вызовы read о и write о, заключается в том, что пользовательские процессы чувствуют синхронное выполнение системного вызова, несмотря на то, что ядро демонстрирует асинхронное поведение. Это рассогласование проиллюстрировано на рис. 14.6. read () Процесс А Процесс А спит Возврат из read() read () Процесс В Процесс В спит Возврат из read() Время > Ядро Аппаратные прерывания Рис, 14.6, Синхронные и асинхронные события Прерывания Прерывание — это способ, которым аппаратные устройства уведомляют ядро, что им нужно выполнить определенную работу. Таким же образом, каким процессы конкурируют за время ЦП, аппаратные устройства конкурируют за обработку прерываний. Устройствам назначены приоритеты прерывания, базирующиеся на их относительной важности, как показано на рис. 14.7. Например, прерывание от системных часов, имеют более высокий приоритет, чем прерывания от клавиатуры.
Высший приоритет Низший приоритет Таблица векторов прерываний О 1 2 3 4 Указатели на обработчики прерываний ядра Рис. 14.7. Прерывания имеют приоритеты Когда происходит прерывание, текущий процесс приостанавливается, а ядро выясняет источник прерывания. Далее ядро проверяет свою таблицу векторов прерываний, расположенную в младших адресах памяти ядра, и находит код, который обрабатывает прерывания. Затем выполняется код обработчика прерывания. Когда обработчик прерывания завершается, возобновляется текущий процесс. Обработка прерывания проиллюстрирована на рис. 14.8. Текущим Приостановка ' Возобнавление процесс ----------------------------------------------------------------------> Время Обработчик | • прерывания | I клавиатуры | Обработка прерывания . « Выполнено Прерывание клавиатуры Рис. 14.8. Обработка прерывания Прерывание прерываний Обработка прерывания может быть также прервана! Если поступает прерывание более высокого приоритета, чем текущее, происходит последовательность событий, подобных нормальному процессу обработки прерываний, и
обработчик прерывания более низкого приоритета приостанавливается, пока обработчик прерывания более высокого приоритета не закончит обработку. Этот процесс показан на рис. 14.9. Текущий процесс Приостановка Возобнавление > Время Обработчик 1 прерывания I клавиатуры I Приостановка Возобнавление । ------------------------------—>1 I Обработчик * Выполнено I прерывания ' | таймера I I------------------ Выполнено Прерывание клавиатуры Прерывание таймера Рис. 14.9. Прерывания могут прерываться Если обрабатывается некоторое прерывание и происходит другое прерывание равного или более низкого приоритета, пришедшее прерывание игнорируется и отвергается, как показано на рис. 14.10. Обработчики прерываний поэтому созданы так, чтобы быть очень быстрыми, поскольку, чем быстрее они выполняются, тем меньше вероятность, что другое прерывание будет потеряно. Рис. 14.10. Прерывания могут игнорироваться
Большинство машин имеют команды, которые позволяют программе игнорировать все прерывания ниже некоторого уровня приоритета. Критические секции кода ядра защищают себя от прерываний, временно вызывая такие команды. Ниже приведен псевдокод, который делает только это: Оапретить почти все прерывания самого высокого приоритета> <Войти в критическую секцию кода> <Покинуть критическую секцию кода> восстановить все прерывания> Позже в данной главе будет приведен способ, которым периферийные устройства обрабатывают прерывания ядра, чтобы осуществлять эффективный ввод/вывод. Файловая система UNIX использует файлы для долговременного хранения и ОЗУ для кратковременного хранения. Программы, данные и текст — все хранится в файлах, которые в свою очередь обычно находятся на жестких дисках, но могут также размещаться и в других средах, например, на магнитных лентах и дискетах. Файлы UNIX организованы в виде иерархии обозначений, известных как структура каталогов. Файлы, отмеченные этими обозначениями, могут иметь три вида: □ обычные файлы, которые содержат последовательность байтов, которая соответствует коду или данным; к обычным файлам можно обратиться через стандартные системные вызовы ввода/вывода; □ каталоги, которые хранятся на диске в специальном формате и формируют основу файловой системы; к каталогам можно обратиться только через системные вызовы, ориентированные на каталог; □ специальные файлы, которые соответствуют периферийным устройствам, например, принтерам и дискам, и механизмам межпроцессной связи, ти
па конвейеров и сокетов; к специальным файлам можно обратиться через стандартные системные вызовы ввода/вывода. Концептуально UNIX-файл — это линейная последовательность байтов. Ядро UNIX не поддерживает более высокие порядки структуры файла, типа записей или полей. Это будет очевидно, если вы рассмотрите системный вызов iseeko, который позволяет помещать указатель файла только в терминах смещения байтов. Старые операционные системы имели тенденцию поддерживать структуры записей, так что UNIX в этом отношении довольно необычна. Давайте начнем наше изучение файловой системы UNIX с рассмотрения архитектуры наиболее общего аппаратного носителя файла — диска. Архитектура диска На рис. 14.11 представлена схема типичной архитектуры диска. Диск разбивается двумя способами: он ’разрезан" подобно пицце на области, называемые секторами, а они, в свою очередь, делятся на концентрические1 кольца, называемые дорожками. Индивидуальные области, ограниченные пересечением секторов и дорожек, называются блоками и формируют основную единицу хранения данных на диске. Типичный блок диска может хранить 4 Кбайт. Отдельная головка чтения/записи перемещается по неподвижной консоли, обращаясь к информации, т. к. диск вращается и его поверхность проходит под головкой. Специальный чип, называемый контроллером диска, перемещает головку чтения/записи в ответ на команды от драйвера дискового устройства, который является частью специального программного обеспечения, расположенного в ядре UNIX. Рис. 14.11. Архитектура диска 1 Имеющие общий центр. — Ред.
Имеется несколько разновидностей этой простой архитектуры диска. Многие драйверы диска фактически предназначены для нескольких жестких дисков, скомпонованных один над другим, как показано на рис. 14.12. В этих системах совокупность дорожек с одним и тем же индексом называется цилиндром. В большинстве мультидисковых систем консоли диска связаны друг с другом так, что все головки чтения/записи ходят синхронно, подобно расческе, перемещающейся по волосам. Поэтому головки чтения/записи таких дисковых систем перемещаются через цилиндры устройства. Некоторые сложные дисковые драйверы имеют отдельно управляемые головки чтения/записи. Одна головка на диск Рис. 14.12. Мупьтидисковая архитектура Замедление диска при Увеличение скорости доступе передачи на внешних к внешним дорожкам добавочные блоки Дорожках на внешних дорожках Уменьшение скорости передачи на внутренних дорожках Ускорение диска при доступе к внутренним дорожкам Рис. 14.13. Техника хранения данных на диске
Заметьте, что блоки на внешней дорожке больше, чем блоки на внутренней дорожке из-за способа, которым разделен диск. Если диск всегда вращается с одной и той же скоростью, то плотность данных относительно внешних блоков диска меньше, чем она могла быть. Таким образом, теряется потенциальная возможность для хранения данных. Некоторые из самых последних разработок драйверов диска пытаются поддержать плотность данных постоянной на всей поверхности диска путем увеличения числа блоков на внешних дорожках и замедления вращения диска или увеличения скорости передачи данных, когда головка перемещается к внешней стороне диска. Техника хранения данных на диске представлена на рис. 14.13. Чередование Когда читается последовательность пронумерованных подряд блоков, существует задержка чтения между блоками из-за накладных расходов на связь между диспетчером диска и драйвером устройства2. Поэтому смежные3 блоки логически размещаются на поверхности диска так, чтобы ликвидировать задержку. В этом случае головка находится над необходимой областью. Расстояние между блоками из-за этой задержки называется коэффициентом чередования. Рис. 14.14 демонстрирует два различных коэффициента чередования. Чередование 1:1 Рис. 14.14. Чередование диска Чередование 3:1 Хранение файла Допустим, размер блока равен 4 Кбайт, отдельный UNIX-файл размером 9 Кбайт требует трех блоков хранения: один, чтобы хранить первые 4 Кбайт, 2 Иными словами, пока осуществляется попытка чтения очередного блока, диск поворачивается и под головкой оказывается совсем не тот блок, что нужен. — Ред. 3 То есть, пронумерованные подряд. — Ред.
один, чтобы хранить следующие 4 Кбайт, и последний, чтобы хранить оставшийся 1 Кбайт4. Потеря дискового пространства из-за использования не до конца последнего блока в 4 Кбайт называется фрагментацией. Блоки файла редко расположены рядом и, как правило, разбросаны по всему пространству диска, как показано на рис. 14.15. Логический файл (9 Кбайт) Рис. 14.15. Блоки файла разбросаны Блочный ввод/вывод Ввод/вывод всегда делается в терминах блоков. Если производится системный вызов read () для чтения первого байта данных из файла, драйвер устройства делает запрос ввода/вывода к диспетчеру диска, чтобы прочитать первый блок из 4 Кбайт в буфер ядра, а затем копирует первый байт из буфера в ваш процесс. (Дополнительная информации о буферизации ввода/вывода представлена позже в данной главе.) Большинство контроллеров диска обслуживают запрос на один блок ввода/вывода за один раз. Когда контроллер диска заканчивает текущий запрос на ввод/вывод блока, он порождает аппаратное прерывание к драйверу устройства, чтобы сигнализировать, что текущий ввод/вывод завершен. В этом месте драйвер устройства обычно делает следующий запрос ввода/вывода блока. На рис. 14.16 представлена диаграмма, иллюстри 4 Некоторые файловые системы исправляют эту ситуацию, имея блок диска, содержащий последний элемент данных в файле. Таким образом, один блок диска может содержать фрагменты нескольких файлов.
рующая последовательность событий, которые могли бы происходить во время чтения 9 Кбайт. П ол ьзовател ьски й процесс read(fd, buf, 9216) Приостановка read() выполнен Рис, 14.16. Блочный ввод/вывод Индексный дескриптор или inode Чтобы хранить информацию о каждом файле, UNIX использует структуру, называемую inode (от англ, index node — узел индекса) или индексным дескриптором. Inode обычного файла или каталога содержит сведения о размещении их блоков на диске, a inode специального файла — информацию, которая позволяет идентифицировать периферийное устройство. Индексный дескриптор также хранит другую информацию, связанную с файлом, например, его флаги полномочий, владельца, группу и последнее время модификации. Inode имеет фиксированный размер и содержит указатели на блоки диска; также как и дополнительные косвенные указатели (для больших файлов). Каждому inode в конкретной файловой системе назначен уникальный номер, и каждый файл имеет точно один inode (рис. 14.17). Все inode, связанные с файлами на диске, хранятся в специальной области в начале диска, называемой списком inode.
Inode Рис. 14.17. Каждый файл имеет inode Содержимое inode Внутри каждого inode содержится следующая информация о файле: □ тип файла: обычный, каталог, специальный блочный, специальный символьный и т. д.; □ полномочия файла; П ID владельца и ID группы; □ счетчик жестких связей (описанный позже в главе); □ последнее время модификации и последнее время доступа; □ сведения о размещении блоков для обычного файла или каталога; □ старший и младший номера устройства, если это специальный файл; □ значение символической связи, если это символическая связь.
Другими словами, inode содержит всю информацию, которую вы видите, когда исполняете команду 1s -1, исключая имя файла. Карта блоков Только сведения о размещении первых 10 блоков файла хранятся непосредственно в inode. Большинство UNIX-файлов имеют размер меньше 40 Кбайт, так что этого достаточно в большинстве случаев. Для адресации больших файлов используется косвенная схема доступа. В этой схеме отдельный пользовательский блок предназначен для хранения данных о расположении до 1024 пользовательских блоков. Когда блок используется этим способом, он называется косвенным (рис. 14.18). Его местоположение хранится в inode и применяется для адресации следующих 1024 блоков. Такой подход позволяет адресовать файлы до 4 Мбайт. Inode Блоки диска Рис. 14.18. Отдельный косвенный блок Для файлов больше 4 Мбайт используется двойная косвенная схема доступа. При этом пользовательский блок служит для хранения сведений о расположении до 1024 других косвенных блоков, каждый из которых указывает максимум на 1024 пользовательских блока (рис. 14.19). Inode хранит местоположение двойного косвенного блока.
Inode Блоки диска Рис. 14.19. Двойной косвенный блок Обратите внимание, когда файл становится больше, увеличивается количество косвенных ссылок, требуемое для получения доступа к конкретному блоку. Эти издержки минимизируются путем буферизации содержимого inode и содержимого ссылающихся косвенных блоков в ОЗУ. Механизм буферизации описан позже в данной главе. Размещение файловой системы Первый логический блок диска назван загрузочным блоком и содержит неко-торый исполняемый код, который используется при загрузке UNIX в первый раз. (Для дополнительной информации см. главу 15.) Второй логический блок известен как суперблок и содержит информацию непосредственно о диске. После него следует набор блоков фиксированного размера, называемых списком inode, который хранит все индексные дескрипторы, связанные с файлами на диске.
Каждый блок в списке inode обычно может хранить приблизительно 40 индексных дескрипторов (хотя это изменяется в различных версиях UNIX). Оставшиеся блоки на диске доступны для размещения блоков файла и содержат как каталоги, так и пользовательские файлы. Расположение блоков диска показано на рис. 14.20. Физическая компоновка диска О 1 2 3 4 200 202 Загрузочный блок Суперблок Inodes 1—40 Inodes 41—80 Inodes 81—120 Пользовательский блок Пользовательский блок Пользовательский блок Пользовательские блоки Блоки содержат inodes, которые описывают файлы 20000 Ползовательский блок Рис. 14.20. Использование блоков диска Суперблок Суперблок содержит информацию, имеющую отношение ко всей файловой системе. Он включает битовый массив свободных блоков, как показано на рис. 14.21. Битовый массив — это линейная последовательность битов, один на блок диска. Значение 1 указывает, что соответствующий блок свободен, а значение 0 — используется. Суперблок содержит следующую информацию: □ общее число блоков в файловой системе; П количество индексных дескрипторов в списке свободных inode; □ размер блока в байтах; П количество свободных блоков; □ количество используемых блоков.
Битовый массив Рис. 14.21. Битовый массив свободных и занятых блоков Bad-блоки Диск всегда содержит несколько блоков, которые по той или другой причине непригодны для использования. Утилита, которая создает новую файловую систему, описанная в главе 15, также создает отдельный файл "наихудшего кошмара", составленный из всех плохих блоков на диске; и записывает местоположение всех этих блоков в inode с номером 1. Что предотвращает выделение этих блоков другим файлам. Каталоги Inode с номером 2 содержит сведения о размещении блоков, хранящих корневой каталог. Каталог UNIX содержит список связей между именем файла и номером inode. Когда создается каталог, в нем автоматически назначаются элементы для ".." (его родительского каталога) и "." (самого каталога). Поскольку пара <имя файла, номер inode> эффективно связывает имя с файлом, эти связи названы жесткими связями. Поскольку имена файлов хранятся в блоке каталога, они не хранятся в inode файла. Фактически не имело никакого смысла хранить имя в inode, поскольку файл может иметь больше, чем одно имя. Соответственно, правильнее рассматривать иерархию каталогов как иерархию обозначений файлов, а не иерархию файлов. Все версии UNIX позволяют имени файла иметь, по крайней мере, длину 14 символов, а большинство поддерживает имя длиной до 255 символов. На рис. 14.22 приведена иллюстрация корневого inode, соответствующего обычному корневому каталогу. Номера inode, связанные с каждым именем файла, обозначены нижними индексами.
Inode для корневого каталога (номер 2) Жесткая связь Рис. 14.22. Корневой каталог связан с inode 2 Преобразование путевых имен в номера inode Системные вызовы типа open () должны получить inode файла из его путевого имени. Они выполняют преобразование следующим образом: 1. Определяется inode, с которого следует начинать поиск путевого имени. Если путевое имя является абсолютным, поиск начинается с inode 2. Если путевое имя относительное, поиск начинается с inode, соответствующего текущему рабочему каталогу процесса. (Для дополнительной информации см. разд. "Управление процессами" далее в этой главе.) 2. Компоненты путевого имени обрабатываются слева направо. Каждый компонент кроме последнего должен соответствовать либо каталогу, либо символической связи. Будем называть inode, с которого начинается поиск путевого имени, текущим inode. 3. Если текущий inode соответствует каталогу, производится поиск текущего компонента путевого имени в каталоге, соответствующем текущему inode. Если он не найден, возникает ошибка; иначе значение текущего номера inode становится номером inode, связанным с компонентом путевого имени, который был обнаружен. 4. Если текущий inode соответствует символической связи, то путевое имя до текущего компонента пути и сам компонент заменяются содержимым символической связи, и путевое имя повторно обрабатывается. 5. Inode, соответствующий заключительному компоненту путевого имени, является индексным дескриптором файла, упомянутого полным путевым именем.
Пример преобразования путевого имени в inode Чтобы проиллюстрировать алгоритм, представленный в предыдущем разделе, опишем шаги, необходимые для перевода путевого имени /usr/test.c в номер inode. Номер inode 1 Номер блока Номер блока Полномочия 200 dr-xr-xr-x 201 dr-xr-xr-x 202 dr-xr-xr-x 203 -r-xr-xr-x 204, 206 -rwxr-xr-x 205 -r-xr-xr-x 2 usr 201 Is ср 202 test.c > 203 2 3 4 3 2 4 5 2 Дескриптор Bad-блоков Корневой дескриптор Is выполняемый Определенный номер inode Иерархия > 204 > 205 > 206 teste, первый блок ср выполняемый teste, второй блок usr4 bin3 ср7 test.c6 2 3 4 5 6 8 > 200 . bin
Рис. 14.23 содержит схему, которая иллюстрирует процесс перевода. На ней путь перевода обозначен жирными линиями, а цель обведена кружком. Вот логика, которую использует ядро: 1. Путевое имя абсолютно, таким образом, текущий номер inode равен 2. 2. В каталоге, соответствующем inode 2, происходит поиск компонента путевого имени usr. Соответствующий элемент найден, и текущий номер inode устанавливается на 4. 3. В каталоге, соответствующем inode 4, выполняется поиск компонента путевого имени test.c. Соответствующий элемент найден, и текущий номер inode устанавливается на 6. 4. test.c — заключительный компонент путевого имени, так что алгоритм возвращает номер inode 6. Как вы можете видеть, происходят перемещения при переводе между inode и блоками каталога, пока путевое имя не будет полностью обработано. Монтирование файловых систем Когда UNIX стартует, иерархия каталогов соответствует файловой системе,, расположенной на единственном диске, называемом корневым устройством. UNIX позволяет создать файловые системы на других устройствах и подключить их к первоначальной иерархии каталогов с помощью механизма, называемого монтированием. Утилита mount позволяет привилегированному пользователю (root) встроить корневой каталог файловой системы в существующую иерархию каталогов. Как правило, иерархия большой UNIX-системы распределена по многим устройствам, каждое содержит поддерево полной иерархии. Например, поддерево /usr обычно хранится на устройстве, отличном от корневого устройства. Некорневые файловые системы обычно автоматически монтируются во время загрузки. (Для дополнительной информации см. главу 15.) Например, предположим, что файловая система хранится на устройстве /dev/flp дискеты. Для подключения ее к каталогу /mnt главной иерархии можно выполнить команду $ mount /dev/flp /mnt На рис. 14.24 проиллюстрирован эффект работы этой команды. Файловые системы могут быть отделены от главной иерархии при помощи утилиты umount. Команды $ umount /dev/flp ИЛИ $ umount /mnt отключили бы файловую систему, хранящуюся на /dev/flp.
После монтирования / Перед монтированием Сращивание Рис. 14.24. Монтирование каталогов bin usr mnt tmp1 tmp2 Система ввода/вывода файлов Чтобы узнать подробности о реализации в ядре системы ввода/вывода файлов, см. разд. "Ввод/Вывод" далее в этой главе. Управление процессами В этом разделе описывается способ, которым ядро разделяет ЦП и ОЗУ между конкурирующими процессами. Область ядра, которая разделяет ЦП, называется планировщиком, а область ядра, разделяющая ОЗУ, называется менеджером памяти. Раздел также содержит информацию об ориентированных на процесс системных вызовах, включая ехес о, fork о и exit о. Ради простоты не будем интересоваться нитями ядра (нити, которые работают в режиме ядра), т. к. большинство программистов, создающих приложения, не имеют никакой возможности их использовать. Однако следует знать, что так же как пользовательское приложение может выполнять многопоточные задачи, некоторые модули ядра (подобно драйверам устройств) могут выполняться многопоточно. Исполняемые файлы Когда исходный код программы компилируется, он сохраняется в специальном формате на диске. Первые несколько байтов файла известны как волшебное число (magic number) и используются ядром, чтобы идентифицировать тип исполняемого файла. Например, если первые два байта файла — символы ядро идентифицирует исполняемый файл, как содержащий текст shell, и вызывает shell, чтобы выполнить этот текст. Другая последова
тельность идентифицирует файл, как правильный загружаемый образ, содержащий машинный код и данные. Этот вид файла разделяется на несколько секций, содержащих код или данные, с отдельной секцией заголовка для каждой секции. Заголовки используются ядром для подготовки системы управления памятью. На рис. 14.25 приведена иллюстрация типичного исполняемого файла. Главный заголовок, включая волшебное число Заголовок первой секции Заголовок второй секции Секция 1 Секция 2 Рис. 14.25. Компоновка выполняемого файла Первые процессы UNIX выполняет программу, создавая процесс и связывая его с исполняемым файлом. Достаточно неожиданно, но нет никакого системного вызова, который предписывает "создать новый процесс, чтобы выполнить программу X". Вместо этого следует продублировать существующий процесс, а затем связать недавно созданный дочерний процесс с исполняемым файлом X. Первый процесс с ID процесса (PID), равным 0, создается UNIX во время загрузки. Этот процесс немедленно дважды выполняет системные вызовы fork () и ехес (), создавая два процесса с PID 1 и 2. В System V UNIX имена этих нескольких первых процессов подобны показанным на в табл. 14.2. Назначение этих процессов будет описано позже в данной главе. Остальные процессы в системе — это потомки процесса init. (Для дополнительной информации о последовательности загрузки см. главу 15.)
Таблица 14.2. Первые процессы, стартующие в системе UNIX PID Имя 0 sched 1 init 2 pageout Процессы ядра и пользовательские процессы Большинство процессов работают в пользовательском режиме, за исключением осуществления ими системных вызовов, когда они временно переключаются в режим ядра. Однако процессы-демоны sched (PID 0) и pageout (PID 2) из-за их важности выполняются постоянно в режиме ядра и называются процессами ядра. В отличие от пользовательских процессов их код связан непосредственно с ядром и не находится в отдельном исполняемом файле. Кроме того, процессы ядра никогда не выгружаются. Иерархия процессов Когда процесс дублируется, используя системный вызов fork(), первоначальный процесс известен, как родитель дочернего процесса, init (PID 1) является процессом, от которого происходят все пользовательские процессы. Поэтому родительские и дочерние процессы связаны в иерархии с процессом init, находящимся в корне. Рис. 14.26 иллюстрирует иерархию процессов, включающую четыре процесса. Процесс 1 fork()/exec() fork()/exec() I I Процесс 48 Процесс 12 fork()/exec() Процесс 34 Рис. 14.26. Иерархия процессов
Состояния процесса Каждый процесс в системе может находиться в одном из шести состояний: □ работающий (running), т. е. процесс в настоящее время использует ЦП; □ готовый (runnable), т. е. процесс может использовать ЦП, как только ЦП становится доступным; □ спящий (sleeping), т. е. процесс ожидает, чтобы произошло событие; например, если процесс выполняет системный вызов read (), он спит, пока не закончится запрос ввода/вывода; □ приостановленный (suspended), т. е. процесс был "заморожен" сигналом типа sigstop. Он возобновится, лишь когда будет послан сигнал sigcont. Например, нажатие комбинации клавиш <Ctrl>+<Z> приостанавливает все приоритетные процессы; □ ожидающий (idle), т. е. процесс создается при помощи системного вызова fork () и еще не является готовым; □ зомбированный (zonibified), т. е. процесс закончился, но еще не вернул код возврата своему родителю. Процесс остается зомби, пока его родитель не примет код возврата через системный вызов wait о . На рис. 14.27 приведена диаграмма, иллюстрирующая возможные изменения состояния, которые могут происходить во время жизни процесса. Рис. 14.27. Состояния процесса
Состав процесса Каждый процесс составлен из нескольких различных частей: □ область кода (code area) содержит исполняемую (текстовую) порцию процесса; □ область данных (data area) используется, чтобы хранить статические данные; □ область стека (stack area) служит для хранения временных данных; □ область пользователя (user area) хранит вспомогательную информацию о процессе; □ таблицы страниц (page tables) предназначены для системы управления памятью. Использование первых трех областей должно быть знакомо читателю. Таблицы страниц будут обсуждаться позже. Следующий раздел содержит описание пользовательской области. Состав процесса показан на рис. 14.28. Рис. 14.28. Состав процесса Область пользователя Каждый процесс в системе имеет некоторую ассоциированную с ним "вспомогательную" информацию, которая используется ядром для управления процессом. Эта информация хранится в структуре данных, называемой областью пользователя. Каждый процесс имеет свою собственную область
пользователя, которая создана в области данных ядра и доступна только ядру. Пользовательские процессы не могут получить доступ к их пользовательским областям. Поля в области пользователя процесса хранят запись: □ о том, как процесс должен реагировать на каждый вид сигнала; □ об открытых дескрипторах файла процесса; □ о том, сколько времени ЦП процесс недавно использовал. Содержание области пользователя более подробно описано позже в данной главе. Таблица процессов Существует отдельная структура данных ядра фиксированного размера, называемая таблицей процессов, которая содержит один элемент для каждого процесса в системе. Таблица процессов Рис. 14.29. Таблица процессов Таблица процессов создается в области данных ядра и доступна только ядру. Ее элемент содержит следующую информацию о каждом процессе: □ ID процесса (PID) и ID родительского процесса (PPID); □ реальный и эффективный пользовательский ID (UID) и ID группы (GID);
□ состояние процесса (работающий, готовый, спящий, приостановленный, ожидающий или зомби); □ местоположение кода, данных, стека и областей пользователя; □ список всех ожидаемых сигналов. На рис. 14.29 приведена таблица процессов, которая возникла бы из маленькой иерархии процессов, представленных на рис. 14.26. Она предполагает, что процесс с PID 48 в настоящее время ожидает завершения ввода/ вывода. Планировщик Ядро ответственно за разделение времени ЦП среди конкурирующих процессов. Секция кода ядра, называемая планировщиком, выполняет эту обязанность и поддерживает специальную структуру данных, называемую очередью многоуровневых приоритетов, которая позволяет ему эффективно планировать процессы. Очередь приоритетов — это связанный список готовых процессов, которые имеют одинаковые приоритеты. Способ, которым ядро вычисляет приоритет процесса, обсуждается позже. Время ЦП выделяется процессам пропорционально их важности и измеряется в единицах фиксированного размера, называемых квантом времени (time quanta). В большинстве систем каждый временной квант равен 1/10 секунды. Рис. 14.30 иллюстрирует очереди совместно с таблицей процессов, основанной на маленькой иерархии процессов, которая приведена на рис. 14.26. Таблица процессов Рис. 14.30. Таблица процессов и очереди приоритетов
Правила планирования Ниже приведены правила, которые описывают способ работы планировщика. □ Каждую секунду планировщик вычисляет приоритеты всех готовых процессов в системе и объединяет их в несколько очередей приоритета. Очереди формируются на основе значения приоритета процесса. □ Каждую 1/10 секунды планировщик выбирает процесс самого высокого приоритета в очередях приоритета и выделяет ему ЦП (если в настоящее время работающий процесс не находится в режиме ядра). □ Если процесс все еще готов и находится в конце своего кванта времени, он помещается в конце своей очереди приоритета. □ Если процесс засыпает в течение своего кванта времени, планировщик немедленно выбирает другой процесс для выполнения и выделяет ему ЦП. □ Если процесс возвращается из системного вызова во время его кванта и процесс более высокого приоритета готов работать, процесс более низкого приоритета выталкивается процессом более высокого приоритета. □ При каждом аппаратном прерывании таймера (которое обычно происходит 100 раз в секунду) увеличивается счетчик импульсов времени процесса. Каждый четвертый импульс планировщик повторно вычисляет значение приоритета процесса. Он стремится уменьшить приоритет процесса во время его кванта. Формула для вычисления приоритета процесса может быть приблизительно сформирована следующим образом: Приоритет = (Последнее_исполъзование_ЦП) / константа + + (Базовый-Приоритет) + (Точная_установка), где Базовый-Приоритет — это пороговый приоритет, а Точная_установка — это значение, устанавливаемое системным вызовом nice (). Данная формула обеспечивает уменьшение приоритета процесса, если он использует много времени ЦП в конкретном "окне" времени. Это также гарантирует, что процессы, имеющие высокую Точную-установку, будут иметь более низкий приоритет. Последствие формулы заключаются в том, что диалоговые процессы будут иметь хорошее время ответа: Так как диалоговый процесс ждет нажатия клавиши пользователем, он не использует время ЦП, и поэтому его уровень приоритета быстро повышается. Акт переключения с одного процесса на другой назван переключением контекста (context switch). Чтобы "заморозить" процесс, ядро сохраняет счетчик программы, указатель стека и другие важные детали процесса в области пользователя процесса. Чтобы "разморозить" процесс, ядро восстанавливает информацию из указанной области.
В результате соблюдения перечисленных выше правил каждую секунду ЦП выделяется циклическим методом процессам из непустой очереди с самым высоким приоритетом. В конце каждой секунды процессы повторно помещаются в очереди в зависимости от их новых приоритетов, й циклическое назначение повторяется. На рис. 14.31—14.33 представлены некоторые иллюстрации правил планирования. Работает кажую секунду ——| Пересчитывает приоритеты всех процессов Рис. 14.31. Каждую секунду Работает каждую 1/10 секунды I Выделяет процесс с наивысшим приоритетом в рабочей очереди 1 Выполняет его, пока что-нибудь не станет истинным: 1. Конец временного кванта. 2. Он засыпает. 3. Он возвращается из системного вызова и процесс высшего приоритета готов выполняться. I Если процесс остается готовым, поместить в конец его рабочей очереди
Работает каждый импульс таймера I Добавить единицу к счетчику импульсов таймера процесса Пересчитать приоритет текущего процесса, если накопилось четыре импульса таймера Рис. 14.33. Каждый импульс таймера Управление памятью В дополнение к управлению планированием ядро отвечает за разделение ОЗУ между процессами безопасным и эффективным способом. Следующие немногие разделы описывают систему управления памятью UNIX.5 Страницы памяти Система управления памятью UNIX позволяет выполняться процессам, которые больше полной емкости ОЗУ системы. Чтобы достичь этого, она делит области ОЗУ, кода, данных и стека на куски памяти фиксированного размера, называемые страницами (pages), аналогично способу, каким диск разделен на блоки фиксированного размера. Размер страницы памяти обычно устанавливается равным размеру блока диска. Причина этого соотношения скоро станет очевидной. Только страницы процесса, к которым в настоящее время обращаются или обращались недавно, хранятся в страницах ОЗУ; остальное размещается на диске. Таблицы страниц и области Области кода, данных и стека процесса не должны находиться в логически смежной памяти. Например, компилятор мог бы сгенерировать программу, 5 Реализация управления памятью UNIX изменяется от версии к версии. Алгоритм, описанный здесь, ориентирован на BSD.
чей код, данные и стек занимают логические области адресного пространст- ва, приведенного в табл. 14.3. Таблица 14.3. Пример компоновки памяти Область Логический адрес Код 0—15 Кбайт Данные 64—72 Кбайт Стек 64—72 Кбайт Таблица страниц кода Таблица процессов Область пользователя Таблица областей На таблицу / страниц кода На таблицу страниц данных На таблицу страниц стека \ Другая информация процесса Текущий каталог, значение umask, ожидающие сигналы, управляющий терминал и т. д. Таблица страниц стека К страницам кода в ОЗУ/диске Таблица страниц данных К страницам данных в ОЗУ/диске * К страницам стека в ОЗУ/диске Рис. 14.34. Область пользователя, таблицы областей и страниц Каждая часть смежного логического адресного пространства называется областью (region); поэтому большинство процессов имеет три области. Страницы областей не должны храниться в ОЗУ рядом. Каждая область имеет связанную структуру данных, называемую таблицей страниц, которая хранит
сведения о размещении каждой из ее страниц. Таблицы страниц процесса объединяются в области данных ядра и доступны только ядру. Местоположения таблиц страниц процесса хранятся в области пользователя процесса. Таблица страниц в системе управления памятью аналогична inode в файловой системе, т. к. каждый из них отслеживает местоположение индивидуальных единиц хранения. Рис. 14.34 иллюстрирует таблицу процессов, область пользователя и таблицы страниц. Таблица ОЗУ Менеджер памяти выделяет страницы ОЗУ процессу, только когда они ему необходимы. Единственная фиксированного размера структура данных ядра, называемая таблицей ОЗУ (RAM table), записывает информацию о каждой странице ОЗУ, например, используется ли страница в настоящее время и заблокирована ли она в памяти. Заблокированные страницы никогда не перемещаются на диск; например, все страницы, которые содержат ядро UNIX, заблокированы. Загрузка исполняемого файла: ехес() Когда процесс выполняет ехес (), ядро выделяет таблицы страниц для областей кода, данных и стека процесса. В этот момент весь код и инициализированные данные находятся на диске в исполняемом файле, поэтому элементы таблицы страниц данных и кода установлены так, что содержат сведения о расположении их соответствующих блоков диска. Эти сведения извлекаются из inode и заголовка исполняемого файла. Когда процесс впервые получает доступ к одной из страниц, его соответствующий блок копируется с диска в ОЗУ, и элемент таблицы страниц обновляется физическим номером страницы ОЗУ. Стек и неинициализированные области данных не имеют соответствующего места на диске. Поэтому ядро помечает их элементы таблицы страниц, как zeroed (пусто). Когда к zeroed-странице обращаются впервые, ядро выделяет страницу ОЗУ и заполняет ее нулями без загрузки чего-нибудь с диска. Оно затем обновляет элемент таблицы страниц физическим номером страницы ОЗУ. Предположим, что первые восемь страниц ОЗУ были первоначально свободны. На рис. 14.35 приведена иллюстрация расположения памяти процесса Сразу ПОСЛе ВЫПОЛНеНИЯ ехес ().
Номер Диск Таблица блока Рис. 14.35. Схема памяти сразу после выполнения ехес () Список свободных блоков Преобразование адреса Все логические адреса, которые перемещаются по аппаратной шине адреса из процесса, должны быть отображены в физический адрес на основе информации, содержащейся в таблице страниц и областей процесса. Этому переводу (трансляции) помогает специальная часть аппаратуры, называемая устройством управления памятью (memory management unit, MMU). Предположим, что каждая страница ОЗУ имеет размер 4 Кбайт, и все адреса являются 32-битовыми значениями. MMU работает следующим образом: I. Когда процесс запланирован, в MMU устанавливаются несколько определенных аппаратных регистров, чтобы указать на область процесса и 24 Зак. 786
таблицы страниц. MMU использует эти регистры для доступа к структурам данных во время процесса перевода адреса. 2. Когда адрес появляется на аппаратной шине адреса, MMU активизируется и начинает процесс перевода. Будем называть поступающий адрес ADDR. 3. Затем MMU определяет, какой области принадлежит поступающий адрес ADDR — коду, данным или стеку. 4. Тогда MMU вычитает стартовый виртуальный адрес (SVA) области от поступающего адреса ADDR. В результате получается смещение поступающего адреса от начала области (OSR). 5. OSR разбивается на две части. Старшие 20 битов соответствуют номеру страницы региона (RPN) поступающего адреса, а младшие 12 битов равны смещению внутри страницы области (ORP). 6. Затем MMU обращается за справкой к таблице страниц области, чтобы определить текущее местоположение логической страницы RPN. Если в настоящее время страница находится в ОЗУ, поступающий логический адрес переводится в физический адрес, заменяя логический номер страницы физическим номером ОЗУ. Если страница не в ОЗУ, MMU не пытается переводить логический адрес, генерирует прерывание допустимости страницы и обрабатывает другие поступающие логические адреса. 7. Когда UNIX получает прерывание допустимости страницы, она генерирует запрос ввода/вывода, чтобы загрузить страницу с диска в свободную страницу ОЗУ. После загрузки соответствующий элемент таблицы страниц обновляется номером страницы ОЗУ, и перевод адреса начинается снова. Иллюстрация алгоритма MMU На рис. 14.36 приведена иллюстрация алгоритма отображения MMU. MMU и таблица страниц Каждый элемент таблицы страниц содержит множество полей, которые используются различными составляющими системы управления памятью. Некоторые из этих полей устанавливаются автоматически MMU при определенных обстоятельствах: □ бит модификации (modified bit) устанавливается, когда процесс пишет страницу; □ бит обращения (referenced bit) устанавливается, когда процесс читает из страница или пишет в нее.
Рис. 14.36. Упрощенный алгоритм управления памятью Следующие дополнительные поля автоматически используются MMU при переводе поступающего логического адреса: □ если бит применимости (valid bit) установлен, MMU заменяет логический номер страницы поступающего адреса полем физического номера страницы} □ если бит применимости не установлен, MMU генерирует ошибку страницы; □ если бит копирования при записи (сору-оп-write bit) установлен, и процесс пытается изменить страницу, MMU генерирует ошибку страницы независимо от состояния бита применимости. Схема памяти после первой команды Выполнение ехес о заставляет первую команду исполняемого файла быть выбранной из памяти, а команда, в свою очередь, предписывает MMU генерировать ошибку на первой странице. Адрес первой команды хранится в
заголовке исполняемого файла и, как правило, является младшим адресом памяти. В диаграмме, показанной на рис. 14.37, сделано предположение, что первая команда была расположена по логическому адресу 0, и страница О области кода переводится в физическую страницу О ОЗУ. Таблица Номер Диск блока страниц кода о О Область пользователя Смещение Таблица областей Таблица процессов О Кбайт 16 Кбайт 64 Кбайт Код Данные Стек MMU-доступ к информации области Номер _ страницы Таблица страниц стека пусто пусто пусто пусто Аппаратная шина О х 00000000 Рис. 14.37. Схема памяти после выполнения первой команды Страница ОЗУ О Блок диска 6 Блок диска 4 Блок диска 9 Блок диска 7 z Блок диска 8 Таблица страниц данных 1 2 3 5 6 8 9 3 4 5 6 7 1 2 Таблица ОЗУ Список свободных блоков 1 2 3 О 1 2 з о О 1 Схема памяти после нескольких команд Когда процесс продолжает выполняться после ехесо, он возвращает ошибки доступа к большинству его страниц кода, данных и стека. Диаграмма,
показанная на рис. 14.38, иллюстрирует ситуацию, в которой все физические страницы ОЗУ были заполнены единственным процессом. Это никогда не может случиться в реальной системе UNIX, т. к. ядро занимает младшие адреса ОЗУ, и несколько других процессов-демонов будут всегда размещаться в старших адресах ОЗУ. Но диаграмма показывает, как таблицы страниц процесса постепенно заполняются адресами ОЗУ. Таблица Номер блока страниц кода Область пользователя Таблица процессов Смещение Таблица у областей / 0 Кбайт Код 1 16 Кбайт Данные - 64 Кбайт Стек । О 1 2 3 Таблица страниц данных 1 2 3 Страница ОЗУ О Страница ОЗУ 2 J г ► Блок диска 4 + Страница ОЗУ 1 J Диск Номер страницы ОЗУ Страница ОЗУ 3 MMU-доступ к информации области Блок диска 8 Страница ОЗУ 5 Страница ОЗУ 4 Страница ОЗУ 7 Страница ОЗУ 6 Таблица страниц стека о 1 2 3 4 5 6 7 Таблица ОЗУ Список свободных блоков Аппаратная шина О X 00005064 О О 1
Демон страниц Диаграмма на рис. 14.38 иллюстрирует ситуацию, в которой все физические страницы ОЗУ заполнены. Если возникает ошибка еще на одной из страниц процесса, система могла бы сохранить одну или несколько страниц ОЗУ на диске, чтобы создать место для поступающей страницы. Практически разработки получаются намного лучше, когда система управления памятью всегда имеет некоторое количество страниц ОЗУ свободными для последующих ошибок. Минимальное число страниц, которые она пробует держать свободными, называется низшей точкой (low-water mark). Когда число свободных страниц ниже этого уровня, система управления памятью побуждает процесс, называемый демоном страниц (page daemon) или ретранслятором страниц (page stealer), освободить некоторые страницы ОЗУ. Демон страниц использует особый алгоритм, чтобы сохранить страницы в специальной области диска, называемой пространством обмена (swap space), пока число свободных страниц не превысит высшую точку (high-water mark). Тогда демон страниц засыпает, пока снова не понадобится. Пространство обмена Пространство обмена (swap space) — это специальная непрерывная область диска, расположенная отдельно и предназначенная для эффективной передачи страниц в ОЗУ и из ОЗУ. Хотя пространство обмена может находиться на корневом устройстве, оно часто размещается на отдельном диске так, чтобы обычный доступ к файлу и страничная подкачка могли происходить одновременно. Пространство обмена поддерживается специальной структурой ядра данных, называемой картой обмена (swap map), которая служит для отслеживания использования ее блоков. Карта обмена ищет свободные смежные блоки в пространстве обмена и обновляет их всякий раз, когда пространство обмена выделяется или освобождается. Когда два соседних участка памяти пространства обмена становятся свободными, карта обмена автоматически объединяет их в отдельный, больший участок памяти свободного пространства. Алгоритм демона страниц Каждый элемент таблицы страниц включает три поля, называемые битом модификации (modified bit), битом обращения (Referenced bit) и возрастом (age). Всякий раз, когда процесс получает доступ к конкретной странице, ее бит обращения устанавливается, а поле возраста сбрасывается в ноль. Демон
страниц (page daemon) использует эти два поля, чтобы освобождать наиболее давние по применению страницы. Он проходит циклически через каждую таблицу страниц в системе, выполняя следующие действия: если бит обращения страницы установлен, он повторно устанавливает его и сбрасывает поле возраста в ноль; иначе, увеличивает поле возраста. Поля возраста страниц, к которым в настоящее время обращаются, едва ли увеличатся вообще, т. к. они непрерывно повторно обнуляются. Однако поля возраста страниц, которые являются бездействующими, продолжают расти. Когда поле возраста достигает некоторого, зависящего от системы, значения, демон страниц пытается освободить страницу, используя следующие правила: □ если страница никогда не перемещалась на устройство обмена, она помещается в список страниц, которые нужно переместить, и ее элемент таблицы ОЗУ отмечается как "готов к перемещению"; □ если страница ранее перемещалась и не изменялась с тех пор, ее бит применимости (valid bit) повторно устанавливается, а соответствующий элемент таблицы ОЗУ немедленно отмечается как "свободный" и помещается в список свободных страниц; □ если страница прежде перемещалась и с тех пор была изменена, она помещается в список страниц, которые нужно переместить, а соответствующий элемент таблицы ОЗУ отмечается как "готов к перемещению", и ее предыдущая область пространства обмена освобождается. Когда список страниц для перемещения достигает некоторого размера, ядро выделяет подходящий участок памяти пространства обмена, консультируясь с картой обмена, и затем намечает страницы, которые будут записаны в пространство обмена. Когда страница записана, ее бит применимости повторно устанавливается, а элемент таблицы ОЗУ отмечается как "свободный" и помещается в список свободных. В результате этого алгоритма наиболее "старые" страницы постепенно перемещаются в пространство обмена, пока количество свободных страниц не превысит высшую точку. Схема памяти после удаления нескольких страниц Диаграмма, приведенная на рис. 14.39, иллюстрирует состояние карты памяти процесса после того, как страница кода 1, страница данных 0 и страница стека 1 были перемещены в пространство обмена.
Диск Таблица Номер блока Номер блока Пространство обмена Список свободных блоков Рис. 14.39. Схема памяти после удаления нескольких страниц Доступ к странице, которая хранится в пространстве обмена Когда MMU пытается получать доступ к странице, чей бит применимости не установлен, оно генерирует ошибку страницы. Перед запросом этой страницы для чтения с диска ядро проверяет, чтобы удостовериться, находится ли страница, освобожденная демоном страниц, но еще не перекрыта другой страницей, все еще в ОЗУ. Оно может сделать это быстро, потому что поддерживает таблицу мусора (hash table), которая связывает адреса блоков диска с номерами страниц ОЗУ. Если ядро выясняет, что страница все еще присутствует в ОЗУ, оно просто модернизирует элемент таблицы страниц и
устанавливает бит применимости. Если страница не найдена в ОЗУ, возможен один из двух.исходов: □ если страница никогда не была загружена в ОЗУ, ядро выполняет запрос, чтобы страница была загружена из исполняемого файла; □ если страница хранится в пространстве обмена, ядро осуществляет запрос, чтобы страница была загружена с устройства обмена. Одно последствие этого алгоритма заключается в том, что страница загружается только однажды из исполняемого файла; затем она проводит остальную часть своей "жизни”, путешествуя между пространством обмена и ОЗУ. Диаграмма, приведенная на рис. 14.40, иллюстрирует это поведение. / Исполняемый --► ОЗУ Страница входящая Пространство обмена Страница выходящая Рис. 14.40. Жизненный цикл страницы Дублирование процесса: fork() Когда процесс ветвится, дочернему процессу должна быть выделена родительская копия областей кода, данных и стека. К сожалению, процесс часто немедленно за системным вызовом fork о выполняет ехес о, таким образом, освобождая его предыдущие области памяти. Чтобы избежать любого ненужного и дорогостоящего копирования, подсказываемого этими наблюдениями, ядро обрабатывает fork о особым способом, описанным ниже. □ Ядро устанавливает элемент области кода дочернего процесса, чтобы тот указывал на таблицу страниц кода родителя, и увеличивает счетчик ссылок, связанный с таблицей страниц для того, чтобы показать, что она разделяется. □ Ядро создает таблицу страниц данных и таблицу страниц стека для дочернего процесса, которые являются дубликатами таблиц родителя, и устанавливает бит копирования при записи (сору-оп-write bit) для каждого элемента таблицы страниц данных и таблицы стека обоих процессов. Если элементы таблицы страниц родителя указывают на ОЗУ, элемент таблицы страниц потомка устанавливается, чтобы указывать на то же самое местоположение, а счетчик ссылок, связанный со страницей ОЗУ, увеличивается для того, чтобы показать, что она разделяется.
Счетчик ссылок Таблица Диск Номер блока 2 страниц кода Страница ОЗУ 0 \ Блок обмена 0 । Блок диска 4 4 Страница ОЗУ 1 у Таблица страниц данных Блок обмена 1 Блок диска 8 Страница ОЗУ 5 Страница ОЗУ 4 Таблица страниц стека Страница ОЗУ 7 Блок обмена 2 Таблица страниц данных Блок обмена 1 Блок диска 8 Страница ОЗУ 5 Страница ОЗУ 4 Таблица страниц стека Страница ОЗУ 7 Блок обмена 2 Таблица ОЗУ Пространство обмена Номер блока Список свободных блоков
Аналогично, если элемент таблицы страниц родителя указывает на пространство обмена, элемент таблицы страниц дочернего процесса устанавливается, чтобы указывать на то же самое местоположение, и счетчик ссылок, связанный с местоположением пространства обмена, увеличивается для того, чтобы показать, что оно разделяется. Бит копирования при записи используется UNIX для обработки специальным образом разделяемого ОЗУ и страницы обмена. На рис. 14.41 приведена иллюстрация карт памяти родителя и дочернего процесса сразу после выполнения системного вызова fork (). Маленькие числа рядом с таблицами областей, таблицей ОЗУ и таблицей пространства обмена — это счетчики ссылок, обслуживаемые ядром. Обработка ссылок на разделяемые страницы ОЗУ и пространства обмена UNIX обрабатывает разделяемые страницы следующим образом. □ Если процесс читает разделяемую страницу ОЗУ, вообще ничего особенного не происходит. □ Если демон страниц решает переместить разделяемую страницу ОЗУ, счетчик ссылок страницы уменьшается, и копия страницы перемещается в пространство обмена. У процесса, чья страница была перемещена, обновляется элемент таблицы страниц перемещенной страницы, чтобы отразить передачу, но другие процессы, которые разделяют ту же самую страницу, все еще ссылаются на страницу ОЗУ. Если счетчик ссылок страницы ОЗУ все еще отличен от нуля после того, как он уменьшился на единицу, страница ОЗУ не добавляется к списку свободных. □ Если процесс осуществляет доступ к разделяемой странице пространства обмена, она перемещается из пространства обмена, и у процесса, чья страница была перемещена, обновляется элемент таблицы страниц перемещенной страницы, чтобы отразить передачу в ОЗУ. Другие процессы, разделяющие ту же самую страницу обмена, ссылаются на пространство обмена. □ Если процесс пытается изменять страницу, у которой установлен бит копирования при записи, MMU автоматически генерирует ошибку страницы. Обработчик ошибки страницы проверяет, является ли счетчик ссылок страницы ОЗУ больше 1. Если это так, то означает, что процесс записывает в разделяемую страницу. В данной ситуации обработчик ошибки копирует страницу в другую страницу ОЗУ и модернизирует элемент таблицы страниц дочернего процесса, чтобы он указывал на новую копию. Повторно устанавливается бит копирования при записи элемента табли
цы страниц потомка. Затем обработчик ошибки уменьшает на 1 первоначальный счетчик ссылок страницы ОЗУ и повторно устанавливает ее бит копирования при записи, если счетчик уменьшился до 1. Если процесс пытается изменить страницу, чей бит копирования при записи установлен, а счетчик ссылок равен 1, обработчик ошибки позволяет процессу использовать физическую страницу и повторно устанавливает бит копирования при записи, но также и отсоединяет страницу от ее текущей копии обмена. Это потому, что другой процесс, созданный разветвлением, также может разделять ту же самую копию обмена. Пробуксовка и свопинг Если одновременно работает много процессов, возможно, что интенсивность недостатка страниц заставляет ЦП большую часть времени тратить на перемещение страниц в/из пространства обмена. Эта ситуация, называемая пробуксовкой (thrashing), заканчивается плохой производительностью системы. Когда система управления памятью обнаруживает пробуксовку, она пробуждает процесс sched, который выбирает процессы для дезактивирования и перемещения на диск, sched находит процессы на основе их приоритета и использования памяти, помечает их "на перемещение" (swapped) и перемещает все их страницы ОЗУ в пространство обмена. Процесс sched продолжает откачку процессов в пространство обмена до прекращения пробуксовки, а затем снова засыпает. Как только истекает предопределенный период, откачанный процесс отмечается, как "готовый работать" (ready to run), и его страницы нормальным образом перемещаются назад в ОЗУ. Прекращение процесса: exit() Когда процесс завершается, происходят следующие события: □ его код возврата помещается в элемент таблицы процесса; □ его дескрипторы файлов закрываются; □ счетчик ссылок каждой из его областей уменьшается на 1; П если счетчик ссылок области уменьшается до 0, сметчики ссылок страниц ОЗУ и страниц обмена процесса (если уместно) уменьшаются на 1; П любые страницы ОЗУ или страницы обмена, которые имеют нулевой счетчик ссылок, освобождаются. Элемент таблицы процесса освобождается только тогда, когда родительский процесс принимает его код завершения при помощи системного вызова wait ().
Сигналы Сигналы (signals) информируют процессы об асинхронных событиях. Структуры данных, которые поддерживают сигналы, хранятся в таблице процесса и пользовательских областях. Каждый процесс имеет три элемента данных, связанных с обработкой сигнала: а массив элементов пользовательской области, называемый массивом обработчиков сигнала (signal handler array), который описывает, что процесс должен сделать, когда он получает конкретный тип сигнала; а массив битов в элементе таблицы процесса, называемый битовой картой ожидающих сигналов (pending signal bitmap), по одному на каждый тип сигнала, которая записывает, поступил ли для обработки конкретный тип сигнала; □ ID группы процесса, который применяется при распространении сигналов. На рис. 14.42 представлена диаграмма связанных с сигналом структур данных ядра. Таблица процесса Область пользователя Битовая карта отложенных сигналов Массив обработчиков сигналов Рис. 14.42. Относящиеся к сигналу структуры ядра \ setpgrpO Системный вызов setpgrpO присваивает номеру группы вызывающего процесса его собственный PID, таким образом, помещая его в свою уни
кальную группу процесса. Разветвленный процесс наследует группу процесса своего родителя, setpgrpo работает, изменяя элемент номера группы процесса в системной таблице процессов. Номер группы процесса используется системным вызовом kill о , как вы увидите позже. signatf) Системный вызов signal о устанавливает способ, которым процесс отвечает на конкретный тип сигнала. Есть три опции: игнорировать сигнал, исполнить действие ядра по умолчанию, выполнить установленный пользователем обработчик сигнала. Элементы в массиве обработчиков сигналов задаются следующим образом: П если сигнал должен игнорироваться, элементу присваивается значение 1; П если сигнал должен вызывать действие по умолчанию, элемент сбрасывается в 0; П если сигнал должен быть обработан с помощью инсталлированного пользователем обработчика, элемент устанавливается на адрес обработчика. Когда сигнал посылается процессу, ядро устанавливает соответствующий бит в битовой карте сигналов процесса получателя. Если процесс-получатель спит в прерываемом приоритете, он пробуждается, чтобы обработать сигнал. Ядро проверяет битовую карту ожидаемых сигналов процесса всякий раз, когда процесс возвращается из режима ядра в пользовательский режим (т. е. когда он возвращается из системного вызова) или когда процесс входит или покидает состояние сна. Поэтому обратите внимание, что сигнал едва ли когда-либо немедленно обрабатывается. Вместо этого процесс-получатель имеет дело с ожидаемыми сигналами только, когда это запланировано. Данная ситуация делает сигналы относительно слабым механизмом для приложений реального времени. Обратите внимание также, что битовая карта ожидаемых сигналов не хранит счетчик конкретных типов сигналов, находящихся в ожидании. Это означает, что, если три сигнала sigint поступают почти одновременно, возможно, что только один из них будет замечен. Сигналы после системных вызовов fork() или ехес() Разветвленный процесс наследует содержимое массива обработчиков сигналов от своего родителя. Когда процесс выполняется, первоначально игнорируемые сигналы продолжают игнорироваться, а остальные настроены на действия по умолчанию. Иными словами, все элементы, равные 1, не изменяются, а другие сброшены в 0.
Обработка сигнала Когда ядро обнаруживает, что процесс имеет ожидаемый сигнал, оно либо игнорирует его, выполняя действие по умолчанию, либо вызывает установленный пользователем обработчик. Чтобы вызвать обработчик, ядро добавляет новую структуру к стеку процесса и изменяет счетчик команд процесса так, чтобы заставить процесс-получатель действовать, будто тот вызвал обработчик сигнала из своей текущей программы. Когда ядро возвращает процесс в пользовательский режим, процесс выполняет обработчик и затем возвращается из функции назад в предыдущую программу. Сигнал sigchld ("смерть" дочернего процесса) обрабатывается чуть иначе (см. описание системного вызова wait ()). exit() Когда процесс заканчивается, он оставляет код возврата в поле своего элемента таблицы процессов и отмечается, как процесс-зомби. Код возврата доступен родительскому процессу при помощи системного вызова wait(). Ядро всегда информирует родительский процесс, что один из его потомков "умер", посылая ему сигнал sigchld. wait() Системный вызов wait () возвращается только при одном из двух условии, или запрашивающий процесс не имеет дочерних процессов, тогда он возвращает код ошибки, или один из "потомков" процесса закончился, тогда возвращаются PID дочернего процесса и код возврата. Алгоритм, которым ядро обрабатывает системный вызов wait о, таков: П если процесс вызвал wait о и не имеет "потомков” , wait () возвращает код ошибки; □ если процесс вызвал wait о и один или несколько его дочерних процессов уже зомби, ядро наугад выбирает "потомка", удаляет его из таблицы процессов и возвращает его PID и код возврата; □ если процесс вызвал wait о и ни один из его порожденных процессов не зомби, вызов wait о засыпает. Он пробуждается ядром, когда получает любые сигналы, тогда проверка возобновляется? Хотя данный алгоритм будет работать и в таком виде, есть одна маленькая проблема: если бы процесс выбрал игнорирование сигналов sigchld, все его "потомки" остались бы зомби, что привело бы к засорению таблицы процессов. Во избежание этого ядро обращается с игнорированием сигнала sigchld как со специальным случаем. Если поступает сигнал sigchld и иг
норируется, ядро немедленно удаляет всех "потомков"-зомби данного родителя из таблицы процессов, а затем позволяет системному вызову wait () работать нормально. Когда вызов wait о возобновляется, он не находит никаких зомби, поэтому опять засыпает. В конце концов, когда "смертельный" сигнал последнего порожденного процесса игнорируется, системный вызов wait о возвращается с кодом ошибки, чтобы показать, что запрашивающий процесс не имеет никаких дочерних процессов. кто Системный вызов kill о использует поля реального пользовательского ID и ID группы процесса в таблице процессов. Например, когда выполняется следующая строка кода kill (О, SIGINT); ядро устанавливает бит в битовой карте ожидающих сигналов, соответствующий sigint в каждом элементе таблицы процессов, чей групповой ID процесса равен ID запрашивающего процесса. UNIX использует эту возможность, чтобы распространять сигналы, вызванные нажатием комбинаций клавиш <CtrI>+<C> и <Ctrl>+<Z>, всем процессам в группе процесса управляющего терминала. Ввод/вывод В этом разделе описываются структуры данных и алгоритмы, которые использует ядро UNIX, чтобы поддержать системные вызовы, относящиеся к вводу/выводу. В частности, будет рассмотрена UNIX-реализация этих запросов в связи с тремя главными категориями файлов: □ обычные файлы; □ файлы каталогов; □ специальные файлы (т. е. периферийные устройства, конвейеры и сокеты). 5 / Объекты ввода/вывода Я представляю файлы как объекты специального вида, которые обладают возможностями ввода/вывода. UNIX-объекты ввода/вывода могут быть классифицированы согласно иерархии, приведенной на рис. 14.43.
Каталог Обычный файл Специальный файл Сокет Конвейер Внешнее устройство Именованный конвейер Безымянный Буферизированный Не буферизированный конвейер Диск Терминал Лента Рис. 14.43. Иерархия объектов ввода/вывода Системные вызовы ввода/вывода Как описано в главе 13. системные вызовы ввода/вывода UNIX могут применяться унифицированным способом ко всем объектам ввода/вывода с некоторыми исключениями. Например, вы не можете использовать системный вызов iseek о для конвейера или сокета. Ниже представлен список системных вызовов, которые описаны в следующих разделах: close () dup () ioctl () link () Iseek() mknod() /mkdir() mount() open() read() sync() umount() unlink() write() Буферизация ввода/вывода Ядро избегает ненужного ввода/вывода устройств путем буферизации большинства операций ввода/вывода в фиксированного размера структуре данных в масштабе всей системы, называемой буферным пулом (buffer pool). Это набор буферов, которые используются для кэширования блоков файла в ОЗУ. Когда процесс читает из блока в первый раз, блок копируется из файла в буферный пул, а затем копируется оттуда в пространство данных процесса. Следующее чтение из того же самого блока обслуживается непосредственно из ОЗУ. Аналогично, если процесс пишет в блок, которого нет в
буферном пуле, блок копируется из файла в пул, а затем буферированная копия изменяется. Если блок уже находится в пуле, буферированная версия изменяется без всякой потребности в физическом вводе/выводе. Несколько смешанных списков, основанных на блочных устройствах и количестве блоков, поддерживаются для буферов в пуле так, чтобы ядро могло быстро расположить буферируемый блок. Когда процесс получает доступ к буферу во время системного вызова вво-да/вывода, буфер выделяется или блокируется, чтобы препятствовать его использованию другими процессами. Если другой процесс пытается получать доступ к выделенному буферу, ядро погружает его в сон до тех пор, пока буфер не освободится. Когда UNIX загружается, все буферы в пуле отмечаются как свободные и помещаются в список свободных буферов. Когда ядро обслуживает системный вызов ввода/вывода процесса и нуждается в копировании блока из объекта ввода/вывода в буферный пул, для этого требуется несколько шагов. 1. Сначала ядро выбирает первый буфер в списке свободных буферов и отмечает его как выделенный. 2. Затем оно удаляет буфер из списка свободных буферов и делает асинхронный запрос чтения соответствующему драйверу устройства. 3. Наконец, ядро погружает процесс в сон. Когда запрос чтения будет обслужен, процесс пробуждается и ядро продолжает выполнять системный вызов. Если список свободных буферов пуст, процесс погружается в сон, пока свободный буфер не становится доступным. Если блок уже буфери-рован, ядро просто выделяет существующий буфер. 4. Когда системный вызов завершает работу с буфером, буфер освобождается и помещается в конец списка свободных буферов. Эта схема гарантирует, что каждый раз, когда требуется новый буфер, выбирается наиболее давний по использованию буфер. Заманчиво предполагать, что, когда файл закрывается, ядро копирует измененные буферированные блоки всего файла обратно на диск. Этого не происходит; взамен ядро устанавливает флаг отложенной записи (delayed-write flag) в заголовке буфера всякий раз, когда он изменяется системным вызовом write (). Буферированный блок физически записывается на диск только, когда другой процесс пытается удалить его из списка свободных буферов. Эта схема задерживает физический ввод/вывод до последнего возможного момента. На рис. 14.44 представлена иллюстрация буферизации в действии.
1. Процесс читает из блока 217 первый раз 2. Процесс записывает в блок 217 3. Процесс закрывает файл 4. Буфер сбрасывается на диск 5. Буферы сшиваются несколькими списками Рис. 14.44. Буферизация в действии sync() Системный вызов sync о заставляет ядро сбрасывать на диск все буферы с флагом отложенной записи. В системах, где демон System V fsflush не существует, администраторы системы создают утилиту sync, которая заставляет системный вызов sync() регулярно выполняться. Это гарантирует, что со
держимое диска всегда современно. Если процесс fsflush работает в системе, ОН управляется функцией flushing. Ввод/вывод обычных файлов ореп() Рассмотрим, что' происходит, когда процесс открывает существующий обычный файл только для чтения. Позже будет представлен способ, которым ядро создает новый файл. Предположим, что некоторый процесс первым открывает файл после последней перезагрузки системы, и предполагается также, что он выполняет следующий код: fd = open (’’/home/glass/sample.txt", O_RDONLY) ; Ядро начинает, переводя имя файла в номер inode, выполнять описанный ранее в главе алгоритм6. Если inode файла не найден, возвращается код ошибки. Иначе, ядро выделяет компонент фиксированного размера структуры данных, называемой таблицей активных inode (active inode table), и копирует inode с диска в этот компонент. Ядро также сохраняет в компоненте несколько других значений, которые описаны позже. Ядро кэширует активные и недавно используемые inode в таблице активных inode, чтобы избежать излишнего доступа к диску. Затем ядро выделяет компонент в другой фиксированного размера структуре данных, называемой таблицей открытых файлов (open file table). Оно заполняет этот компонент несколькими полезными значениями, включая: □ указатель на новый элемент в таблице активных inode; □ флаги разрешения чтения/записи, указанные в системном вызове open о ; □ текущую позицию файла процесса, установленную на 0 по умолчанию. Наконец, ядро выделяет компонент в массиве дескрипторов файла процесса, направляет этот элемент в новый элемент в открытой таблице файлов и передает индекс дескриптора файла в качестве возвращаемого значения системного вызова open о. На рис. 14.45 приведена иллюстрация процесса и структур данных ядра. Если процесс открывает несуществующий файл и определяет флаг o creat, ядро создает указанный файл. Чтобы сделать это, оно выделяет свободный inode из списка inode файловой системы, устанавливает поля внутри него для демонстрации, что файл пуст, а затем добавляет жесткую связь к соответствующему файлу каталога. Вспомните, что жесткая связь — это элемент, состоящий из имени файла и его номера inode. 6 См. разд. "Преобразование путевых имен в номера inode"ранее в этой главе. — Ред.
Диск Массив дескрипторов файла (в области пользователя) Таблица открытых файлов Таблица активных inode о 1 2 (fd)3 Рис. 14.45. Файловые структуры ядра ф TJ о с Теперь, когда представлен способ, которым ядро справляется с системным вызовом open о, опишем системные ВЫЗОВЫ read(), write о, lseek() И close о. Для простоты предположим, что к типовому файлу обращается только один процесс; позже в главе будет описана поддержка ядра для многочисленных пользователей того же самого файла. read() Рассмотрим, что происходит, когда типовой процесс выполняет следующую последовательность системных вызовов read (): read (fd, bufl, 100); /* Читать read (fd, buf2, 200); /* Читать read (fd, buf3, 5000); /* Читать 100 байтов в буфер bufl */ 200 байтов в буфер buf2 */ 5000 байтов в буфер buf3 * Вот последовательность событий, которые произошли бы во время выполнения этих запросов: 1. Данные, требуемые первым read о, находятся в первом блоке файла. Ядро решает, что блок не в буферном пуле, и поэтому копирует его с диска в свободный буфер. Затем оно копирует первую сотню байтов из буфера в bufl. Наконец, позиция файла, хранящаяся в таблице открытых файлов, обновляется новым значением 100. 2. Данные, требуемые вторым read (), также находятся в первом блоке файла. Ядро выясняет, что блок уже присутствует в буферном пуле, и поэто
му копирует следующие 200 байтов из буфера в buf 2. Затем оно модернизирует позицию файла на 300. 3. Данные, требуемые третьим read о, находятся частично в первом и частично во втором блоках файла. Ядро перемещает остаток первого блока (3796 байтов) из буферного пула в buf3. Затем оно копирует второй блок с диска в свободный буфер в пуле и оставшиеся данные (1204 байта) из буферного пула в buf3. Наконец, ядро модернизирует позицию файла на 5300. Обратите внимание, что единственный read о может вызвать копирование больше одного блока с диска в буферный пул. Если процесс читает из блока, который не имеет выделенного пользовательского блока (см. главу 13 для обсуждения разбросанных файлов (sparse file))7, то read о не буферирует ничего, а вместо этого обращается с блоком, как будто он был заполнен символами ASCII null (0). write() Предположим, теперь типовой процесс выполняет следующую серию системных вызовов write о: write (fd, buf4, 100); /* Записать 100 байтов из буфера buf4 */ write (fd, buf5, 4000); /* Записать 4000 байтов из буфера buf5 ★/ Вспомните, что текущее значение позиции файла — 5300, и эта позиция расположена около начала второго блока файла. Вспомните также, что данный блок в настоящее время буферирован благодаря последнему системному вызову read(). Последовательность событий, которые произошли бы во время выполнения нашего типового процесса, такова: 1. Данные, которые должны быть переписаны первым write(), находятся полностью во втором блоке. Этот блок уже присутствует в буферном пуле, так что 100 байтов buf 4 копируются в соответствующие байты буферированного второго блока. 2. Данные, которые будут переписаны вторым write (), находятся частично во втором и частично в третьем блоках. Ядро копирует первые 3792 байта buf5 в оставшиеся 3792 байта буферированного второго блока. Затем оно копирует третий блок из файла в свободный буфер. Наконец, ядро копирует оставшиеся 208 байтов buf 5 в первые 208 байтов буферированного третьего блока. IseekO Реализация системного вызова 1s ее к () тривиальна: ядро просто изменяет значение дескриптора, связанного с позицией файла, расположенного в таб 7 См. разд. "Перемещение в файле: lseek()" в главе 13. — Ред.
лице открытых файлов. Обратите внимание, что нет необходимости в физическом вводе/выводе. Рис. 14.46 иллюстрирует результат следующего кода: Iseek (fd, 3000, SEEK_SET); Область пользователя Таблица открытых файлов Таблица активных inode Диск Рис. 14.46. Системный вызов Iseek () изменяет смещение файла closeO Когда дескриптор файла закрывается, и только он связан с конкретным файлом, ядро копирует inode файла обратно на диск, а затем помечает соответствующую таблицу открытых файлов и таблицу активных inode как свободные. Когда процесс заканчивается, ядро автоматически закрывает дескрипторы файла всего процесса. Как было упомянуто ранее, ядро имеет специальные механизмы, чтобы поддерживать множественные дескрипторы файла, связанные с тем же самым файлом. Чтобы осуществлять эти механизмы, ядро имеет.поле счетчика ссылок для каждого элемента таблицы открытых файлов и каждого элемента таблицы активных inode. Когда файл открывается впервые, оба счетчика устанавливаются в 1. Есть три способа, которыми файл может быть разделен между несколькими описателями файла: □ файл явно открывается несколько раз либо одним и тем же процессом, либо различными процессами; □ дескриптор файла дублируется при помощи системных вызовов dup (), dup2 () ИЛИ fcntl ();
□ процесс разветвляется, что заставляет все вхождения его дескриптора файла дублироватся. Когда дескриптор файла формируется первым методом, ядро создает новый элемент таблицы открытых файлов, который указывает на тот же самый активный inode. Затем ядро увеличивает поле счетчика ссылок в таблице активных inode файла, как показано на рис. 14.47. Область пользователя Таблица открытых файлов Таблица активных inode Диск о 1 2 (fd) 3 (fd2)4 Рис. 14.47. Системный вызов ореп() создает новый элемент в таблице открытых файлов Когда дескриптор файла создается любым из следующих двух методов, ядро устанавливает новый дескриптор файла, чтобы он указывал на тот же самый элемент таблицы открытых файлов, как и первоначальный дескриптор файла, и увеличивает поле счетчика ссылок в элементе дескриптора таблицы открытых файлов, как показано на рис. 14.48. Системный вызов close о обращается с полями счетчиков ссылок следующим образом: 1. Когда дескриптор файла закрывается, ядро уменьшает на 1 поле счетчика ссылок в связанной с ним таблице открытых файлов. 2. Если счетчик ссылок таблицы открытых файлов остается больше 0, ничего не происходит. Если счетчик ссылок уменьшается до нуля, элемент таблицы открытых файлов отмечается как свободный, и поле счетчика ссылок в таблице активных inode файла уменьшается на 1.
3. Если счетчик ссылок в таблице активных inode остается больше 0, ничего не происходит. Если счетчик ссылок уменьшается до нуля, inode копируется обратно на диск, и элемент таблицы активных inode отмечается как свободный. Область пользователя Таблица открытых файлов Таблица активных inode Диск о 1 2 (fd) 3 (fd2)4 Рис. 14.48. Дублирование дескриптора файла dup() Реализация системного вызова dup () проста: он копирует указанный дескриптор файла в следующий свободный элемент массива дескрипторов файла и увеличивает соответствующий счетчик ссылок таблицы открытых файлов. unlinkO Системный вызов unlink о удаляет жесткую связь из каталога и уменьшает на 1 счетчик жесткой связи соответствующего ему inode. Если счетчик жесткой связи уменьшается до 0, освобождаются inode файла и пользовательские блоки, когда последний процесс, который использует файл, завершается. Это означает, что процесс может отключить файл и продолжать получать доступ к нему до завершения процесса. Процесс удаления связи показан на рис. 14.49.
Исходная пользователя Удаление одной связи Метка 1 inode 27 Счетчик ссылок 1 Удаление последней связи: блоки пользователя и inode свободны inode 27 свободен К блокам пользователя Блоки пользователя свободны Рис. 14.49. Удаление связи Ввод/вывод каталогов Каталоги отличаются от обычных файлов тем, что они могут: □ создаваться ТОЛЬКО С ПОМОЩЬЮ системных ВЫЗОВОВ mknod () ИЛИ mkdir () ; □ читаться только через системный вызов getdents о ; □ изменяться только с использованием системного вызова link (). Эти различия гарантируют целостность иерархии каталогов. Каталоги могут быть открыты тем же самым способом, как и обычные файлы. Рассмотрим реализацию системных вызовов mknod () и. link (). mknod() Системный вызов mknod () создает каталог, именованный конвейер или специальный файл. В каждом случае системный вызов начинается с выделения нового inode на диске и установки, соответственно, его поля типа и добавлением его в иерархию каталогов через жесткую связь. Если создается8 ката 8 Во многих версиях UNIX при создании каталога предпочтителен системный вызов mkdir().
лог, пользовательский блок связывается с inode и заполняется элементами и по умолчанию. Если формируется специальный файл, соответствующие старшие и младшие номера устройства сохраняются в inode. Нпк() Системный вызов link о добавляет жесткую связь к каталогу. Вот пример: link ("/home/glass/filel.с", "/home/glass/file2.с"); В этом примере ядро нашло бы номер inode имени файла источника /home/glass/filel.c, а затем связало его с меткой file2.c в каталоге назначения /home/glass. Далее увеличился бы счетчик жестких связей. Только привилегированный пользователь (root) может связывать каталоги, чтобы предохранить неосторожных пользователей от создания циклических структур каталогов. Монтирование файловых систем Ядро поддерживает единственную в масштабе всей системы фиксированного размера структуру данных, называемую таблицей монтирования (mount table), которая позволяет множественным файловым системам обращаться через единственную иерархию каталогов. Системные вызовы mount () и umount () изменяют эту таблицу и могут выполняться только привилегированным пользователем (root). mountO Когда файловая система монтируется, используя системный вызов mounto, компонент, содержащий перечисленные ниже поля, добавляется к таблице монтирования: □ номер устройства, которое содержит недавно смонтированную файловую систему; □ указатель на корневой inode недавно установленной файловой системы; □ указатель на inode точки монтирования; □ указатель на структуру данных монтирования, специфическую для недавно смонтированной файловой системы. Каталог, связанный с точкой монтирования (mount point), становится синонимичным с корневым узлом недавно смонтированной файловой системы, и ее предыдущее содержимое оказывается недоступным для процессов, пока файловая система не будет размонтирована. Чтобы разрешить корректный перевод путевых имен, которые пересекают точки монтирования, активный inode каталога монтирования отмечен как точка монтирования, и установ
лен, чтобы указывать на связанный элемент таблицы монтирования. Например, рис. 14.50 демонстрирует эффект следующего системного вызова, который устанавливает файловую систему, содержащуюся на устройстве /dev/daO в каталоге /mnt: mount ("/dev/daO", "/mnt", 0); Перед монтированием После монтирования bin Сращивание I -----/ Н--------! \ usr mnt tmp1 tmp2 bin usr mnt tmp1 tmp2 Рис. 14.50. Прямое монтирование Перевод имен файлов Алгоритм трансляции имени использует содержимое таблицы монтирования при переводе путевых имен, которые пересекают точки монтирования. Это может происходить при движении вверх или вниз по иерархии каталогов. Рассмотрим следующий пример: $ cd /mnt/tmp 1 $ cd ../../bin Первая команда cd пересекается с корневым устройством на устройстве /dev/daO, а вторая команда cd пересекается на корневом устройстве. Вот как алгоритм включает смонтированные файловые системы в процесс перевода: □ когда во время процесса перевода обнаруживается inode, который является точкой монтирования, вместо него возвращается указатель на корневой inode смонтированной файловой системы. Например, когда путь /mnt, являющийся частью /mnt/dirl, переведен, возвращается указатель на корневой узел смонтированной файловой системы. Этот указатель используется в качестве отправной точки для перевода остальной части путевого имени;
□ когда возникает компонент ".." путевого имени, ядро проверяет, окажется ли точка монтирования пересеченной. Если текущий указатель inode процесса перевода и компонент ".." указывают на корневой узел, то точка пересечения возникнет. Тогда ядро заменяет текущий указатель inode процесса перевода указателем inode точки монтирования в родительской файловой системе, который оно находит, просматривая таблицу монтирования для элемента, соответствующего номеру устройства текущего inode. Точка пересечения показана на рис. 14.51. Корневое устройство / ч---------- Точка пересечения Устройство /dev/daO mnt tmp1 tmp2 Рис. 14.51. Точка пересечения umountO При размонтировании файловой системы ядро выполняет несколько действий. 1. Проверяет, что нет никаких открытых файлов в файловой системе, которая собирается быть размонтированной. Ядро может делать это, просматривая таблицу активных inode для элементов, которые содержат номер устройства файловой системы. Если найден любой активный inode, то системный вызов терпит неудачу. 2. Сбрасывает обратно в файловую систему суперблок, блоки отложенной записи и буферированные inode. 3. Удаляет элемент таблицы монтирования и метку "точка монтирования" из каталога точки монтирования. Ввод/вывод специальных файлов Почти все специальные файлы соответствуют периферийным устройствам типа принтеров, терминалов и драйверов диска, так что в остальной части
этого раздела будут использоваться термины "специальный файл'м "периферия" как синонимы. Каждое периферийное устройство в системе имеет связанный с ним драйвер устройства, являющийся заказной частью программного обеспечения, которое содержит весь специфический для периферийного устройства код. Например, драйвер ленточного устройства содержит код для перемотки и удержания ленты. Единственный драйвер устройства может управлять всеми экземплярами конкретного вида периферийных устройств. Другими словами, три драйвера ленты одного типа могут разделять единственный драйвер устройства. Драйверы устройства для каждого типа периферийных устройств в системе должны быть связаны с ядром, когда системный администратор конфигурирует ее. (Для дополнительной информации см. главу 15). Интерфейс устройства Драйвер периферийного устройства обеспечивает интерфейс к периферийному устройству, который может появляться в двух следующих разновидностях: □ блочный (block oriented interface) означает, что ввод/вывод буферируется и физический ввод/вывод выполняется на базе поблочной передачи. Драйверы диска и драйверы ленты имеют блочный интерфейс; □ символьный (character oriented interface) означает, что ввод/вывод не буферируется и физический ввод/вывод происходит на базе передачи символа за символом. Символьный интерфейс иногда известен, как интерфейс доступа низкого уровня (raw interface). Все периферийные устройства, включая драйверы диска и драйверы ленты, обычно имеют символьный интерфейс. Драйвер периферийного устройства иногда содержит оба вида интерфейса. Вид выбираемого интерфейса зависит от того, как будет организован доступ к устройству. При выполнении случайного и повторяющегося доступа к общему набору блоков будет правильным осуществление доступа к периферии через ее блочный интерфейс. Однако если вы собираетесь получать доступ к блокам в- единственной линейной последовательности, как при создании резервной ленты, тогда больше смысла в доступе к периферийному устройству через его символьный интерфейс. Это позволяет избежать издержек механизма внутренней буферизации ядра и иногда позволяет ядру использовать аппаратные возможности прямого доступа к памяти. Возможно, хотя не желательно, обеспечивать доступ к отдельному устройству одновременно через оба интерфейса. Неприятность состоит в том, что символьный интерфейс обходит систему буферизации, приводя к сбивающим с толку результатам ввода/вывода.
Вот пример: □ Процесс А открывает дискету, используя блочный интерфейс, /dev/flp. А затем пишет 1000 байтов на диск. Этот вывод сохраняется в буферном пуле и отмечается для отсроченной записи. □ Затем процесс В открывает ту же самую дискету, /dev/rflp, используя символьный интерфейс. Когда он читает 1000 байтов с диска, записанные процессом А данные игнорируются, т. к. они все еще находятся в буферном пуле. Решение этой проблемы простое: не открывайте устройство одновременно через различные интерфейсы! Старшие и младшие номера Старшие и младшие номера устройства используются, чтобы классифицировать драйвер, связанный с конкретным устройством. Старший номер устройства определяет тот драйвер устройства, который конфигурируется в ядре и используется для доступа к устройству. Младший номер устройства выясняет, какое (возможно, несколько) устройство будет использоваться. Например, предположим, что в системе существует три драйвера ленты, и драйвер устройства для драйвера ленты соответствуют старшему номеру 15. Если запустить утилиту is, чтобы просмотреть блочные устройства ленты, можно увидеть следующий вывод: brw—w—w- 1 root 15, 0 Feb 13 14:27 /dev/mtO brw—w—w- 1 root 15, 1 Feb 13 14:29 /dev/mt1 brw—w—w- 1 root 15, 2 Feb 13 14:27 /dev/mt2 Из этого мы видим, что ко всем драйверам ленты обращается один и тот же драйвер устройства (помеченный индексом 15), и каждый младший номер уникально идентифицирует определенный драйвер ленты. Старшие и младшие номера используются для индексации в таблицах переключения, чтобы определить местонахождение соответствующего драйвера устройства. Таблицы переключения Все драйверы устройств UNIX должны удовлетворять предопределенному формату, включающему набор стандартных точек входа для функций, которые открывают, закрывают и обеспечивают доступ к периферийному устройству. Драйверы блочных устройств также содержат точку входа, называемую стратегией (strategy), которая используется ядром для выполнения блочного ввода/вывода на физическое устройство. Точки входа каждого блочного и символьного интерфейсов находятся в системных таблицах, называемых таблицей переключения блочных устройств (block device switch table) и таблицей переключения символьных устройств (character, device switch table).
Эти таблицы хранятся в виде массивов указателей на функции и создаются автоматически, когда конфигурируется UNIX. Одна размерность массива индексируется старшим номером периферийного устройства, другая — кодом функции. На рис. 14.52 приведен пример таблицы переключения. Рис. 14.52. Пример таблицы переключения Рис. 14.53 иллюстрирует структуры данных ядра, которые могли бы быть сформированы для выполнения следующего фрагмента кода: fd = open ("/dev/tty2", O_RDWR); Системные вызовы iseek(), chmodо и stat () работают тем же самым способом для специальных файлов, как и для обычных файлов. Системные вызовы read(), write о и close () работают несколько иначе и используют таблицу переключения блочных устройств и таблицу переключения символьных устройств. В каждом случае их действие может быть разбито на периферийно независимую и периферийно зависимую части.
Область пользователя Таблица Таблица открытых активных inode файлов Рис. 14.53. Доступ к специальному файлу ореп() Когда процесс открывает файл, ядро может сообщить, что он является периферийным устройством, рассматривая поле типа inode файла. Если поле указывает на блочное или символьное устройство, оно читает старший и младший номера, чтобы определить класс и экземпляр устройства, которое открывается. При обработке системного вызова open о ядро исполняет периферийнонезависимые действия. Периферийно-независимая часть open о работает точно так же, как и open () для обычного файла; inode файла помещается в таблицу активных inode, и создается элемент таблицы открытых файлов. Периферийно-зависимая часть open () вызывает процедуру open () драйвера устройства. Например, процедура open () драйвера ленты обычно повторно натягивает и перематывает ленту, тогда как процедура ореп() терминального драйвера устанавливает скорость двоичной передачи устройства и задает терминальные установки по умолчанию. 25 Зак. 786
read() При чтении С СИМВОЛЬНОГО устройства read о вызывает функцию read в драйвере устройства, чтобы выполнить физический ввод/вывод. При чтении с блочного устройства системный вызов read () использует стандартный буферированный механизм ввода/вывода. Если блок должен быть скопирован с устройства в буферный пул, вызывается функция стратегии драйвера устройства, которая объединяет как возможность чтения, так и возможность записи. write() При записи на символьное устройство write о выполняет функцию write в драйвере устройства, чтобы осуществить физический ввод/вывод. При записи на блочное устройство системный вызов write () использует буферированную систему ввода/вывода. Когда, в конечном счете, имеет место отложенная запись, чтобы произвести физический ввод/вывод, используется функция стратегии устройства. close() Ядро закрывает периферийное устройство таким же образом,-как оно закрывает обычный файл кроме ситуации, когда выполняющий системный вызов close о процесс является последним обратившимся к устройству процессом. В этом специальном случае выполняется процедура close о драйвера устройства, сопровождаемая рядом действий для закрытия обычного файла. Ядро не может определить, что специальный файл был закрыт его последним пользователем, просто исследуя счетчик ссылок активного режима, т. к. к единственному устройству можно обращаться через несколько inode. Такая ситуация возникает, если один процесс получает доступ к устройству через его блочный интерфейс, а другой получает доступ к тому же самому файлу через его символьный интерфейс. В этом случае должен просматриваться список активных inode для поиска других inode, связанных с тем же самым физическим устройством. ioctl() Системный вызов ioctio управляет определенными особенностями устройства через дескриптор файла. Он просто передает свои аргументы элементу ioctl драйвера устройства. Специфичные для устройства действия включают установку скорости двоичной передачи терминала, выбор шрифта принтера и управление перемоткой ленты.
Ввод/вывод терминала Хотя терминалы являются своего рода периферийными устройствами, терминальные драйверы устройств интересны и достаточно разнообразны, что служит основанием для их отдельного обсуждения. Главное различие между терминальными и другими драйверами устройств заключается в том, что терминальные драйверы устройств должны поддерживать несколько различных видов как предварительной обработки, так и постобработки на входе и выходе. Каждый вариант обработки назван дисциплиной линии (line discipline). Дисциплина линии терминала может быть установлена с помощью системного вызова ioctl(). Большинство терминальных драйверов поддерживают три общие дисциплины линии. □ Низкоуровневый режим (raw mode), который вообще не выполняет никакой специальной обработки. Символы, введенные с клавиатуры, становятся доступными процессу чтения, основанному на параметрах системного вызова ioctl(). Ключевые последовательности вроде <Ctrl>+<C> не генерируют никаких специальных действий и передаются, как обычные ASCII-символы. Например, нажатие комбинации клавиш <Ctrl>+ +<С> воспринимается, как ASCII-символ со значением 3. Низкоуровневый режим используется приложениями типа редакторов текста, которые предпочитают делать всю обработку символов самостоятельно. □ Режим cbreak (cbreak mode), который специально обрабатывает только некоторые ключевые последовательности. Например, поток управления после нажатия комбинаций клавиш <Ctrl>+<S> и <Ctrl>+<Q> остается активным. Точно так же комбинация клавиш <Ctrl>+<C> генерирует сигнал прерывания для каждого процесса, работающего в привилегированном режиме. Как и в низкоуровневом режиме, все символы доступны процессу чтения, основанному на параметрах системного вызова iocti (). □ Режим с обработкой (cooked mode), иногда известный как канонический режим, который исполняет полную предварительную обработку и постобработку. В этом режиме клавиши удаления и возврата на один символ сохраняют свои специальные значения вместе с менее обычными комбинациями клавиш для удаления слова или строки. Ввод делается доступным процессу чтения только, когда нажата клавиша <Enter>. Точно так же символы табуляции имеют специальное значение при выводе, и они расширены для дисциплины линии, чтобы корректировать число пробелов. Символ перевода строки расширен до пары "перевод карет-ки/перевод строки".
Структуры данных терминала Основные структуры данных, которые необходимы ядру для реализации дисциплины линии, таковы: □ С -списки, которые связывают списки массивов символов фиксированного размера. Ядро использует эти структуры для буферизации препро-цессорного ввода, постобработки ввода и вывода, ассоциированные с каждым терминалом; □ tty-структуры, которые содержат состояние терминала, включая указатели на его С-списки, текущую выбранную дисциплину линии, список символов, которые должны быть обработаны специальным образом, и опции, установленные ioctl (). Существует одна структура tty на терминал. На рис. 14.54 приведена иллюстрация структуры tty и связанных с ней С-списков. Рис. 14.54. Структура tty и С-списки Чтение с терминала При нажатии клавиши обработчик прерывании клавиатуры, в зависимости от режима терминала, выполняет следующие действия. □ Низкоуровневый режим. Символ копируется в конец необработанного С-списка, и процесс, ожидающий чтения, пробуждается для чтения из необработанного С-списка. В этот момент все символы из необработанного С-списка перемещаются в адресное пространство процесса. □ Cbreak-режим. Если символ — это символ управления потоком или символ прерывания, он обрабатывается специальным образом; иначе, символ копируется в конец необработанного С-списка, и ожидающий чтения процесс пробуждается. Когда процесс пробуждается, все символы из необработанного С-списка перемещаются в адресное пространство процесса.
□ Режим с обработкой. Если символ — это символ управления потоком или символ прерывания, он обрабатывается специальным образом; иначе, символ копируется в конец необработанного С-списка. Если символ — это перевод каретки, содержимое необработанного С-списка перемещается в конец входного С-списка обработки, и ожидающий чтения процесс пробуждается. В этот момент специальные символы, типа backspace и delete, во входном С-списке обрабатываются, а затем содержимое копируется в адресное пространство процесса. Системный вызов iocti () позволяет определять условия, которые должны быть удовлетворены прежде, чем процесс чтения пробудится. Среди этих условий — количество символов в необработанном С-списке и время, прошедшее с последнего системного вызова read (). Если несколько процессов пробуют читать с одного и того же терминала, для синхронизации их действий необходимо обеспечить выполнение условий. Иначе, вход будет разделен без разбора между конкурирующими процессами. Сигналы, генерируемые специальными символами в режиме cbreak и режиме с обработкой, идут в процессы, связанные с терминалом управления (control terminal). (Для дополнительной информации о терминалах управления см. главу 13.) Запись на терминал Когда процесс пишет на терминал, любые используемые специальные символы обрабатываются согласно текущей выбранной дисциплине линии, а затем помещаются в конец выходного С-списка терминала. Терминальный драйвер вызывает аппаратные прерывания, чтобы вывести содержимое этого списка на экран. Если выходной С-список полон, процесс записи помещается в сон до тех пор, пока некоторый вывод не попадет на экран. Потоки Когда поток создается системным вызовом open (), формируется заголовок потока. Он обеспечивает интерфейс системных вызовов к пользовательскому приложению и содержит структуры данных, которые представляют поток. Заголовок потока управляет последующими вызовами read о, write о, getmsgO или putmsgO, посылая или получая данные от первого модуля в потоке. Заголовок потока, все модули и драйвер потока работают в режиме ядра. Драйверы потока могут быть вставлены в поток из пользовательского режима. Это делается путем "проталкивания" драйвера в поток. Новый модуль, выдвинутый в поток, попадает на вершину любых существующих модулей (т. е. он связывается с заголовком потока). Список модулей — это LIFO (Last In First Out, последний пришел, первый вышел), т. е. стек. Модули выдвигаются в потоки, когда драйвер установлен в ядре. Подобно традицион
ным драйверам устройств, модули выполняются в режиме ядра и связываются с ядром, когда ядро строится. Некоторые более новые терминальные драйверы реализуются при помощи архитектуры STREAMS, а не через только что описанный механизм С-списков. Взаимодействие процессов Ядро UNIX использует ряд структур данных и алгоритмов, чтобы поддержать конвейеры и сокеты. Конвейеры Реализации конвейеров значительно отличаются в System V и BSD, поэтому начнем с описания конвейеров System V. Конвейеры System V Release 3 В System V есть два вида конвейеров: именованные конвейеры (named pipes) и безымянные конвейеры (unnamed pipes). Именованные конвейеры создаются при помощи системного вызова pipe (), а безымянные конвейеры — при помощи системного вызова mknod (). Данные, записанные в конвейер, хранятся в файловой системе, как показано на рис. 14.55. Когда создается любой вид конвейера, ядро выделяет inode, два элемента открытых файлов и два дескриптора файла. Область пользователя Таблица открытых файлов Таблица активных inode Диск Рис. 14.55. Конвейеры системы V.3 хранятся в файловой системе
Первоначально inode описывает пустой файл. Если конвейер именованный, то создается жесткая связь от указанного каталога до inode конвейера; иначе, никакая жесткая связь не создается, и конвейер остается анонимным. Структуры данных конвейера Ядро поддерживает текущую позицию записи и текущую позицию чтения для каждого конвейера в его inode, а не в элементе таблицы открытых файлов. Это гарантирует, что каждый байт в конвейере читается точно одним процессом. Ядро также хранит след количества процессов, читающих из конвейера и записывающих в него. Ядру требуются оба счетчика, чтобы должным образом обработать системный вызов close о . Запись в конвейер Когда данные пишутся в конвейер, ядро выделяет блок диска и увеличивает по мере необходимости текущую позицию записи до тех пор, пока не будет выделен последний прямой блок. По причинам простоты и эффективности конвейер никогда не выделяет косвенные блоки; это запрещение ограничивает размер конвейера приблизительно до 40 Кбайт в зависимости от размера блока файловой системы. Если бы запись в конвейер вышла за пределы его возможностей хранения, процесс запишет в конвейер, сколько сможет, и затем заснет до тех пор, пока некоторые из данных не будут откачены процессами-читателями. Если процесс-писатель пробует записывать мимо конца последнего прямого блока, позиция записи "заворачивается" к началу файла, начиная со смещения 0. Таким образом, с прямыми блоками обращаются подобно круговому буферу. Хотя может показаться, что использование файловой системы для реализации конвейера будет медленным, следует вспомнить, что блоки диска помещаются в буферный пул, поэтому большинство конвейерного ввода/вывода буферируется в ОЗУ. Чтение из конвейера По мере того как данные читаются из конвейера, текущая позиция чтения обновляется. Ядро гарантирует, что позиция чтения никогда не настигает позицию записи. Если процесс пытается читать из пустого конвейера, он переводится в состояние сна до тех пор, пока вывод не станет доступным. Закрытие конвейера Когда дескриптор файла конвейера закрывается, ядро осуществляет следующую специальную обработку: □ модернизирует счетчик конвейерных процессов читателей и писателей; □ если счетчик процессов-писателей уменьшается до 0, и есть процессы, пробующие читать из конвейера, они возвращаются из системного вызова read () с состоянием ошибки;
□ если счетчик процессов-читателей уменьшается до 0, и есть процессы, пробующие писать в конвейер, им посылается сигнал; □ если счетчики процессов-читателей и процессов-писателей уменьшаются до 0, блоки всего конвейера освобождаются, и текущие позиции записи и чтения в inode устанавливаются повторно. Если конвейер безымянный, inode также освобождается. Конвейеры System V Release 4 Начиная с System V Release 4, конвейеры в UNIX были реализованы с использованием архитектуры STREAMS. Конвейеры BSD Конвейеры BSD реализованы в терминах сокетов. Дескрипторы файла записи и чтения связаны с анонимной конечной точкой сокета внутри домена системы UNIX. Сокеты Полное описание реализации сокетов было бы довольно длинным, требуя объяснения работы адресации в Интернете, маршрутизации и взаимодействия. По этой причине представлен только краткий обзор системы сокетов в терминах управления ее памятью и интерфейса с интернет-протоколами. Для более глубокого обсуждения сокетов, см. главу 13, а также [Stevens 1998]. Управление памятью Данные, передаваемые между конечными точками сокета, буферируются посредством динамической системы распределения памяти, которая использует пакеты данных фиксированного размера, называемые mbuf. Каждый mbuf имеет длину 128 байтов и разбит следующим образом: □ буфер размером 112 байтов; □ область, которая записывает размер данных в буфере; □ поле, которое записывает смещение данных в буфере. Процедуры, читающие буферы, могут просто разбирать заголовки протокола, подгоняя размер данных и поля смещения, а не перемещать данные в памяти. Менеджер памяти mbuf достаточно эффективен, и некоторые другие процедуры ядра используют его для целей, не связанных с сокетом.
Сокеты и таблица открытых файлов Когда создается сокет с помощью системного вызова socket о, система в свою очередь создает структуру сокета, которая записывает всю информацию, имеющую отношение к сокету, включая следующие поля: □ домен сокета; □ протокол сокета; □ указатель на список mbuf сокета. Чтобы связывать систему дескрипторов файла с системой сокета, ядро хранит указатель от сокетного элемента таблицы открытых файлов к связанной с ним структуре сокета. К этой структуре происходит обращение, когда выполняется сокетный ввод/вывод. Рис. 14.56 показывает диаграмму этого взаимодействия. Область пользователя Таблица открытых файлов Таблица активных inode Рис. 14.56. Сокеты Запись в сокет Данные, записанные в сокет через системный вызов write (), помещаются в выходной список mbuf для передачи модулем протокола.
Чтение из сокета Данные, которые поступают в модуль протокола, помещаются на входной список mbuf для процесса. Когда процесс выполняет системный вызов read (), данные передаются от входного списка mbuf в адресное пространство процесса. Обзор главы Перечень тем В этой главе мы рассмотрели: П иерархическое представление подсистем ядра; П различие между пользовательским режимом и режимом ядра; П реализацию системных вызовов и обработчиков прерывания; П физическую и логическую компоновку файловой системы; □ inode; П алгоритм, который использует ядро для перевода путевого имени в номер inode; П иерархию процессов; П шесть состояний процесса; П выделение центрального процессора планировщиком; П управление памятью и MMU; П подсистему ввода/вывода, включая буферизацию; П взаимодействие процессов через конвейеры и сокеты. Контрольные вопросы 1. Почему ядро поддерживает множественные очереди приоритета? 2. Почему системные вызовы используют режим ядра? 3. Что происходит, когда прерывание прерывает другое прерывание? 4. Как конструкция современных дисков пытается увеличить полную емкость хранения? 5. Где хранится имя файла? 6. Какую информацию содержит суперблок?
7. Как UNIX избегает использования Bad-блоков? 8. Почему inode 2 является специальным? 9. Каково значение термина "магический номер"? 10. Каково значение термина "контекстное переключение"? 11. Какая информация хранится в пользовательской области процесса? 12. Если сигнал посылается процессу, который приостановлен, где сигнал хранится? 13. Кратко опишите отображение памяти, которое выполняет MMU. 14. Что делает демон страницы? 15. Как UNIX копирует данные родителя своему дочернему процессу? 16. Каково значение термина "отложенная запись"? 17. В чем назначение таблицы открытых файлов? 18. Каково использование таблицы переключения ввода/вывода? 19. Почему терминальный драйвер UNIX использует С-списки? 20. Каково основное различие реализации конвейера в BSD и System V? Упражнения 14.1. Используя утилиту ps, найдите процесс в системе с самым низким ID процесса (PID). Что это за процесс и почему он имеет данный PID? [Уровень: легкий.] 14.2. Суперблок содержит много важной информации. Предложите некоторые способы минимизировать разрушение в файловой системе в случае, когда суперблок искажен. [Уровень: средний.] 14.3. Когда создаются очень маленькие файлы, теряется некоторое пространство диска из-за минимального размера выделяемой единицы. Это потраченное впустую пространство называется внутренней фрагментацией. Предложите некоторые способы минимизировать внутреннюю фрагментацию. [Уровень: средний.] 14.4. Отложенная запись обычно заставляет измененный буфер сбрасываться на диск, когда это необходимо ОЗУ, а не в момент закрытия файла. Альтернативный метод состоит в том, чтобы сбрасывать на диск измененные буферы, когда трафик диска низкий, таким образом, улучшая использование времени простоя. Критически проанализируйте эту стратегию. [Уровень: средний.]
14.5. Низкоприоритетное прерывание может быть потеряно, если оно происходит во время обслуживания прерывания с более высоким приоритетом. Как вы думаете, каким образом системное программное обеспечение взаимодействует с потерянным прерыванием? [Уровень: высокий.] Проекты 1. Исследуйте некоторые операционные системы, например, Mach, Plan 9 и Windows NT. Как они соотносятся с UNIX? [Уровень: средний.] 2. Если вы знаете объектно-ориентированную технику программирования, спроектируйте основное объектно-ориентированное ядро, которое обеспечивает системные услуги через набор системных объектов. Чем конструкция вашего ядра отличается от ядра UNIX? [Уровень: высокий.]
Глава 15 Системное администрирование Мотивация Необходимо выполнять администрирование системы UNIX, чтобы позволить ей работать без сбоев. При отсутствии этих действий файлы могут быть потеряны навсегда, утилиты устареть, а система работать медленнее своей потенциальной скорости. Многие инсталляции UNIX достаточно велики и не рассчитаны на один полный рабочий день системного администратора. Независимо от того, собираетесь ли вы выполнять обязанности системного администратора, эта глава содержит ценную информацию о том, как проследить за установкой UNIX. Предпосылки Чтобы понимать материал данной главы, следует прочитать главы 1 и 2. Также поможет изучение глав 4 и 9. Задачи В этой главе описываются главные задачи, которые должен выполнять системный администратор, чтобы поддерживать беспрепятственную работу ОС UNIX. Изложение Информация представлена в форме нескольких небольших разделов.
Утилиты Рассматриваются следующие утилиты: ас getty newfs accton halt рас config ifconfig reboot cron last route df mkfs shutdown du mknod su fsck netstat Общие сведения о системном администрировании Задачи, которые системный администратор должен выполнять, чтобы поддерживать правильную работу системы UNIX, включают: □ запуск и остановку системы; □ поддержку и восстановление предыдущего состояния файловой системы; П поддержку учетных записей пользователей; □ установку операционной системы и прикладного программного обеспечения; □ установку и конфигурирование периферийных устройств; □ управление интерфейсом сети; □ автоматизацию повторяющихся задач; □ выполнение учетных записей системы; □ конфигурирование ядра; □ проверку системы безопасности. Почти все эти задачи требуют, чтобы администратор находился в режиме привилегированного пользователя (root), т. к. он получает доступ и изменяет важную информацию. Если вы не имеете доступа к паролю привилегированного пользователя, то должны просто использовать ваше воображение. Однако даже в этом случае будьте уверены, что описываемые функции позволят вам лучше понять работу UNIX. Чтобы глубоко рассмотреть каждую из перечисленных тем, потребовалась бы целая книга, так что эта глава просто представляет краткий обзор администрирования системы. (Для более детальной информации см. [Nemeth 2000]).
Как стать привилегированным пользователем Привилегированный пользователь (root) имеет специальный пользовательский ID (0) с разрешением делать фактически все, что угодно в системе UNIX. Поэтому очень важно, что не каждый пользователь имеет доступ к системе, особенно со злонамеренными наклонностями. Большинство задач администрирования требует, чтобы вы имели полномочия привилегированного пользователя, и есть два способа получить их: □ войти в систему как root (это имя привилегированного пользователя); □ использовать утилиту su, описанную в главе 3, чтобы создать дочерний shell, принадлежащий root. Хотя первый метод является прямым, есть некоторые опасности, связанные с ним. Если вы регистрируетесь в системе как root, каждая отдельная команда, которую вы будете выполнять даже с ошибками, получит права привилегированного пользователя. Вообразите печать rm -г*.bak вместо rm-r*.bak в то время, когда текущим является каталог /! Из-за этой проблемы настоятельно рекомендуется всегда использовать второй метод: регистрироваться в UNIX, как обычный пользователь, и становиться привилегированным пользователем только, когда есть необходимость. Другое преимущество второго метода состоит в том, что утилита su регистрирует в системе использующего ее и лишь в то время, когда она работает. В коллективе, где может быть несколько системных администраторов, иногда трудно удостовериться, что пароль привилегированного пользователя дается только тем, кто действительно нуждается в нем. Наличие регистрации для проверки помогает увидеть, кто использует root-привилегии. Запуск UNIX В зависимости от происхождения вашей версии UNIX есть два режима, в которых ОС может работать. Системы UNIX, базирующиеся на BSD, обычно работают в одном из двух режимов. □ Однопользовательский режим (single-user mode) означает, что единственный пользователь может регистрироваться в системе с системной консоли и выполнять команды shell. В этом режиме система управляет очень немногими системными демонами и вообще используется только для обслуживания системы, резервного копирования и изменения конфигурации ядра. □ Многопользовательский режим (multiuser mode) означает, что много пользователей могут зарегистрироваться в системе с различных терминалов.
Данный режим имеет активную систему демонов и является эксплуатационным режимом по умолчанию для большинства систем. Версии UNIX, основанные на System V, работают на различных уровнях управления (run levels). Уровни управления описывают, как работает система (подобно однопользовательскому и многопользовательскому режимам в BSD), но предусматривают большую степень детализации в опциях. Как правило, системы имеют восемь уровней управления: 0—6 и s для однопользовательского режима. Каждый рабочий уровень может иметь свои собственные специфические скрипты загрузки и быть сконфигурирован, чтобы удовлетворить локальные потребности пользователя. Например, можно сконфигурировать рабочий уровень 3, чтобы он обладал всем, что ваша система обычно выполняет в эксплуатационном режиме, кроме приложения базы данных об изделиях вашей компании. Это помогло бы обслуживать базу данных, позволяя другим пользователям использовать остальную часть системы. Некоторые машины предоставляют возможность выбирать режим путем переключения выключателей на передней панели; другие машины по умолчанию входят в многопользовательский режим, если последовательность загрузки не прерывается нажатием комбинации клавиш <Ctrl>+<C>. Единственный способ узнать, что делает ваша собственная система, это прочитать руководство. Когда вы включаете компьютер, происходит следующая последовательность событий: 1. Аппаратура производит диагностическое самотестирование. f 2. Ядро UNIX загружается с корневого устройства. 3. Ядро начинает работать и инициализируется. 4. Ядро запускает init, первый процесс в пользовательском режиме. Процесс init запускается, проверяя непротиворечивость файловой системы, используя утилиту fsck. Если был выбран однопользовательский режим, init создает Bourne shell, связанный с системной консолью. Если был выбран многопользовательский режим, init осуществляет следующие действия: □ запускает скрипты загрузки системы, которые выполняют задачи инициализации, например, старт почтового демона и очистка каталога /tmp; □ создает процесс getty для каждого терминала в файле /etc/ttytab, который содержит одну строку информации на терминал в системе, включая его скорость двоичной передачи и путевое имя. Названия скриптов загрузки изменяются с различными версиями UNIX. Системы, базирующиеся на BSD, обычно выполняют /etc/rc, чтобы запустить стандартные демоны UNIX, и /etc/rc.local, чтобы начать поддержку услуг в местном масштабе. System V объединяет файлы загрузки в более слож
ный набор файлов, группируя их по подсистемам (типа организации сети, диска и т. д.). Эти файлы хранятся в каталоге /etc/rc.d. Процесс getty прослушивает активность на связанном с ним терминале и заменяет себя процессом login, если он обнаруживает чью-то попытку зарегистрироваться в системе. Программа login выводит приглашение для имени пользователя и пароля, сверяет их с элементами в файле /etc/passwd и заменяет себя программой запуска пользователя, если пароль указан верно. Обычно программа запуска — это shell. Когда пользователь выходит из системы, init получает сигнал sigchld от '’умирающего" shell, удаляет пользователя из файла /etc/utmp (который содержит список всех текущих пользователей) и затем добавляет элемент в файл /var/adm/wtmp (который содержит список всех недавних регистраций в системе и выходов из нее). Наконец, init создает новый процесс getty для освобожденного терминала. Поведение процесса init может быть изменено путем посылки ему одного из перечисленных ниже сигналов. □ Сигнал sighup заставляет init повторно просматривать файл /etc/ttytab и создавать процессы getty для всех терминалов в файле, который им необходим. Сигнал также "убивает" getty-процессы, которые не имеют связанного с ними терминала. Таким образом, sighup позволяет добавлять и удалять терминалы без повторной загрузки системы. □ Сигнал sigterm заставляет init перевести UNIX в однопользовательский режим. □ Сигнал sigtstp предписывает init не создавать новый процесс getty, когда пользователь выходит из системы. Это позволяет системе постепенно сокращать терминалы. Для функционирования UNIX процесс init жизненно важен, поскольку он ответственен за создание и поддержку входных shell. Если, по любой причине, процесс init "умирает", происходит автоматическая повторная загрузка системы. Остановка системы Современный компьютер хорошо служит своему пользователю, если все время работает; его включение и выключение вызывает проблемы. Однако есть некоторые обстоятельства, при которых лучше всего выключить компьютер. Например, если наступает шторм, вы должны отключить ваш компьютер от источника питания, чтобы избежать скачков высокого напряжения. Систему UNIX нельзя выключить непосредственно. Вместо этого необходимо запустить одну ИЗ утилит: shutdown, halt ИЛИ reboot. 26 Зак. 786
Утилита shutdown может использоваться, чтобы или остановить UNIX, перевести ОС в однопользовательский или многопользовательский режим. Утилита выводит предупреждающее сообщение перед закрытием так, чтобы пользователи могли выйти из системы перед изменением состояния системы. Утилита halt вызывает немедленное закрытие системы без предупреждающих сообщений. Утилита reboot служит для выполнения повторной загрузки системы. Синтаксис shutdown -hkrn время [сообщение] Утилита shutdown закрывает систему постепенно. Время закрытия может быть определено одним из трех способов (в аргументе время): • now: система закрывается немедленно; • +minutes: система закрывается через указанное число минут; I • hours minutes: система закрывается в указанное время (24-часовой формат). Заданное сообщение предупреждения (или сообщение по умолчанию, если ничего другого не определено) показывается периодически, когда подходит время закрытия. Регистрационные имена выводятся за пять минут до закрытия. Когда наступает время закрытия, shutdown выполняет системный вызов sync и затем посылает сигнал sigterm в процесс init. Это заставляет init перевести UNIX в однопользовательский режим. Опция -ь заставляет shutdown выполнить halt вместо посылки сигнала. Опция -г приводит к выполнению reboot вместо посылки сигнала. Опция -п мешает shutdown выполнить по умолчанию его sync. Интересна опция -к: она заставляет shutdown вести себя так, как будто собираются закрывать систему, но когда наступает время закрытия, она ничего не делает. (Опция к происходит от англ, just kidding — просто шучу.) Синтаксис halt Утилита halt исполняет системный вызов sync и затем останавливает ЦП. Она добавляет запись закрытия к файлу /var/adm/wtmp.
Синтаксис reboot -q Утилита reboot заканчивает все пользовательские процессы, исполняет системный вызов sync, загружает ядро UNIX с диска, инициализирует систему и затем переводит UNIX в многопользовательский режим. Запись reboot добавляется в конец файла /var/adm/wtmp. Чтобы осуществить быструю повторную загрузку, следует использовать опцию -q. Она указывает reboot не "убивать" текущие процессы перед повторной загрузкой. г Поддержка файловой системы Данный раздел описывает связанные с файловой системой задачи администрирования: □ обеспечение целостности файловой системы; □ проверка использования диска; □ назначение квот; □ создание новых файловых систем. Целостность файловой системы Одно из первых действий, которые выполняет процесс init, — это запуск утилиты fsck, предназначенной для проверки целостности файловой системы. К счастью, утилита fsck очень хороша при исправлении ошибок. Это означает, что пользователь, вероятно, никогда не станет исправлять ошибки диска вручную, как это делалось в "старые добрые времена". Синтаксис fsck -р [файловаяСистема]* Утилита fsck (от англ, file system check — проверка файловой системы) просматривает указанные файловые системы и проверяет их на целостность. Виды ошибок целостности, которые могут существовать, включают следующее: • блок отмечен, как свободный в битовой карте, но также упомянут в inode; • блок отмечен, как используемый в битовой карте, но никогда не упоминается в inode;
• больше, чем один inode ссылается на один и тот же блок; • номер блока недействителен; • счетчик связей inode неверен; • используемый inode не упоминается ни в каком каталоге. Для получения дополнительной информации об inode см. главу 14. Если используется опция -р, утилита fsck автоматически исправляет любые ошибки, которые находит. Без опции -р она выводит приглашение пользователю для подтверждения любых исправлений, которые предлагает. Если fsck находит блок, который используется, но не связан с указанным файлом, она соединяет блок с файлом, чье имя равно номеру inode блока в каталогах /lost и /found. Если файловая система не определена, fsck проверяет стандартную файловую систему, указанную в /etc/fstab. Использование диска Ошибки диска редко встречаются и вообще исправляются автоматически. С другой стороны, проблемы использования диска являются общими. Многие пользователи обращаются с файловой системой, как будто она бесконечно большая, и создают огромное число файлов необдуманно. Когда я преподавал UNIX в Техасском университете в Далласе (University of Texas at Dallas), диски неизменно заполнялись в последний день семестра, т. к. все студенты пытались завершить свои проекты. Студенты пробовали сохранить свою работу в редакторе vi, а тот отвечал сообщением "Диск полон" ("disk full"). Когда же студенты выходили из vi, они обнаруживали, что их файлы удалены. Чтобы избежать истощения пространства диска, благоразумно запускать скрипт shell из утилиты cron, которая периодически выполняет утилиту df, чтобы проверить доступное пространство диска. Синтаксис df [-к] [ - Р ] [файловаяСистема]* Утилита df показывает таблицу используемого и доступного пространства диска для указанных смонтированных файловых систем. Если никакая файловая система не задана, описываются все смонтированные файловые системы. Формат вывода df слегка отличается в разных версиях UNIX. Традиционная df, основанная на версии System V, показывает количество 512-байтовых блоков, в то время как базирующиеся на BSD и более поздние версии df обычно выводят количество блоков в терминах 1024-байтового (1 Кбайт) блока. Расположение информации также изменяется от версии к версии, но почти все версии имеют аргу
менты, которые позволяют задать формат и информацию, которую пользователь желает увидеть. Опция -к позволяет показать счетчик блоков размером 1024 байта. Опция -р служит для вывода сведений в формате POSIX (близко к формату BSD). В некоторых системах, когда используется -к, предполагается -р. Вот пример df в действии: $ df ...выводит информацию обо всей файловой системе. Filesystem kbytes used avail capacity Mounted on /dev/sd3a 16415 10767 4006 73% / /dev/sd3g 201631 125513 55954 69% /usr /dev/sd3d 60015 34773 19240 64% /export $ Та же самая утилита df произвела бы другой вывод в системе UNIX, бази- рующейся на System V: $ df / (/dev/sd3a ) : 21£?34 blocks 14922 files /usr (/dev/sd3g ): 251026 blocks 108277 files /export (/dev/sd3d ): 69546 blocks 20844 files $ Обратите внимание на отсутствие заголовка во втором формате. Это может быть полезно, когда производится конвейеризация вывода в другую команду. (Иначе следует удалить заголовок, если обращаться с каждой строкой, как с информацией файловой системы.) Заметьте также, что количество используемых блоков различно в разных версиях, потому что не одинаков размер блока, используемого для подсчета. Во второй системе использование команды df -Pk сгенерировало бы вывод, подобный первому примеру. Пользователь может получать информацию об определенной файловой системе, явно определяя ее имя или точку монтирования, как в следующих командах: $ df /dev/sd3a ...вывести конкретную файловую систему. Filesystem kbytes used avail capacity Mounted on /dev/sd3a 16415 10767 4006 73% / $ df / ..точка монтирования также работает • Filesystem kbytes used avail capacity Mounted on /dev/sd3a 16415 10767 4006 73% / $ _
Также легко определить, сколько осталось дискового пространства на устройстве, на котором находится домашний каталог пользователя: $ df . Filesystem kbytes used avail capacity Mounted on /dev/sd3d 4194304 4194304 . 0 100% /home Если утилита df сообщает, что диск полон больше, чем на 95%, пользовательский скрипт мог бы обнаружить это и посылать некоторое предупреждающее сообщение. Даже лучше, если скрипт запустит утилиту du, чтобы определять, какие пользователи занимают большинство дискового пространства, и автоматически посылать этим пользователям почтовое сообщение, предлагающее удалить некоторые файлы. Синтаксис du [—s] [-к] [имяФайла]* Утилита du показывает количество килобайт (BSD) или 512-байтовых блоков (System V), которые выделены каждому из указанных файлов. Если имя файла ссылается на каталог, его файлы рекурсивно характеризуются. Утилита du с опцией -s показывает только общее количество для каждого файла. Когда добавляется опция -к, пространство всегда сообщается в килобайтах. Если никакое имя файла не определено, то просматривается текущий каталог. В представленном ниже примере утилита du применяется для выяснения, сколько килобайт занимают текущий каталог и все его файлы, после чего появляется пофайловое разбиение диска: $ du -s ...получить общую сумму текущего каталога. 9291 . $ du ... получить пофайловый листинг. 91 ./proj/fall.89 158 ./proj/summer.89/proj4 159 ./proj/summer.89 181 ./proj/spring.90/proj2 21 ./proj/spring.90/proj1 204 ./proj/spring.90 455 ./proj ...другие файлы выводились здесь.
38 ./sys5 859 ./sys6 9291 . $ Назначение квот Некоторые системы позволяют системному администратору устанавливать квоты диска для индивидуальных пользователей. Можно определить максимальное количество файлов и блоков, которые позволено создать конкретному пользователю. Довольно сложно добавлять квоты, ведь выполнение этого может повлечь за собой переконфигурирование ядра, обновление файла /etc/ге, изменение файла /etc/fstab и создание файла управления квотами. Квоты реализованы по-разному в различных версиях UNIX, так что лучше всего обратиться за справкой к документации своей системы. Создание новых файловых систем Если вы покупаете новый диск, то должны выполнить следующие работы прежде, чем ваша файловая система сможет использовать его: 1. Отформатировать носитель информации. 2. Создать новую файловую систему на носителе информации. 3. Смонтировать диск в корневую иерархию. Изготовитель устройства может снабдить вас утилитой форматирования. В этом случае используйте ее, чтобы выполнить шаг 1. Если ваша версия UNIX имеет команду format, это также может работать. Конкретные средства являются индивидуальными для системы, так что вновь обратитесь за справкой к документации своей системы о форматировании носителя информации. Далее создайте файловую систему на носителе информации, используя утилиты mkfs ИЛИ newfs. Синтаксис mkfs специальныйФайл [размерСектора] Утилита mkfs создает новую файловую систему на указанном специальном файле. Новая файловая система состоит из суперблока, списка inode, корневого каталога и объединенного каталога lost и found. Файловая система строится с секторами размером размерСектора. Эту утилиту может использовать только привилегированный пользователь (root).
Поскольку маловероятно, что вы знаете правильное значение для параметра размерСектора без осведомления в руководстве изготовителя, была разработана утилита newfs (доступная в большинстве (но не во всех версий) UNIX), как дружественный внешний интерфейс утилиты mkfs. Обратите внимание, что newfs может работать, если геометрические сведения о носителе информации указаны в файле /etc/disktab. Синтаксис newfs special File deviceType i • ' Утилита newfs вызывает mkfs После просмотра sectorCount deviceType в файле /etc/disktab, который содержит информацию о стандартных характеристиках устройства. Как только файловая система будет создана, ее можно связать с файловой системой корня через утилиту mount, описанную в главе 3. Создание резервных копий файловых систем Создание резервной копии информации файловой системы наиболее важно и чаще всего игнорируется системным администратором. Обидно тратить время на эту работу, т. к. предполагается, что никогда не возникнет нужда в резервной копии информации. Но точно так же, как покупка страховки для автомобиля: вы должны делать это, потому что, если она когда-либо вам понадобится, будет масса проблем при ее отсутствии. Процедура и утилиты для поддержки файловой системы описаны в главе 3. Поддержка пользовательских учетных записей Одна из самых обычных задач системного администрирования — это добавление нового пользователя в систему. Чтобы сделать это, нужно: □ добавить новый элемент в файл паролей; □ добавить новый элемент в файл групп; □ создать домашний каталог для пользователя; □ предоставить пользователю некоторые соответствующие файлы запуска.
Файл паролей Каждый пользователь системы имеет элемент в файле паролей (обычно /etc/passwd), в формате username:password:userid:groupld:personal:homedir:startup где каждое поле имеет значение, описанное в табл. 15.1. Таблица 15.1. Поля файла паролей UNIX Поле Описание username Регистрационное имя пользователя password Зашифрованная версия пароля пользователя userid Уникальное целое, выделенное пользователю groupld Уникальное целое, относящееся к группе пользователя personal Описание пользователя, которое выводится утилитой finger homedir Домашний каталог пользователя startup Программа, которая выполняется для пользователя во время регистрации в системе Так как поле пароля — это зашифрованное значение, помещение любого единственного символа в это поле эквивалентно отмене регистрационной записи в рассматриваемом элементе. Нет такой строки, которую вы могли бы напечатать и которая не будет зашифрована. Например, звездочка (*) в тексте (означает любой вводимый символ) будет соответствовать какому-нибудь символу (или строке) в поле пароля после шифрования. Вот фрагмент файла паролей из реальной жизни: $ head -5 /etc/passwd ...посмотреть первые пять строк. root:refsmtio:0:0:Operator:/:/bin/csh daemon:*:1:1::/: sync:*:1:1::/:/bin/sync sys:*:2:2::/:/bin/csh bin:*:3:3::/bin: $ _ Я использовал утилиту grep, чтобы найти собственный элемент: $ grep glass /etc/passwd ...найти мою строку. glass:dorbnla:496:62:Graham Glass:/home/glass:/bin/ksh
Файл групп Чтобы добавить нового пользователя, следует решить, в какой группе будет находиться пользователь, а затем найти файл групп, чтобы выяснить связанный с пользователем ID группы. Приведем пример добавления нового пользователя по имени Саймон в группу cs4395. Каждая группа в системе имеет элемент в файле групп (обычно /etc/group) в формате groupname:groupPassword:groupld:users где каждое поле определено в табл. 15.2. Таблица 15.2. Поля файла групп UNIX Поле Описание groupname Имя группы groupPassword Зашифрованный пароль для группы (не используется и часто заполняется звездочкой (*)) groupld Уникальное целое, относящееся к группе users Список пользователей в группе, отделенных запятыми Вот отрывок из реального файла /etc/group: $ head -5 /etc/group ...посмотреть начальный файл групп. cs4395:*:91:glass cs5381:*:92:glass wheel:*:0:posey,aicklen,shrid,dth,moore,lippke,rsd,garner daemon:*:1:daemon sys:*:3: $ _ Как видно, группа cs4395 имеет связанный ID группы с номером 91. Чтобы добавить Саймона в качестве нового пользователя, ему выделен уникальный пользовательский ID-номер 10 и ID группы 91, а поле пароля оставлено пустым. Вот как его элемент выглядел: simon::101:91:Simon Pritchard:/home/simon:/bin/ksh Поскольку элемент был добавлен к файлу паролей, сведения о Саймоне добавлены в конец списка cs4395 в файле /etc/group, создан его домашний каталог и переданы некоторые файлы запуска по умолчанию, такие как .kshrc и .profile. Эти файлы скопированы из каталога /usr/template, который я создал для хранения версий по умолчанию пользовательских файлов запуска.
Команды таковы: $ mkdir /home/simon $ ср /usr/template/- /home/simon $ chown simon /home/simon /home/simon/.* $ chgrp cs4395 /home/simon /home/simon/.* $ ...создать домашний каталог. ...копировать файлы запуска. ...установить владельца. ...установить группу. Наконец, была выполнена регистрация пользователя Саймона и запущена утилита passwd для изменения его пароля на разумное значение по умолчанию. Чтобы удалить пользователя, выполните описанные действия в обратном порядке: удалите элемент пароля пользователя, элемент файла групп и домашний каталог. Установка программного обеспечения Установка нового программного обеспечения или модернизация существующего является важной задачей системного администратора. Однако детали могут сильно изменяться от узла к узлу. Если некто поддерживает большой узел со множеством серверов на базе сетевой файловой системы (Network File System, NFS), он может установить приложение на сервере так, чтобы рабочие станции могли получить доступ к нему из одной точки. На меньшем узле или при наличии более дорогого программного обеспечения его можно установить только на тех машинах, где это необходимо. Давняя традиция для UNIX была такой, что локальное программное обеспечение (ПО) должно было устанавливаться в каталоге /usr/local. Это делало очевидным отсутствие ПО в дистрибутиве UNIX. Через какое-то время продавцы изменили имя "стандартного” каталога, который предназначен для прикладного программного обеспечения (например, Sun использует /opt для дополнительного программного обеспечения, a AIX — /usr/lpp для лицензированных программных продуктов). Можно определить один или несколько каталогов, но важно поддержать некоторую логику в структуре так, чтобы легко можно было найти необходимые программы. С философской точки зрения, есть два способа установить программное обеспечение. Один подход — это создать каталог для программного обеспечения и поместить все. что требуется (кроме, возможно, системных файлов), в этот каталог. Например, если создается приложение pianoman, можно записать инструментальные средства установки для него в предположении, что оно будет установлено в каталог /opt/pianoman. Если пользователь хочет установить его в каталог /usr/local/pianoman, он должен иметь возможность сделать это. Другой подход состоит в том, чтобы поместить программное обеспечение
только в основной каталог, а любые необходимые конфигурационные, заголовочные или библиотечные файлы записать в более централизованное место для этих типов файлов. Пример здесь может быть такой, требуемое программное обеспечение для построения приложения может находиться в каталоге /usr/local/pianoman, но когда оно установлено в системе, исполняемый файл копируется в каталог /usr/local/bin/pianoman, а используемая им библиотека помещается в файл /usr/local/lib/pianolib.a. Этот метод имеет преимущества и недостатки. Главное преимущество состоит в том, что пользовательское сообщество не должно добавлять другой каталог к определению $ратн, т. к. двоичный код находится в "известном" месте (/usr/local/bin), которое уже присутствует в списке путей. Главное неудобств^ в том, что пользователь имеет файлы, распределенные по многим другим каталогам помимо /usr/local/pianoman. Предпочтение пользователя в установке программного обеспечения может зависеть от подразумеваемого метода, который выбрал разработчик программного обеспечения. Любой хороший инструментарий установки (скрипт или программа) должен позволить изменять предлагаемое по умолчанию место установки. Несмотря на то, что легче согласиться с предлагаемыми установками, если они разумным образом вписываются в общее окружение, пользователь свободен в их конфигурировании. Любое программное обеспечение, которое твердо кодирует место установки, является плохо разработанным. Утилиты tar и cpio — это два наиболее популярных метода создания образа установки для системы UNIX. Преимущество здесь в том, что обе утилиты уже существуют в большинстве версий UNIX. Скрипт shell, который использует tar и- другие стандартные команды UNIX для установки программного обеспечения, может работать в большинстве систем UNIX без требования другого программного обеспечения. Некоторые продавцы UNIX обеспечивают собственные улучшенные средства установки ПО (например, HP-UX имеет утилиту swinstaii, a AIX — утилиту instaiip). Конечно, эти средства лучше, чем общие команды UNIX, но неудобство состоит в том, что при их применении пользователь "запирает" себя в одной архитектуре. Конечно, можно писать инструкции установки для каждого средства, но это влечет больше работы, чем написание всего одного метода установки для всех UNIX-платформ, которые ваше приложение поддерживает. Лучший выбор метода установки сильно зависит от целевых клиентов, их платформ и опыта работы с инструментами UNIX. Как администратор системы UNIX, вы столкнетесь почти со всеми возможностями и должны знать, как лучше всего интегрировать приложение в локальное окружение.
Периферийные устройства Предположим, вы только что купили новое устройство и хотите присоединить его к своей системе. Как вы его установите? Кроме того, если это — терминал, какие специфические для терминала файлы должны быть обновлены? Следующие разделы представляют краткий ‘обзор установки устройства и список связанных с терминалом файлов. Установка драйвера устройства Чтобы система была способна "говорить" с новым устройством, аппаратура должна быть подключена, а программное обеспечение установлено или активизировано. Некоторые системы требуют, чтобы новые драйверы устройства были загружены в ядро, и ядро должно быть переконфигурировано. Другие могут использовать динамически загружаемые драйверы устройств, которые загружаются в ядро при обращении к устройству. Основные шаги установки устройства таковы: 1. Установить драйвер устройства, если он в настоящее время не в ядре и если загружаемый драйвер устройства не используется. 2. Определить старший и младший номера устройства. 3. Использовать утилиту mknod, чтобы связать имя файла в каталоге /dev с новым устройством. Как только драйвер устройства установлен, и старший и младший номера известны, следует запустить утилиту mknod, чтобы создать специальный файл. Синтаксис mknod имяФайла [с] [Ь] старшийНомер младшийНомер mknod имяФайла р Утилита mknod СОЗДаеТ специальный файл имяФайла в файловой системе. Первая форма mknod позволяет привилегированному пользователю (root) создавать либо специальный символьный файл (с), либо специальный блочный файл (ь) с указанными старшим и младшим номерами. старшийНомер идентифицирует класс устройства, а младшийНомер — экземпляр устройства. Вторая форма mknod создает именованный конвейер и может использоваться любым пользователем.
В представленном ниже примере установлен 13-й экземпляр терминала, чей старший номер был равен 1: $ mknod /dev/tty!2 с 1 12 ...заметьте: 13-й экземпляр, а индекс 12. $ _ Буква с указывает, что терминал — это символьное устройство. В следующем примере задается первый экземпляр дискового устройства, чей старший номер был равен 2: $ mknod /dev/dkl b 2 0 ...заметьте: 1-й экземпляр, а индекс 0. $ _ Буква ь указывает, что диск — это блочное устройство. Старший и младший номера — это, соответственно, четвертое и пятое поле в листинге команды is -1. В следующем примере выводится длинный листинг каталога /dev: $ Is -1 /dev ...получить длинный листинг каталога устройств crw—w—w— 1 root 1, 0 Feb 13 14:21 /dev/ttyO Crw—w—w— 1 root 1, 1 Feb 13 14:27 /dev/ttyl brw—w—w— 1 root 2, 0 Feb 13 14:29 /dev/dkO crw—w—w— 1 root 3, 0 Feb 13 14:27 /dev/rmtO $ _ Терминальные файлы Несколько файлов содержат информацию, специфическую для терминала. В табл. 15.3 перечислены эти файлы и представлено краткое описание их функций. Таблица 15.3. Системные файлы UNIX, содержащие информацию о терминалах Имя Описание /etc/termcap или /etc/terminfo Кодированный список возможностей каждого стандартного терминала и кодов управления. Редакторы UNIX используют значение переменной окружения $term, как индекс в этот файл, и получают характеристики терминала пользователя /etc/ttys Список, содержащий сведения о каждом терминале в системе, вместе с программой, которая должна быть связана с ним, когда система инициализируется (обычно процесс getty). Если тип терминала постоянен и известен, эта информация также включается Zetc/gettytab Список информации о скорости двоичной передачи, которая используется процессом getty при определении, как слушать терминал регистрации в системе
Сетевой интерфейс Важный этап администрирования системы — так настроить UNIX-машину, подключенную к локальной сети, чтобы другие машины и все пользователи могли связываться с ней. Некоторые необходимые для этого основные концепции и инструментарий обсуждались в главе 9. Поскольку детали сильно изменяются в различных версиях UNIX, в данной главе будут затронуты только главные моменты. Для подробного изучения сетевого администрирования настоятельно рекомендуется прочитать [Nemeth 2000]. Если ваша сеть не является беспроводной, некоторый сетевой кабель должен быть связан с вашим UNIX-компьютером для взаимодействия с сетью. Компьютер должен иметь назначенные ему IP-адрес и имя хоста, а остальная часть сети должна знать их (путем обновления локальной таблицы хостов или базы данных DNS). Большинство систем использует утилиту ifconfig, чтобы сконфигурировать интерфейс сети. Типичный способ активизировать его таков: $ ifconfig НО 194.27.1.14 up Это вызывает назначение IP-адреса 194.27.1.14 интерфейсу ПО (это имена устройств где-нибудь в иерархии каталогов /dev) и конфигурирует его, чтобы он появился. Другие признаки IP могут также быть сконфигурированы с помощью утилиты ifconfig. Запуск утилиты можно осуществить вручную с терминала. Кроме того, она обычно находится в загрузочных скриптах, которые инициализируют все сетевые интерфейсы. Когда пользователь добавляет сетевой интерфейс, он должен указать команду конфигурации в соответствующем файле загрузки. Чтобы UNIX-машина взаимодействовала с любым другим компьютером, который непосредственно не связан с тем же самым сетевым кабелем (сегмент), должна быть определена информация маршрутизации на этой машине. Утилита route служит для определения маршрутизаторов, которые обеспечивают путь к другим сетям. Вообще, достаточно удостовериться в том, что установлен маршрут по умолчанию. Этому маршрутизатору будет послан пакет, когда пункт назначения находится не в локальной сети. Пакет посылается подразумеваемому маршрутизатору с предположением, что другие маршрутизаторы будут знать, как добраться к пункту назначения. Можно посмотреть свою текущую таблицу маршрутов при помощи утилиты netstat, указав опцию -г: $ netstat -г Routing tables Destination Gateway Flags Refs Use If
194.27.1.0 194.27.1.1 U 1 16611 ill default 194.21.1.1 UG 0 231142 ilO $ _ Эта машина "знает” о двух маршрутизаторах, 194.21.1.1 является путем по умолчанию. Любой адрес вне локальной сети и вне сети 194.27.1 будет послан 194.21.1.1 для направления к пункту назначения. Автоматизация задач Некоторые задачи системы довольно просты, но утомительны для исполнения: □ добавление учетной записи пользователя; □ удаление учетной записи пользователя; □ проверка заполнения дисков; □ генерация отчетов о входе в систему и выходе из нее; □ выполнение инкрементного резервного копирования; □ учет системных ресурсов; □ удаление старых файлов ядра; □ устранение процессов-зомби. Одна из способностей UNIX — это его возможность облегчить программисту написание простых скриптов shell или С-программ для автоматизации задач, которые выполняются вручную. Рекомендуется автоматизировать столько задач, сколько возможно. Периодически выполняемые задачи могут быть запланированы при помощи утилиты cron. Она работает по-разному в различных версиях UNIX, но вообще, позволяет запланировать программу, чтобы она выполнялась с периодичностью от одного раза в минуту до одного раза каждый год. Любые сообщения, генерируемые программой, посылаются через электронную почту пользователю, зарегистрировавшему программу. Например, ниже приведен простой скрипт, позволяющий выяснить, является ли любая из файловых систем заполненной на 95% или больше: # !/bin/sh # df | egrep "9 [56789]%1100%" Если этот скрипт регистрируется привилегированным пользователем (root) утилитой cron, которая должна работать каждый час, пока файловая система не будет заполнена на 95% или больше, ничего (во всяком случае, видимого) не происходит. Скрипт запускается каждый час, но никакой вывод не производится. Когда файловая система доходит до 95%, образец поиска,
указанный к утилите egrep, будет соответствовать строке для утилиты df и сообщать о нарушении в файловой системе, так что скрипт сгенерирует строку вывода. Строка будет послана по электронной почте привилегированному пользователю, так что в течение одного часа можно узнать о заполнении файловой системой 95% дискового пространства. Учет системных ресурсов Возможности UNIX по учету системных ресурсов позволяют отслеживать деятельность подсистем этой ОС. Каждая подсистема хранит запись своей истории в специальном файле, как описано ниже. □ Управление процессами. Запись пользовательского ID, использования памяти и ЦП каждым процессом добавляется к файлу /usr/adm/acct. Для получения отчета о содержимом данного файла можно запустить утилиту sa. Учет системных ресурсов включается утилитой accton. □ Связи. Запись времени регистрации в системе, пользовательский ID и время выхода из системы каждого соединения добавляются к файлу /usr/adm/wtmp. Утилиты ас и last могут применяться для просмотра информации в этом файле. Учет системных ресурсов связи становится возможным за счет присутствия файла /usr/adm/wtmp. □ Использование принтера. Каждый принтер записывает сведения о своих заданиях в каталоге /usr/adm. Утилита рас может генерировать отчеты из этой информации. Учет ресурсов принтеров включается записью в файле /etc/printcap. □ Другие подсистемы, вызывающие такие утилиты, как uucp и quota, также генерируют регистрационные файлы. Различные подсистемы создают файлы, которые конвертируются в отчеты скриптами shell и утилитами. Администратор системы ответственен за поддержку отчетов учета ресурсов целевых подсистем и за периодическую чистку и архивирование файлов учета системных ресурсов. Конфигурирование ядра Ядро UNIX — это программа, написанная главным образом на С, с несколькими секциями на ассемблере. Производитель UNIX включает несколько частей программного обеспечения, связанных с ядром: □ общее выполняемое ядро; □ библиотеку объектных модулей, соответствующих частям ядра, которые никогда не изменяются;
□ библиотеку модулей С, соответствующих частям ядра, которые могут быть изменены; □ файл конфигурации, описывающий текущую установку ядра; □ утилиту config, которая позволяет повторно собрать ядро, когда изменен файл конфигурации. Файлы конфигурации ядра сохраняются в каталогах /usr/conf (BSD) или /usr/src/uts/cf (System V). Части ядра, которые могут быть изменены, включают: □ драйверы устройств; □ максимальное количество открытых файлов, С-списков, квот и процессов; □ размер буферного пула ввода/вывода и таблицы системных страниц; П некоторую важную сетевую информацию; □ физические адреса устройств; □ имя машины; □ временную зону машины. Чтобы повторно скомпилировать новое ядро, необходимо следовать многошаговому процессу: 1. Отредактируйте файл конфигурации и замените значения параметров новыми. 2. Запустите утилиту config, которая создает некоторые заголовочные файлы, некоторый исходный код С и make-файл. 3. Выполните утилиту make, передавая ей имя make-файла, созданного config. Сделайте повторную компиляцию недавно созданного исходного кода и скомпонуйте его с неизменяющейся частью ядра, чтобы создать новый исполняемый код. 4. Переименуйте старое ядро UNIX. 5. Переименуйте новое ядро UNIX, чтобы поместить его на место старого. 6. Повторно загрузите систему. Проблемы безопасности Безопасность — это тема, которой можно посвятить целую книгу для досконального обсуждения. За дополнительной информацией рекомендуется обратиться к [Garfinkel 1996] и [Curry 1992], а также прочитать главу о безопасности в [Nemeth 2000]. Как вы, без сомнения, знаете, UNIX-системы не являются безопасными на 100%. Ни один компьютер, подключенный к сети, не может быть безопас
ным. Со взрывом интернет-взаимодействий количество проблем только увеличилось. Система UNIX первоначально не была разработана в расчете на безопасность. Изначально пользователи UNIX доверяли друг другу, и не было потребности в безопасности. Большинство UNIX-систем в настоящее время весьма безопасны, но это произошло только после многих лет выявления слабостей и их устранения. Среди аспектов безопасности UNIX есть такие, с которыми каждый пользователь имеет дело. Это права доступа к файлу и пароли. Данные механизмы противостоят обычным пользователям при попытках взлома, но не составляют проблемы для опытных хакеров. Лучшее, что может сделать системный администратор, — это постоянно читать об известных лазейках в системе безопасности и адаптировать известные стратегии. Чтобы поставить читателя в известность, чего следует опасаться, ниже описана пара обычных методов вскрытия паролей. □ Если пользователь имеет обычную регистрацию в системе и хочет получить регистрацию привилегированного пользователя, он начинает с получения копии одностороннего алгоритма шифрования, который использует утилита passwd UNIX. Для этого приобретается электронный словарь. Далее пользователь копирует файл /etc/passwd на свой домашний PC и сравнивает зашифрованные версии каждого слова в словаре с зашифрованным паролем привилегированного пользователя (root). Если один из элементов словаря совпадает, можно считать, что пароль взломан! Другие обычные методы проверки паролей заключаются в проверке имен и слов, записанных наоборот. Эта техника грубой силы очень мощна, и ей можно противостоять путем просьбы к каждому пользователю выбирать неанглийский язык, необратные и нетривиальные пароли. □ Коварный индивидуум может использовать технику перегрузки команды, описанную ранее, чтобы обмануть привилегированного пользователя при выполнении неправильной версии su. Чтобы использовать эту технику "Троянского коня", установите значение переменной окружения $ратн так, чтобы shell просматривал ваш собственный каталог bin перед стандартными каталогами bin. Затем напишите скрипт shell с именем su, который имитирует предложение регистрации привилегированному пользователю, но в действительности сохраняет его пароль в безопасном месте, выводит сообщение "Wrong password" и затем удаляет себя. Когда этот скрипт подготовлен, позвоните привилегированному пользователю и сообщите ему, что с вашим терминалом возникла проблема, которая требует установки полномочий привилегированного пользователя. Когда администратор напечатает su, чтобы войти в режим привилегированного пользователя, ваш скрипт su выполнится вместо стандартной утилиты su, и пароль привилегированного пользователя будет захвачен. Привилеги
рованный пользователь увидит сообщение "Wrong password" и попробует запустить su снова. На сей раз это у него получится, поскольку ваш скрипт "Троянского коня" уже будет удален. Пароль привилегированного пользователя теперь ваш! Способ нанести поражение этой технике — не выполнять команды с относительным путевым именем, когда вы работаете за незнакомым терминалом. Другими словами, указывайте /bin/su вместо просто su. Лучший способ расширять свои знания о хитрых схемах — это общаться с другими системными администраторами и читать специализированную литературу, например, [Nemeth 2000J. Обзор главы Перечень тем В этой главе мы рассмотрели: П главные задачи системного администратора; □ способы получения полномочий привилегированного пользователя; П запуск и остановку UNIX; □ различия между однопользовательским и многопользовательским режимами; □ некоторые полезные утилиты использования диска; □ инсталляцию программного обеспечения; П создание новой файловой системы; □ добавление и удаление регистрационных записей пользователей; □ обзор инсталляции устройств; □ конфигурацию сетевого интерфейса; П процесс создания нового ядра; П некоторые общие проблемы безопасности. Контрольные вопросы 1. В каких ситуациях уместно остановить систему UNIX? 2. Почему теперь большинство версий UNIX использует "теневой" файл паролей в дополнение к нормальному файлу /etc/passwd? 3. Что делает процесс getty? 4. Почему лучше использовать утилиту su, чтобы стать привилегированным пользователем, чем просто регистрироваться как root?
5. Как вы можете переключить UNIX в однопользовательский режим? 6. Когда проверяется целостность файловой системы? 7. Какие файлы должны быть модифицированы, когда вы добавляете нового пользователя? 8. Что делает утилита if config? 9. Какие подсистемы UNIX генерируют учетные записи? 10. Какие параметры ядра могут быть модифицированы? И. Опишите технику "Троянского коня", чтобы захватить пароль привилегированного пользователя. Упражнения 15.1. Попытайтесь, используя утилиты cpio и tar, переместить некоторые файлы на дискету и с дискеты. Какую из этих утилит вы предпочитаете? Почему? [Уровень: легкий.] 15.2. Запустите утилиту du, чтобы исследовать использование своего диска. Напишите скрипт, распечатывающий полные путевые имена ваших файлов, которые отсортированы по размеру файла. [Уровень: средний.] 15.3. Получите дискету, отформатируйте ее, на ней создайте файловую систему, установите ее и скопируйте на нее некоторые файлы. Конечно, вы будете нуждаться в помощи системного администратора, который поможет вам пройти через этот процесс. [Уровень: средний.] 15.4. Заполните функциональные возможности структурного скрипта, который вы написали в проекте 1 главы 6 так, чтобы он исполнял задачи администрирования системы в вашем управляемом при помощи меню интерфейсе. Полезные задачи для автоматизации должны включать: • автоматическое удаление базовых файлов; • автоматические предупреждения тем, кто использует много времени ЦП или дискового пространства; • автоматическое архивирование. [Уровень: средний.] Проект Спросите вашего системного администратора, что он думает о сильных и слабых сторонах UNIX со своей точки зрения. Возможно ли решить эти проблемы текущими релизами UNIX или другими операционными системами? [Уровень: средний.]

Глава 16 Будущее Мотивация Операционные системы продолжают развиваться и улучшаться по мере того, как развиваются программное обеспечение и аппаратные средства. Хотя старые, застойные системы неизбежно долгое время будут бродить вокруг, системы, которые включают лучшие концепции, и философии, в конечном счете, заменят их. UNIX существует больше, чем 30 лет, и этот возраст начинает влиять на внутреннюю архитектуру ОС. Знание тенденций операционных систем поможет читателю понять изменения, которые могут произойти в UNIX в следующие несколько лет, также как позволит примерно определить перспективную роль UNIX. Предпосылки Данная глава не имеет никаких предпосылок, хотя может помочь чтение главы 14. Задачи Глава описывает последние тенденции в проектировании операционных систем, которые влияют на развитие UNIX. Быстрый обзор главных версий ОС, находящихся сегодня в широком применении, готовит читателя к дальнейшей работе с UNIX. Изложение В главе исследуются темы, которые изменяют облик UNIX, и описываются некоторые примеры этих влияний в различных современных версиях UNIX и Linux.
Общие сведения В качестве преамбулы к этой главе рассмотрим некоторые из самых последних тенденций в программном обеспечении и аппаратных средствах ЭВМ: □ объектно-ориентированное программирование; □ программные продукты с открытым исходным текстом; □ распределенное и параллельное программирование; □ движение от 32- к 64-разрядным системам и сетевой адресации; □ высокоскоростная связь и отказоустойчивые системы. Эти тенденции представляют волнующее и интересное будущее для UNIX и вычислительной техники вообще. Чтобы воспользоваться данными преимуществами, программное обеспечение, которым является UNIX, также как платформы аппаратных средств, на которых она работает, обязаны приспособиться к изменяющимся обстоятельствам. Влияние настоящего и будущего на UNIX Много современных тем в теории вычислительных систем и усовершенствованиях аппаратных средств будут иметь сильное влияние на будущие сферы UNIX-систем. Некоторые уже повлияли и продолжают это делать. Другие лишь теперь входят в поле зрения. Объектно-ориентированное программирование Объекты ответственны за большую деловую суету в компьютерной промышленности в течение многих последних лет. Наиболее популярные объектно-ориентированные языки, которые сегодня используются в окружении UNIX, — это C++ и Java. С одной стороны, во многих ситуациях объектно-ориентированная парадигма может сильно увеличивать производительность разработки и сопровождения программных проектов. С другой стороны, использованные просто потому, что они ’’крутые", объекты могут фактически вызывать хлопоты. Давайте бросим беглый взгляд на объектно-ориентированное программирование. Очевидно, все, что мы скажем, будет чрезвычайным упрощением. Целые тома были написаны об объектах и философии их использования; надежда сделать больше, чем просто разжечь аппетит читателя в нескольких разделах, была бы безрассудна.
Что такое объект? Объект — это абстракция, способ описать цель и использование данных. В традиционном процедурном программировании определяются структуры данных и затем выполняются действия над этими данными. Программа должна была "знать”, какие данные были применимы к каким функциям, и какие действия могли быть выполнены над данными. Объект создается в соответствии с определением класса объекта. Думайте о классе, как о проекте дома —- как вы собираетесь строить его — и объекте, как о доме. С помощью единственного проекта можно построить много зданий. В своем коде вы определяете класс, или проект объекта, а затем, т. к. вы должны работать с данными, создаете, или instantiate, так мало или так много объектов этого класса, как необходимо. Идея объектно-ориентированного программирования состоит в том, что вместо простого выполнения процедурных действий на данных вы концептуально заключаете, или инкапсулируете, данные в объекте. Внутри этого объекта вы определяете набор функций, или методов, которые могут работать на определенных данных, поддержанных в объекте. Вызывая метод объекта, можно потребовать, чтобы объект выполнил одну из его функций, но нельзя получить доступ к данным непосредственно. Это защищает данные от любого вида случайной модификации, которая могла бы произойти, потому что модифицирующий код "думает", что он "знает" формат данных, но есть ошибка в коде или изменился формат данных. Как объект используется В дополнение к любым специфическим методам один определен для любого объекта, два других метода всегда определяются. Каждый объект должен иметь способ создания и удаления. В большинстве объектно-ориентированных языков конструкции, которые выполняют эти задачи, называются соответственно конструкторами и деструкторами) Конструктор — это специальный метод, который выполняется, когда создается новый экземпляр объекта. В C++ и Java это происходит, когда функция new вызывается на тип объекта. Конструктор создает и инициализирует любые данные, используемые объектом. Деструктор, как вы могли бы догадаться, делает противоположные конструктору действия. Деструктор — это специальный метод, который наводит порядок, когда объект удаляется. Выполняется любая требуемая завершающая обработка, и все ресурсы, которые были выделены, освобождаются. В С ++ это происходит, когда функция delete вызывается на объекте. 1 Java не предусматривает деструкторы, но возвращает память, используемую объектом во время "сборки мусора", когда объект больше не находится в использовании.
Так чем же хорошо все это? В качестве простого примера рассмотрим принтер, который способен находиться в режиме online (печать) или offline (автономно, не принимать данные для печати). Принтер мог бы иметь байт, который определяет текущий режим (скажем, 1 — для режима online, 0 — для автономного). Можно, конечно, написать программу, которая устанавливает значение 2 в этом байте. Что бы сделал принтер? Это зависит от него самого, но, предположим, 2 означает 'взрываться!” Если вы описали объект принтер в программе, которая скрыла байт статуса принтера, и определили методы printer-on() и printer-off о, то не смогли бы установить 2 в этом байте. Вы в силах вызвать printer-on о, чтобы установить байт в 1, или printer-off о, чтобы установить его в 0. Как видно, состояние в объекте является несущественным вне объекта. Поэтому объекты помогают конкретно описать, какие действия могут быть выполнены и на какие данные можно воздействовать и предотвращать изменение данных способами, не имеющими смысла, t Наследование Можно также создавать новые объекты, основанные на существующих объектах. Если бы был новый принтер, имеющий другую переменную статуса, помимо переменной online/offline, можно было бы создать новый класс объекта, основанный на объекте принтер, который унаследует признаки первоначального объекта принтер. Затем можно добавить любые новые методы в код. Получился бы новейший объект принтер. И нет нужды переписывать весь код, который является общим. Если мой новый объект принтер требовал иной механизм его выключения и включения, я мог бы также перегрузить методы printer-on о и printer-off о и определить собственный метод, который использовался бы в этом объекте вместо методов в родительском объекте. Этим способом подкласс может включать все полезное от родительского класса объекта и реализовать только те части, которые являются новыми и уникальными в подклассе. Другое преимущество состоит в том, что, если изменение сделано в базовом классе (например, исправление ошибки или другое улучшение), подкласс извлечет выгоду из изменения. В искусстве программирования объекты являются революционными. Применяемое должным образом объектно-ориентированное программирование может уменьшить время разработки, способствовать повторному использованию кода и уменьшить ошибки.
Открытые программные средства Сообщество UNIX имеет богатую традицию доступности программного обеспечения в исходной кодовой форме либо бесплатно, либо за разумную плату, позволяя людям учиться на коде или улучшать его далее. Сама ОС UNIX начала этот путь, и много компонентов, связанных с UNIX, разделяют эту традицию. Поэтому не сюрприз, что в мире иных собственников упакованного программного обеспечения, где вы покупаете доступное и приспосабливаете к своим требованиям, люди, поддерживающие идею о свободной доступности исходного кода, объединились, чтобы сохранить живой эту философию. Два главных события имели огромное воздействие на эволюцию идеи открытого программного обеспечения: создание Free Software Foundation и появления Linux на фоне UNIX. В [Fink 2003] приводится превосходная экспертиза феномена открытых программных средств, причины его возникновения, объясняется, где он работает и не работает. Free Software Foundation Одним из первых сторонников идеи свободно доступного программного обеспечения был Ричард Сталлман (Richard Stallman), основатель Free Software Foundation (FSF) в середине 1980-х годов. Сталлман полагал, что каждый должен иметь право получать, использовать, рассматривать и изменять программное обеспечение. Он начал проект GNU2, цель которого состояла в том, чтобы воспроизвести популярные инструменты UNIX и, в конечном счете, полную UNIX-подобную операционную систему в новом коде, который мог быть свободно распространяемым, потому что он не содержал никакого лицензированного кода. Ранние продукты включали версию популярного редактора текста emacs и GNU-компилятор С. "Свободный" в представлении свободного программного обеспечения не обязательно подразумевает, что программное обеспечение доступно бесплатно, а скорее, что его можно свободно использовать, рассматривать и изменять. Чтобы сохранять авторские права на программное обеспечение GNU, но все же обеспечить его использование наиболее широкой аудиторией, FSF разработал GNU-лицензию General Public License (GPL), согласно которой программное обеспечение GNU лицензировано для всего мира. GNU GPL предусматривает копирование, использование, модификацию и повторное распространение программного обеспечения GNU при условии, что та же самая свобода использовать, изменять и распределять его переходит к любому, кто берет вашу версию программного обеспечения. Авторское 2 GNU — это рекурсивный акроним, происходящий от "GNU’s not UNIX" и произносится "guh-NEW".
право (copyright) служит для защиты прав владельца. Здесь цель заключалась в защите прав получателя программного обеспечения. Таким образом, FSF выдумала термин copyleft, чтобы описать это несколько инвертированное значение. Для получения дополнительной информации о Free Software Foundation см. Web-узел http://www.fsf.org. Для получения дополнительной информации о проекте GNU или GNU General Public License посетите сайт http://www.gnu.org. Linux Окончательная цель проекта GNU состояла в том, чтобы обеспечить полную повторную реализацию UNIX, включая ядро. Но в то время, как GNU-приложения были многочисленны и популярны, само ядро требовало большего напряжения сил. Работа продолжается на GNU Hurd — UNIX-подобном ядре. Тем временем, в 1991 г. Линус Торвальдс (Linus Torvalds), студент Хельсинкского университета в Финляндии (University of Helsinki in Finland) написал собственное UNIX-ядро, которое соответствовало стандарту POSIX3. Все началось, как хобби. Но он чувствовал, что мир нуждался в версии UNIX, которая могла бы распространяться свободно без необходимости волноваться об ограничениях лицензирования. Его законченная работа почти совершенно дополняла работу проекта GNU. Сначала Линус и несколько его друзей поддерживали, внося изменения в исходный код, Linux. Сегодня разработчики во всем мире создают новый код и вносят исправления. Комбинация Linux- и GNU-утилит позволяет создавать полную UNIX-подобную операционную систему, работающую на многих различных аппаратных платформах и доступную в исходной форме. Так что можете делать собственные исправления ошибок и усовершенствования в ней. Параллельные, распределенные и многопроцессорные системы Исторически компьютер имел единственный ЦП^ располагался на столе (или в компьютерной комнате) и обрабатывал данные, которые были в него введены. По мере того как растут сети, компьютеры связываются вместе для разделения данных и сотрудничества при совместной обработке. Благодаря развитию микропроцессорной технологии в одном компьютере можно разместить несколько процессоров. 3 Portable Operating System Interface for Computing Environment, переносимый интерфейс операционной системы для вычислительной среды. — Ред.
Параллельная обработка Если задача может быть разделена на отдельные и несвязанные части, эти части способны работать отдельно. Таким образом, задача решается быстрее, чем в случае последовательного выполнения частей на единственном компьютере. Этот подход известен как параллельная обработка (выполнение нескольких задач одновременно). Она может быть выполнена либо различными компьютерами, либо различными процессорами внутри одного и того же компьютера, как мы вскоре увидим. Истинная параллельная обработка является чрезвычайно трудной. Многие задачи имеют некоторые отношения друг с другом и не могут быть легко разделены. Объектно-ориентированное программирование помогает, потому что обработка некоторого объекта может быть отделена от таковой для других, и объектно-ориентированная парадигма поощряет программистов применять методы, увеличивающие это разделение. Многопользовательские системы также извлекают выгоду из параллельных процессоров. Если в системе зарегистрированы десять пользователей, и процессы shell каждого пользователя могут быть выполнены на различных процессорах, то достаточно легко предоставить массу процессорной мощности отдельным пользователям. Но разделение единственной прикладной программы на слабосвязанные части, которые могут работать независимо, является серьезной проблемой. Еще более многообещающая задача состоит в том, чтобы написать компилятор, который может автоматически определять несвязанные части программы и разбивать код во время компиляции так, чтобы различные части могли быть обработаны на разных процессорах во время выполнения программы. Распределенные системы Один из способов распараллеливания отделенных задач заключается в том, чтобы выполнять их на различных компьютерах в одно и то же время. Некая централизованная программа может раздавать части задачи компьютерам и собирать их вывод по мере того, как они будут заканчивать свою работу. Некоторые накладные расходы вовлечены в управление разделением и связью, но если задачи достаточно сложны, параллельное выполнение станет причиной значительного уменьшения общего времени выполнения по сравнению со случаем, когда задачи выполняются последовательно на одном компьютере. Этот тип архитектуры часто упоминается как архитектура, не предусматривающая разделения ресурсов, потому что ни один процессор не разделяет никакие ресурсы с другими. Каждая система имеет собственные память, диск и путь данных к сети. Наряду с тем, что существует возможность для процессов на различных компьютерах разделять блоки памяти (так же, как это
могут делать два процесса, работающие на одном и том же компьютере), накладные расходы будут весьма небольшие, т. к. разделение происходит через сеть. Многопроцессорные системы Другой способ распараллеливания отдельных процессов состоит в том, чтобы выполнять их на одном компьютере. Если машина имеет единственный ЦП, тогда он должен разделить свое время между различными процессами, что не несет выгоду (фактически это приводит к потере времени, с накладными расходами множественных процессов) по сравнению с традиционным программированием единственного процесса. Однако, если компьютер имеет несколько процессоров, каждый отдельный процесс может быть выполнен на его собственном процессоре. Многочисленные процессы, работающие на одном компьютере, разделяют ресурсы диска. Они могут также без труда разделять сегменты памяти при помощи методов операционной системы для доступа к разделяемой памяти. Поэтому данный тип архитектуры часто называется архитектурой с разделяемой памятью или разделяемыми ресурсами. Sequent был одним из первых продавцов UNIX, которые предусматривали мультипроцессорную систему, специально разработанную, чтобы позволить писать параллельные программы и выполнять их на множественных процессорах. Сегодня многие UNIX-платформы доступны для мультипроцессорной архитектуры. Тем не менее, здесь применяются методы, вовлеченные в параллельное программирование, но обходятся дополнительные сложности распределения процессов на различные компьютеры. Поскольку скорости ЦП продолжают увеличиваться, преимущество мульти-пррцессорных систем не очевидно для обычного пользователя. Однако всегда будут существовать приложения, которые нуждаются в производительности, предлагаемой параллельным программированием. "Ошибка" 2000 года Даже притом, что 31 декабря 1999 г. прошло без больших потрясений, это все еще является достойной обсуждения проблемой, даже если нет другой причины, которая, вероятно, может повториться 31 декабря 2099 г. Я взял в кавычки слово "ошибка”, потому что, хотя проблема была упомянута, как ошибка, в действительности проявилось плохое проектирование программы, а не ошибка в кодировании. Недавно я видел "Фольксваген Жук" (Volkswagen Beetle) с номерным знаком "Y2K BUG". Я понял, что 2000-я модель "Жука" была единственным истинным Y2K Жуком (ошибкой)4! 4 Игра слов. Bug имеет при переводе значения* "жук" и "ошибка". — Ред.
Что же это в действительности? Проблема в том, что мы, люди, настаиваем на определении года в двух цифрах. Мы полагаем, что номер столетия будет очевиден либо из контекста, либо просто потому, что мы говорим об этом годе в текущем столетии. Человек это понимает, но компьютер — не человек. До 1 января 2000 г. все компьютерные программы были написаны в XX столетии. Поэтому имевшие дело с датами в двух цифрах программисты предполагали, что другие две цифры были "19". С этим предположением мы попали в плохое положение просто потому, что оно никогда нас не подводило. Очевидно, что предположение было неверным. Это была бы "ошибка компьютера", если бы сама система не могла представить 2000 год, но она может. Проблема заключается в предположении, сделанном программистом приложения. Год в две цифры Рассмотрим утилиту UNIX cal. Синтаксис cal [месяц год] Утилита cal выводит календарь для текущего месяца. Если месяц и год определены, cal печатает календарь для указанного месяца. Например, чтобы получить календарь на июнь 1998 г., надо сделать следующее: $ cal 6 1998 Результат такой: June 1998 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $
Обратите внимание, что произойдет, если указать год двумя цифрами: $ cal 6 98 June 98 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ _ Этот календарь не выглядит правильным, не так ли? Но он есть. Это календарь на июнь 98 года нашей эры5! Является ли это ошибкой cal, или это моя ошибка? Конечно, это моя ошибка; утилита cal сделала буквально то, что я попросил. Рассмотрим другой пример. Скажем, вы рассматриваете форму страхования пациента, но пациент отсутствует. Дата рождения на форме читается 06/06/99. Действительно ли пациент является ребенком или старым гражданином? Конечно, вы будете иметь достаточно информации, чтобы это понять, если пациент сидит в это время в комнате вместе с вами. Но компьютер не способен использовать другую информацию, чтобы сделать вывод. Отсутствуют необходимые данные, доступные для компьютера, чтобы не предположить, будто дата современна. Поэтому он "решит", что пациент — ребенок. И это может стать реальной проблемой, если цель компьютера состоит в том, чтобы выписать дозу лекарства пациенту! Решение для программного обеспечения кроется в сохранении и отображении даты четырьмя цифрами6. Ускорения, которые позволяют специфицировать дату двумя цифрами, прекрасны, пока пользователь знает, что подразумеваются сокращения, и уверен в существовании лишь небольшой вероятности двусмысленности. Иногда, тем не менее, это предположение не будет работать. Тогда должен быть способ определить полную дату. Годы, начиная с 1900 Проблема также проявляется в программах и функциях, которые возвращают значение "года, начиная с 1900". Для даты в XX столетии они возвращают значение с двумя цифрами. Но в 2002 г. функция, которая возвращает количество лет с 1900 г, вернет 102. Если некоторое приложение предпола 5 То есть на июнь 98 г. — Ред. 6 Если бы вы действительно думаете наперед, то могли бы использовать пять цифр, но было бы разумно предположить, что код не будет применяться через 8000 лет.
гает, что оно может дописывать значение "19" возвращаемым значением, то программа будет печатать для года результат "19102" (и существуют программы, делающие это сегодня). И опять здесь неправильное употребление функции, а не действительная ошибка системы. Ее даже просто исправить, т. к. следует прибавить возвращаемое значение к 1900 г. и вывести результат. В таком случае все работает просто прекрасно. UNIX и XXI столетие Непосредственно UNIX никогда не имел проблем 2000 года. Даты в UNIX хранятся как 32-битовые целочисленные значения, представляющие количество секунд, прошедших с полуночи 1 января 1970 г. (иначе известная как "эра" в сообществе UNIX). Поэтому значение 12:00:01 a.m. 1 января 2000 г.7 является просто целым числом, которое на 2 больше, чем значение 11:59:59 р.щ. 31 декабря 1999 г.8 Однако нельзя сказать, что UNIX никогда не будет иметь проблемы. Проницательные читатели поймут, что даже 32-битовое значение имеет предел. В 2038 г. значение даты UNIX перейдет через ноль (все 32 бита будут равны 1, и когда значение увеличится на единицу, все биты будут снова 0). В этот момент все UNIX-машины снова зарегистрируют 1 января 1970 г. Хорошо, что у нас есть масса времени, чтобы приготовиться к такому событию. Я чувствую себя уверенным, высказывая следующее: если мы будем использовать операционную систему в 2038 г., даже с именем UNIX, эта проблема не будет к ней относиться. 64-разрядные системы Первые маленькие компьютеры были 8-битовыми системами. То есть шина, которая соединяла ЦП с памятью, имела 8-битовый канал данных. Слова памяти имели длину 8 битов. Максимальный адрес памяти — 16 битов (т. к. 8 битов могут представить только 256 различных значений). Но в аппаратуре было сделано усложнение для объединения двух слов памяти для организации адресной ссылки. Позже были внедрены 16-битовые системы, позволяя одновременно передавать большее количество данных через шину. 16-битовое значение представляет максимум 65 536 или 64 Кбайт. В то время люди действительно думали, что 64 Кбайт памяти будет достаточно для всего! Наконец, когда возникли 32-битовые системы, адресовались 4 Гбайт памяти. Вновь большее количество данных могло быть послано через 32-битовую 7 Для нас это 00:00:01 1 января 2000 г. — Ред. 8 Для нас это 23:59:59 31 декабря 1999 г. — Ред. 27 Зак. 786
шину. Каждый думал, что настал предел. В конце концов, почему бы ни быть достаточным 4 Гбайт адресуемого диска или памяти? 64-битовые процессоры и системы сегодня стали обычными. Более широкий канал шины позволяет еще большему количеству данных перемещаться по ней. Адресация упрощена, потому что 64-битовый адрес может быть сохранен в единственном слове памяти. Теперь легко превзойти 32-битовый предел в 4 Гбайт для адресов диска и памяти. Если я скажу, что 64-битовая архитектура — это точка, в которой мы остановимся, конечно, я буду не прав, так что я не буду это говорить! Интернет-адресация: IPv6 Текущая реализация протокола IP, версия 4 (IPv4), обеспечивает поддержку 32-битовых адресов. Однако с ростом Интернета данное значение быстро становится недостаточным, чтобы поддержать все машины, подключаемые к сети. Чем больше мы находим применений, тем больше требуется устройств, IP-адресов. Многие принтеры также имеют собственные IP-адреса, и некоторые портативные компьютеры обладают множественными IP-адресами для использования в различных местах. В начале 1990-х гг. стало ясно, что будет необходимо новое поколение IP, которое позволит иметь намного больше адресов. Началась работа по определению IPng (IP next generation, следующее поколение IP) и формальное предложение о версии 6 IP-протокола появилось в 1995 г. Стандарт IPv69 определяет адреса длиной 128 битов. Хотя эти протоколы IPv4 и IPv6 используют адреса различной длины, оба могут применяться в одной и той же сети. Это необходимо потому, что Интернет слишком велик для координации "вырубки" по новому протоколу. Гладкий переход к новой схеме адресации должен быть постепенным, а не требовать, чтобы все мы проснулись однажды с использованием более нового протокола. Пакеты IP (обеих версий) определяют версию в первых 4-х битах пакета. Поэтому компьютер, который "говорит" на IPv6, должен признавать и обрабатывать пакеты с IPv4 (если он сконфигурирован для двух протоколов). Это позволяет двум протоколам сосуществовать в одной сети. Старые машины могут быть модернизированы к IPv6 по мере того, как реализации станут доступными или системные администраторы получат возможность модернизировать их без спешки. Как уже обсуждалось в главе 9, адрес IPv4 — это 32-битовое значение, выраженное через четыре значения, каждое десятичное число представляет 8-битовое значение типа 192.127.63.141 9 IP version 6. — Ред.
В IPv6 128-битовый адрес был бы тяжел, чтобы выразить его тем же самым способом. Можете вы вообразить адрес, подобный этому? 192.127.63.141.241.27.88.16.1.77.34.8.191.253.27.61 Это направило бы администраторов сети на другую линию работы! В IPv6 значения выражены в шестнадцатеричном формате, требуя двух шестнадцатеричных цифр для каждых 8 битов. Вместо разграничения через каждые 8 битов, они разделяются через каждые 16 битов. Другое изменение заключается в том, что разделителем между частями адреса служит двоеточие, а не точка. Таким образом, длинный адрес, представленный выше, может быть выражен в IPv6 как C07F:3F8D:F11B:5810:0140:2208:BFFD:1B3D Воистину, это значительно короче, но это самый длинный 1Р\'6-адрес, какой может быть (принимая во внимание, что десятичный пример перед этим был фактически на несколько цифр длиннее!). Практически, многие IP-адреса имеют 8-битовые или даже 16-битовые значения, равные нулю. IPv6 также учитывает пропуск ведущих нулей и устраняет смежные 16-битовые нулевые значения. В дополнение к изменениям адресации IPv6 также предусматривает усовершенствования в маршрутизации и автоматической конфигурации. В то время, как IPv6 широко не распространен, продавцы реализуют и проверяют новый протокол. В ближайшие несколько лет IPv6 будет развернут в Интернете. Если все пойдет хорошо, пользователи, вероятно, даже и не заметят перехода на новый стандарт. Для получения дополнительной информации о IPv6 посетите узел сети http://www.ipv6.org. Сети с высокой пропускной способностью Изначально для Ethernet-стандарта скорость передачи 1 Мбит/с была высокой. Сегодня в локальной сети обычны скорости в 100 Мбит/с. С применением волоконной оптики и других цифровых средств информации через сеть может быть передано намного больше данных, чем когда-либо прежде. Из-за бума сетевого просмотра в Интернете передаются большие объемы данных. Сообщения электронной почты были малы по сравнению с изображениями, видео и звуком, которые составляют сегодняшние страницы сети. К счастью, полоса пропускания сети увеличивается с такой же скоростью, как скорость ЦП и объем диска. Как только появляются новые, быстрые, лучшие способы делать что-нибудь, человечество находит способы использовать это. Помните, как никто не мог вообразить нехватку 64 Кбайт памяти?
Отказоустойчивые системы Поскольку корпорации все более полагаются на компьютерные системы, время простоя становится серьезной проблемой. В некоторых ситуациях (например, маршрутизаторы в телефонной сети) почти любое время простоя недопустимо. Традиционно решение состояло в том, чтобы иметь системы "горячего" резервирования. Эти системы работают в параллель с системой, выполняющей основную обработку, обновляя одни и те же данные и находясь при этом "в тени". Когда основная система выходит из строя, дублирующая система может почти немедленно подхватить ее функции, пока инженеры будут исправлять первую систему. Отказоустойчивые системы пробуют достичь этого в рамках единственной системы. Система с резервными ЦП, памятью и устройствами может использовать резервный ресурс, если основной ресурс неисправен. Несколько компаний, из которых Tandem (теперь часть Hewlett-Packard) наиболее известна, исследовали и изготовили отказоустойчивые UNIX-системы. Хотя компоненты аппаратуры теперь более надежны, чем десятилетие назад, некоторые приложения будут всегда требовать такой близости к 100% работоспособному состоянию, какой возможно достигнуть. Обзор современных популярных версий UNIX Хотя ОС UNIX "родилась" в компьютерной лаборатории Bell Laboratories, с тех пор путь ее развития длинен и замысловат. Серьезное обсуждение истории развития UNIX лежит за рамками данной книги, но в [Salus 1994] представлен превосходный обзор богатой истории этой ОС. Часть истории UNIX — это соревнование между Berkeley UNIX (BSD) и System III (позже, System V UNIX, как она называлась в AT&T). Много лет мир UNIX был разделен на два лагеря. Большинство реализаций UNIX было основано на одной из двух систем. Различия заключались, главным образом, в архитектуре ядра и низкоуровневых алгоритмах операционной системы (например, BSD и System V использовали радикально различные алгоритмы управления памятью). Для пользователей различия были почти незаметны. Когда Sun Microsystems и AT&T объединили силы, чтобы слить миры BSD и System V вместе, родились System V Release 4 (SVR4) и Solaris. SVR4 со
единила лучшее из обоих миров.10 Когда любители BSD получили большинство их любимых возможностей в базирующихся на SVR4 версиях UNIX, "UNIX-войны" начали стихать. Вскоре несколькими компаниями (наиболее памятны HP и DEC) была сформирована организация Open Software Foundation (OSF), чтобы создать собственную версию UNIX. Это была попытка помешать AT&T и Sun обладать абсолютными правами полученной собственности и, следовательно, будущим управлением UNIX. Поскольку системы, базирующиеся на SVR4, доказали возможность удовлетворения потребностей клиента, а AT&T и Sun не имели всех прав обладания UNIX, как многие боялись, эта UNIX-война также прекратилась, и OSF заняла свое место в истории. Сегодня, в то время как есть все еще некоторые нацеленные на BSD версии UNIX, главные различия связаны с аппаратными платформами и эффективностью. Другие различия преимущественно внешние. Основная UNIX-система и интерфейсы в значительной степени переходят от одной версии к другой (но, правда, достаточно неодинаковы, чтобы доставлять пользователю неприятности время от времени). Продавцы, поставляющие собственные дистрибутивы UNIX, предусматривают свои "дополнительные" команды и возможности, реализованные по требованиям клиентов. Перенос программного обеспечения с одной версии на другую в зависимости от глубины функций операционной системы, которые могло бы использовать приложение, все еще является нетривиальной задачей. Но сейчас это не такая серьезная задача, как раньше. В настоящее время существует много различных версий UNIX. Большинство нацелено на определенные приложения (типа вычислений в реальном времени), конкретную редкую аппаратуру или исследования, основанные на предыдущей работе с UNIX. Когда пользователь изучает "господствующие" версии UNIX, которые легкодоступны на автоматизированном рабочем месте или аппаратуре маленького сервера, то в результате их количество оказывается небольшим. Версии, обсуждаемые в следующих разделах, не составляют исчерпывающий список, но они представляют большинство версии, с которыми, вероятно, столкнется читатель в типичном окружении UNIX. Они обеспечивают возможности (в различной реализации), рассмотренные в предыдущих главах книги (т. е. X Window System, организацию сети TCP/IP, большинство "стандартных" команд UNIX и т. д.). 10 Конечно, "наилучший" является объективным термином. Вы можете найти людей, которые будут оспаривать этот факт. Намерение состояло в том, чтобы объединить лучшее от обеих версий.
-----------------------------------------------------7-------------- AIX Advanced Interactive executive (AIX) — это реализация UNIX от фирмы IBM, работающая на ее RISC System/6000 рабочей станции и серверных платформах. Она базируется на System V UNIX с расширениями SVR4 и BSD. Платформы RS/6000 предлагают мультипроцессорные системы, также как и 64-разрядные системы. Если читатель в прошлом использовал другие системы IBM, части- AIX покажутся более знакомыми, чем другие версии UNIX. AIX имеет более многословные сообщения об ошибках, чем другие версии. Большинство сообщений индексировано кодами, чтобы направить пользователя к информации в руководстве. В то время, как сначала такая ситуация кажется тяжелой, особенно по сравнению с кратким характером первоначальных версий UNIX, время от времени это доказывает свою полезность. Дополнительная информации об AIX доступна в сети по адресу http: //www.ibm.com/servers/aix. Caldera SCO/Unixware Много лет компания Santa Cruz Operation (SCO) была лидером среди продавцов программного обеспечения UNIX. Версия SCO UNIX работала на аппаратуре низкой производительности типа PC и предпочиталась маленькими компаниями, которые не могли позволить себе дорогостоящие рабочие станции. За свою историю SCO приобрела Unix Systems Laboratories, подразделение UNIX компании AT&T, которое разработало UnixWare, истинную UNIX System V. Теперь компания Caldera приобрела подразделение UNIX компании Santa Cruz Operation и продолжает использовать оба названия в своих коммерческих UNIX-предложениях: SCO OpenServer и UnixWare 7. Для получения дополнительной информации о семействе UNIX-продуктов компании Caldera посетите http://www.caldera.com/products/unix. FreeBSD FreeBSD, как можно предположить, является бесплатной реализацией версии UNIX компании Berkeley Standard Distribution. Это один из нескольких UNIX-проектов Open Source, доступных сегодня. FreeBSD работает на совместимой с PC аппаратуре (386, 486, Pentium и большинстве PC со стандартной архитектурой).
Программисты во всем мире исправляют и улучшают существующий код FreeBSD. Узел сети FreeBSD предоставляет информацию об участниках и правилах участия. FreeBSD может быть загружена из сети (но она большая) или получена на CD-ROM за небольшую плату. За дополнительной информацией о FreeBSD обратитесь на сайт http://www.freebsd.org. HP-UX Вклад компании Hewlett-Packard в развитие UNIX известен как HP-UX, и эта версия работает на РА-RISC аппаратной платформе HP, также как на новой архитектуре Intel IA-64 Itanium11. HP-UX базируется на System V с заимствованием лучших возможностей из SVR4 и BSD. HP-UX — это 64-разрядная версия UNIX, которая соответствует всем популярным UNIX-стандартам. В настоящее время она является версией номер один или номер два среди поставляемых продавцами UNIX в зависимости от того, чью статистику вы читаете. Для компании, которая не была вовлечена в начальную эволюцию UNIX, HP сделала хорошую работу по адаптации "философии UNIX" и истинного следования ей в разработке HP-UX. Для получения дополнительной информации о HP-UX обратитесь к http://www.hp.com/go/hpux. IRIX Компания Silicon Graphics, Inc. установила стандарт для быстродействующих графических аппаратных средств с высоким разрешением. Версия SGI's UNIX, известная как IRIX, базировалась на System V в те дни, когда большинство продавцов предлагали системы на основе BSD, поэтому совместимость в смешанных окружающих средах была проблематична. Можно приобрести UNIX на платформе SGI, если вам необходимо невероятное графическое автоматизированное рабочее место. Ввиду того, что мир стандартизировался на SVR4, IRIX стал новой господствующей тенденцией, расставив "правильные" акценты в приоритете. IRIX включает большинство лучших особенностей SVR4, также как BSD UNIX, и является 64-разрядной операционной системой. Для получения дополнительной информации об IRIX посетите Web-страницу http://www.sgi.com/developers/technology/irix.html. 11 Процессор Intel Itanium был совместно создан компаниями Intel и HP.
Linux Linux является несомненно наиболее популярной из Open Source версий UNIX лля архитектуры PC. Кроме того, она была перенесена на платформу Digital Equipment Corporation (DEC)/Compaq/HP Alpha, SPARC-платфор-мы Sun, процессор Intel Itanium и платформы Power PC фирмы Motorola. Хоть Linux и походит на UNIX и полезна даже для наиболее опытных пользователей, однако это не UNIX, потому что не разделяет никакой общий исходный код с любым UNIX-дистрибутивом. Она является повторной реализацией UNIX с теми же самыми интерфейсом и командами. Поскольку Linux не содержит никакой лицензированный исходный код, принадлежащий AT&T, Калифорнийскому университету в Беркли или кому-либо еще, она может распространяться с исходным кодом. Если необходимо узнать о ее внутренних действиях или настроить под себя, такой вариант возможен. Linux может быть изменена, повторно распространена и даже продана, пока исходный код остается доступным. Несколько компаний осуществляют продажу средств информации, документации и поддержки для собственных дистрибутивов Linux. Ниже представлен список некоторых продавцов, обеспечивающих распространение Linux (одни — по номинальной стоимости, другие — бесплатно): □ Caldera OpenLinux; □ Corel Linux; □ Debian GNU/Linux; □ Mandrake Linux; □ RedHat Linux; □ Slackware Linux; □ SuSE Linux. В дополнение к этим отдельным распространителям Linux традиционные UNIX-продавцы, такие как HP, IBM, SGI и Sun, реализуют Linux на их собственных аппаратных платформах. Для получения дополнительной информации об основах операционной системы Linux (включая информацию относительно всех различных поставщиков дистрибутивов) посетите сайт http://www.liniix.org. NetBSD NetBSD часто путают с FreeBSD. Однако они являются двумя отдельными проектами, но с похожими целями: обеспечить свободную реализацию BSD UNIX.
Проект NetBSD является, как и другие проекты Open Source, совместным усилием разработчиков со всего мира в поддержке и улучшении операционной системы. NetBSD также называется UNIX-подобной операционной системой, но основанной на коде от 4.4 BSD Lite, подмножестве BSD. Подобно Linux, NetBSD распространяется с исходным кодом. NetBSD работает на многих различных платформах, включая PC, DEC Alpha и VAX, HP 9000, Macintosh, рабочие станции Sun SPARC, и даже на некоторых переносных устройствах. Для дополнительной информации о NetBSD посетите узел сети по адресу http: //www.netbsd.org. OpenBSD OpenBSD — это другой проект, предназначенный обеспечить свободную реализацию BSD UNIX, но он больше, чем другие, предусматривает обеспечение более надежных механизмов безопасности. Подобно другим проектам Open Source, OpenBSD базируется на 4.4 BSD UNIX. Данная версия гордится двоичной эмуляцией программ со многих UNIX-платформ, включая FreeBSD, HP-UX, Linux и SunOS/Solaris. Для дополнительной информации об OpenBSD посетите http://www.openbsd.org. Tru64 UNIX Компания DEC сделала несколько "набегов" в мире UNIX. Первоначальное предложение компании — это Ultrix, базирующаяся на BSD. Ultrix работала на VAX-линии аппаратуры DEC. DEC позже адаптировал ядро, базирующееся на 0SF/1, и создал Digital UNIX, 64-разрядную операционную систему, которая работает на платформе Alpha. Digital UNIX была переименована в Tru64 UNIX, когда компания Compaq приобрела DEC. Для получения дополнительной информации о Tru64 UNIX посетите http://www.tru64unix.Compaq.com. Solaris В начале 1980-х гг. большинство дистрибутивов поступало непосредственно от AT&T или Калифорнийского университета в Беркли и работало на любой имевшейся у пользователя аппаратуре, которую поддерживали эти версии. Возникли мелкие компании для распространения основных дистрибутивов и поддержки UNIX-систем, в основном базирующихся на ЦП Motorola 68000.
Вначале SunOS работала на МС68010 и MC68020. В конечном счете, компания Sun Microsystems, Inc. решила, что может лучше обслуживать своих клиентов, если она также разработает аппаратуру, специально предназначенную для выполнения UNIX. (Motorola разработала семейство 68000 не специально для работы UNIX.) Sun стала одним из первых лидеров в развитии и улучшении системы UNIX. Solaris — это текущая точка в развитии Sun UNIX. Оригинал SunOS базировался на BSD UNIX, потому что некоторые из основателей Sun — наиболее заметный, Билл Джой (Bill Joy), автор редактора vi — прибыли из Беркли. Sun сделала прыжок от SunOS до Solaris, когда начала сотрудничать с AT&T, чтобы стандартизировать System V. Конечно, Solaris включает все лучшие особенности из BSD и SunOS, к которым привыкли клиенты Sun. Solaris работает на SPARC, 32- и 64-разрядных платформах фирмы Sun, также как и на платформах Intel (PC). Для получения дополнительной информации о Solaris см. http://www.sun.com/software/solaris. Обзор главы Перечень тем В этой главе мы рассмотрели: □ объектно-ориентированное программирование; □ открытые программные средства; □ параллельные и распределенные системы; □ микропроцессорные системы; □ проблему 2000 года; □ 64-разрядные архитектуры; □ сети с высокой пропускной способностью; □ отказоустойчивые системы; □ версии UNIX, которые можно использовать. Контрольные вопросы 1. Какие версии UNIX бесплатны? 2. Как компания может заработать, продавая проекты Open Source, когда они доступны бесплатно?
3. Как данные, связанные с объектом, отличаются от традиционных данных в компьютерной программе? 4. Сколько битов представляет из себя IP-адрес в IPv6? Упражнение Загрузите одну из бесплатных версий UNIX из Интернета и установите ее. [Уровень: средний.] Проект Определите последнюю дату и время в 2038 г., которую будет способен представить UNIX. [Уровень: средний.]

Приложение Регулярные выражения Регулярные выражения — это последовательности символов, которые описывают систему соответствия строкам. Они принимаются как аргументы многим утилитам UNIX, такими как grep, egrep, awk, sed и vi. Обратите внимание, что групповые символы замены имени файла, используемые shell, отсутствуют в примерах допустимых выражений, т. к. они используют другие правила соответствия. Регулярные выражения формируются из последовательностей нормальных и специальных символов. В табл. Ш перечислены некоторые специальные символы, иногда называемые метасимволами, вместе с их значениями. Таблица П1. Метасимволы регулярных выражений Метасимвол Описание Соответствует любому одиночному символу [ ] Соответствует любому отдельному символу, указанному в скобках. Может использоваться дефис, чтобы представить диапазон символов. Если первый символ после [ — это А, то имеется в виду любой символ, неуказанный в скобках. Метасимволы *, А, $, и \ теряют свои специальные значения, когда используются внутри скобок * Может следовать за любым символом и обозначает 0 или большее количество появлений символа, который предшествует ему А Соответствует только началу строки $ Соответствует только концу строки \ Значение любого метасимвола может быть отменено путем предше- ствования ему \ Регулярное выражение соответствует самому длинному шаблону, который может быть. Например, когда образец у.*Ьа разыскивается в строке yabadabadoo, совпадение ПРОИСХОДИТ С подстрокой yabadaba, а не С yaba. Чтобы проиллюстрировать использование метасимволов, рассмотрим представленный ниже фрагмент текста. Well you know it’s your bedtime, So turn off the light,
Say all your prayers and then, Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, And you will be swimming there too. Шаблоны В табл. П2 продемонстрированы строки текста, которые соответствовали бы различным регулярным выражениям. Часть каждой строки, удовлетворяющая регулярному выражению, выделена курсивом. Таблица П2. Строки, соответствующие шаблонам регулярных выражений Шаблон Строки, которые соответствуют the So turn off the light, Say all your prayers and then, Beautiful mermaids will swim through the sea, And you will be swimming there too. . nd Say all your prayers and then, Oh you sleepy young heads dream of wonderful things, And you will be swimming there too. л. nd And you will be swimming there too. sw.*ng And you will be swimming there too. [А-D] Beautiful mermaids will swim through the sea, And you will be swimming there too. \ , And you will be swimming there too. a. Say all your prayers and then, Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, a. $ Beautiful mermaids will swim through the sea, [a-m] nd Say all your prayers and then, [ла-т]nd Oh you sleepy young heads dream of wonderful things, Расширенные регулярные выражения Некоторые утилиты, типа egrep, поддерживают расширенный набор метасимволов, описанных в табл. ПЗ. В та§л. П4 представлены некоторые примеры полных регулярных выражений с использованием текстового файла.
Таблица ПЗ. Метасимволы расширенных регулярных выражений Метасимвол Описание + Соответствует одному или нескольким появлениям отдельного предшествующего символа Соответствует 0 или одному появлению отдельного предшествующего символа | (символ конвейера) 0 Если вы помещаете символ конвейера между двумя регулярными выражениями, будет принята строка, которая соответствует любому выражению. Другими словами, | действует подобно оператору ИЛИ Если вы помещаете регулярное выражение в круглые скобки, то можете использовать метасимволы *, + или ?, чтобы повлиять на полное выражение, а не только на отдельный символ Таблица П4. Строки, соответствующие шаблонам расширенных регулярных выражений Шаблон Строки, которые соответствуют S . *w Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, And you will be swimming there too. S . +w Oh you sleepy young heads dream of wonderful things, Beautiful mermaids will swim through the sea, off|will So turn off the light, Beautiful mermaids will swim through the sea, And you will be swimming there too. im*ing And you will be swimming.there too. im?ing Нет совпадений Модифицированная для UNIX нотация Бэкуса—Наура Синтаксис утилит UNIX и системных вызовов в данной книге представлен в измененной версии языка, известного как форма Бэкуса—Наура (Backus-Naur FORM, BNF). В описании BNF последовательности, приведенные в табл. П5, имеют специальное значение. Последняя последовательность — это ориентированная на UNIX модификация, которая позволяет избежать
использования большого числа скобок вокруг опций командной строки. Чтобы указать символы [, { или - без их специального значения, им предшествует \. Некоторые разновидности команд зависят от опции, которую вы выбираете. Эта зависимость указана в представлении отдельного описания синтаксиса для каждой разновидности. Например, посмотрите на описание синтаксиса утилиты at, показанной ниже. Таблица П5. Нотации BNF Последовательность Описание [ строки ] Строки могут появляться ноль или один раз { строки }* Строки могут появляться ноль или больше раз { строки }+ Строки могут появляться один или больше раз строка 11строка2 Может появиться строка 1 или строка2 -списокОпций Ноль или большее количество опций могут следовать за дефисом Синтаксис at -csm время [дата [, год]] [+ приращение] [скрипт] at -г {10_задания} + at -1 (ID задания}* Первая версия утилиты выбирается любой комбинацией опций командной строки -с, -s и -т. Они должны затем сопровождаться временем и дополнительным спецификатором даты. Дополнительный спецификатор даты может сопровождаться дополнительным спецификатором года. Кроме того, приращение может быть специфицировано с именем скрипта или без него. Вторая версия утилиты at выбирается с опцией -г,\ и может сопровождаться одним или более номерами ID заданий. Третья версия at выбирается с опцией -1 и может сопровождаться нулем или большим числом номеров ID заданий.
Библиография 1. [Anderson 1986] Anderson, Gail, and Paul Anderson. The UNIX C Shell Field Guide. — Prentice Hall, 1986. 2. [Anderson 1995] Anderson, Bart (ed.), Bryan Costales, and Harry Henderson. The Waite Group's UNIX Communications and the Internet. — Sams, 1995. 3. [Andleigh 1990] Andleigh, Prabhat K. UNIX System Architecture. — Prentice Hall, 1990. 4. [Bach 1987] Bach, Maurice J. The Design of the UNIX Operating System. — Prentice Hall PTR, 1987. 5. [Bolsky 1995] Bolsky, Morris I., and David G. Korn. The New KornShell Command and Programming Language, 2d ed. — Prentice Hall PTR, 1995. 6. [Cheswick 1994] Cheswick, William R., and Steven M. Bellovin. Firewalls and Internet Security. — Addison-Wesley, 1994. 7. [Christian 1988] Christian, Kaare. The UNIX Operating System, 2d ed. — Wiley, 1988. 8. [Curry 1992] Curry, David A. UNIX System Security: A Guide for Users and System Administrators. — Addison-Wesley, 1992. ' 9. [Fink 2003] Fink, Marlin. The Business and Economics of Linux and Open Source. — Prentice Hall, 2003. 10. [Fountain 2000] Fountain, Anthony, Paula Ferguson, and Dan Heller. Motif Reference Manual, 2d ed. — O'Reilly & Associates, 2000. 11. [Garfinkel 1996] Garfinkel, Simson, and Gene Spafford. Practical UNIX and Internet Security, 2d ed. — O’Reilly & Associates, 1996.
12. [Horspool 1992] Horspool, R. Nigel. The Berkeley UNIX Environment, 2d ed. — Prentice Hall, 1992. 13. [Kernighan 1992] Kernighan, Brian, and Rob Pike. The UNIX Programming Environment. — Prentice Hall, 1992. 14. [Leffler 1989] Leffler, Samuel I, Marshall Kirk McKusick, Michael J. Karels, and John S. Quarterman. The Design and Implementation of the 4.3 BSD UNIX Operating System. — Addison-Wesley, 1989. 15. [McNulty 1986] McNulty Development. UNIX Ref Guide. — Prentice Hall, 1986. 16. [Medinets 1996] Medinets, David. Perl 5 by Example. — Que, 1996. 17. [Nemeth 2000] Nemeth, Evi, Garth Snyder, Scott Seebass, and Trent R. Hein. UNIX System Administration Handbook, 3d ed. — Prentice Hall PTR, 2000. 18. [OSF 1992] Open Software Foundation (OSF). OSF/Motif User's Guide. — Prentice Hall PTR, 1992. 19. [Quercia 1993] Quercia, Valerie, and Tim O'Reilly. X Window System User’s Guide — OSF/Motif Edition, 2d ed. — O'Reilly & Associates, 1993. 20. [Roberts 1991] Roberts, Ralph, Mark Boyd, Stephen G. Kochan, and Patrick H. Wood. UNIX Desktop Guide to EMACS. — Sams, 1991. 21. [Rochkind 1986] Rochkind, Marc J. Advanced UNIX Programming. — Prentice Hall PTR, 1986. 22. [Sage 1986] Sage, Russell G. Tricks of the UNIX Masters. — Sams, 1986. 23. [Salus 1994] Salus, Peter H. A Quarter Century of UNIX. — Addison-Wesley, 1994. 24. [Seyer 1986] Seyer, Martin D., and William J. Mills. DOS/UNIX — Becoming a Super User. — Prentice Hall, 1986.
25. [Sobell 1994] Sobell, Mark G. A Practical Guide to the UNIX System, 3d ed. — Addison-Wesley, 1994. 26. [Stevens 1992] Stevens, W. Richard. Advanced Programming in the UNIX Environment. — Addison-Wesley, 1992. 27. [Stevens 1998] Stevens, W. Richard. UNIX Network Programming, 2d ed. — Prentice Hall PTR, 1998. 28. [Vahalia 1995] Vahalia, Uresh. UNIX Internals: The New Frontiers. — Prentice Hall, 1995. 29. [Wait 1987] Waite Group and Michael Waite (ed.). UNIX Papers. — Sams, 1987. 30. [Wall 1996] Wall, Larry, Tom Christiansen, Randal L. Schwartz, and Stephen Potter. Programming Perl, 2d ed. — O'Reilly & Associates, 1996. 31. [Young 1994] Young, Douglas A. The X Window System: Programming and Applications with XT — OSF/Motif. — Prentice Hall, 1994.
Предметный указатель А Active inode table 728 Advanced Interactive executive (AIX) 794 Advanced Research Projects Agency (ARPA) 404 Age 714 American National Standards Institute (ANSI) 454 Application Programming Interface (API) 434 ARPANET 405 В Backbone 376 Backus-Naur FORM (BNF) 31, 631, 803 Berkeley Internet Name Daemon (BIND) 409 Berkeley Standard Distribution (BSD) 23 Block device switch table 739 Block oriented interface 738 Bourne Again shell (Bash) 172, 344 автозаполнение 356 запуск 345 история 353 команда: 367 alias 352 builtin 366 case 361 declare 347, 357 dirs 364 export 365 for 362 history 353 if 361 jobs 365 kill 365 local 366 popd 364 команда pushd 363 readonly 366 select 366 set 346, 351, 355, 367 source 367 unalias 353 unset 350 until 363 while 363 массив 347 метасимволы 353 опции командной строки 367 операторы: арифметические 356 арифметические условные 357 строковые условные 358 файловые условные 358 переменные 346 предопределенные 351 списочные 347 получение 345 псевдонимы 352 редактирование команды 355 стек каталогов 363 Bourne shell 172 закрытие стандартного ввода/вывода 240 запуск 212 команда: case 226 export 192, 217 for 228 if 229 null 238 read 216 readonly 218 set 238 sh 212 trap 230 until 231
while 232 точка (.) 237 опции командной строки 241 переменные 213 локальные 219 окружения 221 специальные 214 переназначение 240 последовательности команд 240 Bridge 374 BSD 792, 793, 794, 795 конвейер 748 Buffer pool 725 unalias 313 unhash 339 while 326 конвейер 332 метасимволы {} 330 модификатор: замены 318 имени файла 318 истории 317 операторы: арифметические 307 строковые 307 опции командной строки 340 переменные: С локальные 300, 305 простые 300 С shell 172, 299 дописывание имени 311 замещение имени файла 330 запуск 299 защита файлов от перезаписи 332 история 314 команда: список 300 окружения 306 перенапраление стандартного канала ошибки 331 повторное выполнение команд 316, 330 построение списков 304 @ 308 alias 312 chdir 336 csh 299 dirs 338 foreach 320 glob 336 goto 320 hashstat 339 history 315 if 321 logout 335 nice 334 notify 334 onintr 322 popd 338 pushd 337 rehash 339 repeat 323 set 301, 302 setenv 306 source 337 stop 333 switch 323 псевдонимы 311 параметризованные 314 полезные 313 разделение 314 стек каталогов 337 файловые выражения 310 хеш-таблица 339 CD-ROM Drive 13 Central Processing Unit (CPU) 12, 13 Character device switch table 739 Character oriented interface 738 Collision 373 Common Desktop Environment (CDE) 433, 449 Context switch 704 Control terminal 745 Copyleft 784 Copy-on-write bit 711, 717 Copyright 418, 784 С-компилятор 455 С-программа: выполнение 458 имя исполняемого файла 459 компиляция 456
D ID пользователя 58 реальный 61 Daemons 378 Delayed-write flag 726 Department of Defense (DOD) 409 Digital UNIX 797 Disk 13 Domain Name Service (DNS) 409 эффективный 61 Inode 359, 688 буферизация 691 список 688, 691 текущий 694 Interface Message Processor (IMP) 405 E Internet Service Provider (ISP) 413 Interprocess communication (IPC) 377, errno, глобальная переменная 511 Ethernet 373 Ethernet interface 14 Ethernet-интерфейс 14 External data representation (XDR) 399 509, 510, 577, 596, 628, 674, 675 IPv4 377, 790 IPv6 377, 790 IP-адресация 377 IRIX 795 F к FIFO 601, 602 Firewall 411 Free Software Foundation (FSF) 343, 783 FreeBSD 794, 796 К Desktop Environment (KDE) 433, 449 Kernel 675 Keyboard 14 Korn shell 172 G замена тильды 261 замещение команды 274 Gateway 375 General Public License (GPL) 783 Globbing 180 Graphical User Interface (GUI) 426, 450 Graphics card 13 запуск 246 история 252 команда: alias 248 bg 271 cd 283 H exit 265 fc 253, 254 Hardware 13 Hash table 716 High-water mark 714 HP-UX 795 HyperText Markup Language (HTML) 414 fg 271 jobs 269 kill 272 ksh 246 let 259 print 286 read 287 I return 265 select 262 ID группы 58 реальный 61 эффективный 61, 67 set 284 test 287 trap 289
typeset 266, 278 unalias 249 точка (.) 268 конвейер 274 массивы 278 ограниченный 293 опции командной строки 294 переменные 275 локальные 262, 266, 276 перенаправление 273 псевдонимы 248 полезные 250 предопределенные 250 путей 251 экспортируемые 251 функция 263 рекурсивная 267 L LIFO 745 Lightweight processes 576 Line discipline 743 cbreak mode 743 cooked mode 743 raw mode 743 Linux 23, 779, 783, 784, 796 Local area network (LAN) 372 Low-water mark 714 M Magic number 697 Маке-правило 467, 469 Маке-файл 466 Memory management unit (MMU) 709 Modem 14 Modified bit 710, 714 Monitor 13 Mosaic 414, 415 Motif 432, 433, 436, 445, 448, 449 Mount point 735 Mount table 735 Mouse 14 Multiuser mode 755 N Named pipe 746 National Science Foundation (NSF) 413 NetBSD 796 Network File System (NFS) 399, 767 Network Information Center (NIC) 407, 422 Network Object Model Environment (Gnome) 449 NSFNET 413 О Open file table 728 Open Software Foundation (OSF) 432, 793 Open Software product 343 OpenBSD 797 OpenLook 433 p Page daemon 714, 715 Page stealer 714 Pages 706 Parent PID (PPID) 558 Pending signal bitmap 721 Peripherals 14 Perl 154 AND 158 exit() 163 for 161 foreach 161 gmtime() 164 if 160 index() 164 length() 163 keys() 158 OR 158 split() 164 ключи к массиву 158 конкатенация строк 159 массивы 156 ассоциативные 157 (окончание рубрики см. на с. 812)
Perl (окончание)’. замещение команды 183 оператор: кавычки 193 диапазона 155 конкатенации 156 сравнения 160 печать текста 155 строки 156 PID 526, 560, 587, 632, 637, 651, 652 Pipe 20, 596 named 601 unnamed 597 Pipeline 181 POSIX 19, 344, 761, 784 Printer 14 переменные: встроенные 193 локальные 191 окружения 191 перенаправление ввода в буфер 194 подмена стандартных утилит 203 последовательности команд 184 родительский 190 условные последовательности 185 фоновое выполнение процессов 186 Signal handler array 721 Signals 721 Proccess: idle 700 runnable 700 running 700 sleeping 700 suspended 700 "zombified" 700 Process ID (PID) 558 Single-user mode 755 Socket 18 Solaris 792, 798 Source code control system (SCCS) 479 Sparse file 530, 730 Spoofing 418 Sticky bit 359 Strategy 739 R STREAMS 556, 748 Suitable username 29 RAM table 708 Random Access Memory (RAM) 13 Raw interface 738 Read Only Memory (ROM) 13 Referenced bit 710, 714 Region 707 Remote Procedure Call (RPC) 399 Revision Control System (RCS) 480 Root 22, 31, 66, 755 Root menu 436 Router 375 Run levels 756 Swap map 714 Swap space 714 System V 792, 794, 795 System V Release 4 (SVR4) 792, 794, 795 T Tape 14 Thrashing 720 Thread 576 Time quanta 703 Token Ring 377 Transport Layer Interface (TLI) 556 s Tru64 UNIX 797 Shadow-файл 37 Shared library 478 Shell 30, 172 subshell 190 группирование команд 185 дочерний 190 u UID 525 umask 207 Uniform Resource Locator (URL) 420 Unnamed pipe 746
V Valid bit 711, 715 Visual User Environment (VUE) 433, 448, 450 w Web-браузер 419 Web-страница 420 Wide area network (WAN) 375 Widget 433 X Window System 425, 427 XI1 427 X11R6 428 Х-клиент 428, 438 -background 441 -foreground 441 -geometry 440 -iconic 441 -title 441 копирование и вставка 441 Х-ресурс 444 Х-сервер 427, 428 А Авторское право 418 Адрес IP 377 Американский национальный институт стандартов (ANSI) 454 Аппаратные средства 13 Архив 472 файлов 116 Б Базовый класс 782 Безопасность 774 Библиотека, разделяемая 478 Библиотечная процедура: alarm() 583 bzero() 626 gethostbyname() 625 htonl 627 htons 627 inet_addr() 625 inet_ntoa() 626 nice() 570 ntoh 627 ntohs 627 pause() 585 perror() 511 setegid() 572 seteuid() 572 setgid() 572 setuid() 572 signal() 584 семейства exec() 568 Библиотечная функция printf() 514 Блок: косвенный 747 прямой 747 Брандмауэр 411 Буферный пул 725, 742 В Ввод/вывод 724 блочный 687 буферизация 725 каталог 734 обычный файл 728 сокет 749 специальный файл 737 терминал 743 Взаимодействие процессов 509, 510, 596 Видеокарта 13 Виджет 433, 443, 444, 448, 450 Внешнеее представление данных 399 Выполнение программы 497
г Геометрия экрана 428 Гиперсвязь 414, 421 Глобальная сеть 375 "Горячее" резервирование 792 Графический пользовательский интерфейс (GUI) 426 Интернет-сервер 627 Интерсеть 376 Интерфейс устройства 738 блочный 738 низкого уровня 738 символьный 738 к Дезинформация 418 Демон 378, 381 страниц 714, 715 Дескриптор: индексный 359, 688 файла 513 Деструктор 781 Диалоговое окно 434 Диск 13 архитектура 684 блок 684 загрузочный 691 дорожка 684 квоты 763 контроллер 687 переполнение 760 пространство обмена 714 сектор 684 суперблок 691, 692 фрагментация 687 цилиндр 685 Диспетчер окна 430 Дисциплина линии 743 cbreak-режим 743 канонический режим 743 низкоуровневый режим 743 режим с обработкой 743 Домен 379 Драйвер устройства 738 И Имитация соединения 418 Инкапсуляция 781 Интернет 403 безопасность 417 Карта обмена 714 Каталог 734 домашний 39 текущий рабочий 39, 569 Квант времени 703 Клавиатура 14 Класс объекта 781 Клиент-серверная модель 18, 606 Коллизия 373 Команда: bg 589 cd 48 echo 175, 176 eval 205 exec 205 exit 204 fg 589 kill 199 shift 206 stop 589 trace 498 umask 208 wait 201 меню 434 Командная кнопка 434 Командный интерпретатор 30 Коммутация пакетов 376 Конвейер 20, 181, 274, 332, 596, 746 безымянный 597, 746 именованный 597, 601, 746 Конструктор 781 Контроллер диска 684 Корневое устройство 696 Коэффициент чередования 686 л Лента 14 Локальная сеть 372
м п Магистраль сети 376 Маршрутизатор 375 Маршрутизация 791 динамическая 378 статическая 378 Менеджер памяти 697 Меню: Пакет 406 Палиндром 459 Параллельная обработка 785 Перегрузка методов 782 Переключатель 435 Переключение контекста 704 Переменная: выпадающее 431 корневое 436 окна 437 Метасимвол 176, 801 Метод 781 Многозадачность 195 Модем 14 Модуль 556 Монитор 13 Монтирование файловых систем 696 Мост 374 Мышь 14 $РАТН 568, 573 PATH 202, 251, 299 TERM 299 Переназначение 32 ввода 178, 179 вывода 42 Перенаправление 578 вывода 178 Планировщик 697, 703 Подкласс 782 Поисковая Web-машина 421 Политика приемлемого использования 419 Полоса прокрутки 435 н Постоянное запоминающее устройство (ПЗУ) 13 Накопитель CD-ROM 13 Наследование 782 Нить 576 Поток 745 восходящий 557 заголовок 745 нисходящий 557 Номер устройства: младший 739 старший 739 Потоковое сообщение 557 Право на секретность 418 Прерывание 580, 680 О аппаратное 675 обработчик 681 приоритет 680 Обработчик сигнала 580 Объединение сетей 376 Объект 781 mutex 577 Оперативное запоминающее устройство (ОЗУ) 13 Операционная система (ОС) 15 Опережающий ввод с клавиатуры 184 Отказоустойчивые системы 792 Отладчик 494 Очередь многоуровневых приоритетов 703 Привилегированный пользователь 22, 31, 66, 554, 755 Принтер 14 Пробуксовка 720 Программа 16 Программный интерфейс приложения (API) 434 Протокол: FTP 390 HTTP 420 IP 377, 406, 790 TCP 377, 406 UDP 407
Протокол Интернета 377 Протокол управления передачей 377 Процесс 16, 558, 675 getty 757, 770 init 558, 698, 756 login 757 pageout 699 sched 699 владелец 16 готовый 700 группа 591 зомби 564, 565, 588, 700, 723, 772 клиентский 606, 617 код выхода 203 легкий 576 область: Регулярное выражение 801 Редактор emacs 73, 84 буфер уничтожения 88 ввод текста 87 загрузка файла 90 запуск 84 инкрементный поиск 89 поиск и замена текста 90 перемещение курсора 87 помощь 86 редактирование текста 85 сохранение файла 90 Редактор vi 73, 256 буфер памяти 76 загрузка файла 81 замена текста 78, 80 данных 701 кода 701 пользователя 701 стека 701 ожидающий 700 приоритетный 592 приостановленный 700 работающий 700 реальный ID 572 серверный 606, 613 спящий 700 таблица 702 страниц 701 управления 591 фоновый 186 перенаправление ввода 188 перенаправление вывода 187 эффективный ID 572 ядра 699 Путь 40 абсолютный 40 полный 40 запуск 74 копирование и вставка текста 79 настройка 83 перемещение курсора 77 поиск 80 режим: ввода команд 74, 75 ввода текста 74 сохранение файла 81 удаление текста 77 Режим: многопользовательский 755 однопользовательский 755 пользовательский 678 ядра 678 Ретранслятор страниц 714 С Связь: жесткая 62, 134, 136, 531, 552, 689, 693, 728, 733, 747 символическая 134, 136, 288, 547, р 689, 694 Семафор 629 бинарный 630 Рабочий стол 426 виртуальный 432 Разделение ограниченных ресурсов 16 Разделение сегмента памяти 628 Распределенная система 393 набор 630 Сеть 372 Сигнал 721 CONT 272 EXIT 289
HUP 198 KILL 200 SIGABRT581 SIGALRM 582-584 SIGBUS 582 SIGCHLD 564, 582, 584, 587, 617, 723, 757 SIGCONT 582, 589, 700 SIGEMT 581 SIGFPE 581 SIGHUP 581, 757 SIGILL 581 SIGINT 241, 322, 340, 581, 583, 586, 588, 593, 722 SIGIO 515, 551 SIGKILL 581, 584 SIGPIPE 582, 598 SIGPOLL 582 SIGPROF 582 SIGPWR 582 SIGQUIT 241, 340, 581 SIGSEGV 582 SIGSTOP 582, 589, 700 SIGSTP 582, 584 S1GSYS 582 SIGTERM 241, 340, 365, 582, 757, 758 SIGTRAP 581 SIGTSTP 757 SIGTTIN 582, 592, 595 SIGTTOU 582 SIGURG 551, 582 SIGUSR1 582 SIGUSR2 582 SIGVTALRM 582 SIGWINCH 582 SIGXCPU 582 SIGXFSZ 582 TERM 198, 200, 272 битовая карта ожидающих сигналов 721 массив обработчиков сигнала 721 ошибки 188 Система управления исходным кодом 479 Системный вызов 508, 675, 676 accept() 615 chdirQ 569 chmod 33, 62 chmod() 548 chown() 547 closeQ 514, 531, 742 connect() 618 dup() 549, 733 dup2() 549 exit() 564 fchmod() 548 fchown() 547 fcntlO 515, 550 forkQ 560 ftruncate() 555 getdents() 545 getegid() 572 gethostname() 625 getgid() 572 getpgid() 593 getpid() 561 getppid() 561 getuid() 572 ioctl() 552, 742 killO 587, 724 lchown() 547 link() 552, 735 listenO 615 lseek() 529, 684, 730 mknod() 554, 734 mountO 735 open() 207, 515, 525, 741 pipe() 597 read() 527, 687 setpgid() 592 setpgrpO 721 signal() 722 stat() 544 sync() 554, 727 truncateO 555 unlinkO 531, 733 wait() 562, 566, 723 write() 528 Скрипт 188 junk 327 track 234 загрузки системы 756 Служба доменных имен (DNS) 409 Сокет 18, 605
Стандартный вход 32, 42 Стандартный выход 32, 42, 163 Стандартный канал вывода сообщений об ошибках 32 Страница памяти 706 Стратегия 739, 742 Структура каталогов 683 Суперпользователь 22 Счетчик ссылок 731 т Таблица: активных inode 728 векторов прерываний 681 векторов системных вызовов 678 монтирования 735 мусора 716 ОЗУ 708 открытых файлов 677, 728 переключения блочных устройств 739 переключения символьных устройств 739 процессов 677 страниц 707 бит копирования при записи 711, 717 бит модификации 710, 714 бит обращения 710, 714 бит применимости 711, 715 возраст 714 Терминал: жестко замонтированный 69 файл 770 управления 591, 745 Точка монтирования 735 Трассировка программы 497 У Удаленный вызов процедуры 399 Универсализация файловых имен 180 Уровень управления 756 Устройство управления памятью 709 Утилита 67 ас 773 accton 773 admin 480 аг 472 at 126 awk 128, 182 biff 138 cal 787 cancel 53 cat 42, 44 chgrp 63 chmod 28, 33, 62, 64 chown 66 chsh 174, 188 clear 28, 31 cmp 110 comb 490 compress 140 config 774 cp 49 cpio 50, 116, 768 cron 772 crontab 124 crypt 141 date 28, 31, 379 dbx 495 delta 481, 484 df 760, 773 diff 111 du 762 dump 122 echo 190 egrep 102, 773 emacs 41, 49, 73, 783 env 217 expr 222, 356 fgrep 102 file 59 find 58, 113, 186 finger 384 fsck 756, 759 ftp 391, 408 get 481 grep 101, 121, 184 groups 63 gunzip 141 gzip 141 halt 758 head 45
help 480, 481, 496 hostname 383 ifconfig 771 kill 199 last 773 Id 462, 478 lint 493 In 134 Ip 52 Ipr 54 Iprm 55 Is 42, 47, 57, 181 mail 36, 91, 125, 179, 188 make 58, 774 man 28, 32, 149 mesg 386 mkdir 46 mkfs 763 mknod 769 monitor 532 more 44 mount 151 mv 46 netstat 771 newfs 764 nohup 198 nroff 23 nsiookup 411 od 149 рас 773 page 45 passwd 37, 775 prof 492 prs 485 ps 196 pwd 39, 48 quota 773 ranlib 477 rep 390 reboot 759 restore 123 reverse 516 riogin 394 rm 51 rmdei 489 rmdir 50 route 771 rsh 379, 395 rusers 381 rwho 383 sa 773 sact 482 sed 142 shutdown 758 sleep 196 sort 16, 21, 106, 182 strip 504 stty 34, 71 su 137, 755, 775 tail 45 talk 388 tar 119, 768 tee 182 telnet 380, 396, 408 test 224 time 153 touch 470 tr 147 troff 23 tset 68 tty 69, 152 ul 148 umount 151 uncompress 140 unget 483 uniq 105 users 381 uucp 773 vi 41, 49, 73 w 383 wall 389 wc 56, 181, 597 who 21, 183, 382, 442, 597 whoami 137 write 387 xbiff 439 xciock 438 xhost 429 xrdb 445 xterm 440 Учетное имя пользователя 29
ф Файл 15, 675, 683 атрибуты 56 блок: двойной косвенный 690 косвенный 690 блочный 545 владелец 58 волшебное число 697 время модификации 58 групп 766 каталог 683 обычный 545, 683 параметры полномочий 60 паролей 765 разбросанный 530, 730 размер 57 символьный 545 скрытый 526 специальный 554, 675, 683, 735 схема доступа: косвенная 690 двойная косвенная 690 Файловая система: резервное копирование 764 сетевая 399, 767 создание 763 Файловый указатель 529 Флаг отложенной записи 726 Флажок 435 Фокус окна 430 Фонд открытых программных средств 343 Форма Бэкуса—Наура (BNF) 31, 803 Фрагментация, внутренняя 751 Функции, многократно используемые 459 Ц Цензура 418 Центральный процессор (ЦП) 12, 13 Шина 789 Шлюз 375 э Эквивалентность машин 379 я Ядро 674, 675, 677, 773 Язык гипертекстовой разметки документа 414 Язык программирования С 454
И* для 3-е изд»» программистов о пользователей Грэм Гласс • Кинг Эйблс В доступной и наглядной форме изложены все аспекты работы с ОС UNIX, включая обсуждение основных концепций ОС, основных утилит, различных оболочек, сетевой организации, оконных систем, системного программирования, свойств и администрирования системы. Книга будет полезна как для студентов компьютерных специальностей, так и для опытных программистов и пользователей. В третьем, обновленном и дополненном, издании выделяются следующие темы. • Описаны важные особенности UNIX и Linux • Включена новая глава про командный интерпретатор Borne Again shell (Bash) и описаны все четыре главные оболочки UNIX shells • Представлено более 100 утилит и их основные опции, включая awk, grep, sed, Perl, vi и emacs • Приведен полный текст программы Internet shell для поддержки перенаправления и конвейеризации с другими командными интерпретаторами на удаленных хостах Многочисленные иллюстрации, примеры, резюме, контрольные вопросы, упражнения и большое количество исходных кодов дополняют описание и помогают изучить любую версию UNIX. БХВ-Петербург 1 j0005, Санкт-Петербург, Измайловский пр., 29 E-mail: mail bhv.ru internet www.bhv.ru тел. (812)251-42-44 факс: (812) 251-12-95