Приемы профессиональной работы в UNIX. Введение

ВВЕДЕНИЕ

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

Однако применение UNIX с максимальной отдачей - дело нелегкое. Люди годами высказывали неудовлетворение тем, что она не является "дружественной" по отношению к пользователю - и это разумная критика, хотя на самом деле UNIX содержит средства для построения интерфейсов любого требуемого уровня сложности. Наиболее важная причина трудоемкости эффективного использования UNIX состоит в том, что в системе используются очень плодотворные идеи, не знакомые многим людям, работавшим с более простыми операционными системами. UNIX предоставляет также гораздо больше инструментальных средств, более гибких и с существенно большими возможностями, чем, например, популярная MS-DOS (в чем можно убедиться беглым сравнением соответствующих руководств). Вероятно, Вы, читатель, начинали с изучения UNIX в объеме, достаточном для решения конкретных задач в вашей системе, будь то текстовая обработка и форматирование текстов, программирование или запуск статистических пакетов. Через некоторое время вы, видимо, накопили (от других людей или в результате собственной работы) небольшой набор приемов, включающий, возможно, некоторый опыт простого программирования для интерпретатора командного процессора.

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

ЧТО ВЫ ДОЛЖНЫ УЖЕ ЗНАТЬ

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

Если вы только начинаете использовать UNIX, то книга "UNIX Primer Plus" ("Расширенный букварь по UNIX") Митчела Уэйта (Mitchell Waite), Дональда Мартина (Donald Martin) и Стефена Прата (Stephen Prata) (SAMS, 1983) даст вам исчерпывающее введение в предмет. Если вы уже не новичок, но все еще не имеете четкого представления о внутренней работе командного процессора и программировании для него, то вам даст фундаментальные основы другая книга - "Advanced UNIX - A Programmer's Guide" ("Руководство программиста по расширенному UNIX") Стефена Прата (SAMS, 1985). Фактически эта книга является идеальным спутником и справочником для дополнительных исследований, которые составляют нашу книгу.

О КАКОЙ СИСТЕМЕ UNIX ИДЕТ РЕЧЬ

Имеется, конечно, много вариантов UNIX. Помимо основных семейств реализаций UNIX (AT&T System V, Microsoft XENIX и Berkeley [BSD]), распространено несколько различных командных процессоров, среди которых наиболее широко используются два - командный процессор Bourne командный процессор Си. Все командные файлы в данной книге были проверены и в System V, и в XENIX с использованием командного процессора Bourne, за исключением тех случаев, которые специально отмечены. БОЛЬШИНСТВО наших командных файлов работает также под управлением командный процессор Bourne в BSD, хотя нескольких команд System V нет в системе BSD и наоборот. Мы пытались указать те места, в которых эти две системы существенно отличаются, и дать некоторые альтернативные подходы для пользователей BSD.

Большинство наших командных файлов было также переписано для запуска под управлением программного процессора Си после учета синтаксических отличий. Если вы пользуетесь командным процессором Bourne и хотели бы поэкспериментировать с программным процессором Си, то хорошим введением является указанная ранее книга "Advanced UNIX - A Programmer's Guide".

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

  • Какая у вас версия UNIX? Отмечали ли мы что-нибудь относительно этой версии?
  • Какой командный процессор вы используете (Bourne, Си или другой)?
  • Должны ли вы поменять маршрутное имя в силу того, что в вашей системе что-то находится в другой части?
  • Не утратили ли вы прав доступа к определенному файлу? Не нужно ли вам применить команду su, чтобы получить другой идентификатор пользователя или стать в корень?
  • Использует ли данный командный файл предварительно подготовленный командный файл, который вы еще не ввели в вашу систему?

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

ОБЗОР ГЛАВ

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

Глава 1 - введение в среду выполнения системы UNIX в целом, способы обращения пользователей к ее различным частям. Вы увидите, каким образом ваш рост как мастера UNIX позволит вам максимально успешно применять все аспекты и особенности среды.

В главе 2 рассматривается наиболее важная особенность среды UNIX - файловая система - и вводятся инструментальные средства для изучения файловых структур и содержимого файлов.

Глава 3 предоставляет средства для практических каждодневных задач по сопровождению файлов - для копирования и сохранения файлов и для удаления ненужных файлов.

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

В главе 5 обращено внимание на вашу собственную среду (home-среду) и личное администрирование. Сюда относится управление вашим планированием и задачами. Представлено несколько полезных средств, помогающих вам.

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

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

Глава 8 посвящена коммуникациям в UNIX - сфере, значимость которой быстро возрастает. Этот материал поможет вам работать с несогласованными модемами, а также с проблемами безопасности и управления, которые возникают при работе с командами cu и uucp. Предлагаемые средства помогут вам в работе как с коммуникациями от UNIX к другой операционной системе, так и от UNIX к UNIX. Приведены также практические примеры аппаратных конфигураций.

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

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

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


Глава 1. Среда системы UNIX

  • Введение
  • Многообразие сред системы UNIX
  • Ваш регистрационный каталог: как сделать его комфортным
  • Теории относительности a la UNIX
  • Жизнь системы UNIX: некоторые метафоры

ВВЕДЕНИЕ

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

Читая эту главу, вы, возможно, захотите прочитать (или перечитать) команды profile(4), environ(5), term(5), termcap(5) и termio(7) в руководствах по UNIX, чтобы ознакомиться с механизмами, которые предоставляет UNIX для установки рабочей среды.

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

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

"МНОГООБРАЗИЕ СРЕД"

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

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

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

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

Рис. 1-1 Многообразие компьютерных сред

---------------------------------------------------------------------------
   \                L7                / Командные файлы  (scripts)
    \________________________________/
     \              L6              / Прикладные программы,
      \____________________________/ интерпретатор команд, языковые генераторы
       \            L5            / Компилятор
        \________________________/
         \          L4          / Операционная система
          \____________________/
           \        L3        / Ядро
            \________________/
             \      L2      / Условная машина, ассемблер
              \____________/
               \    L1    / Микропрограммы
                \________/
                 \  L0  / Логические схемы, аппаратные средства
                  \____/
---------------------------------------------------------------------------

УРОВЕНЬ 0 - АППАРАТНЫЕ СРЕДСТВА

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

Отметим, что хотя прогресс на этом уровне продолжается, это вызывает очень малые изменения на верхнем слое пирамиды. Философия системы UNIX состоит в том, чтобы изолировать низкоуровневый аппаратный слой и обеспечить единообразные интерфейсы к нему, которые не нуждаются в изменениях "наверху". Верхний слой даже не должен знать о нижнем слое. Это не значит, что события в мире аппаратуры не важны в реальном мире, ведь противоречия реального мира влияют на скорость и емкость ресурсов, не говоря уже об их стоимости.

УРОВЕНЬ 1 - МИКРОКОМАНДЫ

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

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

УРОВЕНЬ 2 - УСЛОВНАЯ МАШИНА

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

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

УРОВЕНЬ 3 - ЯДРО

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

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

УРОВЕНЬ 4 - ОПЕРАЦИОННАЯ СИСТЕМА

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

УРОВЕНЬ 5 - КОМПИЛЯТОРЫ

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

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

УРОВЕНЬ 6 - ПРИКЛАДНЫЕ ПРОГРАММЫ

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

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

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

УРОВЕНЬ 7 - КОМАНДНЫЕ ФАЙЛЫ

Этот верхний уровень является языком, который интерпретирует программа /bin/sh (в случае командного процессора Bourne shell). Ее синтаксис поддерживает полный язык программирования. Хотя этот язык лишен ряда встроенных структур и функций современного языка высокого уровня, он имеет все необходимое для написания полезных программ. Большим плюсом является то, что языку командного процессора доступны в качестве внешних функций любые средства, утилиты и программы, которые имеются в системе UNIX. Это значит, что алгоритмы, которые могут потребовать сто или более строк на языке низкого уровня типа Си, язык командного процессора может выразить в двадцать строк. За счет потери производительности, разумеется.

ВАШ "РЕГИСТРАЦИОННЫЙ КАТАЛОГ"

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

Помимо определения вашего места в системе, можно привязать "домашний" каталог (home-catalog) к вашим точным спецификациям. "Регистрационный каталог" - это не только область файловой памяти, но и вся ваша среда. Можно установить переменные командного языка для определения путей по системе. Можно создать инструментарий, чтобы помочь вам в работе.

ЧТО ТАКОЕ СОСЕДСТВО?

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

Когда вы регистрируетесь в системе, вы можете попасть в любое место древовидной структуры. Регистрационный каталог определяется в файле паролей. К ней можно обратиться по имени $HOME, которая является одной из предопределенных переменных командного языка для вашего использования. Теперь у вас есть персональная древовидная структура под этим именем каталога. Она полностью ваша и может быть сделана недоступной для кого угодно, кроме корня. Вы можете организовать ваш регистрационный каталог ($HOME) любым приемлемым для вас способом.

ПЛАНИРОВКА РЕГИСТРАЦИОННОГО КАТАЛОГА

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

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

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

Корнем этого дерева является регистрационный каталог, который определен в пятом поле файла /ets/passwd. Использование файла паролей описано в passwd(4). Вот пример парольного входа автора:

   russ:.pDIPADYfIXBY:103:101:Russ
   Sage:/usr/russ:/bin/sh

Слева направо вы видите имя пользователя (russ), пароль (.pDI...), идентификатор пользователя (103), идентификатор группы (101), личный комментарий, имя регистрационного каталога (/usr/russ) и командный процессор shell, получаемый при входе в систему (/bin/ sh).

ФАЙЛЫ В РЕГИСТРАЦИОННОМ КАТАЛОГЕ

Файлы, описываемые ниже, разделяются на три категории: файлы, которые обычно присутствуют в вашей системе, если вы работаете в System V, файлы, которые имеются обычно в Berkeley 4.2, и файлы, которые создаются при использовании программ из настоящей книги.

ФАЙЛЫ System V

Первый файл - это .news_time. Дата этого файла соответствует тому, когда вы последний раз читали новости в каталоге /usr/news. Для чтения новостей пользуйтесь командой news(1). Эта команда выдает новости, появившиеся позже даты создания файла .news_time.

Следующий файл - .profile. Этот файл выполняется при каждой регистрации в интерпретаторе shell и может быть использован для привязки вашей собственной среды. В дальнейшем мы рассмотрим этот файл более подробно.

Следующий файл - calendar (календарь). Этот файл содержит даты и сообщения. Команда calendar(1) читает в этом файле даты, очень близкие к текущей дате. Затем печатаются или посылаются вам по почте сообщения.

Последний файл - mbox, ваш системный почтовый ящик. Когда вы с помощью команды mail(1) сохраняете почту, она направляется по умолчанию в mbox.

ФАЙЛЫ 4.2 BSD

Первым файлом здесь является .cshrc. Это первая стадия настройки системы на пользователя, выполняемой интерпретатором cshell. В системе UNIX присутствие "rc" в имени файла означает "команды запуска" ("run commands") или "запуск при загрузке" ("run on boot up").

Файл .login является синонимом файла .profile интерпретатора sh. Этот файл содержит команды настройки на среду пользователя, которая вам нужна при регистрации в системе.

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

Следующий файл - .msgsrc, предназначенный для команды msgs(1) системы Berkeley. Файл .msgsrc содержит последний, прочитанный вами файл сообщений. Файлы сообщений хранятся в виде последовательно пронумерованных файлов в каталоге /usr/msgs.

ТРЮКИ С ГЛАВНЫМИ ФАЙЛАМИ

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

Следующий файл - .trashcan. Это каталог, который временно хранит файлы, удаленные вами. Если вы уверены, что они вам не нужны, то их можно удалить навсегда. Эта особенность рассмотрена в главе 3. Последний файл - .phone.list. Это ваша личная база данных со списком телефонов. Она обслуживается командой phone (см. главу 5).

КАТАЛОГИ

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

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

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

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

   /.profile
   /etc/bcheckrc
       brc
       checklist
       gettydefs
       group
       inittab
       motd
       mountable
       unmountable
       passwd
       profile
       rc
   /usr/lib/crontab
   /usr/lib/uucp/L.sys
       USERFILE
       uudemon.day
       uudemon.hr
       uudemon.wk

или любой другой информации о системе.

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

Следующий каталог - mail. Это хорошее место для размещения вашей почтовой корреспонденции от других людей, использующих систему. Имена файлов в этом каталоге являются пользовательскими. Например, если бы я получил почту от Боба, то она находилась бы в файле с именем bob.

Каталог src - для всего исходного кода. Логически сгруппируйте ваш исходный код по подкаталогам, чтобы облегчить его поиск в будущем. Возможными подкаталогами являются asm для ассемблерного кода, c для исходного кода на Си, games (игры), misc (разное), script для командных файлов и sys для любого исходного кода, относящегося к системе. (Если вы держите исходные тексты ваших личных инструментов в каталоге /bin/src, то здесь вы, возможно, продублируете их.)

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

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

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

АНАЛИЗ ПРИМЕРА ФАЙЛА НАСТРОЙКИ СИСТЕМЫ

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

   1   # @(#).profile v1.0 Defines "home" on the system
                                        Author: Russ Sage
   3   CHOICE="ushort"
   4   case $CHOICE in
   5   ufull)   PS1="`uuname -l`> ";;
   6   ushort)  PS1="`uuname -l|cut -c1-3`> ";;
   7   graphic) PS1="^[[12mj^[[10m ";;
      8   esac
   10  LOGNAME=`logname`
   11  HOME=`grep "^$LOGNAME:" /etc/passwd | cut -d: -f6`
   12  MAIL=/usr/spool/mail/$LOGNAME
   13  export LOGNAME HOME MAIL

   15  HA=$HOME/adm
   16  HBB=$HOME/bbs
   17  HB=$HOME/bin
   18  HD=$HOME/doc
   19  HE=$HOME/etc
   20  HM=$HOME/mail
   21  HP=$HOME/proj
   22  HSR=$HOME/src
   23  HSY=$HOME/sys
   24  HT=$HOME/tmp
   25  HDIRS="HA HBB HB HD HE HM HP HSR HSY HT"
   26  export $HDIRS HDIRS

   28  P=/usr/spool/uucppublic/$LOGNAME; export P

   30  CDPATH=.:..:$HOME:$HDIRS
   31  PATH=.:/bin/:/usr/bin:/etc:$HOME/bin
   32  SHELL=`grep "^$LOGNAME:" /etc/passwd|cut -d: -f7`
   33  export CDPATH PATH SHELL

   35  case "`basename \`tty\``" in
   36  console) eval `tset -m ansi:ansi -m :\?ansi -r -s -Q`;;
   37  tty00)   eval `tset -m ansi:ansi -m :\?ansi -r -s -Q`;;
   38  tty01)   eval `tset -m ansi:ansi -m :\?ansi -r -s -Q`;;
   38  esac

   41  echo TERM = $TERM
   42  TERMCAP=/etc/termcap
   43  export TERM TERMCAP

   45  HZ=20
   46  TZ=PST8PDT
   47  export HZ TZ

   49  umask 0022

   51  echo "\nTime of this login : `date`"
   52  lastlog -l

   54  RED="^[[31m"
   55  GREEN="^[[32m"
   56  YELLOW="^[[33m"
   57  BLUE="^[[34m"
   58  CYAN="^[[35m"

   60  case "`date|cut -d' ' -f1`" in
   61  Mon)    echo "$RED";;
   62  Tue)    echo "$GREEN";;
   63  Wed)    echo "$YELLOW";;
   64  Thu)    echo "$BLUE";;
   65  Fri)    echo "$CYAN";;
   66  esac

КАК РАБОТАЕТ .profile

Когда вы входите в систему, регистрационная программа выполняет интерпретатор shell с параметром '-' (например, -sh). Это сигнализирует интерпретатору shell, что сейчас момент регистрации и что должен быть выполнен файл настройки. Сначала выполняется /etc/profile - общий файл настройки, установленный системным администратором для всех пользователей, а затем файл .profile пользователя. Каждый интерпретатор shell после этого больше не запускает эти установочные программы. В файле /etc/ profile интересно проверить машинно-зависимую информацию и посмотреть, какие умолчания были для вас установлены. Если вы хотите выполнить ваш .profile в любой момент после входа в систему, наберите ". .profile" (можно писать и ".profile", проверено, что обе формы работают - Прим. переводчика).

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

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

ПОСТРОЧНЫЙ РАЗБОР ПРИМЕРА ФАЙЛА НАСТРОЙКИ

Строки 3-8 делают хитрую установку главной подсказки - переменной PS1. В строке 3 инициализируется переменная, которая выбирает подсказку. Значение ushort жестко закодировано в файле, но вы всегда можете запросить его или установить его в зависимости от файла.

Первой альтернативой является ufull, используемая для установки подсказки в виде полного имени узла uucp в локальной системе. Вы выбираете такую подсказку, если используете несколько машин и для доступа к одной машине применяете другую. Отличительная подсказка напоминает вам, какой машиной вы пользуетесь. Отметим, что подсказка имеет одинаковое число символов и для короткой строки, и для длинной. Если же вам нужно имя узла uucp, но не нужна длинная строка для подсказки, вы можете выбрать ushort, что дает первые три символа имени узла. Как показано в строке 6, имя получается применением команды uuname для получения локального имени узла (опция -l). Затем это имя пропускается через команду cut, которая вырезает символы с первого по третий. Результат присваивается переменной подсказки.

Последняя альтернатива для тех из вас, кто имеет графические символы. Назначение в строке 7 есть греческий символ. Его можно получить применением специальных управляющих последовательностей, которые указывают терминалам отображение специальных символов. Символы ^[ являются визуальным представлением управляющего символа в программе vi. Вы можете получить этот символ в программе vi, набрав control-v, а затем ESC. Последовательность ESC[12m означает, что следующий символ будет напечатан как графический. Символ j является вашей подсказкой и превращается в графический символ, который выдается на ваш экран. Используя различные символы алфавита, вы можете иметь в виде вашей подсказки почти любой графический символ. ESC[10m возвращает ваш терминал в режим обычного текста, так что все символы, печатаемые после того, как вы набрали ESC[10m, являются нормальными.

Если вы хотите сохранить вашу пользовательскую подсказку для всех подчиненных интерпретаторов shell, экспортируйте ее. Иначе вы получите $ для всех интерпретаторов shell нижнего уровня.

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

Строка 11 инициализирует переменную HOME. Она тоже устанавливается для вас системой, но мы хотим показать, как делать эти вещи осознанно, а не по умолчанию. Сначала мы ищем в файле паролей запись, соответствующую переменной LOGNAME. Мы ищем от начала строки имя, которое завершается символом :, чтобы убедиться, что найдено только корректное соответствие имени пользователя. Затем вся запись посылается команде cut, которая вырезает шестое поле - регистрационный каталог. Преимущество такой стратегии в том, что регистрационный каталог автоматически меняется, если меняется запись в файле /etc/passwd.

Строка 12 инициализирует переменную MAIL. Определяя MAIL, вы указываете, что вы должны быть уведомлены о посылке вам новой почты, если вы находитесь в режиме on line. Строка 13 экспортирует эти переменные, так что они доступны нам в порожденных интерпретаторах shell.

Строки 15-24 определяют все каталоги первого уровня в нашем регистрационном каталоге. Большинство имен состоят из двух букв, некоторые из трех. Теперь мы можем применять команды такого вида:

   $ cd $HD
   $ ls -R $HSR
   $ cu -ltty00 dir | tee $HBB/board/session$$

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

   $ for DIR in $HDIRS
   > do
   > echo "disk usage for $DIR: `du -s $DIR`"
   > done

Строка 26 экспортирует переменные так, чтобы мы могли всегда их использовать. Отметим, что мы экспортировали $HDIRS и HDIRS. Перед тем, как выполнить экспортирование, $HDIRS было распространено на все различные имена переменных. Следовательно, фактически мы экспортировали все имена плюс саму переменную HDIRS.

Строка 28 инициализирует P так, чтобы это был ваш каталог в PUBDIR, то есть /usr/spool/uucppublic. Теперь у нас есть простой способ ссылаться на наши файлы при работе с командой uucp.

Строка 30 устанавливает CDPATH. Это путь, который проверяется, когда вы выполняете команду cd. Сначала проверяется текущий каталог (.) на предмет того, есть ли в нем имя каталога, в который вы хотите попасть. Затем проверяется .. (родительский каталог). После этого просматривается ваш регистрационный каталог. Последним назначением CDPATH является $HDIRS, что подключает имена всех подкаталогов. Цель этих имен - позволить команде cd искать в соответствующем каталоге введенное вами имя.

Например, если бы вы были в /etc и набрали "cd doc", вы бы попали в $HOME/doc, поскольку CDPATH содержало в себе $HOME. Аналогично, если бы вы имели подкаталог $HOME/doc/status и ввели "cd status" откуда-либо из другого места в системе, вы бы пришли в $HOME/doc/status, так как корень $HOME/doc был в CDPATH.

Порядок поиска в каталогах такой же, как объявлено в переменной CDPATH. Если вы вводите имя каталога, которое встречается более чем в одном месте, вы попадаете в первый каталог, обнаруженный при последовательном поиске. Например, если бы вы сказали "cd sys", то попали бы в $HOME/sys прежде, чем в $HOME/ src/sys.

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

Таблица 1-1 Три способа использования команды cd

Абсолютный | CDPATH переменной | Относительно
cd /usr/russ | cd | cd $HOME
cd /usr/russ/src/asm | cd asm | cd $HSR/asm
cd /usr/russ/doc/paper/conf | cd paper/conf | cd $HD/paper/conf
cd /usr/russ/tmp | cd tmp | cd $HT

Строка 31 инициализирует переменную PATH. PATH работает таким же образом, как CDPATH. Она ищет программы, которые нужно запустить, в каждом каталоге, указанном в переменной PATH. Если имя не найдено ни в одном из этих каталогов, печатается сообщение ": not found" ("<имя-файла>: не найдено").

Поскольку мы можем установить наш PATH как угодно, можно указать все таинственные места в системе, в которых расположены исполняемые модули. Когда мы хотим их выполнить, мы не обязаны их искать и набирать полное маршрутное имя. Чтобы дополнить PATH, введите, например, следующее:

   PATH=$PATH:/usr/lib/uucp

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

Строка 32 инициализирует переменную SHELL. Эту переменную могут использовать не более чем одна или две утилиты. Обычно она устанавливается системой, когда вы регистрируетесь. Строка 33 экспортирует переменные CDPATH, PATH и SHELL.

Строки 35-39 - это хитрый способ установки определений терминалов. Строка 35 начинается со спрятанной команды tty, заключенной в знаки ударения (`...`). Выходом команды tty является "/dev/tty00". Затем мы берем основное имя этой строки, т.е. "tty00". Далее мы используем структуру переключателя по этому значению, чтобы увидеть, что мы хотим сделать для каждого конкретного терминала. Команды tset, показанные здесь, относятся к среде XENIX и могут быть неприемлемыми в вашей среде.

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

В строке 42 устанавливается переменная TERMCAP, указывающая на /etc/termcap. Это обычный способ установки переменной TERMCAP. Другой способ - присвоить TERMCAP текущую закодированную строку, которая находится в файле описания терминала. Если TERMCAP установлен на закодированную строку, то утилите vi нет необходимости обращаться к файловому вводу-выводу, чтобы получить характеристики вашего терминала. Строка 43 экспортирует эти значения так, чтобы они были доступны на любом уровне интерпретатора shell.

Строка 45 устанавливает частотную переменную. Это переменная из XENIX и, возможно, имеется в System V. Она используется для установки информации о времени.

Строка 46 устанавливает информацию о зоне времени, как это требуется в библиотечном вызове ctime(3). Имея переменную TZ, вы можете перекрыть подразумеваемую зону времени при доступе ко времени из программы на языке Си. Строка 47 экспортирует эти переменные.

Строка 49 устанавливает ваше значение маски пользователя (umask). Она управляет подразумеваемым разрешением доступа для всех файлов, которые вы создаете. Система вычитает значение umask из 777. Результат становится правом доступа к файлу, в данном случае 755. Когда вы создаете каталог с правом доступа 755, этот каталог показывается командой "ls -l" как rwxr-xr-x. Когда вы создаете некаталоговый файл с правом доступа 755, этот файл показывается как rw-r--r--, что эквивалентно 644. Некаталоговые файлы не имеют бита x, поэтому их нельзя исполнить. Каталогам же нужен установленный бит x, чтобы они были доступны по команде cd.

Строки 51 и 52 сообщают вам о времени вашего сеанса работы в системе. Строка 51 сообщает вам текущее время вашего входа в систему, а строка 52 вызывает программу lastlog, которая печатает дату вашей последней регистрации в системе. Программа lastlog описана в главе 5.

Строки 54-58 инициализируют переменные, генерирующие цвета на цветном мониторе. Управляющие значения являются стандартными значениями кодов ANSI. Это работает в системе XENIX и может работать в вашей системе. Растровая графика не доступна, но имеется символьная графика и различные основные (foreground) и фоновые (background) цвета. Основные цвета кодируются числами, начиная с 30, а фоновые цвета - числами с 40.

Строки 60-66 - просто для забавы. Они представляют собой хитрый способ устанавливать каждый день на экране различные цвета. Строка 60 начинается с запуска команды date и передачи ее выхода по конвейеру команде cut. Вырезается первое поле, которое является днем недели. Затем мы создаем структуру переключателя по строке дня, выполняя различные действия для каждого дня. Благодаря эхо-отображению управляющих последовательностей, монитор реагирует немедленно.


ГЛАВА 1. Среда системы UNIX

ТЕОРИЯ ОТНОСИТЕЛЬНОСТИ ВНУТРИ СИСТЕМЫ UNIX

Теперь, когда мы ознакомились с "домашней" средой, следующий шаг - обратиться к средам, находящимся вне регистрационного каталога ($HOME). Например, что представляют собой другие каталоги на том же уровне, что ваш $HOME ? Кто еще работает в системе? Как попроще получить доступ к их каталогам? Можете ли вы запускать программы в чужих каталогах? Такого рода вопросы и действия относятся к другим людям в вашей системе.

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

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

Ниже показаны некоторые примеры относительных команд.

      ls -l $HOME/..     перечисляет файлы в моем родительском
                     каталоге.

      cd ../../..        в предположении, что текущим каталогом
                     является /usr/russ/src/c, делает моим
                     текущим каталогом /usr.

      ls .               перечисляет файлы в текущем каталоге.

      ls ..              перечисляет файлы в моем родительском
                     каталоге.

      $HOME/../../bin/ls запускает ls в каталоге
                     /usr/russ/../../bin, т.е. в /bin/ls.

      ../fred/bin/ls     запускает команду ls в каталоге
                     двоичных модулей Фреда, который имеет
                     тот же родительский каталог, что и я,
                     т.е. /usr/fred/bin/ls.

ОБЩАЯ СИСТЕМНАЯ СРЕДА

Системная среда не просто НАХОДИТСЯ в системе UNIX, а ЯВЛЯЕТСЯ системой UNIX. Как мы увидим в этой книге, вся система - UNIX, Си, команды, файлы и т.д. - это просто логический подход к функционированию компьютера. Программное обеспечение - это то, что определяет система для конечного пользователя. Мы можем представлять все машины, работающие в системе UNIX, как одинаковые и трактовать каждый UNIX как один и тот же. Мы предполагаем, что реакция машины будет каждый раз одинаковой.

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

      программы = структуры данных + алгоритмы

   так и

      UNIX = файловое дерево + утилиты

Среда UNIX - это сочетание двух важнейших вещей: файлового дерева и интерфейса системных вызовов. Это дерево допускает бесконечное расширение возможностей, позволяя монтировать внешние дисковые области в любой точке файловой системы. Дерево помогает также в сборе логически связанных файлов, что делает систему более организованной. Интерфейс системных вызовов обеспечивает набор инструментов, из которых можно построить большинство других функций. Определение интерфейса System V имеется в виде типографской книги и может быть найдено в книжных магазинах. Строгое следование этому стандарту гарантирует совместимость с постоянно развивающейся AT&T System V.

ОБЩЕЕ ФАЙЛОВОЕ ДЕРЕВО

Для того чтобы лучше понять мир UNIX, посмотрите пример распечатки структуры UNIX на рис.1-3 . Это наглядное представление полного дерева корневой файловой системы. Любые другие расширения файловой системы монтируются на эту файловую систему. Точкой временного монтирования является /mnt. Более постоянные точки монтирования должны быть созданы администратором, например /0, /1 и т.д. или /usr1, /usr2 и т.д.

ПЕРВЫЙ СЛОЙ

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

Следующий каталог - /dev, в котором размещены все файлы уст- ройств. Файлы устройств являются точками доступа к периферии, подсое- диненной к системе. Этот файл привязан к периферии с помощью ядра и драйвера устройства.

Административные утилиты и конфигурационные файлы хранятся в /etc. Примерами являются getty и gettydef, init и inittab, а также файл паролей (/etc/passwd).

Следующий каталог - /lib, где размещены библиотеки компилятора.

Здесь могут храниться и другие типы библиотек.

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

Следующий каталог - /mnt. Это временная точка монтирования для файловых систем. Мы часто монтируем и демонтируем файловые системы просто для того, чтобы запустить быструю проверку чего-либо. Здесь подходящее место для этого.

Главным временным рабочим каталогом системы является /tmp. Многие утилиты, такие как vi, fsck, интерпретаторы shell и программы резервного копирования, используют /tmp для хранения рабочих файлов.

Следующий каталог - /usr, который применяется как точка монтирования. Файловая система, смонтированная здесь, содержит дополнительную системную информацию и каталоги пользователей. Это разделение между загружаемой файловой системой и пользовательской файловой системой было сделано, чтобы сбалансировать загрузку диска. Если бы все важные файлы были в одном разделе, он был бы слишком большим. Производительность может быть ухудшена, если все действия направлены в одну логическую область диска. Благодаря разбивке всей системы на две, каждая файловая система поддерживает разумное количество свободного пространства. Чуть ниже мы рассмотрим каталог /usr более подробно.

Последний файл - это само ядро, /unix. Весь /unix фактически существует и представляет собой большой a.out (скомпилированный объектный файл). Ядро изготавливается путем запуска ld на группе библиотек, которые загружаются по очереди в огромный исполняемый модуль, называемый /unix. Машина запускается с первых 512 байтов корневой файловой системы. Программа начальной загрузки, которая находится здесь, загружает программу загрузки большего размера, иногда называемую /boot. /boot загружает и запускает /unix.

ВТОРОЙ СЛОЙ

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

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

В каталоге bin имеются исполняемые модули, которые используются менее часто, чем модули в двоичном каталоге корневого уровня (/bin).

Почти все исполняемые модули распределены между этими двумя каталогами. Другие исполняемые модули рассеяны по всей системе, например /usr/lib/uucp/uucico и /usr/lib/ ex3.7preserve.

Далее games. UNIX приходит с ассортиментом интересных игр. Большинство из них текстовые, но предоставляется несколько программ графического типа, например worm, worms и rain.

Каталог include содержит все файлы-заголовки. Файлы-заголовки используются в программах на языке Си для определения структур и системных присваиваний, полезных для программирования. Здесь имеется подкаталог sys, который содержит все файлы= заголовки, относящиеся к системе. Читая эти файлы-заголовки, можно многое узнать о системе UNIX.

Следующий каталог - lib, который содержит библиотечные файлы для всех видов "имущества": файлы печатающих устройств, файлы поддержки утилиты vi, другие языки и uucp. Каталог /usr/ lib представляется складом всяких библиотек, которые имеются в системе, отличных от библиотек компилятора.

Каталог lost+found находится здесь для той же цели, что и одноименный каталог корневого уровня. Каждая файловая система должна иметь такой файл. Без него fsck не имеет временного места для размещения полуудаленных файлов и поэтому удаляет их навсегда.

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

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

В каталоге news хранятся все файлы новостей. Эти файлы именованы в соответствии с порядком, в котором они были введены в каталог. Команда news(1) смотрит на дату файла $HOME/ .news_time, чтобы сообщить, какие новости вы еще не читали.

Каталог preserve предназначен для файлов, связанных с утилитой vi. Они помещаются сюда, когда вы работаете с vi или с редактором ex и пропадает питание машины либо ваш сеанс работы прерывается в виде "зависания". Когда в системе восстанавливается питание, /tmp содержит файлы редактора ex. Из каталога /etc/rc запускается утилита /usr/lib/ex3.7preserve, которая просматривает /tmp, преобразует его в сохраненный файл и помещает его в /usr/preserve. Когда вы входите в систему, вы получаете почту о том, что у вас имеется сохраненный файл редактора, который вы можете восстановить и поместить его на исходное место.

Каталог pub не содержит ничего особенного, обычно в нем просто некоторые информационные файлы вроде таблицы ASCII или греческих символов.

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

В каталоге src хранится исходный код системы UNIX, если он имеется в системе. От этого каталога ответвляется много уровней: команды, библиотеки, код ядра, код машинного языка и автономные утилиты. Часто в /usr/src хранится также исходный код для локальной машины.

Каталог sys традиционно хранит файлы, необходимые для генерации нового ядра. Это файлы-заголовки, конфигурационный файл, библиотеки и командный файл для создания нового ядра из всех этих файлов.

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

ЖИЗНЬ СИСТЕМЫ UNIX: НЕКОТОРЫЕ МЕТАФОРЫ

UNIX - это особый мир, живущий своей жизнью. Его социальная структура имитирует реальную жизнь, с правительством, содержащим правителя (корень root), штатом поддержки (bin, cron, lp, sys) и массами (/usr/*). Массы не имеют доступа к мощи правителя, если не используют предварительно установленных средств (/bin/su) или не занимаются криминальными действиями и нарушением мер безопасности. Как и в любом обществе, большая многопользовательская система UNIX устанавливает права и обязанности своих пользователей.

При входе в систему пользователь получает свое "место под солнцем" (регистрационный каталог - $HOME ). Это место зависит от того, что было раньше (от родительского каталога ..), а будущие места зависят от того, что происходит позже (каталоги, подчиненные $HOME). Работа распределяется по организациям и иерархиям в зависимости от их функций в обществе (все пользователи в /usr, все транзитные файлы в /usr/spool, все функции безопасности в /etc). Посмотрите вокруг себя в вашей системе, чтобы ознакомиться с вашим миром. Вы можете после этого выбрать, участвовать ли в некоторой части этого мира или игнорировать ее.

Движение людей в системе UNIX происходит параллельно. Некоторые области (/tmp) доступны всем, а некоторые области сильно охраняются от большинства людей (/etc/passwd). Транспортная служба может перевезти наши вещи (передача файлов по сетям uucp). Мы даже можем воспользоваться общественным транспортом, чтобы добраться в разные части города (вход в другие системы (rlogin), эта особенность имеется только в BSD).

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

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

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

Так же, как компьютерный век проходит под присмотром электронной автоматики, так и UNIX ведет таблицы о деятельности всех пользователей. Механизмы учета организованы правительством (внутри ядра) и всегда записывают действия каждого пользователя. Тем не менее, это свободное общество в той мере, что вы можете получить распечатку о вашем кредитном состоянии (используя acctcom для печати учетных записей). Хотя система UNIX имеет негативные аспекты (как и человеческое общество), в ней есть также некоторые очень позитивные особенности. Гибкость системы и богатство инструментов дает нам очень продуктивную и детально разработанную рабочую среду. Наша производительность в этом смысле ограничена в основном нашим собственным воображением. Когда работа становится слишком утомительной и скучной, мы всегда можем создать средства, делающие за нас эту работу. Это обстановка свободной инициативы, в которой хорошие идеи могут дать значительное увеличение производительности.


Глава 2. Доступ к файлам

  • Введение
  • 2.1. Поиск файлов
    • 2.1.1. tree - визуализация файлового дерева
    • 2.1.2. thead - печать начала каждого файла
    • 2.1.3. tgrep - поиск строк в дереве файловой системы
    • 2.1.4. paths - нахождение пути доступа к исполняемым файлам, со специальными опциями
  • 2.2. Вывод информации
    • 2.2.1. lc - вывод файловой информации на экран по столбцам
    • 2.2.2. ll - вывод файловой информации в длинном формате
    • 2.2.3. kind - вывод однотипных файлов
    • 2.2.4. m - простой доступ к команде more
    • 2.2.5. mmm - обработка программой nroff макрокоманд для рукописей
    • 2.2.6. pall - печать всех файлов в дереве

ВВЕДЕНИЕ

В главе 1 был представлен обзор общей структуры системы UNIX и показано, как взаимодействуют ее различные части. Это похоже на введение в географию, когда на глобусе показывают континенты и крупные водные пространства. Такая информация, хотя и является хорошим фундаментом для общих знаний, вряд ли поможет найти наилучший путь из Сан-Франциско в Лос-Анжелес. Необходим следующий уровень детализации: названия поселений, дорог, развилок, улиц, адресов.

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

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

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

КОМБИНИРОВАНИЕ ПРОДУКТИВНЫХ ИДЕЙ

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

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

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

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

ПОИСК ФАЙЛОВ

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

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

Другое инструментальное средство - это thead. Thead печатает несколько первых строк текстовых файлов, которые находятся в данном сегменте файлового дерева. Просматривая заголовок, т.е. первые несколько строк файла, вы можете получить достаточно информации, чтобы идентифицировать содержимое файла. При вызове thead вы можете явно задать каталог либо передать команде thead по конвейеру список полных имен файлов. Это делает команду thead фильтром - особым видом команд системы UNIX, который мы обсудим позже.

Следующее инструментальное средство - tgrep. Как следует из названия, это еще одна команда, связанная с файловым деревом, которая использует утилиту grep. Tgrep ищет символьные строки в каждом файле, который находится в данном сегменте файлового дерева. Tgrep также является фильтром, так что имена файлов можно передавать ей по конвейеру.

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

РАСПЕЧАТКА ФАЙЛОВОЙ ИНФОРМАЦИИ

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

Первые два командных файла являются пре- и постпроцессорами для команды ls. Команда lc выводит файловую информацию по столбцам, команда ll перечисляет файлы в длинном формате. Эти командные файлы дополнены опциями команды ls, чтобы сделать распечатки более информативными. Так как команда ls используется довольно часто, упаковка наиболее часто применяемых нажатий клавиш в командные файлы представляется целесообразной. Упаковка уменьшает количество постоянно набираемых символов и упрощает использование команд, исключает необходимость запоминания подробного синтаксиса.

Третье инструментальное средство - это kind. Kind - еще один командный файл препроцессорного типа, использующий команду UNIX file. Команда file читает указанный файл и затем сообщает, является ли этот файл текстовым, архивным или исполняемым. Поскольку распечатки команды file не выбирают файлы заданного типа, возникает необходимость в создании для этого специальной утилиты. Команда kind работает с распечаткой команды file. Kind выводит на экран имена файлов только заданного типа.

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

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

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

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

2.1. ПОИСК ФАЙЛОВ

2.1.1. tree - визуализация файлового дерева

    ИМЯ:  TREE

tree - вывод на экран структуры файлового дерева

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
    tree [dir]
ПРИМЕР ВЫЗОВА
    $ tree $HOME

Выводит структуру файлового дерева регистрационного каталога.

ТЕКСТ ПРОГРАММЫ
   1  :
   2  # @(#) tree v1.0  Visual display of a file tree Author: Russ Sage
   2а                   вывод на экран структуры файлового дерева

   4  if [ "$#" -gt 1 ]
   5    then echo "tree: wrong arg count">&2
   6         echo "usage: tree [dir]"    >&2
   7         exit 2
   8  fi
   9  if [ "$#" -eq 1 ]
   10 then if [ ! -d $1 ]
   11   then echo "$0: $1 not a directory">&2
   12        echo "usage: tree [dir]"     >&2
   13        exit 2
   14         fi
   15 fi

   17 find ${1:-.} -print | sort | sed -e "1p" -e "1d"          \
   18                                 -e "s|[^/]*/|      /|g"  \
   19                                 -e "s|[^ */|/|"          \
   20                                 -e "s|/\([^/]*\)$|\1|"
ОПИСАНИЕ

Зачем нам нужен командный файл tree?

Как мы уже отмечали, вся система UNIX строится вокруг файловой системы, которая похожа на дерево. Дерево, с которым мы работаем в системе UNIX, растет вверх ногами: корень находится вверху, а ветви и листва растут вниз от корня. Физическая структура реальных деревьев и файловых деревьев, используемых в системе UNIX, очень сходна: один корень (начальная точка) и один ствол. Как глубоко и как далеко могут уходить ветви от основного ствола - не ограничивается ничем, кроме ограничений физического пространства. Аналогично, число листьев, которые может иметь каждая ветвь, фактически не ограничено.

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

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

Что делает tree?

Команда tree - это постпроцессор для команды UNIX find. Find просматривает сегмент файлового дерева и полные имена всех файлов, которые соответствуют заданному критерию. Команда tree использует утилиту sed системы UNIX, чтобы перевести выход команды find в наглядную форму.

Входным параметром для команды tree является имя каталога, которое может быть указано в любом абсолютном виде, например, /usr/spool/uucp, или в относительном, например, ../../bin. Если никакого имени не указано, подразумевается ., что является текущим каталогом.

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

Рассмотрим пример структуры каталога. Пусть корневым каталогом будет /tmp с двумя каталогами: a и b. В каталоге a находится подкаталог aa, который содержит файл file1, а в каталоге b , соответственно, подкаталог bb, содержащий файл file2. Команда find выдаст распечатку такого вида:

   # find /tmp -print
   /tmp
   /tmp/a
   /tmp/a/aa
   /tmp/a/aa/file1
   /tmp/b
   /tmp/b/bb
   /tmp/b/bb/file2

Как видно из этого листинга, файлы a и aa есть каталоги, а файл file1 находится внизу файлового дерева. Сравните этот результат с результатом, который выдает команда tree, используя утилиту sed.

   # tree /tmp
   /tmp
   /     a
   /     /     aa
   /    /      /      file1
   /    b
   /    /     bb
   /    /      /      file2

Корневым каталогом в этом листинге является каталог /tmp. Там, где дерево переходит на более глубокий уровень, печатаются только символы косой черты. Первый уровень - /tmp, под этим уровнем находятся файлы-каталоги a и b, затем, соответственно, их подкаталоги aa и bb. Исходя из этого листинга, мы делаем вывод, что на первом уровне каталога находятся два файла (и эти файлы в действительности являются каталогами) и что два файла находятся в подчиненных каталогах. Отметим, что мы смогли идентифицировать aa и bb как каталоги только потому, что в них присутствуют файлы file1 и file2.

Сравните этот листинг с выходом "необработанной" команды find. Выход команды tree исключает отвлекающее внимание повторение элементов путей доступа при каждом переходе к более низкому уровню. Благодаря этому, сразу же видно СУЩЕСТВЕННУЮ информацию. Вот что мы имеем в виду, когда говорим о создании более наглядного для человека интерфейса с системой UNIX.

ПРИМЕРЫ
    1. $ tree

Использует подразумеваемый каталог (текущий каталог, что равносильно команде "$ tree .") в качестве начала файлового дерева.

    2. $ tree /

Печатает древовидный листинг для КАЖДОГО файла всей системы. Команда find при таком ее запуске начинает с корневого каталога и выдает информацию о всех файлах системы.

    3. $ tree $HOME/..

Показывает древовидный формат для всех других пользователей системы (предполагается, что все пользовательские каталоги находятся в одном и том же каталоге, например /usr/*).

ПОЯСНЕНИЯ

Первая строка содержит только знак двоеточия (:) - "нулевую команду". Это связано с тем, что все командные файлы, описываемые в этой книге, сделаны так, чтобы их можно было запускать в среде интерпретатора Bourne shell. Наш комментарий в строке 2, идентифицирующий версию, начинается со знака решетки (#). Си-shell ищет этот знак как первый знак командного файла. Если он найден, то предпринимается попытка выполнить данный командный файл. В противном случае Си-shell передает командный файл интерпретатору Bourne shell. Вот почему мы не хотим начинать первую строку со знака #. Мы, конечно, могли бы оставить первую строку чистой, но чистая строка невидима и может быть случайно удалена. Соответственно мы будем использовать чистые строки в других случаях, чтобы выделить важные участки программы.

Строка 2 идентифицирует версию. Символьная строка @(#) есть специальная последовательность в строке комментария, которая распознается как строка "what" ("что"). Команда what в системе UNIX читает файл и печатает сообщение, которое следует за строкой "what". Чтобы идентифицировать версию данного командного файла, наберите

    # what tree
и  будет напечатано следующее сообщение:
    tree:
    tree v1.0  Visual display of a file tree Author: Russ Sage

Строки 4-7 проверяют, не слишком ли много аргументов было передано командной строке. Это осуществляется путем исследования переменной $#, которая представляет собой счетчик числа позиционных параметров командной строки. Если насчитывается более одного параметра, печатается соответствующее сообщение об ошибке в стандартный файл ошибок (stderr) и программа останавливается с плохим значением статуса.

Отметим, что команда echo обычно печатает в стандартный выход (stdout). Мы можем перенаправить stdout в другой файловый дескриптор, указав его. В данном случае мы собираемся печатать в stderr. Синтаксис переводится так: "вывести эту строку и перенаправить ее в файловый дескриптор (&) стандартного файла ошибок (2)". Печать сообщений об ошибках в stderr обеспечивает согласованное поведение командного файла независимо от среды, в которой он запущен.

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

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

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

Мы подошли к сердцу команды tree - это строки 17-20. Главным здесь является команда find системы UNIX. Каталог, в котором ведется поиск, определяется при запуске команды. Синтаксис ${1:-.} является формой параметрической подстановки и означает следующее: если $1 (что является первым позиционным параметром) установлен (иными словами, если аргумент был передан командной строке и был ненулевым), то нужно использовать это значение. В противном случае следует использовать каталог . (текущий каталог). Этот тип подстановки дает нам возможность запускать команду tree без указания имени каталога (когда после tree на командной строке ничего не следует),- то есть работать в режиме "по умолчанию", что часто используется в различных файловых инструментах.

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

Далее, отсортированные полные имена файлов передаются по программному каналу команде sed системы Unix. Sed - это "потоковый редактор", очень гибкое средство, которое может быть использовано для идентификации и обработки различных образцов текста. Опции -e являются операциями редактирования, применяемыми к поступающим данным. Первый оператор просто сообщает команде sed, что нужно напечатать первую строку, затем удалить строку 1. Это делается для того, чтобы напечатать название корневого каталога, который исследуется. Этой строке не требуется никакой дальнейшей модификации, так как корневой каталог не имеет никаких дополнительных элементов путей доступа, которые нужно было бы трансформировать в символы косой черты для показа отступов. Удаление первой строки связано с тем, что она не нужна в дальнейшей работе.

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

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

Последняя операция редактирования (в строке 20) заменяет символ косой черты и все отличные от него символы (до конца строки) просто на символы, отличные от /. Отметим, что это устраняет самый правый символ /, который присутствует в листинге команды find. В результате остается имя подчиненного файла, сдвинутое вправо.

Отметим синтаксис \1 команды sed - признак, относящийся к первому (в данном случае единственному) регулярному выражению в скобках, которое ему предшествует. В данном случае команде sed указано пройти символы, соответствующие регулярному выражению - символы, отличные от /.

2.1.2. thead - печать начала каждого файла

    ИМЯ: thead

thеаd Печатает заголовок (первые несколько строк) файлов.

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
    thead [dir...]
ПРИМЕР ВЫЗОВА
$ find $HOME/src -name "*.c" -print | sort | thead

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

ТЕКСТ ПРОГРАММЫ
   1 :
   2 #  @(#)  thead v1.0 Prints head of files in tree Author: Russ Sage
   2а                   Печатает заголовки файлов в дереве

   4 if [ "`echo $1|cut -c1`" = "-" ]
   5  then  echo "$0: arg error"
   6        echo "usage: $0 [dir ...]"
   7        exit 1
   8   fi

   10 case $# in
   11 0)  while read FILE
   12     do
   13         if file $FILE | fgrep text >/dev/null 2>&1
   14           then  echo "\n:::::::::::::::::::::"
   15                 echo " $FILE"
   16                 echo "\n:::::::::::::::::::::"
   17                 head -15 $FILE
   18         fi
   19       done;;
   20  *)  for NAME in $*
   21      do
   22             find $NAME -type f -print | sort | wile read FILE
   23             do
   24                    if file $FILE | fgrep text >/dev/null 2>&1
   25                      then  echo "\n:::::::::::::::::::::"
   26                            echo " $FILE"
   27                            echo "\n:::::::::::::::::::::"
   28                            head -15 $FILE
   29                    fi
   30             done
   31      done;;
   32  esac
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
FILE | Содержит имя каждого файла
NAME | Имя каталога, заданное в командной строке
ОПИСАНИЕ

Зачем нужен командный файл thead?

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

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

Что делает thead?

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

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

    $ thead $HOME

Если мы хотим просмотреть только исходные файлы на языке Си (*.c), то представленный выше синтаксис не годится для этого. Он не обладает достаточной гибкостью. Нам необходимо иметь способ указать (или квалифицировать) файлы в $HOME перед тем, как просматривать их. Так как команда thead может воспринимать полные имена файлов, мы можем использовать следующую команду:

    $ find $HOME -name "*.c" -print | sort | thead

Команда find генерирует список файлов с расширением C, который сортируется и подается по каналу на вход команде thead. Как видно из представленных двух примеров, весьма полезной для командного файла является возможность получать входные данные либо из аргументов командной строки (как в первом примере), либо по программному каналу (как во втором примере). Способность использовать программный канал позволяет вам применять какие-либо другие команды системы UNIX, которые могут отбирать входные данные для вашего командного файла. Команда с такой двойной возможностью называется ФИЛЬТРОМ. Среди стандартных команд системы UNIX вы найдете лишь несколько команд-фильтров, таких как wc, awk, sort.

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

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

Для каждого файла, с которым работает thead, выполняется контроль - текстовый ли это файл. Применение команды head к исполняемым модулям приводит к выводу "таинственных" символов на экран и иногда может вызвать дамп оперативной памяти.

ПРИМЕРЫ
    1. $ thead /etc

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

    2. $ thead /usr/include

Просматривает все подключаемые файлы (*.h), даже в системном подкаталоге sys.

    3. $ find $HOME -ctime 0 -print | thead

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

ПОЯСНЕНИЯ

Строки 4-8 выполняют проверку ошибок. Так как команда thead не имеет никаких опций, любые позиционные параметры, которые начинаются с дефиса (-) являются неверными. Если первым символом первого позиционного параметра оказывается "-", то печатается сообщение "argument error" (ошибка аргумента) вместе с сообщением о способе запуска и команда thead прекращает работу.

Некоторые приемы программирования для интерпретатора shell, используемые в этих строках, довольно часто встречаются в данной книге, поэтому имеет смысл остановиться на них подробнее. Проанализируем строку 4, работающую изнутри наружу. Команда echo выдает содержимое $1 (текущий параметр командной строки), которое передается по программному каналу команде cut. Команда cut используется для того, чтобы выделить определенные символы или группы символов из строки. В данном случае опция -c1 используется для получения только первого символа.

КОМАНДА cut ДЛЯ BSD

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

    for NAME in 'who | sed "s/^\([^ ]*\).*/\1/"'
    do
    done

Для каждой обнаруженной строки (аргумента) команда sed должна подставить вторую строку символов вместо первой строки. Первая строка - это строка, которая вырезается. Мы ищем от начала строки (^) символ, отличный от пробела ([^ ]), за которым следует любое число непустых символов (*). Эта операция прерывается по достижении пробела. Набор непустых символов ограничивается обратными косыми чертами \( и \). Впоследствии ссылка на этот набор дается в виде \1. Символы .* означают, что после того, как найден пробел, необходимо считать подходящими все символы до конца строки. Мы находимся фактически сразу после того, что заключено в пару символов \( и \). Группируя первый набор символов, отличных от пробела, мы получаем то, что является результатом работы команды "cut -f1".

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

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

Остальная часть командного файла представляет собой один огромный оператор выбора (case). Аргументом, используемым для ветвления, является число позиционных параметров в командной строке. Если позиционных параметров нет, то в строках 11-19 активируется цикл while. Заметим, что цикл while выполняет оператор чтения, но не указывает, откуда дол- жен быть взят его вход. Это связано с тем, что входом по умолчанию является стандартный ввод (stdin). Для каждого имени файла, которое читается из стандартного ввода, запускается команда file системы UNIX. Выход команды file передается по программному каналу команде fgrep (а не grep, что увеличивает скорость), чтобы посмотреть, является ли файл текстовым.

Фактический выход команды fgrep перенаправляется на нулевое устройство (в бесконечную область памяти), поскольку он нам не нужен. Нас интересует лишь код возврата после выполнения всего конвейера. Если команды file и fgrep отработали успешно, кодом возврата является ноль. Это истинное значение, поэтому выполняется участок цикла после then (строки 14-17). Если файл не существует или не является текстовым, то код возврата ненулевой, и условный оператор завершается. Это приводит нас в конец цикла, выполняется следующая итерация цикла while и мы рассматриваем следующий аргумент из стандартного ввода.

Теперь рассмотрим обработку, выполняемую по then (строки 14-17). Для каждого файла, который является текстовым, печатается строка двоеточий (:) до и после имени файла, а команда head системы UNIX печатает первые 15 строк. Такой сценарий продолжается, пока не закончатся данные в стандартном вводе.

Рассмотрим другую альтернативу, покрываемую данным оператором выбора. Она обрабатывает ситуацию, когда имеется несколько позиционных параметров (что указано символом * в операторе case). Цикл for пробегает все параметры (строка 20). Звездочка (*) в операторе case означает, что подходит любое значение, которое не подошло ранее. Это улавливающая (catchall) опция. Цикл for использует аргумент $* в качестве своего входа. Он представляет значения всех позиционных параметров, что является фактически всей командной строкой, исключая имя утилиты. Команда find используется для поиска всех нормальных файлов в каталоге. "Нормальные" файлы не означает "только текстовые файлы", поэтому мы проверим это позже. Выход команды find передается по каналу команде sort, чтобы сделать его более наглядным. Отсортированный список передается по каналу в цикл while, который помещает имя файла в переменную FILE (строка 27). Проверяется, текстовый ли файл, затем он печатается командой head.

Если мы сравним строки 13-18 и строки 24-29, то мы увидим, что это один и тот же код. В большинстве языков программирования это означало бы, что мы должны оформить эти строки как процедуру и вызывать ее, когда нужно. Язык программирования интерпретатора shell, хотя и довольно мощный, не имеет хорошего способа реализации процедур. Последний интерпретатор shell в System V имеет функции, которые позволяют решить эти проблемы. Отметим, что внутренний цикл while повторяется на каждом файле, который существует в определенном каталоге, а внешний цикл for проходит от каталога к каталогу.

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

Для увеличения гибкости хорошо бы добавить опции, чтобы вы могли переходить на команду find непосредственно из thead. Полезными аргументами были бы -name для изолирования образцов имен файлов и -ctime для обработки изменений, связанных со временем.

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

ВОЗМОЖНЫЕ ИССЛЕДОВАНИЯ

В чем отличие между двумя следующими операторами?

    $ find $HOME -name "*.c" -print | thead
    и
    $ find $HOME -name "*.c" -exec head {} \;

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

2.1.3. tgrep - поиск строк в дереве файловой системы

    ИМЯ: tgrep

tgrep Поиск строки по шаблону в дереве файлов

НАЗНАЧЕНИЕ

Обходит файловое дерево и ищет в каждом файле указанную строку. Если не указан никакой каталог, tgrep действует как фильтр.

ФОРМАТ ВЫЗОВА
    tgrep [-c|-h] string [file ...]
ПРИМЕР ВЫЗОВА
    # tgrep "profanity" /

Поиск слова "profanity" по всей системе (суперпользователь снова на тропе войны!)

ТЕКСТ ПРОГРАММЫ
   1   :
   2   # @(#) tgrep v1.0 Search for string in tree Author: Russ Sage
   2а                    Поиск строки в дереве

   4   OPT=""

   6   for ARG in $@
   7   do
   8           if [ "`echo $ARG|cut -c1`" = "-" ]
   9             then case $ARG in
   10                 -c) OPT="-name \"*.c\""
   11                     shift;;
   12                 -h) OPT="-name \"*.h\""
   13                     shift;;
   14                 *)  echo "$O: incorrect argument"             >&2
   15                     echo "usage: $O [-c|-h] string [file ...] >&2
   16                     exit 1;;
   17                 esac
   18           fi
   19  done

   21  case $# in
   22  0)  echo "$O: argument error"                 >&2
   23      echo "usage: $O [-c|-h] string [dir ...]" >&2
   24      exit 2
   25      ;;
   26  1)  while read FILE
   27      do
   28              grep -y "$1" $FILE /dev/nul
   29      done
   30      ;;
   31  *)  STRING=$1; shift
   32      eval find "$@" -type f $OPT -print | sort | while read FILE
   33      do
   34              grep -y "$STRING" $FILE /dev/null
   35      done
   36      ;;
   37  esac
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
FILE | Содержит имя каждого файла
OPT | Содержит специальные опции команды find
STRING | Временная переменная, в которой содержится строка поиска
ОПИСАНИЕ

Зачем нам нужен командный файл tgrep?

Как мы могли видеть на примере двух предыдущих утилит, рекурсивный просмотр файлов очень полезен. Он сохраняет время, поскольку позволяет избежать поиска файлов вручную, а также создает средства, которые могут быть использованы в более мощных утилитах. Чем больше имеется созданных нами средств, тем больше новых средств мы можем построить с их помощью. Единственная проблема заключается в том, что вы должны позаботиться об их взаимозависимости (каким утилитам или средствам требуются другие утилиты или средства и кто на кого влияет). Еще одна область, где UNIX не имеет "родной" рекурсивной команды - это обработка строк. Семейство команд типа grep очень велико, но все они работают только по одному фиксированному маршрутному имени файла. Нам необходим препроцессор для команды grep. Правда, мы можем дать запрос на все файлы во всей системе или какой-либо ее части по нашему выбору. Если мы попытаемся сделать это вручную, то это означает, что мы должны много раз нажимать на клавиши, что может привести к синтаксической ошибке. Вы также должны точно помнить, как вы создавали командную строку, если вы в следующий раз захотите выполнить такую же задачу. Зачем выполнять грязную работу? Для этого существует компьютер.

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

Что делает tgrep?

Основным предназначением tgrep является обеспечение большей гибкости и легкости использования возможностей команды grep. Ее синтаксис точно такой же, как и у grep, за исключением допустимых типов файлов. В команде grep UNIX в качестве аргумента может указываться практически любой файл, но указание текстового файла имеет наибольший смысл. В команде tgrep также могут использоваться текстовые файлы, но наибольший смысл имеет указание каталогов, поскольку мы ищем имена файлов. Команда find работала бы не очень хорошо, если бы пыталась извлечь множество имен файлов из текстового файла. В командной строке может указываться множество имен каталогов, поскольку все они используются как начальное место поиска оператора find.

По умолчанию tgrep находит все обычные файлы. В этой программе нет никакой проверки на то, текстовый файл или нет, поскольку мы не пытаемся напечатать все на экран. Поэтому мы ищем строки символов во всех файлах, начиная от архивных и заканчивая исполняемыми. Если вы хотите выбрать типы файлов, используйте две опции, -c и -h. Опция -c заставляет команду UNIX find искать только файлы с именами вида *.c. Аналогично, опция -h соответствует файлам *.h. Эти опции могут быть полезны для управления программами на языке Си, с которыми мы более детально познакомимся в главе 4. Эти опции не самое важное, но они показывают, как легко добавить новые опции в программу. Если при вызове была указана какая-то иная опция, кроме упомянутых, выводится сообщение об ошибке и программа останавливается. Подобно thead, tgrep является фильтром.

ПРИМЕРЫ
    1.  $ tgrep unix $HOME

Поиск любого вхождения слова unix во всех файлах моего регистрационного каталога.

    2.  $ tgrep -c "^sleep()$" $HOME/src

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

    3.  # find /usr/src -name "*.c" -print | tgrep "ioctl"

Поиск всех вызовов ioctl в исходных Си-файлах, начиная с каталога /usr/src. (Обратите внимание, что я являюсь суперпользователем. Это видно из того, что я занимаюсь поиском в ограниченной части системы, а именно в исходных дистрибутивах, а также из того, что в качестве символа приглашения используется символ "#".)

    4.  $ tgrep "| more" `find . -type f -print`

Поиск символа вертикальной черты (|), после которого следует слово more, в списке имен файлов, генерируемом оператором find. Find печатает имена всех файлов текущего каталога и всех подкаталогов, которые являются обычными файлами.

    5.  $ tgrep trap /bin /usr/bin /etc

Поиск команды прерывания (trap) во всех командных файлах интерпретатора shell, которые имеются в трех каталогах.

ПОЯСНЕНИЯ

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

Строки 6-18 выполняют проверку на наличие ошибок. Проверяется, является ли первым символом каждого позиционного параметра символ "-". Если проверка успешна, то проверяется на корректность сам аргумент. Возможными опциями являются -c и -h. Если указано что-либо другое, выводится сообщение об ошибке и программа завершается. Обратите внимание, что с помощью команды shift допустимые опции удаляются из командной строки. Это сделано для того, чтобы впоследствии выражение $@ могло быть использовано для получения только аргументов строки поиска и маршрута. Выражение $@ является другой формой выражения $ *, в которой оно распространяется на все позиционные параметры. Однако в последующем тексте мы увидим одно большое отличие между ними.

Еще один трюк сделан при присвоении значения переменной OPT в строке 10. Этой переменной нам необходимо присвоить значение, которое включает в себя пару кавычек, поскольку кавычки должны быть частью строки поиска, которая в конце концов используется оператором find. Однако обнаружение второй кавычки обычно ЗАВЕРШАЕТ оператор присваивания, а мы этого в данном случае не хотим. Выходом из ситуации является использование символа \, который отменяет специальное значение следующего за ним символа. В данном случае специальное значение было бы концом строки присваивания.

Таким образом, кавычка перед звездочкой (*) в строке 10 рассматривается как часть значения переменной OPT, вместо того, чтобы завершать операцию присваивания. Следующая встреченная кавычка также должна быть сохранена в значении переменной, чтобы указать конец хранимой здесь строки поиска, поэтому она также экранирована символом \. Последняя кавычка в этой строке не экранирована обратной косой чертой. Эта кавычка соответствует своей обычной функции в смысле интерпретатора shell и завершает оператор присваивания.

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

Если указан только один аргумент, то это должна быть строка поиска. Это также означает, что в командной строке не указаны имена файлов и поэтому мы читаем их со стандартного устройства ввода, т.е. командный файл действует как фильтр. Цикл while (строки 26-29) читает со стандартного ввода имя каждого файла для поиска в нем командой grep нужной строки. Опция -y команды grep означает нечувствительность к регистру символов при поиске. Использование этой опции дает нам хорошие шансы попасть на нужную строку.

Другой интересной особенностью этого цикла являются две команды grep в строках 28 и 34. Мы ищем нужную строку не только в файле, но также в каталоге /dev/null. Это кажется странным? Да. Проблема заключается в самой команде grep. Grep знает, когда в командной строке указано более одного файла. Если имеется более одного файла, то имя файла выводится на экран до вывода найденной строки. Если же в командной строке указан только один файл, то его имя не выводится, поскольку считается, что пользователь должен помнить это имя. Это создает проблемы при написании командных файлов, когда вы имеете много имен файлов, но они обрабатываются по одному. Мы обрабатываем файлы по одному, поскольку если бы мы использовали указание группового имени файлов с помощью метасимволов, то могло бы оказаться слишком много имен файлов в одной командной строке для команды grep. Поэтому вместо использования некоторой замысловатой схемы для сокращения количества аргументов, мы избегаем этого путем обработки в цикле каждого файла по очереди.

Поскольку на самом деле мы ищем большое количество разных строк, то мы хотим видеть имена файлов, в которых они были найдены. При указании в командной строке grep каталога /dev/null, grep всегда печатает имя файла до вывода найденной строки в нашем цикле, поскольку теперь имеется два имени файла в качестве аргументов. Конечно, в /dev/null ничего нельзя найти, поскольку он пуст по определению. Последний образец в операторе case (строки 31-36) - это ловушка. Он соответствует любому количеству позиционных параметров сверх одного, что позволяет нам иметь переменное число каталогов в командной строке.

Даже хотя мы можем указать несколько каталогов в командной строке, первым аргументом по-прежнему является строка поиска. Не так легко сказать: "начиная со второго параметра", поэтому мы сохраняем строку поиска (в переменной STRING - Прим. перев.) и убираем ее командой shift. Теперь мы можем получить доступ к остальной части командной строки с помощью выражения $@.

Давайте посмотрим, что происходит, когда мы ссылаемся на переменную $OPT для получения опций команды find. Допустим, мы вызвали tgrep с опцией -c. Когда мы присваиваем значение переменной OPT, мы ставим строку c в виде "*.c" внутри двойных кавычек, поскольку мы не желаем, чтобы shell раскрывал эту строку (т.е. трактовал ее как имена всех файлов, соответствующих данному образцу) именно сейчас. Теперь, как только к переменной $OPT есть обращение в команде find, значением OPT является *.c, что означает поиск файлов, символьные имена которых соответствуют *.c. Для отмены символьной интерпретации мы должны использовать команду eval. Перед выполнением команды find команда eval заставляет shell повторно анализировать команду в отношении распространения значения переменной. На этом проходе выражение "*.c" превращается в *.c, что разрешает генерацию имен файлов таким образом, чтобы были просмотрены все эти файлы.

Указывая $@ в команде find, мы можем производить поиск во всех каталогах сразу. Таким образом, нам нужно вызвать find только один раз, что позволяет сберечь время центрального процессора. Одной из интересных проблем, возникших при разработке данного командного файла было то, что выражение вида $* не работало. В команде find возникала ошибка сохранения. Даже запуск shell'а в режиме -x (установленный флаг разрешения выполнения) не разрешил проблему. Изменение синтаксиса, кажется, помогло разобраться с ней. Оказывается, причина в том, что выражение $* раскрывается в "$1 $2 ...", в то время как выражение $@ превращается в "$1" "$2" (т.е. в отдельные аргументы). Происходило то, что выражение $* передавало имена нескольких каталогов команде find как одну строку. Команда find не могла обнаружить файл такого типа, поэтому прекращала выполнение. Когда же вместо этого было использовано выражение $@, команда find получила несколько независимых строк с именами. Это вполне подошло команде find, и все заработало. Такие мелкие детали всегда требуют много времени для изучения!

ВОЗМОЖНЫЕ ИССЛЕДОВАНИЯ

В чем разница между двумя следующими операторами?

   grep "$1" `find "$2" -print`
   и
   find "$2" -print | while read F
   do
      grep "$1" $F
   done

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

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


ГЛАВА 2. Доступ к файлам

2.1.4. paths - нахождение пути доступа к исполняемым файлам, со специальными опциями

    ИМЯ:  paths

paths Определитель маршрутных имен файлов со специальными опциями

НАЗНАЧЕНИЕ

Выводит на экран каталог, в котором располагается файл, выдает имя файла в длинном формате или ищет файлы с установленным битом пользовательского идентификатора (setuid bit files) в каталогах по указанному маршруту.

ФОРМАТ ВЫЗОВА
    paths [-l] [-s] file [file ...]
ПРИМЕР ВЫЗОВА
    $ paths -l ed ex vi

Выдает в длинном формате имена файлов, которые являются исполняемыми модулями редакторов ed, ex и vi

ТЕКСТ ПРОГРАММЫ
   1   :
   2   # @(#) paths v1.0 Path locator with special options Author: Russ Sage
   2а    Определитель местонахождения файлов со специальными опциями

   4   FORMAT="path"

   6   for ARG in $@
   7   do
   8           if [ '`echo $ARG | cut -c1`" = "-" ]
   9             then case $ARG in
   10                 -l) FORMAT="ls"
   11                     shift;;
   12                 -s) FORMAT="set"
   13                     set "1";;
   14                 *)  echo $0: arg error"                        >&2
   15                     echo "usage: $0 [-l] [-s] file [file ...]" >&2
   16                     exit 1;;
   17                 esac
   18          fi
   19  done

   21  IFS="${IFS}:"

   23  for FILE in $@
   24  do
   25           for DIR in $PATH
   26           do
   27                   case $FORMAT in
   28                   path) if [ -f $DIR/$FILE ]
   29                            then echo $DIR/$FILE
   30                         fi;;
   31                   ls)   if [ -f $DIR/$FILE ]
   32                           then ls -l $DIR/$FILE
   33                         fi;;
   34                   set)  echo "\n:::::::::::::::::::"
   35                         echo "$DIR"
   36                         echo "::::::::::::::::::::"
   37                         ls -al $DIR | grep "^[^ ]*s[^ ]*";;
   38                   esac
   39            done
   40  done
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ARG | Содержит каждый аргумент командной строки
DIR | Элемент с именем каталога в переменной PATH
FILE | Содержит имя каждого файла в командной строке
FORMAT | Тип требуемого формата выходных данных
IFS | Переменная shell'а, разделитель полей
PATH | Переменная shell'а, пути к каталогам исполняемых модулей
ОПИСАНИЕ

ЗАЧЕМ НАМ НУЖЕН КОМАНДНЫЙ ФАЙЛ paths?

В нашей среде интерпретатора shell переменная с именем PATH содержит имена каталогов, отделенные друг от друга символами двоеточия (:). Каждый раз, когда вы вводите команду после приглашения shell'а, интерпретатор shell, начиная с первого каталога, указанного в переменной PATH, смотрит, находится ли введенная вами команда в этом каталоге. Если да, то команда выполняется. Если нет, то shell идет в следующий каталог, указываемый переменной PATH, и так далее, пока не будут проверены все каталоги. Если команда все же не будет найдена, то вы получите следующее сообщение об ошибке:

   |
   |    $ whatchamacallit
   |    sh: whatchamacallit: not found
   |
   |

Такой поиск команды осуществляется автоматически, но сама система не сообщает вам, ГДЕ размещена команда. Нам необходима утилита, которая ищет и выводит на экран маршрут к указанному файлу. Имея такую утилиту, вы можете использовать кратчайшие пути получения того, что вам нужно, и выполнять множество различных трюков. Вы можете комбинировать подобную команду с другими командами для создания гораздо более мощных средств. С маршрутом можно также комбинировать команды more, ls, file и cd системы UNIX. Возможно, вы обнаружите и другие команды по мере экспериментирования.

Команда, несколько похожая на ту, которую мы ищем, существует где-то в мире системы UNIX Systev V. Например, в системе AT&T это команда where. В системе UNIX Berkeley это команда which (текст на языке Си-shell'а) или whereis (исполняемая программа). Whereis дает дополнительную информацию, такую как место размещения файлов с исходными текстами (в каталоге /usr/src). Увидев, как мы создаем нашу собственную команду поиска маршрута, вы можете модифицировать ее для обеспечения работы с особенностями некоторых других команд и приспособить такие вещи к вашим нуждам.

Прежде чем мы удовлетворим свои прихоти, давайте бегло глянем на команду path, более простую, чем paths. Вся программа выглядит примерно так:

   IFS="${IFS}:"
   for FILE in $@
   do
      for DIR in $PATH
      do
          if [ -f $DIR/$FILE ]
            then  echo $DIR/$FILE
          fi
      done
   done

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

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

Если ваша переменная PATH выглядит так:

    PATH=.:/bin:/usr/bin:/etc/:$HOME/bin

то внутренний цикл выполнит пять итераций в таком порядке: ., /bin, /usr/bin, /etc и, наконец, $HOME/bin. Если бы запрос имел вид "path ll" для поиска утилиты, которую мы создадим позже в этой главе, то результат мог бы выглядеть так:

   |
   |    /usr/bin/ll
   |    /usr/russ/bin/ll
   |
   |

Это значит, что команда ll была найдена в двух местах из вашего набора маршрутов поиска.

ЧТО ДЕЛАЕТ paths?

Теперь, когда мы знаем, как работает более простая команда path, мы можем по достоинству оценить дополнительные возможности специальной команды получения маршрута - команды paths. Paths имеет три основные функции. Она может выполняться как основная команда path, которую мы уже рассмотрели, и давать полное маршрутное имя исполняемого модуля. Она может выдавать маршрут файла в длинном формате. Она также может выдать список всех файлов с установленным пользовательским идентификатором (setuid bit files), которые есть в ваших маршрутных каталогах. (См. главу 8, где описаны биты setuid.)

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

    paths [-l] file [file ...]

как, например, в командах "paths ls who date" или "paths -l ll". Для поиска файлов с установленным пользовательским идентификатором (setuid files) не нужно указывать имена файлов в командной строке. Вся команда должна быть такой:

    paths -s

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

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

ПРИМЕРЫ
   1.  $ paths ls more who paths
       /bin/ls
       /usr/bin/more
       /bin/who
       /usr/russ/bin/paths

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

    2.  $ more `paths gettydefs termcap paths`

Если ваша переменная PATH содержит каталог /etc, то этот пример будет работать. Если нет, то первые два файла не будут найдены. Сначала запускается команда paths, и ее вывод помещается на свое место в командной строке команды more. Когда запускается команда more, она не знает, что ее аргументы получены от другой команды. После завершения работы команды paths команда more принимает вид:

    more /etc/gettydefs /etc/termcap/usr/russ/bin/paths

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

    3.  $ ll `paths ll`

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

    4.  $ m `paths paths`

В данном примере генерируется маршрутное имя самого командного файла paths и передается программе m, которая использует команду more для распечатки. (Командный файл m мы также покажем вам позже.)

ПОЯСНЕНИЯ

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

В строках 6-19 все аргументы командной строки проверяются на корректность. Критерием того, что аргумент есть опция, является дефис в роли первого символа. Заметим, что здесь не разрешено использование синтаксиса "-xyz". Это заставляет вас пользоваться синтаксисом "-x -y -z". Хотя этот момент может показаться несущественным, на самом деле он важен. Всегда нужно достигать компромисса между быстрой разработкой командного файла при согласии на недостатки жесткого синтаксиса - и разрешением гибкого формата за счет дополнительных усилий по кодированию и отладке и за счет более медленного выполнения. Ваш выбор зависит от ваших приоритетов, от количества людей, использующих ваше инструментальное средство, и от того, насколько критична скорость выполнения. Конечно, если скорость критична, вы, вероятно, захотите использовать каким-то образом язык Си. Мы оставляем обработку конкатенированных опций в качестве упражнения для читателя.

Цикл for проходит по всем позиционным параметрам. Если первым символом аргумента является "-", то он сверяется со списком допустимых аргументов с помощью оператора case в строках 9-17. Опция "-l" изменяет переменную формата, после чего убирается из рассмотрения. Это делается для освобождения этой позиции, чтобы конечным результатом были просто имена файлов в командной строке.

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

Если использована опция, которая не является ни l, ни s, то этой опции соответствует звездочка (*) и в стандартный файл ошибок выводится сообщение об ошибке. Затем командный файл завершается. Мы бы могли просто проверить первый параметр командной строки, чтобы выяснить, является ли он опцией, и если является, то установить эту опцию. Поскольку можно использовать только одну опцию за один раз, мы могли бы предполагать, что в остальной части командной строки были имена файлов. Тем не менее, этот цикл допускает простое добавление других опций, которые могли бы действовать в дополнение к одной основной. Это более предпочтительно, и оно не влияет на производительность.

В строке 21 мы добавляем символ двоеточия (:) к другим символам разделителя полей. Мы должны именно добавить двоеточие, а не превратить разделитель полей только в двоеточие. Если бы мы сделали последнее, то это запутало бы разбор имен файлов в командной строке. Основной цикл представлен в строках 23-40. Это двойной цикл for. Внешний цикл проходит по каждому файлу в командной строке, а внутренний цикл обрабатывает каждый каталог, указанный в вашей переменной PATH. Обратите внимание, что внешний цикл идет по именам файлов, а не по записям каталогов. Если бы мы выбрали второе, то в распечатке нарушился бы порядок имен файлов, поскольку поиск шел бы сначала по каталогам.

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

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

    ll `path ll`

Находит путь к ll, а затем запускает на нем команду ls -l.

    paths -l ll

Находит путь и вместо того, чтобы его напечатать, выполняет команду ls -l применительно к этому пути.

Формат setuid в строке 34 прощается с подходом "один файл за один раз" и включает каталоговую машину. Поскольку внешний цикл установлен на одну итерацию, внутренний цикл становится главным. Для каждого каталога, указанного в PATH, печатаются оформление из двоеточий и имя каталога. Это делает распечатку приятной, информативной и наглядной. Ключевой командой является комбинация ls-grep. Каждое имя файла в каталоге распечатывается в длинном формате, затем просматривается бит установки пользовательского идентификатора. Модель такова, что команда ls -al $DIR печатает следующее:

   |
   |  -rws--x--x   1 root   bin       16235 Sep 13  1985 /bin/su
   |
   |

Аргумент "^[^ ]*s[^ ]*" означает поиск от начала строки символа, отличного от пробела, за которым следует один или более символов, отличных от пробела, затем символ s и затем один или более символов, отличных от пробела. Это выражение ограничивает поиск битами прав доступа в начале строки. Если имеется символ s где-либо в правах доступа (либо в пользовательском идентификаторе процесса, либо в групповом идентификаторе процесса), то команда grep отрабатывает успешно и печатается вся строка.

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

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

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

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

2.2. ВЫВОД ИНФОРМАЦИИ

2.2.1. lc - вывод файловой информации на экран по столбцам

    ИМЯ:        lc

lc Выдает список файлов в колоночном формате

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
    lc [-m] [ls options] file [file ...]
ПРИМЕР ВЫЗОВА
    lc -R $HOME

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

ТЕКСТ ПРОГРАММЫ
   1   :
   2   # @(#) lc v1.0   List files in a column   Author: Russ Sage
   2а                    Выводит список файлов в колоночном виде

   4   if [ "$1" = "-m" ]
   5     then  MORE="| /usr/bin/more"
   6           shift
   7     else  MORE=""
   8   fi

   10   eval "/bin/ls -a $@ | /bin/pr -5t" $MORE   # pre System V
   11   eval /bin/ls -aCF $@ $MORE                 # System V
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

MORE Содержит программный канал к команде more

ОПИСАНИЕ

Зачем нам нужен командный файл lc?

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

Как видно из предыдущего листинга, lc имеет две формы. Одна предназначена для систем, более ранних, чем System V, а другая - для System V и последующих версий UNIX. Причина в том, что System V версии 2 имеет новую команду ls, которая делает именно то, что мы хотим. Система Berkeley также имеет версию команды ls, которая по умолчанию использует несколько колонок при выводе на терминал. Но для XENIX и ранних версий System V мы должны делать это сами. Дело в том, что хотя в вашей версии UNIX, XENIX или чего-либо еще могут отсутствовать команды, имеющиеся в других версиях, вы обычно можете построить то, что вам нужно. Это может потребовать определенных усилий, и ваши программы могут работать не так быстро и не так эффективно, но вы МОЖЕТЕ получить нужное средство.

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

При работе со старым интерпретатором sh мы должны пройти через обычную процедуру изготовления командного файла и размещения его в каталоге bin. С другой стороны, SCO XENIX System V решает эту проблему, связывая эти же имена (lc, lf, l) с обычной командной ls и используя вызывающее имя для определения формы распечатки.

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

Что делает lc?

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

Главная задача здесь - печать колонок, поэтому мы смотрим на опции команды ls, чтобы задействовать их. Конечно, мы включаем опцию -C. Какие еще опции ls нам нужны? Обычно UNIX не печатает файлы, имена которых начинаются с точек, например, .profile, если только вы не указываете ls -a. Это забывается при просмотре этих важных файлов, поэтому мы конструируем нашу команду так, чтобы она печатала их по умолчанию. Никакие файлы не скрываются от нас. Для пользователей System V и BSD (или для любого, кто имеет опцию -F), листинг улучшается за счет вывода "/" после имени каталога и "*" после исполняемого файла. Ранняя команда ls системы UNIX не имела возможности печатать в таком стиле. Отметим, что данное использование термина "исполняемый" означает показ того, что флаги прав доступа имеют бит "x", а не то, что это файл типа a.out с магическим числом. Это отличие важно тем, что делает наш командный файл более полезным.

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

ПРИМЕРЫ
    1.  $ lc `path lc`

Получает полное имя для lc и распечатывает файловую информацию в виде колонок.

    2.  $ lc -m -R /

Печатает колоночный список ВСЕХ файлов в системе, рекурсивно проходя вниз по иерархии системного дерева и пропуская распечатку через команду more.

Еще один маленький фокус: этот синтаксис был использован для создания другой команды, названной expose. Командная строка "lc -m -R $@" давала бы рекурсивный список всех файлов в любом каталоге по вашему выбору в приятном постраничном формате.

    3.  $ lc -m -R /usr/lib

Рекурсивно распечатывает список всех файлов во всех каталогах, начиная с /usr/lib, и пропускает листинг через команду more.

    4.  $ lc -m . | more

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

ПОЯСНЕНИЯ

В строках 4-8 проверяется, является ли первым аргументом командной строки -m - опция команды more. Если эта опция найдена, то в переменную MORE заносится указание конвейера и команда more. Тем самым устанавливается постобработка, которую следует применить к выходу команды ls. Затем эта опция убирается из командной строки. Это делается для того, чтобы остаток командной строки можно было передать команде ls, не вызвав при этом нежелательных эффектов. Если первой опцией не является -m, переменной MORE присваивается нулевое значение, чтобы она впоследствии не влияла на командную строку.

Строка 10 - это командная строка, которую вы бы использовали на старой UNIX-машине типа Version 7 или System III. Она не имеет ни встроенной опции для печати символов косой черты (/) и звездочек (*), ни возможности печати в виде колонок. Вы должны пожертвовать первой возможностью, а распечатки в виде нескольких колонок можно получить с помощью команды pr системы UNIX. Команда pr использована с опцией "-5t", поэтому она печатает в пять колонок (что обычно приемлемо, но если встречаются длинные имена файлов, то пяти колонок может оказаться слишком много) и не печатает верхний и нижний колонтитулы. Благодаря отказу от колонтитулов, 24-строчный формат не слишком неудобен для вас.

Отметим, что здесь использована команда eval. Это специальная встроенная команда интерпретатора shell, которая выполняет перевычисление текущей строки, подлежащей выполнению. Интерпретатор shell повторно анализирует эту строку, чтобы раскрыть значение имен переменных в командной строке и обеспечить распознавание переменных как таковых. Здесь мы перевычисляем переменную MORE. Напомним, что мы поместили в эту переменную конвейер. Если мы не перевычислим командную строку, то команда pr попытается открыть файлы "|" и "more", которые не существуют. Для того, чтобы shell вместо этого воспринял эти символы как указания конвейеров и программ, и используется команда eval.

Строка 10 имеет еще одну особенность. В командной строке уже есть один конвейер. Откуда shell знает, трактовать ли символ "|" как имя файла или как конвейер? Благодаря тому, что аргумент команды eval заключен в кавычки. Это указывает команде eval сохранить все, что находится в кавычках, без изменений, но раскрыть значение переменной MORE и поместить его в конец командной строки, находящейся в кавычках. Несколько непонятно, но если вы думаете об этом пару лет, оно становится осмысленным.

Для тех из вас, кто имеет новую команду ls (System V, версия 2 или BSD 4.2), не требуется два конвейера в команде. Как показывает строка 11, мы получаем подходящий колоночный формат из самой команды ls, вместе с показом всех файлов и специальными символами / и * для каталогов и исполняемых файлов. Обозначение $@ относится ко всему содержимому командной строки, т.е. к вашим дополнительным опциям команды ls и к именам файлов, список которых вы хотите распечатать. Поступая таким образом, мы создаем фундамент (опции a,C,F), а вы можете надстраивать его (используя опции R,t и т.д.). Скромный, но элегантный фокус заставить ls сообщить свои опции заключается в том, чтобы вызвать ее с неверной опцией. Большинство команд не используют опции z или ?, поэтому вызов "ls -z" или "ls -?" приведет к такому результату:

   |
   |   ls: illegal option -- z
   |   usage:  -1ACFRabcdfgilmnopqrstux [files]
   |

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

Вы обратили внимание, что все обычные команды системы UNIX, используемые в нашем командном файле, имеют полные маршрутные имена? Это может показаться несколько странным, но причина указания полных маршрутных имен в том, что когда shell запускает команду, он не должен возвращаться к анализу переменной PATH и искать, где расположена команда. Если вы обращаетесь к командам относительным способом, время поиска файлов представляет собой большие накладные расходы. Когда вы вызываете lc, интерпретатор shell ищет эту команду, затем lc вызывает ls, которую тоже нужно найти. Если после этого результаты пропускаются через more или pr, то требуется дополнительный поиск. А полные маршрутные имена распознаются интерпретатором shell сразу же (он видит, что первым символом является /), и нужная команда может быть вызвана быстро. Издержки на поиск - единственные издержки команды lc.

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

2.2.2. ll - вывод файловой информации в длинном формате

    ИМЯ:        ll

ll Выдает список файлов в длинном формате

НАЗНАЧЕНИЕ

Выдает список файлов в длинном формате (-l). Распечатку можно пропустить через команду more.

ФОРМАТ ВЫЗОВА
    ll [-m] [ls options] file [file...]
ПРИМЕР ВЫЗОВА

ll *.c Выдача списка файлов с исходными текстами на языке Си в длинном формате.

ТЕКСТ ПРОГРАММЫ
   1  :
   2  # @(#) ll v1.0   Long listing of files   Author: Russ Sage
   2а                   Выводит список файлов в длинном формате

   4  if [ "$1" = "-m" ]
   5    then MORE="| /usr/bin/more"
   6          shift
   7    else MORE=""
   8  fi

   10 eval /bin/ls -al $@ MORE
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

MORE Содержит строку передачи результатов по конвейеру команде more

ОПИСАНИЕ

Зачем нам нужен командный файл ll?

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

Что делает ll?

Основой этой команды является известная команда "ls -l". Она, если вы помните, дает очень емкую информацию о каждом файле, включая права доступа, связи, имя владельца, размер и так далее. (Кстати, программисты, использующие язык Си, могут получить эту информацию при помощи системного вызова stat(2).) Поскольку такой список при наличии множества файлов может легко переполнить экран, то предоставляется опция -m. Она обеспечивает постраничный вывод с помощью команды more. Отметим, что если используется эта опция, то она должна стоять первой. Строка символов, соответствующая этой опции, удаляется командой shift, так что она не смешивается с именами файлов и обычными опциями команды ls, которые передаются как аргументы.

После опции -m (если она есть) ll допускает указание любых других допустимых опций команды ls. Можно также использовать любую комбинацию имен файлов. Здесь применимы обычные средства порождения имен файлов: * соответствует любым символам, ? - одному символу, а символы [] задают диапазон из некоторого набора символов. В итоге мы получили команду ls, которая по умолчанию работает как "ls -l", вызывает команду more с помощью одной опции вместо необходимости указания конвейера в командной строке и при этом сохраняет гибкость команды ls.

ПРИМЕРЫ
    1.  $ ll /etc/*mount*

Выводит список всех файлов в каталоге /etc, имена которых содержат в каком-либо месте слово mount (например, mount, umount, unmountable).

    2.  $ ll -i `who|awk '{print "/dev/" $2}'`

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

    3.  $ ll `kind -a /lib`

Выводит в длинном формате список всех файлов архива в каталоге /lib. Этот каталог содержит библиотеки компиляторов всех языков системы UNIX. (Команда kind, которая отбирает файлы по их типу, рассматривается в следующем разделе.)

    4.  $ ll -m -i /dev

Выводит всю обычную информацию плюс номер индексного дескриптора для всех файлов в каталоге /dev. Выдача на экран происходит с помощью команды more.

ПОЯСНЕНИЯ

Если первым позиционным параметром является -m, то в строке 4 инициализируется переменная MORE для подключения конвейера и программы /usr/bin/more. (Вопрос о том, почему используется абсолютное маршрутное имя, обсуждался в предыдущем разделе.) Затем символьная строка -m командой shift убирается из командной строки. Если же первой опцией не является -m, то переменная MORE устанавливается в нуль, чтобы не влиять на повторный разбор командной строки, выполняемый с помощью команды eval (строка 10).

В строке 10 команда eval использована для получения результирующей командной строки. Команда ls вызывается с опциями -al (выдача списка всех файлов в длинном формате), которые мы установили по умолчанию. Затем берутся аргументы командной строки (минус первый аргумент, если это был -m, который мы убрали командой shift). Этими аргументами могут быть дополнительные опции команды ls плюс имена файлов или каталогов. В конце строки значение переменной MORE обеспечивает конвейер с командой more, если была указана опция -m. В противном случае значение переменной MORE равно нулю и не оказывает никакого влияния на анализ содержимого командной строки.

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


ГЛАВА 2. Доступ к файлам

2.2.3. kind - вывод однотипных файлов

    ИМЯ:        kind

kind Выдача списка имен файлов определенного вида

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
    kind [-a] [-d] [-t] [-x] [file...]
ПРИМЕР ВЫЗОВА
    more `kind /etc/*`

Вывод командой more всех текстовых файлов, имеющихся в каталоге /etc.

ТЕКСТ ПРОГРАММЫ
   1   :
   2   # @(#) kind v1.0  Prints files of the same kind  Author: Russ Sage
   2а                      Выводит список файлов определенного вида

   4   if [ $# -gt 0 ]
   5     then if [ `echo $1 | cut -c1` = "-" ]
   6            then case #1 in
   7                 -a)  KIND='archive'
   8                      shift;;
   9                 -d)  KIND='data'
   10                     shift;;
   11                -t)  KIND='text'
   12                     shift;;
   13                -x)  KIND='executable'
   14                     shift;;
   15                 *)  echo "kind: arg error"                           >&2
   16                     echo "usage: kind [-a] [-d] [-t] [-x] [file...]" >&2
   17                     echo "       -a  archive"                        >&2
   18                     echo "       -d  data"                           >&2
   19                     echo "       -t  text, default"                  >&2
   20                     echo "       -x  executable"                     >&2
   21                     echo "       if no args, reads stdin"            >&2
   22                     exit 1;;
   23                esac
   24         fi
   25   fi

   27  : ${KIND:='text'}
   29  case $# in
   30  0)   while read FILE
   31       do
   32           file $FILE | fgrep $KIND | cut -d: -f1
   33       done;;
   34  *)   file $@ | fgrep $KIND | cut -d: -f1;;
   35  esac
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
FILE | Содержит имена файлов по мере их чтения из stdin (стандартного ввода)
KIND | Содержит строку, определяющую тип файла
ОПИСАНИЕ

ЗАЧЕМ НУЖЕН КОМАНДНЫЙ ФАЙЛ kind?

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

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

Примером полезности такого рода утилиты может служить анализ содержимого каталогов. Возьмем, например, каталог /etc. Он содержит программы, файлы данных и текстовые файлы. Для каждого из этих типов требуется свой собственный тип анализа. Программы анализируются командами ls, size, nm и file. Файлы данных анализируются командой od.

Текстовые файлы анализируются командами more, wc, head, tail и другими. Таким образом, обычно вам необходимо работать в данный момент времени с файлами какого-нибудь одного типа.

ЧТО ДЕЛАЕТ kind?

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

      $ kind -d /etc/*
   то вывод мог бы выглядеть так:
   |
   |   /etc/mnttab
   |   /etc/utmp
   |   /etc/wtmp
   |

То есть, вывелся список всех файлов данных. А если бы я выполнил такую последовательность команд:

      $ cd /etc
      $ kind -d *

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

   |
   |    mnttab
   |    utmp
   |    wtmp
   |

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

Допустимыми опциями команды kind являются -a для файлов архивов, -d для файлов данных, -t для текстовых файлов (что является умолчанием) и -x для исполняемых файлов. Определение этих типов соответствует команде UNIX file. Заметим, что критерии того, что файл является исполняемым, в команде file отличаются от тех, которые применяет команда ls: ls проверяет биты x в индексном дескрипторе файла, в то время как file проверяет, являются ли первые несколько байтов содержимого файла "магическим числом". Это магическое число является идентификатором структуры a.out (см. /usr/include/a.out.h), который сообщает "Я являюсь скомпилированной Си-программой".

Имена файлов появляются в командной строке после опций. Эти имена могут быть порождены любым стандартным методом системы UNIX. Если в командной строке нет имен файлов, то kind превращается в фильтр и читает стандартный ввод для получения списка имен файлов. (Обратите внимание, что я сказал "имен файлов", а не "файлов". Можно использовать опции, поскольку они убираются из командной строки командой shift по мере того, как они встречаются.) Таким образом, вы можете использовать другие команды для того, чтобы передать по конвейеру список файлов утилите kind. Она отфильтровывает и выводит только те из них, которые соответствуют нужному вам типу.

ПРИМЕРЫ
    1.   $ od `kind -d /etc/*`

Выглядит так, как будто это должно работать, но команда od не работает с набором имен файлов. Она может обрабатывать только один файл в данный момент времени.

    2.   $ ll `sh -x kind -a /lib/*` | m

Это длинный пример. Выводит в длинном формате список всех файлов архивов, которые находятся в каталоге /lib. Мы запускаем shell в отладочном режиме выполнения, так что вы можете увидеть каждую командную строку перед ее выполнением. Результат по конвейеру передается команде more.

   3.   # find / -print | kind -x | while read FILE
   > do
   >        ll $FILE
   > done > /tmp/filelist

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

Вы могли бы выполнить ту же операцию при помощи такого оператора find:

    find / -perm -0111 -exec ll {} \;

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

   4. $ for F in `kind /bin/* /usr/bin/* /etc/*`
      > do
      >      fgrep "trap" $F /dev/null
      > done
      $ fgrep "trap" `kind /bin/* /usr/bin/* /etc/*`

$ find /bin /usr/bin/etc -exec fgrep "trap" {} \;

Это три различных способа поиска слова "trap" во всех текстовых файлах.

ПОЯСНЕНИЯ

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

В строке 4 проверяется, включены ли какие-либо параметры. Если параметры есть, то они обрабатываются. Если не используются никакие параметры, ничего не делается и управление передается на строку 27. Если были использованы какие-либо аргументы и первым символом является знак минуса (-), то выполняется оператор case для определения того, какой тип файла указан. Переменная KIND устанавливается в соответствии с типом файла, и данный параметр удаляется из командной строки командой shift. Если аргумент не совпадает ни с одной из допустимых опций, то ему соответствует случай *, что означает ошибку. На стандартное устройство регистрации ошибок выводится соответствующее сообщение об ошибке и синтаксическая подсказка, после этого kind завершается с плохим статусом выполнения.

В строке 27 производится проверка того, установлена переменная KIND или равна нулю. Если она равна нулю, в нее подставляется символьная строка "text". Если KIND уже установлена, то она не меняется. Это неплохой оператор присвоения значения по умолчанию. Таким образом, пользователь не обязан указывать опцию -t в командной строке. Если же опция -t была указана, то ей есть что сопоставить в операторе case.

Оставшаяся часть программы в строках 29-35 представляет собой еще один оператор case, который проверяет количество аргументов, оставшихся в командной строке после обработки ошибок. Если была указана какая-либо опция, то переменная KIND установлена и опция убрана командой shift. В командной строке могли остаться только аргументы, которые являются именами файлов или маршрутами. Если к тому времени, когда мы уже готовы к заключительной обработке, не осталось никаких аргументов, то значит в командной строке не было указано ни одного имени файла.

В этом случае в строках 30-33 организовывается цикл, который читает имена файлов из стандартного ввода, запускает команду file и использует команду fgrep для определения того, соответствует ли тип файла, выданный командой file, интересующему нас типу (хранимому в переменной KIND). Затем мы используем команду cut для выделения того, что нам нужно. Обычный вывод команды file содержит имя файла, двоеточие и затем описание. Нам нужно только имя файла, поэтому мы вырезаем первое поле, используя разделитель ":". Когда никакие данные больше не поступают, цикл while завершается, мы попадаем в конец оператора case и выходим из программы.

Если же аргументы НАЙДЕНЫ в командной строке, то вместо всего этого выполняется ветвь оператора case в строке 34. С помощью обозначения $@, имена всех файлов в командной строке включены в команду file. Таким образом, не нужен никакой цикл. Во всем остальном обработка идентична строке 32.

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

Было бы неплохо, если бы командный файл kind мог работать одновременно с разными типами файлов. Это означает наличие несколько опций в командной строке, например -a и -d. Вам могла бы понадобиться составная строка, в которой каждая часть была бы отделена символом |. Затем эта строка могла бы быть использована в команде egrep, например, "egrep 'archive|data'". Вам пришлось бы организовать цикл по командной строке вместо использования фиксированных позиций и убедиться в том, что вы не получите зациклившийся конвейер, когда задана только одна опция.

2.2.4. m - простой доступ к команде more

    ИМЯ: m

m Простой доступ к команде more

НАЗНАЧЕНИЕ

Обеспечивает быстрый и простой способ постраничного вывода

ФОРМАТ ВЫЗОВА
    m [more options] [file ...]
ПРИМЕР ВЫЗОВА

m * Вывод командой more всех файлов текущего каталога

ТЕКСТ ПРОГРАММЫ
   1   :
   2   # @(#)  m v1.0   Easy access to more
   2а                   Простой доступ к команде more

   4   /usr/bin/more $@
ОПИСАНИЕ

Зачем нужен командный файл m?

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

Имеется два основных способа вывода данных на экран. Первый способ - непосредственный вызов команды, например, "more datafile". Вы направляете данные на экран самой командой. Второй способ - использовать какую-нибудь команду для получения данных, а затем в конце перехватить их командой more, например "od -c . | more". В обоих этих случаях мы вводим с клавиатуры много символов. Сделав так, чтобы команда more вызывалась по одному символу, мы могли бы уменьшить последние две команды на шесть нажатий на клавиши. За целый день это хоть немного предохранит клавиатуру от разрушения! (Если ваша система поддерживает вызов команд по псевдонимам (aliasing), то, как указывалось ранее, вы могли бы использовать в этом случае команду alias: "alias m more".)

Что делает m?

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

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

Для тех, кто не слишком знаком с опциями команды more, отметим, что существуют две изящные возможности: 1) вход в редактор vi в том месте, где находится курсор при выводе командой more; 2) выход из more для запуска команды shell и возврат в то место, откуда вы вышли. Первая опция выполняется при нажатии клавиши "v" в строке состояния команды more. (То есть когда more отобразила полный экран текста и ждет продолжения.) Вторая опция запускается при вводе ":!cmd" или "!cmd".

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

Обычный сеанс работы выглядит так:

   |
   |    m `path termcap`           <-поиск таблицы описания тер-
   |                                 минала (termcap) и вывод
   |          .                      ее командой more
         .
         .
     --More--(5%)                  <-строка состояния more
     v                               vi /etc/termcap
     vi +210 /etc/termcap          <-командная строка для редак-
                                тора vi получена от more
         .
         .
     :q                            <-выход из vi
     --More--(5%)                  <-возврат в more
     :!sh                            порождение нового shell'а
     $ date                          запуск команды date
     Wed Apr 23 07:15:04 PST 1986
     $ ^d                          <-убрать порожденный shell
     --More--(5%)                  <-возврат в more
     :f                              распечатка имени файла,
                                выводимого командой more
     "/etc/termcap" line 54           выход команды f
     --More--(5%)
     f                             <-команда more для пропуска
                                полного экрана
      .skipping 23 lines
         .
         .
     --More--(9%)                  <-пропуск и выдача текста
     q                               выход из команды more
ПРИМЕРЫ
    1.  $ ll -R / | m

Начиная с корневого каталога (/), вывести в длинном формате (ll) все файлы (опция -a подразумевается в ll) всей системы (-R) и постранично распечатать на экран (| m).

    2.  $ m `path inittab rc passwd`

Обнаружить и вывести с помощью more системные файлы inittab, rc и passwd. Неприятность здесь заключается в том, что данный маршрут скорее всего относится к каталогу /bin/passwd, а не /etc/passwd (поскольку каталог /etc размещается в конце каталогов), а это означает, что вы можете попытаться вывести на экран исполняемый файл. В зависимости от того, какую из версий команды more вы запустили, это может привести к чему угодно начиная с сообщения команды more о том, что это был не текстовый файл, и заканчивая тем, что ваш терминал начнет показывать непонятные символы и даже зависнет.

ПОЯСНЕНИЯ

Поскольку в этом командном файле не так много текста, то все довольно понятно, нет ни обработки ошибок, ни других дополнений. Просто нехитрый вызов команды more. Полное имя здесь указано с целью повышения быстродействия, как мы обсуждали ранее. Вы должны перепроверить местонахождение вашей команды more. В системе Berkeley она может находиться в каталоге /usr/ ucb/more. Воспользуйтесь командой path more для определения этого места и вставьте соответствующий маршрут вместо указанного нами.

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

    :.!path more

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

2.2.5. mmm - обработка программой nroff макрокоманд для рукописей

   ИМЯ:      mmm

mmm Командная строка nroff для макросов обработки рукописей

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
    mmm file [...]
ПРИМЕР ВЫЗОВА

mmm memo Обработать с помощью nroff файл моих заметок memo и отобразить его на экран

ТЕКСТ ПРОГРАММЫ
   1   :
   2   # @(#)  mmm v1.0 Nroff command line with mm macros Author: Russ Sage
   2а                   Командная строка nroff с макросами mm

   4   if [ "$#" -eq 0 ]
   5     then echo "mmm: wrong arg count"   >&2
   6          echo "usage: mmm file [...]"  >&2
   7          exit 1
   8   fi

   10  LIST=""
   11  for ARG in $*
   12  do
   13          if [ ! -f $ARG ]
   14            then echo "mmm: $ARG is not a regular file" >&2
   15            else LIST="$LIST $ARG"
   16          fi
   17  done

   19  nroff -r0O -mm $LIST
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ARG | Содержит каждый позиционный параметр командной строки
LIST | Содержит список проверяемых имен файлов
ОПИСАНИЕ

Зачем нужен командный файл mmm?

Одним из фактов делового мира является работа с бумагами. Мы производим заметки, письма, контракты, документы, руководства и так далее. Если вы знакомы со стилем производства документации в системе UNIX, то ваши текстовые файлы в основном представлены в одном из форматов программы nroff.

Однако различные программы форматирования текстов служат различным целям. Имеется стандартный nroff и nroffс дополнениями, такими как макросы ms и mm. Для подготовки графической информации и выполнения типографских работ разработана программа troff. Система AT&T имеет целую программную среду под названием Writers Workbench, и система Berkeley имеет аналогичные возможности.

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

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

Что делает mmm?

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

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

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

ПРИМЕРЫ
    1.  $ mmm nroffile | m

Запуск команды nroff применительно к файлу nroffile, вывод результата на экран с передачей по конвейеру команде more. Это полезно при изучении утилиты nroff, проведении экспериментов с различными командами и наблюдения за соответствующими результатами.

   2.  $ for F in proj.?
       do
          mmm $F > $F.rf
       done

Обработка в цикле всех файлов, имена которых содержат символьную строку "proj.", за которой следует один символ. Это могут быть proj.1, proj.2 и так далее по всему набору символов вплоть до proj.z, proj.{, proj.|, proj.} и proj.~, если считать, что у вас есть файлы, имена которых содержат эти символы. Каждый файл обрабатывается, и выход nroff перенаправляется в файл с таким же именем, дополненным символами .rf.

    3.  $ mmm status[12] | lpr -o5

Обработка командой nroff файлов status1 и status2. Выход в стандартный вывод передается по конвейеру программе lpr. Программа lpr является фильтром и принимает или имена файлов в командной строке, или непосредственно данные, передаваемые ей по конвейеру (но не то и другое сразу). Опция -o5 указывает lpr сместить страницу на 5 символов.

ПОЯСНЕНИЯ

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

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

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

Если имени соответствует допустимый файл, оно добавляется в список хороших имен файлов. Этот список становится главным списком для команды nroff.

После того как все аргументы проверены, мы в строке 9 строим и выполняем командную строку nroff.

Опция -rO0 для nroff указывает макросам обработки рукописей (пакету mm) установить регистр, который имеет дело с отступом текста, в состояние, соответствующее отступу в 0 символов. Это значит, что весь текст начинается с крайней левой позиции, т.е. выровнен слева. Путем проведения экспериментов я обнаружил, что левое выравнивание текста программой nroff и установка отступа для принтера дает наиболее надежный вывод на печать. В противном случае, если вы установите отступ текста в nroff и отступ в принтере, то может произойти настоящее столкновение, когда дело коснется вывода колонок в странице. Вы можете изменить это место, если ваши программы вывода или устройства печати ведут себя как-то иначе. Опция -mm указывает программе nroff просмотреть библиотеку макросов обработки рукописей, чтобы определить, используются ли какие-либо из них во входном документе. Эти макросы очень большие и требуют много времени центрального процессора. Если вам необходимо использовать их, то вам потребуется большой компьютер или компьютер, специально предназначенный для этой цели, чтобы добиться хорошего времени получения результата.

Последним аргументом является $LIST. В этой переменной находится строка имен файлов, разделенных пробелами. Эти имена помещаются в командную строку nroff. Можете быть уверенными, что в этом месте нет никаких ошибок.

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

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

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

Отметим, что место, занятое в нашем командном файле указанием пакета mm, можно вместо этого заполнить ссылкой на другие макропакеты, имеющиеся в вашей системе, например -ms или -me, в зависимости от нужного вам формата. Отказ от поиска макросов, которые вам не нужны, ускорит обработку: подробности вы найдете в документации по nroff или troff.

2.2.6. pall - печать всех файлов в дереве

    ИМЯ:  pall

pall Распечатка всех файлов в дереве каталогов

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
    pall [-t|-d] directory
ПРИМЕР ВЫЗОВА
    pall /usr/lib

Выводит на печать постранично все текстовые файлы в каталоге /usr/lib

ТЕКСТ ПРОГРАММЫ
   1.  :
   2   # @(#) pall v1.0 Print all files in a tree Author: Russ Sage
   2а                   Печатает все файлы в дереве

   4   if [ $# -eq 0 -o $# -gt 2 ]
   5     then echo "pall: wrong argument count"  >&2
   6          echo "usage: pall [-t|-d] dir"     >&2
   7          echo " -t text (default)"         >&2
   8          echo " -d dev (.c,.h,.mk,.s)"     >&2
   9          exit 1
   10  fi

   12  NAME=""
   13  if [ `echo $1 | cut
   -c1` = "-" ]
   14   then case $1 in
   15        -t)     NAME=""
   16                shift;;
   17        -d)     NAME="-name \"*.[chms]*\""
   18                shift;;
   19         *)     echo "pall: invalid arg $1"    >&2
   20                echo "usage: pall [-t|-d] dir" >&2
   21                echo " -t text (default)"      >&2
   22                echo " -d dev (.c,.h,.mk,.s)"  >&2
   23                exit 1;;
   24        esac
   25  fi

   27  echo "creating output file: /tmp/lpr$$"

   29  eval find $1 -type f $NAME -print | sort | while read FILE
   30  do
   31          if file $FILE |
   32            egrep 'exec|data|empty|reloc|cannot
   open' >/dev/null 2>&1
   33          then continue
   34          else file $FILE > /dev/tty
   35               pr $FILE
   36          fi
   37  done >> /tmp/lpr$$

   39  echo "\nSend /tmp/lpr$$ to line printer (y/n): \c"
   40  read CMD
   41  if [ "$CMD" = "y" ]
   42    then lpr /tmp/lpr$$
   43  fi
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
FILE | Содержит имя каждого файла, который должен быть обработан в цикле while
NAME | Содержит строку поиска для определения местонахождения указанных файлов
CMD | Содержит команду выдачи результатов на принтер
ОПИСАНИЕ

Зачем нужен командный файл pall?

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

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

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

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

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

Например, мое значение ulimit равно 4096. Размер блока в данном случае равен 512, а не 1024. Максимальный размер файла равен 2097152. Вы можете вычислить это прямо с клавиатуры, как показано ниже:

   $ ulimit
   4096
   $ expr 4096 \* 512
   2097152

Этого значения достаточно для большинства случаев.

Что делает pall?

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

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

Если выбрана текстовая опция, ищутся все файлы в древовидной структуре и затем отбираются только текстовые файлы. Если указана опция разработки -d (development), то ищутся только файлы, связанные с разработкой программ. Затем эти файлы отфильтровываются с целью получения текстовых файлов. Считается, что к разработке программ относятся файлы, имена которых имеют вид *.c для исходных файлов на языке Си, *.h для включаемых файлов заголовков, *.mk для файлов построения программ (makefiles) и *.s для исходных файлов на ассемблере. Если требуются какие-либо другие шаблоны, то такие символы могут быть легко помещены в текст программы.

Прежде чем начнет выполняться поиск файлов, на экран выводится имя временного файла, чтобы вы знали, как обратиться к нему после завершения выполнения команды. Все результаты, полученные после обработки файлов, направляются в этот один файл. Командный файл pall также выводит на экран сообщения, когда он обрабатывает файлы. Вывод файлов выполняется в реальном времени по мере обработки файлов. Распечатка производится при помощи обычной команды UNIX'а file. По распечатке вы можете судить о том, файлы какого типа обрабатываются в данный момент.

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

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

Способ вызова pall влияет на формат имени файла в заголовке. Если вы вызвали pall, используя абсолютное имя каталога, то в распечатке используются полные маршрутные имена. Если вы вызвали pall с относительными маршрутными именами, то они и используются при выводе на печать. Внутри pall используется команда find системы UNIX. Команда find использует данные из командной строки, т.е. те, которые ввел пользователь. Выводимый заголовок изменяется в зависимости от того, что указано в командной строке, которую использует find. Если вы вызываете pall, используя следующую командную строку, то заголовок содержит полное маршрутное имя:

   |
   |    $ pall /usr/include
   |
   |    May 5 10:39 1986 /usr/include/a.out.h Page 1
   |              .
   |              .
   |              .
   |    May 5 10:39 1986 /usr/include/ar.h Page 1
   |              .
   |              .
   |              .

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

   |
   |    $ cd /usr/include
   |    $ pall .
   |
   |    May 5 10:39 1986 ./a.out.h Page 1
   |              .
   |              .
   |              .
   May 5 10:39 1986 ./ar.h Page 1
             .
             .
             .
ПРИМЕРЫ
    1.  $ pall /usr/include

Выводит ВСЕ файлы заголовков. Сюда включаются файлы заголовков в подкаталоге sys и во всех других каталогах, которые могут располагаться ниже каталога /usr/include. Это и есть причина, по которой был написан командный файл pall. Он создает один огромный листинг всех файлов заголовков в отсортированном порядке с печатью в заголовке страниц полного имени.

    2.  $ pall $HOME/src

Обходит все каталоги, находящиеся ниже каталога исходных текстов, и распечатывает все файлы.

ПОЯСНЕНИЯ

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

В строке 12 инициализируется переменная NAME. Это действие выполняется по умолчанию, поэтому данная строка дает возможность не указывать опцию в командной строке. Оператор if в строке 13 означает: "Если первым символом первого аргумента является дефис", то нужно проверить, какая это опция.

Если установлена опция -t, то переменная NAME инициализируется нулевым значением, что совпадает с действием по умолчанию, поэтому на самом деле ничего не меняется. Затем эта опция удаляется из командной строки.

Если установлена опция -d, то переменная NAME инициализируется для поиска символьной строки, соответствующей именам файлов программной разработки. Обратите внимание, что двойные кавычки внутри оператора экранированы, т.е. впереди них стоят символы наклонной черты. Командный интерпретатор shell воспринимает кавычки вокруг строки поиска на первой фазе синтаксического разбора без отмены присвоения, тем самым оставляя двойные кавычки последующей команде find.

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

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

Строки 29-43 - это главный цикл всей программы. Оператор find должен быть повторно проанализирован командой eval, поскольку переменная NAME содержит нужные нам данные. Если бы не было команды eval, то подстановка символьных строк выполнялась бы неправильно. Обратите внимание, что переменной NAME не требуются кавычки в строке 24. Они уже есть в переменной NAME, так как были обработаны командой eval.

Оператор find находит только файлы типа f, или обычные файлы, т.е. не каталоги и не символьные или блочные устройства. Здесь под "обычными файлами" понимается, что они еще могут быть файлами данных или исполняемыми. Если значение переменной NAME равно нулю, оно не влияет на командную строку. Если NAME содержит символы файлов программной разработки, то они становятся частью команды find при выполнении команды eval. Это накладывает ограничения на шаблон поиска команды find. Когда файлы найдены, их имена выводятся в стандартный вывод.

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

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

Для каждого подходящего файла выполняется проверка в строках 31-36. Проверка начинается с запуска команды file. Выход file по конвейеру передается команде egrep, которая ищет тип файла, соответствующий набору нескольких выражений. Если какое-либо выражение подходит, то нам не нужно обрабатывать этот файл. Это не текстовый файл, и его нельзя вывести на принтер. Во многих случаях файлы данных содержат большое количество символов прогона формата, которые выталкивают страницу после каждой пары символов. Если вы не будете находиться рядом с принтером, когда печатаются такие файлы, то вы можете получить листинг, занимающий половину ящика бумаги, затратив целый лес на ненужную работу.

Нам не нужен выход команды egrep, а только ее статус возврата.

Если egrep обнаруживает одно из указанных ей выражений, она завершается со статусом успеха, или 0. Тем самым проверка if включает выполнение оператора then, который в данном случае выводит нас из конструкции if-then-else и продолжает цикл while, пропуская таким образом файл.

Если же egrep не обнаружила ни одну из указанных символьных строк, то выполнение продолжается с оператора else, который выполняет еще одну команду file и переадресовывает ее вывод на устройство с именем /dev/tty. Это универсальное имя устройства, которое гарантирует вам вывод на экран вашего терминала. UNIX обеспечивает, что указание /dev/tty обходит любые команды переадресации вывода, действующие в данный момент. Поскольку стандартный вывод уже переадресован для всего цикла while, то нам нужно попасть на устройство /dev/tty, чтобы вывод шел на экран терминала, а не в файл печати. Отображение на терминал имени обрабатываемого файла позволяет пользователю знать, какой файл будет добавлен к распечатке.

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

После того как все файлы обработаны, задается вопрос о том, хотите ли вы вывести результирующий файл на печать. Предлагается ответить на этот вопрос "да" (yes) или "нет" (no), однако в программе проверяется только положительный ответ (yes). Это значит, что нажатие любой клавиши трактуется как ответ "no", кроме клавиши "y", означающей "да". Ответ пользователя читается с клавиатуры, и проверяется, является ли он символом "y". Если да, то файл ставится в очередь на печать. Если нет, дальнейшая проверка не производится и командный файл завершается.

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

УПРАВЛЕНИЕ ВЫВОДНЫМИ ФАЙЛАМИ БОЛЬШИХ РАЗМЕРОВ

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

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

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

Что же делать? Радикальным решением является увеличение значения ulimit. Вы можете сделать это либо с помощью программы на языке Си, использующей системный вызов ulimit, либо командой shell'а ulimit. Техника выполнения такой работы представлена в главе 7.

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

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

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

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

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


Глава 3. Поддержка файловой системы

  • ВВЕДЕНИЕ
  • 3.1. СОПРОВОЖДЕНИЕ ФАЙЛОВ
    • 3.1.1. Операции сопровождения
    • 3.1.2. Средства пересылки файлов
    • 3.1.3. Средства копирования
    • 3.1.4. Средства проверки операции копирования
  • 3.2. ПЕРЕСЫЛКА ФАЙЛОВ
    • 3.2.1. cptdir - копирование дерева каталога
    • 3.2.2. can - удаление файлов в "мусорную корзину"
    • 3.2.3. dosflp - копирование файлов с гибкого диска формата MS-DOS с использованием символов шаблона в именах файлов
  • 3.3. СРЕДСТВА ПОЛУЧЕНИЯ РЕЗЕРВНЫХ КОПИЙ
    • 3.3.1. autobkp - автоматически наращивамый файл резервной копии
    • 3.3.2. cpiobr - копирование и восстановление файлов в виде потока данных
  • 3.4. СРЕДСТВА ПРОВЕРКИ ОПЕРАЦИЙ КОПИРОВАНИЯ
    • 3.4.1. dsum - контрольные суммы двух катологов
    • 3.4.2. log - меню доступа к файлам протокола копирования

ВВЕДЕНИЕ

Даже "небольшая" система UNIX с малым числом пользователей порождает сотни файлов в ходе обычной работы. В процессе программирования вы можете создавать множество файлов для различных версий ваших программ. Ведение почты и запись текста при помощи редактора vi способствует тому, что накапливается еще больше файлов. Такие утилиты, как uucp, lp и другие добавляют еще больше файлов. Если у вас система UNIX установлена на микро-ЭВМ, то ваш жесткий диск начинает переполняться. В больших многопользовательских системах дисковая память редко считается проблемой, но в действительности всегда кажется, будто файлы стремятся расшириться до заполнения всей доступной дисковой памяти. Поэтому каждый пользователь должен нести ответственность за расход дискового пространства. (Если вы платите за дисковую память, то у вас также могут быть финансовые стимулы.) Однако, то, что вы хотите сохранить, вы хотите СОХРАНИТЬ. Именно здесь начинается работа по созданию резервных копий.

3.1. СОПРОВОЖДЕНИЕ ФАЙЛОВ

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

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

3.1.1. ОПЕРАЦИИ СОПРОВОЖДЕНИЯ

Сопровождение файлов включает два вида операций: создание резервных копий (копирование) и удаление "мусора".

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

Имеется два вида резервных копий: "мягкие" и "твердые". "Мягкие" резервные копии - это копии в другом файле или каталоге в той же или в другой файловой системе (т.е. разделе) на том же или другом жестком диске. Такого рода копирование сделать легко и оно предохраняет от наносимого самому себе ущерба, такого как удаление файла по невнимательности. Чаще всего для такого типа копирования используется наше средство cptdir. Основной недостаток мягкого копирования заключается в том, что вы по-прежнему уязвимы для таких воздействий, которые влияют на ваш физический носитель (обычно жесткий диск) так, что и оригинал и копия оказываются разрушенными.

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

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

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

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

3.1.2. СРЕДСТВА ПЕРЕСЫЛКИ ФАЙЛОВ

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

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

3.1.3. СРЕДСТВА КОПИРОВАНИЯ

Далее представляется "рабочая лошадка" - средства копирования. Autobkp использует список маршрутных имен, чтобы определить, какие части файловой системы должны быть проверены. Затем эта программа копирует из выбранных областей те файлы, которые были добавлены или изменены в последние 24 часа.

Cpiobr предоставляет интерактивное дополнение к команде cpio системы UNIX. Она позволяет вам скопировать файлы с жесткого диска на гибкий и, если необходимо, восстановить их с гибкого диска на жесткий.

3.1.4. СРЕДСТВА ПРОВЕРКИ ОПЕРАЦИЙ КОПИРОВАНИЯ

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

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

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

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

3.2. ПЕРЕСЫЛКА ФАЙЛОВ

3.2.1. cptdir - копирование дерева каталога

     ИМЯ: cptdir

cptdir Копирует дерево каталога в другое место

ФУНКЦИЯ

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

ФОРМАТ

     cptdir [-s] каталог-источник каталог-приемник
ПРИМЕР ВЫЗОВА
     cptdir $HOME /bkp

Копирует каждый файл из $HOME в каталог /bkp.

КОМАНДНЫЙ ФАЙЛ CPTDIR
     1  :
     2  # &(#) cptdir v.1.0  Copy a directory tree  Autor: Russ Sage

     4  if [ $# -lt 2 -o $# -gt 3 ]
     5     then  echo "cptdir: argument error"           >&2
     6           echo "usage: cptdir [-s] srcdir desdir" >&2
     7           echo "          -s  silent mode"        >&2
     8           exit 1
     9  fi
     11 if [ "$1" ="-s" ]
     12   then  OPT="-pd"
     13         shift
     14   else OPT="-pdv"
     15 fi
     17  SRC=$1
     18  DEST=$2
     19  umask 0
     21 if [ -d $DEST ]
     22    then echo "\"$DEST\" already exist. Remove it? (y/n): \c"
     23         read CMD
     24         if [ "$CMD" = "y" ]
     25            then rm -rf $DEST
     26                 mkdir $DEST
     27         fi
     28    else mkdir $DEST
     29 fi
     31 if [ "`echo $DEST|cut -c1`" = "/" ]
     32   then cd $SRC
     33        find . -print | sort | cpio $OPT $DEST
     34   else PWD=`pwd`
     35        cd $SRC
     36        find . -print | sort | cpio $OPT $PWD/$DEST
     37 fi
ПЕРЕМЕННЫЕ СРЕДЫ
CMD | Команда, полученная от пользователя
DEST | Каталог-приемник, в который нужно копировать
OPT | Опции, которые передаются утилите cpio
PWD | Текущий рабочий каталог
SRC | Каталог-источник, из которого нужно копировать
ОПИСАНИЕ

Зачем нам нужен cptdir?

Мы уже отмечали необходимость в дополнительных командах, которые рекурсивно обходят древовидную структуру файловой системы UNIX. В ранних версиях UNIX единственная команда tar могла управлять движением по дереву. В более новых версиях системы имеется опция -r в команде cp, которая делает cp рекурсивной (эта возможность реализована только в последней версии System V) и команда cpio. Последняя является многоцелевой командой копирования, которая может иметь дело как с потоковым форматом, так и с форматом файловой системы.

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

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

Что делает cptdir?

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

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

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

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

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

Тем не менее каталог-приемник должен быть создан, поскольку необходимо его наличие, чтобы команда cpio работала правильно. Если же его нет, cpio не выполнится и выдаст сообщение об ошибке.

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

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

ПРИМЕРЫ
    1. $ cd /mnt
    $ cptdir /bin .

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

    2. $ cd /bin
    $ cptdir . /mnt/bin

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

     3. $ cptdir /bin /mnt

То же, что и в примере 1.

     4. $ cptdir /bin /mnt/bin

То же, что и в примере 2.

ПОЯСНЕНИЯ

В строках 4-9 производится проверка аргументов командной строки. Если указано меньше двух аргументов, этого недостаточно. Как минимум должны быть указаны имена каталога-источника и каталога-приемника. Более трех аргументов слишком много. Самое большее, там должны быть опция -s, каталог-источник и каталог-приемник.

В строках 11-15 устанавливаются ключи команды cpio. По умолчанию это pdv, что означает "pass" (передача) для копирования в формате файловой системы (в отличие от необработанного потока данных), "directory" (каталог) для создания каталога при необходимости и "verbose" (многословный) для выдачи имен файлов по мере их копирования. Если первым позиционным параметром является ключ -s, который указывает запуск cptdir в молчаливом режиме, ключи команды cpio не содержат ключа выдачи сообщений и, таким образом имена файлов не выдаются на экран.

Строки 17,18 и 19 устанавливают каталоги "откуда" и "куда" и устанавливают переменную umask в 0. Переменная umask определяет подразумеваемые права доступа для всех файлов, созданных нашим командным процессором. Мы изменяем umask для гарантии того, что все файлы копируются в дерево-приемник и ни один из них не будет заблокирован из-за отсутствия прав чтения или записи. Побочным эффектом является то, что все каталоги имеют права доступа вида rwxrwxrwx, а все файлы - вида rw-rw-rw-, что может потребовать изменений для обеспечения вашей безопасности. Изменение umask имеет действие только на время работы процедуры. Когда cptdir завершается, umask вашего вызывающего командного процессора остается неизменным.

Строки 21-29 выполняют проверку каталога-приемника. Если он уже существует, вас запрашивают, нужно ли его удалить и заново создать. Если он не существует, он создается для работы cpio.

Строки 31-36 выполняют непосредственно копирование. Прежде чем объяснить, что здесь делается, давайте сперва посмотрим, как работает cpio. Поскольку оператор find генерирует список файлов, нам необходимо представлять, как его выход может влиять на выполнение cpio.

Если мы указали "find . -print", то полные имена файлов будут иметь точку впереди, например:

         ./dir
         ./dir/file1
         ./dir/file2

Это относительная нотация, которая очень полезна, когда вы не хо тите, чтобы ваши файлы передавались согласно абсолютным маршрутным именам, но хотим сохранить их взаимосвязь друг с другом. Если на них ссылаться относительно точки, то место, куда они будут помещены, может быть, где угодно. Однако, если мы скажем "find /dir -print", список будет выглядеть так:

         /dir
         /dir/file1
         /dir/file2

В обоих случаях мы ссылаемся на наш текущий каталог, но применение записи вида /dir заставляет полное имя начинаться с "/" и не допускает использование относительной нотации. Передача такой же информации команде cpio может радикально изменить место размещения ваших файлов. Например, если я сказал "cd /src; find . -print | cpio -pdv /dest", результирующий список будет таким:

         /dest/./dir
         /dest/./dir/file1
         /dest/./dir/file2

где на первом месте стоит, вероятно, то, что вы хотели. Однако, если я сказал "find /src -print | cpio -pdv /dest", результирующие маршрутные имена будут такими:

        /dest/src/dir
        /dest/src/dir/file1
        /dest/src/dir/file2

что не очень хорошо, поскольку это создает уровень каталога, в котором нет необходимости. Заметьте, что имя каталога "src" было перехвачено при распечатке. Это произошло потому, что его выдал find, а cpio считает, что src было частью имени каталога-приемника.

Повсеместное использование относительной нотации может привести нас к потере уже имеющейся информации. Например, если бы я сказал "cd/nowhere; find /src ....", каталог-приемник получил бы неверное имя. Мыдолжны уметь использовать этот тип нотации и не попадать в ловушки синтаксиса. Это и есть то, что делает cptdir.

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

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

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

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

ВОЗМОЖНЫЕ МОДИФИКАЦИИ КОМАНДНОГО ФАЙЛА

В нынешней реализации никакие дополнительные ключи не допускается передача никаких дополнительных ключей команде cpio. Что случится, если вы захотели заменить копирование файлов, где это возможно, созданием ссылок (ключ -l) или не менять время последнего доступа к исходному файлу при его копировании (опция -a)? Такие возможности были бы недопустимы.

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

Для этого потребуется такой цикл:

     for ARG in $*
     do
             if [ "`echo $ARG|cut -c1`" = "-" ]
               then  CPIOARG="CPIOARG $ARG"
                     shift
             fi
     done

Затем переменная CPIOARG может быть передана команде cpio.

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

Предположим, например, вы имеете каталог с двумя файлами. Если выполнился оператор "find /dir -print", список файлов будет таким:

         /dir
         /dir/file1
         /dir/file2

Обратите внимание, что имя каталога появляется первым. Проблема возникает, если имя каталога не принадлежит вам или вы не имеете права записи. Происходит следующее: имя каталога копируется первым, устанавливаются права доступа (блокируя вас) и после этого file1 и file2 не могут быть скопированы в каталог dir. В cptdir мы применяем решение изменить umask так, чтобы вы всегда имели права записи. Это своего рода клудж, но он работает.

Другой путь - это изменить оператор find. Выполнение оператора "find /dir -depth -print" сгенерирует такой список файлов:

         /dir/file1
         /dir/file2
         /dir

Обратите внимание, что имя каталога стоит ПОСЛЕДНИМ! Это правильно. Ключ -depth переворачивает список файлов так, что имя каталога печатается последним.

Что это дает? Фокус в том, что сначала копируются file1 и file2, а затем устанавливаются права доступа данного каталога. Вы можете записать файлы в каталог, для которого вы не имеете права записи. Благодаря тому, что файлы копируются первыми, вы можете не беспокоиться о том, какого рода права доступа имеет этот каталог. К сожалению, ключ-depth команды find поддерживается не всеми версиями системы UNIX.

3.2.2. can - удаление файлов в "мусорную корзину"

     Имя: can

can Управление "мусорной корзиной" файлов

НАЗНАЧЕНИЕ

Перемещает файлы в "мусорную корзину", симулируя их удаление. Это допускает восстановление файлов после их кажущегося удаления.

ФОРМАТ ВЫЗОВА
     can [-l] [-r] file [file ...]
ПРИМЕР ВЫЗОВА

can junk Посылает файл junk в "мусорную корзину"

ИСХОДНЫЙ КОД ДЛЯ CAN
    1  :
    2  # @(#) can v1.0  Maintain file trash can  Author: Russ Sage
    4  CAN=$HOME/.trashcan
    6  if [ ! -d $CAN ]
    7    then mkdir $CAN
    8  fi
    10 if [ "`echo \"$1\"|cut -c1`" = "-" ]
    11   then case $1 in
    12        -l)    echo "$CAN:"
    13               ls -al $CAN
    14               exit 0;;
    15        -r)    echo "removing $CAN/*:"
    16               rm -rf $CAN/*
    17               exit 0;;
    18        -z|-?) echo "usage can [-l] [-r] file [file ...]" gt;&2
    19               exit 0;;
    20        esac
    21 fi
    23 mv $@ $CAN
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
CAN | Положение каталога "мусорной корзины"
HOME | Положение вашего регистрационного каталога
ОПИСАНИЕ

Зачем нам нужен can?

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

         rm * /tmp <-- Удалить все файлы в каталоге /tmp

Мы хотели сказать rm /tmp/*, а на самом деле произошло сначала удаление всех файлов в текущем каталоге, а затем попытка удалить /tmp. Последнее будет безуспешным, поскольку tmp - это каталог. В результате мы удалили все, что хотели сохранить, и сохранили все, что хотели удалить! Этот синтаксис похож на другие операторы UNIX, вроде "grep *file": противная ошибка.

       rm -rf / tmp  <-- Удалить каталог tmp со всеми файлами

Мы хотели сказать rm -rf /tmp, но нечаянно вставили пробел в команду. На самом деле удалятся ВСЕ файлы во всей системе (если мы дадим команде выполняться достаточно долго), потому что мы сказали UNIX удалить корневой каталог и всех его потомков! Вы должны быть внимательны с командой rm. Если покажется, что что-то не так, удалите эту команду. Она может погубить вас.

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

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

Что делает can?

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

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

Can распознает только ключи -l и -r. Ключ -l показывает, что находится в "мусорной корзине", а -r удаляет все ее содержимое. Запомните, что если вы что-то удалили из "мусорной корзины", вы не сможете его восстановить.

Процесс помещения файлов в "мусорную корзину" выполняется командой mv. Ключи, предназначенные для can, должны быть первым аргументам в командной строке. Если вы желаете передать ключи команде mv, то их можно поместить в любом месте командной строки. Единственные ключи, дающие синтаксическую подсказку, - это -z и -?. Их предназначение - быть флагами только для обработки ошибок. Благодаря наличию специальных флагов обработки ошибок, выдающих справочную (help) информацию, ключи команды mv, как и ключи can, можно помещать первыми в командной строке, не оказывая влияния на can. Если вы создаете ваши командные файлы так, чтобы эти ключи всегда выдавали информацию об использовании (т.е. никогда небыли "настоящими" ключами), то вы имеете хороший способ получения помощи по синтаксису. Многие (но, увы, не все) стандартные команды UNIX дают по ключам -z или -? подсказку об использовании и это полезно помнить всякий раз, когда вы попали в тупик.

Если can не получает никаких ключей, действие по умолчанию заключается в пересылке всех указанных файлов в "мусорную корзину", размещенную в вашем регистрационном каталоге под именем $HOME/.trashcan. Если этот каталог отсутствует, он автоматически создается при первом выполнении командного файла can. Это позволяет вам запускать команду, не указывая специального положения "корзины". Если вы применяете ключ-r, файлы в "мусорной корзине" будут удалены, а сама она нет.

ПРИМЕРЫ
     1.   $ can *.c

Перемещает все файлы, которые оканчиваются на .c, в "мусорную корзину".

     2.   $ can -l

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

     3.   $ can -r

Удаляет все файлы из "мусорной корзины".

     4.   $ can -q *

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

ПОЯСНЕНИЯ

Строка 4 устанавливает место "мусорной корзины" так, чтобы она размещалась в вашем регистрационном каталоге под именем .trashcan. Заметьте, что ее именование, начиная с точки, делает ее не распечатываемым, или скрытым файлом. Единственный способ увидеть такие файлы использовать ключ -a в команде ls.

Строки 6-8 проверяют, определен ли сейчас каталог "мусорной корзины". Если нет, он создается. Обратите внимание, что поскольку его создаете вы, он имеет такие права доступа на чтение и запись, как в вашем регистрационном каталоге. Строки 10-21 проверяют, начинается ли первый позиционный параметр с черточки (-). Если такой параметр обнаружен, проверяется, является ли он ключом командного файла can (-l, -r, -z или -?). Обратите внимание, что для того, чтобы для использования двойных кавычек внутри двойных кавычек (строка 10), вы должны экранировать кавычки. Символ обратной косой черты (\) использован именно для этой цели.

Если указан ключ -l, выдается напоминание об имени каталога "мусорной корзины", команда ls выводит список файлов в "мусорной корзине" и процедура can завершается, поскольку требовалось только вывести список.

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

Если указан ключ -z или -?, выдается подсказка об использовании и can завершается. Это не совсем хорошо, но мы не можем использовать символ *, соответствующий любому другому ключу, поскольку ключ может быть предназначен для команды mv, а не для can. Благодаря использованию всего двух аргументов для обработки ошибок, мы можем разрешить передачу всех остальных аргументов. Если ключ не является одним из ключей can, или одним из указанных ключей обработки ошибок, то он передается команде mv. Если ключ недопустим для этой команды, команда mv выдает свое сообщение об ошибке и завершает работу. Вы можете, естественно, модифицировать командный файл так, чтобы он проверял допустимость ключей команды mv на "внешнем" уровне. Тогда он может выдать сообщение об ошибке и завершиться, если указанный ключ недопустим ни для can, ни для mv. Вопрос в том, стоит ли платить за более полный контроль над обработкой ошибок ценой разбухания программы и временем исполнения. Строка 23 выполняет собственно перемещение файлов в "мусорную корзину". Заметьте, что это выполняется только если не указаны никакие ключи can, поскольку это поведение can, принятое по умолчанию. Здесь используется параметр $@. Путем включения всех параметров в командную строку, любые ключи, предназначенные команде mv, передаются ей. Таким способом мы можем изменить путь, которым файлы посылаются в "мусорную корзину".


ГЛАВА 3. Поддержка файловой системы

3.2.3. dosflp - копирование файлов с гибкого диска формата MS-DOSс использованием символов шаблона в именах файлов

    Имя: dosflp

dosflp Копирование файлов с гибкого диска формата DOS с использованием символов шаблона в именах файлов

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
         dosflp [-a] [-c] [-dDRIV] [-eEXP][-h] [-l] [-r] [-sDIR]

где

-a | означает копирование файлов, соответствующих *.asm
-c | означает копирование файлов, соответствующих *.c
-d | выбирает имя устройства DRIV из набора A,B,X,Y (по умолчанию A)
-e | использует выражение EXP, чтобы применить к файлам grep
-h | копирует файлы, соответствующие *.h
-l | только выдает список файлов
-r | удаляет файлы вместо их копирования
-s | указывает подкаталог DIR на гибком диске формата DOS
ПРИМЕР ВЫЗОВА

dosflp Копирование всех файлов с устройства A: в текущий каталог

ИСХОДНЫЙ КОД ДЛЯ DOSFLP
    1  :
    2  # @(#) dosflp v1.0  Wildcard copies from DOS floppy
                    Author: Russ Sage

    4  EXP=.\*
    5  DRIVE="A:"
    6  OP="c"

    8  if [ "$#" -gt 0 ]
    9    then  for ARG in $@
    10          do
    11          case "$ARG" in
    12          -a)  EXP='.*\.asm$';;
    13          -c)  EXP='.*\.c$';;
    14          -d*) DRIVE="`echo $ARG | cut -c3-`:";;
    15          -e*) EXP='`echo $ARG | cut -c3-`';;
    16          -h)  EXP='.*\.h$';;
    17          -l)  OP="l";;
    18          -r)  OP="r";;
    19          -s*) DRIVE="$DRIVE`echo \"$ARG" | cut -c3- `/";"
    20          *)   echo "dosflp: arg error"
    21               echo "usage: dosflp [-a] [-c] [-d] [-e] [-h] [-l]
     [-r] [-s]"
    22               exit 1;;
    23          esac
    24       done
    25 fi

    27 case $OP in
    28 c)  echo "\nCopying files from $DRIVE to `pwd`";;
    29 l)  echo "\nListing files on $DRIVE"
    30     dosdir $DRIVE | more
    31     exit;;
    32 r)  echo "This option removes all the data on the floppy."
    33     echo -n "Do you want to do this (y/n): "
    34     read RSP
    35     if [ "$RSP" = "y" ]
    36       then  echo "\nRemoving files on $DRIVE"
    37       else exit
    38     fi;;
    39 esac

    41 dosls $DRIVE | tr "[A-Z]" "[a-z]" > /tmp/doslist

    43 for FILE in `grep "$EXP" /tmp/doslist`
    44 do
    45         echo $FILE
    46         case $OP in
    47         c)  doscp $DRIVE$FILE .;;
    48         r)  dosrm $DRIVE$FILE;;
    49         esac
    50 done

    52 rm /tmp/doslist

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ARG | Хранит аргументы командной строки
DRIVE | Устройство с гибким диском формата DOS
EXP | Выражение, имитирующее действие символа-шаблона
FILE | Хранит имя файла, над которым производится действие
OP | Ключ, определяющий необходимое действие
ОПИСАНИЕ

Зачем нам нужен dosflp?

Это команда только для системы XENIX. Операционная система XENIX, являясь продукцией фирмы Microsoft, имеет средства для общения с файловой системой MS-DOS. Для ознакомления с основами совместного использования DOS и XENIX давайте рассмотрим основные параметры.

Каждый жесткий диск может иметь максимум четыре раздела. Это ограничение MS DOS, которое перенесено в мир XENIX. Ничего плохого в этом нет, пока мы не начинаем работать с жестким диском большой емкости. Для70-мегабайтного диска, например, вы можете создать четыре равных раздела, каждый из которых содержит приблизительно 17 Мбайт. Вы можете создать меньший раздел, но тогда другой раздел должен быть больше. В зависимости от того, какая часть ваших программ и данных должна быть использована в основном MS-DOS и какая - XENIX, может быть использована различная конфигурация.

Большим преимуществом системы XENIX/DOS является то, что XENIX может размещаться в одном разделе, а DOS в другом. Как это сделать? Нужно запустить программу "fdisk" в каждой операционной системе. Это значит, что XENIX может общаться с разделом DOS, получая полное имя устройства, указывающее на другой раздел. Драйвер, который читает раздел DOS, должен знать, как выглядит DOS (т.е. знать файловую систему DOS). Если выполнить такую операцию, можно получать списки файлов и копировать их туда и обратно. К сожалению, DOS не имеет возможности чтения разделов XENIX.

При работе с гибким диском вы имеете дело только с одним разделом. Это снова ограничение DOS. Некоторые системы UNIX, в отличие от DOS, позволяют иметь столько разделов на жестком или гибком диске, сколько вы хотите, в отличие от DOS. По определению, гибкий диск DOS сформатирован в системе DOS, которая выполняет форматирование низкого уровня и помещает файловую систему DOS на гибкий диск. В системе XENIX гибкий диск может быть либо в формате файловой системы, либо неструктурированным устройством последовательного доступа подобно магнитной ленте. Для процедуры dosflp мы используем только гибкие диски в формате DOS.

Теперь к делу. Предположим, вы имеете систему DOS и файлы, находящиеся на диске DOS, вы можете читать и писать файлы на гибкий диск изXENIX. Но существуют некоторые ограничения на выполнение операции копирования, которые не слишком удобны пользователю. Например, вы можете сказать "doscp *.c a:". В результате все файлы текущего каталога которые оканчиваются на .c, будут скопированы на гибкий диск формата DOS наустройстве a:. Побочный эффект выполнения doscp заключается в том, что все символы перевода строки (или прогона строки) превращаются в символ возврат каретки/перевод строки, поскольку DOS обрабатывает конец строки иначе, чем XENIX. Таким же образом, когда вы копируете с гибкого диска формата DOS в XENIX, лишние символы возврата каретки убираются.

Что вы не можете сделать, так это сказать "doscp a:*.c". Команда doscp не допускает указания вида *.c при копировании с гибкого диска. Это происходит потому, что командный процессор распространяет метасимволы (*,?,[]) и не может непосредственно читать раздел DOS. Поэтому вы не можете использовать символы при копировании с гибкого диска DOS.

Отметим, что может наблюдаться гораздо больше побочных эффектов, когда вы имеете дело с гибкими дисками DOS. Во-первых, длина имени файла ограничена. DOS допускает до восьми символов имени файла плюс три символа расширения. В результате после копирования всех ваших файлов XENIX на гибкий диск многие из них могут иметь не те имена, которые они имели в XENIX. Это сущее страдание, когда вы пытаетесь сделать копию на гибкие диски DOS, потому что вы больше не имеете уверенности, как обращаться к файлам, когда вы копируете их обратно с гибкого диска. Кроме того, поскольку расширение имени файла в DOS имеет только три символа, файл с именем "spreadsheet.finance" может оказаться на гибком диске DOSс именем "spreadsh.fin" и распознавание его может представлять определенные трудности.

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

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

Что делает dosflp?

Dosflp пытается исключить все негативные аспекты копирования файлов XENIX/DOS. Это высокое требование, но оно достижимо. Вкратце подход dosflp следующий: получить список имен файлов с гибкого диска, перевести имена в нижний регистр, выбрать из полного списка имена тех файлов, которые соответствуют вашим требованиям, и затем копировать файлы один за другим в текущий каталог XENIX. Для того, чтобы сделать это, требуется гораздо больше команд XENIX вида dosxx, а также различных других команд XENIX.

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

Обычно мы хотим управлять файлами определенного типа как группой. Сюда относятся ассемблерные исходные файлы, исходные файлы на языке C и файлы-заголовки на языке C. Поэтому, чтобы снять с вас обязанности по вводу универсальных символов для этих типов файлов, мы прямо указываем их в качестве опций команды dosflp. Например, ключ -a копирует только файлы, которые оканчиваются на .asm, поэтому нет необходимости помнить вид выражения для копирования этих файлов. Аналогично, ключ -c копирует все файлы, оканчивающиеся на .c, и ключ -h копирует файлы, оканчивающиеся на .h.

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

Используйте ключ -d для указания, с какого гибкого диска производится копирование. По умолчанию это устройство a: или A:. Не имеет значения, на каком регистре вы укажете имя устройства. Для уверенности проверьте файл /etc/default/msdos. Этот файл содержит соответствия между символом устройства и маршрутным именем XENIX. Например, файл может выглядеть так:

       A=/dev/fd048ds9
       B=/dev/fd148ds9
       C=/dev/hd0d
       D=/dev/hd1d
       X=/dev/fd096ds15
       Y=/dev/fd196ds15

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

В качестве основного средства выполнения работы dosflp использует команду doscp. Это утилита способна понимать формат файловой системы DOS. Dosflp передает ей обозначение устройства и другие опции посредством переменных командного процессора. Например, ключ "-dB:" меняет устройство на B вместо принятого по умолчанию устройства A.

Если выражения прямого указания типа файлов не соответствуют тому, что вам нужно, вы можете определить свои собственные выражения сопоставления, используя ключ -e. Напомним, что выражение должно соответствовать синтаксису команды grep. Если вы хотите освежить свою память, посмотрите grep(1) в руководстве по AT&T UNIX или grep(C) в руководстве по XENIX. Для получения полной информации о синтаксисе посмотрите ed(1). Этот синтаксис является основой большинства команд, работающих с регулярными выражениями, таких как sed и grep.

Например, если вы используете выражение "*test*", выражение для grep должно иметь вид ".*test.*". Его можно слегка изменить в зависимости от того, что вы желаете иметь с каждой стороны цепочки test. В данном случае синтаксис указывает все символы (.*), за которыми следует цепочка t-e-s-t, а затем любая цепочка символов (.*). В этом случае ключ имел бы вид "-e.\*test.\*". Это кажется немного странным, но это соответствует синтаксису. (Двойные кавычки не являются частью команды.)Символ обратной косой черты (\) используется для экранирования звездочка. Если вы не экранируете ее, командный процессор соотнесет ее с именами всех файлов вашего текущего каталога, чего вы не желаете. Экранирование ее позволит, чтобы нужный символ был передан dosflp, для использования ее в grep-последовательности.

Ключ -h - это еще один из ключей прямого указания. Давайте вкратце рассмотрим его синтаксис внутри dosflp. Это ".*\.h$", и он указывает любой символ, за которым стоит одно или несколько вхождений любого символа (.*), литеральная точка (.\), символа h и вслед за ним конец строки (h$). Вы могли бы указать то же самое, используя ключ -e, но -h делает это гораздо легче. Ключ -l изменяет основное действие команды dosflp. Вместо копирования файлов он выдает список файлов. Это делается путем выполнения различных команд вида dosxx, в данном случае dosdir. Ключ выдачи списка полезен в dosflp, потому что вы можете получить список как информацию к решению о том, что делать дальше, и вам нет необходимости помнить команду dosdir.

Ключ -r также изменяет основную операцию команды dosflp. В этом случае файлы удаляются, а не копируются. Если вы указали этот ключ, выдается сообщение, которое просит вас подтвердить, что вы хотите удалить указанные файлы. Вы можете просто ответить "n", и запретить удаление, если вы ввели этот опцию случайно. Напомним, что удаленные файлы или файлы, включенные в список (в случае ключа -l), выбраны выражением grep, которое жестко запрограммировано или указано пользователем. По умолчанию выбираются ВСЕ файлы. Для ключа -r это соответствует тому, что сказать "rm *".

Последний ключ, -s, обеспечивает возможность доступа к файлам, которые размещены внутри подкаталога на гибком диске DOS. Если вы обращаетесь только к имени устройства, по умолчанию ключ -s относится к каталогу самого верхнего уровня на гибком диске. Если нужный вам файл находится в подкаталоге, вы должны использовать определенную нотацию, чтобы попасть в него. Одно из различий между XENIX и DOS заключается в символе, используемом для разделения элементов маршрутного имени. XENIX использует обычную запись в стиле UNIX - /x/y/z. В DOS применяется символ "обратная косая черта", т.е. \x\y\z. Но если вы хотите использовать команды XENIX на гибком диске DOS, вы должны применять обычную запись XENIX, a:/x/y/z. Это не совсем понятно, но правильно.

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

ПРИМЕРЫ
       1. $ dosflp -dB: -c -l

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

       2. $ cd /destdir
          $ dosflp -ssrc -e.\*src.\*

Переход в каталог, куда будут помещены файлы. Копируются файлы с гибкого диска DOS (устройство A, подкаталог src), в текущий каталог. Файлы для копирования указаны как *src*. В записи UNIX это выглядело бы так: "cp A:/src/*src* .".

       3. $ dosflp -r -stmp

Удаляет все файлы, размещенные в подкаталоге tmp на гибком диске DOS (устройство A). Обратите внимание, что сам каталог не удаляется. В записи UNIX это выглядело бы так: "rm A:/tmp/*".

       4. $ sh -x `path dosflp` -dB:

Запускает процедуру dosflp в отладочном режиме выполнения. Единственное ограничение при таком вызове командного процессора заключается в том, что файл данных, который вы посылаете ему (в данном случае dosflp), должен иметь полное маршрутное имя. Поскольку командный процессор НЕ выполняет поиск маршрутного имени файла, нам необходимо сперва найти маршрутное имя dosflp, затем передать его командному процессору, запущенному в отладочном режиме выполнения, а также передать процедуре dosflp аргумент в командной строке. Заметьте, что вызов dosflp таким путем не меняет значение переменной $#, которое только распознает ключ -dB: как аргумент.

ПОЯСНЕНИЯ

Строки 4-6 выполняют инициализацию по умолчанию путем сохранения значений в соответствующих переменных командного процессора. По умолчанию символ-шаблон ставится в соответствие всем файлам, указанным выражением для команды grep .\*. Обратная косая черта требуется для экранирования звездочки, поэтому она не перехватывается командным процессором. Устройство по умолчанию - A:. Операция по умолчанию - копировать файлы, что указано значением "c" для переменной опции.

В строках 8-25 устанавливаются значения ключей и производится проверка на наличие ошибок. Если командная строка имеет некоторые аргументы ($# -gt 0), мы перебираем каждый аргумент и проверяем его. Если найден допустимый ключ, переменные устанавливаются согласно ключу. Если обнаружен недопустимый ключ, выдается сообщение об ошибке и программа завершается с плохим статусом возврата.

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

Из всего сделанного следует вывод, что пробелы между ключами и символами, которые стоят за ними, не допускаются. Например, ключ -d должен получить имя устройства. По синтаксису должно быть -dB:, но не-d B:, потому что B: интерпретировалось бы как другой аргумент ARG вцикле for, а это все испортит.

В строках 27-39 операция, которая должна быть выполнена, определяется при помощи следующего оператора case. Если должно быть выполнено копирование, выдается сообщение "copying" и выполняется то, что следует за оператором case. Если должен быть выдан список файлов, выдается сообщение об устройстве, содержимое которого должно распечататься, затем выдается список файлов путем выполнения команды dosdir и конвейерной пересылки результата команде more, после чего dosflp завершается.

Если файлы должны быть удалены, пользователю выдается запрос на подтверждение удаления. Если ответ "yes", выдается сообщение, с какого устройства файлы будут удалены. Если ответ "no", dosflp завершается.

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

Строки 43-50 выполняют само копирование. Цикл for запускается для доступа к каждому файлу индивидуально. Это требование команд вида dosxx. Вы должны получать доступ к одному файлу один раз, поскольку этот уровень не обладает возможностью указания символа-шаблона. Имена файлов, которые использует цикл for, определены путем использования команды grep для выбора имен соответственно выражению, установленному ранее.

Имя каждого выбранного файла сначала отображается, так что пользователь может видеть, выполняется ли команда так, как ожидалось. В этом месте мы можем сделать одну из двух вещей: или копировать файлы, или удалить их. Эта операция определяется оператором case в строках 46-49.Если операция - копирование файлов, файлы копируются из комбинации устройство-файл в текущий каталог. Обратите внимание, что в переменную DRIVE включается подкаталог, если он был указан в командной строке. Это объясняет наличие символа "/" в конце присвоения значения переменной DRIVE в строке 16. Полное выражение должно быть таким: B:/subdir/file. Если операция - удаление файлов, комбинация устройство/файл удаляется выполнением команды dosrm. Попутно заметим, что маршрутное имя есть нечто гибкое (или небрежное, в зависимости от того, как вы смотрите на него) в том смысле, что вы можете сказать A:/subdir или A:subdir. Оба варианта правильны. После того как все файлы будут обработаны, временный файл удаляется.

ВОЗМОЖНЫЕ МОДИФИКАЦИИ КОМАНДНОГО ФАЙЛА

Одно из мест, где вы можете настраивать dosflp, это регулярные выражения. Уже включены выражения для .asm, .c и .h, но вы можете изменить это или добавить больше ключей для любой последовательности, которую вы часто используете.

3.3. Средства получения резервных копий

3.3.1. autobkp - автоматически наращивамый файл резервной копии

         Имя: autobkp

autobkp Автоматически наращиваемый файл резервной копии

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
     autobkp [-c] [>logfile]

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

ПРИМЕР ВЫЗОВА
     autobkp < filelist >> bkplog

Копирует все файлы, указанные в filelist, и записывает имена файлов в файл с именем bkplog

КОМАНДНЫЙ ФАЙЛ autobkp
    1   :
    2   # @(#) autobkp v1.0 Automatic file backup Author: Russ Sage

    4   if [ $# -gt 1 ]
    5     then  echo "autobkp: argument error"                     >&2
    6           echo "usage: autobkp [-c] [>logfile]" >&2
    7           exit
    8   fi

    10  if [ "$1" = "-c" ]
    11    then  COPY=on
    12    else  COPY=off
    13  fi

    15  echo "\nBACKUP DATE  `date  '+%a %m/%d/%y %H:%M:%S'`"
    16  echo "-----------------------------------------"

    18  SYSTEM=''     # destination system uucp node name
    19  :  ${SYSTEM:=`uuname -l`}

    21  echo "Sourse system:\t\t`uuname -l`\nDestination system:\t$SYSTEM"

    23  while read SRCDIR DESTDIR FILES
    24  do
    25        if [ ! -d $SRCDIR ]
    26          then  echo "autobkp: $SRCDIR is not a directory"
    27                continue
    28        fi

    30        cd $SRCDIR
    31        echo "\nFinding files in: $SRCDIR"

    33        for FILE in `find . -type f -ctime 0 -name "$FILES" -print`
    34        do
    35                  case $COPY in
    36                  off) uucp $FILE $SYSTEM!$DESTDIR;;
    37                  on)  cp $FILE $DESTDIR;;
    38                  esac
    39                  echo "  Transferred $FILE to $DESTDIR"
    40        done
    41  done

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
COPY | Флаг, определяющий, используется команда uucp или cp
FILE | Имя каждого файла, найденного в исходном списке маршрутов
FILES | Символ-шаблон, указывающий, какие файлы определены
PATH1 | Имя маршрута-источника
PATH2 | Имя маршрута-приемника
SYSTEM | Имя системы-приемника для uucp
ОПИСАНИЕ

Зачем нам нужен autobkp?

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

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

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

Автономная микро- или супермикросистема может иметь всего один жесткий диск. Если диск содержит достаточно места для размещения другого раздела, вы можете копировать в этот раздел. Раздел может также использоваться как неструктурированное устройство в отличие от файловой системы и рассматриваться как магнитная лента или гибкий диск. Среди других возможностей хранения информации могут быть второй жесткий диск, кассетная лента или устройство копирования на ленту. Если вам недоступна ни одна из этих возможностей, вы всегда можете копировать на гибкие диски. Это утомительная ручная работа, но она может быть выполнена при помощи команд tar или cpio.

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

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

Что делает autobkp?

Вы перечисляете маршруты и autobkp находит файлы по этим маршрутами копирует их в то место, которое вы указали. Вы можете указывать имена файлов по образцам, таким как *.c, *.h или каким-либо еще. С помощью autobkp вы можете копировать важные файлы без копирования всех файлов. Иногда это удобно - пропускать файлы при копировании. Типичные файлы, которые вы, возможно, не хотите копировать, - это очень большие файлы(не являющиеся важными, как файл core и файлы данных), временные файлы(как *.o, которые вновь создаются при каждой новой компиляции) и исполняемые файлы, если у вас есть исходные программы на языке Си и вы можете их скомпилировать для получения новых исполняемых файлов. Пропуская эти файлы, вы можете уменьшить размер ваших копий на мегабайты.

По умолчанию копирование производится командой uucp, которая предполагает, что у вас подчиненная система по отношению к главной машине и копирует ваши файлы в большую систему. Если вы хотите копировать ваши файлы в другое место жесткого диска или на другой жесткий диск, используйте ключ -c для копирования командой cp вместо использования команды uucp. Во время процесса копирования на стандартный вывод выводятся сообщения о состоянии дел. Это позволяет легко собрать все сообщения путем переадресации stdout на время копирования. Если вы выполняете autobkp в ручную, сообщения выводятся на экран. Первое сообщение - это заголовок, который печатает день, дату и время. Это выглядит так:

     |  BACKUP DATE   Fri 05/23/86 17:33:35
     |

Второе сообщение определяет систему-источник и систему-приемник. Оно появляется ниже. В нашем примере система-источник - russ, а система-приемник - vax.

     |  Source system:   russ
     |  Destination system:  vax

При каждом входе в систему-источник выдается следующее сообщение:

     |  Finding files in: src_dir
     |

где выражение src_dir - это место, откуда файлы будут переданы в цикл копирования. Обратите внимание, что первое имя должно быть именем каталога, потому что autobkp начинает именно с этого места поиск файлов. Если первое имя не является каталогом, программа печатает сообщение об ошибке и продолжает работу со следующим набором источник/приемник для копирования.

Для каждого найденного файла печатается следующее сообщение после завершения копирования:

     |  Transferred  file  to dest_dir
     |

которое указывает, что файл file был скопирован в каталог-приемник с именем dest_dir.

ФАЙЛ СО СПИСКОМ МАРШРУТОВ

Чтобы сделать интерфейс настолько гибким, насколько это возможно, autobkp читает стандартный ввод. Переназначая stdin, вы можете поддерживать разные списки файлов, которые необходимо копировать и переключать их в командной строке. Вы можете иметь один список маршрутов для системных файлов, другой для исходных файлов, третий для личных файлов, четвертый для файлов с готовым продуктом и так далее. Для каждой из этих групп файлов создается список маршрутов и передается в качестве входа для autobkp. Входные данные читаются как три поля: FROM, TO иTYPE. Поле FROM - это каталог-источник. Поиск файлов начинается с этого места. Напомним, что autobkp проходит вниз до конца дерева файлов, начиная с указанного каталога.

Поле TO - это каталог-приемник, куда все файлы, найденные для данной записи в файле со списком маршрутов, помещаются на машине-приемнике или в разделе-приемнике.

Поле TYPE - это описатель-шаблон, который сообщает autobkp, какие файлы искать. Его значение может быть *, *.c, *src*, и так далее. Как мы увидим позже, этот описатель передается команде find Unix, которая фактически и выполняет поиск файлов. Вы можете использовать любое выражение в поле TYPE, если оно соответствует синтаксису find.

Итак: все файлы, которые были изменены в последние 24 часа, обнаруживаются в списке FROM с помощью описателя TYPE и копируются в область TO.

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

     /usr/russ/bin    /pack1/russ/.bkp/bin       *
     /usr/russ/doc    /pack1/russ/.bkp/doc       *
     /usr/russ/src    /pack1/russ/.bkp/src       *.c
     /usr/product1    /pack1/russ/.bkp/product1  *.[ch]

Эти строки копируют каталоги bin, doc и src на локальной машине автора. В случае каталога src мы указали, что копировать нужно только исходные файлы на языке Си. Будет также скопирована некоторая полезная информация из другого места этой же системы. Будут скопированы только файлы с расширением *.c и *.h.

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

ИСПОЛЬЗОВАНИЕ cron

Теперь, когда процедура autobkp знает, что искать, давайте скажем ей, когда искать. Cron, вечный резидентный хранитель времени, может легко выполнить эту работу. Входные данные для cron обычно устанавливаются системным администратором (или кем-либо, кто имеет права записи в/usr/lib/crontab), так что вы должны попросить администратора установить для вас вход в файл данных cron. Для получения дополнительной информации о входных данных cron, прочтите cron(1M) в Руководстве администратора. Коротко говоря, полями в файле /usr/lib/crontab являются минута, час, день месяца, месяц и день недели. Используя *, мы можем установить принудительно многие из этих полей во все возможные значения. Входные данные для cron, копирующие мой регистрационный каталог в4.00 утра каждый день каждой недели каждого месяца года, выглядят так:

0 4 * * * /usr/russ/bin/autobkp.cron Обратите внимание, что вход в cron вызывает управляющую процедуру вместо того, чтобы непосредственно использовать autobkp. Имеется несколько важных причин, чтобы написать процедуру на базе утилиты autobkp. Во-первых, cron не печатает диагностическую информацию на ваштерминал, поэтому если что-нибудь идет не так, вы никогда об этом не узнаете. Во-вторых, проще поддерживать усеченную версию autobkp, а звуковые предупреждения добавлять в управляющую процедуру. Вы можете сделать собственные модификации управляющей программы и не беспокоиться об отсутствии сообщений от самой утилиты. Управляющую программу можно настолько усложнить, насколько вы желаете. Представленная здесь вполне работоспособна, но легко может быть дополнена.

     # Cron-driven autobkp driver
     echo "backed up: `date`" > /dev/tty00
     /usr/bin/autobkp < /usr/russ/bin/autobkpath
                      >> /usr/russ/bin/autobkp.log

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

ПРИМЕРЫ
       1. $ autobkp

Запускает программу без передачи ей файла со списком маршрутов и без файла протокола. Поскольку поля FROM, TO, TYPE ищутся в стандартном вводе, введите их вручную. Когда вы нажмете возврат каретки, autobkp выполнит указанные действия, напечатает информацию на экран терминала и будет ожидать дальнейшего ввода. Для завершения выполнения командного файла введите ^d (в результате оператор read вернется с ненулевым статусом).

       2. $ autobkp < pathlist

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

       3.  $ autobkp >> logfile

Как и в первом случае, списки маршрутов должны быть введены с клавиатуры. Все выходные данные выводятся в файл протокола, а не на экран. Для завершения autobkp введите ^d.

      4. $ autobkp -c < pathlist >> logfile

Копирует файлы из одной области жесткого диска в другую (определенную каталогом-приемником в файле pathlist). Берет все входные данные из файла pathlist и выводит все выходные данные в файл logfile.

ПОЯСНЕНИЯ

Строки 4-8 выполняют проверку на наличие ошибок. Autobkp може тбыть вызван либо без указания опций, либо с одной опцией (-c, при использовании cp). Вспомните, что переназначение ввода-вывода НЕ принимается во внимание при рассмотрении аргументов, потому что командный процессор интерпретирует символы переназначения и то, что следует за ними, до вызова команды. Таким образом, если количество позиционных параметров больше одного (#1 -gt 1), получаем ошибочное условие. Затем выдается сообщение об ошибке и синтаксическая подсказка и программа завершается.

В строках 10-13 проверяется использование ключа -c. Обратите внимание, что мы не проверяем, равен ли параметр $# единице и не пытаемся выделить первый символ, чтобы посмотреть, равен ли он "-". Это потому, что такая проверка приведет к ошибке, если не указан никакой ключ (что является верным синтаксисом, как указывалось ранее).

Если мы сказали

      if [ $1 = -c ]

и не указали ключей, то команда проверки не сработает и будет выдано сообщение о том, что "no argument in the statement" ("в операторе нет аргументов"). Но если мы выполним экранирование, например, так:

      if [ "$1" = "-c" ]

то кавычки допускают нулевое значение аргумента, так что проверка правильно оценит недостающее значение $1 как "равен ли нуль -c?" Это даст результат "ложь", поэтому все хорошо.

Попутно давайте внимательно рассмотрим работу команды проверки. Вы можете выполнить проверку значения двумя способами. Первый - сравнение строк, а второй - числовое сравнение. Переменные командного процессора ВСЕГДА хранятся в виде строк. Вы можете, тем не менее, заставить систему рассматривать эти последовательности как числа и интерпретировать их значения как числовые, подобно оператору number = val(STRING$) языка Бейсик. Вы можете сказать системе, чтобы она изменила свой способ рассмотрения символьных строк путем изменения синтаксиса операции сравнения. Для символьных строк сравнение выглядит так:

        str1 = str2

а числовое сравнение выглядит так:

        num1 -eq num2
             -lt
             -gt

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

Вернемся к возможности проверки кода. Если был передан ключ -c, переменная COPY устанавливается, что значит "Да, мы собираемся копировать командой cp, а не использовать uucp". Если ключ -c не используется, переменная COPY не устанавливается.

В строках 15-16 печатается заголовочное сообщение о том, что будет выполняться копирование. Обратите внимание, что мы спрятали команду date системы UNIX внутри оператора echo, сократив число перехваченных данных, которые мы должны иметь, чтобы получить дату непосредственно.

Проследите за кавычками в этом операторе. Внешние кавычки являются двойными для того, чтобы упаковать весь аргумент для оператора echo. Знаки ударения (`) обрамляют команду date так, что она является "выполняемой внутри" и ее выходное сообщение перехватывается для наших нужд. Одинарные кавычки внутри команды date используются для передачи формата, который изменяет внешний вид значений так, чтобы заголовок выглядел более красиво. В конце оператора echo кавычки следуют одна за другой.Это не представляет проблемы, поскольку во вложенности нет никакой двусмысленности. Вы должны помнить, что нужно следить за ситуацией, когда вы и командный процессор можете расходиться во мнениях, т. е., когда вы должны обращаться к записи вида "\".

В строке 18 переменной SYSTEM присваивается имя удаленной системы, в которую вы будете копировать командой uucp. Здесь она равна нулю, что позже вызовет выполнение другой операции для обеспечения функционирования по умолчанию. Если же вы хотите всегда копировать на вполне определенную систему, модифицируйте эту строку, чтобы назначить имя этой системы. Если оставить строку 18 так, чтобы она назначала ноль, строка14 поймает это значение и присвоит переменной SYSTEM имя вашей текущей системы. Другими словами, если вы оставите строку 18 так, как она есть, и вызовете autobkp без ключа -c, вы будете копировать командой uucp сами на себя, что вполне допустимо. Однако, из соображений эффективности вы, вероятно хотели бы выполнить autobkp -c для получения локальной копии.

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

Первый символ - это ":". В данном случае мы интересуемся, что происходит при проверке, а не возвращаемым значением, поэтому мы заставили холостую команду ("не делать ничего") получать результат как аргумент. Текст, следующий за двоеточием, интерпретируется так: "Если переменная SYSTEM не установлена или установлена в ноль, присвоить ей значение, которое следует за ней". В данном случае значение - это выход команды uuname -l. Эта команда устанавливает, что система-приемник является той же системой, что и исходная, если система-приемник не была прямо указана ранее.

Мы используем uuname -l, а не стандартное выражение uname -n по причине совместимости. Uname -n правильно получает имя узла из структуры uts ядра операционной системы, но не все системы XENIX используют элемент узла в виде структуры uts ядра системы. Вместо этого они посылают имя в файл /etc/systemid, который соответствует микросети(micnet), разработанной для XENIX фирмой Microsoft. Команда uuname -l это локальное имя (или исходная машина) для системы uucp. Эта команда возвращает правильное значение и в UNIX, и в XENIX. Имеет смысл использовать то, что всегда работает!

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

Строки 23-41 - это полный цикл, который управляет автоматическим копированием. Управляющим циклом является оператор while, который читает значения из стандартного ввода. Заметьте, что вы можете считать несколько значений в операторе read. Это удобно, если вы хотите читать более одного значения, но не должны выделять каждую порцию входных данных для того, чтобы определить, является это первым, вторым или третьим элементом данных. Мы читаем их все сразу и они присваиваются указанным переменным. Поскольку выполняется чтение стандартного ввода, мы можем перенаправить stdin при вызове autobkp и оператор read никогда не узнает, чем они отличаются. Если мы не переназначаем входные данные, мы должны вводить их с клавиатуры. Цикл завершается при чтении конца файла- в данном случае конец файла со списком маршрутов или символа control-d (^d) с клавиатуры. Поэтому управляющий цикл работает так: "пока еще есть данные для чтения, читать их, обрабатывать, затем читать следующие."

Строки 25-28 проверяют, является ли каталог-источник действительно каталогом. Если нет, выдается сообщение об ошибке и оператор continue приводит к следующей итерации цикла while.

В строке 30 производится смена каталога на каталог-источник. Вотпочему выходные данные команды find являются относительными к точке (.). Если бы мы не выполнили команду cd, то полное имя стало бы абсолютным, что могло бы отразиться на системе-приемнике. Тогда маршрут, начинающийся с каталога-приемника, имел бы вниз от себя лишний абсолютный путь.

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

Строки 33-40 выполняют непосредственно копирование файлов. Здесь циклом является цикл for, который читает имена файлов из выхода команды find. Заметьте, что это автоматически ограничивает общее число файлов, которые может обрабатывать цикл. Этот факт ранее был объяснен в этой книге, но давайте рассмотрим его еще раз. Если find выдает список, состоящий из сотен файлов, то список слов оператора for переполняется и нарушает работу вашего командного процессора (или по крайней мере команды find). Здесь принято допущение, что вы не хотите иметь так много файлов в исходном каталоге. Вы можете избежать этого, разбивая исходный каталог на более мелкие части и пересылая их в файл pathlist. Если вы хотите создать действительно хороший цикл, измените его, например, так:

      find . -type f -ctime 0 -name "$FILES" -print | while read FILE

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

Давайте рассмотрим детально команду find. Во-первых, мы указали ей поиск файлов в текущем каталоге (.). Это делает все полные имена относительными по отношению к точке. Затем мы сказали команде find найти все файлы типа f, что означает обычные файлы, а не каталоги или файлы устройств. Мы не хотим копировать такие файлы. Дальше мы говорим ей найти файлы, которые были изменены. Под "изменением" мы подразумеваем доступ или модификацию. (Посмотрите в описании stat(2), какие команды изменяют доступ, изменяют и модифицируют время. Говоря "делать поиск для нахождения "ctime 0"", мы имеем в виду все файлы, измененные за последние 24 часа. Объяснения, которые документация по find дает по поводу этих чисел, довольно непонятны, поэтому отнеситесь к ним с недоверием.) Затем мы говорим команде find "найти только те файлы, которые определены путем соответствия их имен маршрутным именам, указанным в переменной $FILES, значение которой мы читаем". В этом месте мы можем отфильтровать файлы, которые нам не нужны (как объяснялось предварительно) или выбрать файлы, которые нам нужны. В конце мы говорим команде find "напечатать имена всех файлов, которые соответствуют перечисленным критериям". Затем имена файлов передаются в цикл for. Другими словами, выходные данные команды find становятся аргументом для охватывающего цикла for.

В строках 35-38 оператор case определяет, какого рода копирование мы собираемся делать, и запускает команды копирования. Если переменная COPY не установлена, мы копируем файлы командой uucp. Обратите внимание, что местом назначения является SYSTEM. Если мы оставили SYSTEM в нуле в строке 18, то SYSTEM - это наша собственная система и мы копируем командой uucp файлы к себе. Если COPY установлена, то независимо от значения SYSTEM мы копируем (но не командой uucp) файлы в другой каталог текущей системы. Этот каталог может быть на том же жестком диске или в другой смонтированной файловой системе. После того, как файл скопирован, выдается сообщение, которое говорит о том, какой файл и куда был передан. Удобно иметь в файле протокола эту информацию, поскольку мы имеем возможность проследить, куда были пересланы ваши скопированные файлы.

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

НЕКОТОРЫЕ ОСОБЕННОСТИ uucp

Когда используется uucp, в маршруте приемника должен быть установлен бит разрешения выполнения ("x") для группы "others" (остальные) для всех промежуточных каталогов, ведущих к файлу. Это будет выглядеть так:

         --------x

Самый последний каталог должен иметь права доступа вида "wx", чтобы uucp могла писать файл в каталог. После этого владельцем файла считается uucp. Если собственником файла хотите быть вы, скопируйте его (используя cp, а не mv) с другим именем и он будет вашей собственностью. Если вы переименуете его командой mv, вы только измените имя, связанное с тем же индексным описателем файла (inode). Но если вы скопируете его командой cp, вы создадите новый отмеченный описатель файла. Этот новый описатель файла (созданный вами) имеет ваши идентификатор пользователя (uid) и идентификатор группы (gid), поэтому вы владеете им. Если вы находитесь в корне системы и копируете файл (используя cp, а не mv) поверх другого существующего файла, информация в описателе файла не изменяется, а меняются только данные, доступ к которым указывает описатель файла.

Когда uucp устанавливает предшествующие права доступа к файлу на всех промежуточных каталогах такими, что все имеют право записи, последний каталог НЕ будет иметь защиты. Предоставление любому пользователю права записи означает, что кто угодно может удалить или изменить файлы в этом каталоге. Не каждый хочет давать всем это право. Если же вы копируете файлы в обычную область команды uucp общего доступа (/usr/spool/uucppublic/$LOGNAME), то вы должны внимательно следить за ними. Многие системы имеют запускаемые с помощью cron программы, производящие в данном каталоге поиск файлов, к которым не было доступа в течение определенного количества дней, и удаляют такие файлы - это вредит вашим копиям. Если период хранения больше, чем промежуток между вашим копированием, у вас может быть все в порядке. Как и многое другое, это зависит от ваших обстоятельств и требований безопасности.

УСОВЕРШЕНСТВОВАНИЯ

В оригинале файл со списком маршрутов имеет аргумент TYPE в конце аргумента FROM, например /usr/russ/bin/*. Это представляет проблему (кроме того, что показывает, что ваш автор еще не является мастером!), потому что когда символ * будет выделен, он будет расширен в имена всех файлов вместо того, чтобы трактоваться как литеральный символ. Простое решение - использовать отдельные поля, что и было сделано. Мастерским решением является экранировать метасимвол для сохранения его как литерального символа. Как только символ * будет выделен из маршрутного имени, символ \ представит его в виде * вместо того, чтобы дать его на расширение. Например, можно написать так:

TYPE=`basename \"$FROM"` Здесь символ * присваивается переменной TYPE, вместо того, чтобы присвоить TYPE список всех файлов, которые соответствуют метасимволу. Затем, когда будет вызвана команда find, переменная TYPE должна быть экранирована так, чтобы метасимвол интерпретировался не командным процессором, а самой командой find.


ГЛАВА 3. Поддержка файловой системы

3.3.2. cpiobr - копирование и восстановление файлов в виде потока данных

         ИМЯ: cpiobr

cpiobr Копирование и восстановление в виде потока данных командой cpio

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
         cpiobr
ПРИМЕР ВЫЗОВА

cpiobr Вызывает главное меню для копирования, восстановления или выдачи списка файлов

КОМАНДНЫЙ ФАЙЛ CPIOBR
    1   :
    2   # @(#) cpiobr v1.0  Cpio stream backup and restore
     Author: Russ Sage

    4   if [ "$#" -gt "0" ]
    5      then echo "cpiobr: too many arguments"
    6           exit
    7   fi

    9   while :
    10  do
    11            c
    12            set `date`
    13            echo "

    15  $1, $2 $3                  $4

    17         Cpiobr Backup & Restore
    18         -----------------------
    19     Backup to removable media
    20     Restore from removable media
    21     List files on media
    22     Long list files on media
    23      to exit

    25     Press b,r,f,l or : \c"

    27         read CMD
    28         if [ "$CMD" = "" ]
    29           then break
    30          fi

    32          ABORT=off

    34          while :
    35          do
    36                      echo "

    38     Enter media type:
    39        Raw System V floppy drive (/dev/rfp021)
    40        Raw XENIX floppy drive    (/dev/rfd0)
    41        Tape drive                (/dev/rmt0)
    42        Any device                (/dev/???)
    43         to exit

    45        Press s,x,t,a, or : \c"

    47                      read MEDIA
    48                      case $MEDIA in
    49                      s|S)    DEV=/dev/rfp021
    50                              break;;
    51                      x|X)    DEV=/dev/rfd0
    52                              break;;
    53                      t|T)    DEV=/dev/rmt0
    54                              break;;
    55                      a|A)    echo "enter full pathname
     (or <> to exit): \c"
    56                              read DEV
    57                              if [ "$DEV" = "" ]
    58                                then continue
    59                                else break
    60                              fi;;
    61                     "")    ABORT=on
    62                              break;;
    63                     *)       echo "cpiobr: invalid
     command \"$MEDIA\"";;
    64                     esac
    65           done # while get media

    67           if [ "$ABORT" = "on" ]
    68             then continue
    69           fi

    71           case $CMD in
    72           b|B)   echo "\nEnter the source directory name: \c"
    73                  read SRC
    74                  cd $SRC
    75                  echo "\nPlace floppy in drive and hit  ...\c"
    76                  read CMD
    77                  find . -print | sort | cpio -ocBv > $DEV
    78                  echo "\nhit \c"
    79                  read CMD
    80                  ;;
    81           r|R)   echo "\nEnter the destination directory name: \c"
    82                  read DEST
    83                  cd $DEST
    84                  echo "\nPlace floppy in drive and hit  ...\c"
    85                  read CMD
    86                  cpio -icBvdmu < $DEV
    87                  echo "\nhit \c"
    88                  read CMD
    89                  ;;
    90           f|F)   cpio -icBt < $DEV
    91                  echo "\nhit \c"
    92                  read CMD
    93                  ;;
    94           l|L)   cpio -icBtv < $DEV
    95                  echo "\nhit \c"
    96                  read CMD
    97                  ;;
    98           *)     echo "cpiobr: invalid command \"$CMD\""
    99                  ;;
    100          esac
    101 done
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ABORT | Флаг, определяющий, делать ли аварийное прекращение
CMD | Команда, получаемая от пользователя
DEST | Каталог-приемник при восстановлении
DEV | Маршрутное имя устройства носителя
MEDIA | Хранит тип устройства, которое будет использоваться
SRC | Каталог-источник при копировании
ОПИСАНИЕ

Зачем нам нужен cpiobr?

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

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

Что делает cpiobr?

Cpiobr - это управляемая с помощью меню интерактивная утилита копирования и восстановления. На самом деле это интерфейс с командой cpio системы UNIX. Функции, предоставляемые меню, включают копирование файлов с жесткого диска на гибкий диск, восстановление файлов с гибкого диска на жесткий диск, выдачу списка имен файлов, хранимых на гибком диске и выдачу списка файлов с необязательной дополнительной информацией (подобно ls -l). Гибкий диск здесь является первичным устройством назначения, но могут использоваться и другие носители, такие как магнитная лента большой емкости или кассетная магнитная лента (streamer). После выбора типа операции, которая должна быть выполнена, нужно выбрать тип используемого устройства. Утилита Cpiobr может быть использована на устройствах системы UNIX фирмы AT&T (/dev/fp021), устройствах системы XENIX фирмы IBM (/dev/fd0), стримерной ленте (/dev/rmt0) или любом другом устройстве по вашему желанию (/dev/???). Обычно имя устройства определяет тип используемого носителя. Поскольку эта утилита предназначена для всех машин UNIX, некоторые из вариантов могут отсутствовать в вашей машине, поэтому вы имеете право выбрать любое имя устройства, которое вам необходимо.

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

Когда файлы копируются на желаемый носитель, маршрутное имя, переданное cpio, начинается с "/.". Это означает, что никакого префикса имени каталога на гибком диске нет. Поэтому при восстановлении файлов обязательно нужно дать полное маршрутное имя. Все файлы, поступающие с гибкого диска, будут помещены прямо в каталог-приемник, который вы указали cpiobr.

ПРИМЕРЫ

(Здесь приводятся ответы на запросы главного меню, подменю и дополнительная информация, появляющиеся в таком порядке.)

     1.  b
         x
         $HOME

Копирует файлы на гибкий диск системы XENIX, начиная с каталога $HOME.

     2.  r
         a
         /dev/rmt0
         $HOME

Восстанавливает файлы с устройства, выбранного мной (/dev/rmt0, магнитная лента), и помещает файлы в мой регистрационный каталог.

     3.  l
         s

Выдает в широком формате информацию обо всех файлах, размещенных на гибких дисках системы UNIX машины типа PC.

ПОЯСНЕНИЯ

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

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

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

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

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

Начиная со строки 11, мы устанавливаем экран для главного меню. Командой здесь является "c", что будет пояснено позже в этой книге. Она соответствует "очистке экрана" и может быть заменена стандартной командой очистки системы UNIX, которую вы можете использовать в этом месте, если хотите.

Строка 12 устанавливает в позиционные параметры выходные данные команды date системы UNIX. Такой синтаксис достаточно редко встречается, но тем не менее очень полезен. Если бы мы не хотели делать это таким образом, мы бы должны были перехватить все выходные данные команды date в одной переменной, затем разделить их на мелкие порции и поместить каждую порцию в отдельную переменную. Это потребовало бы намного больше команд и переменных в программе. Используя наш синтаксис, мы заставляем первый позиционный параметр быть первым полем выходных данных команды date, второй позиционный параметр быть вторым полем и так далее. Для получения любого указанного поля мы используем запись вида $n, где n есть номер позиционного параметра.

Строки 13-25 - это один огромный оператор echo, который печатает главное меню. Выдача всего необходимого одним оператором echo предпочтительнее, поскольку это минимизирует накладные расходы, с которыми приходится сталкиваться при выполнении большого числа операторов. Такой путь быстрее. Если бы мы использовали оператор echo для каждой строки главного меню, то оно печаталось бы очень медленно и прерывисто. Команда UNIX cat также могла бы быть применена для этого случая, используя здесь документы (вставленный текст). В качестве примера этого может служить следующее:

        cat <<-EOF
            Main Menu Information
        EOF

Однако главная проблема возникает, когда вы печатаете приглашение. Для того, чтобы курсор ожидал ввода в конце строки приглашения, необходимо выдать на терминал символ "\c", а cat не может сделать этого. Вы выводите на экран меню с помощью cat, и echo печатает приглашение, которое направляется на другую сдвинутую строку полностью заполненного экрана. Постоянство и скорость - вот чего мы добиваемся. Обучение таким трюкам еще больше поможет вам при написании программ большого размера, которые используют множество меню и другие текстовые выводы на экран. Использование оператора echo в таком виде имеет некоторые недостатки, но они совершенно тривиальные. Во-первых, тело оператора echo должно содержать все, что вы хотите вывести на экран, и это требует абсолютного позиционирования внутри оператора echo для получения символов пробела в нужных местах. Обычно при таком позиционировании имеется сдвиг строк в тексте программы, поэтому визуально в том месте командного файла, где выводится меню, появляется этот сдвиг, но после меню строки снова идут ровно. Это может немного смущать при чтении текста программы.

Другой несущественной деталью является то, что оператор echo не любит выводить символы табуляции. Если же вы вставили символ табуляции внутри кавычек оператора echo, он обычно выводится как пробел. Для того, чтобы заставить echo выводить символы табуляции, вы должны сказать это оператору echo на его собственном языке с помощью символов "\t" или \\t, если без кавычек. Поэтому меню в cpiobr заполнено символами пробела. Это также позволяет легко сдвигать меню влево и вправо при помощи небольшого количества пробелов для соответствующего позиционирования на экране.

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

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

    |    Среда, май 28                          13:18:49
    |
    |          Cpio - Сохранение/восстановление файлов
    |          ---------------------
    |       Копирование данных
    |       Восстановление данных
    |       Список файлов на носителе
    |       Полный список файлов на носителе
    |       <ВК> для выхода
    |
    |       Нажмите b,r,f,l, или <ВК>:

В левом верхнем углу расположен день недели, месяц, день месяца. Это поля 1, 2 и 3 команды date. В правом верхнем углу расположено текущее время. Это поле 4 команды date. Все эти данные приводятся для того, чтобы меню на экране смотрелось красиво, было равномерно заполнено и информативно.

После того, как меню выдано на экран, строка 27 читает команду пользователя. Заметим, что один из ключей вызывает завершение программы, если был нажат только возврат каретки. Каким образом мы проверяем это? Мы заключаем в кавычки входную переменную таким образом, что проверка распознает нулевое значение (см. строки 28-30). Если был введен ноль, мы выходим из текущего цикла while. Тем самым мы попадаем в конец программы, которая после этого завершает выполнение. Если входное значение не было равно нулю, мы продолжаем и выполняем в следующей команде проверку на наличие ошибки.

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

В строках 34-65 разместился вложенный цикл while, который обрабатывает подменю. Причина, по которой мы использовали циклы типа "вечный while" заключается в том, что они выполняются, пока не получат нужное входное значение. Как только получены правильные входные данные, мы выходим из цикла. Это очень простой способ обработки меню.

В строках 36-45 мы снова используем оператор echo для выдачи на экран полного подменю. Это меню запрашивает имя устройства, которое используется в командах копирования/восстановления.

Строка 47 читает входные данные от пользователя в переменную MEDIA. Значение переменной MEDIA затем оценивается в операторе case. Обратите внимание, что шаблоны сравнения включают символы и в верхнем, и в нижнем регистре. Это облегчает жизнь пользователя и делает немного более логичным программирование, уменьшая число проверок на ошибки, которое мы должны произвести. Также заметьте, что каждый образец заканчивается оператором break. Когда обнаружено допустимое входное значение, мы желаем продолжать выполнение после конца оператора while, что осуществляется оператором break. Переменная DEV теперь установлена как маршрут к выбранному устройству.

Ключ "a" в строках 55-60 требует дальнейшей обработки. Пользователь запрашивается об имени устройства, которое он выбрал. Если пользователь забыл имя или решил не использовать этот ключ, он может ввести возврат каретки, который распознается как нуль и приводит к выполнению оператора continue. Это вызывает выполнение следующей итерации текущего цикла, которая снова выводит подменю. Еще один возврат каретки после этого может использоваться для выхода из подменю и возврата в главное меню. В противном случае, если пользователь ввел ненулевое значение, выполняется оператор break, и цикл меню завершается, имея маршрут, указанный в переменной DEV.

Если пользователь ввел неверное значение, печатается сообщение об ошибке и подменю выводится снова. Заметьте, что ввод только возврата каретки в подменю устанавливает переменную ABORT в "on". Почему это так? Теперь мы подошли к той части, где язык командного процессора неприменим для нашего случая. Сценарий выглядит примерно так. Мы находимся в подменю. Мы решаем, что не будем здесь производить выбор, поэтому мы хотим выйти из подменю и вернуться в главное меню (или в предыдущее меню). Если мы выйдем из цикла while подменю, мы попадем во внешний цикл while и продолжим обработку запросами о каталоге-источнике и каталоге-приемнике.

Если мы попытаемся решить эту проблему путем использования оператора "break 2", мы выйдем из обоих циклов while (попадая в самый низ программы) и программа завершится, ничего не сделав для нас. Снова не то, что мы хотим. Что мы действительно хотим, так это выйти из текущего (внутреннего) цикла и продолжить следующую итерацию во внешнем цикле для получения главного меню. Нет никакой возможности сказать командному процессору об этом, поэтому мы создали переменную в качестве флага для имитации этого действия и назвали ее ABORT. Если мы устанавливаем переменную ABORT в состояние "да", то мы НЕ желаем продолжать работу с командой главного меню, а хотим прекратить ее и вернуться в главное меню. На самом деле это означает продолжить, поэтому в строках 67-69 проверяется именно это. Если флаг ABORT установлен, подменю принудительно завершается и оператор continue заставляет снова печатать главное меню вместо того, чтобы пытаться выполнить какую-то наполовину определенную операцию копирования.

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

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

В случае копирования для поиска ВСЕХ файлов, размещенных в дереве файлов, начиная с текущего каталога, используется команда find. Оператор find выдает отсортированный список файлов, поэтому файлы на носителе с копией отсортированы. Затем отсортированный список файлов передается по каналу команде cpio с опциями -ocBv. Это означает: "потоковый вывод, использовать символьные заголовки, блоки размером по 5K, с выдачей сообщений". При этом печатаются имена файлов по мере того, как они копируются на носитель.

В случае операции восстановления используются ключи -icBvdmu. Это значит "потоковый ввод, использовать символьные заголовки, блоки по 5К, с выдачей сообщений для печати имен файлов по мере их восстановления, создавать каталоги при необходимости, файлы сохраняют исходную дату модификации, и все файлы безусловно копируются".

Если должен быть выдан только список файлов, то ключами будут или -icBt для печати таблицы скопированных файлов (это соответствует записи команды cpio "ls"), или -icBtv для печати таблицы файлов с более подробной информацией ("ls -l" в записи для cpio).

В конце выполнения каждой команды главного меню выдается сообщение

       hit            (Нажмите <ВК>)

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

Замечания по операции копирования

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

Например, вы можете сказать ей, какой множитель блокировки вы используете и насколько велик образ копии, так что вы можете разбить большие копируемые потоки данных на много мелких частей (например k=360 для гибких дисков низкой плотности в системе XENIX). Одним из странных аспектов команды tar является то, что копия является одним длинным и непрерывным потоком, а при восстановлении используется уникальный формат для каждого носителя. Например, вы должны, скажем, скопировать 10M данных. Вам лучше иметь достаточно отформатированных гибких дисков и приготовить их до того, как вы начнете копировать командой tar. Когда дискета заполнится, вы должны прервать выполнение команды и затем снова продолжить. Пример такой команды мог бы выглядеть так:

        cd $HOME
        tar cvefbk /dev/fd048ds9 18 360 .

Здесь указано, что требуется скопировать ВСЕ файлы (рекурсивно обходя дерево сверху вниз) из текущего каталога (.) в файл на указанном устройстве, со множителем блокировки 18 K и размером образа копии 360 Кбайт. Одним из интересных аспектов здесь является то, что когда команда tar рекурсивно проходит вниз по дереву, она получает имена файлов в порядке расположения описатель файла, что обычно НЕ совпадает с отсортированным порядком. Вы НЕ МОЖЕТЕ получить отсортированный список файлов для копирования командой tar, если только не сделаете еще одну точную копию всех данных, которые вы хотите скопировать и не разместите описатель файла в отсортированном порядке. Как это сделать? Вот так:

        cd $HOME
        find . -print | sort | cpio -pdv /bkpsort

При этом получится новая копия всех ваших данных и имена файлов разместятся в отсортированном порядке. Если после этого вы перейдете в каталог /bkpsort и выполните команду tar, файлы будут перенесены на носитель в отсортированном порядке.

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

        tar xvf /dev/fd048ds9

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

Команда cpio является следующим поколением команд копирования. Ее общие функции подобны функциям команды tar, но имеется несколько важных отличий. Во-первых, вы должны сгенерировать список файлов для cpio, что означает использование команды find для порождения списка. Поскольку мы можем конвейером пропустить список, полученный от команды find, через команду sort, нам нет необходимости делать еще одну копию всех наших файлов для получения отсортированного списка.

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

Одно существенное различие между командами cpio и tar заключается в получающемся образе копии. Поскольку cpio не различает границ между различными носителями (дискетами), файлы получаются разорванными между двумя гибкими дисками. Это значит, что когда вы пытаетесь копировать с этих 15 дискет, то они являются ОДНИМ непрерывным потоком входных данных, точно так, как и последовательный поток выходных данных при создании этой копии. Что произойдет, если дискета номер 2 повредится? ВСЕ ваши файлы после второй дискеты стали бесполезны. Вы просто потеряли весь ваш образ копии. Поскольку tar копирует в виде отдельных образов, когда дискета номер 2 потеряется, вы все равно можете копировать с дискет 3-15 без проблем.

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

Как же управляется с этим система 4.2 BSD? В течение многих лет применялась команда tar для пересылки туда и обратно, как описано на страницах руководства по tar(1). Не самый элегантный подход, но работоспособный. Сейчас они имеют ключ -r (для рекурсивного обхода дерева сверху вниз) для обычной команды cp. Я же по-прежнему считаю, что команда cpio лучше.

3.4. Средства проверки операций копирования

3.4.1. dsum - контрольные суммы двух каталогов

        ИМЯ: dsum

dsum Контрольная сумма двух каталогов

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
         dsum [-c|-o] control_dir backup_dir
ПРИМЕР ВЫЗОВА
         dsum $HOME/bin /mnt

Просматривает, были ли какие-либо файлы изменены при копировании из моего регистрационного каталога на гибкий диск, смонтированный в каталоге /mnt.

КОМАНДНЫЙ ФАЙЛ DSUM
    1   :
    2   # @(#) dsum v1.0  Dual directory sum  Author: Russ Sage

    4   if [ $# -lt 2 -o $# -gt 3 ]
    5     then echo "dsum: invalid argument count"    >&2
    6          echo "usage: dsum [-c|-o] control_dir backup_dir" >&2
    7          echo "  -c = C source files, -o = object files"   >&2
    8          exit 1
    9   fi

    11  case $# in
    12  2)      FLIST=*;;
    13  3)      case $1 in
    14          -c)   FLIST=*.c;;
    15          -o)   FLIST=*.o;;
    16          *)    echo "dsum: invalid argument $1" >&2
    17                echo "usage: dsum [-c|-o] control_dir
     bacup_dir"  >&2
    18                exit 1;;
    19          esac
    20          shift;;
    21  esac

    23  for FILE in $1/$FLIST
    24  do
    25          BASEF=`basename $FILE`
    26          if [ `expr $BASEF : '.*'` -lt 7 ]
    27            then echo "`$BASEF:  \t'`sum $FILE | cut -d' '
      -f1`\t\c"
    28          else echo "$BASEF:\t`sum $FILE | cut -d' '
       -f1`\t\c"
    29          fi
    30          sum $2/$BASEF | cut -d' ' -f1
    31  done
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
BASEF | Содержит базовое имя файла из полного маршрутного имени
FILE | Содержит имя каждого проверяемого файла
FLIST | Содержит указание на тип проверяемых файлов
ОПИСАНИЕ

Зачем нам нужен dsum?

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

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

Одним из способов слежения за копиями является использование команды sum. Эта команда читает данные и выводит число, являющееся разновидностью контрольной суммы. Другими утилитами UNIX, которые делают что-то подобное, являются cmp для сравнения объектных файлов и diff для обнаружения различий в текстовых файлах.

Автор привык думать, что sum будет сообщать об отличии даже в одном бите (своего рода циклическая избыточная проверка), но это оказалось совсем не так. Недавно имелся 35 Кбайтный файл, содержащий в виде длинного формата список файлов, которые должны были быть скопированы. В действительности, там были два файла, один из которых был отсортирован, а другой нет. Они были одного размера, и sum выдала одно и то же число для обоих файлов. Когда же cmp сравнила эти два файла, оказалось, что 39-е байты отличаются. Как мы можем объяснить тот факт, что sum рассматривала эти два файла как совершенно одинаковые? Возможно, sum свернула эти два файла таким образом, что контрольная сумма оказалась одинакова, даже хотя один из файлов был отсортирован, а другой нет. Это значит, что sum на самом деле не выполняет контрольную проверку каждого бита. Только проверка алгоритма работы программы в исходном модуле позволит убедиться в этом. Конечно, в большинстве случаев, если файл отличается от оригинала, то это не является простой перестановкой данных, так что sum все-таки полезна.

Что делает dsum?

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

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

Альтернативой для dsum может быть выполнение какого-либо сценария, подобного приводимому ниже.

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

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

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

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

ПРИМЕРЫ
     1.  $ mount /dev/fd0 /mnt
         $ cp /usr/include/* /mnt
         $ dsum /usr/include /mnt

Монтирует гибкий диск в каталог /mnt. Копирует все файлы заголовков в каталоге /usr/include на гибкий диск. Проверяет копии, используя dsum для исходного каталога и для каталога с копией.

Примечание: Указывая копировать *, мы вообще не попадем в каталог /usr/include/sys.

     2.  $ dsum . ..

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

ПОЯСНЕНИЯ

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

В строках 11-21 производится инициализация переменной FLIST. FLIST - это управляющая переменная, которая определяет имена файлов, на которые надо обратить внимание. Если в командной строке указаны только имена каталогов ($# = 2), FLIST присваивается значение по умолчанию * (все файлы) в строке 12. Значение * присваивается переменой FLIST и не трактуется в это время как метасимвол (это особенность командного процессора). Если в командной строке указан ключ ($# = 3), производится проверка первой переменной и FLIST получает соответствующее значение, *.c или *.o. Если указана не такая опция, выводится сообщение об ошибке и программа завершается.

В строках 23-31 выполняется сама работа. Здесь выполняется цикл for, который проходит по списку слов, созданному управляющим каталогом в соответствии со значением переменной FLIST. В строке 23 переменная FLIST расширяется фактически с символа * в имя каждого файла. Тем самым цикл for получает данные для использования. Следовательно, переменная FLIST является полным маршрутным именем каждого файла в управляющем каталоге.

Строка 25 разбирает расширение, сделанное в строке 19. Переменная BASEF получает базовое имя полного маршрута из переменной FILE. Причиной, по которой мы это делаем, является тот факт, что позже при ссылке на каталог копии нам необходимо только имя файла. (В системе UNIX команда basename возвращает последний элемент в указанном маршруте, т.е. само имя файла, если маршрут содержит промежуточные каталоги.)

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

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

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

    |    4243 3 autobkp
    |    247 1 can
    |    25167 6 cpiobr
    |    186 3 dosflp
    |    56864 2 dsum
    |    2782 1 log
    |

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

Чудо вывода в одну строку совершается в строке 27 (для файлов с именами менее 7 символов) или в строке 28 (для файлов с более длинными именами). Внутри команды echo в каждом случае мы прячем другие команды, но по-прежнему управляем тем, как их результаты выводятся на экран. Во-первых, сами имена файлов выводятся, будучи ранее извлеченными из полного маршрутного имени. Обратите внимание, что имена файлов не содержат информацию о том, из какого они каталога. Затем мы несколько сдвигаемся и печатаем первое поле выходной суммы для этого файла (саму контрольную сумму). Это контрольная сумма версии файла в управляющем каталоге, поскольку переменная FILE была сгенерирована для этого каталога. Команда sum выводит три значения (контрольная сумма, число блоков, занятых файлом, и само имя файла). Нам нужно получить только первое значение, которое извлекается путем выполнения команды sum и передачи ее выхода по каналу команде cut, которая и возвращает первое поле. После того, как значение контрольной суммы напечатано, мы отображаем \c для запрещения перехода на новую строку. Это сохраняет курсор в той же строке.

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

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

ВОЗМОЖНЫЕ МОДИФИКАЦИИ КОМАНДНОГО ФАЙЛА

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

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

     for FILE in $1/$FLIST
     do
             BASEF=`basename $FILE`
             S1=`sum $FILE 2>&1 | cut -d' ' -f1`
             S2=`sum $2/$BASEF 2>&1 | cut -d' ' -f1`
             if [ "$S1" = "$S2" ]
               then M=""
               else M="<---"
             fi
             if [ ` expr $BASEF : '.*'` -lt 7 ]
             then  echo "$BASEF:   \t$S1\t$S2     $M"
             else  echo "$BASEF:\t$S1\t$S2   $M"
             fi
     done

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

3.4.2. log - меню доступа к файлам протокола копирования

         ИМЯ : log

log Меню доступа к файлам протокола копирования

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА
        log
ПРИМЕР ВЫЗОВА
        log
КОМАНДНЫЙ ФАЙЛ LOG
    1   :
    2   # @(#) log v1.0 Menu access to backup logfiles Author: Russ Sage

    4   c
    5   set `date`
    6   echo "

    8   $1, $2 $3       $4

    10               Logfile Menu
    11            ----------------
    12     1 - list all log file names
    13     2 - display log of home backup
    14     3 - display log of product backup
    15      to exit

    17     Enter command (1-3,<>): \c"
    18  read CMD

    20  case $CMD in
    21  "")  exit;;
    22  1)     echo "\nLogfile names:"
    23         sed -n -e "/more/s/^.*more \(.*\);;$/\1/p" $HOME/bin/log;;
    24  2)     more $HOME/bin/autobkplog.home;;
    25  3)     more$HOME/bin/auto2.bkplogm;;
    26  *)     echo "log: $CMD is not a command";;
    27  esac
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
CMD | Команда, полученная от пользователя
HOME | Ваш регистрационный каталог в системе
ОПИСАНИЕ

Зачем нам нужен log?

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

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

Что делает log?

Командный файл log - это управляемая при помощи меню утилита. Меню позволяет вам видеть текущие имена файлов в файлах протокола, поэтому вам нет необходимости помнить их. Остальные команды являются входными точками в файлы протокола, использующими команду more для просмотра.

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

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

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

ПОЯСНЕНИЯ

Строка 4 очищает экран, используя команду c, представленную ниже в этой книге. (Вместо этого вы снова можете использовать команду clear, если она доступна.)

Строка 5 устанавливает в позиционные параметры выход команды date. Это то же самое, что мы делали в программе cpiobr. Строки 6-17 выводят меню. Здесь использован один оператор echo, как описано в cpiobr. Строка 13 читает команду пользователя.

Строки 20-27 выполняют основную работу программы. Если введенная команда была просто возвратом каретки (трактуется как нуль), программа завершается. В строке 23 команда sed просматривает файл $HOME/bin/log. Это требует, чтобы вы поместили log в подкаталоге двоичных модулей вашего регистрационного каталога. Если вы разместите ее где-либо в другом месте, вы должны изменить эту строку. Команда sed использует ключ -n, который запрещает вывод, за исключением того, что явно указано для печати. Строка -e находит имена файлов.

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

Затем мы указываем команде sed сделать замену. Первое выражение содержит в себе всю строку от начала до конца, но мы применяем круглые скобки для отметки внутри нее образца .*, тем самым выделяя часть строки между пробелом после "more" и первой точкой с запятой в конце строки. Если вы посмотрите на все строки в файле log, которые начинаются с "more", то вы увидите, что это соответствует имени файла, которое мы ищем.

Затем мы указываем команде sed заменить всю строку на первый образец "pattern 1". "Pattern 1" - это запись команды sed для первого отмеченного или "отмеченного биркой" выражения. Другими словами, мы заменили имя файла на всю строку целиком и указали команде sed напечатать результат, тем самым выдавая на экран имя файла.

Эта работа выполняется для такого количества операторов more, сколько вы имеете. Чем больше файлов log вы имеете, тем больше файлов обрабатывает команда sed. Обратите внимание, что оператор sed просматривает любое количество символов от начала строки для нахождения слова "more". Не указывая в программе конкретное число символов, на которое нужно отступить, вы получаете тем самым свободу выбора ваших собственных уровней отступа.

Если введенная команда не является допустимой, выдается сообщение об ошибке.

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

ПРИМЕР
       $  log
       1

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

Теперь, когда мы изучили, как распознавать и управлять файлами вообще, давайте рассмотрим некоторые систематические методы управления ИНФОРМАЦИЕЙ в файлах. Мы начинаем в следующей главе с файлов, которые важны для нас как для программистов.


ГЛАВА 4. Управление программной документацией

  • Введение
  • 4.1. Программирование и управление документацией
  • 4.2. Извлечение документирующих заголовков
    • 4.2.1. stripc - из файла на языке Си
    • 4.2.2. stripf - из Си-функции
    • 4.2.3. strips - из командного файла Shell
  • 4.3. ctags - создание файла признаков исходного кода проекта

ВВЕДЕНИЕ

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

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

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

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

4.1. Программирование и управление документацией

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

Этот подход реализуют командные файлы stripc, stripf и strips. Stripc и stripf предоставляют листинги блоков документации уровня файла и уровня функций из ваших исходных файлов на Си, а strips извлекает документацию из командных файлов командного процессора.

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

Ctags объединяет свой выводной файл с редактором vi/ex с целью предоставления простого способа доступа к любой заданной функции и ее просмотра, копирования или редактирования в текущей программе. Ctags делает это путем предоставления признаков, которые понимает vi, для каждой функции, обнаруженной в любом указанном наборе файлов. Таким образом, вы можете использовать простую команду редактора, чтобы получить то, что вам нужно. Вы больше не обязаны заботиться о том, какой файл содержит какую функцию. Ctags - отличный пример применения мощи UNIX в полном объеме.

Имея такие инструментальные средства, вам не нужно изобретать колесо, так как вы можете легко находить и выбирать те средства, которые необходимы вам в конкретном приложении. Вы уже написали программу управления терминалом Trantor TR-101? Примените ctags и найдите ее. Более того, самодокументируемый напечатанный файл и документация о функциях, полученная с помощью этих командных файлов, дают другим программистам хороший старт в понимании того, что вы сделали. Это даже может слегка произвести впечатление на вашего начальника.

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

4.2. Извлечение документирующих заголовков

4.2.1. stripc - из файла на языке Си

      ИМЯ: stripc

stripc Извлекает документирующий заголовок из исходного файла на языке Си.

НАЗНАЧЕНИЕ

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

ФОРМАТ

     stripc файл [...]

ПРИМЕР ВЫЗОВА

     stripc prog*.c > header

Извлекает начальные блоки комментариев из всех файлов и помещает в один файл с именем header.

ИСХОДНЫЙ КОД ДЛЯ stripc

    1  :
    2  # @(#) stripc v1.0  Strip comment header  Author: Russ Sage

    4  if [ "$#" -eq "0" ]
    5    then  echo "stripc: arg count error"  >&2
    6          echo "usage: stripc file [...]" >&2
    7          exit 1
    8  fi

    10 for FILE in $@
    11 do
    12         if [ ! -s $FILE ]
    13           then  echo "file \"$FILE\" does not exist" >&2
    14                 continue
    15         fi

    17         awk '/^\/\*/, /^ \*\// { if ($0 != " */")
    18                                         print
    19                                    else {print;exit}
    20         }' $FILE
    21         echo "^L"
    22 done

(Перед тем как вводить этот исходный код, обратите внимание, что в строке 21 должен быть действительно символ control- L, введенный между двумя кавычками, по причинам, рассмотренным ниже.)

ПЕРЕМЕННАЯ СРЕДЫ

FILE Хранит имя файла, полученное из командной строки.

ОПИСАНИЕ

Зачем нам нужен stripc?

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

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

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

Что делает stripc?

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

Рассмотрим на модельном исходном файле, какого рода информацию мы должны извлечь из исходных файлов.

    /*
     *  Это документирующий заголовок для файла
     *  с исходным кодом на языке Си.
     *  Он поясняет, что содержится в файле (программы, функции,
     *  библиотеки и т.д.) и идентифицирует проект.
     *
     */ Это отметка конца заголовочного комментария.
    ^L  Инструменты извлечения применяют control-L как разделитель.
    /*  Это документирующий заголовок для главной части программы.
     *  Главная пометка должна объяснять, что это за программа
     *  и что она делает. Здесь могут быть также указаны автор,
     *  дата и история изменений.
     */
    main()
    {
     /* Здесь находится главная Си-программа */
    }
    ^L
    /*  Это документирующий заголовок для определенной функции,
     *  которая за ним следует. Документируется последователь-
     *  ность вызова, вход и выход и общее назначение этой
     *  функции.
     */
    func(arg1,arg2)
    int arg1;
    char arg2;
    {
     /* Текст функции находится здесь */
    }
    ^L
    /*  Аналогично, этот блок комментариев документирует
     *  следующую функцию. Наличие документации вместе с кодом
     *  сокращает объем накладных расходов при чтении и
     *  изменении кода.
     */
    func(arg1,arg2)
    int arg1, arg2;
    {
     /* Текст функции находится здесь */
    }

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

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

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

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

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

Отметим, что "извлечение" ("strip") здесь и в следующих двух утилитах означает не УДАЛЕНИЕ, а копирование соответствующей информации. Никаких изменений во входных файлах не делается.

Когда все файлы в командной строке обработаны, командный файл завершается.

ПРИМЕРЫ

       1.  $ stripc test?.c > test.filehdrs

Извлекает блок файловых комментариев из всех файлов, соответствующих строке "test", за которой следуют один произвольный символ, а затем .c. Сюда подходят test1, testA, testb и т.д. Все блоки комментариев помещаются в файл test.filehdrs в таком порядке, как они обрабатывались бы в цикле for.

       2.  $ for DIR in src1 src2 src3
           > do
           >           stripc $DIR/*.c > /tmp/$DIR.hdrs
           > done

Этот цикл проходит каждый из трех каталогов src1, src2 и src3. В каждом из них имеются исходные файлы, из которых нужно извлечь документирующие заголовки. Каждый раз берется один каталог, и stripc вызывается для всех исходных файлов на языке Си из данного каталога. Выход, т.е. все документирующие заголовки из файлов в этом каталоге, помещается в файл в каталоге /tmp. Файлы в /tmp имеют имена /tmp/src1.hdrs, /tmp/src2.hdrs и /tmp/src3.hdrs. Отметим, что число файлов, передаваемых stripc, имеет ограничение в 255 символов. После этого командная строка переполняется и выполнение командного файла аварийно завершается.

ПОЯСНЕНИЯ

Строки 4-8 делают проверку на ошибки. Если число параметров в командной строке нулевое, возникает ошибка. При вызове stripc должно быть хотя бы одно имя файла.

Цикл for в строках 10-22 пробегает по каждому имени файла из списка позиционных параметров в командной строке.

Первым делом, в строках 12-15 проверяется существование файла. Если файл не существует, выдается соответствующее сообщение об ошибке и цикл продолжается со следующим файлом.

Строки 17-20 - это цикл awk, который делает всю важную работу. Входным для awk является имя файла, которое сейчас обрабатывает цикл for.

Если вы не очень знакомы с программой awk, сообщим, что она вызывается так:

         awk программа имя-файла

где программа состоит из последовательности предложений, имеющих вид:

         шаблон { действие }

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

В stripc для указания начала комментария (/*) и конца комментария (*/) используются соответственно шаблоны

         /^\/\*/ и /^ \*\//

Для интерпретации этих обозначений нужно вспомнить регулярные выражения ed, sed и grep. Регулярные выражения (РВ) должны быть ограничены (символом /). awk воспринимает два выражения, разделенные запятыми, как начальный и конечный шаблоны для квалификации вводных строк.

В данном примере начальное выражение означает "от начала строки (^), вслед за действительным символом косой черты (который должен быть экранирован, чтобы убрать его специальное значение, т.е. \/) и действительной звездочкой (\*), использовать все строки, обнаруженные вплоть до конечного выражения". Этим выбирается только первое вхождение сопоставляемого шаблона (т.е. первого блока комментариев), так как программа awk заканчивает работу при обнаружении завершающей строки. (Остальные блоки комментариев выбираются с помощью stripf вместе с именем функции и аргументами.)

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

Строки 17, 18 и 19 содержат предложение if-then= else, которое выполняет всю работу. Если $0 (что является всей строкой) НЕ равно пробелу и концу комментария (*/), печатается вся строка. Формируя таким образом предложение if, мы печатаем первую строку и все последующие строки, пока не доберемся до конца комментария.

Когда обнаружена последняя строка, результатом проверки является значение "ложь", поэтому выполняется else-часть. Поскольку мы знаем, что это последняя строка, мы печатаем ее для завершенности блока комментария, а затем выходим из awk. Отметим, что благодаря вложению этих двух команд вместе в фигурные скобки ({}), они рассматриваются как одно составное предложение.

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

4.2.2. stripf - из Си-функции

     ИМЯ: stripf

stripf Извлекает документирующий заголовок Си-функции.

ФУНКЦИЯ

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

ФОРМАТ

    stripf file [...]

ПРИМЕР ВЫЗОВА

    stripf lib1.c

Извлекает документирующие заголовки для всех функций в файле lib1.c.

ИСХОДНЫЙ КОД ДЛЯ stripf

    1  :
    2  # @(#) stripf v1.0  Strip function header  Author: Russ Sage

    4  for FILE in $@
    5  do
    6          sed -n -e '
    7          /^L$/ {
    8                   s/^L$/.bp/p
    9          : loop
    10                  n
    11                  /^{/b exit
    12                  p
    13                  b loop
    14         : exit
    15                  i\
    16  {}
    17                  b
    18         }' $FILE
    19 done

ПЕРЕМЕННАЯ СРЕДЫ

FILE Хранит имя файла для каждого файла из командной строки.

ОПИСАНИЕ

Зачем нам нужен stripf?

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

Как нам получить ее из файлов?

Что делает stripf?

Командный файл stripf решает эту проблему. Он просматривает весь файл и печатает всю документацию для каждой ФУНКЦИИ, которая размещена в этом файле (включая "main", если она есть).

Входом для stripf являются имена файлов, переданные в командной строке. Stripf обрабатывает файлы по очереди и помещает выход в stdout. Этот выход можно перенаправить, но вход должен быть в командной строке.

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

ПРИМЕРЫ

       1.  $ stripf module1.c | grep >"^\.bp$" | wc -l

Печатает число модулей-функций, содержащихся в файле module1.c, путем поиска каждого появления команд новой страницы программы nroff и их подсчета. Мы знаем, что одна из таких команд является выходом для каждой обнаруженной функции. Данную строку можно вложить в предложение echo, которое говорит "имеется X модулей в файле $FILE".

        2.  $ for FILE in *.c ../*.c $HOME/src/*.c
            > do
            >        stripf $FILE
            > done >> /tmp/func.hdrs

Данный цикл for распространяется на все файлы в текущем каталоге, которые оканчиваются на .c, все файлы в родительском каталоге с таким же суффиксом и все файлы в подкаталоге src моего home-каталога с тем же суффиксом. Из каждого файла извлекается документация о функциях и направляется в стандартный вывод. Весь цикл перенаправлен с помощью >>, поэтому выход каждого вызова stripf ДОБАВЛЯЕТСЯ к файлу в /tmp.

ПОЯСНЕНИЯ

Вся программа - это один большой цикл for в строках 4-19. Этот цикл присваивает переменной FILE каждое имя, имеющееся в командной строке. Данный командный файл не имеет опций и обработки ошибок.

Команда sed системы UNIX вызывается для каждого имени файла. Программа sed читает весь вход и выводит измененный текст в стандартный вывод.

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

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

Когда обнаружен control-L, он активизирует все выражение, заключенное в фигурные скобки. Первым действием является подстановка команды начала страницы программы nroff, как описано ранее. Эта подстановка печатается, что является самым первым выводом. В строке 9 объявлена метка "loop". Это не приводит ни к каким действиям, но устанавливает точку перехода, которая впоследствии используется. (Управляющие структуры программы sed довольно примитивны, но они позволяют описать выполняемую работу.)

Строка 8 использует команду n программы sed, чтобы вызвать чтение следующей строки. Мы разобрались с первой строкой - строкой, которая содержит control-L - так что мы можем ее отбросить. В случае выполнения цикла мы видим, что sed продвигается по нашему требованию, но не продвигается сам.

Вспомним модель документации, рассмотренную ранее. Эта модель включает документирующий заголовок для файла в целом, выполненный в обычном стиле языка Си. Модель завершается символом control-L. Этот первый блок обрабатывается с помощью stripc, как описано ранее. Мы не хотим использовать его здесь при работе со stripf. Поэтому мы сейчас должны спозиционироваться после файлового документирующего заголовка.

Вслед за символом control-L имеется еще один набор из одной или более строк комментария языка Си, которые описывают функцию, следующую за ними. Далее идет само имя функции, объявление параметров и открывающий символ самой функции, которым является левая фигурная скобка (}).

Строка 11 ищет эту фигурную скобку. Если она найдена, выполнение переходит на метку exit (строка 14). Мы можем полагать, что мы все сделали, если найдена левая фигурная скобка, так как этот символ должен появляться только в начале функциимодуля. Когда мы находим фигурную скобку, мы уже напечатали к этому моменту всю комментирующую информацию и заголовок функции посредством строки 12, которую мы сейчас опишем. А что если фигурная скобка появляется в поле комментария? Нет проблем, поскольку поиск фигурной скобки привязан к началу строки с помощью символа ^. Он производит выражение, означающее "от первого символа в строке". Мы только тогда сопоставляем фигурную скобку этому выражению, когда она встречается в качестве самого первого символа в строке.

Затем строка 12 предполагает, что мы еще не обнаружили фигурную скобку и поэтому мы должны напечатать строку. Оператор p печатает текущую строку, которую обрабатывает sed. Этот вывод направляется на экран.

Строка 13 - безусловный переход на метку loop. Отметим, что этот переход только изменил процесс выполнения и не привел к чтению еще одной вводной записи. С этим мы должны быть осторожны при управлении процессом выполнения в программе sed. Мы только что напечатали текущую запись, поэтому теперь мы должны отбросить ее и получить следующую запись, что означает возврат в строку 9. Этот цикл печати и чтения следующей записи продолжается до обнаружения фигурной скобки, которая переводит выполнение на метку exit.

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

Строка 15 завершает вывод, вставляя некоторый текст в выводной поток. Мы не можем сказать "печатать это в буквенном виде", поэтому происходит движение вправо по тексту, как по команде echo. Мы должны сыграть на правилах, установленных программой sed. Любой вывод должен быть порожден обычными командами в стиле редактора ed. Вставка текста с помощью команды "i" делает нам это. Отметим, что мы также вставляем символ возврата каретки (или перевода строки, в зависимости от вашей осведомленности). Он может быть определен символом обратной косой черты (\). Обратная косая черта убирает специальное значение символов и при использовании в конце строки, как здесь, означает, что специальный символ, вставленный в выражение, является возвратом каретки. Вдобавок к возврату каретки, мы вставляем пару фигурных скобок. Это обозначает объявление начала-конца функции, которую мы обрабатываем. Поскольку мы вставляем текст, мы не должны говорить программе sed, что его нужно печатать.

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

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

4.2.3. strips - из командного файла Shell

       ИМЯ: strips

strips Извлекает документирующий заголовок командного процессора.

ФУНКЦИЯ

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

ФОРМАТ

         strips файл [...]

ПРИМЕР ВЫЗОВА

         strips *.sh

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

ИСХОДНЫЙ КОД ДЛЯ strips

    1  :
    2  # @(#) strips v1.0  Strip shell comment header
                                     Author: Russ Sage

    4  for FILE in $@
    5  do
    6        cat $FILE | (read LINE; echo $LINE
    7        while read LINE
    8        do
    9              if [ "`echo $LINE|cut -c1`" = "#" ]
    10               then echo "$LINE"
    11               else exit
    12             fi
    13       done)
    14 done

ПЕРЕМЕННЫЕ СРЕДЫ

FILE | Хранит каждое имя файла, полученное из командной строки.
LINE | Хранит каждую строку вводного текста, полученную из читаемого файла.

ОПИСАНИЕ

Зачем нам нужен strips?

Так же как нам нужны средства обработки документации для файлов с исходным кодом на Си, нам нужны и аналогичные средства для командных файлов командного процессора. Разница между извлечением комментариев из кода на языке Си и их извлечением из кода командных файлов командного процессора - в способе разграничения комментариев в исходном файле. Исходный файл на Си использует пару /* */, а командные файлы командного процессора применяют #, чтобы выделить остаток строки как комментарий.

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

      :
      # ИМЯ
      #       strips   Извлекает поля shell-комментариев
      #
      # ФОРМАТ ВЫЗОВА
      #       strips файл [...]
      #
      # АВТОР
      #       Russ Sage   mm/dd/yy
      #
      # ОПИСАНИЕ
      #       Данный командный файл извлекает комментирующие
      #       заголовки из командных файлов интерпретатора shell.
      #
      # СМ. ТАКЖЕ
      #       sh(1), cat(1)

Отметим, что в первой строке имеется оператор :, который является для командного процессора нулевым оператором. Он ничего не делает, но всегда возвращает успешный статус выхода. Он находится в отдельной строке, поскольку это обозначает командный файл для Bourne shell. Если вы запускаете /bin/csh вместо /bin/sh, командные файлы могут вызвать путаницу. C Shell ищет символ # в первой колонке первой строки. Если он там есть, командный процессор считает этот файл командным файлом интерпретатора csh. Чтобы указать интерпретатору csh, что командный файл предназначен для интерпретатора Bourne shell, мы можем оставить эту строку пустой (что будет не слишком хорошо печататься и подлежит удалению) или поместить оператор, который сообщает интерпретатору C Shell, что это командный файл для Bourne shell, но ничего не делает под управлением Bourne shell.

Что делает strips?

Strips читает командный файл и печатает все комментарии с начала файла, которые являются непрерывными (т.е. печатает, пока не достигнет строки, не являющейся комментарием). Первая строка игнорируется, но печатается. Когда строка не имеет символа # в первой символьной позиции, strips прекращает чтение этого файла.

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

ПРИМЕРЫ

       1.  $ strips `kind /bin /usr/bin`

Блоки комментариев извлекаются из текстовых файлов, размещенных в каталогах /bin и /usr/bin.

       2.  $ find / -name "*.sh" -print | while read FILE
           > do
           > strips $FILE > /tmp/doc/$FILE
           > done

Find порождает список всех имен файлов, который попадает в strips. Выход strips направляется в каталог документации в /tmp. Окончательный выход попадает в файл с точно таким же именем, как исходный файл, только выход помещается в /tmp, поэтому никаких случайных удалений не происходит.

ПОЯСНЕНИЯ

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

Строка 6 применяет команду cat к файлу, который сейчас обрабатывается. Выход направляется в конвейер, чтобы его прочитал другой shell. Новый shell получает длинную командную строку (обозначенную скобками в строках 6 и 13). Первая команда read читает самую первую строку. Поскольку мы не собираемся проверять эту строку, мы отображаем ее, а затем опускаемся в цикл последовательного чтения. Предполагается, что первая строка начинается с двоеточия, но если она начинается с символа #, она все равно печатается, так что вы не будете терять текст.

Строки 7-13 являются внутренним циклом while, читающим строки текста со стандартного ввода, который был выходом команды cat. Когда текст заканчивается, прекращается и цикл while.

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


ГЛАВА 4. Управление программной документацией

4.3. ctags - создание файла признаков исходного кода проекта

       ИМЯ: ctags

ctags Делает файл признаков исходного кода для простоты доступа с помощью утилиты vi.

ФОРМАТ

         ctags [файл ...]

ПРИМЕР ВЫЗОВА

         ctags proj*.c

Делает файл признаков для всего исходного кода проекта.

ИСХОДНЫЙ КОД ДЛЯ ctags

    1  :
    2  # @(#) ctags v1.0  Create a C source code tag file
                                      Author: Russ Sage

    4  awk -F'(' '/^[a-zA-Z_][a-zA-Z0-9_]*\(/ {
    5        printf ("%s\t%s\t/^%s$/\n", $1, FILENAME, $0) }'
                                      $@ | sort -u +0 -1

ПЕРЕМЕННАЯ СРЕДЫ

FILENAME awk Переменная, содержащая имя файла.

ОПИСАНИЕ

Зачем нам нужен ctags?

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

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

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

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

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

Это сочетание возможностей иллюстрирует то, чему не часто придается значение: владельцы UNIX всегда настороженно относятся к возможности использовать преимущества многочисленных средств, уже имеющихся в таких программах, как vi или ex. Зачастую от 90 до 95 процентов необходимых вам возможностей уже имеются, ожидая относительно простого командного файла интерпретатора shell, связывающего их вместе в мощный новый инструмент.

Ctags уже существует в виде исполняемого модуля в системе Berkely (BSD) и в нынешней AT&T System V. Он происходит из системы Berkely, но поддерживается теперь в System V. Это иллюстрация взаимодействия между этими двумя источниками в мире UNIX, поскольку они взаимствуют полезные идеи друг у друга. Данное конкретное воплощение ctags является командным файлом утилиты awk, имитирующим исполняемую программу из системы Berkely, а это значит, что пользователи систем XENIX и предыдущих версий AT&T могут теперь извлечь пользу от применения ctags. Еще одно преимущество версии в виде командного файла в том, что его можно легко модифицировать, чтобы обрабатывать другие особенности языка Си. Такое вы не можете делать с исполняемым модулем, если только у вас нет дорогостоящей лицензии на исходный код.

Что делает ctags?

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

       main()
       {
       }

       func1(arg1,arg2)
       int arg1,arg2;
       {
       }

       func2(arg1,arg2)int arg1,arg2;
       {
       }

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

       for F in *.c
       do
        ctags $F > $F.tags
       done

Выход ctags состоит из трех полей в таком формате:

       признак   имя_файла   шаблон_поиска

Реальный выход для примера программы на Си, приведенного выше, был бы таким:

       main   /usr/russ/src/program.c  /^main()$/
       func1  /usr/russ/src/program.c  /^func1(arg1,arg2)$/
       func2  /usr/russ/src/program.c  /^func2(arg1,arg2)$/

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

Предположим, вы можете сгенерировать правильный файл признаков. Как согласовать файл признаков с редакторами таким образом, чтобы вы могли найти интересующую вас функцию? Редактор vi предоставляет много путей для этого. Первый способ - поместить имя используемого файла признаков в файл .exrc. (Файл .exrc является аналогом файла .profile для редактора ex и работает также с редактором vi, что не удивительно, так как vi построен na ex. Поскольку vi - наиболее популярный редактор системы UNIX, мы применяем его здесь для наших примеров.) Вы можете иметь файл .exrc, который выглядит примерно так:

         set tags=/usr/russ/mytags

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

         :set tags

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

         :set tags=/usr/russ/mytags

Теперь, когда редактор знает, в каком файле искать признаки, к которым вы обращаетесь, давайте рассмотрим, как обращаться к признаку (т.е. к имени функции). Первый способ - объявить его в командной строке при вызове редактора. Например:

         $ vi -t tag

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

         :ta tag

Двоеточие означает, что мы направляемся в ex, чтобы выполнить этот поиск. Мы просим ex найти указанную строку как признак, который размещается в текущем файле признаков. Когда этот признак найден в файле признаков, редактор vi редактирует файл с соответствующим именем, которое он берет из поля 2. Это аналогично команде ":e имя_файла". Когда новый файл внесен в буфер редактора, последнее поле файла признаков используется в качестве строки шаблона поиска. Синтаксис точно такой же, как если бы вы набирали его вручную. Курсор перемещается в позицию в файле, которая соответствует строке поиска, при этом вы попадаете на интересующую вас функцию.

ВЗАИМОСВЯЗЬ МЕЖДУ ex И vi

Несколько отклоняясь от темы, рассмотрим два файла: /bin/ ex и /bin/vi. Небольшое исследование обнаруживает, что на самом деле это один и тот же файл. Мы можем проверить это, посмотрев на их индексные описатели файлов. Введите такую команду:

         $ ls -li `path ex vi`

Выход показывает, что два числа в первой колонке одинаковы.

       510 -rwx--x--t   5 bin    bin    121412 Sep 19  1985 /bin/ex
       510 -rwx--x--t   5 bin    bin    121412 Sep 19  1985 /bin/vi

Это число и есть индексный описатель файла (inode). Поскольку оба файла являются одним и тем же, вызов любого из них запускает один и тот же исполняемый модуль. Каким образом он знает, как вы его вызвали? Программа смотрит на строку argv[0] и видит, какое имя вы использовали для вызова файла. Затем редактор устанавливает свой интерфейс в соответствии с тем, как вы его вызвали.

Обратите внимание, что эта программа имеет пять связей. Как нам найти все другие имена, которыми можно вызвать vi и ex? Мы можем использовать команду системы UNIX ncheck. Эта команда воспринимает индексный описатель файла и печатает все файлы, имеющие такой описатель файла . Примеры таких команд:

         $ ncheck -i 510 /dev/root
         $ ncheck -i 510

Первый синтаксис указывает команде ncheck искать файлы с inode, равным 510, только в корневой файловой системе. Ncheck ограничена поиском в одной файловой системе. Это подкрепляет тот факт, что файлы не могут быть привязаны к различным файловым системам, поскольку каждая файловая система начинается с inode 2 и последовательно наращивается. Каждая файловая система имеет inode 510, который уникален для каждой файловой системы. Выход предыдущей команды выглядит так:

       dev/root:
       510     /bin/edit
       510     /bin/ex
       510     /bin/vedit
       510     /bin/vi
       510     /bin/view

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

ПРИМЕРЫ

         1. $ ctags *.c

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

         2. $ ctags `Find /usr/src -name "*.c" -print`

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

         3. $ find /usr/src -name "*.c" -exec ctags {} \; > tags

Находит все исходные файлы на Си в сегменте дерева /usr/src. Для каждого подходящего файла запускает программу ctags на этом файле. Использование такой формы записи предотвращает порчу вашей команды (о которой только что шла речь), а также запускает ctags для каждого имени файла. Весь выход помещается в файл tags для последующего использования.

         4. $ find /usr/src -type f -print | sort |
         > while read FILE
         > do
         > ctags $FILE
         > done >> tags

Используя преимущество множественных каталогов, находит и сортирует все файлы из каталога /usr/src и печатает их маршрутные имена в стандартный вывод. Затем они сортируются и поступают по конвейеру в цикл while. Цикл while используется для обслуживания сколь угодно большого числа файлов без переполнения буферов. Выход цикла while добавляется к одному файлу - tags. Этот цикл громоздкий и медленный, но он выполняет свою работу.

         5. $ find /usr/src -print | ctags

Это неправильный способ использования ctags. Выходом команды find являются маршрутные имена. Ctags читает стандартный ввод, поскольку в командной строке нет файлов. Получается, что данные, которые читает awk, являются маршрутными именами от find, которые не имеют корректных полей для соответствия шаблонам функций. Никакого сопоставления не происходит.

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

         find /usr -print | wc -l

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

         find /usr -exec cat {} \; | wc -l

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

         find /usr/src -name "*.c" -exec cat {} \; | ctags

В отличие от результата, который мы получили бы в предыдущих примерах:

         func1  /usr/russ/src/program.c  /^func1(arg1,arg2)$/
         func2  /usr/russ/src/program.c  /^func2(arg1,arg2)$/

теперь выход будет выглядеть так:

         func1         -          /^func1(arg1,arg2)$/
         func2         -          /^func2(arg1,arg2)$/

ПОЯСНЕНИЯ

Символы "-" вместо имени файла появляются из-за того, что ctags читает из стандартного ввода. Awk автоматически присваивает своей внутренней переменной FILENAME значение "-", так как знает, что в командной строке не было файлов.

Весь командный файл есть программа awk. Входом для командного файла утилиты awk является $@, что представляет все позиционные параметры. Каждый параметр считается именем исходного файла на Си. Если никакие файлы не передаются в командной строке, awk ищет данные в стандартном входном потоке, но это создает некорректный выход, так как переменная FILENAME в awk имеет по умолчанию значение "-". Поскольку awk требует имена файлов, мы должны вызывать ctags с именами файлов, а не передавать ему данные по конвейеру через стандартный ввод, как показано в предыдущем примере.

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

Шаблон поиска утилиты awk соответствует синтаксису имени Си-функции. Оно может начинаться с символов a-z, A-Z или символа подчеркивания. Далее в имени могут быть любые символы из набора a-z, A-Z, 0-9 и _. Между именем и скобкой нельзя использовать пробелы. Поиск начинается от начала строки (^), за которым следует последовательность допустимых символов (a-z, A-Z, 0-9), а затем левая скобка.

Когда строка соответствует данному шаблону, генерируется выход с помощью оператора printf. Первое поле - строка, представленная обозначением $1. В данном случае $1 - это только имя функции, исключая левую скобку. Печатается символ табуляции, затем следующая строка, которая является переменной FILENAME из утилиты awk. Эта переменная должна быть получена из командной строки, иначе awk не будет знать имя файла, в котором размещена данная функция, и файл признаков потеряет информацию, необходимую для доступа к файлу, содержащему функцию. Печатается еще одна табуляция, затем строка поиска. Строкой поиска является $0, что представляет всю строку, с которой работает awk. Строке предшествует символ ^, а за строкой следует символ $.

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

МОДИФИКАЦИИ ДЛЯ ctags

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

       struct name {
        int val1;
        char val2;
       };

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

Мы надеемся, что облегчили сопровождение ваших программ и предложили вам идеи для других способов автоматической обработки документации. Вы можете без особого труда учреждать и поддерживать локальные соглашения о документации с помощью командных файлов, аналогичных представленным здесь. Примером проекта, за который вы можете взяться, является согласование наших программ извлечения информации (stripf, stripc, strips) и других программ, которые вы пишете, таким образом, чтобы они могли читать файл-формирователь (makefile, см. Make(1)) и выдавать полную документацию по всем исходным файлам, участвующим в данном проекте.


ГЛАВА 5. Управление личной информацией I

УПРАВЛЕНИЕ ВРЕМЕНЕМ И ДЕЛОПРОИЗВОДСТВОМ

УПРАВЛЕНИЕ ВРЕМЕНЕМ

at | выполнение задач в указанное время
b | порожденный shell фоновых задач
greet | своевременное приветствие с терминала
lastlog | сообщение времени последней регистрации
timelog | учет и статистика сеансов работы
today | печать календаря с отмеченной текущей датой

УПРАВЛЕНИЕ ДЕЛОПРОИЗВОДСТВОМ

jargon | генератор технических терминов
phone | база данных с телефонными номерами
office | делопроизводитель

УПРАВЛЕНИЕ ЛИЧНОЙ ИНФОРМАЦИЕЙ I:

УПРАВЛЕНИЕ ВРЕМЕНЕМ И ДЕЛОПРОИЗВОДСТВОМ

ВВЕДЕНИЕ

Мы уже многое знаем о файлах и о том, как управлять файловой структурой. Пора рассмотреть, как мы можем использовать систему UNIX для управления множеством задач, которые составляют наш рабочий день и держат нас в курсе того, что делают другие пользователи. Термин "управление личной информацией" (personal management) подразумевает, что вы хотите создать свою собственную ПЕРСОНАЛЬНУЮ рабочую среду и инструментальные средства. Мы предлагаем вам пакет программ, которые вы можете приспособить к вашим требованиям. Фактически мы в этой и следующей главе представляем четыре отдельных набора программ, каждый из которых посвящен определенному аспекту управления личной информацией.

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

УПРАВЛЕНИЕ ВРЕМЕНЕМ

Поскольку система UNIX имеет встроенные функции поддержки времени и часы, она может следить за временем. Объединение функций поддержки времени с возможностью автоматического запуска группы команд означает, что мы можем настроить их так, чтобы компьютер выполнял многие наши рутинные работы, связанные со временем. Мы также можем использовать компьютер для отслеживания нашего собственного времени. В данном разделе представлены инструментальные средства at, b, greet, lastlog, timelog и today.

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

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

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

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

Последнее средство, относящееся ко времени - это командный файл today. Это утилита, которая изменяет вид выходных данных команды UNIX cal. Она печатает обычный календарь, только текущая дата выводится в инверсном виде. Это очень наглядно. Вы можете развить этот инструмент для того, чтобы отмечать праздники или другие особые дни.

        ИМЯ:  at

at - выполнить команду или файл в указанное время

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

            at hr:min cmd [;cmd ...]

ПРИМЕР ВЫЗОВА

            at 12:00 echo "time for lunch!"

В двенадцать часов дня выводит сообщение на экран терминала.

ТЕКСТ ПРОГРАММЫ at

    1  :
    2  # @(#) tree v1.0  Execute command line at specific time
    Author: Russ Sage
    2а            Выполнить командную строку в указанное время

    4  if [ $# -lt 2 ]
    5    then echo "at: wrong arg count"             >&2
    6         echo "usage: at hr:min cmd [;cmd ...]" >&2
    7         exit 1
    8  fi

    10 ITS=$1; shift

    12 while :
    13 do
    14       TIME=`date | cut -c12-16`

    16       if [ "$ITS" = "$TIME" ]
    17         then  eval $@
    18               exit 0
    19         else  sleep 35
    20       fi
    21 done &

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ITS | Время, в которое следует выполнить указанные команды
TIME | Текущее время в системе

ОПИСАНИЕ

Зачем нам нужен at?

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

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

Данная концепция не нова и уже существует в системе Berkeley UNIX под тем же именем. Она реализована также в последних версиях System V. Почему же тогда мы представляем здесь нашу собственную версию? Одна из причин в том, что многие из вас имеют более ранние версии UNIX, в которых это средство отсутствует. Но важнее, видимо, другое наша цель не в том, чтобы сделать существующие команды at устаревшими, а в показе того, как легко отслеживать время и реализовывать обработку, связанную со временем. Имея нашу собственную команду at, мы можем настроить ее по своему вкусу и изменить ее, когда необходимо. Команда at, представленная здесь, фактически более гибкая, чем at в системе Berkeley, хотя в первой отсутствуют некоторые особенности второй. Она более гибкая потому, что вы можете поместить настоящие команды в фоновую задачу at, в то время как для at в системе Berkeley вы должны использовать имя командного файла интерпретатора shell. Метод Berkeley запрещает вам вызывать исполняемые модули непосредственно в командной строке, а наша at - нет. (Конечно, вы можете с таким же успехом использовать командные файлы интерпретатора shell, если вам это необходимо.)

Что делает at?

Команда at дает нам возможность собирать несколько команд в одно целое и впоследствии запускать их. Когда они выполняются, их вывод может либо идти на экран, либо перенаправляться в определенный файл. Командная строка принимает два параметра: время выполнения и командную строку, которую следует выполнить. Время выражено в формате час:минута. Час должен быть указан строкой из двух цифр (в диапазоне от 0 до 23 часов), так как его использует команда date. Использование того же стандарта, что и в команде date, значительно упрощает команду at. В качестве второго параметра может быть любая команда, которую обычно можно ввести в командной строке интерпретатора shell. Можно также использовать конвейеры, составные команды и переназначения. Нет ограничений на то, какая команда может быть выполнена. Команда at может запустить обычный исполняемый модуль UNIX или ваш собственный командый файл.

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

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

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

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

ПРИМЕРЫ

           1. $ at 11:45 echo ^G^G It's almost lunch time

Без пятнадцати минут двенадцать дважды выдается звуковой сигнал (control-G) и выводится сообщение о ленче.

           2. $ at 10:45 "if [ -s $MAIL ]; then echo ^G You have mail; fi"

Без пятнадцати одиннадцать проверяется, существует ли мой почтовый файл и есть ли в нем хотя бы один символ ($MAIL есть /usr/spool/mail/russ). Если это так, выдается звуковой сигнал и сообщение о том, что у меня есть почта.

           3. $ at 17:00 "c; date; banner ' time to' ' go home'"

В пять часов вечера очищается экран (с помощью команды c, описанной далее в данной книге), печатается дата и выводится крупными буквами на весь экран сообщение "time to go home" ("пора домой"). С помощью апострофов в командной строке banner мы можем добиться вывода символа возврата каретки, чтобы разместить каждый набор слов в отдельной строке. Если какая-либо из этих команд не срабатывает (например, не найдена команда c), то и весь фоновый процесс оканчивается неудачей.

ПОЯСНЕНИЯ

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

Переменная интерпретатора shell ITS инициализируется в строке 10. В ней устанавливается значение первого позиционного параметра ($1), которым является час:минута. Как только мы занесли это значение в переменную, оно больше не нужно нам в командной строке. Команда shift удаляет $1 из командной строки. Теперь командная строка состоит из вызывающей команды $0 (т.е. самой at) и остатка строки ($@ или $*). Вызывающая команда не вычисляется как часть остатка строки, поэтому вам не нужно заботиться об аргументе $0.

Далее at переходит к вечному циклу while в строках 12-21. Вечным этот цикл делает команда : (двоеточие). Это встроенная команда интерпретатора shell, которая ничего не делает кроме того, что всегда возвращает успешный статус выхода, заставляя тем самым цикл продолжаться. Команда true интерпретатора shell очень похожа и делает программу более наглядной. Мы же используем : вместо true, чтобы сократить издержки на порождение процесса для каждой итерации цикла. Команда : встроена в сам shell. True, напротив, является внешней командой в каталоге bin (так же, как ls), она должна быть найдена по файловому пути, выполниться и вернуть значение. Это занимает гораздо больше процессорного времени.

На каждой итерации цикла текущее время сверяется с назначенным временем, переданным из командной строки. Текущее время извлекается из команды date в строке 14. Обычно date выдает результат в таком формате:

    |
    |        Mon Mar 31 06:54:25 PST 1986
    |
    |

Поскольку это строка фиксированного размера, мы можем посчитать номера позиций, в которых размещены час и минута. Данные час:минута находятся в позициях 12-16. Для получения этих символов мы запускаем команду date, пропускаем ее результат по конвейеру через cut и вырезаем нужные позиции. Весь результат присваивается переменной TIME. Заметим, что поле секунд не используется. Наименьшая единица времени в этой программе - минута.

Все волшебство данной команды заключено в строках 16-20. Если время, указанное в командной строке, равно текущему времени (строка 16), вычислить и выполнить остальные аргументы командной строки (строка 17), затем выйти с успешным нулевым значением (строка 18). Если время не совпало, немного поспать (строка 19) и повторить все сначала. Символ & в конце цикла в строке 21 превращает весь цикл while в фоновый процесс. Как мы можем убедиться, что shell выполняет все свои команды в фоновом режиме и никакие из них не выполняет в оперативном режиме? В действительности мы не можем этого сделать. Мы должны полагать, что shell так работает. Поскольку многое при программировании на shell делается исходя из опыта и интуиции, вам приходится испытывать многие вещи, чтобы увидеть, как они работают. Периодически shell преподносит сюрпризы и делает нечто совершенно неожиданное.

ИССЛЕДОВАНИЯ

Что бы случилось, если бы вы поставили задание at в фоновый режим, а затем вышли из системы? Ответ зависит от того, с каким shell вы работаете. Если у вас Bourne shell, то ввод команды control-D при выходе из системы прекращает выполнение всех ваших фоновых задач. Единственный способ оставить в живых фоновые задачи после выхода из системы - использовать команду nohup ("no hang up" - "не казнить").

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

         nohup at 13:00 echo "back from lunch yet?"

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

Вы, возможно, удивлены тем, что в строке 17 использована команда "eval $@". Это сформировалось методом проб и ошибок. На начальных этапах разработки at команда "$@" использовалась сама по себе. При самостоятельном применении эта команда означает "выполнить все позиционные параметры". Поскольку это была единственная команда, выполнялась вся строка позиционных параметров, после чего возникали проблемы.

Использование переназначений и переменных интерпретатора shell сильно запутывало at.

Для иллюстрации рассмотрим пару примеров. Если мы запускаем at с командной строкой

          at 09:30 echo $HOME

то все вроде бы работает. Сначала раскрывается переменная $HOME, затем echo печатает ее значение - /usr/russ. Но если мы запускаем командную строку

          at 09:30 echo \$HOME

то переменная не раскрывается и echo фактически печатает $HOME вместо значения переменной $HOME. Мы избежали этого просто с помощью команды eval для повторного вычисления командной строки перед ее выполнением. Существо проблемы в том, что вызывающий интерпретатор shell не раскрывает значение переменной, поэтому мы заставляем выполняющийся shell повторно анализировать командную строку и вычислять все переменные. На этот раз значения переменных раскрываются, и мы получаем верный конечный результат.

МОДИФИКАЦИИ

Возможно, вы захотите более подробно рассмотреть интерфейс со временем. В нынешнем состоянии at воспринимает только время в пределах от 0 до 23 часов в течение одного дня. Неплохим дополнением было бы заставить его различать время суток, т.е. 8:30 a.m. (до полудня) или 8:30 p.m. (после полудня). Было бы неплохо также иметь возможность сказать "через 10 минут сделать то-то и то-то". В этом случае команда могла бы иметь примерно такой вид:

          at -n 10 echo "do in now plus 10 minutes"

где -n было бы текущим временем, а 10 добавлялось бы к нему.

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

           ИМЯ: b

b Обработчик фоновых задач

ФОРМАТ ВЫЗОВА

            b any_command_with_options_and_arguments
           (любая команда с опциями и аргументами)

ПРИМЕР ВЫЗОВА

            b cg f.c

Компилировать исходный файл в фоновом режиме, где cg - командная строка компилятора, описанная в главе 10.

ТЕКСТ ПРОГРАММЫ b

    1   :
    2   # @(#) b v1.0    Background task handler  Author: Russ Sage
    2а                   Обработчик фоновых задач

    4   ($@; echo "^G\ndone\n${PS1}\c") &

ОПИСАНИЕ

Зачем нам нужен b?

Как вы видели в последнем разделе, Bourne shell дает возможность запускать задачи в фоновом режиме выполнения. Это делает символ &. Что же на самом деле происходит, когда мы запускаем что-нибудь в фоновом режиме? Порождается еще один shell, который должен выполнить свою собственную командную строку. После того, как все его команды выполнятся, он завершается. Вы можете определить фоновые задачи по результату работы команды ps. Эти задачи выглядят как интерпретаторы shell, запущенные с вашего терминала, однако их владельцем, или родительским процессом в действительности является команда init, а не ваш регистрационный shell (это справедливо только для shell, к которым применена команда nohup). Интерпретаторы shell, к которым не применялась nohup, принадлежат вашему регистрационному shell. Ниже приводится пример распечатки команды ps для фоновых задач. Командой для выполнения в фоновом режиме была:

          while :;do date; done &

Команда ps показывает мой регистрационный shell (PID=32), введенную мной командную строку для выполнения в фоновом режиме (PID=419) и shell, который выполняет цикл while (PID=449).

    |
    |    UID   PID   PPID  C   STIME  TTY   TIME  COMMAND
    |
    |    root     0      0  0  Dec 31    ?   0:03  swapper
    |    root     1      0  0  Dec 31    ?   0:02  /etc/init
    |    russ    32      1  0 14:18:36  03   1:26  -shV
    |    russ   419     32  0 15:30:31  03   0:02  -shV
    |    russ   449    419  2 15:30:31  03   0:02  -shV
    |

Ниже приведен листинг команды ps, который показывает фоновый shell, принадлежащий процессу init. Он был получен командой "b ps -ef", где b - утилита, которая будет рассмотрена далее. Как видите, последний процесс 471 есть фоновый shell, принадлежащий процессу 1, которым является init, а не мой регистрационный shell (PID=32).

    |
    |    UID   PID   PPID  C   STIME  TTY   TIME  COMMAND
    |    root     0      0  1  Dec 31    ?   0:04  swapper
    |    root     1      0  0  Dec 31    ?   0:02  /etc/init
    |    russ    32      1  1 14:18:36  03   1:30  -shV
    |    russ   472    471  5 15:46:46  03   0:12  ps -ef
    |    russ   471      1  0 15:46:46  03   0:00  -shV
    |

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

Во-первых, мы никогда не знаем момента завершения фоновых задач.

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

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

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

Что делает b?

Командный файл b - это механизм, который помогает в выполнении фоновых задач. Он запускает наши фоновые задачи. По завершении он отображает на экран слово "done" и затем повторно выводит символ-приглашение shell.

Данное средство не имеет опций и проверки на наличие ошибок. Обработчик фоновых задач фактически выполняет командную строку, которую мы ему передаем, и последующую обработку. Отметим, что для выдачи на экран вашего символа приглашения, вы должны экспортировать переменную PS1 из вашей текущей среды. Это может соблюдаться не на всех машинах, поскольку каждая система UNIX имеет свои особенности. В системе XENIX переменная PS1 не передается, возможно из-за того, что это shell, который вызывает другой shell в фоновом режиме. Если вы скажете "sh" в интерактивном режиме, он будет передан как приглашение. Система UNIX так прекрасна и удивительна!

ПРИМЕРЫ

            1.  $ b ls -R ..

Начиная с родительского каталога, рекурсивно составляется список всех файлов и выводится на экран. Обратите внимание, что при использовании фоновых задач вы не можете эффективно передать все это по конвейеру команде more, поскольку обычным входным устройством для фоновых задач является /dev/null. Команда more не работает нормально, когда ее вызывают из фонового режима. Это также имеет смысл, поскольку вы могли бы иметь две задачи - одну в фоновом режиме, а другую в приоритетном производящие беспорядок на экране. Фоновая команда more должна была бы сохранять в неприкосновенности то, что выводит на экран приоритетная команда.

            2.  $ b echo hello > z

Файл z содержит не только слово "hello", но также и сообщение "done", поскольку переадресация в файл z выполняется во внешнем shell. Переадресация для подзадачи должна быть выполнена в круглых скобках программы b, а мы в данном случае не можем этого сделать.

            3.  $ b sleep 5; echo hello

Эта командная строка не может быть выполнена, поскольку программа b воспринимает только команду sleep. Команда echo не помещается в фоновый процесс и сразу же выполняется.

            4.  $ b "sleep 5; echo hello"

Эту командную строку мы тоже не можем выполнить, поскольку эти две команды будут восприняты командным файлом b как одна. Затем команда sleep не выполнится, поскольку "5; echo hello" является недопустимым указанием временного периода для команды sleep.

ПОЯСНЕНИЯ

Обратите внимание, что в строке 4 вся структура команды заключена в круглые скобки, за которыми следует символ &. Круглые скобки передают всю структуру подчиненному shell, который затем помещается в фоновый режим выполнения. Помещая все команды в один shell, мы гарантируем вывод на экран слова "done" после завершения последнего процесса.

Данная командная строка выполняется с помощью символов $@. Это означает: "выполнить всю командную строку, расположенную справа". Поскольку выражение $@ выполняет само себя (т.е. не в операторе echo или в чем-либо подобном), то shell просто выполняет команды исходной командной строки. Это именно то, что мы хотим! Обратите внимание, что здесь нет никакого оператора eval. Поскольку то, что мы делаем, похоже на своего рода "командный интерпретатор строк" для их ввода и исполнения, вы могли бы подумать, что команда eval здесь необходима. По опыту мы знаем, что это не так. Похоже, что применение eval усложнит дело.

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

             b echo $HOME

на экран будет выдано сообщение

            /usr/russ

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

             ИМЯ: greet

greet Своевременное приветствие с терминала

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

             greet

ПРИМЕР ВЫЗОВА

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

ТЕКСТ ПРОГРАММЫ greet

    1 :
    2 # @(#) greet v1.0  Timely greeting from the terminal
    Author: Russ Sage
    2а                     Своевременное приветствие с терминала

    4 if [ `expr \`date +%H\` \< 12` = "1" ]
    5   then echo "\nGood morning.\nWhat is the best use of your
    time right now?"
    6 elif [ `expr \`date +%H\` \< 18` ="1" ]
    7   then echo "\nGood afternoon.\nRemember, only handle a piece
    of paper once!"
    8 else   echo "\nGood evening.\nPlan for tomorrow today."
    9 fi

ОПИСАНИЕ

Зачем нам нужен greet?

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

Как мы извлекаем и выделяем время с помощью командного файла интерпретатора shell? Доступно много способов, но стандартная команда UNIX date, видимо, является наилучшим способом. В случае языка Си вы должны программно управлять преобразованием времени и временными зонами. Команда date делает это для вас.

Важна также единица времени. Должны ли мы различать секунды, минуты, часы, дни или недели? Это все зависит от требуемого приложения. В нашем простом примере мы различаем только три части суток: утро, день и вечер. Мы определили эти периоды так: с полуночи до полудня, от полудня до шести часов и от шести часов до полуночи соответственно.

Что делает greet?

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

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

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

ПРИМЕРЫ

           1.  $ if greet | fgrep 'morn' > /dev/null
          >   then morning_routine
          > fi

Выполняется greet. Стандартный вывод greet по конвейеру передается на стандартный ввод fgrep. Производится поиск символьной строки "morn". Весь выход переадресовывается в никуда, так что он не засоряет экран. Если выходной статус команды fgrep равен нулю (она нашла нужную строку), выполняется файл morning_routine.

          2.  $ at 10:30 greet; at 13:50 greet

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

ПОЯСНЕНИЯ

Вся программа представляет собой один большой оператор if -then-else в строках 4-9. Логика программы выглядит на псевдокоде следующим образом:

          if it is morning                    если утро
          then echo morning statement       то вывести "утреннее"
                                              приветствие
          else if it is noon                  иначе если день
          then echo noon statement          то вывести "дневное"
                                              приветствие
           else echo evening statement         иначе вывести "вечернее"
                                              приветствие

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

В строке 4 проверяется текущее время на то, меньше ли оно 12 часов. Если да, то фраза команды expr выводит на стандартное устройство вывода единицу ("1"). Поскольку символы ударения (`), которые обрамляют эту фразу, перехватывают стандартный вывод, символ 1 становится частью оператора проверки, что указано квадратными скобками ([]). Затем оператор test проверяет, равен ли выход команды expr литеральной единице. Если они одинаковы, то в строке 5 выводится "утреннее" сообщение.

Рассмотрим более подробно, как раскрывается оператор expr. Во-первых, он заключен в символы ударения. Это означает, что он выполняется перед оператором проверки. Затем его выход помещается для обработки в оператор test. Однако внутри оператора expr имеется еще одно выражение между знаками ударения, которое выполняется до оператора expr. Такое старшинство выполнения управляется интерпретатором кода внутри shell.

Внутренние знаки ударения сохраняются при начальном синтаксическом разборе строки, поскольку они экранированы символами обратной косой черты. Первой запускается команда date, имеющая в качестве выхода только текущее значение часа в соответствии с форматом %H. Затем expr использует данное значение часа для проверки, меньше ли оно 12.

Если да, expr печатает единицу. Если значение часа больше или равно 12, то возвращаемое значение равно 0. Такое понимание, что 1=истина и 0=ложь, соответствует синтаксису, используемому в языке Си.

Однако ранее мы замечали, что в среде программирования на языке shell 1 означает ложь, а 0 - истину. Это происходит потому, что проверяемое значение оператора if является в действительности статусом выхода из предварительно выполненной команды. Нуль соответствует нормальному завершению, поэтому 0 использован для переключения проверки в состояние "истина" и выполнения оператора then. Для того, чтобы преобразовать возвращаемый статус 1 (при условии, что значение часа меньше 12) в нуль (для переключения оператора then), мы используем команду test. Возвращаемый статус единицы равен константе 1, поэтому команда test возвращает 0, что представляет истину. Вот так!

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

Если проверка в строке 4 дает "ложь", выполняется строка 6. Это оператор else от первого if и одновременно следующий if. В таких особых случаях синтаксис shell меняется. Ключевое слово "else" становится ключевым словом "elif".

Второй if использует команду test точно так же, как и первый. Проверяемое время здесь 18, что представляет собой 6 часов вечера. Если вторая проверка также дает "ложь", выполняется последний оператор в строке 8. Этот else не использует команду test, поскольку после выполнения первых двух проверок мы можем сделать вывод, что остался последний период времени, а именно период после 18:00.

            ИМЯ:    lastlog

lastlog Сообщает время последней регистрации

НАЗНАЧЕНИЕ

Записывает и выводит на экран день и время вашей последней регистрации в системе.

ФОРМАТ ВЫЗОВА

            lastlog [-l]

ПРИМЕР ВЫЗОВА

lastlog Печатает дату, когда вы последний раз регистрировались

ТЕКСТ ПРОГРАММЫ lastlog

    1   :
    2   # @(#) lastlog v1.0 Report last login time Author: Russ Sage
    2а                        Сообщает время последней регистрации

    4   if [ $# -gt 1 ]
    5     then echo "lastlog: arg error"  >&2
    6          echo "usage: lastlog [-l]" >&2
    7          exit 1
    8   fi

    10  if [ "$#" -eq "1" ]
    11    then if [ "$1" = "-l" ]
    12           then date >> $HOME/.lastlog
    13                lastlog
    14           else echo "lastlog: unrecognized option $1" >&2
    15                echo "usage: lastlog [-l]"             >&2
    16                exit 1
    17         fi
    18    else echo "Time of last login : `tail -2 $HOME/.lastlog |
    19         (read FIRST; echo $FIRST)`"
    20  fi

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

FIRST | Хранит первую из двух введенных строк
HOME | Хранит имя вашего регистрационного каталога

ОПИСАНИЕ

Зачем нам нужен lastlog?

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

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

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

Что делает lastlog?

Lastlog - это программа, которая записывает время вашей регистрации при каждом входе в систему. Затем это время хранится в файле данных в вашем регистрационном каталоге под именем $HOME/.lastlog. Имя файла lastlog начинается с точки с той целью, чтобы сделать его невидимым для команды ls. Укрытие "служебных" файлов от распечатки по умолчанию несколько предохраняет от любопытных глаз, а также убирает эти файлы с дороги, когда вы просматриваете что-то другое.

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

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

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

ПОЯСНЕНИЯ

В строках 4-8 выполняется проверка на наличие ошибок. Если вы вызвали lastlog с числом аргументов больше одного, то это приведет к ошибке. Выводится сообщение на стандартное устройство регистрации ошибок, и lastlog завершается со статусом ошибки 1.

Строки 10-20 представляют собой оператор if-then-else, который показывает, был ли это вызов для записи нового значения времени или для печати старых значений.

Если в строке 10 число позиционных параметров равно одному, то мы знаем, что либо этот параметр должен быть опцией -l, либо это ошибка. Следующий оператор if в строке 11 проверяет, является ли первый позиционный параметр опцией -l. Если да, то в файл $HOME/.lastlog добавляется текущая дата и lastlog вызывается снова без аргументов для печати предыдущей даты регистрации. (Мы только что видели, как это делается.) Если это не был аргумент -l, то строки 14-16 выполняют обработку ошибки.

Если число позиционных параметров равно нулю, выполняется оператор else в строке 18. Отсутствие опций означает, что мы хотим найти время нашей последней регистрации на машине и распечатать его. Это кажется довольно простым, но кто сказал, что машины просты?

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

Как видно из строки 18, она занимается получением последних двух строк. Команда tail красиво выполняет эту работу. Нам нужен такой способ, чтобы мы могли прочитать именно первую строку, а вторую отбросить, что выполняется в строке 19. Мы передаем по конвейеру выход команды tail подчиненному shell (указанному круглыми скобками), который читает первую строку и затем отображает ее. А что же со второй строкой? Она никогда не берется и пропадает. Другим способом может быть передача выхода команды tail по конвейеру команде "head -1". Поскольку эта команда не имеет других опций, мы не даем никаких примеров. Тем не менее, давайте теперь рассмотрим наше другое средство регистрации времени входа в систему.


ГЛАВА 5. Управление личной информацией I

            ИМЯ:      timelog

timelog Учет и статистика времени

НАЗНАЧЕНИЕ

Интерфейсное меню для слежения и сопровождения файлов регистрации времени.

ФОРМАТ ВЫЗОВА

            timelog

ПРИМЕР ВЫЗОВА

timelog Выводит на экран главное меню, из которого можно выбирать необходимое действие

ТЕКСТ ПРОГРАММЫ timelog

    1   :
    2   # @(#) timelog v1.0 Time accounting and statistics
    Author: Russ Sage
    2а                        Учет и статистика времени

    4   PROJ=""

    6   while :
    7   do
    8          set `date`
    9          echo "

    11  $1, $2 $3                  $4

    13          Time Logger
    14          -----------          Project: $PROJ
    15    s) Select a project file
    16    c) Create a new project file
    17    l) List current project files
    18    v) View the project file
    19    n) Turn billing on
    20    f) Turn billing off
    21    r) Report ststistics

    23    enter response (s,c,l,v,n,f,r,): \c"

    25          read RSP

    27          case $RSP in
    28          "")  break;;
    29          s)   echo "\Enter project name ( for exit): \c"
    30               read PROJ2
    31               if [ "$PROJ2" = "" ]
    32                 then continue
    33               fi
    34               if [ ! -s $PROJ2.time ]
    35                 then echo "you must specify a valid project
    file"
    36                      continue
    37               fi
    38               PROJ="$PROJ2";;
    39           c)  echo "\nEnter the new project name ( to
    exit): \c"
    40               read PROJ2
    41               if [ "PROJ2" = "" ]
    42                 then continue
    43               fi
    44               if [ -f "$PROJ2.time" ]
    45                 then echo "\n ** $PROJ2 already exists **"
    46                      continue
    47               fi
    48               PROJ="$PROJ2"
    49               echo "\nProject file created: $PROJ"
    50               echo "Project file created: `date`\nOFF: begin"
    > $PROJ.time;;
    51           l)  echo "\nCurrent project files:\n"
    52               ls -l *.time 2>/dev/null || echo "no project
    files" |
    53                 sed "s/\.time//";;
    54           v)  if [ "$PROJ" = "" ]
    55                 then echo "you must select a project file
    first"
    56                      continue
    57               fi
    58               echo "\n:----------------------------"
    59               more $PROJ.time
    60               echo ":---------------------------";;
    61           n)  if [ "$PROJ" = "" ]
    62                 then echo "you must select a project file
    first"
    63                      continue
    64               fi
    65               if [ "`tail -1 $PROJ.time|cut -d: -f1`" !=
    "OFF" ]
    66                 then echo "logging was not turned off"
    67                      continue
    68               fi
    69               echo "\nBilling turned on for project file:
    $PROJ"
    70               echo "ON: `date`" >> $PROJ.time;;
    71           f)  if [ "$PROJ" = "" ]
    72                 then echo "you must select a project file
    first"
    73                      continue
    74               fi
    75               if [ "`tail -1 $PROJ.time|cut -d: -f1`" !=
    "ON" ]
    76                 then echo "logging was not turned on"
    77                      continue
    78               fi
    79               echo "\nBilling turned off for project file:
    $PROJ"
    80               echo "OFF: `date`" >> $PROJ.time;;
    81           r)  while :
    82               do
    83                       echo "
    84          Statistics
    85          ----------            Project: $PROJ
    86    a)  Accumulative time totals
    87    n)  All times on
    88    f)  All times off

    90    enter response (a,n,f,): \c"

    92                       read RSP

    94                       case $RSP in
    95                       "")  break;;
    96                       a)   awk '/Total:/ { PRINT $0 }'
    $PROJ.TIME;;
    97                       n)   awk '/ON/ { print $0 }'
    $PROJ.time;;
    98                       f)   awk '/OFF/ { print $0 }'
    $PROJ.time;;
    99                       *)   echo "\n ** Wrong command,
    try again **";;
    100                      esac
    101             done;;
    102         *)  echo "\n ** Wrong command, try again **";;
    103         esac
    104 done

ПРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

PROJ | Содержит текущее имя проекта
PROJ2 | Содержит временное имя проекта, введенное пользователем
RSP | Содержит команду выбора из меню

ОПИСАНИЕ

Зачем нам нужен timelog?

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

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

Как раз почти все эти функции поддерживаются программой timelog.

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

Что делает timelog?

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

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

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

Например, чтобы напечатать отчет, вы должны вызвать timelog, выбрать меню статистики, затем выбрать нужный вам отчет. Здесь три уровня, а при наличии утилиты вы могли бы всего одной командой сказать "report report_file".

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

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

При вызове timelog на экран выводится начальное меню, как показано ниже.

    |
    |    Thu, Jun 19                     21:32:12
    |
    |            Time Logger
    |            -----------              Project:
    |      s) Select a project file
    |      c) Create a new project file
    |      l) List current project files
    |      v) View the project file
    |      n) Turn billing on
    |      f) Turn billing off
    |      r) Report statistics
    |
    |      enter response (s,c,l,v,n,f,r,):

В левом верхнем углу показан день недели и дата. В правом верхнем углу показано время. Это реальное время, и оно обновляется при каждом вызове меню. Имя меню "Time Logger" (регистратор времени). "Report statistics" (сообщить статистику) вызывает появление подчиненного меню.

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

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

    |
    |      Enter project name ( for exit):
    |      Введите имя проекта ( для выхода):

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

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

    |
    |      Enter the new project name ( to exit):
    |      Введите имя нового проекта ( для выхода):

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

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

Каждый файл проекта хранится на диске в формате "project. time".

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

Следующей опцией является v для просмотра файла проекта. Файлом, который вы собираетесь просмотреть, является текущий файл проекта. Его имя выводится в меню справа от слова "Project:". Если не появилось никакого имени, вы должны сперва создать новый проект или выбрать существующий. Файл проекта выводится на экран командой UNIX more. Следующей опцией является опция n для включения подсчета времени.

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

Следующая опция f отключает подсчет времени для файла проекта.

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

Последней опцией является r для отчета и статистики. После ее выбора на экран выводится подчиненное меню:

    |
    |     Statistics
    |     ----------            Project:
    |     a)  Accumulative time totals
    |     n)  All times on
    |     f)  All times off
    |
    |     enter response (a,n,f,):

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

ПРИМЕРЫ

        1.  c,l,v

Это первый набор команд при первоначальном запуске. Опция c пункт меню для создания файла проекта. Команда l выводит список всех имен файлов проектов, а v просматривает исходные данные, находящиеся в файле проекта.

         2.  n,n

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

         3.  s,junk

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

ПОЯСНЕНИЯ

В этом командном файле много текста, но замысел не очень сложен.

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

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

В строке 8 позиционным параметрам присваивается результат команды date. Поступая таким образом, мы можем затем легко обращаться к каждому полю без выделения его командой cut - shell выполняет за нас синтаксический разбор полей. Мы можем ссылаться на поля даты в виде $1, $2 и т.д.

Строки 9-23 выводят на экран главное меню. Верхняя строка обращается к данным из команды date. $1, $2 и $3 представляют собой день недели, месяц и число. $4 - это время. Перед тем как команда echo выводит текстовые строки, эти переменные раскрываются таким образом, что они появляются в меню. Если переменная PROJ равна нулю, то ничего не печатается в качестве имени текущего проекта. Символы \c в конце огромного оператора echo устанавливают курсор после приглашения в этой же строке, так что мы готовы принимать вводимые пользователем символы.

После печати меню в переменную RSP читается ответ в строке 25.

Затем наступает черед огромного оператора case (строки 27-103), который содержит ветку для каждой команды.

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

Иногда возврат каретки - более желательный метод выхода, чем команда exit интерпретатора shell. В конечном итоге эта команда shell приводит к выполнению программ exit и _exit Си -интерфейса. Выполнение вызова exit в Си иногда приводит к неожиданным побочным эффектам, в то время как нормальное выполнение текста программы до конца не дает таких же результатов. Однажды мы столкнулись с такой проблемой при использовании ESC -последовательностей для изменения цвета на цветном мониторе. Когда программа завершалась нормально, цвет не переустанавливался. Однако когда был сделан системный вызов exit, печатались некоторые ESC-последовательности, что переустанавливало отдельные части экрана. Очень странно!

Строки 29-38 управляют функцией выбора проекта. Имя проекта запрашивается и читается в переменную PROJ2. PROJ2 использована для временного хранения этого значения. Если был введен возврат каретки, оператор continue приводит к следующей итерации внешнего цикла while. Это позволяет пользователю прекратить выполнение этой функции при ошибочном вводе, оставаясь все же в timelog. Если ввод был непустым, файл проекта проверяется на существование и на наличие в нем данных. Если файл не существует, пользователя просят указать верное имя проекта.

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

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

Опция вывода списка выполняется в строках 51-53. Выводится заголовок, а затем команда ls используется для генерации списка. Если нет файлов нужного нам типа, то команда ls возвращает статус, отличный от нуля, тем самым включается оператор ||. В сущности этот фокус shell дает нам встроенный оператор if-then, который может использовать результаты предыдущей команды. Если выполнение команды ls неудачно (т.е. не найдены подходящие файлы), это сообщение об ошибке отбрасывается (не выводится) и выполняется оператор echo. Команда echo сообщает, что нет файлов, чтобы вы знали об этом.

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

Команда просмотра выполняется в строках 54-60. Текущий файл проекта проверяется на то, было ли выбрано имя. Если нет, главное меню выводится снова. В противном случае печатается строка из черточек, выводится командой more файл проекта и печатается еще одна строка черточек для обрамления выведенной информации. Вы можете удивиться, почему первым символом оператора echo является двоеточие (:). Это некоторый казус, поскольку, если вы попытаетесь отобразить символ черточки (-) в качестве первого символа, то оператор echo "подумает", что это пустая опция и не выведет ее на экран. Вы просто должны поставить в первой позиции какой-то непустой символ, отличный от черточки.

Включение подсчета времени выполняется в строках 61-70. Текущее имя проекта проверяется на то, было ли оно выбрано. Если да, то проверяется, был ли отключен файл проекта. Мы выполняем это, используя команду tail для выделения последней строки файла, затем передаем по конвейеру эту строку команде cut, где мы изменяем символ-разделитель на символ : и отрезаем первое поле. Если в этом поле находятся символы OFF, все в порядке. После этого строка 69 выводит на экран сообщение для пользователя, а строка 70 вставляет в файл проекта строку ON, за которой следует текущая дата. Тем самым файл отмечается как включенный. Подсчет времени начался. Если эта операция уже была включена, мы сообщаем об этом пользователю и выходим из данной операции меню.

Строки 71-80 обрабатывают отключение подсчета времени. Здесь почти все идентично тексту программы, который включает эту операцию, только если там было слово "on", то здесь слово "off".

Строки 81-101 обрабатывают подчиненное меню выдачи отчетов и статистики. Как видите, экранное меню спланировано таким же образом: цикл while, печать меню, чтение ответа, выполнение оператора case в зависимости от выбранной команды и т.д. Команда r подобна главному меню, только сокращена для того, чтобы поместиться внутри оператора case. Вы также можете заметить, что в строках 96-99 выполняется не очень много обработки. Это то место, где вы должны выполнить некоторую работу.

Строка 102 выполняет обработку ошибок при любом неверном вводе.

Печатается сообщение об ошибке, оператор case выходит на следующую итерацию цикла while, и все начинается сначала.

МОДИФИКАЦИИ

Основной возможностью для модификации является добавление фактической обработки информации о времени. Один из подходов к этому - запросить почасовой тариф времени в момент создания файла проекта (если считать, что вы работаете на таких основаниях). Этот тариф может храниться первым пунктом в файле проекта. Следующие две строки могут быть отведены для "счета по текущему сеансу" и "общего счета" соответственно. Когда подсчет времени работы над проектом отключается, можно проанализировать текущее системное время и начальное время подсчета и затем пересчитать в минуты (для упрощения арифметики) с использованием команды expr (или, возможно, awk). Затем это значение можно умножить на хранимый в файле тариф времени, а результат сохранить в записи о текущем сеансе работы и ДОБАВИТЬ к совокупной общей записи.

             ИМЯ:   today

today Печать календаря с подсвеченной сегодняшней датой

НАЗНАЧЕНИЕ

Модифицирует вывод утилиты cal для печати сегодняшней даты инверсным цветом.

ФОРМАТ ВЫЗОВА

             today

ПРИМЕР ВЫЗОВА

today Печатает календарь на этот месяц с подсвеченной сегодняшней датой

ТЕКСТ ПРОГРАММЫ today

    1   :
    2   # @(#) today v1.0  Calendar with today highlighted
    Author: Russ Sage
    2а                       Календарь с подсветкой сегодняшнего дня

    4   SYSV=n

    6   set `date`

    8   if [ "$SYSV" = "y" ]
    9     then RVR=`tput smso`
    10         BLNK=`tput blink`
    11         NORM=`tput rmso`
    12         cal ${2} ${6} | sed "s/${3}/${RVR}${BLNK}${3}${NORM}
    /g" -e "s/^/ /"
    13    else RVR="^[[7m"   # termcap so
    14         NORM="^[[0m"  # termcap se
    15         cal ${2} ${6} | sed -e "s/ ${3}/ ${RVR}${3}${NORM}
    /" -e "s/^/  /"
    16  fi

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

RVR | Управляющий символ инверсного отображения для вашего терминала
BLNK | Управляющий символ мерцания для вашего терминала, если таковой имеется
NORM | Управляющий символ для возврата терминала в обычный режим

ОПИСАНИЕ

Зачем нам нужен today?

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

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

Что делает today?

Today - это постпроцессор для команды cal, который делает ее результат более информативным и графически наглядным. Выполнение модификации команды cal зависит от того, в какой системе вы работаете. Если вы в системе UNIX System V (версия 2 или старше), то у вас есть утилита terminfo. Terminfo является заменой для файла termcap и поставляется с несколькими утилитами, которые возвращают значения с информацией о ПРИЕМЫ ПРОФЕССИОНАЛЬНОЙ РАБОТЫ В UNIX нальных характеристик. Если ваш компьютер работает не с системой System V, то вам необходимо немного поисследовать тип ваших конкретных терминалов и внести полученные значения в вашу программу.

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

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

Поскольку в пределах командного файла не так легко определить, с какой системой работает ваш компьютер, использована переменная SYSV. Эту переменную можно изменить при помощи редактора, поэтому today может работать в разных системах. Способами выяснения системы могли бы служить программа uname, существование определенных shell-программ в каталоге /bin или какой-нибудь системный файл, содержащий номер версии. По умолчанию переменная SYSV установлена так, чтобы утилита today работала НЕ в системе System V. С этим связано существование фрагмента программы, который нужно изменять вручную.

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

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

Termcap, похоже, не содержит информацию о том, как включить режим мерцания, поэтому вам, вероятно, нужно будет для этого посмотреть документацию на терминал. В системе System V (версия 2) команда tput возвращает соответствующее значение.

По умолчанию в today выполнены установки для ANSI терминала, являющегося консольным в системе XENIX. Эти коды были вручную извлечены из файла /etc/termcap и вставлены в текст программы. Если ваши коды отличаются, вы должны выяснить их. Обратите внимание, что в файле /etc/termcap символ ESCAPE представлен как \E. Это не годится для today, и вы должны изменить такое представление на настоящий ESCAPE. Поскольку ESCAPE является символом выхода из режима ввода в редакторе vi, вы должны использовать команду control-V в этом редакторе для ввода управляющих символов. Последовательность control-V вызывает печать символа ^, а ESCAPE - печать символа [. Таким образом, реальная команда входа в инверсный режим в редакторе vi представлена последовательностью ^[[7m. Эта команда включает символы ^[ в качестве ESCAPE и затем обычные символы [7m для изменения режима.

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

Данная программа не имеет опций или какого-то особого входа. Она распечатывает календарь с отмеченным сегодняшним днем.

ПОЯСНЕНИЯ

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

Если вы работаете с последними версиями системы System V, то вам нужно, чтобы эта переменная имела значение "y".

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

Строки 8-16 - это остальная часть программы. Они представляют собой один оператор if-then-else. Строки 9-12 поддерживают принятый в System V метод tput для получения характеристик терминала, а строки 13-15 управляют ручным способом их получения.

В обоих случаях переменным shell присваиваются ESC-последовательности. Эти значения используются позже. В обоих случаях выполняется вызов команды cal с использованием значений месяца и года, полученных от команды date. Этот образ календаря пропускается по конвейеру через утилиту sed, которая ищет указанный день "сегодня", также полученный от команды date. Когда этот день найден, sed заменяет цифры дня на последовательность включения инверсного режима, последовательность включения режима мерцания, если он есть, и символы дня, а затем последовательность возврата в обычный режим работы терминала. Последняя команда sed заполняет пробелами начало строки для размещения ее в центре экрана.

УПРАВЛЕНИЕ ДЕЛОПРОИЗВОДСТВОМ

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

Хотя мощные коммерческие программы для сохранения такой информации имеются в среде MS-DOS, эти программы не имеют широкого распространения в UNIX. Часто у вас нет необходимости в отдельной большой программе для выполнения таких работ в UNIX. UNIX обеспечивает хороший компромисс между легкими в применении, но не гибкими коммерческими программами и программированием на мощных, но непростых в использовании традиционных языках программирования. Возможности языка shell плюс богатый ассортимент встроенных команд UNIX дают мощный, гибкий и СРАВНИТЕЛЬНО простой в использовании компромисс. В данном разделе мы представляем широкий набор средств управления личной информацией, которые вы можете приспособить для своих нужд.

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

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

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


ГЛАВА 5. Управление личной информацией I

            ИМЯ:   jargon

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

             jargon

ПРИМЕР ВЫЗОВА

jargon
125 		Если ввести 125 в ответ на запрос, то на выходе получится фраза Total Monitored Concept

ТЕКСТ ПРОГРАММЫ jargon

    1   :
    2   # @(#) jargon v1.0 Technical jargon generator
    Author: Russ Sage
    2а                       Генератор технического жаргона

    4   echo "\n\t\tThe Jargon Generator"
    5   while :
    6   do
    7           echo "\nEnter a 3 digit number (000-999), ?
    , or : \c"
    8           read NUM

    10          case $NUM in
    11          "") exit;;
    12          \?) cat < /dev/
    null ||
    27                    { echo "\nNot a valid number, try again
    "; continue; };;
    28          *)   echo "\nInvalid input, try again"
    29               continue;;
    30          esac

    32          N1=`echo $NUM|cut -c1`
    33          N2=`echo $NUM|cut -c2`
    34          N3=`echo $NUM|cut -c3`
    35          SEN=""

    37          case $N1 in
    38          0)  SEN="${SEN}Integrated ";;
    39          1)  SEN="${SEN}Total ";;
    40          2)  SEN="${SEN}Systematized ";;
    41          3)  SEN="${SEN}Parallel ";;
    42          4)  SEN="${SEN}Functional ";;
    43          5)  SEN="${SEN}Responsive ";;
    44          6)  SEN="${SEN}Optional ";;
    45          7)  SEN="${SEN}Synchronized ";;
    46          8)  SEN="${SEN}Compatible ";;
    47          9)  SEN="${SEN}Balanced ";;
    48          esac

    50          case $N2 in
    51          0)  SEN="${SEN}Management ";;
    52          1)  SEN="${SEN}Organizational ";;
    53          2)  SEN="${SEN}Monitored ";;
    54          3)  SEN="${SEN}Reciprocal ";;
    55          4)  SEN="${SEN}Digital ";;
    56          5)  SEN="${SEN}Logistical ";;
    57          6)  SEN="${SEN}Transitional ";;
    58          7)  SEN="${SEN}Incremental ";;
    59          8)  SEN="${SEN}Operational ";;
    60          9)  SEN="${SEN}Third-Generation ";;
    61          esac

    63          case $N3 in
    64          0)  SEN="${SEN}Options";;
    65          1)  SEN="${SEN}Flexibility";;
    66          2)  SEN="${SEN}Capability";;
    67          3)  SEN="${SEN}Mobility";;
    68          4)  SEN="${SEN}Programming";;
    69          5)  SEN="${SEN}Concept";;
    70          6)  SEN="${SEN}Time-Phase";;
    71          7)  SEN="${SEN}Projection";;
    72          8)  SEN="${SEN}Hardware";;
    73          9)  SEN="${SEN}Contingency";;
    74          esac

    76          echo "\n\"$SEN\""
    77  done

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

N1 | Первая цифра числа
N2 | Вторая цифра числа
N3 | Третья цифра числа
NUM | Число, введенное пользователем с клавиатуры
SEN | Предложение, полученное из найденных слов

ОПИСАНИЕ

Зачем нам нужен jargon?

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

Одним из простых способов получения таких вещей является наличие таблицы взаимозаменяемых слов, которые можно использовать для составления предложений. Если у вас система BSD, то вы можете запустить jargon вместе с программой fortune, которая имитирует случайные подарки судьбы. Мудрость веков может принадлежать вам!

Что делает jargon?

Jargon - это инструмент для генерации фраз, состоящих из технических терминов. Строится фраза, которая является комбинацией трех слов. Первые два слова являются прилагательными, используемыми для описания третьего слова, имени существительного. Каждое слово выбирается из столбца, содержащего десять возможных слов. Именно это придает программе jargon ее творческие способности. Она может объединять слово из одного столбца с любым словом из другого столбца для построения многих полезных фраз. Если вы хотите увидеть всю таблицу слов, введите символ ?. Вот пример таблицы:

    |
    |                 The Jargon Generator
    |
    |    Enter a 3 digit number (000-999), ?, or : ?
    |
    |    0. Integrated        0. Management       0. Options
    |    1. Total             1. Organizational   1. Flexibility
    |    2. Systematized      2. Monitored        2. Capability
    |    3. Parallel          3. Reciprocal       3. Mobility
    |    4. Functional        4. Digital          4. Programming
    |    5. Responsive        5. Logistical       5. Concept
    |    6. Optional          6. Transitional     6. Time-Phase
    |    7. Synchronized      7. Incremental      7. Projection
    |    8. Compatible        8. Operational      8. Hardware
    |    9. Balanced          9. Third-Generation 9. Contingency
    |

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

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

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

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

ПРИМЕРЫ

        1.  $ jargon
         898

Открывается секрет, что у меня персональный компьютер 898, то есть Compatible Third-Generation Hardware (совместимая аппаратура третьего поколения).

         2.  $ jargon
          187

Оказывается это Total Operational Projection (всеобъемлющий разрабатываемый проект).

ПОЯСНЕНИЯ

Строка 4 печатает заголовок при начальном запуске программы.

Отображается возврат каретки, две табуляции и сообщение.

Строки 5-77 представляют собой один большой бесконечный цикл while. В нем имеется всего одна точка выхода, находящаяся внутри оператора case. Строка 7 выводит приглашение, а строка 8 читает вводимый ответ в переменную NUM.

Строки 10-30 являются оператором case, который проверяет информацию, введенную с клавиатуры. Если был введен только возврат каретки, строка 11 рассматривает это как нулевой ввод. В этом случае выполняется выход из программы. Это и есть нормальная точка выхода.

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

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

В строках 12-24 команда cat получает текст из последующего фрагмента самой программы. Такого рода файл иногда называют "встроенным документом". Возможность обработки встроенного документа активируется последовательностью символов <<. Слово, которое следует за ней, является признаком начала-окончания, в данном случае EOF. После того, как текст будет выведен на экран, строка 25 продолжает выполнение следующей итерации внешнего цикла while.

Попутно отметим: для того, чтобы увидеть, как shell управляет встроенными документам, посмотрите во время работы командного файла каталог /tmp. В нем находится файл с именем "shXXXX", где XXXX - идентификатор shell, создавшего этот файл. Весь встроенный документ целиком помещается в этот временный файл. Затем shell выполняет переадресацию своего входа на этот временный файл. Довольно простой метод.

Строка 26 соответствует всем случаям ввода, когда имеется три символа. Эти символы могут быть буквами и/или цифрами. В этом месте shell еще не знает, есть ли там буквы. Для проверки того, что все введенные символы являются цифрами, мы должны использовать команду expr, выполняющую дополнительный анализ. Оператор expr указывает, что нужно сравнить строку NUM с последовательностью "начало строки, цифра, цифра, цифра, конец строки". Если сопоставление успешно, expr возвращает статус успешного возврата и программа идет дальше. Поскольку expr возвращает число совпавших символов, этот результат должен быть перенаправлен в каталог /dev/null.

Если сравнение завершилось неудачей, активизируется оператор || (мы уже видели такого рода управляющую структуру ранее), который печатает сообщение об ошибке и вызывает следующую итерацию цикла while. Такой синтаксис представляет собой то же самое, что и оператор if-then-else. Поскольку за символами || может следовать список команд, то внутрь простых разделителей списка {} можно вставить более одной команды. Будьте внимательны. Если отсутствуют символы-разделители, то оператор continue выполнится как после сообщения об ошибке, ТАК И при успешном выполнении команды expr. Это может заставить вас заниматься отладкой, пока вы не обнаружите, что же произошло на самом деле.

Точно такую же проверку числа можно было бы выполнить с помощью оператора case. Синтаксис был бы таким же, за исключением зацепочных символов ^ и $. Шаблон для оператора case выглядел бы так:

          [0-9][0-9][0-9]  statement;;

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

Любой другой ввод перехватывается в строке 28 путем проверки на совпадение с универсальным символом-заменителем *. Выводится сообщение об ошибке, и оператор continue вызывает следующую итерацию цикла while, который запрашивает новый ввод.

Обратите внимание, как shell рассматривает строки. Команда test фактически выполняет сравнение значения строки. Несмотря на то, что команде test(1) посвящена своя страница справочного руководства, она является встроенной функцией shell. Если при вызове команды test использован синтаксис =, !=, то два аргумента рассматриваются как строки. Но если в команде test используется синтаксис вида -lt, -eq, то производится сравнение двух аргументов-строк как чисел и выполняется их числовая обработка. Эти два различных режима нельзя смешивать, т.е. нельзя сравнивать строки при помощи числового оператора, например str1 -eq str2.

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

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

Строки 37-48 представляют собой первый оператор case. Оператор case берет значение переменной N1 и добавляет слово с таким номером к предложению. На самом деле нет необходимости включать значение переменной SEN в правую часть присваивания, поскольку еще ничего нет. Однако это делает текст программы более гибким, если мы решим предварительно сгенерировать первоначальное предложение некоторыми другими средствами. Аналогичные операторы case обрабатывают две следующие цифры.

Полученное предложение выводится в строке 76 после того, как найдены все слова. Вы могли бы сказать, что вся эта штука представляет собой 754 подвиг, или Synchronized Logistical Programming (Синхронизированное логическое программирование).

МОДИФИКАЦИИ

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

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

            ИМЯ:  phone

phone База данных с телефонными номерами

НАЗНАЧЕНИЕ

Управляемое меню средство, поддерживающее базу данных с телефонными номерами

ФОРМАТ ВЫЗОВА

          phone

ПРИМЕР ВЫЗОВА

phone | Вызов телефонной базы данных
s | Ввод опции поиска
russ | Поиск номера телефона Расса

ТЕКСТ ПРОГРАММЫ phone

    1   :
    2   # @(#) phone v1.0 Maintain telephone database
    Author: Russ Sage
    2а                      Сопровождение телефонной базы данных

    4   if [ $# -gt 0 ]
    5     then  echo "phone: argument error" >&2
    6           echo "usage: phone"          >&2
    7           exit 1
    8   fi

    10  BASE="$HOME/.phone.list"

    12  while :
    13  do
    14         echo "

    16  phonebase = $BASE

    18        PHONE MENU
    19        ----------
    20    add name to list
    21    delete name from list
    22    edit list
    23    search for name in list
    24    view complete list
    25     - exit program

    27  Press a,d,e,s,v or : \c"
    28  read RSP

    30          case $RSP in
    31          "")     exit 0
    32                  ;;
    33          a|A)    echo "\nEnter name to add ( to exit
    ): \c"
    34                  read NAME
    35                  if [ "$NAME" = "" ]
    36                    then continue
    37                  fi
    38                  echo "Enter description of person: \c"
    39                  read DESC
    40                  echo "Enter number to add: \c"
    41                  read NUM
    42                  echo "$NAME\t$DESC\t\t\t$NUM" >> $BASE
    43                  sort -t" " +1 -1.3b -o $BASE $BASE
    44                  ;;
    45          d|D)    echo "\nEnter name to delete ( to exit
    ): \c"
    46                  read NAME
    47                  if [ "$NAME" = "" ]
    48                    then continue
    49                  fi
    50                  sed -e "/$NAME/d" $BASE.new
    51                  mv $BASE.new $BASE
    52                  ;;
    53          e|E)    vi $BASE
    54                  ;;
    55          s|S)    echo "\nEnter name to search: \c"
    56                  read NAME
    57                  echo "\n----------------------------------"
    58                  grep -y "$NAME" $BASE
    59                  echo "------------------------------------"
    60                  ;;
    61          v|V)    echo "\n\tPhone List\n\t---------" &
    62                  more $BASE
    63                  echo "\nhit \c"
    64                  read RSP
    65                  ;;
    66          *)      echo "Not a valid command"
    67                  ;;
    68          esac
    69  done

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

BASE | Фактическое имя файла телефонной базы данных
DESC | Описание, вводимое в базу данных
NAME | Имя, вводимое в базу данных
NUM | Номер телефона, вводимый в базу данных
RSP | Ответ пользователя на приглашение

ОПИСАНИЕ

Зачем нам нужен phone?

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

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

Что делает phone?

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

Файл, представляющий собой базу данных, размещен в вашем регистрационном каталоге. Это файл $HOME/.phone.list. Phone использует ваш регистрационный каталог, так что один и тот же исполняемый модуль работает для любого пользователя. Независимо от того, где размещается командный файл phone или кто и откуда его запустил в вашей системе, вы всегда получите именно ваш файл телефонных номеров, поскольку он подключен к вашему регистрационному каталогу посредством встроенной переменной shell с именем HOME.

Имеется всего одно главное меню. Оно выводится на экран, и внизу появляется приглашение. Большинство вариантов выбора запрашивают дополнительную информацию. Меню выглядит так:

    |
    |
    |  phonebase = /usr/russ/.phone.list
    |
    |         PHONE MENU
    |         ----------
    |    add name to list         (добавить имя к списку)
    |    delete name from list    (удалить имя из списка)
    |    edit list                (редактировать список)
    |    search for name in list  (поиск имени в списке)
    |    view complete list       (просмотр списка)
    |     - exit program      (выход из программы)
    |
    |  Press a,d,e,s,v or :
    |

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

ПРИМЕРЫ

         1. $ phone
        a
         russ sage
         unix master
         123-4567

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

         2.  $ phone
              V

Просмотр всех записей в вашей базе данных.

ПОЯСНЕНИЯ

Строки 4-8 выполняют некоторую проверку на наличие ошибок. Если вы вызвали phone с какими-либо аргументами, выводится сообщение об ошибке и программа завершается. Поскольку phone управляется при помощи меню, она не использует аргументы.

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

Строки 12-69 представляют собой один большой бесконечный цикл while. Вся эта структура подобна структуре других программ, управляемых с помощью меню, которые мы рассмотрели. Само меню выводится оператором echo. Текст меню НЕ выделен отступами подобно остальной части программы, поскольку оператор echo воспринимает этот текст как литеральные данные, включая пробелы.

Строка 28 читает ответ пользователя, а строки 30-68 представляют собой оператор case, который проверяет значение ответа. Если был нулевой ввод, программа завершается.

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

Если была введена буква d, то она распознается в строке 45. Запрашивается удаляемое имя. Если вводится только возврат каретки, оператор continue возвращает нас обратно в цикл while и снова выводится главное меню. Для удаления записи использована команда sed, поэтому вводимое имя должно указываться точно в таком же виде, как оно хранится в файле. Результат удаления помещается во временный файл, который затем в строке 51 переименовывается в исходный файл базы данных. Одной из модификаций может быть показ пользователю того, что он собирается удалять, и запрос подтверждения. Если подтверждение получено, то данные удаляются окончательно. Сейчас сделано так, что вы на самом деле не знаете, что именно собираетесь удалять. Поэтому необходимо точно указывать удаляемое имя. Здесь выбран относительно быстрый и не совсем честный подход. А вообще вы даже могли бы использовать непосредственно редактор vi для поиска и удаления.

Опция редактирования в строке 53 выполняет только одно - вызывает редактор vi для работы с базой телефонных номеров. Используя редактор vi, вы можете вручную сделать файл базы данных таким, как вам нужно. Зачастую формат входных данных "плывет" из-за разной длины. Все данные хранятся в свободном формате и поэтому их можно как угодно смещать вдоль строки.

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

Для выхода из редактора и возврата к утилите phone используйте обычные методы, такие как ZZ, :x и :wq.

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

Опция просмотра начинается со строки 61. Сначала выводится заголовок в фоновом режиме. Это делается для быстроты. На самом деле это просто эксперимент для того, чтобы увидеть, будут ли асинхронные события производить вывод на экран синхронно. В данном случае это именно так. Файл выводится командой more. Пока утилита more постранично выводит файл, все ее команды нам доступны. (Наш командный файл m в главе 4 иллюстрирует, что мы можем делать с помощью more.)

В строке 63 выводится сообщение пользователю о том, что он должен нажать возврат каретки. Строка 64 выполняет чтение. Эта последовательность придерживает распечатанный файл данных на экране, чтобы его строки не пропали при сдвиге вверх. Если бы этих операторов не было, напечаталось бы главное меню и мы потеряли бы то, что находилось в верхней части экрана.

Строка 66 выполняет проверку на ошибки для меню в целом. Если вы ввели данные, которые не соответствуют ни одному из предыдущих шаблонов, то им сопоставляется шаблон *. В этом случае печатается сообщение об ошибке, управление переходит из оператора case в конец цикла while и при следующей итерации этого цикла снова печатается главное меню.

             ИМЯ:  office

office Делопроизводитель

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

             office

ПРИМЕР ВЫЗОВА

office Проверяет мой почтовый ящик и сообщает, пуст ли он m

ТЕКСТ ПРОГРАММЫ office

    1   :
    2   # @(#) office v1.0  Office Manager  Author: Russ Sage
    2а                        Делопроизводитель

    4   if [ $# -gt 0 ]
    5     then  echo "office: argument error" >&2
    6           echo "usage: office"          >&2
    7           exit 1
    8   fi

    10  while :
    11  do
    12          c
    13          set `date`
    14          echo "
    15  $1, $2 $3             $4

    17         Office Menu
    18         -----------
    19          Mail
    20          News
    21          Calendar
    22          Phone
    23          Automatic Reminders
    24          Shell Command
    25           to exit

    27  press m,n,c,p,a,s or  : \c"

    29          read CMD

    31          case $CMD in
    32          "")     exit;;
    33          m|M)    if [ -s /usr/spool/mail/$LOGNAME ]
    34                    then  echo
    35                          ll /usr/spool/mail/$LOGNAME
    36                          echo "\nWould yuo like to see it (
    y/n): \c"
    37                          read CMD
    38                          if [ "$CMD" = "y" ]
    39                            then echo
    40                                 mail
    41                          fi
    42                   else echo  "\nNo mail today"
    43                   fi
    44                   echo "\nhit \c"
    45                   read CMD;;
    46          n|N)     PWD=`pwd`
    47                   cd /usr/news
    48                   echo "\nThe following files are news item
    in /usr/news:\n"
    49                   lc
    50                   echo "\nEnter filename or  to exit: \c"
    51                   read NAME
    52                   while [ "$NAME" != "" ]
    53                   do
    54                           if [ -s $NAME ]
    55                            then echo "\n-------------------"
    56                                 cat $NAME
    57                                 echo "---------------------"
    58                            else echo "$NAME is not a news
    file"
    59                           fi
    60                           echo "\nEnter filename or  to
    exit: \c"
    61                           read NAME
    62                   done
    63                   cd $PWD;;
    64          c|C)     echo "\n"
    65                   today
    66                   if [ -s $HOME/calendar ]
    67                     then echo "Calendar file:\n`ll $HOME/
    calendar`"
    68                          echo "\nCalendar notifications:"
    69                          PWD=`pwd`
    70                          cd $HOME
    71                          calendar
    72                          cd $PWD
    73                          echo "\nCheck your mail for calendar
    notifications"
    74                     else echo "\nYou do not have a calendar
    file at $HOME"
    75                   fi
    76                   echo "\nhit \c"
    77                   read CMD;;
    78          p|P)     phone;;
    79          a|A)     greet
    80                   $HB/at 11:45 echo ^GLunch in 15 minutes
    81                   $HB/at 16:45 echo ^GShift change in 15
    minutes

    83                   echo "\nYou will receive notices at 11:45
    & 4:45"
    84                   echo "\nWould you like to leave some
    reminders (y/n): \c"
    85                   read CMD
    86                   if [ "$CMD" = "y" ]
    87                     then echo "\nThe syntax is: at HR:MN
    executable_phrase"
    88                          echo "The time now : `date '+%T'`"
    89                          echo "\n\ncmd ( to exit): \c"
    90                          read CMD
    91                          while [ "$CMD" != "" ]
    92                          do
    93                                  eval $CMD
    94                                  echo "cmd ( to exit
    ): \c"
    95                                  read CMD
    96                          done
    97                   fi;;
    98          s|S)    echo "\nenter command: \c"
    99                  read CMD
    100                 eval $CMD
    101                 echo "\nhit \c"
    102                 read CMD;;
    103         *)      echo "\nInvalid command"
    104                 echo "\nhit \c"
    105                 read CMD;;
    106         esac
    107 done

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

CMD | Содержит различные команды на разных уровнях меню
HOME | Еще одна экспортируемая переменная, взятая из среды выполнения
LOGNAME | Shell-переменная, предварительно экспортированная
NAME | Содержит имя файла с новостями

ОПИСАНИЕ

Зачем нам нужен office?

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

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

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

Что делает office?

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

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

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

Главное меню содержит все доступные функции. Оно выглядит так:

    |
    |    Fri, Jun 20                       16:18:23
    |
    |           Office Menu
    |           -----------
    |             Mail                 (Почта)
    |             News                 (Новости)
    |             Calendar             (Календарь)
    |             Phone                (Телефоны)
    |             Automatic Reminders  (Автоматические напоминания)
    |             Shell Command        (Переход в shell)
    |              to exit         (Выход)
    |
    |     press m,n,c,p,a,s or  :
    |

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

Первым вариантом выбора является почта. Здесь ваш почтовый файл проверяется на ненулевой размер. Если размер нулевой (или такой файл отсутствует), выводится сообщение "no mail today" (сегодня почты нет). Если для вас есть почта, то информация из файла почтового ящика распечатывается в длинном формате командой ls и выводится запрос о том, хотите ли вы прочитать вашу почту. Если вы сказали "yes" (да), выполняется обычная команда UNIX mail.

Вторым вариантом выбора являются новости. Файлы новостей хранятся в одном каталоге, где каждая порция новостей представляет собой независимый файл. Все порции новостей хранятся в специальном каталоге с именем /usr/news. Office выводит список всех файлов новостей и затем спрашивает, хотите ли вы просмотреть один из них. Если да, введите имя файла - и он распечатается командой more. Мы предполагаем, что у вас есть сетевая связь между пользователями или какое-то другое средство для получения новостей.

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

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

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

Пятый вариант выбора - автоматические напоминания. Эта опция предназначена для того, чтобы помогать вам в слежении за важными моментами времени в течение дня. Первым делом она приветствует вас в духе утилиты greet. Затем запускаются в фоновом режиме два командных файла at. Они срабатывают в 11:45 для объявления о ленче и в 16:45 для объявления о конце рабочего дня. Если вы хотите запустить некоторые из ваших собственных командных файлов в фоновом режиме выполнения, ответьте "yes" (да) на следующий вопрос. Для напоминания выводится синтаксис утитлиты at, печатается текущее время, и ожидается ваш ввод команд для at. Красиво и просто в применении! (Если в вашей системе имеется встроенная возможность at, а вы реализовали нашу версию at под другим именем или в другом каталоге, проследите за соответствующими изменениями в тексте командного файла.)

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

Последняя опция предназначена для выполнения любой желаемой команды вне программы office, оставаясь тем не менее в office. Вы даже можете бы запустить еще один shell вне программы office (введя с клавиатуры sh), а затем по control-D вернуться в office. Нет почти ничего невозможного.

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

ПРИМЕРЫ

       $ office
       s
       sh
       $

Запуск office и выбор работы с shell. Затем запуск shell-команды с именем sh, т.е. самого shell. При этом вы запускаете новый shell и получаете его символ приглашения. По окончании работы с этим shell введите ^d для выхода из него и возврата к главному меню утилиты office.

ПОЯСНЕНИЯ

Строки 4-8 выполняют проверку на ошибки. Если вы вызвали office с какими-либо опциями, то вы создали ситуацию ошибки.

Строки 10-107 - это один большой цикл while. Его структура подобна ранее рассмотренным утилитам, управляемым с помощью меню. Строка 12 очищает экран командой c (см. главу 7). Если вы не хотите читать с забеганием вперед и включать в текст эту команду, вы можете пока пропустить эту строку или, если у вас система BSD, использовать команду clear. В строке 13 позиционным параметрам присваивается результат команды date.

Строки 14-27 выводят само меню, а строка 29 читает вводимую пользователем команду. Строки 31-106 представляют собой оператор case, который распознает введенную команду и выполняет соответствующие действия.

Строка 32 выполняет выход из программы office, если был введен только возврат каретки. Строки 33-45 управляют командой mail. Вначале с помощью команды test проверяется, имеет ли файл с почтовыми сообщениями размер больше нуля. Если да, выводится пустая строка и почтовый файл печатается в длинном формате команды ls. Затем вам задается вопрос о том, хотите ли вы просмотреть почту. Еслы вы вводите "y", вызывается команда mail. При любом другом вводе выполняется возврат в главное меню.

Строки 46-63 обрабатывают команду новостей. Сначала переменная PWD устанавливается на наш текущий рабочий каталог. Это делается для того, чтобы при переходе командой cd в какое-то другое место, мы смогли по окончании обработки вернуться туда, где мы начали работать. Затем по команде cd мы переходим в каталог /usr/news. Это каталог, в котором хранятся файлы новостей. В системе Berkeley доступен каталог /usr/msgs. Этот каталог очень похож на /usr/news и может использоваться вместо него, если у вас есть такое желание. Выводится в виде столбцов список файлов из каталога /usr/news (строка 49), и вам задается вопрос о том, хотите ли вы посмотреть какие-нибудь из этих файлов. Если вы вводите возврат каретки, команда test воспринимает это как нулевой ввод и выполняется выход из оператора while.

Цикл while выполняется до тех пор, пока значение переменной NAME не равно нулю (строка 52). Введенное имя проверяется на существование. Если такой файл существует, то он распечатывается командой cat, обрамленной строками из черточек. Если файл не существует, то выводится сообщение о том, что такого файла новостей нет. Затем вас просят ввести имя другого файла. Этот процесс продолжается в цикле, пока вы не введете только возврат каретки. Когда вы выходите из цикла в строке 62, выполняется команда cd для возврата в тот каталог, откуда вы начали работать. Эта команда cd не оказывает никакого влияния после запуска утилиты office. Вы должны указать команду cd вашему регистрационному shell. Никакая другая программа, запущенная из порожденного shell, не может выполнить для вас команду cd.

Строки 64-77 выполняют функции календаря. Сначала выводятся две пустые строки. Почему две? Потому что обычно команда echo вставляет символ новой строки в конце каждой выводимой строки, а выводимой в данный момент строкой является еще один символ новой строки. Затем в строке 65 запускается программа today. Она печатает изображение календаря на месяц. Строка 66 проверяет, имеется ли календарный файл в вашем регистрационном каталоге. Если да, он выводится в длинном формате команды ls в строке 67. Печатается также сообщение-заголовок "Calendar Notifications" (календарные извещения).

Строка 69 запоминает текущий рабочий каталог переменной PWD. Затем мы переходим командой cd в каталог $HOME, чтобы находиться в том же каталоге, что и календарный файл. В строке 71 вызывается команда UNIX calendar. Она просматривает календарный файл и выводит все подходящие по времени сообщения. По команде cd мы возвращаемся в наш исходный каталог для того, чтобы восстановить порядок. Строка 73 выводит сообщение, чтобы вы проверили вашу почту на наличие календарных записей. Как указывалось ранее, команда calendar работает двумя способами.

Оператор else в строке 74 выводит вам сообщение, если у вас нет календарного файла. По завершении оператора if, в строках 76 и 77 печатается сообщение о том, что нужно нажать возврат каретки, и выполняется оператор read. Это блокирующее чтение, которое означает остановку и ожидание какого-либо ввода. Это противоположность неблокирующему чтению, или более широко известной технике чтения с опросом. Опрос возможен в программах, написанных на Си, но запуск опроса в shell может потребовать немного больших усилий. Для изучения чтения с опросом ознакомьтесь с главой, описывающей терминальные устройства.

Строка 78 управляет командой phone. Она вызывает утилиту phone, которую мы рассмотрели ранее, после чего управление передается в конец цикла while. Печатается главное меню, и все начинается сначала. Строки 79-97 управляют автоматическими напоминаниями. Сначала запускается программа greet. Она была описана ранее в этой главе. Затем в строках 80 и 81 в режиме фонового выполнения вызываются два командных файла at, которые выполнятся позже в течение дня. В строке 84 вам задается вопрос, желаете ли вы оставить какие-то свои напоминания. Если вы отвечаете "y", выполняются строки 87-96. Если вы не ввели "y", оператор else не выполняется, поэтому управление передается главному меню. Вы видите, что эта возможность предполагает однократное использование в течение дня.

Строки 87-89 выводят некоторую информацию, необходимую для того, чтобы вы завели для себя напоминания. Выводится синтаксис программы at, текущая дата (только время) и затем приглашение вводить вашу команду. Строка 90 читает вашу команду. Строка 91 означает, что если был введен не только возврат каретки, то нужно выполнить этот цикл. Поэтому цикл while продолжается, пока вы не нажмете только возврат каретки. Строка 93 пытается выполнить команду, которую вы ввели. Команда eval используется для того, чтобы раскрыть значения тех переменных, которые могли быть не раскрыты ранее. Строка 94 просит ввести следующую команду, а строка 95 читает ее. Поскольку чтение стоит в самом конце цикла, оператор while выполняет проверку нового ввода с клавиатуры.

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

Если вы хотите перейти в shell, выполните команду sh - и она породит shell. Строки 101 и 102 не дают появиться на экране главному меню до тех пор, пока вы не нажмете возврат каретки.

Строки 103-105 выполняют проверку на ошибки ввода. Любая неправильная команда попадает сюда. Выводится сообщение об ошибке и выдается приглашение "hit " (нажмите возврат каретки).


ГЛАВА 6. Управление личной информацией II

ОСВЕДОМЛЕННОСТЬ ПОЛЬЗОВАТЕЛЯ И ЗАЩИТА ЛИЧНОЙ ИНФОРМАЦИИ

ОСВЕДОМЛЕННОСТЬ ПОЛЬЗОВАТЕЛЯ

activ | показать активность терминалов
info | показать информацию о паролях пользователей
uchk | посмотреть процессы других пользователей
watch | наблюдать за регистрацией в системе
whox | команда who с дополнительными возможностями

ЗАЩИТА ЛИЧНОЙ ИНФОРМАЦИИ

acme | показать учетную информацию
inuse | запретить использование терминала
lock | блокировать и разблокировать файлы

УПРАВЛЕНИЕ ЛИЧНОЙ ИНФОРМАЦИЕЙ II:

ОСВЕДОМЛЕННОСТЬ ПОЛЬЗОВАТЕЛЯ И ЗАЩИТА ЛИЧНОЙ ИНФОРМАЦИИ

ВВЕДЕНИЕ

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

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

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

Поэтому мы здесь представим две группы инструментальных средств. Средства ОСВЕДОМЛЕННОСТИ ПОЛЬЗОВАТЕЛЯ помогают нам следить за тем, что делают другие, и тем самым упростить связь с ними. Средства ЛИЧНОЙ ЗАЩИТЫ важны для защиты нашей учетной информации и наших данных и для получения информации о наших собственных действиях.

Первая группа рассматриваемых здесь средств, относится к "осведомленности пользователя". Этими средствами являются activ, info, uchk, watch и whox.

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

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

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

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

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

ОСВЕДОМЛЕННОСТЬ ПОЛЬЗОВАТЕЛЯ

      ИМЯ: activ

activ Показывает активность терминалов

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

       activ

ПРИМЕР ВЫЗОВА

activ Выводит на экран информацию об активности пользователей на своих терминалах

ТЕКСТ ПРОГРАММЫ

    1  :
    2  # @(#) activ v1.0  Display terminal activity  Author: Russ Sage
    2а                    Показать активность терминалов

    4  if [ $# -gt 0 ]
    5    then echo "activ: argument error" >&2
    6         echo "usage: activ"          >&2
    7         exit 1
    8  fi

    10 who -u | cut -c1-17,38-42

ОПИСАНИЕ

Зачем нам нужен командный файл activ?

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

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

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

Что делает activ?

Activ печатает информацию об активности всех пользователей системы. Распечатка содержит имя пользователя, номер терминала, на котором он зарегистрировался, и время последнего обращения к клавиатуре. Если клавиатура была использована в течение последней минуты, этот терминал отмечается точкой. Если терминал не был использован в течение последних 24 часов, напротив него печатается слово "old". Во всех остальных случаях выводится время последнего использования терминала в минутах и секундах. Вот пример такой распечатки:

    |
    |    root      tty01  01:23
    |    sage      tty05   .
    |    batch     tty12  old
    |    nuucp     tty16   .
    |

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

То, что мы ищем, уже есть в системе. Это опция -u команды who. Эта опция, однако, существует только в системе AT&T UNIX System V, поэтому команда activ, по крайней мере в представленном виде, может быть реализована только в этой системе. Activ берет выход команды "who -u" и "отрезает" некоторые данные, не имеющие отношения к нашей цели.

Путем проб и ошибок, а затем проверки исходного кода я обнаружил, что значение, используемое в команде "who -u", является временем "модификации" файла устройства, которое сообщает системный вызов stat(2). На страницах руководства по stat дается полная таблица системных вызовов, которые изменяют временные характеристики файлов. В табл. 6.1 представлены эти характеристики:

Таблица 6.1
Временные характеристики файла

Временные характеристики | Системные вызовы, изменяющие временные характеристики
Время доступа | creat, mknod, pipe, utime, read
Время модификации | creat, mknod, pipe, utime, write
Время создания | creat, mknod, pipe, utime, write, chmod, chown, link

Давайте на некоторое время отвлечемся и поговорим о том, что эти временные характеристики означают и как они могут быть модифицированы. Одно интересное замечание: UNIX дает вам как обычному пользователю возможность установить любое время доступа и время модификации файла по вашему желанию. Это и хорошо, и плохо. Это обеспечивает гибкость, когда дело касается информации индексного дескриптора файла, но также позволяет скрывать следы при нарушении защищенной информации. Возьмем, например, файл login. Кто-нибудь мог бы вставить новую строку регистрации перед старой и затем вернуть время доступа и время модификации в исходные значения. Тогда бы никто, просматривая файл /bin/login командой ls, не смог бы сказать, что файл был изменен.

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

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

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

Какое же все это имеет отношение к определению того, делал ли пользователь что-либо недавно за своим терминалом? Время модификации изменяется вызовом write(2). Поэтому представляется разумным, что запись на терминал будет иметь место, когда драйвер читает символ, а затем посылает его обратно на экран. По мере того, как кто-то нажимает на клавиши, время модификации постоянно обновляется при эхо-отображении символов. Когда символы перестают поступать с клавиатуры, приостанавливается запись в файл терминала. Формула для определения последнего времени работы за терминалом (last_activity) такова:

      last_activity = time(NULL) - mod_time,

где mod_time - время модификации. Эта формула закодирована внутри команды who(1). Отметим, что вызов команды time со значением NULL возвращает истинное текущее время.

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

ПОЯСНЕНИЯ

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

Строка 10 - это команда, выполняющая вывод. Команда who вызвана с опцией -u для получения основных данных. Затем ее выход по конвейеру передается команде cut, которая отображает колонки 1-17 и 38-42. Тем самым печатается только три поля, как показано в нашем предыдущем примере.

        ИМЯ: info

info Вывод на экран информации о пароле пользователя

НАЗНАЧЕНИЕ

Печатает информацию поля комментария из файла /etc/ passwd для указанного пользователя.

ФОРМАТ ВЫЗОВА

      info login_name [ login_name ... ]

ПРИМЕР ВЫЗОВА

info russ Печатает информацию, которая хранится о пользователе russ

ТЕКСТ ПРОГРАММЫ

    1   :
    2   # @(#) info v1.0  Display password info on a user Author: Russ Sage
    2а                    Отобразить парольную информацию пользователя

    4   for NAME in $@
    5   do
    6         USER=`grep "^${NAME}:" /etc/passwd`
    7         echo "$NAME:\t`echo ${USER}|cut -d: -f6`\t`
    echo ${USER}|cut -d: -f5`"
    8   done

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

NAME | Каждое имя, указанное в командной строке
USER | Полная запись в файле /etc/passwd для данного имени

ОПИСАНИЕ

Зачем нам нужен командный файл info?

Система UNIX использует конфигурационные файлы для хранения основной информации о пользователях и другой системной информации. Одними из наиболее популярных конфигурационных файлов являются /etc/group, /etc/passwd и /etc/inittab. Для получения информации о пользователях нам необходимо заглянуть в эти файлы.

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

Что делает info?

Info - это командный файл, который получает информацию из регистрационного каталога и комментарий о пользователе из файла /etc/passwd. Результат выглядит так:

    |
    |    name:     home dir                   comments
    |    имя:      регистрационный каталог    комментарии
    |

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

       grep login_name /etc/passwd

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

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

ПРИМЕР

     $ for NAME in `cat /etc/passwd | cut -d: -f1`
         > do
         >         NAMELIST="$NAMELIST $NAME"
         > done; info $NAMELIST

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

ПОЯСНЕНИЯ

Строки 4-8 - это цикл for, который обрабатывает все имена, переданные в командной строке. Для каждого переданного имени выполняются строки 6 и 7.

В строке 6 в переменную USER заносится результат команды grep, заключенной между символами ударения (`). Начиная с начала строки (обозначено символом ^), grep ищет имя, за которым следует символ двоеточия (:). Такое указание заставляет выполнять поиск образца только в первом поле файла паролей.

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

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

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

        ИМЯ:  uchk

uchk Проверка процессов, запущенных другим пользователем

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

       uchk [-a] login_name [...]

ПРИМЕР ВЫЗОВА

uchk -a Вывод всех процессов, запущенных администраторами

ТЕКСТ ПРОГРАММЫ

    1   :
    2   # @(#) uchk v1.0  Check processes of another user Author: Russ Sage
    2а                    Проверка процессов другого пользователя

    4   trap "rm /tmp/ps$$ 2> /dev/null"  0 1 2 3 15

    6   if [ $# -eq 0 ]
    7     then  echo "uchk: argument error"                >&2
    8           echo "usage: uchk [-a] login_name [ ... ]" >&2
    9           exit 1
    10  fi

    12  if [ "`echo $1 | cut -c1`" = "-" -a "$1" != "-a" ]
    13    then  echo "uchk: invalid argument $1"            >&2
    14          echo "usage: uchk [-a] login_name [ ... ]"  >&2
    15          exit 1
    16  fi

    18  ADMIN="administrators names go here"
    19  if [ "$1" = "-a" ]
    20    then  shift
    21          set $ADMIN $@
    22  fi

    24  ps -ef > /tmp/ps$$
    25  for NAME
    26  do
    27          echo
    28          fgrep "$NAME" /tmp/ps$$
    29  done

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ADMIN | Строка с именами пользователей, являющихся администраторами в вашей системе
NAME | Содержит каждое имя, считываемое из командной строки

ОПИСАНИЕ

Зачем нам нужен командный файл uchk?

Поскольку UNIX является многопользовательской системой, множество задач выполняются одновременно. Единственный способ следить за такими задачами - с помощью команды ps. Ps - это довольно специфичная команда в том смысле, что она должна обращаться к памяти (/dev/mem) и проходить по связанному списку структур процесса. Это тяжелая вещь, поэтому нелегко сделать свою собственную команду для вывода такого рода информации. Вдобавок память защищена, и доступ к ней имеет только пользователь root.

Так что мы должны или удовлетвориться опциями, которые дает команда ps, или модифицировать вывод этой команды. Uchk скорее относится к последней категории. Мне хотелось получить список состояний процессов по имени пользователя, а не смесь информации, относящейся к разным пользователям. Хотя то, что я хотел, не соответствует ни одной из стандартных опций команды ps, я смог модифицировать ее выход для получения того, что мне надо.

Что делает uchk?

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

Чтобы делать это, uchk должен работать с временными файлами. Система UNIX весьма элегантно управляет временными файлами. Shell позволяет использовать идентификатор процесса для создания уникальных имен файлов для каждого запуска. После того как утилита запущена, временные файлы очищаются путем использования команды trap в shell'е. Команда trap удаляет файл, если что-либо прервало выполнение командного файла или когда программа завершается. Это прекрасная особенность, которая исключает накопление случайных файлов в системе.

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

Uchk имеет переменную ADMIN, которая определяет всех администраторов вашей системы. Отредактируйте символьную строку, присваиваемую переменной ADMIN, указав имена администраторов, которых вы хотите проверить. Имена должны быть разделены пробелами. Это позволит вам проверять процессы ваших администраторов, указывая опцию -a в командной строке. Все остальные имена должны быть указаны в командной строке. Естественно, вы таким же образом могли бы установить другие группы пользователей. Слежение за администраторами поможет вам оценить, как они управляют сохранностью информации, а также распознать административные задачи, которые имеют склонность загружать процессор.

ПРИМЕР

     $ uchk -a russ uucp

Показывает процессы всех администраторов, мои собственные, и процессы uucp по порядку. Все сообщения об ошибках выводятся на стандартное устройство регистрации ошибок, а списки процессов выводятся на стандартное устройство вывода.

ПОЯСНЕНИЯ

Строка 4 - это оператор trap. Символьная строка между двойными кавычками содержит команды, которые должны быть выполнены, когда происходит прерывание. В этом случае мы удаляем временный файл и переадресовываем все выходные данные на нулевое устройство. Когда команда rm пытается удалить несуществующий файл, выводятся сообщения об ошибках. Поскольку мы не знаем, какие файлы могут быть в наличии в тот момент, когда возникнет прерывание, то мы хотим избавиться от сообщений об ошибках. Оператор trap активизируется по выходу из программы (program exit, сигнал 0), разрыву линии (hangup, сигнал 1), прерыванию (interrupt, сигнал 2), выходу (quit, сигнал 3) или программному завершению (software termination, сигнал 15).

В строках 6-10 проверяется, переданы ли какие-либо аргументы. Если вы вызвали uchk без аргументов, выводится сообщение об ошибке и uchk завершается.

В строках 12-16 проверяется, указана ли какая-то опция со знаком минус и является ли она опцией -a - единственно допустимой опцией. Командный файл делает это, применяя команды проверки для сравнения двух различных случаев. Первая проверка вырезает первый символ первого позиционного параметра и смотрит, является ли он символом "-". Следующая проверка делается для того, чтобы увидеть, что первый позиционный параметр не является опцией -a. Поскольку обе проверки соединены операцией AND, то для получения значения "истина" они обе должны быть истинными. Если они обе истинны, выводится сообщение об ошибке и uchk завершается.

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

В строке 18 переменная ADMIN инициализируется символьной строкой, следующей за ней. Это строка, которую вы должны модифицировать в соответствии с вашей системой. Все, что вы должны сделать - это использовать редактор vi и вставить в эту строку имена администраторов, разделенные пробелами. Ниже мы используем переменную ADMIN для обработки аргументов командной строки.

В строках 19-22 проверяется, была ли указана в командной строке опция -a. Если да, эта опция удаляется из командной строки с целью избавления от нее. Строка 21 использует команду set для того, чтобы поместить символьную строку ADMIN в позиционные параметры. Команда set вставляет значение переменной ADMIN, начиная с первого позиционного параметра, и сдвигает все настоящие параметры вправо. Тем самым в цикл for передается множество имен, которые должны быть обработаны.

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

Строки 25-29 представляют собой цикл for, который выполняется от $1 до $x, где x - последний позиционный параметр. Для каждого имени выполняется следующее: печатается пустая строка для разделения листинга (строка 27), затем командой fgreps (для ускорения) выбираются из временного файла все процессы, принадлежащие данному пользователю. Благодаря применению команды fgrep для каждого имени пользователя, все процессы данного пользователя печатаются за один раз. Когда закончится проверка всех имен, указанных в командной строке, цикл завершится, завершится командный файл и сработает оператор trap, который удаляет временный файл.

        ИМЯ:  watch

watch Наблюдение за регистрацией указанных пользователей

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

      watch [-k] [login_name ...]

ПРИМЕР ВЫЗОВА

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

ТЕКСТ ПРОГРАММЫ

    1   :
    2   # @(#) watch v1.0  Watch for specific logins  Author: Russ Sage
    2а                     Наблюдение за регистрацией пользователей

    4   if [ "`echo $1 | cut -c1`" = "=" -a "$1" != "-k" ]
    5     then  echo "watch: invalid argument $1"         >&2
    6           echo "usage: watch [-k] [login_name ...]" >&2
    7           echo "           -k  kill background process"
    8           exit 1
    9   fi

    11  if [ "$1" = "-k" ]
    12    then if [ -s $HOME/.watch ]
    13           then echo "killed `cat $HOME/.watch`"
    14                      kill `cat $HOME/.watch`
    15                      rm $HOME/.watch
    16                      exit 0
    17         fi
    18  fi

    20  echo $$ > $HOME/.watch

    22  LIST="root sys bin administrator1 administrator2 $*"

    24  while :
    25  do
    26        for NAME in `who | cut -d" " -f1`
    27        do
    28              for PERSON in $LIST
    29              do
    30                    if [ "$NAME" = $PERSON" ]
    31                      then echo ONLINE: $NAME
    32                    fi
    33              done
    34        done
    35        sleep 10
    36  done &

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

HOME | Полный маршрут к вашему регистрационному каталогу
LIST | Список имен пользователей системы, разделенных пробелами
NAME | Содержит имена зарегистрированных в настоящий момент пользователей
PERSON | Отдельное имя из списка имен в переменной LIST

ОПИСАНИЕ

Зачем нам нужен командный файл watch?

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

Что делает watch?

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

Количество имен, за которыми вы можете следить, не ограничено. Общий перечень имен - это объединение имен, указанных в командной строке, и списка системных пользователей, т.е. пользователя root и всех сопровождающих его администраторов. В командном файле watch список системных имен ВСЕГДА включен. Это вызвано тем, что это важные пользователи и вы хотите всегда знать об их входе в систему и выходе из нее. Это отличает watch от uchk, поскольку последний требует указания опции -a для включения списка с системными именами.

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

За кем бы мы ни следили, мы сталкиваемся с одной проблемой. После регистрации в системе указанного лица сообщение об этом поступает на ваш экран, не обращая внимания на то, чем вы в данное время занимались, что не очень приятно. Единственный способ остановить вывод сообщения - аварийно завершить watch командой kill. Это легко сделать путем помещения идентификатора этого процесса в файл $HOME/.watch. Затем этот номер может быть использован в операторе kill для остановки выполнения командного файла. Для того чтобы попроще избавиться от watch, возможность аварийного завершения оформлена в виде опции -k данного командного файла.

ПРИМЕРЫ

      1. $ LIST="root bin" watch daemon

Если переменная LIST не была инициализирована внутри самого командного файла watch, то мы можем инициализировать ее на командном уровне shell'а, а затем вызвать watch. Список пользователей для watch будет такой: root, bin и daemon по порядку, поскольку watch добавляет имена, указанные в ее командной строке к именам, имеющимся в переменной LIST. Такой способ указания имен более гибок, чем жесткое программирование части списка в тексте командного файла watch, но он требует помнить больше информации и больше нужно набирать на клавиатуре.

       2. echo "Watch (y/n): \c"
        read ANS
        if [ "$ANS" = "y" ]
        then watch
        fi

Это фрагмент, который вы можете вставить в ваш .profile. Когда вы регистрируетесь, вам автоматически предлагается запустить watch. Если вы ответите "y", watch станет фоновой задачей и запустится по умолчанию (просматривая пользователей согласно списку в переменной LIST). Если будет введен любой символ, отличный от y, watch не запустится.

ПОЯСНЕНИЯ

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

Строки 11-16 проверяют, что первый позиционный параметр - это -k. Если это так, значит пользователь хочет уничтожить уже запущенный процесс watch. В этом случае выводится сообщение, указывающее идентификатор процесса, который будет уничтожен, и выполнение продолжается. В строке 12 мы смотрим, существует ли в нашем регистрационном каталоге файл с именем .watch. Если нет, то это означает, что предыдущий экземпляр watch предположительно уже уничтожен и нет необходимости пытаться сделать это снова, поэтому происходит переход к оставшейся части программы и выполнение watch происходит так, как будто опция "-k" не была указана.

Если же файл .watch имеется, то в строке 14 используется команда kill для уничтожения фонового процесса watch. Напомним, что при использовании опции -k мы подразумеваем, что watch был вызван ранее, поэтому файл $HOME/.watch имеет идентификационный номер самого процесса watch. В строке 15 удаляется временный файл watch и в строке 16 происходит выход из программы. Теперь командный файл watch более не выполняется как фоновый.

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

В строке 22 инициализируется переменная LIST. Ее значением является символьная строка с именами, разделенными пробелами. Вам нужно вручную отредактировать переменную LIST перед запуском в вашей системе. Просто уберите ее нынешнее содержимое и вставьте туда имена администраторов вашей системы. Если в командной строке будут указаны дополнительные имена пользователей, они будут добавлены в переменную LIST посредством символов расширения параметров $*. Тем самым переменная LIST станет основным списком всех имен пользователей, за которыми будет вестись наблюдение.

Строки 24-36 выполняют цикл постоянного наблюдения. В начале каждой итерации с помощью команды who создается список имен пользователей, который передается циклу for в строке 26. Цикл for использует командную подстановку для получения списка слов, образованного из первого поля команды who. Каждое зарегистрированное имя сравнивается со списком предварительно определенных имен, за которыми мы наблюдаем. Обратите внимание, что внешний цикл while сам себя помещает на выполнение в фоновом режиме. Это означает, что вам нет необходимости вводить это с клавиатуры.

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

После того как все имена проверены, командный файл watch приостанавливается на 10 секунд (строка 35). Когда он снова пробуждается, выполняется следующая итерация вечного цикла while. Все зарегистрированные имена вновь сравниваются со списком. Это будет продолжаться до тех пор, пока вы не прекратите выполнение watch. Как отмечалось ранее, watch можно легко уничтожить с помощью опции -k или же вручную путем ввода команды "kill `cat $HOME/.watch`".

МОДИФИКАЦИИ

Watch выполняет довольно мало работы и использует какую-то часть времени центрального процессора. Вы можете поэкспериментировать с увеличением интервала паузы (sleep), чтобы watch запускался не так часто. Большинство пользователей находятся в системе по крайней мере минуту, поэтому вы можете попробовать значение sleep(60). Вы по-прежнему можете обнаружить регистрацию всех интересующих вас пользователей?

       ИМЯ:   whox

whox Команда who с дополнительными возможностями

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

     whox [-f] [-n] [-m] [-p] [-t] [-w] [-x]

где
-f указывает каждого зарегистрированного пользователя
-n сортирует выход команды who по именам
-m передает почту каждому пользователю
-p выводит информацию о паролях пользователей
-t сортирует выход команды who по времени (умолчание)
-w показывает возможность записи на зарегистрированные терминальные устройства
-x дополнительная информация о регистрационном каталоге и паролях

ПРИМЕР ВЫЗОВА

     whox -w

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

ТЕКСТ ПРОГРАММЫ

    1   :
    2   # @(#) whox v1.0 Who with expanded options  Author: Russ Sage
    2а                   Команда who с дополнительными опциями

    4   XTRA="no"
    5   SORT="sort -b +2"
    6   DISPLAY="norm"

    8   CUT1="cut -d' ' -f1"
    9   CUT5="cut -d: -f5"
    10  CUT6="cut -d: -f6"

    12  for ARG in $@
    13  do
    14          case $ARG in
    15          -f)     DISPLAY="finger"
    16                  COMMAND="finger \$NAME; echo";;
    17          -n)     SORT="sort";;
    18          -m)     DISPLAY="mail";;
    19          -p)     DISPLAY="pass"
    20                  COMMAND="grep \"^\$NAME:\" /etc/passwd";;
    21          -t)     SORT="sort -b +2";;
    22          -w)     DISPLAY="write";;
    23          -x)     XTRA="yes";;
    24          *)      echo "whox: invalid option $ARG"
    25                  echo "usage: whox [-f] [-n] [-m] [-p] [-t] [-w] [-x]"
    26                  echo "             -f  finger users"
    27                  echo "             -n  sort by name"
    28                  echo "             -m  mail to each user"
    29                  echo "             -p  password info on users"
    30                  echo "             -t  sort by time (default)"
    31                  echo "             -w  show writeability of devices"
    32                  echo "             -x  extra home dir and gcos info"
    33                  exit 1;;
    34           esac
    35  done

    37  if [ "$XTRA" = "yes" ]
    38    then EXTRA="| while read LINE; do \
    39         NAME=\`echo \$LINE | cut -d' ' -f1\`;\
    40         ENTRY=\`grep \"^\$NAME:\" /etc/passwd\`;\
    41         echo \"\$LINE\t\`echo \$ENTRY|\$CUT6\`\t\`echo \$ENTRY|\$CUT5\`
    \";done"
    42    else EXTRA=""
    43  fi

    45  case $DISPLAY in
    46  norm)           eval "who | $SORT $EXTRA";;
    47  finger|pass)    for NAME in `who | $SORT | cut -d' ' -f1`
    48                  do
    49                          eval $COMMAND
    50                  done;;
    51  mail)           who | cut -d' ' -f1 | while read NAME
    52                  do
    53                          echo "mail to $NAME (y/n): \c"
    54                          KB=`line < /dev/tty`
    55                          if [ "$KB" = "y" ]
    56                            then mail $NAME < /dev/tty
    57                          fi
    58                  done;;
    59  write)         ls -il `who | sed "s/...........\(.......\).*
    /\/dev\/\1/"`;;
    60  esac

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ARG | Аргументы командной строки
COMMAND | Команда, которую следует выполнить при использовании команды who со списком имен
CUT1 | Содержит синтаксис для выделения первого поля строки
CUT5 | Содержит синтаксис для выделения пятого поля строки
CUT6 | Содержит синтаксис для выделения шестого поля строки
DISPLAY | Определяет, какой режим отображения использовать
ENTRY | Запись в файле паролей для указанного пользователя
EXTRA | Данная переменная содержит полный цикл shell-команд, хранимых в виде одной строки
KB | Входные данные от клавиатуры, полученные в цикле
NAME | Содержит в каждый данный момент времени одно имя из списка всех регистрационных имен
SORT | Содержит выполняемый тип сортировки
XTRA | Флаг, определяющий, должны ли быть активизированы дополнительные опции

ОПИСАНИЕ

Зачем нам нужен командный файл whox?

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

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

Что делает whox?

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

По умолчанию действие whox заключается в печати обычного выхода команды who в порядке времени регистрации от более раннего до более позднего. Опция -x добавляет к этому списку информацию из регистрационного каталога и поле комментария из файла паролей. Если эта опция -x кажется вам знакомой, то так оно и есть, поскольку это то же самое, что и команда info, представленная ранее.

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

Второй режим отображения состоит из режимов указания и паролей, включаемых опциями -f и -p. Основное отличие от первого режима заключается в том, что выход команды who не печатается, а используется для генерации списка имен пользователей, который применяется для других целей. Мы указываем каждого пользователя или печатаем парольную запись каждого пользователя. Выполняемая команда хранится в переменной, поэтому мы можем иметь общий цикл, использующий особым образом переменные. (Команда finger имеется в системе Berkeley UNIX и в некоторых других, но не во всех реализациях. Посмотрите руководство, чтобы выяснить, что выводится на экран по этой команде.)

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

Последний режим - это режим записи на терминал. Режим записи (опция -w) показывает информацию о файле терминала для каждого зарегистрированного терминального устройства. Эта информация полезна, если вы хотите использовать команду UNIX'а write. Посмотрев на права доступа к файлу устройства пользователя, вы можете сказать, имеется ли у вас возможность записать текст на его экран. Некоторые пользователи, которые не хотят, чтобы их прерывали, закрывают право записи на их терминал, выполняя команду "mesg n". Вопрос о праве записи касается любого способа посылки текста в другой файл, а не только с использованием команды write. Право записи также защищает от таких вещей, как "echo hello > /dev/tty00".

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

Например, мы хотим указать каждого пользователя. Мы используем опцию -f. Опция -f устанавливает в качестве режима отображения режим указания. Если мы поместим опцию -w справа от -f, как в команде "whox -f -w", то установится режим записи на терминал. Команда whox будет считать, что вы вообще не указывали опцию -f. На самом деле это не представляет большую проблему, если вы знаете, что делает каждая опция. Случайно смешивая их в одной команде, вы можете получить несколько странные выходные данные.

ПРИМЕРЫ

     1. $ sh -x whox -x

Запуск интерпретатора shell в отладочном режиме выполнения, подача ему командного файла whox в качестве данных, передача опции -x для whox. Отладочный режим показывает присвоение значений переменным и вызовы команд. (Мы видели это ранее.)

     2. $ whox -n -x

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

ПОЯСНЕНИЯ

В строках 4-10 выполняется инициализация переменных. Переменная XTRA устанавливается в значение "no". Эта переменная используется для построения командной строки при использовании опции -x. Установка XTRA в значение "no" означает, что действие по умолчанию - не получать дополнительную информацию.

Умолчанием для сортировки является сортировка по времени регистрации. Это указывается сортировкой по колонке, стоящей после второй (+2) и игнорированием ведущих пробелов (-b) в строке 5. Такой же синтаксис применен ниже с опцией -t. Опция -t здесь лишняя, но она делает более понятной командную строку. Опция -n также изменяет синтаксис сортировки, чтобы просматривать первую колонку, которая является списком имен.

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

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

Строки 12-35 обрабатывают аргументы командной строки. Цикл for подставляет в переменную ARG каждый параметр по порядку и выполняет оператор case по значению ARG.

Если опцией является -f, строка 15 изменяет переменную DISPLAY в режим указания. Она также подставляет в переменную COMMAND команду finger, которая выполнится в следующем цикле. Причина, по которой нам нужно иметь переменную, содержащую команду, заключается в том, что данный цикл является общим циклом, применяемым для двух разных целей. Для того чтобы один и тот же цикл справился с двумя различными задачами, мы помещаем эти задачи в переменную и выполняем эту переменную.

Это значительно сокращает общее количество текста, хотя привносит некоторую дополнительную работу. Обратите внимание в строке 16, что символ $ экранирован в операторе присваивания. Это необходимо, ведь мы хотим, чтобы переменная COMMAND содержала символьную строку $NAME, а не значение, которое имеет переменная NAME после присваивания. Значение NAME расшифровывается в цикле во время его выполнения.

Строка 17 обрабатывает опцию -n. Все, что здесь требуется - изменить способ сортировки выхода команды who, чтобы отразить порядок по именам. Поскольку имя находится в первой колонке выхода команды who, а команда sort сортирует по умолчанию по первой колонке, то используется команда sort без опций.

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

Строки 19 и 20 справляются с опцией -p. Опция паролей изменяет режим отображения на парольный режим и устанавливает команду, которую мы вызываем, находясь в общем цикле. В данном случае мы используем команду grep для получения парольной записи из файла /etc/passwd. Обратите внимание, что в строке 20 мы используем внутренние двойные кавычки. Для этого мы вынуждены экранировать их символами обратной косой черты. Напомним, что обратная косая черта используется для отмены специального значения особых shell-символов.

Строка 21 управляет опцией -t. Как уже упоминалось ранее, опция -t в действительности не требуется в данной программе. Поскольку она является умолчанием, требуемые для нее действия уже были предприняты в начале программы - была выполнена точно такая же инициализация. Синтаксис команды sort точно такой же, как и в строке 5.

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

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

Строка 24 - это улавливатель для обработки ошибок. Символ * соответствует любому символу, который не был распознан ранее. Печатается сообщение об ошибке и синтаксическая подсказка, и whox завершается.

Строки 37-43 устанавливают переменные, используемые в опции дополнительной информации. Строка 37 проверяет, установлена ли переменная XTRA в состояние "yes", что имеет место только тогда, когда в командной строке имелась опция -x. Если это так, то в переменную EXTRA заносится много всяких вещей, которые мы рассмотрим позже. В противном случае в переменную EXTRA заносится пустая строка, так что она никак не проявляется на стадии фактического выполнения.

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

Строки 38-41 вставлены внутрь переменной EXTRA. Это сделано путем взятия в двойные кавычки всех четырех строк. Все специальные символы, которые должны быть частью данных в этой переменной, должны быть экранированы символами обратной косой черты. В строке 38 в переменную EXTRA заносится символ конвейера (|) и начало цикла while. В конце строки 38 имеется символ косой черты, указывающий интерпретатору shell, что присваивание продолжается после символа конца строки (возврата каретки или перевода строки).

Строка 39 присваивает переменной NAME значение поля, вырезанного из данных, читаемых в цикле while. Напомним, что весь данный оператор помещается внутрь переменной EXTRA. Когда я выше упоминал, что в строке с переменной CUT1 есть проблемы, то как одно из таких проблемных мест я имел в виду именно это. Когда я попытался использовать переменную CUT1 в этом операторе вместо указания команды cut, shell не смог правильно распознать этот оператор. Одинарные кавычки, отмечающие символ-разделитель для вырезки, не были распознаны. В результате команда cut считала, что символом-разделителем является символ ' и после этого аварийно завершалась, поскольку второй символ ' представлял собой недопустимое описание списка для опции -f. Строка с опцией -f шла позже, но команда cut этого никогда не узнавала, поскольку аварийно завершалась до этого. Когда я заменил переменную CUT1 просто командой cut, эта проблема исчезла.

Давайте рассмотрим, как я отлаживал эту часть. Я использовал shell с опцией -x, поэтому я мог следить за тем, что происходит. Как вы можете видеть, когда переменная CUT1 была инициализирована, одинарные кавычки находились все еще в операторе, но когда выполнялась настоящая команда cut, одинарные кавычки уходили при синтаксическом расширении. Для генерации такого списка данных я выполнил следующий вызов: sh -x whox -x. Вот что я увидел:

         XTRA=no
         SORT=sort -b +2
         DISPLAY=norm
         CUT1=cut -d' ' -f1         <- Одинарные кавычки все еще здесь.
                                Основная проблема.
         CUT5=cut -d: -f5
         CUT6=cut -d: -f6
         XTRA=yes
         + who
         + read LINE
         + sort -b +2
         + echo russ console Jun 20 14:11
         + cut -d  -f1              <- Теперь выполняется правильно.
                                 Кавычек нет.

Это сокращенная распечатка. Она показывает, что когда выполнялась команда cut, она не имела одинарных кавычек. Когда же запускалась переменная CUT1, она имела одинарные кавычки. Я не мог представить, как избавиться от кавычек, поэтому я просто вставил вызов самой команды cut обратно на это место. Может быть какой-нибудь молодой растущий мастер сможет себе это представить.

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

Цикл, выполняющий такое же присваивание, имеет такой вид при обычном стиле записи на языке shell:

       | while read LINE
         do
         NAME=`echo $LINE | cut -d' ' -f1`
         ENTRY=`grep "^$NAME:" /etc/passwd`
         echo "$LINE\t\`echo $ENTRY|$CUT6\`\t\`echo $ENTRY|$CUT5\`\"
         done

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

Строки 45-60 - это оператор case, который реализует различные режимы отображения. Строка 46 выполняет обычный режим отображения команды who. Поскольку в обычном режиме имеется возможность использовать переменную EXTRA, нам необходимо произвести повторный разбор командной строки командой eval, чтобы эта переменная приняла свое истинное значение во время исполнения. Обратите внимание, что в команде eval имеются кавычки, заключающие всю командную строку. Это необходимо потому, что вся строка является одним набором входных данных для команды eval. Без кавычек команда eval не работала бы. Переменная EXTRA не подвергается повторному разбору.

Строки 47-50 управляют режимами указания пользователя и выдачи информации из файла паролей. Оба эти режима используют один и тот же цикл. Цикл for использован для установки переменной NAME в значение первого поля каждой строки, полученной от команды who. Для каждого имени, вырезанного из результата работы команды who, выполняется повторный синтаксический разбор командой eval переменной COMMAND (которая была установлена в операторе case, выполнявшем разбор аргументов). Тем самым повторно анализируются и выполняются команды, находящиеся в переменной COMMAND. Для режима указания пользователя переменная COMMAND содержит команду finger, а для режима паролей в COMMAND хранится команда grep.

Строки 51-58 похожи на режим указания пользователя. Этот цикл тоже требует имена от команды who, но вместо использования оператора for мы используем метод прямой пересылки по конвейеру. Результат работы команды who по конвейеру передается команде cut (переменная CUT1 и здесь бы не работала), которая по конвейеру передает данные в цикл чтения while. Обратите внимание, что в этом месте нет никакой сортировки. По умолчанию результат команды who выводится в порядке номеров терминальных устройств. Я не думаю, однако, что порядок вывода этих данных имеет большое значение.

Для каждого имени пользователя выводится запрос о том, хотите ли вы передать ему почтовое сообщение. При чтении ответа в строке 54 должна быть использована команда UNIX'а line. Почему? Потому что весь цикл использует оператор read для чтения имен. Оператор read читает только со стандартного ввода, который в данном случае привязан к конвейеру. Для получения входных данных с клавиатуры мы должны использовать команду line, которая получает их из файла /dev/tty. Это распространенный способ чтения данных с клавиатуры из переадресованного цикла.

Строка 55 проверяет, является ли ответом символ y. Если да, вызывается команда UNIX'а mail, и снова ввод переадресовывается из файла /dev/tty (поскольку строки почтового сообщения мы должны вводить с клавиатуры.) В данном случае мы фактически переадресовываем стандартный ввод для вызова подчиненного shell-процесса, выполняющего команду mail. Без выполнения переадресации команда mail читает из файла /dev/null, что нарушает выполнение всего цикла whox.

Строка 59 управляет режимом показа возможности записи на терминал. Цель здесь такова - использовать одну команду ls и, применяя подчиненный процесс, извлечь файлы терминальных устройств из выходных данных команды who. Эти файлы являются вторым полем результата команды who. Сначала запускается команда who, которая по конвейеру передает свои данные команде sed.

Затем sed использует команду подстановки для отбрасывания всего, кроме того, что ограничено символами \( и \). Последующая часть команды подстановки ссылается на этот ограниченный участок с помощью обозначения \1. Используя символ . как соответствующий любому символу распечатки, мы должны всего лишь посчитать столбцы, которые нам нужно вырезать. Кроме того, имена устройств в команде who не имеют префикса /dev/, который нам необходим. Команда sed вставляет его перед текстом, вырезанным из команды who. В результате команде ls дается список полных маршрутных имен ко всем файлам устройств зарегистрированных пользователей. Затем это выводится на экран.


ГЛАВА 6. Управление личной информацией II

ЗАЩИТА ЛИЧНОЙ ИНФОРМАЦИИ

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

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

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

В данном разделе мы рассмотрим инструментальные средства acme, inuse и lock. Acme - это препроцессор к команде UNIX'а acctcom. Acctcom выполняет довольно неплохую работу по отображению учетной информации, но некоторые опции требуется указывать все время. Acme устанавливает их для нас. Напомним, что учетные записи хранятся в виде структуры, а не в текстовом виде, и поэтому мы заставляем acctcom показывать их для нас.

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

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

      ИМЯ:  acme

acme Отображение учетной информации обо мне

НАЗНАЧЕНИЕ

Генерирует опции, необходимые для вывода на экран информации обо мне, которая хранится в учетном файле.

ФОРМАТ ВЫЗОВА

     acme [-l] [-u]

ПРИМЕР ВЫЗОВА

acme -u Выводит всю учетную информацию о пользователе с именем $LOGNAME

ТЕКСТ ПРОГРАММЫ

    1   :
    2   # @(#) acme v1.0  Give accounting info on me Author: Russ Sage
    2а                    Дать учетную информацию обо мне

    4   if [ "$1" != "-l" -a "$1" != "-u" ]
    5     then  echo "usage: acme [-l] [-u]" >&2
    6           echo "     -l for ttyline"   >&2
    7           echo "     -u for user name" >&2
    8           exit 0
    9   fi

    11  OPT=""
    12  for ARG in $*
    13  do
    14          case $ARG in
    15          -l)     OPT="$OPT -l `basename \`tty\``";;
    16          -u)     OPT="$OPT -u $LOGNAME";;
    17          *)      OPT="$OPT $ARG";;
    18          esac
    19  done

    21  echo "acctcom $OPT"
    22  acctcom $OPT

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ARG | Каждое значение, указанное в командной строке
LOGNAME | Переменная среды, содержащая мое регистрационное имя
OPT | Объединенный список всех опций и их аргументов

ОПИСАНИЕ

Зачем нам нужен командный файл acme?

Большинство больших систем UNIX запускают стандартное программное обеспечение для сбора учетной информации об использовании системы. Результаты учетных транзакций передаются обычно в файл /usr/adm/pacct. Фактически сбор учетной информации выполняется ядром системы. Каждый раз при завершении процесса программы сбора учетной информации в ядре производят одну запись. Переключателем, который включает и выключает эту операцию, является acct(2). Команды пользовательского уровня также взаимодействуют с системным вызовом (acct on(1M)) и печатают результаты сбора учетной информации (acctcom(1)).

Теперь, когда мы знаем, где находятся учетные записи и как они туда попадают, нам нужно напечатать эту информацию. Acctcom может печатать таблицы с информацией, но вам необходимо знать, какой использовать индекс. Просмотр может производится по номеру терминальной линии (это полезно, если идентификатор процесса был изменен командой setuid), по имени пользователя, по группе, по времени и т.д. Я наиболее часто использую опции поиска информации по номеру линии терминала и по имени пользователя. С их помощью вы можете получить список всех основных данных, имеющих отношение к вам. Когда вы вызываете acctcom с этими опциями, вам необходимо указать дополнительную информацию, такую как имя вашего терминала и ваше пользовательское имя. Было бы хорошо, если бы мы могли уменьшить количество нажатий на клавиши и объем вычислений, требуемых для получения информации. Для этого и предназначен acme.

Что делает acme?

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

Команда acctcom имеет много опций. В действительности мы используем одну или две, но зато используем их часто. Двумя опциями, которые понимает acme, являются -l и -u. Когда указана опция -l, acme получает имя вашего терминала и помещает его в командную строку. Если указана опция -u, acme получает ваше пользовательское имя и тоже вставляет его в командную строку. Время от времени используются другие опции для бавления специфической информации или небольшого изменения выходного формата. Для того чтобы была возможность использовать другие опции команды acctcom, acme включает в командную строку, формируемую для fcctcom, любые дополнительные корректные опции acctcom, переданные в командной строке для acme. Таким образом, acme поддерживает базовые возможности, а кроме того позволяет вам подгонять команду под ваш вкус.

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

ПРИМЕРЫ

     1. $ acme

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

     2. $ acme -u -b

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

     3. $ acme -l

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

ПОЯСНЕНИЯ

В строках 4-9 выполняется проверка на наличие ошибок. Если первый позиционный параметр не -l и не -u, то это ошибка. Выводится сообщение об этом и программа завершается.

В строке 11 переменная OPT инициализируется пустой строкой. Эта переменная содержит все дополнительные опции acctcom и их аргументы.

Строки 12-19 представляют собой цикл for, который повторяется по всем позиционным параметрам. Каждый аргумент сверяется в операторе case с допустимыми опциями. Если опцией является -l (строка 15), в переменную OPT заносится то значение, которое она уже имеет, опция -l и добавляется имя терминального устройства, полученное от команды UNIX'а tty. Команда tty выводит и префикс /dev, который не нужен. Для того чтобы избавиться от этого префикса, мы берем из этой символьной строки только основное имя.

Если указана опция -u, в переменную OPT добавляется -u и наше регистрационное имя. Если аргументом являются любые другие данные, то они просто добавляются в переменную OPT. Поступая таким образом, мы можем передать в командной строке acme другие опции команде acctcom. Обратите внимание, что здесь не выполняется проверка на ошибки в аргументах командной строки. Вы можете ввести неверное значение, которое нарушит работу команды acctcom. Однако это та цена, которую мы вынуждены платить за гибкость при передаче аргументов в командной строке, иначе нам придется значительно увеличивать текст командного файла.

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

      ИМЯ:   inuse

inuse Запретить использование терминала

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

      inuse

ПРИМЕРЫ ВЫЗОВА

inuse Перевод терминала в состояние занятости

mypasswd Вводится мой пароль, но не отображается на экран

ТЕКСТ ПРОГРАММЫ

    1   :
    2   # @(#) inuse v1.0  Disable terminal and alert if used  Author: Russ Sage
    2а    Запретить использование терминала и сообщить о попытке использования

    4   trap "echo you\'re BUSTED!!; stty echo; kill $$" 2 15

    6   PATH=/bin:/usr/bin
    7   SECRET="secret"

    9   stty -echo
    10  echo "Lock string: \c"
    11  read BUF1
    12  echo

    14  while :
    15  do
    16          BUF2=`line < /dev/tty`
    17          if [ "$BUF2" = "$BUF1" ]
    18            then  break
    19          elif [ "$BUF2" = "$SECRET" ]
    20            then  break
    21          fi
    22          echo "^G\c"
    23  done
    24  stty echo

ОПИСАНИЕ

Зачем нам нужен командный файл inuse?

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

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

Что делает inuse?

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

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

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

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

ПОЯСНЕНИЯ

Строка 4 инициализирует оператор trap. При активизации обработчика trap выполняются три команды.

Целью применения ловушки trap является реагирование на любую попытку прервать работу командного файла и прорваться на ваш терминал. Первая команда выдает предупреждение о том, что вы вторгаетесь. Вторая команда переключает терминал обратно в режим эхо-отображения, так что все, что будет впоследствии введено с клавиатуры, отобразится на экране. и последняя команда заставляет программу совершить самоубийство. Как мы увидим позже, это самоубийство является особым родом прекращения работы программы. Обращение к "самому себе" в операторе kill выполняется с использованием метасимволов $$, которые представляют собой идентификационный номер выполняющегося shell-процесса. Обработчик ловушек включается сигналами 2 и 15, которыми являются прерывание и программное завершение соответственно. Отметим, что сигнал выхода из программы (сигнал 3) здесь не используется. Причину этого мы объясним позже.

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

Строка 9 отключает эхо-отображение, строка 10 печатает запрос пароля, а строка 11 читает пароль, который вы вводите, и заносит его в переменную BUF1.

Строки 14-23 представляют собой вечный цикл while, который можно прервать только вводом правильного пароля. Строка 16 читает ввод с клавиатуры. При нажатии возврата каретки строка 17 проверяет, соответствует ли то, что введено с клавиатуры, паролю пользователя. Если нет, переменная BUF2 сравнивается с секретным паролем. Если какой-то из паролей совпадает, оператор break производит выход из цикла while, тем самым прекращая выполнение программы. Если введенные данные не соответствуют ни одному из паролей, то в строке 22 выдается звуковой сигнал и снова начинает выполняться оператор чтения клавиатуры.

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

ПОДРОБНЕЕ О ЛОВУШКАХ

Нам нужно рассмотреть смысл клавиши выхода из программы. Она производит прерывание, похожее на все другие прерывания, но кроме того выводит дамп памяти для запущенного процесса. Мы оставляем клавишу выхода нетронутой оператором trap, поскольку она становится нашей последней надеждой на приостановление командного файла inuse. Когда ваш терминал заблокирован, он эхо-отображает вводимые с клавиатуры символы, но не реагирует на них. Тот, кто нажимает на клавиши, видит это и пытается выйти из ситуации, нажимая на клавишу прерывания (обычно это клавиша DEL). Когда он это делает, на экран выводится сообщение "you're busted", эхо-отображение снова включается и программа сама себя уничтожает (сигнал 15). Когда сигнал уничтожения принимается программой, этот сигнал ловится, печатается сообщение и программа снова сама себя уничтожает. Эта последовательность выполняется снова и снова, как в вечном цикле. Каждый раз, когда ловушка уничтожается и снова запускается, используется стек. Если все это будет выполняться достаточно долго, то весь стек заполнится записями об активизации и переполнится, аварийно завершая весь сеанс работы.

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

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

        speed 9600 baud; intr = DEL; quit = ^|; erase = ^h; kill = ^u;
        eof = ^d;

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

Теперь мы представляем версию на языке Си.

ТЕКСТ ПРОГРАММЫ inuse НА ЯЗЫКЕ СИ

    1   char id[] = "@(#) inuse v1.0 Disable terminal Author: Russ Sage";

    3   #include
    4   #include
    5   #include

    7   #define SSIZ 7
    8   #define BSIZ 512
    9   #define BELL "\07"
    10  #define LF   "\n"

    12  main()
    13  {
    14          register int fd, sig, n;
    15          char    secret[SSIZ];
    16          char    buf1[BSIZ], buf2[BSIZ];
    17          struct  sgttyb sav_tty, chg_tty;

    19          secret[0] = 's';
    20          secret[1] = 'e';
    21          secret[2] = 'c';
    22          secret[3] = 'r';
    23          secret[4] = 'e';
    24          secret[5] = 't';
    25          secret[6] = '\n';

    27          buf1[0] = buf2[0] = '\0';
    28          if ((fd = open("/dev/tty",O_RDONLY)) == -1)
    29                  exit(1);

    31          for (sig = 2; sig <= 15; sig++)
    32                  signal(sig, SIG_IGN);

    34          if (gtty(0, &sav_tty))
    35                  exit(2);
    36          chg_tty = sav_tty;
    37          chg_tty.sg_flags &= ~ECHO;
    38          if (stty(0, &chg_tty))
    39                  exit(3);

    41          write(1,"Lock string: ",13);
    42          read(fd, buf1, BSIZ);
    43          write(1, LF, 1);

    45          for (;;) {
    46                  n = read(fd, buf2, BSIZ);
    47                  buf2[n] = '\0';

    49                  if (strcmp(buf2, buf1) == 0)
    50                          break;
    51                  if (strcmp(buf2, secret) == 0)
    52                          break;
    53                  write(1, BELL, 1);
    54          }
    55          stty(0, &sav_tty);
    56          close(fd);
    57  }

ОПИСАНИЕ

Зачем нам нужна программа inuse (си)?

Версия inuse на языке Си работает почти так же, как и версия на языке shell. Основное отличие заключается в том, что командные файлы на языке shell пользуются командами раздела (1), в то время как программы на Си используют команды разделов (2) и (3).

Что делает программа inuse (си)?

Теоретические основы функционирования такие же, как и в shell-версии. Инициализируется секретный пароль (в данном случае применяется такой синтаксис, чтобы команда strings(1) не смогла посмотреть его в исполняемом модуле), перехватываются сигналы, читается пароль пользователя и начинается бесконечный цикл, который читает символы с клавиатуры. Как только на клавиатуре что-то набрано и нажата клавиша возврата каретки, входные данные сравниваются с двумя известными паролями. Если они соответствуют одному из паролей, программа переустанавливает терминал и завершается. Если совпадения не произошло, терминал выдает звуковой сигнал и снова читает клавиатуру.

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

ПОЯСНЕНИЯ

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

Строка 3 подключает файл fcntl.h. Этот файл содержит все определения языка Си для открытия, закрытия, чтения и записи файлов. Строка 4 подключает файл signal.h. Мы используем этот файл для определения переменной SIG_IGN, которая является отметкой игнорирования сигналов (signal_ignore). Строка 5 подключает файл sgtty.h, который мы используем для определения всего, что относится к получению информации о терминале посредством вызова ioctl(2).

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

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

Строки 9 и 10 определяют управляющие символы звукового сигнала и перевода строки.

Строка 14 объявляет некоторые рабочие переменные. Обратите внимание, что мы используем регистровые целые. Использование регистровых переменных для ускорения работы - полезный прием. Если вы объявили слишком много переменных по сравнению с количеством регистров в вашей машине, не будет никакой ошибки. Оставшиеся переменные рассматриваются как обычные переменные. Переменная fd используется в качестве файлового дескриптора при открытии файла /dev/tty, переменной si g последовательно присваиваются значения всех сигналов, а переменная n представляет собой число прочитанных символов.

Строка 15 определяет секретный массив. Этот символьный массив содержит наш секретный пароль, который прямо закодирован в программе. Строка 16 определяет два буфера, в которые мы читаем вводимые символы. Buf1 предназначен для нашего пользовательского пароля, а buf2 для попытки ввода пароля, который считывается, когда мы хотим прекратить выполнение программы. Строка 17 определяет две рабочие структуры, которые содержат информацию об установках терминала (ioctl). Здесь у нас две структуры, поскольку одна из них - первоначальная, а вторая - та, на которую мы хотим изменить, чтобы не забыть первоначальные установки.

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

В строке 27 эти два буфера инициализируются в нулевой размер.

Строки 28 и 29 открывают устройство /dev/tty. Если возвращаемый дескриптор файла равен -1, это говорит об ошибке и программа завершается.

Строки 31 и 32 перехватывают все сигналы. Цикл for работает с сигналами, имеющими номера от 2 до 15. Для каждого из этих значений выполняется системный вызов signal с целью игнорирования сигналов с такими значениями.

В строках 34-39 выполняется модификация терминальных характеристик для отключения эхо-отображения символов. Строка 34 получает информацию об установках терминала в структуру sav_tty. Системный вызов gtty - это просто программный интерфейс с системным вызовом ioctl(get_values). Если этот вызов неудачен, программа завершается.

Строка 36 заносит данные из структуры sav_tty в структуру chg_tty. Затем строка 37 присваивает элементу sg_flags результат операции отрицания над его же значением и символом ECHO, что означает "отключить эхо-отображение". После этого строки 38 и 39 записывают измененные значения обратно на терминальное устройство. Системный вызов stty - это просто программный интерфейс с системным вызовом ioctl(set_values).

Строка 41 выводит на экран запрос на ввод пароля. Дескриптор файла 1 является стандартным устройством вывода, а 13 - длина строки символов. Строка 42 читает BSIZ символов из файла /dev/tty. После чтения на экран выдается символ перевода строки. Это необходимо сделать, поскольку при отсутствии эхо-отображения на экран не выводится символ перевода строки, когда вы вводите свой пароль. Поэтому мы вынуждены вставить этот символ здесь сами.

Строки 45-54 представляют собой бесконечный цикл, который читает символы с клавиатуры. Строка 46 выполняет чтение терминала для распознавания пароля. В этой строке введенный пароль помещается в buf2, а не в buf1. Мы выясняем количество символов, прочитанных в buf2 (n). Поскольку индексирование массивов начинается с нуля, а не с 1, при вводе n символов мы попадаем в конец текста и здесь мы вставляем ноль для того, чтобы все, что было введено, представляло собой строку символов. Мы делаем это потому, что команда read не производит обработку символьной строки. Это делают системные вызовы stdio. Их описание находится в разделе (3) руководства по системе, а не в разделе (2). Нам нужно оформить прочитанные символы в виде строки, чтобы ее можно было сравнить с паролями.

Строка 49 сравнивает то, что ввели с клавиатуры, с тем паролем, который вы ввели в начале работы программы. Если эти символьные строки одинаковы, strcmp возвращает значение ноль, которое сообщает о совпадении. Команда break выводит выполнение из цикла for, и программа продолжается. Строка 51 выполняет такое же сравнение с секретным паролем. Если происходит совпадение, вы также выходите из цикла.

Если совпадения не произошло, строка 53 выдает на терминал звуковой сигнал и управление передается оператору read в начало цикла for.

Если произошел выход из цикла for, управление передается строке 55. Происходит запись первоначальной информации об установках терминала, тем самым включается эхо-отображение. Строка 56 закрывает файл /dev/tty, и происходит нормальное завершение работы программы.

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

      ИМЯ: lock

lock Блокирование и разблокирование файлов

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

      lock [-u] file [...]

ПРИМЕР ВЫЗОВА

     lock $HOME

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

ТЕКСТ ПРОГРАММЫ

    1   :
    2   # @(#) lock v1.0  Lock and unlock files  Author: Russ Sage
    2а                    Блокирование и разблокирование файлов

    4   if [ $# -eq 0 ]
    5     then echo "lock: incorrect argument count" >&2
    6          echo "usage: lock [-u] file [...]"    >&2
    7          exit 1
    8   fi

    10  if [ "`echo $1 | cut -c1`" = "-" -a "$1" != "-u" ]
    11    then  echo "lock: invalid argument $1"   >&2
    12          echo "usage: lock [-u] file [...]" >&2
    13          exit 1
    14  fi

    16  MODE1="go-rw"
    17  MODE2="u-w"

    19  if [ "$1" = "-u" ]
    20    then  shift
    21          MODE1="go+r"
    22          MODE2="u+w"
    23  fi

    25  chmod $MODE1 $@
    26  chmod $MODE2 $@

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

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

ОПИСАНИЕ

Зачем нам нужен командный файл lock?

Все файлы в системе UNIX имеют некоторые права доступа. Эти режимы изменяются в соответствии с тем, как используется этот файл. Для каждого файла должны быть установлены три набора прав доступа - для владельца, группы и других пользователей. Обычно текстовые файлы имеют режим 644, а исполняемые и каталоги - 755. Некоторые системы присваивают по умолчанию другие значения.

Если вы хотите ограничить права чтения или записи, следует использовать команду chmod(1). Новый режим должен указываться либо как абсолютное восьмеричное число (например, 777), либо как буквенное выражение, указывающее, какая категория пользователей что может делать (например, ugo+rwx). Если вы хотите добавить или запретить определенные возможности, легче использовать для этого буквенное выражение. Но даже в таком случае нам будет полезно средство, позволяющее уменьшить число нажатий на клавиши и избавляющее от необходимости точно запоминать все, что касается прав доступа к файлу.

Что делает lock?

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

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

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

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

ПРИМЕРЫ

    1.  $ lock -u $HOME/src *.c

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

    2.  $ lock $HOME/bin

Блокирует мой каталог bin так, чтобы никто не мог читать или писать в него файлы. Даже хотя мой каталог нельзя читать, любой посторонний может все же войти в него командой cd, если установлен бит x. Если он попытается выполнить команду ls, каждый файл будет выдавать сообщение об ошибке вида "filename not found" (файл с именем "filename" не найден). Никто не может получить информацию из индексного дескриптора файла, такую как временные характеристики и права доступа, но любой может увидеть имена всех файлов из сообщения об ошибке.

ПОЯСНЕНИЯ

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

Строки 10-14 проверяют, является ли первый символ первого позиционного параметра знаком "минус" и отличается ли первая опция от допустимой опции -u. Если эти условия выполняются, выводится сообщение об ошибке и программа завершается.

Строки 16 и 17 инициализируют установки режимов прав доступа по умолчанию. MODE1 устанавливается для запрета чтения и записи категориям пользователей "группа" и "другие". MODE2 устанавливается для запрета пользователю (т.е. мне) права записи. Это страховка для меня от случайной записи в файл. Нам нужны две такие переменные, поскольку эти два режима довольно разные. Единственный способ сделать это - дважды вызвать команду chmod с двумя различными установками.

Строки 19-23 проверяют, была ли указана в командной строке опция -u. Если была, она убирается из командной строки командой shift и переменные режима инициализируются для разблокирования файлов. Строка 21 разрешает возможность чтения группе пользователей и другим. Строка 22 разрешает мне возможность записи. Обратите внимание, что в командном файле lock не происходит модификации битов x, s или t. Это сделано намеренно, поскольку бит x должен быть установлен только в случае, если файл может быть исполняемым. Для каталогов бит x должен быть установлен только в случае, если вы хотите, чтобы другие пользователи могли заходить в этот каталог. Мы также никогда не устанавливаем возможность записи для группы пользователей и для других пользователей, но мы отключаем ее при блокировании файлов. Это дополнительная мера предосторожности на случай, если файл имеет установленными такие права доступа к нему, которые мы по каким-то причинам не желаем оставлять.

В строках 25 и 26 выполняется команда chmod. Параметр $@ использован как обозначение всех имен файлов, указанных в командной строке. Такой способ позволяет вам вызывать lock с несколькими именами файлов.

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


ГЛАВА 7. Устройства и файловые системы

  • Терминальные устройства
  • Работа с клавиатурой
  • Файлы termcap
  • Дисковые устройства
  • Файловые системы
  • Загрузочный диск
  • Размерные параметры
c | Быстрая очистка экрана
mntf | Монтирование гибкого диска в системном дереве
mntlook | Поиск всех монтированных файловых систем
umntsys | Размонтирование всех файловых систем, кроме корневой
lrgf | Создание файла максимального размера, допустимого в вашей системе

УСТРОЙСТВА И ФАЙЛОВЫЕ СИСТЕМЫ

ВВЕДЕНИЕ

Ниже уровня известной нам области файловых систем находится мир устройств и их драйверов. В данной главе мы исследуем некоторые методы, необходимые для работы с терминалами, дисками и непосредственно файловыми системами. Программное средство 'c' иллюстрирует доступ к терминалу на примере операции быстрой очистки экрана. Следующие три средства - mntf, mntlook и umntsys - имеют дело с монтированием и размонтированием файловых систем. Наконец, средство lrgf позволит вам проверить пределы емкости вашей файловой системы.

СИСТЕМА UNIX И АППАРАТУРА

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

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

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

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

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

Рисунок 7-1
Модель среды системы UNIX

UNIX обращается к периферийным устройствам через "специальные файлы". Имеется два типа специальных файлов: блочные и символьные. Оба типа имеют свое предназначение и особенности. Блочный (например, /dev/hd0) использует буферизацию и позволяет получить доступ к большим объемам данных на жестком диске. Символьный (например, /dev/tty00 или /dev/rfd0) не использует значительную буферизацию, а выполняет обмен с устройством по одному символу за обращение. Даже несмотря на особые свойства таких файлов, для них поддерживается все тот же механизм защиты, что и для всех других файлов в системе.

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

Затем мы рассмотрим дисковые устройства - жесткие и гибкие диски.

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

В дополнение к работе с устройствами мы рассмотрим файловые системы на жестком диске. Всем нам известно, что система UNIX существенно ориентирована на диски, поэтому чем больше мы знаем о файловых системах, тем лучше для нас. Для более полного понимания разделов диска и файловых систем мы представим три программных средства. Средство lrgf проверяет граничные значения параметров файловой системы путем создания файла наибольшего возможного размера в вашей системе. Средство mntf обеспечивает удобный способ монтирования и размонтирования гибких дисков. Наконец, средство mntlook выполняет поиск немонтированных файловых систем, которые представляют собой потенциальную опасность.

ТЕРМИНАЛЬНЫЕ УСТРОЙСТВА

Драйверы терминальных устройств являются одними из самых сложных драйверов устройств. Причина этого заключается в том, что существует множество уровней программного обеспечения, которые поддерживают характеристики интерактивных терминалов. При работе терминала по последовательной линии связи необходима мощная поддержка для того, чтобы облегчить его использование. Различные установки, которые может иметь терминал, программируются командами stty(1) и ioctl(2). Команда termio(7) также описывает различные аспекты протокола работы терминала.

ПРОТОКОЛ ОПЕРАЦИЙ ВВОДА/ВЫВОДА ТЕРМИНАЛА

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

Содержимое терминальной подсистемы показано на рис. 7-2. Рисунок разбит на три части: слева - область пользователя, посредине - область ядра и справа - область устройства. Этот рисунок показывает, как передаются данные между терминалами и программами пользователя.

Рисунок 7-2.
Управление протоколом терминала

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

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

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

ОПРЕДЕЛЕНИЕ ВВОДИМЫХ СИМВОЛОВ

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

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

       $ od -cx
       test string
       ^d
       ^d

даст такой результат:

    |
    |    0000000    6574    7473    7320    7274    6e69    0a67
    |              t   e   s   t       s   t   r   i   n   g  \n
    |    0000014
    |    $
    |

Здесь вызов команды od делается без указания имени файла в командной строке и с применением стандартного вывода в качестве выводного устройства. Мы используем опцию -cx для того, чтобы байты интерпретировались как символы ASCII, а соответствующие 16-битовые слова отображались в шестнадцатиричном виде. По мере того, как вы набираете символы, они отображаются на экране, а команда od сохраняет их в своем буфере. В конце строки нажмите возврат каретки, затем CTRL-D. Ввод CTRL-D завершает чтение символов командой od и выдает распечатку, в которой сверху будут шестнадцатиричные значения, а снизу символы в коде ASCII.

Обратите внимание, что два символа, выводимые для каждого шестнадцатиричного слова, располагаются в обратном порядке по сравнению с двумя байтами, образующими это слово. Например, слово 6574 интерпретируется как два символа, t и e, где 65 - код ASCII для символа e, а 74 - ASCII-код для символа t. Для того чтобы выйти из команды od, введите еще один CTRL-D для прекращения блочного чтения. Если вы хотите еще проверять символы, продолжайте их вводить. Команда od работает несколько загадочно. Если вы введете достаточное количество символов, она выдаст на экран информацию по нажатию только лишь возврата каретки. Но если вы ввели всего несколько символов, требуется нажатие как возврата каретки, ТАК И CTRL-D для получения результата на экране.

Теперь мы можем сделать один фокус - изменить канонический способ обработки при чтении символов командой od. Это позволит нам увидеть эффект от различных установок протокола работы. Для этого проверьте текущие установки вашего терминала. В версии System V используйте команду "stty -a", а в версии Berkeley вам нужно применить команду "stty everything". System V выдает гораздо больше параметров, чем Berkeley. (Наиболее популярные версии UNIX'а разработаны и поддерживаются следующими фирмами: System V - фирмой AT&T Bell Laboratories, которая в настоящее время называется Unix System Laboratories; BSD (Berkeley Software Distribution) - Калифорнийским университетом в Беркли; XENIX - фирмой Microsoft.- Прим. перев.) Ниже приводится пример из XENIX:

    |
    | speed 9600 baud; line = 0;
    | intr = DEL; quit = ^|; erase = ^h;
    | kill = ^u; eof = ^d; eol = ^`
    | parenb -parodd cs7 -cstobp hupcl cread -clocal
    | -ignbrk brkint ignpar -parmrk -inpck istrip -inlcr -igncr icrnl -iuclc
    | ixon ixany -ixoff
    | isig icanon -xcase echo echoe echok -echonl -noflsh
    | opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel ff1
    |

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

Что происходит при канонической обработке? Символ возврата на шаг назад (backspace) является одним из важных вопросов. Когда вы вводите символ CTRL-H, он поступает в необработанную очередь как литеральный символ CTRL-H. Когда программа canon() читает CTRL-H, она понимает это так: "Изменить CTRL-H на символ возврата на шаг назад, записать пробел на место символа, затем сделать еще шаг назад." При эхо -отображении вы получаете удаление символа на экране. Когда каноническая обработка отключена, вы посылаете CTRL-H как обычные символы. Вот пример того, как это выглядит:

    |
    | $ stty -icanon           Отключение канонической обработки
    | $ od -cx
    | test string^h^h^h^h^h^hcase
    | ^d...
    |
    | 0000000    6574    7473    7320    7274    6e69    0867    0808    0808
    |   t   e   s   t       s   t   r   i   n   g  \b  \b  \b  \b  \b
    | 0000020    6308    7361    0a65    0a04    0a0a    0a0a    0a0a    0a0a
    | \b    c   a   s   e  \n 004  \n  \n  \n  \n  \n  \n  \n  \n  \n
    |

После слова "string" вы видите группу чисел 08, которые в ASCII-коде обозначают CTRL-H. Эти числа 08 показывают вам, что литеральные символы CTRL-H действительно присутствуют в их "необработанной" форме. Поскольку CTRL-H не является больше специальным символом, команда od рассматривает его подобно любому другому символу. Здесь возникает новая проблема: поскольку специальные символы не распознаются, мы потеряли возможность завершить блочное чтение вводом символа конца файла (EOF). Когда вводится CTRL-D, он понимается просто как еще один символ. Поэтому мы должны заполнить буфер команды od, чтобы заставить ее выполнить дамп. В нашем примере CTRL-D - это символ 004 после символов case \n.

Кстати, в системе Berkeley используются установки "обработанная" ("cooked") и "необработанная" ("raw") для stty, которые по существу служат для тех же целей, что "canon" и "-canon" в System V.

ДИНАМИЧЕСКОЕ ПЕРЕОПРЕДЕЛЕНИЕ СИМВОЛОВ ПРЕРЫВАНИЯ

Обратите внимание, что в предыдущей распечатке команды stty -a присутствуют определения символов "intr" и "quit". Это две функции, которые прерывают работу ваших работающих процессов. Строки intr и quit представляют собой особые функции, а не просто нажатие клавиши. Эти функции можно назначить на любую клавишу клавиатуры при помощи команды stty.

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

       $ stty intr x
       $ this is a junk stringx
       $

Когда вы вводите символ x в конце строки, то вся введенная строка обрывается и вы получаете новый символ приглашения. Для того чтобы вернуться к обычному режиму работы, введите в качестве символа прерывания старый символ. Если старым символом был "delete", вы даете такую команду:

       $ stty intr DEL

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

       while :
       do
       :
       done

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

Очень важным является также символ "eof". Он соответствует концу файла (end of file), и обычно им является CTRL-D. Это объясняет, почему вы выходите из системы UNIX с помощью CTRL-D. Когда вы посылаете CTRL-D вашему регистрационному shell, вы тем самым говорите ему: "Shell, это метка конца файла для данного сеанса работы". Поскольку терминал рассматривается как файл, CTRL-D закрывает файл, а shell, который работает как цикл вида "читать команды с клавиатуры, пока не встретится EOF", завершается, тем самым посылая сигнал программе init. Программа init порождает команду getty по данной терминальной линии, которая выводит на экран приглашение "login:" для ожидания команды от следующего пользователя. Если вы измените символ конца файла, вы больше не сможете выйти из shell по CTRL-D. Он будет выводиться на экран точно так же, как любой другой символ. Вот пример таких действий:

       $ stty eof x
       $ x
       login:

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

ФАЙЛЫ ТЕРМИНАЛЬНЫХ УСТРОЙСТВ

Физически адресация терминалов производится посредством файлов устройств в каталоге /dev. Когда вы регистрируетесь в системе, вам присваивается определенный номер терминала, например tty01. Этот номер терминала в действительности является файлом /dev/tty01. Если вы вызовете команду tty UNIX, она выведет полное маршрутное имя файла того терминального устройства, за которым вы работаете.

Файлы терминальных устройств выглядят так же, как обычные файлы, за исключением того, что команда "ls -l" показывает, как называются старший и младший номера устройства, которые не являются частью обычных файлов. Старший номер является индексом в таблице cdevsw[], которая содержит адрес драйвера устройства, используемого ядром для данного типа устройства. Младший номер идентифицирует конкретное физическое устройство. Эти номера появляются в последовательном порядке для всех устройств, использующих один и тот же драйвер. Так выглядит типичный список файлов устройств в системе XENIX:

    |
    | crw--w--w-   1 russ   tricks  0,  0 Jun 22 02:34 /dev/console
    | crw--w--w-   1 russ   tricks  0,  1 Jun 22 00:41 /dev/tty02
    | crw--w--w-   1 root   tricks  0,  2 Jun 21 17:56 /dev/tty03
    | crw--w--w-   1 root   tricks  0,  3 Jun 21 05:47 /dev/tty04
    | crw-rw-rw-   1 root   root    0,  4 Feb 18 17:09 /dev/tty05
    | crw-rw-rw-   1 root   root    0,  5 Feb 18 17:09 /dev/tty06
    | crw-rw-rw-   2 root   root    5,  0 Jun 21 20:23 /dev/tty11
    | crw--w--w-   2 root   tricks  5,  8 Jun 22 02:20 /dev/tty12
    | crw-rw-rw-   2 root   root    5,128 Feb 18 17:09 /dev/tty13
    | crw-rw-rw-   2 root   root    5,136 Feb 18 17:09 /dev/tty14
    |

По символу 'c' в первом столбце мы видим, что это символьные устройства, а биты прав доступа показывают, кто имеет доступ к этим файлам. Первый столбец чисел (0 или 5) является старшим номером. Младшие номера в следующем столбце обычно следуют в последовательном порядке, но не всегда (как видно в данном примере).

В дополнение к использованию абсолютного номера вашего терминала, одно из устройств используется в качестве "логического", или "родового" адреса вашего терминала. Оно использует другой драйвер устройства, называется /dev/tty и применяется в случаях, когда стандартный ввод и стандартный вывод переадресовываются в другие файлы, а прикладной программе необходимо читать с клавиатуры или писать на экран. При помощи доступа к файлу /dev/tty образуется связь с самим терминалом. Выбор использования устройства с именем tty вместо устройства tty01 главным образом зависит от того, какая стоит задача. Если вам необходимо иметь независимую от типа терминала программу, используйте /dev/tty.

ПРАВА ДОСТУПА К ТЕРМИНАЛУ

Поскольку терминальное устройство является файлом, оно имеет режимы прав доступа точно так же, как и все другие файлы. Эти режимы представляют собой защиту доступа к вашему терминалу. Если все пользователи имеют право записи на ваш терминал (это обозначается как rw--w--w-), то они могут записать на ваш экран все, что угодно, и вы никогда не узнаете, кто это сделал. Если вы хотите предупредить это, то выполните команду "chmod 600 `tty`", где символы ударения и обозначение tty соответствуют маршрутному имени вашего терминального файла. Более простой в использовании является команда UNIX'а mesg. Команда "mesg n" запрещает запись извне на ваш терминал. Ваши собственные процессы по-прежнему имеют доступ на запись.

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

Другим примером подобного рода является команда write(1). Она используется для установки связи по линии в реальном режиме времени, или "болтовни". Она общается с терминалом путем выполнения записи в файл устройства. Измените биты прав доступа, выключив их командой "mesg n", и никто не сможет выполнить команду write с вашим терминалом. Таким способом вы можете "снять трубку телефона", когда вы хотите, чтобы вам не мешали. Вместе с тем кто-нибудь мог бы сделать такое:

       $ while :
       > do
       >         clear > /dev/tty00
       > done &

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

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

ОТМЕТКИ О ВРЕМЕНИ ДОСТУПА К ТЕРМИНАЛУ

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

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

ОБРАБОТКА ТЕРМИНАЛОМ ВВОДИМОЙ ИНФОРМАЦИИ

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

Каким образом это делается в программе? Прием старый и часто используется в UNIX, хотя и не очень хорошо описан в документации. Такого рода информацию можно добыть путем просмотра большого количества текстов программ. Необходимо отметить, что этот прием лучше всего работает в программах на языке Си. Командные файлы, написанные на языке shell, могут использовать для этой цели команду stty, но результат будет не один и тот же. Следующий фрагмент программы на языке Си отключает каноническую обработку, затем читает символы и выводит их на экран.

       1   #include
       3   struct termio  tsav, tchg;
       5   main (argc, argv)
       6   {
       7           int c;
       9           if (ioctl (0, TCGETA, &tsav) == -1) {
       10                  perror("can't get original settings");
                            невозможно получить исходные установки
       11                  exit(1);
       12          }
       14          tchg = tsav;
       16          tchg.c_lflag &= ~(ICANON | ECHO);
       17          tchg.c_cc[VMIN] = 1;
       18          tchg.c_cc[VTIME] = 0;
       20          if (ioctl (0, TCSETA, &tchg) == -1) {
       21                  perror("can't initiate new settings");
                            невозможно задать новые установки
       22          }
       24          while (1)
       25          {
       26                  c = getchar();
       28                  if (c == 'x')
       29                          break;
       31                  putchar(c);
       32          }
       34          if (ioctl(0, TCSETA, &tsav) == -1) {
       35                  perror("can't reset original settings");
                            невозможно вернуть исходные установки
       36                  exit(3);
       37          }
       38  }

У нас есть две "терминальные" структуры данных, одна из которых содержит исходные установки, а другая - установки, которые мы изменяем и записываем. Первый системный вызов ioctl получает информацию об установках терминала. Затем мы присваиваем эти значения изменяемой структуре (строка 14). Модификации терминального интерфейса мы выполняем в строках 16-18. Строка 16 отключает каноническую обработку и эхо-отображение символов. Строка 17 устанавливает, что минимальное количество нажатий на клавиши равно одному. Строка 18 определяет, что время ожидания для повторного чтения данных равно 0. По существу, это блочное чтение по одному символу.

Новые значения терминальных характеристик устанавливаются в строке 20. В этот момент режим работы терминала меняется. Цикл while читает, проверяет и выводит символы. Только при вводе символа x цикл завершается, терминал устанавливается в первоначальное состояние, и программа заканчивает работу.

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

Этот вопрос эквивалентен такому вопросу: "Как опросить клавиатуру в UNIX?". Опрос является весьма важным приемом для некоторых применений. Опрос работает примерно так: "Посмотреть на клавиатуру. Если введен символ, получить его и каким-то образом обработать. В противном случае продолжать делать то, что вы делали. После истечения интервала времени, определенного программой, проверить клавиатуру снова." Таким образом, если пользователь не нажимает на клавиши, программа продолжает работу, а не ожидает, пока что-нибудь будет нажато на клавиатуре.

Для выполнения такой работы нам нужно несколько более подробно рассмотреть терминальный интерфейс. Как было отмечено ранее, терминал представляет собой файл. Это значит, что он должен обладать всеми обычными свойствами файлов - возможностью открытия, закрытия, чтения, записи и т.д. Мы также видели, что терминалы имеют протокол работы, который может быть изменен командой stty. Мы видели, что для получения одного символа с клавиатуры используется протокол работы. Теперь мы увидим, что для выполнения опроса вы должны использовать технику, которая относится к файлам, а не ioctl.

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

       1   #include
       2   #include
       4   struct termio  tsav, tchg;
       6   main (argc, argv)
       7   {
       8       int c;
       10      /* change the terminal based on file primitives */
           изменить режим терминала с помощью файловых примитивов
       11      close(0);
       12      if (open("/dev/tty",O_RDWR|O_NDELAY) == -1) {
       13          perror("can't open tty");
                    невозможно открыть tty
       14          exit(1);
       15      }
       17      /* change the terminal based on line disciplines */
           изменить режим терминала с помощью протокола работы
       18      if (ioctl (0, TCGETA, &tsav) == -1) {
       19          perror("can't get original settings");
                    невозможно получить исходные установки
       20          exit(2);
       21      }
       23      tchg = tsav;
       25      tchg.c_lflag &= ~(ICANON | ECHO);
       26      tchg.c_cc[VMIN] = 1;
       27      tchg.c_cc[VTIME] = 0;
       29      if (ioctl (0, TCSETA, &tchg) == -1) {
       30          perror(can't initiate new settings");
                   невозможно задать новые установки
       31      }
       33      while (1)
       34      {
       35          putchar('.');
       36          c = getchar();
       38          if (c == 'x')
       39              break;
       41          putchar(c);
       42      }
       44      if (ioctl(0, TCSETA, &tsav) == -1) {
       45          perror("can't reset original settings");
                    невозможно вернуть исходные установки
       46          exit(3);
       47      }
       48  }

Основное изменение производится в строках 11-15. Закрытие файла с нулевым дескриптором (который обозначает стандартное устройство ввода) закрывает стандартный ввод. Затем мы снова открываем файл /dev/tty. Значение дескриптора файла равно нулю, так что мы переназначили стандартный ввод на новое устройство. Фокус в том, что при открытии файла используется режим, называемый NODELAY. Это означает, что когда выполняется чтение по данному дескриптору файла (т.е. чтение стандартного ввода), вместо ожидания ввода выполняется просмотр, есть ли там что-нибудь, а затем работа продолжается.

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

ВОЗМОЖНОСТИ ТЕРМИНАЛОВ

Теперь, когда мы имеем понятие о характеристиках терминальных интерфейсов, давайте перейдем к возможностям терминалов. ВОЗМОЖНОСТИ это те функции, которые выполняет аппаратура терминала. Если мы знаем эту информацию, мы можем создать список возможных функций и использовать его, например, для работы редактора vi. Это осуществляется при помощи специального файла данных termcap (terminal capabilities - возможности терминала), который описывает возможности терминала.

Большинство из существующих типов терминалов уже занесены в файл termcap. Это файл /etc/termcap. Файл termcap и редактор vi происходят из системы Berkeley. Такая комбинация оказалась настолько эффективной, что была перенесена в System V. В более поздней System V Release 3 файл termcap уже не используется, его заменяет файл terminfo фирмы AT&T. Мы применяли файл terminfo совместно с командным файлом today в главе 5, но подробное обсуждение terminfo выходит за пределы нашей книги. В системе Berkeley файл termcap по-прежнему остается стандартом, и он заслуживает более детального рассмотрения.

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

В качестве примера мы приводим запись файла termcap для компьютера Apple II. Это описание распространено в различных формах, но наш пример относится к видеоплате Videx UltraTerm для Apple II+. Заметим, что возможности, предоставляемые файлом termcap, являются обычно подмножеством тех возможностей, которые фактически предоставляет аппаратура. В частности, видеоплата в компьютере Apple выполняет некоторые функции, которые не умеет делать файл termcap, например комбинации настроечных битов для изменения видеоатрибутов. Самое большее, что мы можем сделать с видеоатрибутами посредством файла termcap, это включить или выключить инверсное отображение.

С другой стороны, некоторые типы аппаратуры не обладают всеми возможностями, обеспечиваемыми файлом termcap. Например, одной из функций, которой недостает в Apple, является функция прокрутки ("scroll reverse"). Аппаратура не делает этого, поэтому и в termcap нет необходимости иметь описание этой функции. Вместо скроллинга (прокрутки) вниз, отображаемый на экране текст продолжает выводиться в верхней строке.

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

       a2|aii|Apple II with UltraTerm :\
        :bl=^G:\
        :bs:\
        :cd=^K:\
        :ce=^]:\
        :cl=^L:\
        :cm=^^%r%+ %+ :\
        :co#80:\
        :cr=^M:\
        :do=^J:\
        :ho=^Y:\
        :kb=^H:\
        :kd=^J:\
        :kl=^H:\
        :kr=^\\:\
        :ku=^_:\
        :le=^H:\
        :li#24:\
        :nd=^\\:\
        :nl=^J:\
        :se=^O:\
        :so=^N:\
        :up=^_:

В табл. 7-1 представлен список функций файла termcap с сопоставлением терминалов Apple и vt52. Если какая-либо функция отсутствует у одного или другого терминала, это отмечается словом "нет".

Таблица 7-1
Терминальные возможности и их конкретные значения

Функция | Apple II | vt52
bl - звуковой сигнал (bell) | ^G | ^G
bs - возврат на шаг по коду ^H (can backspace with ^H) | да | да
cd - очистка до конца экрана (clear to end of display) | ^K | \EJ
ce - очистка до конца строки (clear to end of line) | ^] | \EK
cl - очистка всего экрана (clear entire screen) | ^L | \EH\EJ
cm - движение курсора (cursor motion) | ^^%r%+ %+ | \EY%+ %+
co - число позиций в строке (number of columns in a line) | #80 | #80
cr - возврат каретки (carriage return) | ^M | ^M
do - сдвиг на строку вниз (down one line) | ^J | ^J
ho - курсор в начало экрана(без команды cm) (home cursor) | ^Y | \EH
kb - код клавиши backspace (sent by backspace key) | ^H | ^H
kd - код клавиши "стрелка вниз" (sent by down arrow key) | ^J | \EB
kl - код клавиши "стрелка влево" (sent by left arrow key) | ^H | \ED
kr - код клавиши "стрелка вправо" (sent by right arrow key) | ^\\ | \EC
ku - код клавиши "стрелка вверх" (sent by up arrow key) | ^_ | \EA
le - курсор влево (cursor left) | ^H | ^H
li - число строк экрана (number of lines per screen) | #24 | #24
nd - нестирающий пробел (nondestructive space) | ^\\ | \EC
nl - символ перевода строки (newline character) | ^J | ^J
pt - наличие аппаратной табуляции (has hardware tabs) | нет | да
se - обычный экран (end stand out mode (normal)) | ^O | нет
so - инверсный экран (begin stand out mode (inverse)) | ^N | нет
sr - прокрутка (scroll reverse) | нет | \EI
ta - символ табуляции (tab) | ^I | ^I
up - сдвиг вверх на строку(up a line) | нет | ^_

Самое интересное здесь, наверное, то, что терминалы vt52 и Apple имеют взаимно обратный порядок указания координат в команде движения курсора. Терминал vt52 воспринимает значения x и y в порядке YX, что является умолчанием для файла termcap. Apple воспринимает их в порядке XY, поэтому в записи файла termcap требуется поменять координаты местами, что указано обозначением %r в функции cm.

Файл termcap позволяет вам спрятать основную информацию о специфических характеристиках терминала (за исключением характеристик, которые могут отсутствовать у терминала, или специальных возможностей, которые не описаны в termcap). Это значит, что вы можете создавать терминально-независимые программы. При этом вам нет необходимости изменять все специфические обращения к терминалу, такие как ESC-последовательности (символы, указывающие терминалу, что передаваемые после них символы (символ) должны интерпретироваться как управляющие коды). Это символы (\E) для терминала vt52 и (^) для Apple.

Наилучший пример - способ использования файла termcap редактором vi. Он начинает выполнять указанную ему функцию, например движение курсора, после чего ставит вопрос: "Какой код функции, которую мы хотим выполнить?". Затем он ищет соответствующую последовательность в той информации, которую предоставляет termcap.

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


ГЛАВА 7. Устройства и файловые системы

      ИМЯ: c

c Быстрая очистка экрана

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

   c

ПРИМЕР ВЫЗОВА

    c         Очистка экрана

ТЕКСТ ПРОГРАММЫ

       1  char id[] = "@(#) c v1.0 Fast clear screen Author: Russ Sage";
                            Быстрая очистка экрана
       3  #define FF "\014"
       5  main()
       6  {
       7          if (write(1, FF, 1) == -1)
       8                  write(2,"c: write error to stdout\n",25);
                               ошибка записи в стандартный вывод
       9  }

ОПИСАНИЕ

Зачем нам нужна программа c?

В System V уже имеется команда для очистки экрана терминала - это команда clear. Она работает путем определения типа вашего терминала и затем вывода на экран символа очистки для данного терминала. Все прекрасно, но есть один существенный недостаток: она очень МЕДЛЕННАЯ!

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

Что делает c?

Программа 'c' выводит на экран символ очистки настолько быстро, насколько быстро может выполняться операция ввода-вывода в UNIX. Применяя прямой системный вызов, мы избавляемся от необходимости запускать другую программу. Поэтому программа 'c' работает очень быстро. Мы уверены, что точно такую же функцию можно вызывать как команду Си-shell (поместить в csh alias), поэтому данная программа наиболее полезна тем, кто работает в System V.

Для того чтобы определить, какой символ очистки соответствует вашему терминалу, найдите строку с обозначением cl в файле termcap. Это и есть то значение, которое вы должны вручную вставить в данную программу. Если вы работаете не на таком терминале, для которого эта программа написана, то данная команда будет работать неверно.

ПОЯСНЕНИЯ

Первым делом мы должны найти в файле termcap код очистки экрана. Для терминала Apple это код ^L, а для vt52 это \EH\EJ. Как только вы найдете этот код, вставьте его в оператор define в строке 3 или сразу в оператор write в строке 7. В приведенном примере в качестве символа очистки экрана используется ^L.

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

Системный вызов write в строке 7 посылает символ очистки в файл с дескриптором 1, который является стандартным устройством вывода. Если операция записи неудачна (по ряду причин), то в файл с дескриптором 2, т.е. на стандартное устройство регистрации ошибок, выводится сообщение об ошибке. Здесь не проверяется, успешно ли завершилась запись на стандартное устройство регистрации ошибок. Если ошибка все-таки возникнет, то мы ее увидим.

Программа не использует НИКАКИХ возможностей стандартного ввода-вывода (stdio). НИКОГДА нельзя смешивать системные вызовы ввода-вывода (т.е. вызовы из раздела (2) документации по системным функциям, например read или write) со стандартными вызовами ввода-вывода (т.е. вызовами из раздела (3), такими как getchar и printf). Дополнительный буфер, который создается при выполнении функций stdio, не согласован во времени с системными вызовами, поэтому все выходные сообщения перемешиваются.

Еще один аспект, о котором мы должны помнить, принимая решение об использовании системных вызовов, это преимущество получения как можно более короткого объектного кода. Небольшая программа загружается и работает быстрее. Для того, чтобы ненужные подпрограммы стандартного ввода-вывода не включались в наш объектный модуль, в исходном тексте программы не делается никаких ссылок на подпрограммы stdio. Тем не менее, ваша система могла их каким-то образом включить. Так поступает XENIX, а вместе с stdio вызывается malloc и все остальное. Вы можете просмотреть таблицу символов вашего объектного модуля с помощью nm(1) или nlist(2). Вы увидите весь мусор, который был добавлен в ваш объектный модуль. Не так редко мы получаем 6 Кб кода всего лишь для одного оператора printf! Приучайтесь программировать непосредственно на ассемблере, чтобы достичь того, что вам нужно.

ИССЛЕДОВАНИЯ

Когда эта программа была написана, возник вопрос: "Каким образом мы можем проверить неудачу записи на стандартное устройство вывода?". Раньше такой вопрос не стоял, но показалось, что неплохо было бы это сделать. Решение было найдено на страницах описания sh(1). Способ, которым можно вызвать ошибку выполнения записи на стандартное устройство вывода, заключается в том, что нужно закрыть дескриптор файла стандартного устройства вывода. Это легко делается с помощью команды exec, которая является внутренней по отношению к shell:

$ exec >&-Эта команда переназначает файловый дескриптор 1 стандартного вывода (обозначение >) на дескриптор файла (&) закрытого устройства (-). Такой эксперимент может оказаться полезным для более полной отладки ваших программ.

ДИСКОВЫЕ УСТРОЙСТВА

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

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

РАЗБИЕНИЕ ДИСКОВ НА РАЗДЕЛЫ

Части, или области диска, известны как разделы. Раздел может содержать файловую систему, которая сгенерирована командой mkfs(1), или же может содержать неструктурированные данные, доступ к которым выполняется с помощью команды 'cpio -o'.

В системе XENIX управление разделами осуществляется программой fdisk, которая концептуально подобна своей тезке в системе MS-DOS. В других системах UNIX используются другие имена. Например, в системе AT&T 7300 UNIX PC используется программа iv, что значит "format" (хотите верьте, хотите нет). Как упоминалось ранее, обычно разделы содержат одну файловую систему. В настоящее время в системах XENIX и SCO XENIX у вас есть возможность "разделить раздел" на более мелкие части для получения большего количества файловых систем. Это сделано по той причине, что машины с системами DOS и XENIX ограничены четырьмя дисковыми разделами, а у вас может возникнуть желание иметь больше файловых систем, чем число доступных разделов. В системе AT&T 7300 UNIX PC управление разделами диска осуществляется по списку начальных номеров дорожек. Вы можете создать столько разделов, сколько хотите. Каждый компьютер имеет свои преимущества и недостатки.

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

    |
    | brw-------   1 sysinfo  sysinfo  1,  0 Feb 18 17:07 /dev/hd00
    | brw-------   1 sysinfo  sysinfo  1, 15 Feb 18 16:59 /dev/hd01
    | brw-------   1 sysinfo  sysinfo  1, 23 Feb 18 16:59 /dev/hd02
    | brw-------   1 sysinfo  sysinfo  1, 31 Feb 18 16:59 /dev/hd03
    | brw-------   1 sysinfo  sysinfo  1, 39 Feb 18 16:59 /dev/hd04
    | brw-------   1 sysinfo  sysinfo  1, 47 Feb 18 17:07 /dev/hd0a
    | brw-------   1 sysinfo  sysinfo  1, 55 Feb 18 17:09 /dev/hd0d
    | crw-------   1 sysinfo  sysinfo  1,  0 Feb 18 16:59 /dev/rhd00
    | crw-------   1 sysinfo  sysinfo  1, 15 Feb 18 16:59 /dev/rhd01
    | crw-------   1 sysinfo  sysinfo  1, 23 Feb 18 16:59 /dev/rhd02
    | crw-------   1 sysinfo  sysinfo  1, 31 Feb 18 16:59 /dev/rhd03
    | crw-------   1 sysinfo  sysinfo  1, 39 Feb 18 16:59 /dev/rhd04
    | crw-------   1 sysinfo  sysinfo  1, 47 Feb 18 16:59 /dev/rhd0a
    | crw-------   1 sysinfo  sysinfo  1, 55 Feb 18 17:09 /dev/rhd0d
    |

Имена файлов с префиксом hd указывают блочные устройства, а с префиксом rhd - "неструктурированные" символьные устройства. Не все символьные устройства являются неструктурированными блочными устройствами. Терминалы являются символьными устройствами, как мы уже видели ранее в данной главе. В табл. 7-2 показаны различные характеристики этих двух типов устройств.

Таблица 7-2
Сравнение блочных и символьных устройств

Блочное устройство | Символьное устройство
/dev/hd0, /dev/fd0 | /dev/rhd0, /dev/rfd0
буфер управляется ядром системы, медленное устройство | буферизация отсутствует, быстрое устройство
произвольное размещение блоков данных | последовательное размещение блоков данных
доступ через файловую систему | доступ непосредственно на диск
cpio -p | cpio -o, -i
mkfs, mount, df, du, fsck, fsdb | tar

Как видите, существует много способов работы с устройствами.

Давайте рассмотрим устройство /dev/hd01 из приведенного выше списка. Если вы хотите адресоваться к физическому разделу на диске как к блочному устройству, вы можете создать на нем файловую систему. Для этого вам нужно выполнить следующую команду, которая создаст файловую систему размером 5000 Кб (5 Мб) на жестком диске:

       # mkfs /dev/hd01 5000

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

       # mount /dev/hd01 /mount_pt

Файлы могут быть помещены в дисковый раздел командами mv или cp, путем переадресации вывода в каталог с этим именем, например, >/mount_pt/file.

Для использования раздела диска в качестве области неструктурированных данных, а не блочного устройства, применяйте файл с именем символьного устройства, которое начинается с буквы r. Например, для использования того же устройства, что и в предыдущем примере, в качестве неструктурированного устройства, укажите имя /dev/rhd01. (Из списка устройств вы видите, что это символьное устройство, так как права доступа в первой колонке начинаются с символов crw, а не brw). Это устройство (и соответствующий раздел) в данный момент не имеет файловой системы и является просто набором байтов. Единственным ограничением является то, что вы можете записать в этот раздел не более 5 Мб данных.

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

       $ find . -print | cpio -ocBv > /dev/rhd01

ИЗУЧЕНИЕ ДАННЫХ

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

       $ od -c /dev/hd01

Если бы вы получали дамп файла НЕСТРУКТУРИРОВАННОГО устройства (/dev/rhd01), то это выглядело бы точно так же. Единственное отличие заключается в том, как драйвер осуществляет доступ к данным. Формат, в котором будут выводиться данные, зависит от того, какой командой производилось копирование: cpio, tar, mkfs или какой-то иной. Некоторые другие способы получения данных с устройства:

       $ cat /dev/hd01
       $ cat < /dev/hd01
       $ tail /dev/fd0

Если вы дампируете файл устройства, содержащего файловую систему, то данные будут представлять собой неупорядоченные блоки по 512 байт. В одном месте вы можете увидеть списки каталогов. Другими словами, одно и то же устройство может рассматриваться двумя совершенно разными способами: как файловая система и как набор неструктурированных битов. Хотя выполнение чтения двумя этими способами может быть поучительным, в большинстве случаев у вас не возникнет желания выполнить ЗАПИСЬ информации на одно и то же устройство двумя способами, поскольку, например, неструктурированное устройство не будет ничего знать о файловой системе в данном разделе и может затереть данные, относящиеся к файловой системе.

Теперь, когда вы знаете, как осуществить доступ к диску, мысленно вернемся к главе 2 и программам копирования. Командный файл cpiobr использует для копирования файлов неструктурированное дисковое устройство /dev/rfd0, в то время как autobkp использует файловую систему.

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

Итак, поскольку вы обычно должны выбрать тот или иной метод использования раздела диска, то ничто не мешает вам завести на устройстве все разделы одинакового типа. Обычным подходом является создание файловых систем во всех возможных разделах, чтобы они могли содержать файлы. Тем не менее, вы можете сочетать файловую систему с "неструктурированными" разделами любым способом, который вам нравится. Одной из возможных схем является использование одного раздела (fd01) в качестве неструктурированного устройства для копирования файлов командой "cpio -o". Этот раздел занимает почти весь диск, но какая-то часть отводится для размещения второго раздела с файловой системой (fd02). Распределенное пространство содержит некоторые справочные (help) файлы и текстовый файл с именами файлов, находящихся в неструктурированном разделе. Такое разбиение на разделы использует преимущества обоих способов. Для того чтобы получить данные, скопированные командой cpio, вы вводите команду "cpio -i < /dev/rfd01". Для получения данных из второго раздела, вы вводите команду "mount /dev/fd02 /mnt", а затем используете команды ls, file, grep и другие, которые относятся к файловой системе. В этом случае раздел с файловой системой служит для документирования неструктурированного раздела.

ЗАГРУЖАЕМЫЙ ДИСК И АВТОНОМНЫЙ shell (SASH)

Инсталляция системы UNIX на жесткий диск обычно выполняется с помощью автономного shell (SASH, standalone shell). Иногда эта операция выполняется с магнитной ленты, но легче всего использовать гибкий диск. Возникает вопрос: "Как загрузить UNIX с гибкого диска?"

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

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

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

       # mount /dev/hd01 /mnt <-вызов с гибкого диска для монтирования
                         первого раздела жесткого диска
       # copy /unix /mnt      <-копирование ядра жесткого диска в раздел
                         жесткого диска

ОБНОВЛЕНИЕ ФАЙЛОВОЙ СИСТЕМЫ

Мы описали суперблок как запись с ключевой информацией о размере и содержимом файловой системы. Причиной разрушения файловой системы обычно являются проблемы, возникающие в суперблоке. Команда sync(1) выполняет запись образа суперблока на диск, тем самым обновляя его. Иногда эта операция должна выполняться автоматически и постоянно для того, чтобы образы суперблока на диске и в памяти были одинаковы. В настоящее время в System V включена программа update, которая запускается из загрузочного файла /etc/rc. Она живет в системе и исполняет команды sync и sleep. В результате информация о состоянии файловой системы на диске хранится со всеми текущими изменениями, произведенными с самой файловой системой. Если у вас нет такой программы, вы можете написать командный файл на языке shell, которая работает в цикле, вызывая команду sync через соответствующие интервалы команды sleep. Запустите этот командный файл в фоновом режиме, чтобы поддерживать целостность файловой системы.

МОНТИРОВАНИЕ ФАЙЛОВЫХ СИСТЕМ

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

Рисунок 7-3
Монтирование одной файловой системы в другую

В примере, показанном на рис. 7-3, файловая система из раздела 2 монтируется в корневой файловой системе (раздел 1) в каталог /usr. Однако мы помним, что каждая файловая система имеет свой собственный корневой каталог. В каждой файловой системе нумерация индексных дескрипторов файла начинается с числа 2, поэтому номера индексных дескрипторов дублируются в двух файловых системах. Это и является причиной, по которой не могут быть образованы связи между файлами, находящимися в разных файловых системах.

Одним из атрибутов корневого каталога является то, что номер его индексного дескриптора равен 2. Это значение может быть проверено в корневом каталоге командой "ls -lid /". Каталог /usr - это просто еще один файл (а именно каталог) в корневой файловой системе. Этот каталог может содержать файлы и подчиненные каталоги, которые хранятся в разделе 1. После выполнения команды "mount /dev/hd02 /usr" корневой каталог раздела 2 (индексный дескриптор 2) помещается в каталог /usr (индексный дескриптор 245). Если какие-либо файлы существуют в каталоге /usr в разделе 1, они остаются там, но получить доступ к ним вы не можете. Единственным способом увидеть их является размонтирование файловой системы, которая была смонтирована на их место. Хитрость команды mount заключается в том, что она представляет новый раздел как бы принадлежащим реальному корневому разделу. В сущности, это позволяет иметь безграничную файловую систему.

Механизмом, который позволяет производить это, является таблица смонтированных устройств, находящаяся внутри ядра системы. Когда выполняется обращение к файлу, его индексный дескриптор определяет маршрут, по которому находится данный файл. Если в таблице смонтированных устройств имеется запись, то этот маршрут ведет на другой раздел диска или в другую файловую систему. Для того чтобы убедиться, что вновь смонтированная файловая система уникальна, посмотрите индексный дескриптор каталога /usr сначала из корневого каталога (командой "ls -li /", индексный дескриптор 245), а затем из другой файловой системы (ls -ldi /usr, индексный дескриптор 2).

КАК ПОЛУЧИТЬ ПОБОЛЬШЕ ИНФОРМАЦИИ О ФАЙЛОВОЙ СИСТЕМЕ?

Как указывалось ранее, файловая система размещается внутри раздела на диске. Файловые системы создаются командой mkfs(1), поддерживаются командой fsck(1), отлаживаются командой fsdb(1), а первый доступ к ним осуществляется командой mount(1). Каталог /usr/include содержит все включаемые файлы для использования в программах на языке Си, реализующих эти команды. Таким образом, этот каталог представляет собой прекрасную возможность для поиска информации о файловой системе, поскольку включаемые файлы содержат глобальные определения, используемые подпрограммами файловой системы. В документации Bell Labs (в руководстве программиста) также описаны некоторые внутренние таблицы, используемые файловой системой.

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

      ИМЯ:  mntf

mntf Монтирование и размонтирование гибкого диска

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

   mntf [-d] [-h] [-l] [-r] [-s]Опции:

-d размонтирование гибкого диска из корневой файловой системы

-h использование устройства с высокой плотностью записи (а не с низкой)

-1 использование устройства 1, а не устройства 0

-r монтирование гибкого устройства как файловой системы с возможностью только чтения

-s использование имен устройств, принятых в System V

По умолчанию выполняется монтирование гибкого диска 0 в каталог /mnt.

ПРИМЕР ВЫЗОВА

   mntf -d -1Размонтирование гибкого диска на устройстве 1.

ТЕКСТ ПРОГРАММЫ

       1   :
       2   # @(#) mntf v1.0  Mount floppies  Author: Russ Sage
                      Монтирование гибких дисков
       4   CMD="/etc/mount"
       5   DIR="/mnt"
       6   DRIVE="0"
       7   DENSITY="48ds9"
       8   SYSTEM="xenix"
       10  if [ $# -gt 0 ]
       11    then  for ARG in $*
       12          do
       13            case $ARG in
       14             -d)  CMD="/etc/umount"
       15                  DIR="";;
       16             -h)  DENSITY="96ds15";;
       17             -1)  DRIVE="1"
       18                  if [ -d /mnt1 ]
       19                    then DIR="/mnt1"
       20                    else echo "the directory /mnt1 does not exist" >&2
                                 нет каталога /mnt1
       21                         echo "using the directory /mnt instead"   >&2
                                 используется каталог /mnt
       22                  fi;;
       23             -r)  DIR="$DIR -r";;
       24             -s)  SYSTEM="sysv";;
       25             *)   echo "mntf: invalid argument $ARG"          >&2
       26                  echo "usage: mntf [-d] [-h] [-1] [-r] [-s]" >&2
       27                  echo "      -d  dismount"                   >&2
       28                  echo "      -h  high density"               >&2
       29                  echo "      -1  use drive 1"                >&2
       30                  echo "      -r  read only"                  >&2
       31                  echo "      -s  System V device"            >&2
       32                  echo " default: mount XENIX drive 0 48 tpi to " >&2
       33                  echo "         /mnt as a read/write filesystem" >&2
       34                  exit 1;;
       35             esac
       36         done
       37  fi
       39  case $SYSTEM in
       40  sysv)   $CMD /dev/fp${DRIVE}21 $DIR;;
       41  xenix)  $CMD /dev/fd${DRIVE}${DENSITY}  $DIR;;
       42  esac

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

CMD | Основная команда, подлежащая выполнению
DIR | Каталог, в котором производится монтирование устройства
DENSITY | Плотность записи в виде, указанном в имени устройства
DRIVE | Номер устройства, начиная с 0
SYSTEM | Тип имени устройства, принятый в UNIX'е

ОПИСАНИЕ

Зачем нам нужен командный файл mntf?

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

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

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

Поскольку использование гибких дисков включает в себя взаимосвязанные шаги монтирования и размонтирования, то было бы вполне естественным применять одну команду с соответствующими опциями для выполнения монтирования и размонтирования. Однако UNIX так не делает. Наш командный файл mntf объединяет эти две функции в одной команде для упрощения работы с гибким диском. Для того чтобы сделать нашу программу более независимой, мы предусмотрели в ней поддержку устройств системы XENIX наравне с устройствами System V. (Системы Berkeley (BSD) не так часто используют гибкие диски, поэтому мы не пытались иметь с ними дело.)

Что делает mntf?

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

Действие программы по умолчанию заключается в монтировании гибкого диска низкой плотности записи, находящегося в устройстве 0, в каталог /mnt. Имеется много опций, чтобы попросить программу mntf сделать то, что вам нужно. Опция -h поддерживает диск высокой плотности (1.2 Мб). В машинах PC AT первое из устройств гибких дисков имеет 96 дорожек на дюйм, объем 1.2 мегабайта, но может также читать и писать гибкие диски с более низкой плотностью. Второй гибкий диск является устройством низкой плотности с 48 дорожками на дюйм и объемом 360 килобайт.

Опция -1 (цифра один, а не буква l) выполняет монтирование гибкого диска в устройстве 1, а не 0. Опция -r монтирует файловую систему с возможностью ТОЛЬКО ЧТЕНИЯ. Для РАЗМОНТИРОВАНИЯ диска вместо монтирования используется опция -d. Если применяется опция -s, имя устройства изменяется таким образом, чтобы оно соответствовало системе System V, а не XENIX. Это незначительная проблема, поскольку схемы именования не очень отличаются. Данная программа создана для системы XENIX и обеспечивает наилучшие возможности именно в ней.

Не все опции совместимы друг с другом, но проверка на совместимость не выполняется. Например, команда "mntf -d -r" пытается размонтировать файловую систему с возможностью только чтения, а команда UNIX unmount, которая выполняет эту операцию, отбрасывает ее, выдавая сообщение об ошибке. В целях упрощения мы отказались от проверки соответствия опций, а вместо этого предоставили UNIX'у право выдавать сообщения об ошибках для информирования пользователя о возникших проблемах. Если вы хотите, чтобы эту программу мог применять относительно неопытный пользователь, вам нужно вставить в нее выполнение таких проверок.

ПРИМЕРЫ

   1.  $ mntf -s

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

   2.  $ mntf -h -1 -r

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

   3.  $ mntf -d -h

Размонтирование файловой системы на устройстве 0 с высокой плотностью записи и с использованием имен устройств, принятых в XENIX.

ПОЯСНЕНИЯ

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

В строках 4-8 инициализируются установки по умолчанию. Переменная CMD содержит команду UNIX, которая в итоге должна быть выполнена, по умолчанию это команда mount. Переменная DIR указывает каталог, в который должно быть смонтировано устройство, по умолчанию это каталог /mnt. Переменная DRIVE является номером устройства (по умолчанию 0) и используется для формирования корректного имени устройства. Переменная DENSITY по умолчанию установлена для носителя низкой плотности, т.е. 48 дорожек на дюйм, двусторонняя дискета с 9 секторами на дорожку (48ds9).

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

Строка 14 управляет опцией -d для размонтирования гибкого диска. Переменная CMD изменяется на umount вместо mount. После этого переменной DIR присваивается нулевое значение, поскольку команде umount требуется не каталог, а только имя устройства. Переменная DIR должна быть частью строки с командой для того, чтобы мы могли использовать одну и ту же "заготовленную" командную строку для всех вариантов. В данном случае мы устанавливаем эту переменную в нуль, а shell при синтаксическом разборе удаляет ее из командной строки.

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

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ ОБ ИМЕНАХ УСТРОЙСТВ

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

    |
    |  32 brw-rw-rw-  3 bin   bin   2,  4 Jun 25 09:25 /dev/fd0
    |  32 brw-rw-rw-  3 bin   bin   2,  4 Jun 25 09:25 /dev/fd048
    | 126 brw-rw-rw-  1 root  root  2, 12 Feb 18 17:09 /dev/fd048ds8
    |  32 brw-rw-rw-  3 bin   bin   2,  4 Jun 25 09:25 /dev/fd048ds9
    | 125 brw-rw-rw-  1 root  root  2,  8 Feb 18 17:09 /dev/fd048ss8
    | 127 brw-rw-rw-  1 root  root  2,  0 Feb 18 17:09 /dev/fd048ss9
    | 131 brw-rw-rw-  3 root  root  2,  5 Feb 18 17:09 /dev/fd1
    | 131 brw-rw-rw-  3 root  root  2,  5 Feb 18 17:09 /dev/fd148
    | 129 brw-rw-rw-  1 root  root  2, 13 Feb 18 17:09 /dev/fd148ds8
    | 131 brw-rw-rw-  3 root  root  2,  5 Feb 18 17:09 /dev/fd148ds9
    | 128 brw-rw-rw-  1 root  root  2,  9 Feb 18 17:09 /dev/fd148ss8
    | 130 brw-rw-rw-  1 root  root  2,  1 Feb 18 17:09 /dev/fd148ss9
    |

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

Большинство из этих имен устройств соответствуют определенному шаблону. Они состоят из символов fd (floppy disk - гибкий диск), цифры 0 или 1 (номер устройства), числа 48 (плотность, выраженная в виде количества дорожек на дюйм), символов ss или ds (single-sided - односторонняя или double-sided - двусторонняя дискета) и цифры 8 или 9 (число секторов).

Мы видим по индексным дескрипторам, что устройство fd0 связано с устройствами fd048 и fd048ds9. Самым информативным именем (и самым трудным при вводе с клавиатуры) является имя fd048ds9. Оно точно выражает, к какому устройству и типу носителя мы обращаемся. Для того чтобы упростить указание этого имени, устройство fd048ds9 связывается с более короткими именами. Все три имени файла являются корректными. Следующий список получен на машине AT, в которой имеется устройство высокой плотности:

    |
    | 102 brw-rw-rw-  3 bin  bin  2,  7 Jun 17 14:28 /dev/fd0
    |  95 br--r--r--  2 bin  bin  2,  3 Jun  6 09:23 /dev/fd048
    |  93 br--r--r--  1 bin  bin  2,  2 Jun  6 09:23 /dev/fd048ds8
    |  95 br--r--r--  2 bin  bin  2,  3 Jun  6 09:23 /dev/fd048ds9
    |  92 br--r--r--  1 bin  bin  2,  0 Jun  6 09:23 /dev/fd048ss8
    |  94 br--r--r--  1 bin  bin  2,  1 Jun  6 09:23 /dev/fd048ss9
    | 102 brw-rw-rw-  3 bin  bin  2,  7 Jun 17 14:28 /dev/fd096
    | 102 brw-rw-rw-  3 bin  bin  2,  7 Jun 17 14:28 /dev/fd096ds15
    |  99 brw-rw-rw-  3 bin  bin  2, 11 Jun 26 19:34 /dev/fd1
    |  99 brw-rw-rw-  3 bin  bin  2, 11 Jun 26 19:34 /dev/fd148
    |  97 br--r--r--  1 bin  bin  2, 10 Jun  6 09:23 /dev/fd148ds8
    |  99 brw-rw-rw-  3 bin  bin  2, 11 Jun 26 19:34 /dev/fd148ds9
    |  96 br--r--r--  1 bin  bin  2,  8 Jun  6 09:23 /dev/fd148ss8
    |  98 br--r--r--  1 bin  bin  2,  9 Jun  6 09:23 /dev/fd148ss9
    | 103 brw-rw-rw-  2 bin  bin  2, 15 Jun  6 09:23 /dev/fd196
    | 103 brw-rw-rw-  2 bin  bin  2, 15 Jun  6 09:23 /dev/fd196ds15
    |

Если мы посмотрим на записи с индексным дескриптором 102, начиная с середины списка, то увидим прогрессирующее упрощение имен по мере продвижения к первой записи - устройству 0, которое имеет высокую плотность записи по умолчанию. Для того чтобы обратиться к нему как к устройству с низкой плотностью записи, необходимо использовать имя fd048, а не fd0. Поскольку большинство используемых гибких дисков имеют низкую плотность записи, то имя fd048ds9 является умолчанием в программе mntf. Строка 17 соответствует опции -1 для указания устройства 1 вместо устройства 0. Строки 18-22 проверяют, сиществует ли каталог для монтирования второго устройства. Если вы используете два гибких диска одновременно, то вы не можете монтировать их оба в один и тот же каталог. Для разрешения этой проблемы программа mntf использует для монтирования устройства 1 каталог /mnt1, а не /mnt. Если каталог /mnt1 не существует, по умолчанию используется каталог /mnt, и все хорошо, если вы используете только устройство 1. Однако следует избегать монтирования одного гибкого диска на место второго. Вы можете получить непредвиденные результаты. Если вы собираетесь монтировать два гибких диска, убедитесь, что у вас есть и каталог /mnt, и /mnt1. Строка 23 делает монтируемую файловую систему доступной только для чтения в случае, если была указана опция -r, что выполняется добавлением символов -r к имени каталога. Это не является частью имени каталога, но когда shell выполняет обработку команды, пробела между именем каталога и -r достаточно, чтобы распознать -r как опцию. Строка 24 соответствует опции -s и присваивает переменной SYSTEM значение sysv. Это означает, что нужно использовать другие соглашения об именах устройств. Строки 25-34 выполняют проверку на ошибки в командной строке. Любая опция, отличная от уже проверенных, является ошибкой, поэтому все, что соответствует улавливающей ветке оператора case (*), считается недопустимой опцией. В этом случае выводится синтаксическая подсказка, и программа завершается. В строках 39-42 выполняется вся основная работа. Оператор case действует в соответствии со значением переменной SYSTEM. Если оно равно "sysv", выполняется строка 40. В противном случае выполняется строка 41 для системы XENIX. Обратите внимание, что в нашей версии командного файла mntf в строке sysv имеется только переменная с номером устройства. Если вы используете System V, вы можете добавить переменную для указания плотности записи или другие параметры, которые вам нужны. Строка 41 выполняет версию команды, рассчитанную на систему XENIX. Переменная CMD содержит, как мы отмечали, команду монтирования (mount) или размонтирования (umount). Последовательность символов /dev/fd указывает файл устройства для гибкого диска. Переменная DRIVE равна 0 или 1. Переменная DENSITY указывает устройство с высокой или низкой плотностью записи. Если должна быть выполнена команда монтирования, переменная DIR содержит каталог. Если выполняется размонтирование, значение переменной DIR равно нулю.

ЗАМЕЧАНИЕ ПО ВОПРОСУ БЕЗОПАСНОСТИ

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

       # chown root /etc/mount  <- делает пользователя root владельцем
                            модуля /etc/mount
       # chmod 4511 /etc/mount     и дает возможность выполнять команду mount
                            всем пользователям
       # chown root /etc/umount <- делает то же самое для команды
       # chmod 4511 /etc/umount    размонтирования

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


ГЛАВА 7. Устройства и файловые системы

      ИМЯ: mntlook

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

НАЗНАЧЕНИЕ

Просмотр всех файлов дисковых устройств и обнаружение всех файловых систем, включая немонтированные.

ФОРМАТ ВЫЗОВА

   mntlook

ПРИМЕР ВЫЗОВА

   mntlook /dev/hd*Поиск файловых систем на всех жестких дисках

ТЕКСТ ПРОГРАММЫ

       1   static char id[] =
         "@(#) mntlook v1.0 Look for mounts Author: Russ Sage";
                       Поиск файловых систем

       3   #include
       4   #include
       5   #include
       6   #include
       7   #include

       9   #define BSIZ 512

       11  main(argc,argv)
       12  int argc;
       13  char *argv[];
       14  {
       15      struct filsys sb;
       16      int d, dev;
       17      char buf[BSIZ];

       19      for (d = 1; d < argc; d++)
       20      {
       21          if (argv[d][0] == '-')
       22          {
       23              printf("mntlook: invalid argument %s\n", argv[d]);
       24              printf("usage: mntlook device [device ...]\n");
       25              continue;
       26          }
       27          if ((dev = open(argv[d],O_RDONLY)) < 0)
       28          {
       29              sprintf(buf, "cannot open %s",argv[d]);
                              невозможно открыть
       30              perror(buf);
       31              continue;
       32          }

       34          /* throw away first block */
               обойти первый блок
       35          if (read(dev, &sb, sizeof(sb)) == -1)
       36          {
       37              perror("cannot read block 0");
                        не читается блок 0
       38              continue;
       39          }

       41          /* block 1 is the superblock */
               блок 1 является суперблоком
       42          if (read(dev, &sb, sizeof(sb)) == -1)
       43          {
       44              perror("cannot read block 1");
                        не читается блок 1
       45              continue;
       46          }

       48          if (sb.s_magic == S_S3MAGIC)
       49          {
       50              printf("\nDEV: %s --> VALID file system\n",argv[d]);
       51              printf("filsys: %s\n",sb.s_fname);
       52              printf("pack  : %s\n",sb.s_fpack);
       53              printf("type  : %s byte block\n",
       54                     (sb.s_type == S_B512) ? "512" : "1024");
       55              printf("magic : %lx\n",sb.s_magic);
       56          }

       58          close(dev);
       59      }
       60  }

ОПИСАНИЕ

Зачем нам нужна программа mntlook?

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

Что делает mntlook?

Программа mntlook предназначена для просмотра содержимого файлов устройств и поиска суперблока. (Мы вкратце рассматривали суперблоки ранее). Когда суперблок обнаружен, из него извлекается и выводится на экран имя файловой системы, имя дискового пакета, используемый размер блока и идентификационный "магический номер". Первоначальной целью данной утилиты было обнаружение на внешнем носителе машины таких файловых систем, которые в настоящий момент не смонтированы. Но когда данная программа открывает и читает устройство, ей все равно, было ли устройство смонтировано или нет, поскольку доступ к нему выполняется на более низком уровне, чем уровень файловой системы. В результате обнаруживаются все файловые системы, независимо от того, были они смонтированы или нет. Давайте рассмотрим, каким образом файловые системы связаны с физическим носителем. К каждой машине подсоединено фиксированное число периферийных устройств. Для работы со сменными носителями в UNIX реализована концепция монтированного и немонтированного файлового пространства. Но первым шагом является установка пакета дисков (или гибкого диска) в дисковое устройство. После этого можно выполнять операции чтения и записи на физических носителях, указывая определенные имена устройств. Монтирование представляет собой логическое действие, которое читает суперблок с пакета дисков и записывает его в память, где ядро системы поддерживает свою версию суперблока. Периодически выполняется запись версии, находящейся в памяти, обратно на диск, чтобы эти две версии были по возможности одинаковыми. В System V это делает программа update, которая работает в фоновом режиме с момента загрузки системы. Для обращения непосредственно к физическому носителю используются такие команды, как, например, команда "od -c /dev /rfd0", которая дампирует неструктурированный гибкий диск. Одной из команд, которые непосредственно помещают данные на устройство, является команда "cp file /dev/rfd0". Область данных начинается с самого первого байта на гибком диске. Такие данные несовместимы с командами tar, cpio или файловой системой. Для обращения к файловой системе используется команда "mount /dev/fd0 /mnt". Начиная с этого момента, все обращения к данному устройству выполняются через каталог /mnt. Важно то, что непосредственный доступ к файлу устройства является операцией более низкого уровня, чем операции файловой системы, что позволяет прочитать информацию о суперблоке непосредственно с устройства. Входной информацией для программы mntlook являются имена файлов устройств. В командной строке нельзя указывать никакие опции. Имена устройств могут быть как блочными, так и символьными. Для операции чтения это не имеет никакого значения, если только у вас имеются права чтения. Затем программа читает второй блок и проверяет "магическое число", определяющее суперблок. Суперблок - это просто структура языка Си, которая предопределена системой и хранится в файле filsys.h, что указано в нашей программе оператором #include. Магическое число представляет собой длинное целое, имеющее заранее определенное значение. Если элемент структуры, которая прочитана с диска, содержит это число, то считается, что остальная часть структуры является корректными данными. Для каждой файловой системы имеется только одна структура суперблока. Если магическое число такое, как надо, выводится остальная информация о файловой системе. Если это число некорректно, на экран ничего не выводится и программа обрабатывает в цикле следующий файл устройства, указанный в командной строке. Данная программа может служить в качестве функции, обеспечивающей безопасность, поскольку она умеет идентифицировать файловые системы, которые кто-либо оставил на машине несмонтированными. Как отмечается в главе 9, нарушители защиты могут читать данные с немонтированных устройств, поэтому если оставить устройство или файловую систему немонтированными, то на самом деле это не предохраняет их от несанкционированного доступа.

ПРИМЕРЫ

   1.  $ mntlook /dev/hd13

Поиск суперблока на устройстве с именем hd13. Это имя указывает устройство 1, третий раздел. Для просмотра разделов в среде XENIX нужно запустить программу fdisk. Для System V нужно воспользоваться командой iv.

   2.  $ mntlook /dev/fd0*

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

   3.  $ for DEV in /dev/*[fh]d*
   > do
   >        echo "checking device: $DEV"
   >        mntlook $DEV
   > done

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

ПОЯСНЕНИЯ

Строки 3-7 определяют включаемые файлы, которые использует данная программа. Вам необходимо изучить эти файлы, поскольку они не только помогут вам понять работу этой программы, но и покажут вам некоторые важные значения, имеющие отношение к файловым системам. Строка 9 определяет размер используемого буфера. Этот буфер применяется только для хранения сообщений об ошибках, поэтому он не должен быть очень большим. Строка 15 определяет структуру суперблока. Он имеет тип filesys (см. включаемый файл sys/types.h). На моей машине суперблок имеет размер 1024 байта. Если вы не знаете точно размер буфера у вас, то вы можете проверить его, вставив в программу следующий оператор:

    printf ("the size is %d",sizeof(sb))

Строка 16 описывает рабочую переменную d и дескриптор файла dev. Строка 17 объявляет буфер размером BSIZE. Строки 19-59 - это один большой цикл for. Этот цикл представляет собой всю остальную часть программы. Он выполняется столько раз, сколько аргументов указано в командной строке. Счетчик цикла начинается с 1, поскольку первым аргументом argv[0] является имя команды. В качестве аргументов должны быть указаны имена файлов, поэтому данный цикл использует каждое имя по очереди. В строках 21-26 проверяется, не начинается ли текущий рассматриваемый нами аргумент со знака '-'. Если да, то это опция, что представляет собой ошибку, поэтому выводится сообщение об ошибке и оператор continue вызывает выполнение следующей итерации цикла. Таким образом, данная программа отвергает опции, но работает при обнаружении имен файлов. Считая, что имя файла было найдено, строки 27-32 открывают файл устройства с этим именем только для чтения. Если данный вызов open неудачен, мы посылаем сообщение в буфер вместе с именем, указанным в командной строке. Этот буфер передается программе обработки ошибок (системной), которая использует наше сообщение как первую часть своего сообщения об ошибке. Она выводит системное сообщение, которое определяет данную ошибку. По оператору continue начинается выполнение следующей итерации цикла for. Строки 35-39 читают первый блок файла. Для корневой файловой системы первым блоком является загрузочная запись. Если при этом чтении возникает ошибка, выводится сообщение об ошибке и выполнение цикла продолжается. Строки 42-46 читают второй блок, который должен находиться в том месте, где размещается суперблок, если он имеется. По информации, прочитанной и помещенной в структуру, мы можем получить доступ к каждому элементу по его имени. Строка 48 проверяет, равно ли магическое число в структуре магическому числу, определенному в файле заголовка. Если они совпадают, программа mntlook выводит имя файла устройства и сообщение о том, что файловая система корректна, имя файловой системы (если оно имеется), имя пакета дисков, размер используемого блока и магическое число в шестнадцатиричном виде. В строках 53-54 мы имеем подобие кодированной структуры: оператор printf использует структуру типа if-then-else для указания строки, которую нужно выводить. После того как выведена первая строка, выполняется проверка и если она прошла успешно, то выводится значение 512. Если в результате проверки получено значение "ложь", выводится значение 1024. Этот метод описан в книге B.W.Kernighan, D.M.Ritchie "The C Programming Language" (Prentice-Hall, 1978). Строка 58 закрывает устройство, и цикл возвращается в начало для принятия следующего имени устройства.

РАЗМЕРНЫЕ ПАРАМЕТРЫ

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

РАЗМЕРЫ БЛОКОВ

System V является последним достижением ветви фирмы AT&T в фамильном дереве UNIX. Это означает, что System V содержит последние правки, внесенные в исходную систему UNIX. Эти правки предназначены для того, чтобы сделать UNIX жизнеспособным и стойким коммерческим продуктом. Для повышения устойчивости были внесены изменения, касающиеся работы с файлами и размеров их блоков. Обычно обмен данными с дисками осуществляется блоками по 512 байтов. Дисковая аппаратура имеет дело именно с таким размером. Для учета этого факта UNIX первоначально использовал 512-байтные блоки внутри файловой системы, что, возможно, облегчало написание программ и создавало впечатление, что так и нужно. Однако нельзя отрицать, что при этом UNIX может работать медленно! Для ускорения работы внутренние программы в настоящее время используют блоки размером 1024 байта. Сам диск должен выполнить два обращения к 512-байтным блокам, но в системе две эти операции чтения рассматриваются как одна операция чтения блока размером 1024 байта. Единственная проблема заключается в том, что одни утилиты выдают результаты в 512-байтных блоках, а другие - в 1024-байтных, в зависимости от того, когда они были написаны. Когда сильно приближаются пределы свободного пространства на диске, вам действительно нужно знать, с каким размером вы имеете дело. Для лучшего понимания проблемы размеров блоков, в табл. 7-3 показано, какие утилиты какой размер блока используют. Эта информация получена в основном из System V на машине VAX, из другого варианта System V и из XENIX. Эти значения могут отличаться на разных машинах, но идея сохраняется. Вы видите, что большинство утилит выдают результат в блоках размером 512 байтов, но утилиты, относящиеся к файловой системе, выдают результат в 1024-байтных блоках. Поскольку UNIX обращается к дисковому пространству поблочно, важно уметь точно вычислять, сколько свободного пространства в файловой системе. Весьма плоха ситуация, когда имеется какой-то большой файл в редакторе vi (который использует файл /tmp для промежуточного редактирования), а на диске недостаточно места для записи временного файла редактора vi в реальный файл на диске. На самом деле это может случиться на персональных машинах с ограниченным (скажем, 20 Мбайт) объемом жесткого диска.

Таблица 7-3
Размеры блоков для различных команд системы UNIX

512 байтов/блок | 1024 байта/блок
ls -s | fdisk (размеры разделов)
sum | mkfs
cpio, df, du | fsck

РАСЧЕТЫ, СВЯЗАННЫЕ С БЛОКАМИ

Еще одним важным вопросом, имеющим отношение к физическим устройствам и логическим файловым системам, является определение местонахождения определенного блока на жестком диске. Номер этого блока вычисляется по номерам цилиндра, дорожки и сектора. Знание номеров блоков становится важным, когда на диске появляется дефектное место. Это дефектное место отмечается номерами цилиндра и головки. Вам необходимо вычислить, какие блоки попадают в дефектную область и занести их номера в таблицу дефектных блоков. Обратная задача также важна. Если программа fsck начинает сообщать, что где-то появился дефектный блок, то каким образом мы можем узнать номера цилиндра, головки, сектора и т.д. для данного дефектного блока? Такое обратное вычисление сделать очень тяжело, если не невозможно. Во-первых, номер блока представляет собой произведение четырех чисел. Трудно узнать, какие именно эти числа. Кроме того, файловые системы могут использовать информацию вида база/смещение, поэтому блок номер 1 в файловой системе в действительности является блоком номер 1382 на диске. Определить, какого вида информация была использована в данном случае, тоже тяжело. Конкретные данные в следующем примере относятся к вполне определенной машине, но на других машинах используются подобные зависимости. Эти данные относятся к машине с жестким диском объемом 20 Мбайт с системами XENIX/DOS.

Характеристики устройства:

    1 диск    = 615 цилиндров, или 615 цилиндров/диск
    1 цилиндр = 4 головки (дорожки), или 4 головки/цилиндр

Промышленный стандарт:

    1 дорожка  = 17 секторов, или 17 секторов/дорожку
    1 сектор   = 512 байт, или 512 байт/сектор
    1 Кбайт    = 1024 байта = 2^10
    1 Мбайт    = 1024 килобайта = 2^20 = 1 048 576 байт

Характеристики устройства различны для разных устройств, но промышленный стандарт для числа секторов на дорожку и байтов на сектор остается одинаковым. В табл. 7-4 показаны примеры характеристик различных устройств.

Таблица 7-4
Размеры жестких дисков и их конфигурация

Число цилиндров | Число головок | Мегабайты
981 | 3 | 25
697 | 5 | 30
981 | 5 | 42
925 | 7 | 55
1024 | 8 | 71

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

  615 цил   4 дор   17 сек   512 байт
  ------- * ----- * ------ * -------- = 21 411 840 байт/диск
   1 диск   1 цил   1 дор     1 сек

  21411840 байт    1 мегабайт
  ------------- * ------------ = 20.4 мегабайта/диск
   1 диск      1048576 байт

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

     цилиндр, дорожка, сектор = физический сектор
     цилиндр, головка, байт   = блок

Эти две записи выражают в точности одно и то же. Когда вы используете запись вида цилиндр/дорожка/сектор, то в результате получаете физический сектор. Используя запись вида цилиндр/головка/байт, вы получаете в результате номер блока. Следует помнить, что ГОЛОВКА - это то же самое, что и ДОРОЖКА. Если вы это запомните, все остальное встанет на свое место. Ниже приводятся некоторые общие вычисления, которые часто всплывают, когда вы работаете с диском на низком уровне. Эти примеры в большей степени относятся к самому устройству, чем к системе UNIX. Однако после того, как вы поймете эту информацию о диске, вам будет легче понять, как работает UNIX на этом уровне. 1. Сколько дорожек имеет диск?

              615 цил   4 дор
     Решение: ------- * ----- = 2460 дор/диск
               1 диск   1 цил2. Сколько байт в дорожке?
              17 сек   512 байт
     Решение: ------ * -------- = 8704 байт/дор
               1 дор    1 сек3. Сколько дорожек в одном мегабайте?
              2460 дор   1 диск
     Решение: -------- * ------ = 123 дор/Мб
               1 диск    20 Мб4. Сколько цилиндров в одном мегабайте?
              1 цил   2460 дор
     Решение: ----- * -------- = 30 цил/Мб,
              4 дор    20 Мб

          615 цил   1 диск     123 дор
     или  ------- * -------- * ------- = 30 цил/Мб
           1 диск   2460 дор    1 Мб5. Дан цилиндр 47,  дорожка 2, сектор 4.
Какой физический номер сектора?
    Решение:
    Сначала мы обращаем внимание на то, что вопрос касается секторов.
    В качестве единиц измерения даны цилиндр,  дорожка и сектор.
    Как перевести их в другие единицы?  Мы знаем,  что головки -
    это то же  самое, что  и  дорожки,  поэтому  в  вычислениях
    нужно использовать 4 головки вместо 4 дорожек:
              4 дор   17 сек           17 сек
     47 цил * ----- * ------ + 2 дор * ------ + 4 сек =
              1 цил   1 дор             1 дор

            = 3196 сек       +         34 сек + 4 сек =

            = сектор 3234

РАЗМЕРЫ ФАЙЛОВ

В основном при работе в системе UNIX мы считаем, что ее ресурсы безграничны. Например, мы не заботимся о том, что созданный файл получится "слишком большим", а это не так уж редко в персональных компьютерах на гибких дисках. Если же мы занимаемся сопровождением и администрированием системы UNIX, то мы должны быть готовы иметь дело с ситуациями, когда превышаются различные предельные значения системы. Всегда лучше исследовать эти вопросы заранее в некритичных ситуациях, поэтому давайте рассмотрим пределы размеров файлов и их смысл. Некоторые параметры "зашиты" в ядро системы при ее генерации. Одним из таких значений является максимальный размер файла. Он определяет наибольшее число блоков, которые может занимать файл. Этот параметр тесно связан с принятым в UNIX методом использования индексных дескрипторов файла (inodes). Это наборы указателей, среди которых первые десять указывают на блоки данных, следующий указывает на другую таблицу, следующий - на таблицу, указывающую на таблицу и т.д. Имеется еще одно ограничение размера файла, которое определено для каждого пользователя во время работы в системе - число ulimit (user limit - пользовательский предел). Это значение устанавливается в момент вашей регистрации в системе и представляет собой число блоков по 512 байт, которые вы можете записать в любой заданный файл. В shell'е имеется команда ulimit, которая при ее вызове без аргументов выводит это число. Эта же команда позволяет вам уменьшить ваше значение ulimit. Только суперпользователь (root) может УВЕЛИЧИТЬ значения ulimit. Побочным эффектом уменьшения значения ulimit является то, что вы не можете снова увеличить его до регистрационного значения. Значение ulimit остается таким же на все время работы вашего shell, поэтому для восстановления регистрационного значения вам необходимо выйти из системы, а затем снова зарегистрироваться. Еще одним интересным моментом является то, что если вы установите ваше значение ulimit равным 0, вы не сможете создать никакие файлы! Максимально допустимым размером файла в данном случае является нулевой, поэтому никакой файл не может быть создан. Это представляется достаточно резонным, однако существуют такие ситуации, когда файл нулевого размера МОЖЕТ существовать. Опять же, для восстановления вашего обычного значения ulimit необходимо выйти из системы, а затем снова зарегистрироваться. Как отмечалось ранее, увеличить значение ulimit может только суперпользователь. Эта процедура довольно проста. Сначала нужно увеличить значение ulimit командой ulimit, а затем запустить shell. Этот новый shell имеет новое значение ulimit. Если мы хотим, чтобы система загружалась с shell, имеющим большее значение ulimit, мы можем установить программу в inittab (таблице инициализации системы), чтобы эта операция выполнялась автоматически. Ниже приводится пример программы, которая изменяет значение ulimit и запускает shell с этим новым значением. Напомним, что эта программа может быть запущена только суперпользователем.

       1   #include
       2   #include

       4   main()
       5   {
       6       long v1, v2, v3, newlimit = 5120;

       8       v1 = (long)ulimit(UL_GFILLIM, 0L);
       9       v2 = (long)ulimit(UL_SFILLIM,newlimit);
       10      v3 = (long)ulimit(UL_GFILLIM, 0L);

       12      printf("v1: %ld     v2: %ld    ulim: %ld\n",v1,v2,v3);
       13      setuid(getuid());
       14      execl("/bin/sh","ulimit sh", 0);
       15  }

Значение ulimit является возвращаемым значением системного вызова ulimit. Первый вызов ulimit в строке 8 получает исходное значение по умолчанию. Это значение сохраняется в переменной v1. Вызов в строке 9 устанавливает новое значение ulimit равным значению переменной newlimit. Если этот вызов оканчивается неудачей, переменной v2 присваивается возвращаемое значение -1, и мы видим это по распечатке, которую выдает строка 12. Если вызов был успешным, возвращаемым значением является новое значение ulimit, и это мы тоже видим. Затем вызов в строке 10 получает это значение ulimit. Это или новое значение, или старое, в зависимости от того, была ли успешной попытка изменить ulimit. В строке 13 значение идентификатора текущего процесса устанавливается равным значению идентификатора пользователя, запустившего данный процесс. Это сработает только в том случае, если пользователь, запустивший данный shell, имеет более низкий идентификатор, чем сам процесс. Цель заключается в том, чтобы предоставить возможность обычным пользователям запускать данный процесс, давая им временно права суперпользователя. (Не оставляйте исходный текст этой программы в системе, поскольку кто-то может превратить ее в "лазейку" и перекомпилировать ее - в главе 9 мы увидим такого рода дыры в системе защиты.) Строка 14 запускает shell. Аргументом этого shell является строка "ulimit sh". Эта строка будет выведена на экран, если мы выполним команду "ps -ef". Данный shell имеет новое значение ulimit. Возможность изменить значение ulimit позволяет нам определить наибольший возможный размер файла. Создание одного или нескольких таких файлов максимального размера полезно в целях тестирования. Например, полезно выяснить, сколько данных может содержать гибкий диск без переполнения или что произойдет, когда система выйдет за пределы свободных блоков. Мы хотим понять, как ведет себя система в таких ситуациях.

      ИМЯ: umntsys

НАЗНАЧЕНИЕ

Размонтирование всех файловых систем, смонтированных в данный момент.

ФОРМАТ ВЫЗОВА

   umntsys

ПРИМЕР ВЫЗОВА

    umntsys     Размонтирует все смонтированные файловые системы

ТЕКСТ ПРОГРАММЫ

       1   :
       2   # @(#)umntsys v1.0  Unmount all file systems  Author: Russ Sage
                        Размонтирование всех файловых систем

       4   if [ "$#" -gt 0 ]
       5     then  echo "umntsys: too many arguments" >&2
       6           echo "usage: umntsys"              >&2
       7           exit 1
       8   fi

       10  /etc/mount | sed -n -e '/^\/ /d' -e 's/^.* on \(.*\)
       read.*/umount \1/p' | sh -

ОПИСАНИЕ

Зачем нам нужен командный файл umntsys?

Иногда возникают ситуации, когда вы как администратор хотели бы запустить систему в однопользовательском режиме. Например, вы хотите сменить или установить жесткие диски и вам нужно, чтобы никто не имел доступа к этому устройству, пока вы с ним работаете. Вам может также понадобиться запустить систему в минимальной конфигурации с целью локализации какой-то проблемы. Поскольку выполнение операций завершения работы системы и перезагрузки представляет собой довольно длительную процедуру, было бы лучше иметь способ сохранить систему работающей, но переключить в однопользовательский режим, а затем быстро перезапустить многопользовательский режим, чтобы свести к минимуму неудобства пользователей. Для того чтобы сделать это, нам нужно понять концепцию "уровней работы системы" и использовать их. Уровень работы (run level) в системе UNIX представляет собой состояние или конфигурацию, в которой может быть машина. Фактически это число, которое определяет, какие возможности системы включены или отключены и находится ли система в одноили многопользовательском режиме. Описание того, что происходит на каждом уровне работы системы, содержится в файле /etc/inittab. Обычно изменение уровня работы системы включает в себя переход от многопользовательского режима (например, уровень 6), к однопользовательскому режиму (уровень S). Одним из побочных эффектов перехода от многопользовательского режима к однопользовательскому является то, что все дополнительные файловые системы размонтируются. Единственной смонтированной файловой системой является корневая (определенная как /dev/root, /dev/hd0a и т.п.). Ее никогда нельзя размонтировать. Когда происходит переход обратно к многопользовательскому режиму, файловые системы обычно повторно монтируются с помощью файла /etc/rc. Мы можем эмулировать однопользовательский режим путем прекращения выполнения всех процессов в системе командой kill и размонтирования всех файловых систем. Командный файл umntsys предназначен для этой цели.

Что делает umntsys?

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

ПОЯСНЕНИЯ

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

    |
    |    / on /dev/hd0a read/write on Mon Jan 06 09:53:03 1986
    |    /tmp on /dev/hd01 read/write on Mon Jan 06 09:53:03 1986
    |    /usr on /dev/hd02 read/write on Mon Jan 06 09:53:03 1986
    |    /u1 on /dev/hd03 read/write on Mon Jan 06 09:53:03 1986
    |    /u2 on /dev/hd04 read/write on Mon Jan 06 09:53:03 1986
    |    /u3 on /dev/hd05 read/write on Mon Jan 06 09:53:03 1986
    |    /mnt on /dev/fd01 read/write on Mon Jan 06 09:54:41 1986
    |

Когда файловая система смонтирована, требуются и каталог, и имя устройства. Когда файловая система не смонтирована, используется только имя устройства. Нам нужно вырезать имена устройств из таблицы монтирования и вставить их в команду umount. Это делается с помощью команды sed. Команда sed начинает работать с опцией -n, которая подавляет выполняемый по умолчанию вывод на экран, поэтому ничего не выводится, пока мы не попросим. Мы можем использовать это в своих интересах, отфильтровывая ненужные нам строки. Первой коррекцией таблицы смонтированных файловых систем является избавление от записи о корневой файловой системе, поскольку мы бы не хотели пытаться ее размонтировать. Поскольку корневой файловой системе соответствует каталог "/", мы можем использовать его в качестве ключа. Выражение в операторе sed означает: "Искать с начала строки первый символ наклонной черты (поскольку этот символ имеет специальное значение, он экранирован обратной косой чертой) и пробел за ним. Когда наклонная черта найдена, удалить ее". Данный шаблон поиска соответствует только записи о корневой файловой системе. Следующая операция редактирования выполняется более замысловато. Она использует возможность группирования регулярных выражений и последующей ссылки на них по номеру, что вы уже видели в некоторых наших предыдущих командных файлах. Данный синтаксис (регулярное выражение) предназначен для группирования символов и последующей ссылки на них с помощью номера \n. Фокус в том, чтобы выделить только имя устройства и сгруппировать его, что и делает команда подстановки sed'а. Первое выражение означает: "От начала строки распознать любой символ, за которым следует любое количество любых символов, пробел и слово `on'; сгруппировать следующие символы вплоть до пробела, слово `read' и все символы после него". В результате всего этого выделяется имя устройства и помещается во временную переменную, чтобы впоследствии к ней можно было обратиться. Вторая часть подстановки создает новую строку взамен исходной. Эта строка состоит из слова "umount", пробела, затем группового выражения номер 1, которое представляет собой временную переменную, содержащую имя устройства. В результате всех этих действий таблица смонтированных файловых систем (за исключением записи о корневой системе) превращается в набор команд размонтирования с именами устройств в качестве аргументов. Полученный результат имеет примерно такой вид:

    |
    |    umount /dev/hd0a
    |    umount /dev/hd01
    |    umount /dev/hd02
    |    umount /dev/hd03
    |    umount /dev/hd04
    |    umount /dev/hd05
    |    umount /dev/fd01
    |

Теперь эти команды по конвейеру передаются другому shell ("sh -"). Символ "-" указывает shell, что свои команды он должен получать со стандартного ввода, а в данном случае это наши команды umount, переданные по конвейеру. Они размонтируют все файловые системы.

      ИМЯ: lrg

flrgf Создает файл максимально возможного размера

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

   lrgf

ПРИМЕР ВЫЗОВА

    lrgf      Определение границы размера файла

ТЕКСТ ПРОГРАММЫ

       1   char id[] =
         "@(#) lrgf v1.0 Create the largest file Author: Russ Sage
                    Создать файл максимального размера

       3   #include
       4   #include
       5   #include
       6   #include

       8   #define FSIZ 512
       9   #define BSIZ 1024

       11  long ulimit();
       12  char buf[BSIZ];

       14  main()
       15  {
       16      register int n, fd, bcnt;
       17      char file[FSIZ];

       19      for (bcnt=0; bcnt

ОПИСАНИЕ

Зачем нам нужна программа lrgf?

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

Что делает lrgf?

Lrgf - это программа, которая создает файл максимально возможного размера. Выполняется это путем записи в файл до тех пор, пока команда записи не окончится неудачей. Это означает, что в данный файл больше нельзя записать данные, и он достиг границы. При вызове программа lrgf выводит сообщение, содержащее общее количество байтов, которое вы можете записать в файл. Это значение вычисляется по значению ulimit, поэтому оно разное для разных пользователей и зависит от shell, с которым пользователь сейчас работает. Затем у вас запрашивается имя файла. Вы можете указывать любое имя, для которого вы имеете права записи. Одним из способов проверки жесткого диска является запись одного из таких больших файлов в каждый раздел диска и затем проверка отдельных файловых систем. После запуска программы вы увидите строку с сообщением, которое постоянно обновляется. Оно содержит общее число записанных блоков и количество байтов, которое записывалось при каждой попытке записи. Программа lrgf записывает в файл каждый раз по 1024 байта. В зависимости от вашего значения ulimit количество байтов, дописываемых в конец файла, может не быть равным в точности 1 Кб. Выходное сообщение постоянно печатается в одной и той же строке, заменяя на экране старое значение. Это достигается путем вывода только символа возврата каретки, а не перевода строки. Когда программа не может больше записывать данные в файл, итоговое количество блоков выводится на экран. Это общее число блоков, записанных в файл.

ПРИМЕРЫ

   1.  $ lrgf
   /dev/rfd0

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

    2.  $ lrgf
    /usr/tmp/lrg

Создание файла в файловой системе /usr. Большинство систем XENIX используют каталог /usr как отдельную файловую систему, отличную от корневой. Созданием файла в каталоге /usr /tmp мы можем проверить положение дел в этой часто используемой файловой системе.

    3.  $ lrgf
    /tmp/lrg

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

    4.  $ lrgf
         /mnt/lrg

Создание файла на гибком диске в предположении, что на гибком диске имеется файловая система и она смонтирована в каталог /mnt.

    5.  $ F=0
    $ while :
    > do
    >           echo -r "-->  Making file $F  <--"
    >           ./lrgf <<-!
    >           $F
    >           !
    >           echo
    >           F=`expr $F + 1`
    > done

Данный цикл запускает программу lrgf бесконечное число раз. Счетчиком является переменная F. Она должна быть предварительно установлена в нуль, чтобы shell рассматривал ее как число, а не как символьную строку. Сначала выводится сообщение, содержащее имя создаваемого файла. Первым именем файла является 0. Программа lrgf запускается, используя в качестве входных данных "данный документ" (т.е. сам командный файл). В качестве ответа на вопрос об имени файла используется значение $F. Значение переменной F увеличивается, и программа lrgf вызывается снова. Именами файлов являются 0, 1, 2 и т.д. Это продолжается до тех пор, пока не останется больше свободного места. Вряд ли вы будете пользоваться этим часто, но для тестирования это прекрасное средство заполнить все свободное пространство. Если вы хотите увидеть, что делает ваша система, когда исчерпаны свободные блоки, примените данный командный файл.

ПОЯСНЕНИЯ

Строки 3-6 включают все необходимые файлы заголовков. Эти файлы содержат определения и метки, необходимые данной программе. Строки 8 и 9 определяют размеры буфера для имен файлов и буфера для записи на диск. Значение BSIZ можно поднастроить, если программа работает слишком медленно. У вас может возникнуть желание увеличить BSIZ до 4096, чтобы производилось не так много операций записи. Строка 11 определяет возвращаемое значение системного вызова ulimit как длинное целое. Строка 12 резервирует буфер, который должен быть записан. Этот буфер находится вне основной части программы из-за ограничений на размер внутри функций. В основном блоке программы наибольшая область автоматической памяти, которую вы можете иметь, равна размеру вашего стека. Вы можете сделать по-другому, объявив данный буфер как статическую переменную в функции main. Мы решили вынести его за пределы функции main и не объявлять как статическую переменную. Строка 16 объявляет некоторые рабочие переменные. Заметим, что они помещаются в регистры. Это сделано для ускорения работы программы и получения более компактного объектного кода. Строка 17 резервирует буфер, в который вы вводите имя файла.

Строки 19 и 20 заполняют записываемый буфер символами "x", поэтому после создания файла мы можем их там увидеть. Строка 22 выводит значение ulimit для вашего процесса. Обратите внимание, что вызов ulimit возвращает количество блоков, поэтому мы должны умножить это число на 512. В результате мы получим общее количество байтов, которое может содержать файл. Строки 24-26 запрашивают имя файла, читают его и подготавливают экран для следующего сообщения. Строки 28-32 открывают файл с указанным именем для получения дескриптора файла. Файл открывается для записи и чтения, создается при необходимости и обрезается, если он уже существует. Если операция открытия файла заканчивается неудачей, выводится сообщение об ошибке, и программа завершается. Строки 34-42 выполняют запись. Цикл for бесконечен, поскольку в середине оператора нет проверки значения счетчика. Переменная bcnt постоянно увеличивается, пока выполняется запись. Строка 36 выполняет запись в файл. Если запись неудачна, выводится сообщение об ошибке и по оператору break осуществляется выход из цикла. Строка 41 выводит количество выполненных операций записи и количество записанных байтов. Обратите внимание, что данный оператор print содержит возврат каретки (\r), а не перевод строки. Это позволяет курсору оставаться в одной итемах. экране поверх старых значений. Экран не скроллируется, что удобно для наблюдения. Выполнение цикла продолжается до тех пор, пока системный вызов write не закончится неудачей и оператор break не прекратит цикл. Когда это происходит, выполнение продолжается со строки 43, которая печатает "end of program". Выполнение команды "ls -l" для записанного файла показывает, сколько байтов имеет файл максимального размера. Это количество должно совпадать с числом, которое сообщила вам программа lrgf. В данной главе представлена лишь небольшая часть возможных исследований внутренней работы файловых систем и устройств в UNIX. Некоторые из представленных программ могут быть неприменимы в вашей версии системы UNIX или в вашей конфигурации аппаратных средств или могут выглядеть в вашей системе иначе. Однако общие принципы сохраняются, и вы можете использовать рассмотренные средства в качестве основы для ваших собственных исследований.


ГЛАВА 8. Коммуникации в системе UNIX

  • Введение
  • Физическое подключение
    • Подключение без коммуникационных устройств
    • Доступ микро-ЭВМ или терминала к системе UNIX
    • Прямое подключение
    • Дистанционное подключение
  • Доступ из системы UNIX к микро-ЭВМ
    • Обнаружение модемов в системе
    • Использование и конфигурирование линии
    • Изменение скоростей обмена
    • Захват данных
  • Инструментальные средства для коммуникационных линий
    • cuchk - Cu check - проверка свободной линии для cu
    • talk - Обращение к последовательному порту
  • Доступ из UNIX в UNIX
    • Связывание UNIX-машин
    • Области для передаваемых файлов
  • Сетевая безопасность
    • Ограничительные файлы
  • Отладка коммуникаций
    • uust Состояние uucp и служебные действия
    • uutrans Передача файловых деревьев из одной системы UNIX в другую систему UNIX
  • Конфигурационные решения
    • Подключение UNIX к модему и главной машине
    • Подключение UNIX и терминала к модему
    • Подключение UNIX к терминалу, модему и главной машине
    • Система UNIX, МИКРО-ЭВМ И МОДЕМ
    • Альтернативное решение
    • Три системы UNIX

ВВЕДЕНИЕ

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

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

Сначала мы займемся физическими соединениями, что является первым этапом установки линии связи. Мы рассмотрим модель интерфейса RS232-C и выясним, как подключить прямую межмашинную связь.

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

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

В завершение мы рассмотрим связь между системами UNIX с помощью утилиты uucp. Мы увидим, как и куда передавать файлы между системами, как файлы защиты данных управляют средой uucp и изучим способы отладки механизма передачи файлов с помощью uucp. Здесь представлены командные файлы uust для выполнения рутинной работы по обслуживанию uucp и uutrans для копирования древовидной структуры каталогов из одной системы в другую.

ФИЗИЧЕСКОЕ ПОДКЛЮЧЕНИЕ

Большинство машин, на которых работает UNIX, имеют один или несколько последовательных портов. Эти порты - глаза и уши машин, позволяющие системе связываться с внешним миром. Их можно использовать для подключения любого устройства с интерфейсом RS-232-C и для связи или управления. В данном разделе мы рассмотрим, как подключить интерфейс RS -232-C для обеспечения связи типа UNIX-UNIX, терминал-UNIX и модем-UNIX.

Начнем с рассмотрения базовой модели RS-232-C, показанной на рис. 8-1. Эта модель иллюстрирует, как могут общаться друг с другом две машины и/или терминалы либо через модемы по телефонным линиям, либо по прямой (проводной) связи. Хотя последующее обсуждение мы ведем преимущественно в терминах телефонных соединений, те же базовые принципы относятся и к прямой связи, за исключением того, что коммуникационные устройства (DCE, data communication equipment) в этом случае не нужны.

Риунок. 8-1.
Стандартная модель интерфейса RS-232-C

На каждом конце находятся терминальные устройства, называемые DTE (data terminating equipment). В роли DTE может выступать терминал, например, VT-100, или центральный процессор микро-, миниили большой ЭВМ.

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

Подсоединение и обработка сигнала между DTE и DCE полностью соответствуют стандарту RS-232-C. Аппаратный протокол позволяет DTE использовать DCE для посылки и приема данных от другого DTE.

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

Устройства DCE необходимы по той причине, что устройства DTE являются цифровыми, а телефонные линии - аналоговыми. Единственный способ передать цифровую информацию по аналоговым линиям закодировать цифровую информацию в аналоговый сигнал, послать этот сигнал по телефонным линиям, а затем декодировать аналоговый сигнал обратно в цифровую информацию.

ПОДКЛЮЧЕНИЕ БЕЗ КОММУНИКАЦИОННЫХ УСТРОЙСТВ

Если ваши машины расположены довольно близко (в пределах 50 футов / 15 метров. Один фут составляет около 30.5 см. - Примеч. перев./), вам не нужен модем, вы можете использовать кабель "нулевого модема" вместо DCE. Кабель нулевого модема имитирует такой же протокол, что и DCE, но не требует наличия модема для коммуникаций. Основная задача подключения нулевого модема - обеспечить перекрещивание между передающими и принимающими сигналами. На рис. 82 показана общая схема подключения без устройств DCE.

Рисунок 8-2.
Конфигурация с нулевым модемом

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

Рисунок 8-3.
Кабель нулевого модема RS-232-C

Линии 1 и 7 используются для шасси и сигнальной земли соответственно. Линии 2 и 3 пересекаются таким образом, чтобы когда одна сторона говорит, другая слушала. Обе стороны могут говорить одновременно (это называется полнодуплексным режимом), если мы используем различные наборы проводов.

Для имитации управляющих сигналов линии 4, 5 и 8 подсоединяются так, как показано на рис. 8-3. Каждый раз, когда устройство DTE-1 активизирует линию "Request to Send" ("Запрос передачи"), т.е. передает по ней сигнал, оно получает назад сигнал "Clear to Send" ("Гашение передачи"), указывающий, что другая сторона готова принять данные. Затем, посылая сигнал по линии "Dаta Carrier Detect" ("Идет передача данных"), устройство DTE-1 сообщает другой стороне, что поступают данные. Такое методичное "аппаратное рукопожатие" гарантирует, что никакие данные не будут отправлены, пока другая сторона не будет готова их принять.

Линии 6 и 20 подсоединяются так, чтобы обеспечить последние управляющие сигналы нулевого модема. Пока DTE активно ("Data Terminal Ready" - "Готовность терминала", линия 20), другая сторона считает, что имеет дело с активным модемом ("Data Set Ready" - "Готовность набора данных", линия 6). При таком способе соединения линий 6 и 20 всякий раз, когда вы выдергиваете ваш кабель из машины или переключаете его на другой канал соединительной коробки, другая сторона теряет ваш сигнал активности и отключается (или генерирует сигнал HUP - hangs up, повесить трубку телефона). Чтобы сделать такой кабель, который не вызывает отключения при вынимании штепселя (т.е. NOHUP), присоедините выход "Data Terminal Ready" ко входу "Data Set Ready" на том же устройстве DTE. Это заставляет систему сообщать самой себе, что модем всегда готов.

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

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

ДОСТУП МИКРО-ЭВМ ИЛИ ТЕРМИНАЛА К СИСТЕМЕ UNIX

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

ПРЯМОЕ ПОДКЛЮЧЕНИЕ

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

Обычный сценарий подключения терминала выглядит примерно таким образом. Пользователь с помощью терминала, например DEC VT-100, регистрируется в системе UNIX по прямой связи. Терминальное устройство DTE должно быть установлено в соответствии с правильной внутренней конфигурацией, включая скорость в бодах, стартовые и стоповые биты, число битов данных и четность. Это обычные установки терминала, которые можно задать либо тумблерными переключателями, либо при помощи терминальной микропрограммы "SETUP".

Микро-ЭВМ, в отличие от терминала, должна запустить некоторое коммуникационное программное обеспечение для корректного управления аппаратурой. Эти программы обычно снабжены меню или каким-то другим способом указания параметров - таких же, как у терминалов. Действительно, имеются программные пакеты, обеспечивающие полную или почти полную эмуляцию популярных терминалов, таких как VT-100. Коммуникацинное программное обеспечение, используемое вами, устанавливается не специально для UNIX, а для обработки сигналов RS-232-C, которые обычно не зависят от того, прямое ли подсоединение или через модем.

Рисунок 8-4.
Прямое подключение терминалов и микро-ЭВМ к UNIX

В качестве кабеля, соединяющего терминал (или микро-ЭВМ) с системой UNIX, нужно использовать нулевой модем. Когда коммуникационное устройство DCE отсутствует, применяется кабель нулевого модема.

В системе UNIX на терминальном устройстве DTE должна быть утилита getty (что первоначально означало "get teletype" - доступ к телетайпу), которая работает с определенным портом и обнаруживает момент, когда кто-то пытается зарегистрироваться в системе. Программа getty (описанная в init(M) и getty(M)) выводит подсказку "login:" и читает символы, поступающие по линии.

При прямом подключении процесс getty может работать со скоростью 9600 бод, поэтому пользователи могут получить выигрыш от применения этой скорости. Это значительно быстрее, чем 1200 или иногда 2400 бод - скоростей, которые используются чаще всего при работе с обычными телефонными линиями. В зависимости от того, как установлен файл gettydefs, вы можете обычно изменить скорость работы getty, нажимая клавишу break. Клавиша break генерирует не символ, а сигнал в линию, который длится определенный интервал времени. Драйверы устройств в системе UNIX распознают этот сигнал и действуют соответствующим образом. Рассмотрение самого метода, с помощью которого getty работает на различных скоростях, выходит за пределы данной книги. Важно знать, что если getty работает на скорости 9600 бод, а вы используете терминал, установленный на 1200 бод, то вам нужно прокрутить переключение скоростей getty с 9600 бод через все промежуточные скорости до достижения значения 1200 бод. Держите клавишу break нажатой до тех пор, пока getty не установится на нужную вам скорость. Следующий пример взят из файла gettydefs в OC XENIX System V и показывает, как связаны между собой скорости обмена.

    |  5# B9600 PARENB CS7 OPOST ONLCR # B9600 SANE IXANY #Login: #1
    |  4# B4800 PARENB CS7 OPOST ONLCR # B4800 SANE IXANY #Login: #5
    |  3# B2400 PARENB CS7 OPOST ONLCR # B2400 SANE IXANY #Login: #4
    |  2# B1200 CS8 OPOST ONLCR # B1200 SANE IXANY #Login: #3
    |  1# B300 CS7 OPOST ONLCR # B300 SANE IXANY #Login: #2
    |

Здесь указание #5 в файле inittab (или в файле ttys для пользователей XENIX и System III) порождает процесс getty, работающий на скорости 9600 бод. Если вы нажимаете клавишу break, следующим по порядку является номер 1 (как указано в конце строки номер 5). Если вы нажимаете break еще раз, то следующей является строка номер 2, и т.д. Если вы нажмете break достаточное количество раз, скорость getty вернется по циклу к значению 9600 бод.

Вся последовательность действий, связанных с регистрацией в системе, выглядит примерно так. Сначала init порождает процесс getty (инициируемый из файла /etc/inittab) на определенном номере терминала и с определенной скоростью. При этом устанавливаются характеристики линии и выдается регистрационная подсказка. Когда пользователь вводит регистрационное имя, getty проверяет его на правильность, а затем выполняет программу login. Login запрашивает пароль, зашифровывает его и сверяет с зашифрованным паролем в файле /etc/passwd. Если пароли совпадают, login запускает процесс shell, который печатает shell-подсказку и читает ваши команды, поступающие с терминала. Какой именно shell будет запущен - определяется записью в файле паролей, соответствующей данному регистрационному имени.Моему регистрационному имени соответствует такая запись:

        russ:j9egLecqEpXLg:201:51:Russ Sage:/usr/russ:/bin/shV

В этом случае запускается System V shell, один из новых shell-интерпретаторов фирмы AT&T.

ДИСТАНЦИОННОЕ ПОДКЛЮЧЕНИЕ

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

Рисунок 8-5.
Дистанционное подключение терминалов и микро-ЭВМ к UNIX

Терминал (когда он установлен на 1200 бод) общается непосредственно с модемом. При этом задействованы модемные команды "набрать телефонный номер" (dial), "повесить трубку" (hang up) и т.д. Микро-ЭВМ, запускающая коммуникационное программное обеспечение, обычно имеет команду набора номера, которая генерирует команду для модема. Соединение между терминалом/микро-ЭВМ и модемом должно быть выполнено в виде прямолинейного кабеля. Модем имеет также телефонный кабель, идущий в телефонную систему.

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

Как только все действия по конфигурированию завершены, терминал или микро-ЭВМ может связаться с системой UNIX. Как это сделать? При регистрации пользователя в системе структура данных termcap используется для поддержки управления экраном. Если у вас еще нет записи в файле termcap, обратитесь к предыдущей главе, где описано, как подготовить такую запись. Терминалы (поскольку обычно они являются просто аппаратурой) не слишком гибки и дружественны по отношению к пользователю. Они не обладают широким спектром возможностей помимо регистрации в системе и запуска некоторых программ. Микро-ЭВМ, с другой стороны, имеют большую гибкость и могут многое добавить к пользовательскому интерфейсу с системой UNIX.

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

Следует иметь в виду, однако, что программные файлы или другие файлы, требующие стопроцентной точности при передаче, нужно передавать с явным использованием протокола проверки ошибок, поскольку телефонные линии или даже прямое подключение могут вносить "шумы" и один неверный символ может сделать программу бесполезной. Некоторые протоколы, поддерживаемые в мире микро-ЭВМ, приспособлены к машинам с системой UNIX, например программы xmodem и kermit.

Из этих двух протоколов xmodem более широко распространен в мире микро-ЭВМ и поддерживается почти всеми системами типа досок объявлений. Kermit очень популярен в мире UNIX, имеет растущую популярность в мире микро-ЭВМ и доступен почти для каждой модели микро-ЭВМ за цену, немногим большую стоимости диска.

Используя эти протоколы, микро-ЭВМ может посылать и принимать файлы из системы UNIX, и вам не нужно заботиться о контроле ошибок при передаче данных. Чтобы подробнее узнать об этих протоколах, проконсультируйтесь у администраторов вашей системы UNIX и у пользователей микро-ЭВМ, имеющих те же проблемы.

ДОСТУП ИЗ СИСТЕМЫ UNIX К МИКРО-ЭВМ

Обратная ситуация, в отличие от только что рассмотренной, состоит в обращении из системы UNIX к системе, отличной от UNIX, функционирующей на микро-ЭВМ или на большой машине. Для того чтобы это сделать, необходимо знать, как получить доступ к последовательному порту, какую команду модема использовать для набора телефонного номера, как зарегистрироваться в той системе, в которую мы обращаемся. Модель этой ситуации показана на рис. 8-6.

Терминальное устройство DTE-1, т.е. система UNIX, использует прямой кабель для подключения модема. Для последовательной линии, которой в нашем примере является tty00, не нужно запускать процесс getty. Программа getty нужна только для регистрации в системе, а не для обращения к другой системе. Последовательным портом UNIX-машины управляет программа cu. Система, отличная от UNIX, имеет регистрационную последовательность некоторого вида. Если вы обращаетесь к микро-ЭВМ, на которой функционирует программное обеспечение типа доски объявлений, то система, отличная от UNIX, обычно запрашивает ваше имя, город и т.д., а также пароль. (Обычно вы можете зарегистрироваться как новый пользователь и завести пароль, но это зависит от конкретной доски объявлений.) Если другой системой является большая машина, то ее регистрационную последовательность нужно знать заранее.

Как уже отмечалось, основной программой, которую предоставляет UNIX для обращения к другой системе, является cu(1), что означает "call unix" ("вызвать UNIX"). На практике, как и в нашем примере, другая система не обязана быть системой UNIX, поэтому эту программу было бы более правильно характеризовать как "подключение к UNIX-порту".

Рисунок 8-6.
Обращение UNIX к системе, отличной от UNIX

Программа cu открывает последовательный порт и помещает "файл занятости" ("lock file") в каталог /usr/spool/uucp. Присутствие этого файла занятости просто означает, что порт задействован, и препятствует доступу к нему других пользователей. Вы имеете право исключительного доступа до тех пор, пока не выйдете из программы cu и не отдадите этот порт кому-нибудь другому.

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

ОБНАРУЖЕНИЕ МОДЕМОВ В СИСТЕМЕ

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

Начинать нужно с файла /usr/lib/uucp/L-devices. Это основной файл, определяющий, какие порты используются и каким образом они используются. Вот пример распечатки с информацией о портах:

    |
    |   ACU    cul0    cua0    1200
    |   DIR    tty00    0      300
    |   DIR    tty00    0      1200
    |   DIR    tty00    0      2400
    |   DIR    tty00    0      4800
    |   DIR    tty00    0      9600
    |

В файле L-devices каждый порт определяется либо как прямое подключение (DIR - direct connection), либо как специальное устройство, называемое устройством автоматического вызова (ACU automatic call unit). Об устройствах вызова мы поговорим позже. Сейчас нам нужно рассмотреть только записи типа DIR. Программа cu использует записи типа прямого подключения, а uucp - типа ACU. Теперь очень просто идентифицировать каждый последовательный порт: как к нему обращаться, на какой скорости он работает и можем ли мы осуществить вызов через этот порт.

Данные файла L-devices показывают, например,что последовательный порт tty00 используется как линия прямого вызова. Вам разрешается использовать для вызова через этот порт скорости от 300 до 9600 бод. Это полный диапазон используемых обычно скоростей обмена, причем более высокие скорости применяются для прямого подключения к другим машинам, а не к модему.

А как насчет модемных линий, которые ВХОДЯТ в машину? Для того чтобы их найти, нужно посмотреть каталог /etc. Там два файла dialin и dialup определяют, на каких линиях tty применяется вторичная регистрационная парольная последовательность для удаленных пользователей. Эти линии tty предназначены только для обращения извне в данную систему и обычно не могут быть использованы для доступа из этой системы во внешний мир.

ИСПОЛЬЗОВАНИЕ И КОНФИГУРИРОВАНИЕ ЛИНИИ

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

К основным возможностям, которые мы имеем, относятся: выбор номера линии (т.е. устройства tty), скорости функционирования и подключаться ли к устройству напрямую для ручного набора номера либо же набирать номер автоматически. Самый простой способ - чтобы номер набирала программа cu, но если это невозможно, то вы должны подключиться непосредственно к модему. Следующий пример показывает, как непосредственно подключиться к устройству и вручную управлять модемом с целью набора номера. В данном случае используется довольно широко распространенный модем Hayes 1200 Smartmodem. Если у вас модем, который не является "Hayes-совместимым", то вы должны найти эквивалентные управляющие последовательности в вашей документации.

       $ cu -ltty00 dir        # Получить прямой доступ к терминальной
                        # линии со скоростью 1200 бод
       Connected               # Отзыв программы cu
       ATdt555-1212            # Внимание модему, установка связи путем
                        # набора следующего номера
       CONNECT                 # Модем подтверждает факт подключения
                    # Отправка возврата каретки в систему-адресат
       Welcome to Micro BBS    # Получение приветствия от микро-ЭВМ
       .
       .
       выполнение сеанса работы с удаленной системой
       .
       .
       exit                    # Выйти из микро-системы ИЛИ,
       +++                     # если вы этого не хотите, временно
       # выйти из модемного подключения
       # (по команде escape, а не exit)
       OK                      # Ответ модема на временный выход
       ATh0                    # Попросить модем занять телефон
       OK                      # Ответ модема
       ~.                      # Завершить работу с cu

Для того чтобы указать программе cu автоматический набор номера, используйте другой синтаксис в командной строке. Мы применяем устройство автоматического вызова ACU для того, чтобы сделать вызов. Фактическая команда набора номера генерируется программой /usr/lib/uucp/dial. Пример вызова выглядит так:

       $ cu -acua0 555-1212

ИЗМЕНЕНИЕ СКОРОСТЕЙ ОБМЕНА

Модемы типа Smartmodem могут работать со скоростями 300 и 1200 бод. По умолчанию, программа cu подключается к последовательному порту на скорости 1200 бод. Если вы подключаетесь с использованием умолчаний, проверьте скорость, временно выйдя из программы cu (по команде escape, а не exit), и применив команду stty, которая отображает скорость обмена:

       ~!stty < /dev/tty00

Эта команда должна сообщить обычные установки stty для последовательного порта /dev/tty00. Скорость обмена должна равняться 1200. Модемы типа Smartmodem автоматически понижают скорость до 300 бод при ответе по телефону. При вызове вы можете понизить скорость модема до 300 бод двумя различными способами.

Первый способ - заставить cu открыть линию со скоростью 300 бод, указав 300 в командной строке программы cu. Заметим, что когда вы применяете альтернативную скорость (отличную от 1200 бод), запись о скорости обмена ДОЛЖНА присутствовать в файле L-devices И в командной строке cu, как в следующем примере:

$ cu -ltty00 -s300 dir

Второй метод хитрее, но зато предоставляет больше гибкости. Мы можем вызвать cu со значением по умолчанию 1200, затем временно (по команде escape) выйти из cu и вызвать программу stty, чтобы указать скорость обмена последовательного порта равной 300 бодам. Это нужно сделать после того, как вы обратились к линии с помощью cu. Эта измененная скорость обмена действительна до тех пор, пока вы не закроете линию (т.е. окончательно выйдете из cu по команде exit). В этот момент порт инициализируется заново. Например:

       $ cu -ltty00 dir           # Захватить линию на 1200 бод
       Connected                  # Отзыв программы cu
       ~!stty 300 < /dev/tty00    # Временно выйти из cu и
                           # установить для линии tty
                           # скорость 300 бод

Отметим, что этот метод НЕ требует наличия записи о скорости 300 бод в файле L-devices, поскольку мы работаем с последовательным портом вне области действия программы cu.

Попутно заметим, что временный выход (по команде escape) из cu совершенно аналогичен временному выходу из любой команды системы UNIX. Вы можете временно выйти для запуска определенной команды, например:

       ~!echo "you can run any command here"

Вы можете также перейти в shell и работать так, как вы всегда работаете:

       ~!sh

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

ЗАХВАТ ДАННЫХ

Теперь мы умеем найти модем, подключиться к последовательной линии и установить связь с другой системой. Если мы вызываем доску объявлений, нам может понадобиться захватывать файлы с целью последующего использования. Программа cu не предоставляет такой возможности. Как мы уже отмечали, микро-ЭВМ делают это обычно при помощи RAM-буфера, который сохраняется на диске, когда он заполняется. А UNIX не может действовать таким образом.

Простейший способ, придуманный мною для захвата данных из другой системы - пропустить по конвейеру все, что выдается на терминал, через команду tee системы UNIX. Весь ввод с вашей клавиатуры не перехватывается, а все, что идет на стандартное устройство вывода (stdout) попадает в выходной файл утилиты tee. Это выглядит так:

       cu -ltty00 dir | tee файл_перехвата

Одно из последствий перехвата информации таким методом состоит в том, что все символы возврата каретки, печатаемые из удаленной системы, попадают в выводной файл команды tee. Вы обнаруживаете их в виде "^M" в конце каждой строки файла. Избавиться от всех этих возвратов каретки легко, попробуйте выполнить такую последовательность команд редактора ed:

       ed файл_перехвата
       1,$s/^M//g
       w
       q

Смысл этих команд следующий. Для каждой строки во всем файле (1, $) заменить возврат каретки (^M) на ничего (//). Сделать это для произвольного количества символов возврата каретки в отдельной строке ("g" означает глобальную замену в строке). Вы можете превратить control-M в заменяемую строку либо вводом символа обратной косой черты (\) и настоящим нажатием на клавишу возврата каретки, либо в редакторе vi использовать префикс control-V для разрешения ввода управляющих символов. После редактирования запишите файл и выйдите из редактора ed.

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

ИНСТРУМЕНТАЛЬНЫЕ СРЕДСТВА ДЛЯ КОММУНИКАЦИОННЫХ ЛИНИЙ

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

     ИМЯ: cuchk

cuchk Cu check - проверка свободной линии для cu

НАЗНАЧЕНИЕ

Опрашивает таблицу состояния процессов и ищет процессы cu. Когда они завершаются, на ваш экран выдается сообщение и программа прекращает работу.

ФОРМАТ ВЫЗОВА

   cuchk           -   Сообщить, когда cu освободит линию

ПРИМЕР ВЫЗОВА

      cuchk

ТЕКСТ ПРОГРАММЫ

       1   :
       2   # @(#) cuchk v1.0  Check for a free cu line  Author: Russ Sage
       Проверка свободной линии для cu

       4   if [ "$#" -gt "0" ]
       5     then  echo "cuchk: too many arguments" >&2
       6           echo "usage: cuchk"              >&2
       7           exit 1
       8   fi

       10  while :
       11  do
       12    ps -e | fgrep cu > /dev/null  \
       13                && sleep 5
       14                || { echo "\ncu is free"; exit; }
       15  done &

ОПИСАНИЕ

Зачем нам нужен командный файл cuchk?

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

Что делает cuchk?

При вызове программы cuchk она запускается в фоновом режиме. Она управляется бесконечным циклом, обеспечивающим ее работоспособность. Программа cuchk наблюдает за системой, ведя поиск среди всех активных процессов. Если она обнаруживает процесс cu (который может работать с интересующим нас портом, а может и нет), она засыпает на 5 секунд и снова ведет поиск. Если нет процессов cu, функционирующих на машине, она выдает сообщение "cu is free" ("cu свободна") и завершается. Таким образом, мы не обязаны вручную следить за работой cuchk, она сама запускается и сама завершается. Это имеет смысл, поскольку вся ценность этой утилиты свелась бы на нет, если бы ее саму нужно было периодически проверять!

ПОЯСНЕНИЯ

В строках 4-8 выполняется проверка на ошибки. Поскольку cuchk не имеет никаких опций, в командной строке не должно быть никаких параметров. Если параметры присутствуют, это считается ошибкой. Строки 10-15 организуют вечный цикл while. Обратите внимание, что амперсанд в строке 15 автоматически запускает cuchk в фоновом режиме, ведь нет никакого резона выполнять эту программу в приоритетном режиме.

В строке 12 выполняется команда "ps -e", которая выдает информацию о состоянии всех значительных пользовательских процессов. Эти данные пропускаются по конвейеру через fgrep. Утилита fgrep ищет наличие символов "cu". Мы надеемся, что эти символы соответствуют только процессам cu, которые мы ищем. Они могут, однако, относиться к чему-то совершенно неожиданному, например "picuser" или к какому-то аналогично построенному имени. Результат работы команды fgrep направляется на устройство /dev/null, чтобы избавиться от лишней информации. Нас интересует только статус завершения команды fgrep. Он сообщает нам, найдено ли вхождение символов "cu" или нет.

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

Строка 13 выполняется в том случае, если fgrep отработала успешно (т.е. если она выдает нулевой статус завершения). Это означает, что символы "cu" найдены, а значит процесс cu работает и в настоящее время не доступен. Поэтому процесс cuchk засыпает на 5 секунд. Когда он пробуждается, выполнение продолжается со строки 10, возобновляется вечный цикл и проверка процессов продолжается. Если fgrep не находит символы "cu" (возвращается ненулевой статус завершения), то выполняется строка 14, которая выдает сообщение о том, что программа cu свободна, и работа программы завершается. Таким образом, мы сразу же знаем, что линия cu открыта для использования, поэтому мы можем поскорее занять ее.


ГЛАВА 8. Коммуникации в системе UNIX

     ИМЯ:  talk

talk Обращение к последовательному порту

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

      talk [-bBAUD] [-l] [-tTTY] [-u]

Опции:

-b установить новую скорость обмена

-l протоколировать все поступающие данные

-t использовать другой порт tty

-u использовать принятую в UNIX скорость обмена 9600

ПРИМЕР ВЫЗОВА

      talk -b300 -t01 -l

Обратиться к последовательному порту tty01 на скорости 300 бод и протоколировать выводные данные в текстовый файл.

ТЕКСТ ПРОГРАММЫ

       1   :
       2   # @(#) talk v1.0  Talk to the serial port  Author: Russ Sage
        Обращение к последовательному порту

       4   BAUD="1200"
       5   TTY="tty11"
       6   PIPE=""

       8   for ARG in $@
       9   do
       10          case $ARG in
       11          -b*) BAUD="`echo $ARG|cut -c3-`";;
       12          -l)  echo "logging in /tmp/talk.$$"
       13               PIPE="| tee /tmp/talk.$$";;
       14          -t*) TTY="tty`echo $ARG|cut -c3-`";;
       15          -u)  BAUD="9600";;
       16          *)   echo "talk: invalid argument $ARG"            >&2
       17               echo "usage: talk [-bBAUD] [-l] [-tTTY] [-u]" >&2
       18               echo "         -b   baud rate"                >&2
       19               echo "         -l   log the output"           >&2
       20               echo "         -t   use another tty"          >&2
       21               echo "         -u   9600 baud to UNIX"        >&2
       22               exit 1;;
       23          esac
       24  done

       26  eval cu -s$BAUD -l$TTY dir $PIPE

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ARG | Аргумент из командной строки
BAUD | Скорость обмена, которую нужно указать программе cu
PIPE | Содержит строку, создающую конвейер для захвата данных
TTY | Номер используемого порта tty

ОПИСАНИЕ

Зачем нам нужен командный файл talk?

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

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

Что делает talk?

Программа talk генерирует командную строку, основанную на подразумеваемой конфигурации. Эта конфигурация такова: 1200 бод, подключение к терминальной линии /dev/tty11 (последовательный порт 1 в системе SCO XENIX), захват данных не производить. Все подразумеваемые параметры можно изменить в тексте исходного файла, а терминальная линия должна быть установлена с учетом специфики вашей системы.

Если вы хотите изменить скорость обмена, укажите с помощью опции -b нужную вам скорость (подразумевается, что это одна из стандартных скоростей, поддерживаемых программой cu). Например,"talk -b2400" устанавливает скорость 2400 бод. Указанная скорость передается команде cu, которая непосредственно изменяет характеристики вашего подключения.

Важно иметь в виду, что эта скорость должна быть определена в файле L-devices.

Если вы хотите захватить все данные, выводимые на ваш экран, включите опцию протоколирования, указав -l. Файл с захваченными данными будет называться /tmp/talk.$$, где $$ - уникальный идентификатора процесса вашего текущего интерпретатора shell. Это имя выдается на экран при вызове данной опции, так что вы увидите его на вашем экране. Уникальность имен файлов обеспечивается для того, чтобы вы не потеряли по недосмотру регистрационные файлы от предыдущих сеансов работы (или чтобы, по крайней мере, было очень мало шансов, что это произойдет).

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

Если вы не используете ваш последовательный порт для подключения модема, а имеете прямое подсоединение к другой системе UNIX, опция -u тотчас же установит скорость обмена 9600 бод. Заметим, что того же эффекта можно было бы достичь, указав -b9600, но опцию -u проще вводить и запоминать.

ПРИМЕРЫ

      1.  $ talk -l -t12

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

      2.  $ talk -u

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

      3.  $ talk -b2400 -t04 -u

Внимание! Сначала опция -b устанавливает скорость 2400, но далее следует опция -u, которая переустанавливает значение скорости на 9600.

      4.  $ talk -u -l -b4800

Снова будьте внимательны! Сначала линия подключается напрямую со скоростью 9600 бод. Затем включается протоколирование. Наконец, значение скорости меняется с 9600 на 4800. Если на самом деле линия, к которой вы обращаетесь, работает на скорости 9600 бод, вы должны, если это возможно, понизить скорость, нажимая ~%b для генерации сигнала break.

ПОЯСНЕНИЯ

В строках 4-6 устанавливаются действия, выполняемые программой talk по умолчанию. Эти действия можно полностью изменить путем редактирования исходного файла. Главное достоинство этих умолчаний в том, что если такую конфигурацию вы используете чаще всего, то вам достаточно просто ввести "talk", без всяких опций.

Строка 4 инициализирует переменную BAUD значением 1200, это значение по умолчание для скорости модема. Строка 5 присваивает переменной TTY значение tty11, что соответствует первому последовательному порту аппаратуры. Обычно нужно tty00, но SCO XENIX имеет виртуальную консоль, причем tty02-tty10 являются отдельными экранами, доступ к которым осуществляется с одного консольного устройства. Если это не так в вашей системе, укажите здесь правильное значение TTY. В строке 6 переменная PIPE инициализируется пустой строкой, так как конвейер применяется для протоколирования данных, а по умолчанию захват данных не выполняется.

Строки 8-24 представляют собой цикл, который перебирает все аргументы командной строки. В строках 10-23 используется оператор выбора среди опций для выполнения требуемых функций. Строка 11 проверяет, начинается ли данная опция с символов -b, после которых следует что-то еще. Если да, то символы, следующие после -b, вырезаются и заносятся в переменную BAUD. Обратите внимание, что именно поэтому вы должны набирать "-b2400", а не "-b 2400".

В строке 12 проверяется, хотите ли вы включить протоколирование. Если да, то на экран выводится имя протокольного файла, а переменная PIPE устанавливается так, чтобы направить стандартный вывод по конвейеру программе tee системы UNIX. Программа tee - это универсальная конвейерная программа расщепления, которую можно использовать для посылки копии потока данных в определенный пункт назначения, не воздействуя на основной конвейер. В данном случае мы применяем программу tee для посылки данных, проходящих через последовательный порт, на экран и в наш регистрационный файл /tmp/talk/.$$. (Если вы представите себе водяную трубу с T-образным стыком, то поймете, что происходит.)

Строка 14 проверяет, начинается ли опция с -t. Если это так, то вырезается номер терминала. Здесь можно передать любое значение tty, но не забывайте, что этот номер tty должен также присутствовать в файле /usr/lib/uucp/L-devices. Благодаря этому файлу, программа cu знает, что терминальное устройство подключено к разрешенному порту. В строке 15 проверяется, собираетесь ли вы подключаться к системе UNIX напрямую. Если да, скорость обмена автоматически устанавливается на 9600 бод в целях повышения пропускной способности терминала.

Строки 16-22 выполняют проверку ошибок. Если какая-либо опция, переданная в командной строке, не соответствует никакому из предыдущих образцов оператора case, этот факт фиксируется здесь, выдаются сообщения об ошибках и командный файл завершается. Строка 26 - это волшебная строка. Поскольку все shell-переменные получили свои значения, мы можем использовать их в командной строке, чтобы подставить в нее необходимые значения. Сначала применяется команда eval. Она раскрывает все имена переменных, заменяя их на соответствующие значения.

В данном случае мы формируем полную командную строку утилиты cu: скорость берется из переменной BAUD, линия из TTY, а подсоединение прямое. Если мы не ведем протоколирование данных, значение PIPE нулевое и оно будет отброшено при синтаксическом разборе. Если же переменная PIPE содержит команду для отправки данных в протокольный файл, команда eval обеспечивает это, организовывая такой конвейер, как мы указали.

ДОСТУП ИЗ UNIX В UNIX

Теперь давайте рассмотрим полновесное общение двух систем UNIX. Имеется область, в которой UNIX опередила свое время. Система uucp позволяет связать несколько машин воедино и создать то, что в некоторых отношениях является виртуальным окружением, позволяющим вам работать на любой машине. Функционирование такой сети основано на удаленной регистрации в системе (cu(1)), дистанционном выполнении команд (uux(1)), электронной почте (mail(1)), передаче файлов (uucp(1), uucico(1)) и опознавании узла системы (uname(1) и uuname(1)).

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

СВЯЗЫВАНИЕ UNIX-МАШИН

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

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

Первый тип подключения - прямое подключение (см. рис. 8-7). Слева показана вызывающая система, справа вызываемая. Вызывающая система использует последовательный порт для вывода, поэтому на этом порту не должно быть процесса getty. Инициирующая команда "cu -ltty00 -s9600 dir" обеспечивает подключение к последовательному порту tty00 на очень высокой скорости обмена. Прямые подсоединения могут поддерживать такую скорость. Сам кабель должен быть выполнен в виде нулевого модема (рассмотренного ранее в данной главе). В вызываемой системе задействованы процессы getty, работающие со скоростью 9600 бод на входящей терминальной линии. Когда пользователь вводит регистрационное имя, getty выполняет процесс login, запрашивающий пароль, и если этот пароль верный, запускается shell.

Рисунок 8-7.
Прямое подключение одной системы UNIX к другой

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

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

Рисунок 8-8.
Прямое подключение через селектор порта

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

Последняя конфигурация (на рис. 8-9) представляет собой дистанционное подключение двух систем UNIX. Каждое терминальное устройство DTE соединяется со своим модемом прямым кабелем. DTE-1 вызывает DTE-2 либо вручную с помощью cu, либо с помощью утилиты uucp, использующей uucico и программу набора телефонного номера. Самое большое отличие в том, что это подсоединение работает на скорости 1200 бод. Это значит, что либо DTE-2 запускает 1200-бодовый процесс getty, либо если getty имеет скорость 9600, то вам нужно сбросить ее. Для того чтобы понизить скорость, в программе cu требуется ввести ~%b в качестве сигнала break. Или, если вы работаете с помощью uucp, можно поместить строку BREAK в файле L.sys, чтобы отправить ее в вызванную систему.

Рисунок 8-9.
Дистанционное соединение двух систем UNIX

ОБЛАСТИ ДЛЯ ПЕРЕДАВАЕМЫХ ФАЙЛОВ

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

Наиболее важен каталог /usr/spool/uucp. Он содержит LOGFILE, что дает возможность с помощью команды "tail -f LOGFILE" заглянуть в операции передачи во время их выполнения. Транзакции uucp и mail попадают в этот каталог. Обычно транзакция состоит из управляющего файла (C.*) и файла данных (D.*). Когда одна машина используется в качестве центрального узла, ее каталог uucp может заполниться очень большим количеством файлов. Необходимо обычное сопровождение и постоянное слежение за файлами занятости (LCK* и STST*), чтобы быть уверенным, что все работает правильно.

Следующий интересный каталог /usr/spool/uucppublic, чаще всего известный под названием PUBDIR (это shell-переменная). Он содержит каталоги, названные по имени каждого пользователя, чтобы хранить файлы, проходящие транзитом с одной машины на другую. Большинство каталогов имеют все права доступа, что обеспечивает другим пользователям возможность копировать файлы. Я посчитал полезным создать переменную среды интерпретатора shell, которая содержит маршрутный префикс моего каталога в /usr/spool/uucppublic, т.е. P=/usr/spool/uucppublic/russ. Теперь я могу обращаться к файлам, вводя $P/*. Это значительно сокращает ввод и упрощает пересылку файлов в этот каталог и из него.

Переменная PUBDIR поддерживается синтаксисом uucp. В данном примере используется синтаксис uucp:

       $ uucp * remote!~/user

Эта команда копирует каждый файл текущего каталога в систему "remote", затем обозначение ~/ превращается в префикс /usr/spool/uucppublic. Если в этой команде user не является каталогом, копируемые файлы получают имя user, а не копируются в каталог с именем user. Вы должны сами создать каталог, после чего применить к нему команду chmod 777, чтобы в него можно было копировать. Для справки отметим, что указание ^user превращается программой uucp в $HOME/user, а ^/user превращается uucp в $PUBDIR/user.

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

       $ uucp * remote!$P

Такая команда копирует все файлы в мой каталог PUBDIR. Если я зарегистрировался в этой системе, я могу ввести:

       $ ls -al $P
    или
       $ cd $P
       $ ls -al

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

Один из моментов, за которым вы должны следить,- разрушительные командные файлы типа "uuclean". Эти программы обычно запускаются процессом cron или некоторыми другими фоновыми программами. Они проходят по всем областям системы, связанным с uucp, находят файлы, к которым не было обращений определенный период времени, и удаляют их. Это может быть катастрофическим, если вы используете PUBDIR в качестве временной области хранения. Вот как может выглядеть одна из таких "очищающих" операций:

       PATH=/usr/bin:/bin
       export PATH
       cd /usr/spool/uucp
       find C. D. TM. X. XTMP -type f -mtime +7 -exec rm {} \;
       cd /usr/spool/uucppublic
       find . -type f -mtime +7 -exec rm {} \;

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

       $ find $P -exec touch {} \;

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

Другая стратегия - проанализировать очищающую программу (или командный файл). Она запускается суперпользователем (root)? Если нет, то команда "chmod 000 $P" может помешать ей вести поиск имен внутри моего каталога. Если же программа очисткизапущена суперпользователем, то, конечно, никакие ограничения прав доступа ее не остановят. В какое время она запускается? Могу ли я приказать ей пройти мимо меня, не заглядывая в мои файлы? Что является стартовым каталогом для очищающего командного файла? Углубляясь в эти вопросы, мы можем собрать много информации о том, что делает этот командный файл и какие действия мы можем предпринять, чтобы он нам не навредил. Может показаться, что самый легкий способ - просто удалить программу очистки (в предположении, что вы имеете на это право). Это, однако, не способствует поддержке свободного пространства и чистых каталогов. К тому же если вы хотите запретить очистку каталогов, которые очень важны для вас, то вы должны нести ответственность за их сопровождение.

СЕТЕВАЯ БЕЗОПАСНОСТЬ

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

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

Широко открытый доступ на запись позволяет доставлять файлы прямо к вашему порогу, но вы не знаете, кому вы открываете двери. Если вы заботитесь о безопасности, вам не пон ПРИЕМЫ П. может записать все, что угодно, в ваше рабочее пространство. Одно из решений - разблокировать ваш регистрационный каталог, но заблокировать все ваши подкаталоги, кроме тех, которые нужны для uucp. Основная проблема при этом - так установить все права доступа, чтобы каждый файл обрабатывался надлежащим образом.

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

ОГРАНИЧИТЕЛЬНЫЕ ФАЙЛЫ

Когда удаленная система регистрируется в центральной системе с помощью uucp, несколько файлов в центральной системе определяют, какие возможности имеет удаленная система. Эти конфигурационные файлы размещаются в каталоге /usr/lib/uucp.

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

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

       uucp, /

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

Это открывает лазейки в системе защиты, например:

       uucp central!/etc/passwd /tmp

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

       uucp /usr/spool/uucppublic /tmp

что ограничивает файловые пересылки только указанными каталогами. Это препятствует предыдущей попытке пересылки парольного файла. Последний файл L.sys, вероятно, наиболее важен для uucp с точки зрения системы защиты. Он содержит имена узлов, телефонные номера, регистрационные имена и пароли для всех удаленных систем, известных центральной системе. Если бы какая-то часть этой информации была доступна широкой публике, кто-нибудь смог бы выполнить пересылку с помощью uucp из удаленной системы и претендовать на роль центральной системы. Новая система HoneyDanber uucp в System V кое-что делает для того, чтобы не давать удаленным системам перехватывать почту и пересылки данных путем маскировки под другие удаленные системы. В главе 9 более подробно рассматриваются вопросы безопасности программы uucp и коммуникаций вообще.

В следующем примере показан файл L.sys, в котором определены два разных вида систем: системы типа прямого подключения и удаленные системы с номеронабирателем (dial-up systems).

       remote Any ACU 1200 5551212 ogin:--ogin: uucp word: uucp
       selector Any ACU 1200 5551213 \d--CLASS--CLASS A ogin:--ogin: uucp word:
       uucp
       direct Any tty00 9600 tty00 ogin:-@-ogin: uucp word: uucp

Запись для системы "remote" отражает, что это линия с набором номера, может быть вызвана произвольное число раз ("any time"), доступна через ACU (automatic call unit - автоматическое устройство вызова) со скоростью 1200 бод и по номеру 5551212. Регистрационная последовательность определена в виде регистрационного имени uucp и пароля uucp. Слово "ogin" - не опечатка. Uucp использует подсказку "ogin:" для того, чтобы отличить ее от обычной подсказки login системы UNIX. Распознавание шаблона "ogin:" более надежно, чем использование "Login:" или "login:".

Указание "Any" можно заменить на определенные интервалы времени, если 24-часовый доступ не разрешен. Заметим, что обозначение "ACU" соответствует записи ACU в файле L-devices (как рассмотрено ранее). В следующей записи файла L-devices имена cua0 и cul0 связаны с устройством, присоединенным к модему, в данном случае /dev/tty00:

       ACU cul0 cua0 1200

Эту связь можно проверить с помощью команды "ls -li /dev/tty* /dev/cul* /dev/cua*".

Система "selector" в нашем файле L.sys также имеет набор номера, только подключается через коммутатор порта. Для общения с коммутатором порта необходима дополнительная информация, которая начинается с символов "\d". Поля в файле L.sys следуют в таком порядке: "ожидание посылка ожидание посылка ...". Когда мы в первый раз подключаемся к селектору порта, в нашу линию ничего не выводится. Просто он так работает. Селектору нужен символ , чтобы стать активным, а первое поле uucp означает ожидание. Как нам ничего не дождаться и послать возврат каретки? Путем указания uucp ожидать невозможный символ, например control-D (\d). Uucp никогда не получит его, поэтому по тайм-ауту эта программа выдаст возврат каретки (-). Если слово CLASS приходит назад, когда мы посылаем CR, то мы отправляем символ "A", который означает класс системной идентификации, указанный в данной записи. Когда связь устанавливается, мы ищем "ogin:" в качестве регистрационной подсказки. Если мы не находим ее, то посылаем символы возврата каретки. Это может потребоваться по той причине, что инициирующий процесс getty в системе A может быть настроен на 9600 бод. Тогда мы должны будем послать либо символы возврата каретки, либо символы break, чтобы сбросить скорость обмена до 1200 бод.

Последняя системная запись "direct" не использует ACU и телефонный номер. Она собирается обращаться к терминальной линии tty00 на скорости 9600 бод, чтобы получить регистрационную последовательность. Здесь нет селектора порта, через который нужно пройти, а есть просто прямая линия. В файле L-devices эта линия описана так:

       DIR tty00 0 9600

ОТЛАДКА КОММУНИКАЦИЙ

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

Если после проверки и перепроверки всех конфигурационных файлов имеют место отказы при передаче файлов, запустите вручную программу /usr/lib/uucico с некоторыми активными отладочными флагами. Они помогут увидеть, как происходит рукопожатие. Вот командная строка для отладочного режима:

       /usr/lib/uucp/uucico -r1 -x9 -ssystem_name

где r1 указывает программе uucico стартовать в ведущем (master) режиме, вызывая system_name, а x9 указывает уровень отладочных сообщений. Если вы хотите получать от uucico поменьше подробностей, можете понизить уровень отладки до x4 или другого значения. Отладочный вывод варьируется в диапазоне от x1 до x9. Типичная последовательность для этой команды выглядит так. Во -первых, поставить некоторые файлы в очередь для того, чтобы направить их в другую систему. Благодаря постановке файлов в очередь, вы избегаете автоматического запуска процесса uucico. После того как файлы поставлены в очередь и готовы к передаче, запустите отладочный режим и следите за происходящим. Это выглядит примерно так:

    |
    |   $ uucp -r *.c remote!~/src
    |   $ /usr/lib/uucp/uucico -r1 -x4 -sremote
    |

Если вам нужно только активизировать передачу с помощью uucp в обычном режиме, то проще всего применить команду mail. Направьте почтой некоторый текст пользователю другой системы - и механизм uucp сразу же начнет работу. Следующая команда приводит к тому, что этот механизм вызывает систему "remote" и запускает утилиту rmai(1) в другой системе, чтобы передать по почте файл "dummy" пользователю "user":

       $ mail remote!user < dummy

Есть команда, которая явно вызывает /usr/lib/uucp/uucico - это команда uusub(1M), размещенная в каталоге /usr/lib/uucp. Она вызывается с указанием имени системы, с которой вы хотите связаться. Большинство систем имеют такую команду. Если у вас ее нет, пользуйтесь uucico. Если такая команда есть, вы имеете еще один способ запуска uucico. Синтаксис выглядит так:

       $ /usr/lib/uucp/uusub -c system

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

     ИМЯ: uust

uust Состояние uucp и служебные действия

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

      uust

Опции меню:

c - подключиться к другой системе в отладочном режиме

d - показать файлы в вашем каталоге PUBDIR

f - длинноформатный список файлов в каталоге подкачки (spool directory)

l - динамически отображать регистрационный файл

r - повторное подключение к другой системе

s - дать пользователю сводку о транзакциях

u - разблокировать все терминальные линии (ОСТОРОЖНО: это может нарушить сеанс работы)

w - отобразить регистрационный файл за последнюю неделю

ПРИМЕР ВЫЗОВА

uust Динамически отображать регистрационный файл, чтобы следить за транзакциями uucp

ТЕКСТ ПРОГРАММЫ

       1   :
       2   # @(#) uust v1.0   Uucp status utility   Author: Russ Sage

       4   if [ $# -gt 0 ]
       5     then  echo "uust: argument error" >&2
       6           echo "usage: uust"          >&2
       7           exit 1
       8   fi

       10  UUNODE=`uuname -l`
       11  echo "
       12                   UUST MENU                system node: $UUNODE
       13                   ---------
       14      c - connect with another system in debug mode
       15      d - show files in your directory under PUBDIR
       16      f - long list files in the spool directory
       17      l - display the logfile dynamically
       18      r - reconnect with another system
       19      s - give user summary of transactions
       20      u - unlock the tty line
       21      w - display logfile for the last week
       22      - exit program

       24  Press c,d,f,l,r,s,u,w,or : \c"
       25  read CMD

       27  case $CMD in
       28  "")     exit 0;;
       29  c)      echo "\nSystem name ( to exit): \c"
       30          read SYSTEM
       31          if [ "$SYSTEM" = "" ]
       32            then exit 0
       33          fi
       34          echo "\nrm /usr/spool/uucp/STST.$SYSTEM : \c"
       35          rm /usr/spool/uucp/STST.$SYSTEM 2>/dev/null \
       36                    && echo "" || echo "no STST files"
       37          echo "\n/usr/lib/uucp/uucico -r1 -x4 -s$SYSTEM:"
       38          /usr/lib/uucp/uucico -r1 -x4 -s$SYSTEM;;
       39  d)      echo "\n/usr/spool/uucppublic/$LOGNAME:"
       40          ls -l /usr/spool/uucppublic/$LOGNAME;;
       41  f)      echo "\n/usr/spool/uucp:"
       42          ls -l /usr/spool/uucp | more;;
       43  l)      echo "\n/usr/spool/uucp/LOGFILE:"
       44          tail -20f /usr/spool/uucp/LOGFILE;;
       45  r)      echo "\nSystem name ( to exit): \c"
       46          read SYSTEM
       47            if [ "$SYSTEM" = "" ]
       48            then exit 0
       49          fi
       50          echo "\nrm /usr/spool/uucp/STST.$SYSTEM : \c"
       51          rm /usr/spool/uucp/STST.$SYSTEM 2>/dev/null \
       52                    && echo "" || echo "no STST files"
       53          echo "uusub -c$SYSTEM:"
       54          if [ -f /xenix ]
       55            then /usr/bin/uusub -c$SYSTEM
       56            else /usr/lib/uucp/uusub -c$SYSTEM
       57          fi
       58          tail -20f /usr/spool/uucp/LOGFILE;;
       59  s)      echo "\nuulog -u$LOGNAME:"
       60          uulog -un$LOGNAME | more;;
       61  u)      echo \\nrm /usr/spool/uucp/LCK* :
       62          rm /usr/spool/uucp/LCK* 2>/dev/null || echo "no lock files";;
       63  w)      echo "\n/usr/spool/uucp/Log-WEEK:"
       64          more /usr/spool/uucp/Log-WEEK;;
       65  *)      echo "uust: invalid argument '$CMD'" >&2;;
       66  esac

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

CMD | Содержит символ входной команды, поступивший из стандартного ввода
LOGNAME | Содержит ваше регистрационное имя
SYSTEM | Узловое имя uucp той системы, в которую вы обращаетесь
UUNODE | Узловое имя uucp локальной системы

ОПИСАНИЕ

Зачем нам нужен командный файл uust?

Многие области системы UNIX являются фактически самостоятельными подсистемами. Uucp одна из них. Она использует конфигурационные файлы, файлы подкачки (spool files), файлы блокировки (lock files) и имеет много исполняемых модулей, образующих целую систему. Если вы интенсивно используете uucp, вы часто занимаетесь просмотром регистрационных файлов, очисткой каталогов после аварийных завершений работы uucp и наблюдением за системой в целом. Если делать все это вручную, требуется запоминать каталоги, файлы, команды, опции. Трудно запомнить так много символов для ввода команд. Uust значительно сокращает издержки и облегчает использование утилиты uucp.

Что делает uust?

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

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

    |
    |                   UUST MENU                system node: russ
    |                     ---------
    |      c - connect with another system in debug mode
    |      d - show files in your directory under PUBDIR
    |      f - long list files in the spool directory
    |      l - display the logfile dynamically
    |      r - reconnect with another system
    |      s - give user summary of transactions
    |      u - unlock the tty line
    |      w - display logfile for the last week
    |      - exit program
    |
    |  Press c,d,f,l,r,s,u,w,or :

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

Следующая опция 'd' дает длинноформатный листинг файлов вашего каталога, определяемого вашей переменной $PUBDIR, т.е. /usr/spool/uucppublic/$LOGNAME. Если вы часто делаете пересылки с помощью uucp, здесь может собраться много файлов. Данная опция предоставляет вам простой способ их просмотра.

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

Опция 'l', видимо, наиболее часто используемая во всей утилите.

Она позволяет увидеть в динамике функционирование uucp, наблюдая за регистрационным файлом. При первом вызове она печатает последние 20 строк этого файла, а затем ведет наблюдение, пока не будет остановлена ее работа. Общее количество нажатий на клавиши для вызова этой команды с помощью uust равно 5: 4 для вызова uust и 1 для указания опции меню. Для того чтобы набрать ту же команду вручную, требуется 31 символ. Вот где экономия времени и избавление от лишних проблем!

Очень интересна опция 'r'. Укажите ей имя системы - и она установит связь с этой системой при помощи uucp. Используемый при этом метод отличается от метода, применяемого в опции 'c', но работает в System V. Отметим, однако, что в большинстве систем команда uusub, задействованная здесь, требует возможностей суперпользователя. Uusub возвращает управление обратно вашему интерпретатору shell, но затем запускает uucp в фоновом режиме. Когда это произойдет, uust продвигается вперед и показывает динамический хвост регистрационного файла, как описано выше. После этого вы можете наблюдать все действия uucp по вызову, передаче файлов и завершению работы.

Опция 's' показывает все ваши транзакции к утилите uucp, выбирая из регистрационного файла только то, что относится к вашему имени. Обычно имеется много записей, поэтому вывод передается по конвейеру команде more в целях удобства чтения.

Следующую опцию 'u' нужно использовать с большой осторожностью.

Она удаляет файлы блокировки, устанавливаемые системой uucp для самой себя. Если в это время uucp работает, другой пользователь может обратиться при помощи cu к линии и все разрушить. Эти файлы блокировки существуют также, когда кто-то применяет cu на последователной линии. Данная опция предусмотрена по той причине, что иногда uucp или cu завершается аварийно. Когда такое происходит, нужно удалить файлы блокировки, чтобы начать все сначала. Эту опцию следует использовать только для этой цели либо для запланированного эксперимента, чтобы посмотреть, что при этом может произойти. Последняя опция 'w' предназначена для отображения из регистрационного файла тех транзакций uucp, которые относятся к последней неделе. Это нетрудно сделать, но если бы вы искали определенную транзакцию, вы могли бы применить для этого средства поиска символьных строк команды more.

ПРИМЕР

      $ uust
      r

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

ПОЯСНЕНИЯ

Uust является однопроходной утилитой, т.е. не имеет внутренних циклов. Вы выбираете опцию, и после завершения ее выполнения завершается весь командный файл. Таким способом это сделано главным образом по той причине, что вы должны обрывать вывод команды tail, коорая используется некоторыми опциями меню. Однако, клавиша break прекращает все, даже выполнение uust. Не имеет смысла делать внутренний цикл, так как этот цикл редко смог бы повториться. В строках 4-8 выполняется проверка командной строки на наличие ошибок. Если переданы какие-то аргументы, выдается сообщение об ошибке и командный файл завершается.

Строка 10 инициализирует переменную UUNODE текущим именем uucp той системы, в которой вы работаете. Это обеспечивается вызовом утилиты uuname.

Строки 11-24 отображают главное меню одним большим оператором echo. Имя системы, поступившее в переменную UUNODE, выдается для справки в правой верхней части экрана.

В строке 25 читается ответ пользователя, а строки 27-66 проверяют команду и выполняют ее. Если вы ввели только возврат каретки, это соответствует строке 28, и программа завершается. Строки 29-38 обрабатывают команду 'c' - подключение в отладочном режиме. Запрашивается имя системы и проверяется, не пустое ли оно. Если оно пустое, командный файл завершается. В противном случае строка 34 сообщает, что uust пытается удалить все файлы STST, которые создавались при неудачных вызовах. Перед тем как обратиться к системе, вы должны удалить все файлы STST. Если таких файлов нет, команда rm не срабатывает и выдается сообщение "no STST files".

Строки 39-40 выполняют опцию 'd'. Сначала отображается для справки название каталога, который мы просматриваем. Затем распечатывается каталог PUBDIR в длинном формате. Заметим, что LOGNAME соответствует любому пользователю, запустившему данную программу, поэтому нет необходиvости вписывать в текст программы какое-то значение.

В строках 41-42 аналогичным образом обрабатывается опция 'f'. Мы распечатываем здесь каталог подкачки.

Команда 'l' в строках 43-44 сообщает, что отображается регистрационный файл. Затем используется команда "tail -f". Опция -f означает, что нужно производить отображение того, что находится в файле следующим образом: как только транзакции печатаются в файл, они отображаются на ваш экран. Пользователь должен нажать клавишу break для выхода из программы.

Строки 45-58 выполняют команду 'r', которая пытается произвести повторное подключение. Сначала запрашивается имя системы и сравнивается с пустой строкой. Если имя введено, мы пытаемся удалить файлы STST и выдаем сообщение о том, было ли это удаление успешным. Затем печатается сообщение о том, что запускается процесс uusub. В строке 54 проверяется, является ли корневым файлом XENIX. Если это так, то утилита uusub вызывается с учетом ее нового местонахождения в системе XENIX. В противном случае она вызывается с указанием ее обычного местонахождения - /usr/lib/uucp. После выполнения uusub отображается динамический хвост регистрационного файла, пока пользователь не нажмет клавишу break, после чего командный файл завершается.

В строках 59-60 выполняется проверка сводки о транзакциях. Используется команда uulog, ей передается LOGNAME текущего пользователя. Подробности о команде uulog можно найти в документации. Опция 'u' рассматривается в строках 61-62. Сначала отображается команда rm, которая будет выполнена, чтобы уведомить об этом пользователя. Затем эта команда выполняется, чтобы попытаться удалить файлы блокировки. Если команда удаления завершается неудачей, выдается сообщение об ошибке, указывающее на отсутствие файлов блокировки.

Строки 63-64 запускают опцию 'w', чтобы посмотреть деятельность uucp за последнюю неделю. С помощью команды more печатается файл LogWEEK. Этот файл создается утилитой uucp, чтобы обеспечить сжатую сводку о работе в течение недели.

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


ГЛАВА 8. Коммуникации в системе UNIX

     ИМЯ: uutrans

uutrans Передача файловых деревьев из одной системы UNIX в другую систему UNIX

НАЗНАЧЕНИЕ

Копирует полную иерархию файловой системы с помощью uucp в другую систему UNIX и поддерживает структуру файлового дерева.

ФОРМАТ ВЫЗОВА

      uutrans

ПРИМЕР ВЫЗОВА

       cd $HOME/backup
       uutrans
       remote
       ~russ

Начиная с подкаталога backup моего регистрационного каталога, запустить uutrans с целью передачи всех моих файлов. Файлы посылаются в систему remote и размещаются там в моем регистрационном каталоге (/usr/russ).

ТЕКСТ ПРОГРАММЫ

       1   :
       2   # @(#) uutrans v1.0  Transfer file trees via uucp
       3   #                    Author: Russ Sage
       4   if [ $# -gt 0 ]
       5     then  echo "uutrans: argument count error" >&2
       6           echo "usage: uutrans"                 >&2
       7           exit 1
       8   fi

       10  SELF=`uuname -l`

       12  echo "source directory( to exit): \c"
       13  read SOURCE
       14  if [ "$SOURCE" = "" -o ! -d "$SOURCE" ]
       15    then  exit 1
       16  fi

       18  echo "\ndestination system( for $SELF): \c"
       19  read SYSTEM
       20  echo "\ndestination directory( for ~/$LOGNAME): \c"
       21  read DEST

       23  : ${SYSTEM:="$SELF"}
       24  : ${DEST:="~/$LOGNAME"}

       26  echo "\nQUEUEING:"

       28  find $SOURCE -type f -print | sort | while read FILE
       29  do
       30          echo $FILE
       31          uucp -c -d -r $FILE $SYSTEM!$DEST/$FILE
       32  done

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

DEST | Место назначения, куда нужно копировать файлы
FILE | Определенный копируемый файл
LOGNAME | Содержит регистрационное имя
SELF | Содержит узловое имя текущей системы
SOURCE | Источник всех копируемых файлов
SYSTEM | Имя системы, в которую нужно копировать

ОПИСАНИЕ

Зачем нам нужен командный файл uutrans?

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

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

Что делает uutrans?

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

Когда вы запускаете uutrans, первым делом он запрашивает исходный каталог для пересылки файлов. Если вы не хотите продолжать, нажмите возврат каретки для выхода. Обратите внимание, что используется команда find, поэтому путь, который вы указываете в ответ на подсказку, является префиксом маршрутного имени для всех передаваемых файлов. Например, если вашим текущим каталогом является $HOME и вы хотите сделать его каталогом-источником, введите ".". Оператор find сгенерирует для файла /src/f.c маршрутное имя ./src/f.c. Если же вы введете, например, имя /usr/russ, то результирующим маршрутным именем будет /usr/russ/src/f.c. Таким образом, требуется небольшая предусмотрительность, чтобы избежать создания лишних уровней каталога в системе-адресате.

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

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

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

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

Когда все файлы поставлены в очередь, вам нужно вручную подсоединиться к uucp с помощью команды 'c' или 'r' утилиты uust. Все каталоги в системе-адресате должны создаваться автоматически по мере пересылки файлов.

Несколько неприятный вопрос - права собственности и доступа к файлам на принимающей стороне. Обычная последовательность действий такова:

  1. Поставить файлы в очередь к uucp, используя инструментальное средство uutrans.
  2. Выйти из системы-источника.
  3. Войти в систему-адресат.
  4. Запустить uust и повторно подключиться к uucp.

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

Необходимо отметить, что если у вас нет прав доступа на запись в вашей umask, то создается пересылочный каталог, но в него не могут быть помещены никакие файлы, что подрывает передачу файлов с помощью uucp. Для разрешения этой проблемы измените вашу umask на "000" (по умолчанию она равняется 777 для каталогов и 644 для обычных файлов).

ПРИМЕРЫ

      1.  cd $P
      uutrans
      .
      remote

Сменить каталог на PUBDIR. Передать все файлы из текущего каталога (.) в удаленную систему, размещая их в подразумеваемом каталоге ~/$LOGNAME, т.е. в моем каталоге PUBDIR.

       2.  uutrans
         /etc
         remote
         /tmp/etc

Передать все файлы из каталога /etc в систему remote, помещая их в каталог /tmp/etc.

ПОЯСНЕНИЯ

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

Строка 10 инициализирует переменную SELF именем вашего системного узла, используемого утилитой uucp.

Строки 12-16 запрашивают каталог-источник и читают ответ в переменную SOURCE. Если ничего не введено или указан не каталог, программа завершается. Строки 18-21 запрашивают систему-адресат и каталог-адресат. Умолчаниями являются ваша собственная система и каталог PUBDIR для размещения файлов. Эти умолчания облегчают конфигурирование, связанное с пересылкой информации, чтобы не было необходимости вводить много данных.

В строках 23 и 24 проверяется, инициализированы ли переменные.

Если переменной SYSTEM присвоен возврат каретки, в нее заносится значение SELF. Если переменная DEST пустая, ей присваивается ~/$LOGNAME. Эти операторы устанавливают значения по умолчанию. Строка 26 печатает сообщение о том, что файлы ставятся в очередь. Весь фокус заключается фактически в строках 28-32. Команда find начинает работать с каталога SOURCE и находит все обычные файлы. Этот список передается утилите sort, так что uucp пересылает файлы в отсортированном порядке. Это облегчает отслеживание того, какие файлы были переданы, если возникают какие-то проблемы.

Отсортированный список попадает в цикл while, читающий имена файлов. Каждое имя отображается на экран для справки, и файл ставится в очередь к удаленной системе. В строке 31 указаны такие опции uucp: -r для постановки в очередь, -c для того, чтобы не копировать файлы в каталог подкачки, и -d для создания каталогов, необходимых в удаленной системе.

Для поддержки древовидной структуры мы применяем магический синтаксис $SYSTEM!$DEST/$FILE. Нам нужно не только скопировать файлы в систему и в каталог, но и определить маршрутное имя вплоть до самого файла. Это гарантирует, что файлы будут расфасованы по каталогам. Если бы был использован синтаксис $SYSTEM!$DEST, то все файлы попали бы в один каталог и их иерархическая структура была бы утеряна.

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

КОНФИГУРАЦИОННЫЕ РЕШЕНИЯ

Зачастую на практике вокруг нашей системы UNIX накапливается много не особенно совместимой аппаратуры, включая терминалы, микро-ЭВМ и модемы. Как заставить их работать совместно? Теперь когда мы рассмотрели основные особенности аппаратуры и программмного обеспечения для коммуникаций в системе UNIX, мы готовы представить некоторые примеры работы коммуникаций в комплексах с системой UNIX. Мы хотим рассмотреть ряд конфигураций, использующих различные комбинации аппаратуры. Проблемы меняются, но наш общий подход остается.

ПОДКЛЮЧЕНИЕ UNIX К МОДЕМУ И ГЛАВНОЙ МАШИНЕ

Первая задача, которую мы рассмотрим, это использование "персональной" системы UNIX, модема и главной машины. У нас есть система UNIX с одним последовательным портом, но мы имеем как модем, так и линию прямого подключения к системе большего размера. Как нам использовать один последовательный порт и для модема, и для главной машины? Решение показано на рис. 8-10.

Рисунок 8-10.
Подключение системы UNIX к модему и главной машине

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

       UNIX <-- --> модем
                Вызов из UNIX при помощи cu, uucp и обращение в UNIX
                с удаленного терминала

       UNIX --> главная машина
                UNIX обращается к главной машине, как прямо
                подключенный терминал

ПОДКЛЮЧЕНИЕ UNIX И ТЕРМИНАЛА К МОДЕМУ

Эта конфигурация аналогична предыдущей, за исключением того, что вся аппаратура локальна. Мы хотим использовать модем как для терминала, так и для UNIX. Конфигурация показана на рис. 8-11. Порт системы UNIX подсоединяется к одной стороне переключателя, а терминал к другой. Отметим, что терминал НЕ будет обращаться в систему UNIX. Все кабели прямолинейные. В таком комплексе возможны следующие конфигурации:

       UNIX <-- --> модем
                Обращение в UNIX с удаленного терминала и вызов из
                UNIX с помощью cu, uucp

       терминал --> модем
             Для вызова из UNIX только в качестве немого терминала

Рисунок 8-11.
Подключение UNIX и терминала к модему

ПОДКЛЮЧЕНИЕ UNIX К ТЕРМИНАЛУ, МОДЕМУ И ГЛАВНОЙ МАШИНЕ

Эта ситуация довольно сложна и требует двух блоков переключения. Соответствующая архитектура показана на рис. 8-12.

Рисунок 8-12.
Подключение системы UNIX к терминалу, модему и главной машине

Начнем с системы UNIX. В ней имеется линия, идущая в переключатель 1 в качестве главного входа. Переключатель 1 осуществляет переключение между терминалом и блоком переключения 2. Это позволяет системе UNIX либо получать терминал для регистрации в системе (в предположении, что работает getty), либо получать модем для регистрации в системе, либо посылать информацию модему или главной машине (предполагается, что процесса getty нет). Заметим, что между терминалом и UNIX должен быть нулевой модем, но мы не можем сделать нуль-модем сразу после UNIX, потому что нам нужен прямой кабель для подсоединения к модему.

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

       UNIX <-- терминал
                Обращение в UNIX с терминала по линии getty

       UNIX <-- --> переключатель <-- --> модем
                Вызов из UNIX с помощью cu, uucp и обращение в
                UNIX с удаленного терминала

       UNIX --> переключатель --> главная машина
                Обращение из UNIX при помощи cu, uucp

СИСТЕМА UNIX, МИКРО-ЭВМ И МОДЕМ

В этой конфигурации также требуется два блока переключения. У нас есть машина с двумя последовательными линиями, на которой работает UNIX, автономная микро-ЭВМ и модем, который они должны совместно использовать. UNIX-машина также воспринимает регистрацию с автономной машины, позволяя ей эмулировать терминал, если у нас есть соответствующее программное обеспечение. Решение показано на рис. 8-13.

Рисунок 8-13.
Система UNIX, микро-ЭВМ и модем

Микро-ЭВМ может работать двумя способами: либо как терминал в системе UNIX, либо как терминал для обращения к модему. Система UNIX имеет один последовательный порт, позволяющий связываться с микро-ЭВМ через переключатель 1. Другая последовательная линия идет в блок переключения 2, подсоединенный к модему. По этой линии можно либо обращаться из системы UNIX во внешний мир, либо принимать внешние вызовы с целью удаленной регистрации в системе через модем. Переключатель 2 коммутирует модем между микро-ЭВМ и системой UNIX. Возможны такие комбинации:

       микро-ЭВМ --> переключатель --> модем
                Обращение вовне с помощью коммуникационных средств

       микро-ЭВМ --> переключатель --> UNIX
                Регистрация в системе путем эмуляции терминала

       UNIX <-- переключатель <-- микро-ЭВМ
                Регистрация с терминала, подключенного напрямую

       UNIX <-- --> переключатель <-- --> модем
                Вызов из UNIX при помощи cu, uucp, удаленная
                регистрация

       модем <-- переключатель <-- микро-ЭВМ
                Вызов модема с микро-ЭВМ

       модем <-- --> переключатель <-- --> UNIX
                Вызов из UNIX или обращение в UNIX; требуется
                изменение getty для последовательной линии и
               автоответа модема

АЛЬТЕРНАТИВНОЕ РЕШЕНИЕ

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

Рисунок 8-14.
Система UNIX, микро-ЭВМ и модем в альтернативной конфигурации

Как микро-ЭВМ, так и система UNIX подключены к переключателю 1. Этот переключатель присоединен к блоку переключения 2 главным входом. Линии от микро-ЭВМ, от UNIX, между переключателями и модемная линия должны быть прямыми кабелями. Только входная линия системы UNIX должна быть нуль-модемом.

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

       микро-ЭВМ --> переключатель --> модем
                Обращение вовне при помощи коммуникационных средств

       микро-ЭВМ --> переключатель --> UNIX
                Регистрация в системе путем эмуляции терминала

       UNIX <-- --> переключатель <-- --> модем
                Обращение из или в UNIX с помощью cu, uucp

       UNIX --> переключатель --> UNIX
                Выход с первой линии идет во вторую линию для
                регистрации в системе

       модем <-- --> переключатель <-- --> процессор
                Выбор центрального процессора зависит от
                переключателя 1

       UNIX <-- переключатель <-- процессор
                В зависимости от переключателя 1, один из
                процессоров может зарегистрироваться в системе

ТРИ СИСТЕМЫ UNIX

Последняя конфигурация, которую мы рассмотрим, это соединение трех систем UNIX. Данное решение было подсказано конфигурацией с прямым подсоединением 16-разрядной машины PC XENIX, 32-разрядной машины PC XENIX и главной машины VAX. Для того чтобы выполнить такое подключение, нам нужно три двунаправленных блока переключения.

Не так важно, какая из микро-ЭВМ 1 и микро-ЭВМ 2 является 32или 16-разрядной машиной. Они могут быть в вашем помещении, а главная машина может стоять где-то в другом месте и соединяться кабелем с вашим помещением. При этом предполагается, что кабель от главной машины уже является нуль-модемом в вашем подключении. Еще один нулевой кабель располагается между двумя системами UNIX на микро-ЭВМ, чтобы можно было зарегистрироваться с одной из этих систем в другой системе в качестве терминала. Остальные кабели прямые (см. рис. 8-15).

Рисунок 8-15.
Три системы UNIX

Каждая микро-ЭВМ подсоединена к блоку переключения через главное соединение. Эти переключатели 1 и 2 коммутируются друг с другом и с блоком переключения 3.

Микро-ЭВМ 1 может переключаться на микро-ЭВМ 2 в качестве интерфейса терминал/uucp и на главную машину в качестве удаленного терминала. В этом случае линия главной машины проходит через селектор порта. Микро-ЭВМ 2 может переключаться между микро-ЭВМ 1 и главной машиной аналогичным образом. Переключатель 3 коммутирует главную машину между микро-ЭВМ 1 и микро-ЭВМ 2. Возможны следующие комбинации:

       микро-ЭВМ 1 --> переключатель --> главная машина
                Зарегистрироваться в качестве удаленного терминала

       микро-ЭВМ 1 <-- --> переключатель <-- --> микро-ЭВМ 2
                Обращение в UNIX или из UNIX в зависимости от getty

       микро-ЭВМ 2 <-- --> переключатель <-- --> микро-ЭВМ 1
                Обращение в UNIX или из UNIX в зависимости от getty

       микро-ЭВМ 2 --> переключатель --> главная машина
                Зарегистрироваться в качестве удаленного терминала

       главная машина <-- переключатель <-- микро-ЭВМ 1
                Зарегистрироваться с удаленного терминала

       главная машина <-- переключатель <-- микро-ЭВМ 2
                Зарегистрироваться с удаленного терминала

                                ***

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


ГЛАВА 9. Администрирование и безопасность

  • Обязанности и ответственность администратора
  • Поддержка безопасности в системе
  • Типичные проблемы безопасности
access | показ всех беспарольных входов в систему
chkset | проверка наличия в системе файлов с разрешенной установкой идентификатора пользователя или группы
suw | отслеживание нарушителей по протокольному файлу команды su

АДМИНИСТРИРОВАНИЕ И БЕЗОПАСНОСТЬ

ЗАЧЕМ НАМ ИЗУЧАТЬ АДМИНИСТРИРОВАНИЕ

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

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

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

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

Необходимость знать администрирование становится ощутимой, когда вы обнаруживаете, что вам вдруг задали работу системного администратора или администратор ушел в отпуск, что пользователи выстроились возле ВАШЕГО стола в ожидании помощи, поскольку вас считают признанным авторитетом. Другой причиной возникновения необходимости может быть то, что вы стали безраздельным хозяином вашей собственной микроЭВМ с системой UNIX и хотите все установить и поддерживать по своему вкусу. Взгляните на такую необходимость администрирования как на возможность накопить обширные и доскональные знания о UNIX, которые могут сослужить вам добрую службу в вашей будничной работе с компьютером.

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

НЕКОТОРЫЕ НАБЛЮДЕНИЯ ПОСВЯЩЕННОГО В АДМИНИСТРИРОВАНИЕ

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

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

АДМИНИСТРАТИВНЫЕ ОБЯЗАННОСТИ

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

ПОДДЕРЖКА РАБОТОСПОСОБНОСТИ СИСТЕМЫ

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

УЛУЧШЕНИЕ СИСТЕМНЫХ ВОЗМОЖНОСТЕЙ

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

По всей видимости, наиболее важным ресурсом является время центрального процессора. Посадите тридцать пользователей в системе, рассчитанной на двадцати четырех - и вы сразу почувствуете нехватку процессорного времени. В работу администратора входит поддержка текущих ресурсов, а также планирование роста системы на будущее. Поэтому требуется, чтобы вы знали типичные раскладки использования вашей системы, знали, где могут возникнуть "узкие места", как эффективно распределить имеющиеся ресурсы и какие способы наращивания системы могут быть наиболее эффективными по стоимости. Некоторую полезную информацию можно извлечь путем "статического" инспектирования файловой системы с помощью средств, представленных в главе 2. Более динамичную картину эксплуатации системы вы можете получить, применяя команду ps, чтобы увидеть, какие процессы активны в настоящее время, и применяя команду w (в системе Berkeley), которая выдает статистику загрузки системы и организации очередей.

ПОМОЩЬ ПОЛЬЗОВАТЕЛЯМ

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

БЕЗОПАСНОСТЬ: НУЖЕН СТОРОЖЕВОЙ ПЕС

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

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

В работе сторожевого пса задействовано пять функций:

  1. Защита от неразрешенных входов в систему, файлов, программ и команд su.
  2. Сохранение конфиденциальности определенных данных.
  3. Наблюдение за использованием модемов.
  4. Предотвращение неразрешенных пересылок файлов.
  5. Сведение к минимуму возможностей взлома.

Здесь много работы! Система UNIX так обширна, и файлы могут скрываться в таком большом количестве мест, что один лишь поиск самозванцев занимает почти все время. Это требует от администратора не столько тяжелой, сколько изобретательной работы. Для того чтобы обеспечить себе шансы на победу в этой борьбе, вы должны сделать так, чтобы система помогала себя защищать. Несколько позднее в этой главе мы предложим инструментальные средства access и suw, которые помогают вам защищаться от запрещенных входов в систему, и командный файл chkset, имеющий дело с защитой конфиденциальных файлов. Мы также предлагаем более детальный взгляд на конкретные проблемы безопасности.

ЗАЩИТА ОТ ЗАПРЕЩЕННЫХ ВХОДОВ В СИСТЕМУ, ФАЙЛОВ, ПРОГРАММ И КОМАНД su

Имеется много способов получения несанкциониованных привилегий в системе UNIX. Самый простой способ - иметь корневой (суперпользовательский) интерпретатор shell. Это такой shell, который запускается как особо привилегированный процесс, имеющий возможность читать, удалять или модифицировать ЛЮБОЙ файл в системе независимо от того, какие права доступа установлены для этого файла его владельцем. Корневой shell можно заполучить, узнав корневой пароль у неосторожного администратора или при помощи других средств, рассматриваемых ниже. Несанкционированный пользователь, имея доступ к правам суперпользователя, может подготовить "потайные двери", обеспечивающие дальнейший запрещенный доступ. Они позволяют нарушителю запускать shell с корневыми привилегиями. Более подробно мы рассмотрим их позднее.

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

ВХОДЫ В СИСТЕМУ

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

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

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

       date::100:50:Print the date:/bin:/bin/date

who::101:50:Print all logged on users:/bin:/bin/who Это может запустить любой, кто имеет доступ к терминалу или модемному порту. Иногда это правильные имена, например date, who или sync. Хотя для администратора может быть удобным наличие программ, запускаемых при входе в систему, они часто становятся лазейками, через которые кто-нибудь может проникнуть в систему и обнаружить много информации о системе.

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

       break::102:50::/:/usr/bin/break

Такая запись позволила бы кому угодно набрать имя "break" в ответ на регистрационную подсказку, в результате чего выполнился бы файл /usr/ bin/break. Когда break отработает, снова поступает регистрационная подсказка, и в системе появляется новая лазейка. Почему? Потому что командный файл break может содержать команды для редактора, которые отредактировали бы парольный файл и добавили несанкционированные записи. Это становится возможным по той причине, что процесс getty (печатающий регистрационную подсказку) запускается процессом init, а владельцем файла init является суперпользователь. Такая привилегия передается командному файлу, так как он запущен в момент регистрации в системе, а программам, запускаемым при входе в систему, обычно требуется суперпользовательский доступ для выполнения необходимых инициализаций. В данном случае, однако, он позволяет редактору читать файл /etc/passwd и писать в него. Таким образом, как только нарушитель ОДИН РАЗ получает доступ на запись в /etc/passwd (аналогично "диверсионным программам"), он может установить постоянный доступ, часто даже через несколько точек входа.

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

       rt::::The Super User:/:/bin/sh

Вот некоторые другие проблемы, за которыми должен следить администратор. Если первая строка парольного файла пустая, то пользователь может зарегистрироваться как корневой без пароля. Проверьте также запись "bin" в парольном файле, которая обычно запускает системные программы. Если запись bin не содержит пароля, как в приведенном выше примере, кто-то может войти в систему в качестве bin и отредактировать файл crontab, чтобы применить к парольному файлу команду chmod (change permission mode, изменение прав доступа) и обеспечить себе доступ к нему. Пользователь bin может также отредактировать файл /etc /rc, чтобы сменить парольный файл. Файл rc используется для конфигурирования системы в момент ее старта путем автоматического запуска ряда программ. Все, что нужно для успешного вторжения,- подождать, когда администратор перегрузит систему (поскольку именно в этот момент файл запускается). После перезагрузки нарушитель может войти в систему как обычный пользователь, отредактировать парольный файл, записать его, а потом в любой момент входить в систему в качестве суперпользователя. Это всего лишь несколько способов, которыми можно добиться несанкционированного входа в систему. К сожалению, каждый день выдумывают новые способы.

ФАЙЛЫ И ПРОГРАММЫ

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

Основными файлами, в которые вторгается корневой нарушитель, являются /etc/passwd, /etc/*rc*, /usr/lib/crontab, /usr/lib/uucp/L.sys. Для обнаружения трещин в вашей административной броне можно поискать файлы, для которых взведен бит разрешения установки пользовательского идентификатора (что указывается буквой "s" в правах доступа, отображаемых командой "ls -l"), и файлы, владельцем которых является суперпользователь. Назначение бита установки пользовательского идентификатора - разрешить программе иметь временный доступ к более привилегированному состоянию (например, суперпользовательскому), чем она имеет в момент своего запуска. На самом деле это очень полезное свойство системы UNIX, так как оно позволяет управлять доступом ко многим таким особенностям, к которым вы бы не хотели предоставить непосредственный доступ для других пользователей. К сожалению, в эти программы может кто-нибудь проникнуть, чтобы использовать их временный корневой статус для вредительства, которое мы уже описывали. Таких файлов имеется конечное число, и все они могут быть проверены. Рассматриваемый далее командный файл chkset автоматизирует для вас процесс проверки. Тем не менее, знание того, какие файлы МОГЛИ быть подвергнуты вмешательству, еще ничего не говорит о том, в какие файлы ДЕЙСТВИТЕЛЬНО произошло вторжение и как. Тяжелее всего обнаружить залатанные системные файлы. Некоторыми из часто латаемых файлов являются login, su, passwd, ps, crypt и mv.

Бывает, что изощренный нарушитель скрывает даты модификации файла, чтобы никто не смог его обнаружить по этому признаку. Единственный способ зафиксировать такое вмешательство - иметь КОНТРОЛЬНУЮ СУММУ, т.е. запись с суммой (количеством байтов) всех важных файлов и хранить ее в отдельном месте или в закодированном виде. Путем периодической сверки старых сумм с новыми, можно обнаружить измененные файлы.

Еще одна вещь, за которой должен следить администратор, это "скрытые файлы". Скрытые файлы являются частью системы и имеют определенный смысл: они предназначены для того, чтобы не загромождать распечатки каталогов. Для того чтобы скрыть файл, нужно сделать первым символом имени файла точку (.). При использовании команды ls вы должны указать опцию -a, если вы хотите увидеть файлы, начинающиеся с точки. Обнаружение запрещенных файлов может быть затруднено, если файл зарыт на три-четыре уровня каталога вниз и назван незаметным именем. Решение заключается в том, чтобы всегда применять опцию -a команды ls, если вы сталкиваетесь с проблемами. Некоторые команды по умолчанию печатают файлы, начинающиеся с точки. Ncheck(1M) печатает все файлы, имеющие взведенный бит разрешения установки пользовательского идентификатора. Если файл назван странным образом, его сразу же видно. Одним из моих любимых является файл "...". Он выглядит несколько странно, но это правильное имя файла. Вы даже можете завести имя файла, образованное 14 точками - такова максимальная длина имени файла.

КОМАНДЫ su

Последний вопрос, за которым нужно следить,- запрещенные команды su. Su - это такое средство, которое позволяет вам ПОДСТАВИТЬ другой пользовательский идентификатор вместо вашего собственного. Если кто-то знает корневой пароль, он может войти в систему с любого терминала и применить команду su с корневым паролем. Однако, это, вероятно, тот случай, когда нарушители потратят больше всего времени, пытаясь чего-либо добиться. Дело в том, что все транзакции su записываются в протокольный файл под названием sulog. Правда, к сожалению, если уж нарушитель стал суперпользователем, то ему ничто не мешает модифицировать протокольный файл с целью удаления компрометирующих записей. К тому же если редактор vi вызван без имени файла, то никто не может увидеть, какой файл редактируется в то время, когда в системе происходит вредительство.

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

СОХРАНЕНИЕ КОНФИДЕНЦИАЛЬНОСТИ ДАННЫХ

Даже если допустить, что секретность обеспечена, бывают случаи, когда администратору нужно защитить важные файлы от любопытных глаз. В системе UNIX это можно сделать с помощью специальных атрибутов защиты файла, специальных групповых прав доступа, шифровки или даже размещения этих данных на диске, который монтируется только в случае необходимости. Однако, такие данные не должны оставаться физически присутствующими в системе, если они не монтированы, потому что нарушитель может их смонтировать и получить к ним доступ. Командный файл mntlook, рассмотренный ранее, умеет просматривать все устройства и находить такие доступные, но немонтированные файловые системы. Необходимо соблюдать такое правило: "Если вы не хотите, чтобы кто-нибудь видел этот файл, не держите его на виду". И не думайте, что вы так хорошо его спрятали, что никто не сможет его найти. Если люди имеют суперпользовательский доступ в вашу систему, они могут за считанные минуты получить распечатку каждого файла этой системы. Затем, когда вы не видите, они могут при помощи uucp передать интересный файл в другую систему для последующего изучения, скопировать его на гибкий диск или даже отпечатать. Помните, что если в вашу систему проник несанкционированный пользователь, НИКАКОЙ БЕЗОПАСНОСТИ БОЛЬШЕ НЕТ!

КОНТРОЛЬ ЗА ИСПОЛЬЗОВАНИЕМ МОДЕМА

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

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

Обращение вовне, в другие системы через модемы - отдельная история. Обычно тот, кто правильно зарегистрировался в системе, хочет обращаться к другим системам. Но что, если на вашей стороне имеются улавливающие регистрационные имена типа class, education, test и т.д.? Кто-то может войти в систему под видом одного из таких пользователей и использовать модем безо всякого риска быть схваченным за руку. Единственный способ поймать таких нарушителей - по номеру терминальной линии, если у вас имеются специально выделенные линии. Что произошло бы, если бы тот, кто вошел в вашу систему через модем при помощи одного из перечисленных регистрационных имен, обратился бы потом вовне, к какой-нибудь "дальней земле"? Тогда не было бы никакой возможности уследить за обратным вызовом определенного пользователя.

ПРЕДОТВРАЩЕНИЕ ЗАПРЕЩЕННЫХ ПЕРЕСЫЛОК ФАЙЛОВ

Запрещенные пересылки файлов имеют отношение почти исключительно к средствам uucp. В системе Berkeley (BSD 4.1 и старше) сетевые команды также имеют аналогичные проблемы. Вот пример: если кто-то запускает в системе Berkeley командный файл для "взлома двери", то команда удаленной регистрации в системе (rlogn) регистрирует нарушителя на другой машине в качестве суперпользователя и никогда не спрашивает корневой пароль. Разве это не очевидная пробоина в системе? Несанкционированный пользователь может также применить удаленное копирование (rcp), чтобы скопировать программу "взлома двери" во все системы.

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

СВЕДЕНИЕ К МИНИМУМУ ВОЗМОЖНОСТЕЙ ВЗЛОМА

Это то, чем администраторы часто пренебрегают. Совет номер один - НИКОГДА не оставлять без присмотра терминал, зарегистрированный как суперпользовательский. Бросить без присмотра терминал с корневым доступом - все равно, что оставить тысячу ключей от сейфа компании на вашем столе. Все несанкционированные пользователи могут воспользоваться этим, подготовив командный файл "взлома двери", ожидающий такого момента. Как только они получают в свои руки ваш терминал, всего лишь одна команда предоставляет им безграничные суперпользовательские возможности. С этого момента система перестает быть защищенной. Системные администраторы должны проверять свои системы и смотреть на них с точки зрения нарушителя. Есть ли хоть когда-нибудь момент, в который система уязвима? Что может произойти среди ночи, когда работают программы резервного копирования? Может ли кто-нибудь завладеть консольным терминалом? Не сможет ли навредить тот, кто приходит к вам помогать? Если вы на секундочку вышли, не сможет ли кто-то применить команду chmod, а потом заполнить экран чем-нибудь другим, чтобы вы не узнали, что это было сделано? Вот те опасности, о которых вам нужно помнить.

ТИПИЧНЫЕ ПРОБЛЕМЫ БЕЗОПАСНОСТИ

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

ПОТАЙНЫЕ ДВЕРИ

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

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

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

Проблему решает исполняемый файл команды passwd, в котором бит setuid установлен на владельца файла, а владельцем файлов, соответствующих обычным системным командам, является суперпользователь (пользовательский идентификатор 0). Это означает, что ВО ВРЕМЯ РАБОТЫ ПРОЦЕССА, соответствующего данной команде, пользователь имеет корневые привилегии! Когда команда завершается, прекращается и корневой доступ ... если только в данную команду не было какого-то вмешательства или если нарушитель не установил особую программу setuid. В этих случаях остается только войти в потайную дверь.

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

Фактическое проникновение в систему с обретением возможностей суперпользователя происходит тогда, когда работает "дверная" программа. Здесь используется волшебство бита setuid. Когда этот бит взведен, программа устанавливает (или изменяет) пользовательский идентификатор процесса на пользовательский идентификатор владельца данного файла (который оказывается суперпользовательским). Пока этот пользовательский идентификатор временно является суперпользовательским, программа превращается в shell-интерпретатор (обычно путем выполнения системного вызова exec). Такой shell находится по другую сторону двери, в царстве суперпользователя, со всеми принадлежащими ему привилегиями.

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

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

Опытный нарушитель вряд ли оставит исходный текст программы потайной двери в системе, поэтому администратор вынужден разглядывать только исполняемый модуль. Для реассемблирования объектного кода можно применить отладчик (adb), но если только вы не имеете безумно близкого знакомства с внутренностями системы UNIX, вам будет весьма трудно представить себе, что происходит. Изощренные программы потайной двери избегают также присутствия легко узнаваемых строк в исполняемых модулях. Вы можете, однако, применить команду strings (если она есть в вашей системе) для поиска символьных строк, которые там могли бы быть.

ПРОТОКОЛЬНЫЕ ФАЙЛЫ

Одна из простейших ловушек для того, кто пытается добыть права суперпользователя - создавать запись, помещаемую в протокольный файл. При этом администраторам нужно следить за протокольными файлами, не появляются ли там записи, которые могут быть признаком злодейства. Ниже мы покажем вам инструментальное средство, которое автоматически следит за одним из таких протокольных файлов - файлом sulog, содержащим транзакции "замененного пользователя". Другой протокольный файл, часто нуждающийся в проверке, это протокол программы uucp, потому что эта программа может быть использована для несанкционированных пересылок файлов. Многие нарушители пытаются проверить протокольные файлы и удалить компрометирующие записи, сгенерированные при их вмешательстве. В арсенале администратора есть средства борьбы с этим. Они не на 100 процентов эффективны, но отлавливают некоторых нарушителей и, усложняя им жизнь, могут отбить охоту вторгаться в систему.

В дополнение к обычным протокольным файлам, поддерживаемым системой UNIX, некоторые администраторы заводят свои собственные протокольные файлы, а затем подправляют ключевые команды так, чтобы они помещали данные в эти новые регистрационные файлы в процессе своей работы. Это может помочь обезвредить неосторожных лазутчиков. Один знакомый администратор сделал протокольный файл для команды cu и назвал его /tmp/.../.culog. Довольно умный тайный фокус, но в /usr/lib/crontab у него была запись для периодической печати этого файла. Это его выдавало: нужно было маскироваться получше. Заметим также, что ваши "скрытые" имена протокольных файлов могут быть извлечены путем просмотра исполняемого образа команды с помощью утилиты strings.

Если у вас есть пользователь, от которого вы ожидаете чего-нибудь запрещенного, вы должны уметь установить специальную систему регистрации, которая запускала бы более совершенный механизм, когда такой пользователь работает в системе. Программу watch из главы 6 можно модифицировать так, чтобы она вызывала специальную протоколирующую программу, когда в систему входит пользователь из известного списка. Протоколирующая программа могла бы повторять команды ps (process status, состояние процесса) и/или делать "моментальные снимки" обычных регистрационных файлов (особенно учетных файлов) и направлять результаты в припрятанный протокольный файл. Идея состоит в том, что опасные процессы можно было бы обнаружить до того, как нарушитель получает возможность войти в протокольные файлы и изменить их. (Видимо, вам нужно избегать применения команды at в такой программе, а периодически пользоваться вместо нее командой sleep. В противном случае, нарушитель может распознать ваши мероприятия по записи файла crontab.) Как только вы имеете результат о сеансе работы опасного пользователя, вы можете запустить grep для поиска интересующих вас имен или написать инструментальное средство, которое выполняет для вас такой поиск.

УЧЕТНЫЕ ФАЙЛЫ

Вероятно, наиболее важным среди протокольных файлов является учетный файл. В учетном файле имеется запись о каждом и всяком процессе, запускаемом в системе. Точную структуру можно посмотреть в файле /usr/include/sys/acct.h. В одном из полей этой структуры записаны процессы, имеющие суперпользовательские возможности. Когда кто-либо входит в систему через корневую дверь, для shellинтерпретатора, который он запускает, и для всех процессов, которые он порождает, владельцем является корень (суперпользователь). В учетном файле отражен номер терминала, с которого запущен процесс, поэтому вы можете увидеть корневые процессы, запущенные с таких терминалов, на которых пользователям не разрешен суперпользовательский доступ.

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

Учетные файлы могут хорошо разоблачить процессы, имеющие не такой пользовательский идентификатор, как у лица, запустившего эти процессы. Поищите процессы, владельцем которых был известный пользователь, но которые имеют суперпользовательские возможности. Среди них могут быть корректные записи, например lpr, так как отображаются все системные программы, запущенные со взведенным битом setuid. Записи, которые мы ищем, относятся к shell-интерпретаторам с установленным учетным флагом суперпользователя. Это выдает тот факт, что была выполнена программа потайной двери. Изучите подключаемый файл acct.h, чтобы увидеть все определения. Используя бит ASU для проверки поля, мы можем изолировать флаговую область, отражающую привилегию суперпользователя. Самый лучший способ рассмотреть эту структуру - написать программу на языке Си, печатающую все элементы структуры. В следующей распечатке показаны некоторые важные учетные поля:

    |
    |  cmd   f   uid   tty      btime
    |
    |  more  0   russ   0   Sat Jul  5 01:25:59 1986
    |  ls    0   russ   0   Sat Jul  5 01:31:12 1986
    |  ps    0   russ   0   Sat Jul  5 01:31:59 1986
    |  id    0   russ   0   Sat Jul  5 01:34:00 1986
    |  pwd   0   russ   0   Sat Jul  5 01:34:12 1986
    |  sh    1%  russ   0   Sat Jul  5 01:33:51 1986
    |   \__ корневой shell с эффективным пользовательским идентификатором
    |
    |  sync  0   russ   0   Sat Jul  5 01:34:21 1986
    |  df    0   russ   0   Sat Jul  5 01:34:27 1986
    |  id    0   root   0   Sat Jul  5 01:34:37 1986
    |  sh    2#  root   0   Sat Jul  5 01:34:33 1986
    |   \__ корневой shell с реальным пользовательским идентификатором,
    |       2# обозначает бит суперпользователя,
    |       владелец изменен на root
    |

Отметим, что shell-интерпретаторы с эффективным пользовательским идентификатором маскируют бит во флаге суперпользователя, но владельцем их процессов является обычный пользователь. Не известно, все ли системы применяют значение 1 в качестве флага shell-интерпретатора с эффективным пользовательским идентификатором. Похоже, что Berkeley поступает именно так, а System V нет.

КОМАНДА su

Как мы уже отмечали, UNIX предоставляет пользователям обычный способ стать суперпользователем - это команда su. Как видно из предыдущего изложения, суть команды su заключается в системном вызове exec. Тот, кто применяет команду su, должен знать корневой пароль, транзакция протоколируется в файле /usr/adm/sulog, а команда ps оглашает тот факт, что активен корневой shell. Этот прием не самый хитроумный.

ДОСТУП К ТЕРМИНАЛУ СУПЕРПОЛЬЗОВАТЕЛЯ

Помните о том, что НИКОГДА нельзя оставлять без присмотра терминал, на котором вы работаете в качестве суперпользователя? Пока вы отсутствуете, кто-то может воспользоваться этим терминалом, чтобы выполнить команду chmod с целью установки бита пользовательского идентификатора. Хорошо подготовленный нарушитель уже имеет откомпилированную программу потайной двери, владельцем которой является суперпользователь, и ждет, когда будут изменены права доступа. Не остаются ли консольные терминалы доступными в качестве суперпользовательских ночью, во время действий по резервному копированию? Если остаются, то наутро администратор может оказаться в незавидном положении!

БОЛЕЕ ПОДРОБНО О ПОЛЬЗОВАТЕЛЕ bin

Мы уже упоминали о "лазейке" в некоторых системах, позволяющих пользователю "bin" без труда завладеть суперпользовательским доступом. Проблема "bin" имеет еще ряд аспектов. Если несанкционированные пользователи могут войти в систему через bin (являющийся владельцем большинства дистрибутивных исполняемых модулей), то они почти наверняка смогут получить права суперпользователя. Прежде всего, в некоторых версиях владельцем всех исполняемых модулей, размещенных в каталогах /bin и /usr/bin, является bin. Это значит, что нарушители могут перезаписать или подправить исполняемые модули своими собственными вариантами, выполняющими некоторые особые действия, например "chmod 4755 door", а затем восстановить на место исходную версию исполняемого модуля.

Еще один способ простого превращения bin в суперпользователя заключается в модификации /etc/rc - файла запуска команд ("run command"). Он запускается каждый раз, когда машина загружается в многопользовательском режиме. Записывая в него "chmod 777 /etc/passwd", нарушитель может превратить парольный файл в обычный пользовательский после того, как машина загрузится.

Последний способ - использовать файл /usr/lib/crontab. Этот путь изменен в последней версии System V. Теперь полное имя этого файла /usr/spool/cron/crontabs/xxx, где xxx - файл crontab для каждого пользователя. В старом варианте владельцем файла /usr/lib/crontab иногда является bin. Кто-нибудь может отредактировать этот файл и поместить в него такие, например, команды:

      * * * * *        chmod 777 /etc/passwd
      * * * * *        chmod 4755 /tmp/door
      * * * * *        /bin/su root -c "chmod 777 /etc/passwd"

Все это срабатывает по той причине, что cron выполняется процессом init. Поскольку init - один из первых запущенных процессов, его владельцем является суперпользователь. Поэтому любая команда, которую выполняет cron, имеет корневые привилегии. Звездочки означают, что cron должен выполнить указанные команды в очередной возможный момент времени. Cron запускает процесс, изменяющий права доступа к указанному файлу. Нарушители могут поменять парольный файл (что несколько более опасно) или просто активизировать программу потайной двери. Если две первые команды не сработают, то владелец будет изменен на суперпользователя методом грубой силы - выполнением команды su, а затем передачей команды chmod shell-интерпретатору, запущенному командой su. Вы должны почаще проверять файлы crontab и /etc/rc!

ВОЗМОЖНОСТЬ ЗАПИСИ В СПЕЦИАЛЬНЫЕ ФАЙЛЫ

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

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

ПЕРЕЗАГРУЗКА СИСТЕМЫ

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

ИСПОЛЬЗОВАНИЕ ПРЕИМУЩЕСТВ ПЕРЕМЕННОЙ $PATH

Кое-кто может воспользоваться ситуацией, когда пользователь имеет каталог $HOME/bin впереди системных каталогов /bin и /usr/bin в своей переменной PATH. Помещая подправленную программу в регистрационный каталог безвредного пользователя, нарушитель заставляет эту программу выполняться вместо настоящей, поскольку система исполняет первый файл, совпадающий с именем команды, который она обнаруживает в ходе просмотра определенного пользователем маршрутного списка. Примером могла бы служить команда ls.

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

ФАЛЬШИВЫЕ ВЕРСИИ НА ЛЕНТАХ

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

ЗАШИФРОВАННАЯ БАЗА ДАННЫХ

Хотя мы не можем дать гарантию, что кто-нибудь смекалистый не опишет, как расколоть парольную защиту, кодирование пароля в системе UNIX по алгоритму DES считается довольно секретным. (Рассмотрение механизма DES выходит за пределы данной книги.) К этой защите добавляется одна вещь - два символа, генерируемые случайным образом, называемые "солью" (salt) и хранимые в файле /etc/passwd для каждой записи. "Соль" используется для определения того, какой из 4096 вариантов алгоритма DES применяется для кодирования заданного пароля. Нарушитель мог бы захватить пароль, использовать его "соль" и зашифровать список известных паролей. Если бы результат совпадал с пробным паролем, взлом был бы осуществлен.

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

ЗАПАДНЯ

Западня работает только на специально выделенных линиях. Когда UNIX ожидает, что кто-то будет входить в систему, она печатает регистрационную строку из файла /etc/gettydefs. Пользователь вводит свое регистрационное имя и пароль и попадает в систему. Программа западни извлекает из этого выгоду. Она имитирует поведение экрана во время регистрации. Когда пользователь вводит свое имя и пароль, такая программа печатает сообщение "login incorrect" ("неверная регистрация"), а затем запускает настоящую регистрационную программу. Пользователь думает, что он сделал ошибку и повторяет попытку входа в систему, на этот раз успешно. Регистрационное имя и пароль можно куда-нибудь отложить для последующего изучения. Самая лучшая защита администратора от этого - обучение пользователей. Видимо, с помощью регулярной почты или бюллетеней нужно напоминать им, что если им кажется, что они набрали пароль правильно, но получили при этом сообщение "incorrect", то они должны немедленно сменить свой пароль и сообщить об этом происшествии администратору. Бывают случаи, когда пользователь действительно ошибается при вводе пароля, но к большому количеству таких происшествий нужно отнеститсь со всей серьезностью.

КОМАНДА МОНТИРОВАНИЯ

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

Обычное действие, предпринимаемое в небольших системах для того, чтобы дать пользователям возможность работать с их собственными гибкими дисками,- это установка бита пользовательского идентификатора для файла команды /etc/mount. Таким образом, когда запускается команда монтирования, ее пользовательский идентификатор становится корневым, и она может смонтировать гибкий диск. Команда демонтирования должна быть подготовлена аналогично.

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

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

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

АВТОНОМНЫЙ shell (SASH)

В системах UNIX с гибкими дисками загрузочный диск обычно загружается с гибкого накопителя. Подразумевается, что гибкий диск применяется для подготовки жесткого диска и копирования всех файлов системы UNIX с гибкого диска на жесткий. Но за этим кроется нечто большее. На самом деле загружаемый гибкий диск - это компактная, переносимая версия системы UNIX. Ядро адаптировано к тому, чтобы размещаться на гибком диске, а не на жестком. Когда вы его загружаете, вы получаете интерпретатор shell и среду точно так же, как при работе с жесткого диска. Вот почему такой shell называется автономным (SASH, standalone shell).

Корневая файловая система на гибком диске даже выглядит точно так же, как файловая система жесткого диска. Фактически вы можете смонтировать загружаемый диск и скопировать утилиты с жесткого диска на гибкий. Нужны две важные команды: mount и umount. Ограничением является размер гибкого диска. На него не так много помещается. Сценарий, с помощью которого несанкционированный пользователь может применить SASH для входа в систему с суперпользовательскими привилегиями, выглядит примерно так. Сначала он должен отключить питание или перезагрузить защищенную систему. Затем он должен загрузить SASH и смонтировать корневую файловую систему жесткого диска в точку монтирования своей файловой системы гибкого диска. Команды могут быть такими:

       # /etc/mount /dev/fp001 /mnt  <-- для System V
       # /etc/mount /dev/hd0a  /mnt  <-- для XENIX

Это дает нарушителям доступ на жесткий диск при помощи обращения /mnt/*. Все, что им нужно для редактирования файла /etc/passwd - это пройти вниз по дереву каталогов. Для этого можно применить такие команды:

       # /mnt/bin/vi /mnt/etc/passwd
       # sync

Теперь жесткий диск изменен, и его можно вернуть на место. Нарушитель может остановить автономный вариант UNIX и перезагрузиться с жесткого диска. Он может использовать новое регистрационное имя, созданное при помощи SASH. Мы не знаем, насколько часто люди могут предпринимать такие попытки. Небольшие системы более уязвимы, но в них и меньше пользователей (и потенциальных злодеев). Во многих случаях нижней границей "логической защиты" становится "физическая защита". Большинство людей, имеющих большие машины или даже мини-ЭВМ, постоянно осознают, что им есть что защищать. А микро-ЭВМ выглядят настолько "дружественными" и простыми, что люди обычно забывают, что информация, которая содержится на микрокомпьютерах, при определенных обстоятельствах может оказаться настолько же желаемой и значительной, как и на больших машинах.

ПРАВКИ ИСХОДНЫХ ТЕКСТОВ

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

ЯДРО

Еще одно место, в котором нужно следить за несанкционированными правками - библиотеки ядра. Подправленные объектные модули можно легко поместить в библиотеки незамеченными. Другим библиотекам грозит та же опасность. Дополнительные разумные усовершенствования ядра можно обнаружить в системных вызовах chmod и chown. Когда выполняются эти системные вызовы, они проверяют, имеете ли вы пользовательский идентификатор 0. Если нет, то ваш запрос не удовлетворяется. Отменив эту проверку, любой обычный пользователь мог бы изменить владельца файла на суперпользователя, а также изменить режим защиты файла, чтобы взвести бит установки пользовательского идентификатора. Это позволило бы успешно обойти защитный барьер.

ПРОГРАММА passwd

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

ПРОГРАММА crypt

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

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

КОМАНДА su

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

    |
    |  Получить информацию о пользователе: пользовательский идентификатор,
    |  групповой идентификатор, пароль, номер терминала, ...
    |  Если пароль пустой или пользовательский идентификатор равен нулю,
    |      То пройти мимо вопросов о пароле.
    |
    |  Запрос пароля
    |  Если зашифрованный вариант того, что было только что набрано,
    |       не совпадает с парольной строкой из файла /etc/passwd,
    |  То   запротоколировать неудачную попытку применения su,
    |       напечатать сообщение "sorry" (сожаление по поводу неудачи),
    |       выйти.
    |  Пароль прошел:
    |    запротоколировать успешную попытку применения su,
    |    выполнить системные вызовы для того, чтобы ввести в действие
    |      пользовательский и групповой идентификатор,
    |    установить среду, если это требуется,
    |    выдать на консоль сообщение, если это корневой shell,
    |      а вы не за системной консолью,
    |    организовать аргументы для показа su в команде ps,
    |    выполнить интерпретатор shell.
    |

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

Если введен такой пароль, то проверку пароля и действия по протоколированию можно обойти, поэтому запрещенный доступ не отразился бы в протоколе. Несанкционированный пользователь добился бы того, что ДРУГИЕ пароли для команды su записывались бы в "секретный" файл. В результате он бы потихоньку получил все интересующие его пароли для потенциального использования. Для пользователя команда su срабатывала бы успешно, если пароль правильный, а нарушитель получал бы в свое распоряжение пароль.


ГЛАВА 9. Администрирование и безопасность

КОМАНДА login

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

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

ПРОСТИТЕЛЬНЫЕ ГРЕХИ

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

СИСТЕМНЫЙ РЕЖИМ

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

Во многих процессорах, например в процессоре Motorola 68000, имеется регистр состояния процессора (Processor Status Register), называемый обычно PSW, хотя у разных процессоров он может называться по -разному. PSW содержит бит, определяющий, работает ли машина в "супервизорном" или в пользовательском режиме. Этот режим важен для многопользовательской аппаратуры, поскольку все пользовательские программы работают в пользовательском режиме, что сегментирует и защищает память от "коллизий" между процессами.

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

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

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

СИСТЕМНЫЙ ВЫЗОВ

Первый метод - создать "пользовательский" системный вызов. Системные вызовы находятся в исходных файлах с именами вида os/sys?.c. Это примерно 60 системных вызовов, и каждый из них имеет специфический номер. Этот номер определяется таблицей системных входов - таблицей адресов точек входа в системные вызовы. Для добавления нового системного вызова необходимо подготовить его исходный код. Когда ядро перекомпилировано и установлено, можно производить системный вызов из любой программы в системе. Как только такой вызов активизирован, он может перевести машину в системный режим.

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

ПСЕВДОУСТРОЙСТВО

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

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

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

НАРУШИТЕЛЬ ВЫДАЕТ СЕБЯ ЗА УДАЛЕННЫЙ УЗЕЛ uucp

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

При помощи uucp несанкционированные пользователи могут попасть в систему, выдав себя за удаленный узел uucp. Это очень легко сделать. Нарушители могут заглянуть в файл /usr/lib/uucp/L.sys в вашей системе и обнаружить, где находятся удаленные системы, путем поиска входов в систему на других машинах. Затем они могут посмотреть в файле /etc/passwd такие входы в систему, которые запускают программы uucico вместо обычного shell-интерпретатора. Если они обнаружат соответствующие пароли, они могут попытаться применить некоторые вероятные пароли или использовать один из методов внесения правок, рассмотренных ранее, с целью перехвата паролей.

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

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

ПОДДЕЛКА ПОЧТЫ

Этот прием довольно хорошо известен, но мы включаем его для полноты изложения. Похоже, однако, что он работает не во всех версиях системы UNIX. Он работает в System V, но не работает в XENIX, System III и Berkeley 4.2. Данный метод заключается в изменении пользовательской переменной среды LOGNAME. Поскольку команда mail использует ее, чтобы идентифицировать вас при отправке вам почты, меняется заголовок почты. Обычно это всего лишь мелкая неприятность, но вы должны уведомить об этом пользователей, чтобы они очень внимательно относились к таким сообщениям, которые кажутся несвойственными для их мнимого отправителя.

СКРЫТЫЕ ИМЕНА ФАЙЛОВ ПРИ РАБОТЕ С РЕДАКТОРОМ vi

Полезной практикой для обеспечения безопасности является выполнение случайных команд ps. Такая мера более-менее равносильна периодическому патрулированию с целью увидеть, не происходит ли что-нибудь опасное. Необходимо, однако, отметить, что лица, использующие редактор vi для несанкционированной работы, могут замести свои следы, выбирая такое имя редактируемого файла, чтобы оно не появлялось в распечатке команды ps. Самый простой способ, которым они могут это сделать - вызвать vi без указания имени файла. Тем самым vi запускается с пустым файлом. Затем они могут применить команду ex для редактирования нужного им файла. Это убережет имя файла от распечатки командой ps, так как оно не является частью набора аргументов команды vi. Массив аргументов формируется при вызове команды vi, а не после ее запуска.

Другой способ - использовать маскировку. Нарушители могут переименовать файл, который они хотят редактировать, в ничего не означающее имя, например tmp, а потом использовать имя tmp при вызове редактора vi. В результате в массив аргументов занесется имя tmp. Оно и появится в распечатке команды ps.

      ИМЯ: access

      access

НАЗНАЧЕНИЕ

Ищет в парольном файле все регистрационные записи, не имеющие паролей.

ФОРМАТ ВЫЗОВА

      access

ПРИМЕР ВЫЗОВА

access Выдает список всех беспарольных входов в систему

ТЕКСТ ПРОГРАММЫ

       1   :
       2   # @(#) access v1.0  Show all free access logins  Author: Russ Sage

       4   if [ "$#" -gt "0" ]
       5     then  echo "access: too many arguments" >&2
       6           echo "usage: access"              >&2
       7           exit 1
       8   fi

       10  grep '^[^:]*::' /etc/passwd || echo "All logins protected"

ОПИСАНИЕ

Зачем нам нужен командный файл access?

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

Что делает access?

Командный файл access использует команду grep с шаблоном поиска, описывающим регистрационную запись, не имеющую пароля. Когда такая запись попадается, она печатается в стандартный вывод. Если указанных записей не найдено, выводится сообщение "All logins protected" ("Все входы в систему защищены").

ПОЯСНЕНИЯ

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

Оператор в строке 10 выполняет поиск в парольном файле. Применяется утилита grep, т.к. мы используем в этой команде выражение. Если бы мы использовали фиксированную строку, более предпочтительной была бы утилита fgrep, потому что она быстрее. Выражение, задающее поиск, означает следующее: начиная с начала строки, найти все символы, отличные от двоеточия, вплоть до обнаружения двух двоеточий подряд. Если вы заглянете в файл /etc/passwd, то увидите, что первое поле представляет собой имя (от начала строки до первого двоеточия). Затем между первым и вторым двоеточием размещается пароль. Если пароль отсутствует, то после первого двоеточия сразу же следует второе - именно это соответствует нашему шаблону поиска. Поиск выполнятся в файле /etc/passwd. Если grep успешно обнаружил хотя бы одну запись, то код возврата нулевой. Если grep ничего не обнаружил, то код возврата единица. Тогда активизируется последняя часть строки 10 и выводится сообщение о том, что все записи о входе в систему защищены.

      ИМЯ:  chkset

      chkset

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

      chkset [-l] [dir ...]

ПРИМЕР ВЫЗОВА

      chkset -l

Вести поиск, начиная с корневого каталога, поскольку каталог не указан. С помощью команды "ls -d" выдать список файлов, для которых установлен в единицу бит разрешения установки идентификатора пользователя либо идентификатора группы. Результат отсортировать по имени файла. (Бит установки пользовательского идентификатора S_ISUID и бит установки группового идентификатора S_ISGID являются атрибутами защиты файла наряду с битами прав доступа на чтение/запись/выполнение и определены в подключаемом файле /sys/stat.h. - Примеч. перев.)

ТЕКСТ ПРОГРАММЫ

       1   :
       2   # @(#) chkset v1.0  Check for set bits on  Author: Russ Sage

       4   FORM="-print"
       5   SORT="sort"

       7   if [ "`echo $1 | cut -c1`" = "-" ]
       8     then  case $1 in
       9           -l)     shift
       10                  FORM="-exec ls -ld {} ;"
       11                      SORT="sort +7";;
       12          *)      echo "usage: chkset [-l][file/dir ...]" >&2
       13                  exit 1;;
       14          esac
       15  fi

       17  if [ "$#" -gt 0 ]
       18    then SRC="$*"
       19    else SRC="/"
       20  fi

       22  find $SRC \( -perm -4000 -o -perm -2000 \) $FORM | $SORT

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

FORM | Команда и опции для листинга
SORT | Команда и опции для сортировки результата
SRC | Исходный каталог, от которого нужно начинать поиск

ОПИСАНИЕ

Зачем нам нужен командный файл chkset?

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

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

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

а) имеют необычные имена (некоторые нарушители любят выставлять свои достижения напоказ);

б) содержат в исполняемом файле символьные строки, которые можно прочитать;

в) размещены в необычном или очевидном каталоге;

г) не имеют ограничений относительно того, кто может их запустить.

Для изощренных shell-интерпретаторов характерно следующее:

а) они имеют имена, похожие на обычные команды системы UNIX;

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

в) содержат упрятанные символьные строки, которые не так-то легко прочитать;

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

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

Конечно, перед тем как вы сможете проверить файлы со включенными битами разрешения установки пользовательского/группового идентификатора, вам нужно найти все такие файлы. В системе UNIX это делают две команды: find(1) и ncheck(1M). Утилита find ищет файлы по нашей точной спецификации и может быть использована для поиска файлов с заданным набором прав доступа, включая биты установки идентификаторов. Ncheck печатает вперемешку специальные файлы и файлы с разрешенной установкой идентификатора пользователя. Это очень большой список, чтение и поиск в нем интересующих нас файлов занимает много времени. Таким образом, полезно создать командный файл, предоставляющий нам всю необходимую информацию и только такую информацию. Chkset использует команду find и ищет только файлы со включенными битами разрешения установки пользовательского/группового идентификатора, так что результат содержит лишь необходимую нам информацию, и этим результатом можно сразу же воспользоваться.

Что делает chkset?

Chkset имеет два режима функционирования: один для сканирования всей системы в целом, а другой для сканирования указанных деревьев каталогов. Это хорошее свойство, так как сканирование каждого файла системы занимает очень много времени. Если имеется много больших дисковых устройств, проверка всего содержимого системы может занять целый час. Утилита chkset также очень сильно загружает центральный процессор из-за всех процессов, которые она генерирует. Указывая имена каталогов, вы можете выполнить проверку лишь на определенной области системного дерева. Отметим, однако, что поскольку chkset пользуется командой find, она сканирует не только указанный вами каталог, но и ВСЕ подчиненные каталоги. Заметим также, что chkset обнаруживает ВСЕ файлы с установленными в единицу битами установки пользовательского/группового идентификатора, а не только те, владельцем которых является суперпользователь (root).

Результат работы chkset можно выдать также двумя способами. Если не применять никакую опцию, то форма выдачи результата определяется командой "find ... -print", что означает полные маршрутные имена найденных файлов. Затем эти полные имена сортируются. Если применяется опция -l, то для форматирования результата используется команда "ls -ld", порождающая длинный формат листинга. При этом распечатываются полное указание прав доступа, число связей, владелец, размер и имя файла. Этот результат также сортируется по именам. Выбирайте тот или иной формат в зависимости от обстоятельств. Если вам нужно проверить большой участок системы и получить список подозрительных файлов, примените листинг по умолчанию (без опций), так как он компактнее и, как мы увидим позднее, занимает значительно меньше процессорного времени. Если вы хотите заняться определенным каталогом и посмотреть на его файлы подробно, воспользуйтесь опцией l. Она предоставляет больше информации и избавляет от необходимости вручную набирать команды ls для интересующих вас файлов.

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

ПРИМЕР

      # chkset /bin /usr/bin /lib /usr/lib

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

ПОЯСНЕНИЯ

Первым делом chkset инициализирует две переменные - FORM и SORT. Переменная FORM содержит команду для выдачи результата работы команды find, а переменная SORT - команду, определяющую, что нужно сортировать.

В строке 7 проверяется, является ли первый позиционный параметр опцией. Если да, то оператор case (строки 8-14) смотрит, какая это опция. Если это опция "-l", то подготавливается команда для распечатки результата (это мы обсудим позже). Команда для утилиты sort формируется так, чтобы сортировка шла по полю владельца. Опция убирается из командной строки, потому что все последующие аргументы должны быть каталогами и мы захотим получить к ним доступ с помощью "$#". Если попалась опция, отличная от "-l", то это ошибка, выдается сообщение об ошибке (строка 12), и командный файл завершается.

Если осталось более нуля аргументов, когда мы попадаем в строку 17, то они проверяются в цикле, чтобы убедиться, что все они являются каталогами. Если это не каталоги, на стандартное устройство регистрации ошибок выдается сообщение об ошибке и командный файл завершается. Если имеются параметры (т.е. каталоги), то в строке 18 в переменную SRC заносятся все каталоги. Если же параметров нет, то в переменную SRC заносится значение "/", т.е. корневой каталог, чтобы обеспечить подразумеваемую стартовую точку для поиска.

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

После того как мы указали команде find, откуда начинать поиск, мы указываем ей, что нужно искать. В данном случае нас интересуют все файлы, которые имеют включенный бит установки пользовательского либо группового идентификатора. Мы объясняем это команде find путем указания прав доступа, которые требуется искать. Строка "-perm -4000" означает поиск всех файлов, имеющих права доступа со включенным битом установки пользовательского идентификатора и с любыми другими включенными битами. Вы можете понимать эту запись как применение символов -заменителей - 4???. Мы ищем как установку пользовательского идентификатора (-4000), так и установку группового идентификатора (-2000), поэтому две строки прав доступа соединены опцией -o, означающей "or" ("или"). (Более полное описание прав доступа в символической и восьмеричной форме приведено в chmod(1).)

Следующая задача - добавить строку, хранимую в переменной FORM, в командную строку. Если опция -l не была использована, то в переменной FORM хранится строка "-print", а это значит, что find будет печатать маршрутные имена найденных файлов. Если же -l использовалась, то переменная FORM содержит строку "-exec ls -ld {} ;". Опция -exec это очень гибкая опция команды find, позволяющая применить любые команды, которые за ней следуют, к каждому найденному файлу. В данном случае эти команды выполняют распечатку в длинном формате (-l), причем для каждого каталога (-d) выводится только его имя (а не содержимое). Именно здесь происходят наибольшие затраты ресурсов центрального процессора, так как для опции -l требуется системный вызов stat.

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

Затем весь поток данных пропускается через утилиту sort. На самом деле мы хотим сделать сортировку по восьмому полю (вы можете проверить это, выполнив команду "ls -l" и изучив ее результат). Утилита sort ПРОПУСКАЕТ указанное число полей, начиная с поля 1, являющегося по умолчанию стартовой точкой, поэтому использование записи +7 означает переход к восьмому полю, которым является имя файла.

АЛЬТЕРНАТИВНЫЙ ПОДХОД

Для поиска файлов с интересующими нас правами доступа можно применить другой метод, хотя и более медленный. Он основывается на том, что вместо поиска прав доступа по числу, можно искать их по символьной строке. Для этого нужно применять grep. Альтернативная команда выглядит так:

       # find $* -exec ls -ld {} \; | grep "^[^ ]*s[^ ]*"

Этот вариант команды работает несколько иначе. Он находит каждый файл из указанных каталогов и применяет к каждому из них команду "ls -ld". Затем весь список данных передается по конвейеру команде grep. Grep использует для распознавания интересующих нас файлов такой шаблон поиска: начиная с начала строки, найти повторяющийся символ, отличный от пробела, затем символ "s", за которым следует повторяющийся символ, отличный от пробела. Такому шаблону соответствуют все режимы прав доступа, содержащие s, будь то бит установки пользовательского идентификатора или бит установки группового идентификатора. Не существенно, входят ли в данный режим "r", "w", "x" или "-". Убедившись, что пробелов нет, мы обеспечиваем соответствие шаблона только полю прав доступа. Важно, чтобы после s следовали символы, отличные от пробелов, так как s может встретиться либо в порции прав доступа, относящейся к владельцу, либо в групповой порции и нигде больше. Мы не очень рекомендуем этот метод, поскольку он привлекает очень интенсивную обработку. Однако, если бы мы не испробовали этот метод первым, мы бы не оценили, что использование команды find со строками "-perm" является более предпочтительным. В силу невероятной гибкости системы UNIX, имеется очень много различных способов выполнения одной и той же работы, и многие из этих способов могут давать одинаково хорошие результаты, но за разную цену в смысле быстродействия и процессорных затрат. Перед тем как применять универсальную утилиту вроде grep для решения задачи, рассмотрите, нет ли другой команды со встроенной опцией распознавания по шаблону. Скорее всего, применение такой команды окажется оптимальнее и гораздо быстрее, чем использование grep. С другой стороны, иногда вам нужно быстро решить нечасто встречающуюся задачу, не слишком заботясь об эффективности решения.

      ИМЯ: suw

      suw

НАЗНАЧЕНИЕ

Просматривает протокольный файл команды su и печатает имена всех пользователей, которые нелегально превратились в суперпользователя при помощи команды su (substituted user, замененный пользователь).

ФОРМАТ ВЫЗОВА

      suw [-m] [sulog]

ПРИМЕР ВЫЗОВА

suw Запуск в режиме по умолчанию, проверка файла /usr/adm/sulog и выдача записей о нарушителях в стандартный вывод.

ТЕКСТ ПРОГРАММЫ

    1   static char id[]="@(#)suw v1.0   Author: Russ Sage";

    3   # include

    5   # define FALSE 0
    6   # define TRUE  1
    7   # define MATCH 0
    8   # define BSIZ  80

    10  main(argc,argv)
    11  int argc;
    12  char *argv[];
    13  {
    14          register int  alert, c, mail, n;
    15          FILE   *fp1, *fp2;
    16          char   *p, *uname, line[BSIZ], tmp[BSIZ],
    17                 *log = "/usr/adm/sulog";

    19          static char  *legal[] = {sage-root\n","root-root\n",NULL};
    20          static char  *adm[]   = {"sage",NULL};

    23          mail = FALSE;

    25          if (argc > 1 && argv[1][0] == '-')
    26                  switch (argv[1][1])
    27                  {
    28                      case 'm':
    29                          mail = TRUE;
    30                          --argc;
    31                          ++argv;
    32                          break;
    33                      default:
    34                          fprintf(stderr,"suw: invalid argument %s\n",
                                                         argv[1]);
    35                          fprintf(stderr,"usage: suw [-m] [sulog]\n");
    36                          exit(1);
    37                  }

    39          if (argc == 2)
    40                  log = *++argv;

    42          if ((fp1 = fopen(log,"r")) == NULL)
    43          {
    44                  fprintf(stderr,"suw: error opening %s\n",log);
    45                  fprintf(stderr,"usage: suw [-m] [sulog]\n");
    46                  exit(1);
    47          }

    49          sprintf(tmp,"/tmp/suw%d",getpid());
    50          if ((fp2 = fopen(tmp,"w+")) == NULL)
    51          {
    52                  fprintf(stderr,"suw: error opening %s\n",tmp);
    53                  fprintf(stderr,"usage: suw [-m] [sulog]\n");
    54                  exit(1);
    55          }

    57          while (fgets(line,sizeof(line),fp1) != NULL)
    58          {
    59                  p = line + 15;
    60                  if (*p == '+')
    61                  {
    62                          p = p + 2;
    63                          while (*p != ' ') p++;
    64                          p++;
    65                          uname = p;
    66                          while (*p && *p++ != '-')
    67                                  continue;

    69                          if (strcmp (p,"root\n") == MATCH)
    70                          {
    71                                  alert = TRUE;
    72                                  for (n=0; legal[n] != NULL; n++)
    73                                          if (strcmp (uname,legal[n])
                                                        == MATCH)
    74                                          {
    75                                                  alert = FALSE;
    76                                                  break;
    77                                          }
    78                                  if (alert)
    79                                          fprintf(fp2,"Illegal --> %s",
                                                             line);
    80                          }
    81                  }
    82          }

    84          if (mail)
    85          {
    86                  fclose(fp2);
    87                  for (n=0; adm[n] != NULL; n++)
    88                  {
    89                          sprintf(line,"cat %s | mail %s",tmp,adm[n]);
    90                          system(line);
    91                  }
    92          }
    93          else
    94          {
    95                  rewind(fp2);
    96                  while ((c = getc(fp2)) != EOF)
    97                          putc(c, stdout);
    98                  fclose(fp2);
    99          }

    101        fclose(fp1);
    102        unlink(tmp);
    103 }

ОПИСАНИЕ

Зачем нам нужна программа suw?

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

Что делает suw?

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

По умолчанию записи о нарушителях печатаются в стандартный вывод. Протокольным файлом по умолчанию является /usr/adm/sulog. Если применяется опция -m, то записи о нарушителях рассылаются по почте администраторам, занесенным в предопределенный список. Если нужно проверить другой протокольный файл, например /usr/adm/Osulog, то его имя можно указать в командной строке.

ПРИМЕРЫ

      1.  # suw -m

Проверить файл /usr/adm/sulog и разослать записи о нарушителях администраторам, определенным в тексте программы.

      2.  # suw /usr/adm/Osulog

Проверить файл /usr/adm/Osulog и напечатать записи о нарушителях в стандартный вывод.

ПОЯСНЕНИЯ

В самом начале программы определяются и инициализируются все переменные и списки. В строке 14 определены два флага: alert (сигнал тревоги) и mail (почта). Они имеют значение TRUE или FALSE. В этой программе используются два файла: протокольный файл команды su, который вы выбираете, и временный файл. Поскольку мы собираемся применять некоторые подпрограммы стандартного ввода-вывода (stdio), мы пользуемся указателями на файлы fp1 и fp2, а не дескрипторами файлов. Применяется два буфера: один для зачитывания в него данных из протокольного файла команды su, а другой - для хранения имени временного файла. Первоначально именем протокольного файла является sulog. Если вместо него используется другое имя файла, то переменная log переустанавливается на это имя.

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

Подразумеваемое состояние рассылки почты устанавливается в строке 23 на значение FALSE, т. е. почты нет. Это делается для того, чтобы после разбора командной строки переменная mail имела правильное значение.

Далее выполняется проверка на ошибки. Первая проверка выглядит несколько странной, но на самом деле вполне понятна. Переменная argc возвращает число аргументов командной строки, а argv указывает на массив, содержащий сами аргументы, каждый из которых сам является массивом символов. Если командная строка вообще имеет какие-либо аргументы (argc > 1) и первым символом первого аргумента (argv[1][0]) является дефис, то проверяется, корректная ли это опция. С целью проверки второго символа первого аргумента используется оператор case. Если аргументом является символ m, то флаг mail устанавливается в состояние TRUE, число аргументов (argc) уменьшается на единицу, а указатель на массив аргументов (argv) на единицу увеличивается. Это служит той же цели, что и удаление аргументов из командной строки в командных файлах интерпретатора shell. Напомним, что argv является в действительности массивом указателей, а массивы трактуются точно так же, как строки, с базовым адресом и смещением. Поэтому argv[0] == argv, и argv[1] == ++argv. Если опция отличается от -m, то в стандартный вывод печатается сообщение об ошибке и программа завершается. В строке 39 проверяется счетчик аргументов, чтобы понять, есть ли еще один аргумент. Напомним, что argv всегда на единицу отстает от argc, потому что само имя команды является первым аргументом массива. Следовательно, для того чтобы получить второй аргумент, мы должны перейти к следующей позиции (*++argv). Если аргумент имеется, он должен быть именем протокольного файла, и это имя заносится в переменную log. Далее мы пытаемся открыть файлы, используемые в программе. Сначала открывается для чтения протокольный файл. Если это не срабатывает (возвращен нулевой указатель), то печатается сообщение об ошибке и программа завершается. Затем в строке 49 создается имя временного файла с добавлением к нему идентификатора процесса, чтобы гарантировать уникальность имени файла. Этот файл открывается на чтение и запись. Если эта операция не удается, печатается сообщение об ошибке и выполнение завершается.

В строках 57-103 заключен главный цикл. Управляющая часть главного цикла определяется тем, все ли данные прочитаны из протокольного файла. Если больше никакие байты прочитать нельзя, то fgets (строка 57) возвращает нулевой указатель, что завершает цикл while. Когда строка данных читается из протокольного файла, эти данные помещаются в массив line. Мы применяем указатель на символ для прохода вдоль строки и поиска записи о нарушителе.

Изучая запись в вашем файле sulog, вы можете увидеть, где находятся интересующие нас поля. Сначала указатель смещается на 15 символов от начала строки. Это позиция флага, определяющего, была ли успешной команда su. Если она была успешной, указатель увеличивается на два, чтобы пропустить пробел и установить указатель на имени терминала. Это имя имеет переменную длину, поэтому мы ориентируемся по пробелу в конце имени. В строках 63-64 применяется цикл while, который заставляет указатель пропустить все символы, отличные от пробела. Затем он увеличивается на единицу еще раз, чтобы пропустить пробел между именем терминала и строкой имени пользователя.

В этот момент указатель находится в последнем поле строки. Указатель на это поле помещается в переменную uname, чтобы затем ее можно было использовать для сравнения строк. Следующий цикл while (строки 66-67) проходит по строке, пока не попадет на символ дефиса. Цикл while выполняется до тех пор, пока указатель не указывает на конец строки (т.е. на нелевой указатель, *p == '\0') и еще не указывает на дефис. Поскольку в цикле используется p++, то когда мы попадаем на нулевой указатель, цикл завершается, причем p указывает на следующий символ.

Выполняется проверка, был ли суперпользователем тот, кто применил команду su. Если был, то взводится флаг alert, и мы должны проверить, разрешенный ли это суперпользователь. Цикл for (строки 72-77) пробегает по всем именам в массиве, содержащем имена разрешенных пользователей, до тех пор, пока мы не обнаружим последнюю запись, которая является пустой. Строка с именем пользователя, установленная предварительно, сверяется с каждой записью в массиве разрешенных имен. Если они совпадают, то мы можем предположить, что с данным применением команды su все в порядке. Тогда мы выключаем флаг alert и завершаем сравнение имен. Если же по окончании цикла флаг alert все еще взведен, значит имя не содержится в списке разрешенных - это, возможно, нарушитель. Вся запись целиком записывается во временный файл и управление передается назад в начало цикла для следующей операции чтения.

Когда все данные прочитаны, цикл while завершается. В строке 84 проверяется, взведен ли флаг mail. Если да, то временный файл закрывается (чтобы сработала команда cat) и выполняется еще один цикл for (строки 87-91). Он перебирает всех администраторов и завершается, когда попадает на нулевой указатель в конце массива. Для каждого из обозначенных администраторов конструируется команда mail и посылается в систему UNIX при помощи системного вызова (строка 90). Метод отправки по почте временного файла заключается в применении к этому файлу команды cat и передаче данных по конвейеру команде mail.

Если флаг mail выключен, то временный файл нужно напечатать в стандартный вывод, а не отправить по почте. Для того чтобы сделать это как можно быстрее, временный файл (который пока еще открыт) "перематывается" (мы позиционируемся в его начало) и посимвольно пропускается по циклу. Обратите внимание, что данный цикл является сердцевиной команды cat, как описано на странице 153 книги Кернигана и Ритчи "Язык программирования Си" (B.W.Kernighan, D.M.Ritchie. The C Programming Language). После того как временный файл напечатан, он закрывается. Наконец, закрывается протокольный файл и удаляется временный файл.


ГЛАВА 10. Смешанные приемы

  • Введение
  • Способы преобразования в языке shell
    • conv
  • Модули преобразования
    • dtoh
    • dtoo
    • htod
    • htoo
  • Тонкости bc
    • otod
    • otoh
  • Встроенный ввод
  • С редактором ed
  • С файлом a.out
  • C архивами языка shell
  • Управление статусом цикла
  • Фильтры и синтаксис
  • Недостатки/особенности программирования на языке shell
  • Программа для перенаправления ошибки
  • Некорректный код возврата
  • Хитрости редактора Vi
  • Возвращение в язык shell
  • Поддержка Escape
  • Макросы
  • Команда "One-Liners" - крошечная, но мощная

Введение

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

Способы преобразования

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

Название: conv

conv Переводит числа из одной системы счисления в другую

НАЗНАЧЕНИЕ

Обеспечивает возможность преобразования основания системы счисления

ВЫЗОВ

    conv

ПРИМЕР ВЫЗОВА

$conv | Вызвать главное меню различных преобразований
2 | Выбрать опцию 2 ( из шестнадцатиричной в десятичную)
FFF | Ввести шестнадцатиричное число FFF. На выходе программы получим десятичный эквивалент

ИСХОДНЫЙ ТЕКСТ ДЛЯ ФУНКЦИИ conv

       1  :
       2  # @(#) conv v1.0  Преобразование основания   системы
          счисления, используя shell   Автор: Russ Sage
       3
       4  while :
       5  do
       6           echo "
       7
       8  Преобразование оснований
       9  ------------------------
       10 1 - Десятичное в шестнадцатиричное
       11 2 - Шестнадцатиричное в десятичное
       12 3 - Десятичное в восьмеричное
       13 4 - Восьмеричное в десятичное
       14 5 - Восьмеричное в шестнадцатиричное
       15 6 - Шестнадцатиричное в восьмеричное
       16
       17  enter choice (1-6, <>): \c"
       18        read CHOICE
       19
       20        case $CHOICE in
       21        "")    exit;;
       22        1)     echo "\пВведите десятичное число (<> to exit): \c"
       23               read DEC
       24               if [ "$DEC" = ""]
       25                 then  exit
       26               fi
       27               HEX='. dtoh'
       28               echo "\n${DEC}d = ${HEX}x";;
       29        2)     echo"\nВведите шестнадцатиричное число
                 в верхнем регистре (<> to exit):  \c"
       30               read HEX
       31               if [ "$HEX" = ""]
       32                 then  exit
       33               fi
       34               DEC='. htod'
       35               echo "\n${HEX}x= ${DEC}d;;
       36        3)    echo "\nВведите десятичное число в
                верхнем регистре (<> to exit): \c"
       37               read DEC
       38               if [ "$DEC" = ""]
       39                 then  exit
       40               fi
       41               OCT='. dtoo'
       42               echo "\n${DEC}d = ${OCT}o";;
       43        4)   echo "\nВведите восьмеричное число
               (<> to exit): \c"
       44               read OCT
       45               if [ "$OCT" = ""]
       46                 then  exit
       47               fi
       48               OCT='. otod'
       49               echo "\n${OCT}o = ${DEC}d";;
       50        5)   echo "\nВведите восьмеричное число
               (<> to exit): \c"
       51               read OCT
       52               if [ "$OCT" = ""]
       53                 then  exit
       54               fi
       55               HEX='. otoh'
       56               echo "\n${OCT}o = ${HEX}x";;
       57        6)   echo "\nВведите шестнадцатиричное число
               в верхнем регистре (<> to exit): \c"
       58               read НЕХ
       59               if [ "$НЕХ" = ""]
       60                 then  exit
       61               fi
       62               OCT='. htoo'
       63               echo "\n${HEX}x = ${OCT}o";;
       64        *)     echo "\n$CHOICE-неизвестная команда";;
       65        esac
       66  done

ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ

CHOICE | Выбор команд из главного меню
DEC | Выдает десятичное значение как результат преобразования
HEX | Выдает шестнадцатиричное значение как результат преобразования
OCT | Выдает восьмеричное значение как результат преобразования

ОПИСАНИЕ

Зачем нам нужна функция conv ?

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

Что делает conv?

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

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

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

ПОЯСНЕНИЕ

Строки 4-66 - это один большой бесконечный цикл while. Мы используем бесконечный цикл, чтобы в случае ошибочного ввода программа вернулась в главное меню для повторного ввода. Для того, чтобы выйти из программы, нужно прервать цикл, т.е. выйти из цикла. Строки 6-17 печатают меню и выдают подсказку для выбора. Если Вы просто нажмете "Ввод", программа завершит свою работу.

Строка 18 читает ввод с клавиатуры, и строки 20-65 выполняют выбор по условию для этой величины. Если получен нулевой (пустой) ввод, то программа завершает свою работу.

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

Подсказка запрашивает число в строке 23. В строках 24-26 проверяется, не было ли введенное значение пустым. Строка 27 выглядит несколько загадочно, вызывая один из внешних командных файлов dtoh для преобразования десятичных чисел в шестнадцатииричные. Обратите внимание на то как одна программа выполняет другую.

Командный файл dtoh запускается, используя команду ".". Это означает : "Выполните программу, используя тот же shell". Процедура dtoh использует переменную DEC для ввода числа и выдает преобразованное число на стандартный вывод. Чтобы записать это число в переменную, мы делаем присвоение, потом запускаем программу, используя командную подстановку. Строка 28 выдает на экран первоначальное десятичное число и шестнадцатиричное, к которому оно было преобразовано.

Варианты 2, 3, 4, 5 и 6 работают аналогично. Единственное, что меняется - это имя переменной, которое соответствует типу преобразования и название командного файла (скрипта), который вызывается для этого преобразования.

МОДУЛИ ПРЕОБРАЗОВАНИЯ

Теперь давайте рассмотрим отдельно каждый из модулей перевода. Эти модули или командные файлы языка shell используют команду UNIX bc, чтобы осуществлять преобразования оснований систем счисления. Нельзя сказать, что команда bc - это наиболее простой и удобный способ перевода, но тем не менее она работает, и единственное, что нам нужно, - это изучить ее и поместить в командный файл.

НАЗВАНИЕ : dtoh

dtoh Десятичные в шестнадцатиричные.

НАЗНАЧЕНИЕ

Преобразовывает входные десятичные числа в выходные шестнадцатиричные числа.

СИНТАКСИС

    $DEC="decimal_number"; HEX='.dtoh'

ПРИМЕР ВЫЗОВА

        $DEC="25";HEX='.dtoh'
        $echo $HEX

Присвоить DEC начальное значение 25, вызвать dtoh для его преобразования и записать результат в HEX. Вывести результаты на экран.

ИСХОДНЫЙ ТЕКСТ ДЛЯ dtoh

       1    :
       2    # @(#) dtoh v1.0  Преобразование языка shell--десятичные в
                       шестнадцатиричные  Автор: Russ Sage
       3
       4     bc < HEX
        $ echo "шестнадцатиричное число: 'cat HEX'"

Обозначение () запускает вызываемую процедуру в дочернем языке shell. Используя "." для ее выполнения, мы по прежнему имеем доступ к переменной DEC. Стандартный вывод перенаправляется в HEX. Эхо сопровождение получает значение, используя командную подстановку. Результат cat помещается в эхо предложение.

НАЗВАНИЕ: dtoo

dtoo Десятичные в восьмеричные

НАЗНАЧЕНИЕ

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

СИНТАКСИС

     DEC="decimal_number"; OCT='.dtoo'

ПРИМЕР ВЫЗОВА

     $DEC="16";OCT='.dtoo'
     $echo $OCT

Присвоить DEC начальное значение 16, вызвать dtoo для ее преобразования и записать результат в OCT. Вывести результаты на экран.

ИСХОДНЫЙ ТЕКСТ ДЛЯ dtoo

     1    :
     2    # @(#) dtoo v1.0  Преобразование языка shell--десятичные в
                    восьмеричные  Автор: Russ Sage
     bc <

НАЗВАНИЕ: htod

htod Шестнадцатиричные в десятичные

НАЗНАЧЕНИЕ

Переводит входные шестнадцатиричные числа в выходные десятичные.

СИНТАКСИС

     HEX="hex_number"; DEC='.htod'

ПРИМЕР ВЫЗОВА

     $HEX="1EAC" ; DEC='.htod'
     $echo $DEC

Присвоить HEX шестнадцатиричное значение, преобразовать его, напечатать результаты.

ИСХОДНЫЙ ТЕКСТ ДЛЯ htod

     1    :
     2    # @(#) dtoo v1.0  Преобразование языка shell--шестнадцатиричные
                     в десятичные Автор: Russ Sage
     bc <

НАЗВАНИЕ: htoo

htoo Шестнадцатиричные в восьмеричные

НАЗНАЧЕНИЕ

Переводит входные шестнадцатиричные числа в выходные восмеричные.

СИНТАКСИС

     HEX="hex_number"; OCT ='.htoo'

ПРИМЕР ВЫЗОВА

     $HEX="F1E" ;
     $OCT='.htoo'
     $echo $OCT

Присвоить HEX шестнадцатиричное значение, преобразовать его, напечатать восьмеричное число.

ИСХОДНЫЙ ТЕКСТ ДЛЯ htoo

     1    :
     2    # @(#) htoo v1.0  Преобразование с помощью языка shell --
              шестнадцатиричные в восьмеричные Автор: Russ Sage
     bc <

НАЗВАНИЕ: otod

otod Восьмеричные в десятичные

НАЗНАЧЕНИЕ

Переводит входные восьмеричные числа в выходные десятичные.

СИНТАКСИС

     OCT="octal_number"; DEC = '.otod'

ПРИМЕР ВЫЗОВА

     $OCT="777" ;
     $DEC='.ot
     $echo $DECod'

Присвоить OCT восьмеричное число, преобразовать его в десятичное, напечатать результат.

ИСХОДНЫЙ ТЕКСТ ДЛЯ otod

     1    :
     2    # @(#) otod v1.0  Преобразование языка shell--восьмеричные в
                     десятичные    Автор: Russ Sage
     bc <

НАЗВАНИЕ: otoh

otoh Восьмеричные в шестнадцатиричные

НАЗНАЧЕНИЕ

Переводит входные восьмеричные числа в выходные шестнадцатиричные.

СИНТАКСИС

     OCT="octal_number"; HEX = '.otoh'

ПРИМЕР ВЫЗОВА

      $OCT="777" ;
      $DEC='.otoh'
      $echo $HEX

Присвоить OCT восьмеричное число, преобразовать значение OCT в шестнадцатиричное, запуская otoh. Присвоить результат преобразования HEX, отобразить значение HEX.

ИСХОДНЫЙ ТЕКСТ ДЛЯ otoh

     1    :
     2    # @(#) otoh v1.0  Преобразование с помощью языка shell --
                     восьмеричные в шестнадцатиричные
                     Автор: Russ Sage
     bc < idfile

Команда ls запускается как дочерний shell, используя обозначение (). Дочерний shell помещается в фоновый режим, используя символ & . Когда результат процесса id отображен, он направляется в файл ошибок дочернего языка shell, который его выполняет. Мы просто перенаправляем стандартную ошибку в файл и получаем число! Теперь мы можем сделать что-нибудь типа:

     $ kill -9  'cat idfile'

где процесс id, переданный kill, генерируется из команды cat, которая печатает процесс id, захваченный ранее. Это может дать программам опцию "kill self", где они могут отслеживать их id, чтобы вам не пришлось это делать. Программа watch, которую мы видели в главе 6 делает нечто подобное.

ВСТРОЕННЫЙ ВВОД

Редактор vi удобен до тех пор, пока Вам не нужно делать построчное редактирование текста или редактирование в командном режиме. Sed тоже неплохой редактор, но в нем не предусмотрена возможность перемещения по файлу. Sed может перемещаться только вперед по файлу до конца файла. Все наши проблемы может решить скромный редактор ed.

С редактором ed

Еd является интерактивным редактором и в нем есть все необходимое для обработки выражений. Поскольку он читает стандартный ввод для своих команд, мы можем помещать в stdin встроенный текст для управления собственно редактором. Еd читает команды, как если бы они были даны с клавиатуры. Он не знает, что запущен в командном режиме. Это открывает совершенно новый способ использования мощи интерактивного редактирования в командном режиме.

В качестве примера рассмотрим следующую программу. Помните, что все специальные символы в языке shell должны быть заключены в кавычки, например $. Если они не заключены в кавычки, то ввод будет некорректным.

      ed file << -!
         1, \$s/^ *//
         w
         q
      !

В этом примере редактируется файл под названием "file" и над ним выполняется несколько команд. Первая команда говорит "От первой строки до последней, для каждой строки, имеющей пустые символы в начале строки, за которыми следует любое количество таких же символов, заменить эти символы "ничем". Запишите файл и выйдите." Эта процедура удаляет пробелы из начала строки.

С файлом a.out

Возможность встроенного текста также можно использовать, чтобы автоматизировать запуск программ. Вам нужно записать входные данные, необходимые, чтобы программа выполняла желаемую задачу, и поместить их в текст программы. (Это нечто вроде построения макросов клавиатуры для прикладных программ для PC).

В следующем примере исполняемый файл a.out запускается как дочерний shell. Его ввод берется из самого файла, а вывод передается команде more, так что мы можем сделать постраничный вывод.

      $ (a.out < text
      > input
      > lines
      > !
      ) | more

Это можно напечатать непосредственно с клавиатуры. Мы используем символы скобок, поскольку, если непосредственно печатать этот код, shell будет выдавать подсказку PS2 вплоть до знака !, затем выполнит команду. Единственное, что мы можем сделать, чтобы он не вышел автоматически - это продолжать запрашивать ввод, опуская его на уровень ниже.

C архивами языка shell

Архивы языка shell - это один из самых простых способов упаковать текст в самоустанавливающуюся программу. Идея состоит в том, что мы используем командный файл языка shell, чтобы упаковать некоторый текст. Этот текст может быть документом, командным файлом или даже исходным текстом программы.

Мы используем конструкцию встроенного текста, чтобы передать текст в shell, который потом пересылает его в предопределенные файлы. Ниже приведен пример архива, который может быть в файле.

     $ cat archive
     #
     # Это архивный файл текстовых файлов 1, 2 и 3
     #

     echo "извлекаем текстовый файл 1"
     cat > text1.sh << !
     #
     #  Это пример текстового файла 1
     #

     who | sort
     !
     echo "извлекаем текстовый файл 2"
     cat > text2 << !

Это содержимое второго файла. Это не программа, а просто текст. Заметьте, что ему не нужно строк комментария, потому что запущенный shell знает, что это ввод. Но не пытайтесь запускать text2, т.к. у Вас все равно ничего не получится.

     !

     echo "извлекаем текстовый файл 1"
     cat > text3.c << !

     /* Это содержимое файла 3,  Си программа */

     main()
     {
     printf("hello world");
     }
     !

     #
     # конец архивного файла
     #

При выполнении архив проходит через три команды cat. Первая команда cat создает файл text1.sh (командный файл языка shell), text2 (обычный текст) и text3.c (Си-программа). Все это выполняется после того, как Вы наберете на клавиатуре "archive". Это удобно, если нужно перенести текст в другое место. Вместо того чтобы пересылать три файла, нам нужно переслать один. Вместо трех файлов, соединенных вместе, у нас три отдельно упакованнных файла, каждый из которых восстанавливает себя при запуске архива. Таким образом, нам не придется гадать, пытаясь представить какой текст в какой файл попадет.

Управление статусом цикла

Иногда условные выражения цикла нужно подбирать специальным образом, чтобы они отвечали нашим потребностям. Это происходит не часто, однако бывают ситуации, когда Вы можете захотеть использовать определенный синтаксис. В таблице 10-1 приведены три разных способа сделать условие цикла while "истинным". Помните, что shell ищет успешный статус выхода - статус (0) из последней синхронно выполняемой команды.

Таблица 10-1
Способы заставить цикл быть "истинным"

Цикл | Условие со значением "истина"
while true | True - это команда в /bin, которая возвращает статус 0
while[1 -eq 1] | Мы используем здесь тестовую команду, чтобы возвратить статус 0
while : | Мы используем встроенное предложение shell'а, чтобы возвратить статус 0

Фильтры и синтаксис

Ранее в этой книге уже шла речь о фильтрах. Не все команды являются фильтрами или могут быть использованы в качестве фильтров. Вспомните определение фильтра - это команда, которая берет ввод из аргументов командной строки, если они есть. Иначе ввод читается из стандартного ввода.

Почему все команды не могут действовать как фильтры? Потому, что они не предназначены для этого. Возьмем, например, ls. Что делает ls? Она выдает список файлов в текущем каталоге. Если мы говорим "ls file", он выдает информацию только для этого файла. Если мы говорим "echo file | ls", ls не дает информацию о файле, но выдает список файлов в текущем каталоге, потому что ls не смотрит в стандартный ввод, если в командной строке нет аргументов.

Один важный момент, связанный с фильтрами - это способ их вызова. Если Вы помещаете имя файла в командную строку, фильтр открывает файл и читает данные. Помните, что фильтры хотят читать данные. Если Вы присоединяете стандартный ввод к фильтру, он думает, что то, что он читает из программного канала- это данные. Если Вы дадите фильтру имена файлов, Вы не получите того результата, который ожидаете.

Давайте рассмотрим несколько примеров. Команда UNIX wc - это фильтр. Мы можем вызывать ее как "wc file1 file2 file3", чтобы она подсчитала слова в трех файлах. Что было бы, если бы мы сказали: "ls file1 file2 file3 | wc" ? Wc подсчитала бы сумму символов, которые выдала бы ls. В данном случае в строке file1, file2, и file3 - 15 символов. Как нам получить реальные файловые данные, а не имена файлов в wc? Изменяя способ, которым мы вызываем wc:

cat file1 file2 file3 | wс

Путем предварительного соединения файлов, данные передаются на вход wc, и wc суммирует подсчитанные суммы символов, содержимого файлов. Та же самая концепция применима для всех команд фильтров. Еще одной подобной командой является awk. Мы можем сказать "awk file", и команда прочитает содержимое файла, или "cat file | awk", и получим такой же результат. Мы не можем использовать синтаксис "ls file | awk", т.к. awk выполняет свою программу только над символами в имени "file".

Недостатки/особенности программирования на языке shell

В этом разделе мы рассмотрим некоторые недочеты языка shell. До конца пока не ясно, почему проявляются эти дефекты или особенности. Так уж shell работает и такой уж он на самом деле.

Программа для перенаправления ошибки

     1   :
     2   # @(#)  перенаправление ошибочного значения переменной в цикл,
          присоединенный к программному каналу
      3
      4   N=1
      5   echo "начальное значение N = $N"
      6
      7   echo "1\n2\n3" | while read LINE
      8   do
      9         N=2
      10        echo "значение в цикле N = $N"
      11  done
      12
      13  echo "конечное значение N = $N"

Программа показывает, что различные присвоения, сделанные в дочерних языках shell, не распространяются на их родителей. Строка 4 присваивает N начальное значени 1. Затем значение N отображается в строке 5 для проверки. Вся хитрость этой программы заключена в строке 5. Мы отправляем символы "1 новая строка 2 новая строка 3" в программный канал и даем это на вход циклу while. Таким образом, мы заставляем цикл выполнить три итерации. Присоединяя вывод к программному каналу, мы создаем дочерний shell, чтобы выполнить цикл while. Внутри цикла while мы изменяем значение N и печатаем его для проверки.

В конце цикла мы печатаем окончательное значение N. Оно больше не равно 2 как это было внутри цикла, а равно 1, как это было после первого присвоения. Ниже представлен пример прогона этой программы.

      $  redir
      начально значение N = 1
      значение в цикле  N = 2
      значение в цикле  N = 2
      значение в цикле  N = 2
      конечное значени  N = 1

Это показывает, что значение переменной передается вниз дочернему языку shell, но изменения в дочернем shell не передаются родительскому.

Некорректный код возврата

Откуда shell знает, являются ли корректными те или иные коды возврата? На это сложно ответить. Иногда оказывается, что код ошибочен, когда ошибку содержит ваша программа.

НАПРИМЕР

     1   :
     2   # @(#)  ошибка кода возврата
     3
     4   echo "Введите команду :  \с"
     5   read CMD
     6
     7   eval $CMD
     8   echo "\$? = $?"
     9
     10  if [ $? -eq 0 ]
     11     then     echo хороший выход - $?
     12     else     echo плохой выход - $?
     13  fi

Программа начинается с того, что в строке 4 Вам выдается подсказка для введения команды. Команда читается, строка 7 оценивает не требуются ли ей дополнительные переменные и выполняет ее. Помните, что нам нужно оценить параметр в том случае, если кто-нибудь сказал что-то типа "echo $HOME". Если команды eval нет, то печатается литеральная строка $HOME. После команды eval печатается действительное значение $HOME. Так что нам приходится использовать команду eval в этой ситуации. После того как команла выполнена строка печатает статус выхода, ссылаясь на $?. Это совершенно нормально.

Строка 10 затем использует тестовую команду, чтобы программа разветвилась в зависимости от кода возврата. Это и есть то место, где ошибка. Величина, которую видит тестовая команда не совпадает с той, которую печатает эхо.

      $ status
      введите команду: ls -z
      ls : illegal option --z
      usage : -1ACFRabcdfglmnopqrstux [files]
      $? = 2
      хороший выход - 0

Это показывает, что ls запустили в ошибочном режиме. Ls напечатал свое сообщение об ошибке и возвратил код возврата равный 2. Однако тестовая команда видит $? как значение 0 и выбирает ветвь истина. Можете ли вы найти ошибку в строке 8 программы ? Это хорошая нота для окончания раздела об ошибках.

Хитрости редактора Vi

Одна из самых замечательных особенностей UNIX - это возможность выйти из програм так, чтобы Вы могли запускать другие команды вне языка shell. Это было разработано внутри UNIX и является простым и мощным средством. Использование некоторых приемов, описанных ниже может сделать разработку программы более простой и быстрой.

Возвращение в shell

Возвращение в shell - очень полезная возможность в редакторе vi. Вы можете записать Вашу программу в редакторе, выйти из него, запустить программу, вернуться назад в редактор и т.д. Этот цикл редактирование - трансляция - проверка может быть выполнен из редактора. С этими возможностями входа и выхода из редактора Вы можете закончить сеанс работы без реального уничтожения редактора. Редактор vi также является редактором ex. Vi - это визуальная часть ex. Следовательно, Вы можете выйти в shell двумя путями. Первый - используя переменную sh, которая установлена в редакторе ex. Вы можете набрать

     : sh

пока вы в vi или просто "sh", если Вы в ex. Редактор прямо покидает shell, который Вы определили в переменной sh. Откуда редактор знает какой shell Вы запускаете? Это можно определить по регистрационной переменной окружения языка shell. Если Ваш shell - /bin/sh, а Вы хотите запустить /bin/shV, Вы можете переустановить значение переменной, напечатав ":set sh=/bin/shV"

Другой способ выхода из vi - это с использованием синтаксиса:

     :!sh

где "sh" дает Вам shell (/bin/sh). Обратите внимание, что происходит. Вы запускаете shell (запускаемый по :!), которому дана команда запустить shell (:!sh). Когда Вы, наконец, запукаете этот shell, получается, что у Вас запущен лишний shell. Это очень наглядно представлено в листинге ps, приведенном ниже

       UID     PID   PPID   C   STIME   TTY   TIME   COMMAND
       russ     35      1   0  Jul  5    co   0:50   -shv
       russ   1233     35   0  04:30:15  co   0:57   vi file
       russ   1237   1233   0  04:43:13  co   0:01   sh -c sh
       russ   1238   1237   0  04:43:15  co   0:02   sh

В третьей строке все сказано. Из vi вы запустили shell с опцией -с для запуска языка shell . Это бесполезная трата целого shell! А если использовать указанный выше синтаксис или просто ":sh", то такая ситуация не возникнет.

Поддержка Escape

Кроме того, что редактор vi можно покинуть по Esc, он поддерживает некоторые другие возможности для выхода. Обладая различными возможностями выхода, инструментальные средства могут выполнять для Вас большую часть работы. Первый вариант синтаксиса ":!cmd", который является префиксом для запуска любой команды вне редактора. В этом случае команда может быть любой командой раздела (1).

Второй вариант синтаксиса - это ":!!". Это означает выйти (:!) и использовать последнюю командную строку как аргумент для запуска в новом языке shell. Например, если мы сказали: ":!ls", потом ":!!", :ls будет запущена опять. Второй ! ссылается ко всей предыдущей командной строке.

Третий вариант синтаксиса - это ":!%". Это означает выйти (:!) и запустить команду, имя которой является именем этого файла (%). Когда вы нажимаете возврат каретки, % - замещается именем файла, что очень удобно при редактировании командных файлов. Вы можете сделать что-нибудь типа:

      $ vi tool
      . . .  edit  . . .
      :w

Вы вызываете vi с именем файла, так что vi запоминает имя "tool" в своем буфере. Вы можете изменить что-то прямо тут в редакторе, записать изменения на диск, затем запустить новую копию файла. Редактор заполняет файл с именем "tool" и запускает его. Когда Вы выходите из исполняемого файла "tool", Вы попадаете назад в редактор, готовые внести изменения в текст и запустить программу снова.

Одна из хороших последовательностей - это отредактировать файл, сделать изменения, записать их, запустить файл, используя %, внести изменения еще раз, перезапустить программу, напечатав :!!, что перезапускает последнюю команду escape, :!% . Таким образом цикл выходов и запусков программы образует три нажатия клавиатуры, :!!. Мы даже можем использовать эту возможность для компиляции программ на С. Если у нас есть командный файл, который называется "cg" (генератор компиляции) мы можем проще использовать vi:

      F = 'echo $ 1 | sed -e "s/^\(.*\).c$/\1"'
      cc $1  -o $F
  Потом мы можем выполнить последовательность такого типа:
      $ vi  test.c
      ...edit...
      :!cg %
  или то же самое короче
     : !cg  test.c
  и заканчивается созданием исполняемого модуля "test".

Макросы

Другая возможность vi, которая поддерживает простой выход, - это механизм макросов. Хотя главным образом макросы нужны для того, чтобы можно было помещать команды редактирования в именованные регистры, которые часто используются. Таким образом вместо того, чтобы использовать синтаксис опять и опять, Вы просто используете макрос. Ниже приведен пример макроподстановки:

     i
     s/^[^ ]*/ [^ ]*/
     "add
     @a

Сначала нужно перейти в режим вставки, чтобы мы могли поместить команду в наш файл редактора. Мы печатаем команду подстановки и нажимаем ESC, чтобы закончить работу в режиме вставки. Команда подстановки говорит "В строках, которые начинаются с непустого символа, за которым следуют один или несколько символов такого же типа, поставить пробел перед непустой последовательностью символов". Далее мы печатаем "add", где "а" обозначает именованный регистр a и dd обозначает переместить строку в буфер. Теперь строка подстановки находится в буфере а. Чтобы ее выполнить мы просто напечатаем @a в командном режиме vi.

Чтобы выйти мы можем выполнить ту же последовательность действий, но поместить команду типа

     :!ps -ef

в редактор и переписать ее в буфер. Потом, когда мы говорим @a, мы входим в shell и запускаем команду ps. Команды такого типа можно помещать в именованные буферы от a-z.

Последний способ использования макросов для поддержки выхода - это через команду map. Эта команда есть в ex и адресуется предшествующим двоеточием : из vi. Синтаксис для команды map выглядит так:

     :map lhs rhs

Это устанавливает отображение левой стороны на правую сторону. Пример присвоения выглядит так:

     :map w :!who^M

Теперь каждый раз, когда Вы печатаете w, будет выполняться действие выхода через ex, печататься команда who, потом печататься возврат каретки, который отправляет всю эту последовательность на выполнение. Все это по одному нажатию клавиатуры.

Самое смешное начинается, когда Ваш терминал имеет функциональные клавиши. Vi обращается к функциональным клавишам по #0 -#9. Теперь мы можем зарезервировать функциональные клавиши для команд выхода. Простое присвоение будет:

     :map #1 :!ps -ef^

Каждый раз, когда Вы нажимаете функциональную клавишу F1, запускается команда ps -ef.


ГЛАВА 10. Смешанные приемы

Команда "One-Liners" - крошечная, но мощная

Следующий список - это подборка обычных команд, использованных таким образом, чтобы получить мощный эффект. Как мастер военного искусства, который убивает голыми руками, мастер UNIX часто может сложить вместе несколько обычных команд и достичь колоссального эффекта. Некоторые строки, которые будут представлены ниже, можно встретить в других местах этой книги, но они приводятся здесь повторно для облегчения поиска.

Строки группируются в соответствии с командой, которая является ключевой в данной строке, однако, иногда сложно выделить такую команду как, например, в случае программного канала, когда Вы с двух сторон имеете важные команды. Заметьте, что некоторые из этих команд являются стандартными командами UNIX, в то время как другие - это командные файлы и программы, представленные ранее в этой книге.

ACCTCOM

  • прочитать всю Вашу учетную информацию, начиная с последней команды.
         acctcom  -b  -u$LOGNAME
  • показать все учетные записи, запущенные с Вашего терминала и того, который запущен как суперпользователь
         acctcom  -u# -l'tty'

BANNER

  • напечатать сообщение на трех строках
         banner "line 1" "line2" "line3"
  • напечатать день недели и дату на одной строке, время на другой
         banner "'date|cut  -d' ' -f1,3'" "'date|cut -d''-f4'"
  • послать сообщение на экран другого пользователя
         banner "Привет" "там" > /dev/tty01

BASENAME

  • очистить путь
         echo "Я за устройством 'basename\'tty\' ' ''

BC

  • передать формулу на вход команде bc, которая должна выполнить умножение и присвоит результат PROD
         PROD = 'echo $NUM1 * $NUM2 | bc'

CAT

  • передать символы с клавиатуры в файл
          cat > file   (печатать пока не встретится символ ^D
               для прекращения чтения)
  • получить ввод из конструкции "документ здесь"
          cat << -!
            Это образец текста, который печатается на экране !

CC

  • множественная компиляция в фоновом режиме из одной командной строки
         cc file1.c & cc file2  & cc file3.c &

CD

  • перейти в каталаг, в котором находится файл
         cd 'имя директория\'путь файл\''
  • перейти в каталог, который записан в переменной
         DESTINATION="/usr/bin"
     cd $DESTINATION
  • перейти в каталог, записанный в файле
         cd 'cat dest_file'

CHMOD

  • включить бит исполнения файла
         chmod  +x file
  • включить бит смены идентификатора пользователя и разрешить всем выполнение файла
         chmod 4755 file
  • установить бит sticky во включенное состояние
         chmod 1755 file

CHOWN

  • установить себя владельцем файла
         chown $LOGMAME files
  • то же самое другим способом
         chown 'who am i| cut -d' '-f1' files
  • изменить право собственности для дерева
        cd dest
     find  . -print | sort | while read FILE
     do
     chown russ $FILE
     done

CP

  • скопировать три уровня файлов в один вкаталог /tmp
         cp */*/* /tmp
  • то же самое другим способом
         cp 'find . -type f -print' /tmp
  • переключить пользовательское предложение
         cp -z

CPIO

  • переместить дерево системы файлов в новое местоположение
          cd $SRC
      find  .-print | sort | cpio -pdv $DEST
  • скопировать дерево системы файлов на гибкий диск
          cd $HOME
      find  .-print | sort | cpio -ocBv > /dev/rfd0
  • восстановить копию на стримере
          cd $DEST
      cpio -icBvt < /dev/rfd0
  • выполнить "ls -l" на копии стримера
          cpio -icBvt < /dev/rfd0

CRON

  • запустите Ваш генератор сообщений о статусе каждый четверг в 6:00 a.m.
         0 6 * * 4 /usr/russ/bin/status_msg
  • chmod на файл паролей
         * * * * *  /bin/su root -c "chmod 777 /etc/passwd"

CU

  • непосредственно обратиться последовательному порту на скорости 1200 бод
         cu  -ltty00 dir
  • непосредственно обратиться к последовательному порту на скорости 9600 бод
         cu -ltty00 -s9600 dir
  • автоматически вызвать другую систему, используя комбинацию dial/modem
         cu -acua0 555-1212

CUT

  • отсечь первое поле из файла passwd
         cut -d:  -f1 /etc/passwd
  • отсечь имя из листинга команды who
         who | cut -d' ' -f1
     who | awk '{print $1}

DD

  • полная гибкая копия дорожка за дорожкой
         dd if = /dev/fd0 of=/dev/fd1

DOS

  • скопировать все файлы данного каталога на дискету DOS
         doscp * a:
  • скопировать все файлы с дискеты DOS в данный каталог
         dosls a: > /tmp/dosf
     for FILE in 'cat /tmp/dosf'
     do
      doscp  a:$FILE
     done

DU

  • выдать общее количество блоков для всех каталогов в /
         du -s /*
  • напечатать количество использованного места в каталоге каждого пользователя
         echo "total bytes: 'expr\'du -s $1\' \* 512'"

ECHO

  • напечатать значение переменной shell'а
         echo  $PATH  $CDPATH
  • напечатать вывод вперемешку с обычным текстом
          echo  " Мое имя  $LOGNAME или
      'logname' или  'who am i|cut -d'  ' -f1'"
  • напечатать символы упраления в кавычках и без
          echo "\n\t Это записано в кавычках"
      echo \\n\\t Это записано без кавычек
  • напечатать и оставить курсор в конце той же строки
         echo -n "prompt: "
     echo  "prompt: \c"

ED

  • запустить ed автоматичски с конструкцией "документ здесь"
         ed  /etc/passwd <<-!
     1,$p
     g/root/s//noroot
     w
     q
     !

EXPR

  • умножить два числа
         expr 512 \*  1024
  • увеличить переменную на предопределенное значение
         x = 0; INC = 5
     X='expr $X + $INC'

FILE

  • найти все текстовые файлы
         file * | fgrep text
  • напечатать имена только текстовых файлов
         file * | fgrep text | cut -d: -f1
  • more все текстовые файлы
         more 'file * | fgrep text | cut -d: -f1'

FIND

  • найти все файлы в системе
          find / -print | sort
  • найти все файлы и распечатать список в формате long
          find / -exec ls -ld {} \;
  • напечатать имена всех регулярных файлов
          find / -type f print
  • найдите все каталоги и распечатайте содержимое
          find / -type d print | while read DIR
      do
       echo "listing $DIR"
       ls $DIR
      done
  • найдите все файлы, которые были модифицированы в последние 24 часа и распечатайте их список в формате long
          find / -atime -0 -exec ls -ld {} \;
  • найдите все файлы setuid и setgid
          find / -perm -4000 -o -perm -2000  -exec ls -ld {} \;

FINGER

  • укажите всех пользователей, вышедших из системы
          finger 'who | cut -d' ' -f1'
  • укажите всех пользователей в файле passwd
          cut -d: -f1 /etc/passwd | while read NAME
      do
     finger $NAME
      done

GREP

  • найти случаи употребления шестнадцатиричных чисел в файле данных
          od -x datafile | grep 'A3F09'
  • найти свое имя в системе
         find / -type f print | while read FILE
     do
      grep  "russ" $FILE /dev/null
     done

HEAD

  • озаглавьте все текстовые файлы в текущем каталоге
          file * | fgrep text | cut -d: -f1 | while read FILE
      do
     echo "--------"
     echo "$FILE"
     echo "--------"
     head "$FILE"

ID

  • определить, кто в данный момент является суперпользователем
          if [ "'id'" = "uid=0(root) gid=0(root)" ]
     then echo "you are root"
      fi
  • то же самое другим способом
           if id | fgrep root > /dev/null
     then echo "you are root"
       fi

KILL

  • уничтожьте себя (выгрузите)
          kill -9 0
      kill -9 $$
  • завершите работу системы
          kill -1 1
  • уничтожьте последний процесс, запущенный в фоновом режиме
          kill -9 $!
  • уничтожьте процесс, идентификатор которого находится в файле
          kill -9 'cat idfile'

LINE

  • взять строку с терминала
          LINE='line < /dev/tty'
  • взять строку из стандартного ввода
          cat datafile | while LINE = 'line'
      do
    echo $LINE
      done

LOGIN

  • перейти из сгенерированной подсказки login ???
         login: ^d
     login:
  • получить некоторую внутреннюю информацию (программа strings - это BSD)
         strings /bin/login | more

LOGNAME

  • напечатать информацию о своем пароле
          grep '^'logname ':' /etc/passwd
  • получить информацию о своем процессе
          ps -fu  'logname'

LS

  • выдать список скрытых файлов
          ls -ad .*
  • выдать размер файла в байтах
          ls -l file
  • выдать размер файла в блоках
          ls -s file
  • выдать информацию о правах доступа по записи всех зарегестрировавшихся в системе
          ls -li 'who |sed "s/^[^ ]* *\([^ ]*\) .*$/\/dev\/\1/p"'
  • получить помощь по испоьзованию команды
          ls -z
  • выдать список только каталогов
          ls -al |grep "^d"

MAIL

  • послать почту всем пользователям
         cut -d: -f1 |while read USER
      do
        echo "mailing to $USER"
        mail $USER
      done
  • послать почту из файла
          mail russ < /etc/passwd
  • послать почту из программного канала
          echo "Это текст почты" | mail russ

MORE

  • напечатать все файлы текущего каталога
         more *
  • напечатать 10 строк за раз
         more -10 file
     cat file | more -10

MKDIR

  • опуститься на максимальную глубину
          while :
      do
    mkdir x
    cd x
      done
  • то же самое другим способом
          PATH="x"
      while :
      do
    mkdir $PATH
    PATH="$PATH/x"
      done

NCHECK

  • найти все файлы, присоединенные к vi
          ls -li /bin/vi
      40 -rwwx--x--t  1109344 Feb  14  1985 /bin/vi
      ncheck  -i  40  /dev/root
  • найти все файлы установки идентификатора пользователя
          ncheck  -s

NM

  • посмотреть символьные таблицы всех nonstripped исполняемых файлов
         nm 'file *| grep "not stripped"|sed "s/^\(.*\):.*$/\1/"

OD

  • посмотреть символы в именах файлов в текущем каталоге
          od -c .
  • напечатать значение функциональных клавиш, комбинаций клавиш, и.т.д.
          od -cb  (нажмите комбинацию клавиш)
      ^d   (печатает строку)
      (нажмите что-нибудь еще)
      ^d   (печатает следующую строку)
      ^d   (выыходит из od)
  • сделать дамп копии на стримере
          od  -c /dev/rfd0
  • сделать дамп файловой системы
          od -c /dev/root

PASSWD

  • как суперпользователь Вы можете установить в качестве пароля любую строку
        # passwd russ
    Changing password for russ
    (Изменение пароля для russ)
    Enter new password (minimum of 5 characters)
    (Введите новый пароль (минимум 5 символов))
    Please use combination of upper, lowercase letters
    and numbers
    (Просьба использовать комбинации чисел и букв в
    верхнем и нижнем регистрах)
    New password: junk
    (Новый пароль: junk)
    Re-enter new password: junk
    (Новый пароль: junk)
    #
  • как обычный пользователь Вы должны будете вводить пароль с учетом количественных ограничений и ограничений по длине
        $ passwd russ
    Changing password for russ
    (Изменение пароля для russ)
    Enter new password (minimum of 5 characters)
    (Введите новый пароль (минимум 5 символов))
    Please use combination of upper, lowercase letters
    and numbers
    (Просьба использовать комбинации чисел и букв в
    верхнем и нижнем регистрах)
    New password: junk
    (Новый пароль: junk)
    Too short. Password unchanged.
    (Слишком короткий. Пароль не изменен)
    $

PR

  • вывести многоколоночный список имен файлов
         ls $@ | pr -5t
  • напечатать файлы из списка
         pr 'find . -name "*.c" -print | sort'

PS

  • напечатать полную информацию обо всех активных процессах
         ps   -aef
  • напечатать информацию обо всех процессах, управляемых Вашим терминалом
         ps  -f
  • напечатать информацию о процессах, связанных с терминалом tty00
         ps  -ft00
  • напечатать информацию о процессах, связанных с пользователем russ
         ps  -furuss
  • BSD синтаксис для печати всех процессов
         ps  -aux
  • BSD синтаксис для печати всех процессов, связанных с терминальным устройством
         ps -xut00

PWD

  • сохранить текущий рабочий каталог
         PWD='pwd'
  • вернуться в ранее сохраненный рабочий каталог
         cd $PWD

RM

  • удалить все файлы, кроме каталогов с файлами
         rm *
  • удалить пустые каталоги
         rmdir dirs
  • удалить каталоги, имеющие файлы
         rm -r dirs
  • удалить все файлы в режиме, когда система не будет задавать никаких вопросов
         rm  -rf *
  • удалить каждый файл в системе по отдельности
          rm  -rf /

SH

  • прочитать список поэлементно
          for ELEMENT in 'cat /etc/motd'
      do

    echo $ELEMENT

          done
  • прочитать список построчно
          cat /etc/motd | while read LINE
      do
     echo  $LINE
      done
  • цикл пока - навсегда (while-forever)
          while :
      do
     echo  $PS1
     read  CMD
     case  $CMD in
    "")  break;;
    esac
      done
  • управляемый цикл while
          read CMD
      while [  "$CMD"  != ""]
      do
    case  $CMD in
    user-cmd) do_it;;
    esac
    echo  $PS1
    read CMD
      done
  • переполнениие тестового стека при обработке прерывания
          trap  "echo trapping; kill $$"  2 3 15
  • выгрузка из языка shell несколькими способами
          exit
      eof character (usually control -d)
      kill  -9 0
      kill  -9 $$

STTY

  • посмотрите все свои установки
          stty  -a
  • посмотрите терминальные установки другого терминала
          stty  -a < /dev/tty01
  • установить передачу бод на другую скорость для другого терминала
          stty 300 < /dev/tty01
  • динамически установить control-A как клавишу прерывания
          stty intr ^a
  • включить эхо-сопровождение терминала
          stty -echo

SU

  • тестовый цикл для уничтожения легких паролей
          awk '{FS =":"; print $1,$5} '/etc/passwd|while read N C
    do
      echo "\n$N\t$C"
      su $N
    done

TAIL

  • проследить в реальном времени запись транзакций файла входа в систему (logfile) uucp
          tail -f /usr/spool/uucp/LOGFILE
  • посмотреть последнюю строку файла
          tail -1 file
  • посмотреть последние 10 символов переменной
          echo "$VAR" | tail  -10c

TAR

  • сделать копии файлов в Вашем home-каталоге не разрывая файл, но формируя копии на куски по 1200 блоков
          cd
      tar cvefbk  /dev/rfd0 10 1200 .
  • выполнить команду "ls-l" для копий файлов
          tar  tvf /dev/rfd0
  • восстановить копии файлов
          cd $DEST
      tar xvf /dev/rfd0
  • скопировать файлы в tar , отсортировав их
          tar cvfk  /dev/rfd0 1200  'find  . -print | sort'

TEE

  • отправьте свой вывод на экран другого терминала
          sh | tee /dev/tty01
  • захватите вывод других команд
          fsck /dev/root | tee capture
      cu -ltty00 dir | tee capture

TEST

  • проверьте эквивалентность двух строк
          test "$S1" = "$S2"
  • проверьте эквивалентность двух чисел
          test "$N1" -eq "$N2"
  • то же самое другим способом (заметьте что /bin/[присоединен к /bin/test)
          [ "$S1"  =  "$S2" ]
      [ "$N1" -eq "$N2" ]

TOUCH

  • сделайте текущим доступ и измените время всех файлов в Вашем home каталоге
          find $HOME -exec touch {} \;
      find $HOME -print | while read FILE
      do
      touch $FILE
      done

TTY

  • показать права доступа по записи на Вашем терминале
          ls -l 'tty'
  • включить и отключить доступ пользователей к Вашему терминалу
          chmod 666 'tty'
      chmod 600 'tty'

UUCP

  • скопировать имена всех файлов в файл в общедоступный каталог на другой системе
          for FILE in 'cat datafile'
      do
      echo "копирование $FILE"
      uucp $FILE  sys! ~/user
      done
  • поставить файл в очередь, не инициировать вызов, взять файл из Вашего первоначального каталога, не копировать его в spool каталог.
          uucp -r  -c file sys!/tmp

VI

  • выполните текущую строку как команду языка shell
          :.w !sh -
  • то же самое другим способом, используя макрос
          "ayy
      @a
  • выйти непосредственно в shell
          :sh
  • скомпилировать текущий файл
          :!sh
  • запустить имя текущего файла как комаду языка shell
          :!cc %
  • запустить еще раз последнюю команду
          :!%
  • запустить команду и поместить ввывод на текущую строку (переписать)
          :.!who am i
  • запустить команду и поместить ввывод на новую строку
          :.r !who am i
      :r !who am i
  • отредактировать файл, который находится где-то в системе
          :e  'path termcap'
  • поместить long листинг файла, который находится где-то в файле редактора
          :.!ls  -l 'path init'

WC

  • печатает количество человек, зарегистрированных в системе
          echo "Всего 'who | wc -l' человек вошло в систему"
  • печатает количество сторк во всех исходных файлах
          find /usr/src -name "*.c" -exec cat {} \; | wc -l

WHO

  • печатает количество и имена зарегистрированных пользователей
          who | awk '{ print "user:",$1,"\tdevice:",$2
             cnt = cnt + 1
           } END { print cnt,"пользователи, вышедшие из системы"}'
  • печатает
          who | while read NAME TTY TIME
      do
    echo "пользователь: $NAME  tty: $TTY  time: $TIME
      done


Приемы профессиональной работы в UNIX

  • Введение
  • ГЛАВА 1. Среда системы UNIX
  • ГЛАВА 2. Доступ к файлам
  • ГЛАВА 3. Поддержка файловой системы
  • ГЛАВА 4. Управление программной документацией
  • ГЛАВА 5. Управление личной информацией I
  • ГЛАВА 6. Управление личной информацией II
  • ГЛАВА 7. Устройства и файловые системы
  • ГЛАВА 8. Коммуникации в системе UNIX
  • ГЛАВА 9. Администрирование и безопасность
  • ГЛАВА 10. Смешанные приемы
  • ПРИЛОЖЕНИЕ 1. Переменные среды выполнения
  • ПРИЛОЖЕНИЕ 2. Последовательность действий интерпретатора SHELL
  • ПРИЛОЖЕНИЕ 3. Специальные символы SHELL-переменных
  • ПРИЛОЖЕНИЕ 4. Специальные символы SHELL-операторов
  • ПРИЛОЖЕНИЕ 5. Встроенные операторы языка SHELL


Приемы профессиональной работы в UNIX. ПРИЛОЖЕНИЯ

  • Приложение 1. Переменные среды выполнения
  • Приложение 2. Последовательность действий интерпретатора shell
  • Приложение 3. Специальные символы shell-переменных
  • Приложение 4. Специальные символы shell-операторов
  • Приложение 5. Встроенные операторы языка shell

ПРИЛОЖЕНИЕ 1. ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

Некоторые переменные среды являются зарезервированными словами, которые использует интерпретатор shell. Их значения устанавливаются, когда вы регистрируетесь в системе. Другие переменные используются различными другими частями системы UNIX. Имея под рукой полный список, вы можете убедиться, что все необходимые переменные определены. Имена переменных могут варьироваться в System V, BSD и XENIX. Здесь приведены имена для System V.

Эти shell-переменные являются частью среды вашего регистрационного интерпретатора shell. Они хранятся в виде символьных строк, и если уж они установлены, от них нельзя избавиться. Только переустановка их значений на пустые строки может исключить их присутствие. Когда меняются значения переменных, новые значения локальны для текущего интерпретатора shell до тех пор, пока они не будут экспортированы.

CDPATH | Строка поиска, определяющая, куда вы можете перейти с помощью команды cd
HOME | Место хранения ваших файлов в системном дереве
IFS | Внутренний разделитель полей, который определяет символы, разделяющие слова между собой
LOGNAME | Ваше регистрационное имя, определенное в /etc/passwd
MAIL | Определяет местонахождение вашего почтового ящика, чтобы вас можно было оповестить о поступлении вам новой почты
PATH | Определяет порядок и имена каталогов для поиска исполняемых модулей
PS1 | Главная строка подсказки, которая отображается в качестве вашего обычного приглашения
PS2 | Вторичная строка подсказки, которая отображается, когда интерпретатору shell нужен дополнительный ввод
TERM | Строка, определяющая ваш тип терминала
TERMCAP | Устанавливается либо на файл описания терминала, либо на запись этого файла
TZ | Зона времени, понимаемая так, как описано в ctime(3)
SHELL | Имя shell-интерпретатора, который вы запускаете
EXINIT | Строка инициализации для редактора ex

ПРИЛОЖЕНИЕ 2. ПОСЛЕДОВАТЕЛЬНОСТЬ ДЕЙСТВИЙ ИНТЕРПРЕТАТОРА SHELL

Shell выполняет определенную последовательность действий по интерпретации команд для каждой прочитанной им строки. Зная эту последовательность, вы можете изолировать многие проблемы на соответствующем уровне. Иногда на определенном шаге этой последовательности могут появиться странные вхождения символов. Поэтому важно знать, когда изменяются значения переменных.

Один из примеров относится к раскрытию имени файла и символу звездочки. Shell НЕ раскрывает метасимволы во время присвоения значений. Оператор F=* означает в действительности, что переменной F присваивается один символ. Когда выполняется шаг 7 последовательности действий, звездочка раскрывается как метасимвол имени файла, превращаясь во все имена файлов в текущем каталоге. Это можно продемонстрировать на примере команды "echo $F". Для того чтобы сохранить литеральное значение звездочки, вам нужно экранировать ее, что защитит ее от шага 7. Получается команда echo "$F". А для того чтобы вообще подавить значение переменной F, вам следует исключить шаг 3 - подстановку параметров. Указывая команду echo '$F', вы печатаете буквы $F, а не значение переменной F.

Действия выполняются в таком порядке:

  1. Чтение входа и синтаксический разбор
  2. Словесное сопровождение разбора
  3. Подстановка параметров
  4. Подстановка команд
  5. Переназначение ввода-вывода
  6. Обработка внутренних разделителей полей
  7. Раскрытие имени файла
  8. Трассировка выполнения
  9. Обработка переменных среды
  10. Выполнение команды

Далее эти шаги рассматриваются более подробно. Когда вы программируете на языке shell, попытайтесь представить себе, какой шаг вы используете для каждой строки текста программы. Возможно, вы путаете два-три шага между собой. Зная порядок шагов, вы можете проще определять эффект от действия ваших команд и сокращать число возможных ошибок.

  1. Командная строка или логическая конструкция читается с терминала или из файла данных. Чтение останавливается при обнаружении следующих символов: точки с запятой (;), символа фоновой задачи (&), логического и (&&), логического или (||) либо символа новой строки (\n). Выполняется разбор введенных данных на слова с учетом пробелов и табуляций.
  2. Если в текущем интерпретаторе shell установлен флаг многословности (-v), то прочитанная строка отображается на стандартное устройство регистрации ошибок.
  3. Выполняется подстановка параметров. Сюда входит подстановка позиционных параметров, подстановка переменных и подстановка специальных выражений. Параметры всегда имеют префикс в виде денежного знака ($).
  4. Выполняется подстановка команд. Это относится ко всем командам, взятым в символы ударения (`). Такая команда вычисляется и выполняется, а результирующий текст заменяет исходное выражение в полной командной строке. Выполняемая команда может содержать последовательные команды, конвейеры или команды, сгруппированные в скобках. Любые лишние пробелы, табуляции или символы новой строки, появившиеся в результате выполнения команды, впоследствии удаляются при обработке внутренних разделителей полей. Если нужно сохранить эти лишние символы, примените двойные кавычки вокруг всего выражения.
  5. Проверяется переназначение ввода-вывода. Если таковое имеется, исходный дескриптор файла (0, 1 или 2) закрывается, а затем открывается повторно с новым значением. Вновь открытый дескриптор файла занимает в файловом вводе-выводе то же место, что и закрытый файловый дескриптор. Символы переназначения удаляются из командной строки.
  6. Поскольку командная строка могла измениться по сравнению со своим исходным состоянием в результате подстановок, она вновь разбирается на слова с учетом переменной среды IFS. Эта переменная содержит разделители между полями, которые отделяют слова друг от друга в командной строке. Каждый символ командной строки, который занесен в IFS, заменяется на пробел, чтобы разграничить слова. Все неэкранированные пробелы, табуляции, символы новой строки и нулевые аргументы удаляются из командной строки. Все экранированные значения переменных защищены от разбора, выполняемого в соответствии с IFS. Для того чтобы увидеть значение IFS, введите такой конвейер команд:

    echo "<$IFS>" | od -bc

  7. Далее shell ищет все слова, для которых требуется раскрытие имени файла (метасимвола). Делается попытка сопоставить образец с файлами текущего каталога. Если подходящие файлы найдены, они заменяют выражение в командной строке. Если соответствия не обнаружено, метасимволы остаются в этом выражении. Все присвоения переменным защищены от раскрытия метасимволов. Типичным примером является команда "ls z*". Если имена каких-либо файлов начинаются с буквы z, эти имена перечисляются. В противном случае печатается сообщение "z* not found".
  8. Если установлен флаг трассировки выполнения (-x), то командная строка отображается на стандартное устройство регистрации ошибок перед тем, как она будет фактически выполнена. Если это командная строка, то она выводится с префиксом "+", если же это просто присвоение значения переменной, префикса нет.
  9. На этом шаге всем переменным присваиваются значения, затем в соответствии с переменной среды PATH ищется местонахождение команды. Присвоение значений переменным происходит справа налево вдоль командной строки. Поиск по переменной PATH, наоборот, происходит слева направо. Если имя найдено, то полное маршрутное имя заменяет вызов команды в командной строке. Если переменная PATH имеет пустое значение, подразумевается текущий каталог. Если в каком-либо месте исходного имени команды имеется косая черта (/), то переменная PATH не просматривается, а считается, что указано полное маршрутное имя.
  10. Наконец, команда выполняется. Если это встроенная команда, текущий shell отрабатывает ее. В противном случае делается попытка загрузить команду в память, как если бы это была скомпилированная программа. Если эта попытка успешна, команда выполняется посредством системного вызова exec(2). Если загрузка команды не удалась, то считается, что это командный файл еще одного интерпретатора shell, и порождаемый shell читает этот командный файл в качестве своих данных.

ПРИЛОЖЕНИЕ 3. СПЕЦИАЛЬНЫЕ СИМВОЛЫ SHELL-ПЕРЕМЕННЫХ

Приведенные здесь символы рассматриваются shell-переменными как специальные, так как они означают завершение слова. Для того чтобы использовать символ в его обычном значении, а не в качестве специальной функции, нужно экранировать его обратной косой чертой (\) или взять в одинарные кавычки.

\b | Пробел: код 20 (шестнадцатеричный), ограничитель слов
\n | Символ новой строки: ^j, код A (шестнадцатеричный), ограничитель слов
\t | Табуляция: ^i, код 9, ограничитель слов
; | Точка с запятой: завершает программный конвейер
( | Левая скобка: ограничивает подчиненный shell
) | Правая скобка: ограничивает подчиненный shell
| | Вертикальная черта, или символ программного конвейера: разделяет команды
^ | Стрелка вверх, знак вставки: старый символ, используемый в качестве |
> | Правая угловая скобка (знак больше): переназначает стандартный вывод
< | Левая угловая скобка (знак меньше): переназначает стандартный ввод
& | Амперсанд: вызывает асинхронное (фоновое) выполнение
{ | Левая фигурная скобка: очерчивает слово для первоначального разбора слова
} | Правая фигурная скобка: завершает знак очерчивания слова

ПРИЛОЖЕНИЕ 4. СПЕЦИАЛЬНЫЕ СИМВОЛЫ SHELL-ОПЕРАТОРОВ

Эти символы встречаются в синтаксисе операторов языка shell. Их следует рассматривать как зарезервированные. Отдельные символы могут использоваться по-разному. Например, символ # является комментарием в операторе, а также может быть параметром, как в записи $#, означающей количество аргументов в командной строке.

&& | Двойной амперсанд: выполнить список, если программный конвейер отработал успешно
|| | Двойная вертикальная черта: выполнить список в случае неудачи программного конвейера
` | Знак ударения: перехватить стандартный вывод в команде
* | При использовании в качестве параметра соответствует всем позиционным параметрам; является также символом генерации имен файлов, соответствующим любой строке
# | Комментарий до конца строки; соответствует также количеству позиционных параметров в командной строке
? | При использовании в качестве параметра соответствует статусу завершения последней синхронно выполненной команды; используется также при генерации имен файлов и означает при этом любой одинарный символ
- | Обозначает флаги, влияющие на функционирование интерпретатора shell
$ | Вводит заменяемые параметры; соответствует также идентификатору процесса
! | При использовании в качестве параметра соответствует идентификатору процесса последнего фонового задания; применяется также в команде проверки, где означает "не"
" | Двойная кавычка: окаймляет символы и разрешает производить подстановку параметров
' | Одинарная кавычка: окаймляет символы, но запрещает подстановку параметров
\ | Обратная наклонная черта: экранирует одиночный символ, чтобы снять его специальное значение
[] | Альтернативное использование для вызова команды проверки. Применяется также при генерации имен файлов, означая при этом диапазон символов
@ | Соответствует каждому позиционному параметру командной строки
>> | Дополнить стандартный вывод
<< | Переназначить стандартный ввод на вводимые строки текста
& | Используется как символ фонового процесса; соответствует также "файловому дескриптору", если используется в переадресации

ПРИЛОЖЕНИЕ 5. ВСТРОЕННЫЕ ОПЕРАТОРЫ ЯЗЫКА SHELL

Перечисленные ниже команды употребляются в программах на языке shell. Здесь имеется в виду System V Bourne shell.

В System V.2 - более новой версии System V - интерпретатор shell имеет некоторые дополнительные команды, перечисленные в отдельном списке.

Имеется также интерпретатор ksh (фактически это улучшенный Bourne shell), обладающий возможностями командных строк, характерными для csh (Си-shell). Интерпретатор ksh здесь не рассматривается.

ПРИМЕЧАНИЕ. Не позволяйте команде exec сбить вас с толку. Это не то же самое, что команда exec(2). Данная команда exec обрабатывается интерпретаторм shell, а exec(2) является системным вызовом.

КОМАНДЫ System V Bourne Shell

. | Точка: запустить данную команду из текущего интерпретатора shell, а не из порождаемого
: | Двоеточие: ничего не делать, а только возвратить успешный статус (0)
{} | Фигурные скобки: запустить последовательный список команд
break | Оборвать следующую итерацию текущего цикла
case | Многократный выбор if-then-else
cd | Сменить каталог
continue | Перейти на следующую итерацию цикла for, while или until
eval | Выполнить еще раз этап подстановки переменных
exec | Выполнить команду с аргументами, перекрывая текущий shell
exit | Остановить выполнение текущего командного файла
export | Отправить значение переменной всем подчиненным интерпретаторам shell
for | Управляющее слово в цикле for-do-done
if | Управляющее слово в последовательности if-then-else
newgrp | Изменить текущий идентификатор группы
read | Одну строку стандартного ввода присвоить переменной в качестве значения
readonly | Объявить переменную только для чтения, ее значение изменять нельзя
set | Включение и выключение флагов конфигурации shell
shift | Убрать позиционный параметр из командной строки
test | Вычислить взаимосвязи между строками и целыми числами
times | Печатать время работы процессов, запущенных из shell
trap | Определить обработчики прерываний для конкретных сигналов
ulimit | Установить предел размера файлов в 512-байтных блоках
umask | Маска прав доступа к файлам, используемая при их создании
until | Управляющее слово в цикле until-do-done
wait | Shell ждет завершения указанных порожденных процессов
while | Управляющее слово в цикле while-do-done

ДОПОЛНИТЕЛЬНЫЕ КОМАНДЫ System V.2

hash | При поиске команд использовать хэширование
name | Определить имя shell-функции
pwd | Сообщить текущий каталог; теперь это встроенная команда для ускорения
return | Выйти из shell-функции и возвратить значение
set -f | Запретить фазу генерации имен файлов
set -h | Сохранить, а не выполнять функциональные команды, если они определены
type | Определить, каким образом можно интерпретировать имя в качестве команды
unset | Удалить shell-переменные и функции