загрузка...
Перескочить к меню

Linux и UNIX: программирование в shell. Руководство разработчика. (fb2)

- Linux и UNIX: программирование в shell. Руководство разработчика. 1790K, 400с. (скачать fb2) - Дэвид Тейнсли

Использовать online-читалку "Книгочей 0.2" (Не работает в Internet Explorer)


Настройки текста:




Введение

Настоящая книга посвящена shell–программированию, или, точнее, программированию в интерпретаторе Bourne shell.

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

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

При написании книги во внимание принимались шесть основных положений:

   • читатель должен быстро освоить инструментальные средства интерпретатора shell, а также изучить основы программирования на языке shell;

   • книга должна служить не только руководством, но и справочником;

   • для повышения производительности системы читателю следует научиться писать shell–сценарии;

   • необходимо, чтобы shell–сценарии имели четкую и понятную структуру}

   • главы, по возможности, должны быть самодостаточными и независимыми друг от друга, что упрощает их изучение;

   • читатель должен уметь решать ряд административных задач, в частности создавать CGI–сценарии.

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

Если вы приступили к изучению shell–программирования, очевидно, у вас есть на то веские причины. Основные причины изучения shell–программирования заключаются в следующем:

   • язык программирования интерпретатора shell вполне самодостаточен, он содержит все необходимые управляющие конструкции и удобен в применении;

   • открывается возможность быстрого создания сценариев;

   • сценарии позволяют автоматизировать рутинные операции, выполняемые обычно вручную.

Интерпретатор bourne shell

Bourne shell является стандартным интерпретатором команд, который входит в состав всех систем UNIX и совместим с интерпретатором bash в Linux. В книге, посвященной shell–программированию и не привязанной к конкретной операционной системе, в качестве общего знаменателя должен рассматриваться именно Bourne shell. Учтите, что существуют и другие интерпретаторы, такие как bash, Korn shell и С shell. Если в вашей системе установлен интерпретатор bash, сценарии из этой книги будут выполняться в нем, поскольку bash совместим с Bourne shell. Синтаксис интерпретатора Korn shell близок к синтаксису рассматриваемого здесь языка.

Если внимательно изучить сценарии системной инсталляции, то можно обнаружить, что более чем на 95 процентов они являются сценариями Bourne shell. Это объясняется тем, что создатели сценариев знали: они будут выполняться в любой системе UNIX и Linux.

Переносимость интерпретатора shell

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

Проблемы, связанные с первым фактором, решаются автоматически, если сценарии создаются для интерпретатора Bourne shell.

Что касается второго фактора, то для большинства shell–сценариев характерна следующая особенность: по крайней мере, 20 процентов (а то и больше) их рабочего времени затрачивается на выполнение таких shell–команд, как cp, mv, mkdir и др. Здесь и заключена проблема переносимости. Дело в том, что поставщики разных операционных систем оснащают эти команды различными наборами опций. В нашей книге используются сценарии общего характера и применяются только те опции и команды, которые присутствуют как в System V, так и в BSD. Если же имеются какие‑либо различия, то приводятся альтернативные команды, но это случается сравнительно редко.

Структура книги

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

Книга состоит из пяти частей, посвященных различным аспектам программирования на языке интерпретатора shell.

Интерпретатор shell

В первой части книги рассматриваются общие вопросы, связанные с работой в интерпретаторе shell.

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

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

главу 2, посвященную команде find .

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

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

Фильтрация текста

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

В отдельных главах описаны: grep — утилита поиска текстовых файлов (глава 8), awk — текстовый редактор, имеющий собственный язык сценариев (глава 9), sed — потоковый редактор, позволяющий выполнять быстрое редактирование (глава 10), и tr — утилита трансляции символов (глава 12). В главе 11 обсуждаются вопросы сортировки и объединения файлов, а также принципы работы с отдельными фрагментами текста.

Регистрация в системе

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

В главе 14 подробно описаны механизмы управления локальными переменными интерпретатора shell и глобальными переменными среды,

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

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

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

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

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

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

Завершает данную часть обзор встроенных shell–команд, которые до этого не рассматривались.

Совершенствование навыков по написанию сценариев

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

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

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

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

Если вы интересуетесь вопросами разработки HTML–страниц, прочитайте главу 29, в которой рассматривается создание CGI–сценариев, но не с помощью языка Perl, а с помощью интерпретатора Bourne shell.

В книгу включены два приложения: приложение А, содержащее таблицу кодов ASCII, и приложение Б, в котором представлен ряд интересных shell–команд.

Что нужно знать

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

Чтобы запускать CGI–сценарии из главы 29, нужно располагать инсталлированным Web–сервером и иметь право выполнять CGI–сценарии.

Соглашения, принятые в книге

В книге употребляются следующие обозначения и шрифтовые выделения:


[Ctrl+клавиша] Означает нажатие указанной клавиши одновременно с клавишей [Ctrl]; например, [Ctrl+O] — это указание одновременно нажать клавиши [Ctrl] и [О]
Courier New Применяется во всех листингах сценариев, а также для обо значения результатов выполнения команд
Courier New Используется для выделения командной строки

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

В Linux…

Они служат для того, чтобы кратко описать различия между синтаксисом рассматриваемой команды в BSD/Linux и System V.

Приводимые в книге сценарии протестированы в Linux (Red Hat) и в AIX. Не которые сценарии протестированы в системе Data Generals.

ЧАСТЬ 1

Интерпретатор shell

ГЛАВА 1

Файлы и права доступа к ним

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

В этой главе рассматриваются следующие темы:

   • права доступа к файлам к каталогам;

   • биты смены идентификаторов {SUID и SGID);

   • изменение владельца файла или каталога с помощью команд chown и chgrp;

   • команда umask;

   • символические ссылки.

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

Примечание:

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

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

   • Путем чтения, при этом содержимое файла отображается на экране.

   • Путем записи, при этом содержимое файла редактируется или удаляется.

   • Путем выполнения, если файл содержит сценарий интерпретатора shell либо является программой.

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

   • Владелец файла, создавший его.

   • Члены группы, к которой относится владелец файла.

   • Остальные пользователи.

1.1. Информация о файлах

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

   • раздел диска, где физически находится файл;

   • тип файла;

   • размер файла;

   • идентификатор владельца файла, а также тех, кому разрешен доступ к файлу;

   • индексный дескриптор;

   • дата и время последнего изменения файла

   • режим доступа к файлу.

Рассмотрим типичный список файлов, полученный в результате выполнения команды ls -l.

$ ls -l

total 4232

-rwxr‑xr‑x 1 root root 3756 Oct 14 04:44 dmesg
-r‑xr‑xr‑x 1 root root 12708 Oct 3 05:40 ps
-rwxr‑xr‑x 1 root root 5388 Aug 5 1998 pwd

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


total 4232 Суммарный размер файлов в каталоге
-rwxr‑xr‑x Режим доступа к файлу, отображаемый в виде строки из десяти
символов. Первый символ ('-') указывает на то, что текущая запись
относится к файлу (если на его месте стоит символ d, значит,
запись относится к каталогу). Остальные символы делятся на три
категории:
rwx — права владельца (первая триада);
r‑x — права группы (вторая триада);
r‑x — права остальных пользователей (последняя триада).
Символ r означает право чтения, символ w — право записи,
символ x — право выполнения, символ ' — ' -oтсутствие соответ-
ствующего права. Система разрешений подробно описана ниже
1 Количество жестких ссылок на файл
root Идентификатор владельца файла
root Идентификатор группы, в которую входит владелец файла
3756 Размер файла в байтах
Oct 14 04:44 Дата и время последнего изменения файла
dmesg Имя файла

1.2. Типы файлов

В начале строки режима может стоять не только символ ' — " или d, ведь в каталоге насчитывается до семи различных типов записей (табл. 1.1):

Таблица 1.1. Типы файлов


d Каталог
l Символическая ссылка (указатель на другой файл)
s Сокет
b Специальный блочный файл
с Специальный символьный файл
p Файл именованного канала
Обычный файл или, если выразиться точнее, ни один из файлов, перечисленных выше

1.3. Права доступа к файлам

Давайте создадим файл, используя команду touch:

$ touch myfile

Теперь выполним команду ls -1:

$ ls -1 myfile

-rw‑r--r-- 1 dave admin 0 Feb 19 22:05 myfile


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


Права владельца Права группы Права остальных пользователей
rw- r-- r--

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

Существует три вида разрешений:


r Право чтения данного файла
w Право записи/изменения данного файла
x Право выполнения данного файла, если он является сценарием или программой

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


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

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

Рассмотрим несколько дополнительных примеров (табл. 1.2).

Таблица 1.2. Примеры строк режима

Строка режима Результат


r-- --- --- Доступ к файлу разрешен только владельцу, который может читать содержимое файла, но не имеет права осуществлять запись в файл и выполнять его
r--r-- --- Доступ к файлу возможен только для чтения и разрешен владельцу и всем пользователям группы, в которую он входит
r--r--r-- Любой пользователь может получить доступ к файлу для чтения, остальные действия запрещены
rwx --- --- Владелец имеет полный доступ к файлу, для остальных пользователей файл недоступен
rwxr‑x --- Владелец имеет полный доступ к файлу; пользователи группы, в которую входит владелец, могут читать файл и запускать его на выполнение; для остальных пользователей файл недоступен
rwxr‑x r‑x Владелец имеет полный доступ к файлу; остальные пользователи могут читать файл и запускать его на выполнение
rw‑rw- --- Владелец и пользователи группы, в которую он входит, могут осуществлять чтение и запись файла; для остальных пользователей файл недоступен
rw‑rw‑r-- Владелец и пользователи группы, в которую он входит, могут осуществлять чтение и запись файла; остальным пользователям разрешено только чтение файла
rw‑rw‑rw- Все пользователи могут осуществлять чтение и запись файла

1.4. Изменение прав доступа к файлу

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

1.4.1. Символьный режим

Общий формат команды chmod для символьного режима таков:

chmod [кто] оператор [разрешения] файл

Значения параметра кто:

u Владелец

g Группа

o Другие пользователи

a Все (владелец, группа и другие пользователи)

Значения параметра оператор:

+ Добавление разрешения

— Удаление разрешения

= Установка заданного разрешения

Значения параметра разрешения:

r Право чтения

w Право записи

x Право выполнения

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

s Установка бита SUID или SG1D для владельца или группы

t Установка sticky–бита[1]

u Установка тех же прав, что и у владельца

g Установка тех же прав, что и у группы

o Установка тех же прав, что и у других пользователей

1.4.2. Примеры использования команды chmod

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


Команда Строка режима Результат
chmod a‑x myfile rw‑rw‑rw- Отмена всех разрешений на выполнение
chmod og‑w myfile rw‑r--r-- Отмена разрешений на запись для группы и других пользователей
chmod g+w myfile rw‑rw‑r-- Добавление разрешения на запись для группы
chmod u+x myfile rwxrw‑r-- Добавление разрешения на выполнение для владельца
chmod go+x myfile rwxrwxr‑x Добавление разрешения на выполнение для группы и других пользователей
chmod g=o myfile rwxr‑x r‑x Предоставление группе тех прав, которые уже установлены для других пользователей

1.4.3. Абсолютный режим

Общий формат команды chmod для абсолютного режима таков:

chmod [режим] файл

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

соответствует разрешению не чтение (1 — установлено, 0 -cнято), средний -pазрешению на запись, а младший -pазрешению на выполнение. Рассмотрим примеры:

Таблица 1.3. Восьмеричные значения Режима


Восьмеричное число Результат
400 Владелец имеет право чтения
200 Владелец имеет право записи
100 Владелец имеет право выполнения
040 Группа имеет право чтения
020 Группа имеет право записи
010 Группа имеет право выполнения
004 Другие пользователи имеют право чтения
002 Другие пользователи имеют право записи
001 Другие пользователи имеют право выполнения

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

Обратимся к примеру файла, который рассматривался ранее:

-rw‑r--r-- 1 dave admin 0 Feb 19 22:05 myfile

Его строка режима эквивалентна числу 644, сформированного таким образом:


право чтения и записи для владельца -400+200=600
+
право чтения для группы 040
+
право чтения для других пользователей 004
=644

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

Таблица 1.4. Определение режима доступа к файлу


Владелец Группа Другие пользователи
rwx rwx rwx
4 + 2 + 1 4+2+1 4+2+1

1.4.4. Дополнительные примеры использования команды chmod

Ниже приведен ряд Примеров, иллюстрирующих применение команды chmod в абсолютном режиме:


Команда Строка режима Результат
chmod 666 rw‑rw‑rw- Установка разрешений на чтение и запись для владельца, группы и других пользователей
chmod 644 rw‑r--r-- Установка разрешений на чтение и запись для владельца; группа и остальные пользователи получают право чтения
chmod rwxr--r-- Предоставление полного доступа владельцу; группа и другие пользователи имеют право чтения
744
chmod rw‑rw‑r-- Установка разрешений на чтение и запись для владельца и группы; другим пользователям предоставляется право чтения
664
chmod Предоставление полного доступа только владельцу; остальным пользователям доступ запрещен
700
chmod r--r--r-- Все пользователи получают разрешение только на чтение
444

В качестве примера изменим права доступа к файлу myfile:

-rw‑r–r-- 1 dave admin 0 Feb 19 22:05 myfile

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

$ chmod 740 myfile

$ ls -l myfile

-rwxr 1 dave admin 0 Feb 19 22:05 myfile

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

$ chmod 744 myfile

$ la -1 myfile

-rwxr--r-- 1 dave admin 0 Feb 19 22:05 myfile

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

$ chmod 664 *

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

$ chmod -R 664 *

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

1.5. Каталоги

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

Таблица 1.5. Праве доступа к каталогу


r w X
Возможность просмотра списка файлов в каталоге Возможность Возможность поиска файлов в каталоге и перехода в него
создания/удаления файлов в каталоге

Таблица 1.6. Примеры режимов доступа к каталогу


Строка режима Владелец Группа Другие пользователи
drwxrwxr‑x (775) Чтение, запись, Чтение, запись, Чтение, поиск
поиск поиск
drwxr‑xr- — (754) Чтение, запись, поиск Чтение, поиск Чтение
drwxr‑xr‑x (755) Чтение, запись, поиск Чтение, поиск Чтение, поиск

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

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

drwxr--r-- 1 louise admin 2390 Jul 23 09: 44 docs

а в нем — файл pay:

-rwxrwxrwx 1 louise admin 5567 Oct 3 05:40 pay

пользователь, который является членом группы admin и собирается редактировать файл pay, не сможет этого сделать из‑за отсутствия соответствующих прав доступа к каталогу- Этот файл доступен каждому, но поскольку бит поиска не установлен для группы admin, владеющей каталогом docs, ни один из пользователей группы не может получить доступ к каталогу. Если предпринимается попытка доступа, отображается сообщение "Permission denied" (доступ не разрешен).

1.6. Биты смены идентификаторов (SUID и SGID)

Биты SUID (Set User ID — установить идентификатор пользователя) и SGID (Set Group ID — установить идентификатор группы) были предметом жарких споров на протяжении многих лет. В некоторых системах установка этих битов не допускается либо они полностью игнорируются, даже если установлены. Это связано с тем, что при использовании данных битов возникает дополнительная угроза безопасности системы.

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

1.6.1. Для чего нужны биты SUID и SGID?

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

Существует несколько системных команд UNIX, для которых установлен бит SUID или SGID. Чтобы найти эти команды, перейдите в каталог /bin или /sbin и введите:

$ ls -l | grep '^…s'

Вы получите список команд с установленным битом SUID.

$ ls -1 | grep '^…s..s'

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

1.6.2. Установка битов SUID и SGID

Чтобы установить бит SUID, вставьте цифру 4 перед числом, задающим режим доступа. Биту SGID соответствует цифра 2. Если одновременно устанавливаются оба бита, следует ввести цифру 6 (4 + 2).

В строке режима установленные биты SUID и SGID обозначаются символом s, который помещается на место символа x в первую или вторую триаду соответственно. Помните, что сам бит выполнения (х) также должен быть установлен. Если, например, вы хотите для какой‑либо программы установить бит SGID, убедитесь в том, что группа обладает правом выполнения этой программы.

Примечание:

Команда chmod не запрещает вам установить бит SUID или SGID при отсутствии соответствующего разрешения на выполнение файла. В этом случае при выполнении команды ls -l в строке режима будет указан символ S, а не s. Система таким образом информирует вас о неправильной установке прав доступа к файлу.

Рассмотрим несколько примеров:


Команда Строка режима Результат
chmod 4755 rwsr‑xr‑x Для файла установлен бит SUID; владелец имеет право чтения, записи и выполнения; группа и другие пользователи имеют право чтения и выполнения
chmod 6711 rws--s — х Для файла установлены биты SUID и SGID; владелец имеет право чтения, записи и выполнения; группа и другие пользователи имеют право выполнения
chmod 2751 rwxr‑s--x Для файла установлен бит SGID; владелец имеет право чтения, записи и выполнения; группа имеет право чтения и выполнения; другие пользователи имеют право выполнения

Для установки битов SUID и SGID можно также воспользоваться символьными

операторами команды chmod. Вот как это делается:

chmod u+s имя_файла — SUID

chmod g+s имя_файла — SGID

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

1.7. Команды chown и chgrp

Создав файл, вы автоматически становитесь его владельцем, но можете передать право владения другому пользователю, у которого есть запись в файле /etc/passwd. Только системный администратор либо фактический владелец может передавать права на файл другому пользователю. Если вы отказались от владения файлом, для того чтобы восстановить свои права на него, вам придется обратиться к системному администратору.

Для передачи прав владельца предназначена команда chown. Команда chgrp задает группу, которой принадлежит файл. Общий формат этих команд таков:

chown владелец файл chgrp владелец файл

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

1.7.1. Пример использования команды chown

Вот как можно поменять владельца файла с помощью команды chown:

$ ls -l

-rwxrwxrwx 1 louise admin 345 Sep 20 14:33 project

$ chown pauline project

$ ls -l

-rwxrwxrwx 1 pauline admin 345 Sep 20 14:33 project

Право владения файлом project переходит от пользователя louise к пользователю

pauline.

1.7.2. Пример использования команды chgrp

Следующий пример демонстрирует, как поменять группу, которой принадлежит файл:

$ ls -1

-rwxrwxrwx 1 pauline admin 345 Sep 20 14:33 project

$ chgrp sysadmin project

$ ls -1

-rwxrwxrwx 1 pauline sysadmin 345 Sep 20 14:33 project

Пользователь pauline передал группе sysadmin право владения файлом project, которое до этого принадлежало группе admin.

1.7.3. Определение групп, в состав которых вы входите

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

$ groups

admin sysadmin appsgen general

либо воспользуйтесь командой id:

$ id

uid=0(root) gid=0(root) groups=0(root), 1(bin), 2(daemon), 3(sys), 4(ado)

1.7.4. Определение групп, в состав которых входят другие пользователи

Определить, в состав каких групп входит другой пользователь, позволяет команда groups, в качестве аргумента которой указано имя пользователя:

$ groups matty

sysadmin appsgen post

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

sysadmin, appsgen и post.

1.8. Команда umask

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

Как правило, значение umask устанавливается в файле /etc/profile, доступ к которому имеют все пользователи. Поэтому, если вы хотите установить общесистемное значение umask, отредактируйте данный файл (для этого нужно иметь права администратора). Свое собственное значение umask можно задать в файле .profile или .bash_profile, находящемся в каталоге /home.

1.8.1. Обработка значений umask

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

Общий формат команды umask таков:

umask nnn

где nnn — это маска режима в диапазоне от 000 до 777.

Ниже показано; как на основании значения umask определить режим доступа к файлу или каталогу (табл. 1.8).

Таблица 1.8. Интерпретация значения umask


Цифра в значении umask Результат для файла Результат для каталога
0 6 7
1 6 6
2 4 5
3 4 4
4 2 3
5 2 2
6 0 1
7 0 0

Из таблицы следует, что, например, значению umask, равному 002, соответствует режим 664 для файлов и 775 для каталогов.

Если вам удобнее работать со строками режима, руководствуйтесь описанной ниже последовательностью действий. Предположим, значение umask равно 002.

   • Сначала запишите полную строку режима, эквивалентную числу 777.

   • Под ней запишите строку режима, соответствующую значению umask (002).

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

   • Вычеркните из полученной строки все символы x. Вы получите строку режима для файлов.


1. Полная строка режима rwxrwxrwx (777)
2. Значение umask (002) w-
3. Строка режима для каталогов rwxrwxr-x (775)
4. Строка режима для файлов rw‑rw‑r-- (664)

1.8.2. Примеры установки значений umask

В табл. 1.9 представлены некоторые возможные значения umask и указаны соответствующие им режимы доступа к файлам и каталогам.

Таблица 1.9. Примеры значений umask


Значение umask Режим доступа к каталогам Режим доступа к файлам
022 755 644
027 750 640
002 775 664
006 771 660
007 770 660

Для просмотра текущего значения umask введите команду umask без параметров.

$ umask

022

$ touch file1

$ 1s -1 file1

-rw‑r--r-- 1 dave admin 0 Feb 18 42:05 file1

Чтобы изменить существующую установку, просто укажите новый аргумент команды umask:

$ umask 002

Убедимся в том, что система приняла изменения:

$ umask

002

$ touch file2

$ ls -l file2

-rw‑rw‑r-- 1 dave admin 0 Feb 18 45:0? file2

1.9. Символические ссылки

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

1.9.1. Применение символических ссылок

Предположим, у нас есть файл с информацией о продажах, находящийся в каталоге /usr/locai/admin/sales. Необходимо, чтобы каждый пользователь мог работать с этим файлом. Вместо того чтобы создавать множество копий файла в пользовательских каталогах, можно образовать в них символические ссылки, которые указывают на исходный файл в каталоге /usr/local/admin/sales. Тогда о всех изменениях, производимых в файле любым пользователем, немедленно узнают остальные пользователи. Имена символических ссылок могут быть произвольными и не обязаны совпадать с именем исходного файла.

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

1.9.2. Примеры создания символических ссылок

Символическая ссылка создается с помощью команды ln -s, формат которой таков:

ln -s исхолный_файл [имя_ссылки]

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

Рассмотрим случай, когда в системе регистрируются 40 пользователей, относящихся к двум группам — менеджеры (sales) и административный персонал (admin). Для каждой группы при входе в систему должны быть заданы свои установки. Сначала

я удалю все профильные файлы (.profile) во всех пользовательских начальных каталогах, а затем создам в каталоге /usr/local/menus два новых профильных файла — sales.profile и admin.profile. В начальном каталоге каждого пользователя необходимо создать символическую ссылку на один из этих двух файлов. Вот как это делается для пользователя matty, являющегося членом группы sales:

$ cd /home/sales/matty

$ rm .profile

$ ln -s /usr/local/menus/sales.profile .profile

$ ls -la .profile

lrwxrwxrwx 1 matty sales 5567 Oct 3 05:40 .profile -> /usr/local/menus/sales.profile

Аналогичные действия выполняются для всех пользователей. Теперь для изменения любого из профилей достаточно поменять всего один файл — либо sales.profile, либо admin.profile.

Когда ссылка больше не нужна, ее можно удалить. Однако помните, что при удалении исходного файла символическая ссылка не удаляется автоматически, а лишь становится недействительной. При обращении к ней выводится сообщение "No such file оr directory" (нет такого файла или каталога).

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

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

$ ln -s /tmp /var/tmp $ cd /var $ ls -l

lrwxrwxrwx 1 root root 5567 Sep 9 10:40 tmp -> /tmp

1.10. Заключение

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

Создание сценариев с установленным битом SUID является весьма удобным. Но старайтесь осторожно применять SUID–сценарии, обеспечивающие предоставление прав пользователя root.

ГЛАВА 2

Команды find и xargs

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

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

   • параметры команды find;

   • примеры использования различных опций команды find;

   • примеры совместного использования команд xargs и find .

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

Общий формат команды find таков:

find путевое_имя -oпции

где путевое_имя — это каталог, с которого необходимо начинать поиск. Символ '.' служит для обозначения текущего каталога, символ '/' — корневого каталога, а символ '~' — записанного в переменной $НОМЕ начального каталога текущего пользователя.

2.1. Опции команды find

Остановимся на описании основных опций команды find . Таблица 2.1. Основные опции команды find


-name Поиск файлов, имена которых соответствуют заданному шаблону
-print Запись полных имен найденных файлов в стандартный поток вывода
-perm Поиск файлов, для которых установлен указанный режим доступа
-prune Применяется для того, чтобы команда find не выполняла рекурсивный поиск по уже найденному путевому имени; если указана опция -depth, опция -prune игнорируется
-user Поиск файлов, принадлежащих указанном пользователю
-group Поиск файлов, которые принадлежат данной группе
-mtime -n +n Поиск файлов, содержимое которых модифицировалось менее чем (-) или более чем (+) n дней назад; имеются также опции -atime и -ctime, которые позволяют осуществлять поиск файлов соответственно по дате последнего чтения и дате последнего изменения атрибутов файла
-nogroup Поиск файлов, принадлежащих несуществующей группе, для которой, иначе говоря, отсутствует запись в файле /etc/groups
-nouser Поиск файлов, принадлежащих несуществующему пользователю, для которого, другими словами, отсутствует запись в файле
/etc/passwd
-newer файл Поиск файлов, которые созданы позднее, чем указанный файл
-type Поиск файлов определенного типа, а именно:
b -cпециальный блочный файл;
d — каталог;
с -cпециальный символьный файл;
p — именованный канал;
1 -cимволическая ссылка;
s -cокет;
f -oбычный файл
-size n Поиск файлов, размер которых составляет n единиц; возможны
следующие единицы измерения:
b — блок размером 512 байтов (установка по умолчанию);
c — байт;
k — килобайт (1024 байта);
w — двухбайтовое слово
-depth При поиске файлов сначала просматривается содержимое текущего каталога и лишь затем проверяется запись, соответствующая самому каталогу
-fstype Поиск файлов, которые находятся в файловой системе определенного типа; обычно соответствующие сведения хранятся в файле /etc/fstab, который содержит данные о файловых системах, используемых на локальном компьютере
-mount Поиск файлов только в текущей файловой системе; аналогом этой опции является опция -xdev
-exec Выполнение команды интерпретатора shell для всех обнаруженных
файлов; выполняемые команды имеют формат
команда {} \;
(обратите внимание на наличие пробела между символами {} и
-ok Аналогична опции -exec, но перед выполнением команды отображается запрос

2.1.1. Опция -name

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

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

$ find ~ -name "*.txt" — print

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

$ find . — name "*.txt" — print

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

$ find . — name "[A‑Z]*" — print

Найти в каталоге /etc файлы, имена которых начинаются с символов "host", позволяет команда

$ find /etc -name "host*" — print

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

$ find ~ -name "*.txt" — print -o -name ".*" — print

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

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

$ find / -name "*" — print

Ниже показано, как найти все файлы, в именах которых сначала следуют символы нижнего регистра, а за ними — две цифры и расширение .txt (например, ах37.txt):

$ find . — name "[a‑z][a‑z][0—9][0—9].txt" — print

2.1.2. Опция -perm

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

$ find . — perm 755 -print

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

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

$ find . — perm -007 -print

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

2.1.3. Опция -prune

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

Следующая команда проводит поиск в текущем каталоге, не заходя в подкаталог /bin:

$ find . — name "bin" — prune -o -print

2.1.4. Опции -user и -nouser

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

$ find ~ -user dave -print

Поиск в каталоге /etc файлов, принадлежащих пользователю uucp, выполняет

следующая команда: |

$ find /etc -user uucp -print

Благодаря опции -nouser возможен поиск файлов, принадлежащих несуществующим пользователям. При ее использовании производится поиск файлов, для владельцев которых нет записи в файле /etc/passwd. Конкретное имя пользователя указывать не нужно: команда find выполняет всю необходимую работу сама. Чтобы найти все файлы, которые принадлежат несуществующим пользователям и находятся в каталоге /hоте, задайте вот такую команду:

$ find /home -nouser -print

2.1.5. Опции -group и -nogroup

Опции -group и -nogroup аналогичны опциям -user и -nouser и позволяют искать файлы, принадлежащие заданной группе или несуществующим группам. Ниже приведена команда для нахождения в каталоге /apps всех файлов, которыми владеют пользователи группы accts:

$ find /apps -group accts -print

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

$ find / -nogroup -print

2.1.6. Опция -mtime

Опцию -mtime следует применять при поиске файлов, доступ к которым осуществлялся x дней назад. Если аргумент опции снабдить знаком ' — ', будут отобраны файлы, к которым не было доступа в течение x дней. Аргумент со знаком '+' приводит к противоположному результату — производится отбор файлов, доступ к которым осуществлялся на протяжении последних х дней.

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

$ find / -mtime -5 -print

Ниже приведена команда, выполняющая поиск в каталоге /var/adm файлов, которые обновлялись в течение последних трех дней:

$ find /var/adm -mtime +3 -print

2.1.7. Опция -newer

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

-newer старый_файл ! —newer новый_файл

Знак '!' является оператором логического отрицания. Он означает: найти файлы, которые новее, чем старый_файл, но старее, чем новый_файл.

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


rwxr‑xr‑x 1 root root 92 Apr 18 11:18 age.awk

rwxrwxr‑x 1 root root 1054 Apr 20 19:37 belts.awk

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

$ find . — newer age.awk! — newer beIt*.awk -exec ls -l {} \;


-rwxrwxr‑x 1 root root 62 Apr 18 11:32 . /who.awk

-rwxrwxr‑x 1 root root 4 9 Apr 18 12:05 ./group.awk

-rw‑r--r- 1 root root 201 Apr 20 19:30 ./grade2.txt

-rwxrwxr‑x 1 root root 1054 Apr 20 19:37 ./belts.awk

Ho как быть, если необходимо найти файлы, созданные, скажем, в течение последних двух часов, а у вас нет файла, сформированного ровно два часа назад, с которым можно было бы сравнивать? Создайте такой файл! Для этой цели предназначена команда touch -t, которая создает файл с заданной временной меткой в формате ММДДччмм (месяц–день–часы–минуты). Например:

$ touch -t 05042140 dstamp

$ ls -l dstamp

-rw‑r—r— I dave admin 0 May 4 21:40 dstamp

В результате будет получен файл, дата создания которого — 4 мая, время создания —21:40 (предполагается, что текущее время — 23:40). Теперь можно применить

команду find с опцией -newer для нахождения всех файлов, которые обновлялись в течение последних двух часов:

$ find . — newer dstamp -print

2.1.8. Опция -type

Операционные системы UNIX и Linux поддерживают различные типы файлов (см. главу 1). Поиск файлов нужного типа осуществляется посредством команды, find с опцией -type. Например, для нахождения всех подкаталогов в каталоге /etc воспользуйтесь такой командой:

$ find /etc -type d -print

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

$ find . ! — type d -print

Ниже приведена команда, которая предназначена для поиска всех символических ссылок в каталоге /etc.

$ find /etc -type l -print

2.1.9. Опция -size

В процессе поиска размер файла указывается с помощью опции -size n, где N -pазмер файла в блоках по 512 байтов. Возможные аргументы имеют следующие значения:

+N — поиск файлов, размер которых больше заданного,

— N — меньше заданного,

N -pавен заданному.

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

Для поиска файлов, размер которых превышает 1 Мб, предназначена команда

$ find . — size +1000k -print

Следующая команда выполняет поиск в каталоге /home/apache файлов, размер которых в точности равен 100 байтам:

$ find /home/apache -size 100c -print

Произвести поиск файлов, размер которых превышает 10 блоков (5120 байтов), позволяет приведенная ниже команда:

$ find . — size +10 -print

2.1.10. Опция -depth

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

Следующая команда выводит список всех файлов и подкаталогов текущего каталога:

$ find . — name "*" — print -о -name ".*" — print -depth

Вот как могут выглядеть результаты ее работы:

./.Xdefaults

./.bash_logout

./.bash_proflie

./.bashrc

./.bash_history

./file

./Dir/file1

./Dir/file2

./Dir/file3

./Dir/Subdir/file4

./Dir/Subdir

./Dir

2.1.11. Опция -mount

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

$ find / -name "*.XC" — mount -print

2.1.12. Поиск файлов с последующей архивацией командой cpio

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

Вот как выполняется запись на магнитную ленту содержимого каталогов /etc, /home и /apps:

$ cd /

$ find etc home apps -depth -print | cpio -ov > dev/rmt0

Опция -o команды cpio задает режим записи файлов на ленту. Опция -v (verbose -cловесный режим) является указанием команде cpio сообщать о каждом обрабатываемом файле.

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

2.1.13. Опции -exec и -ok

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

ls -l). Многие пользователи применяют опцию -exec для нахождения старых файлов, подлежащих удалению. Я рекомендую вместо команды rm выполнить сначала команду ls, чтобы убедиться в том, что команда find нашла именно те файлы, которые нужно удалить.

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

$ find . — type f -exec ls -1 {} \;

-rwxr‑xr‑x 10 root wheel 1222 Jan 4 1993 ./sbin/C80
-rwxr‑xr‑x 10 root wheel 1222 Jan 4 1993 ./sbin/Normal
-rwxr‑xr‑x 10 root wheel 1222 Jan 4 1993 ./sbin/Revvid

Здесь выполняется поиск обычных файлов, список которых отображается на экране с помощью команды ls -l.

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

$ find /logs -type f -mtime +5 -exec rm {} \;

Следует соблюдать осторожность при перемещении и удалении файлов. Пользуйтесь опцией -ok, которая позволяет выполнять команды mv и rm в безопасном режиме (перед обработкой очередного файла выдается запрос на подтверждение). В следующем примере команда find находит файлы с расширением LOG, и если какой‑то файл создан более пяти дней назад, она удаляет его, но сначала просит вас подтвердить эту операцию:

$ find . — name "*.LOG" — mtime +5 -ok rm {} \;

< rm… ./nets. LOG > ? у

Для удаления файла введите у, а для предотвращения этого действия — n.

2.1.14. Дополнительные примеры использования команды find

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

$ find ~ -print

Найти все файлы, для которых установлен бит SUID, позволяет следующая команда:

$ find . — type f -perm +4000 -print

Чтобы получить список пустых файлов, воспользуйтесь такой командой:

$ find / -type f -size 0 -exec ls -l {} \;

В одной из моих систем каждый день создается системный журнал аудита. К имени журнального файла добавляется номер, что позволяет сразу определить, какой файл создан позже, а какой -pаньше. Например, версии файла admin.log нумеруются последовательно: admin.log.001, admin.log. O02 и т. д. Ниже приведена команда find, которая удаляет все файлы admin.log, созданные более семи дней назад:

$ find /logs -name 'admin.log. [0-9] [0-9][О-9] ' — atime +7 exec \ rm {} \;

2.2. Команде xargs

При наличии опции -exec команда find передает указанной команде все найденные файлы, которые обрабатываются за один раз. К сожалению, в некоторых системах длина командной строки ограниченна, поэтому при обработке большого числа файлов может быть выдано сообщение об ошибке, которое обычно гласит: "Too many arguments" (слишком много аргументов) или "Arguments too long" (слишком большой список аргументов). В этой ситуации на помощь приходит команда xargs. файлы, полученные от команды find, она обрабатывает порциями, а не все сразу.

Рассмотрим пример, в котором команда find возвращает список всех файлов, имеющихся в системе, а команда xargs выполняет для них команду file, проверяющую тип каждого файла:

$ find / -type f -print | xargs file

/etc/protocols: English text /etc/securetty: ASCII text

Ниже приведен пример, демонстрирующий поиск файлов дампа, имена которых команда echo помешает в файл /tmp/core.log.

$ find / -name core -print | xargs echo > /tmp/core.log

В следующем примере в каталоге /apps/audit выполняется поиск всех файлов, к которым другие пользователи имеют полный доступ. Команда chmod удаляет для них разрешение на запись:

$ find /apps/audit -perm -7 -print | xargs chmod o‑w

Завершает наш перечень пример, в котором команда grep ищет файлы, содержащие слово "device":

$ find / -type f -print | xargs grep "device"

2.3. Заключение

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

ГЛАВА 3

Выполнение команд в фоновом режиме

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

В этой главе рассматриваются следующие темы:

   • планирование заданий с помощью программы cron;

   • планирование заданий с помощью команды at;

   • выполнение заданий в фоновом режиме;

   • выполнение заданий с помощью команды nohup.


cron Системный планировщик, применяемый для многократного выполнения заданий в указанные периоды времени; является демоном, т. е. работает только в фоновом режиме
at Команда, которая служит для однократного выполнения заданий в назначенное время
& Оператор, позволяющий перевести задание в фоновый режим
nohup Команда для перевода задания в фоновый режим таким образом, чтобы оно не реагировало на сигнал HUP (hang‑up -oтбой) и продолжало выполняться, даже если запустивший его пользователь выйдет из системы

3.1. Планировщик cron и команда crontab

Программа cron является основным системным планировщиком, служащим для выполнения различных заданий в фоновом режиме. Команда crontab позволяет пользователям создавать, редактировать и удалять инструкции для программы cron посредством специального crontab–файла. У каждого пользователя может быть свой crontab–файл, но в крупных системах администратор (пользователь root) обычно исключает данную возможность. В этом случае администратор создает вспомогательные файлы cron.deny и cron.allow, содержащие списки пользователей, которым соответственно запрещено и разрешено выполнять команду crontab.

3.1.1. Структура crontab–файла

Чтобы планировать выполнение заданий в определенное время, нужно знать формат записей в crontab–файле. Каждая запись содержит шесть полей;


1 Минуты — 0—59
2 Часы — 0—23 (0 означает полночь)
3 День месяца — 1—31
4 Месяц — 1—12
5 День недели — 0—7 {0 и 7 означает воскресенье)
6 Команда, которая должна быть выполнена

Общий формат записи таков:

минуты часы день_месяца месяц деиь_нвдели команда

Все поля разделяются пробелами.

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

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

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

ввести 1, 4.

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

В crontab–файле допускаются комментарии. В начале строки комментария должен стоять символ #.

3.1.2. Примеры записей в crontab–файле

Запись

30 21 * * * /apps/bin/cleanup.sh

означает выполнение сценария cteanup.sh в каталоге /apps/bin каждый вечер в 21:30. Запись

45 4 1,10,22 * * /apps/bin/backup.sh

означает выполнение сценария backup.sh в каталоге /apps/bin в 4:45 утра 1–го, 10–го и 22–го числа каждого месяца. Запись

10 1 * * 6,0 /bin/find -name "core" — exec rm {} \;

означает выполнение команды find для удаления файлов дампа в 1:10 ночи по субботам и воскресеньям. Запись

0,30 18-23 * * * /apps/bin/dbcheck.sh

означает выполнение сценария dbchecksh в каталоге /apps/bin каждые полчаса между 18:00 и 23:00.

Запись

0 23 * * 6 /apps/bin/qtrend.sh

означает выполнение сценария qtrend.sh в каталоге /apps/bin в 23:00 каждую субботу. При выполнении команд и сценариев, указанных в crоntab–файле, следует убедиться, что корректно заданы все необходимые переменные среды. Программа cron не сделает это за вас: это не входит в ее компетенцию. Поэтому локальные переменные среды должны быть установлены вручную, в отличие от глобальных переменных, которые устанавливаются автоматически. Данная задача может быть решена непосредственно в crontab–файле за счет создания записи следующего вида:

имя_переменной=значение

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

3.1.3. Опции команды crontab

Общий формат команды crontab таков:

crontab [-u пользователь] — e -l -r


ОПЦИЯ назначение
-u пользователь Установка имени пользователя, для которого нужно создать
crontab–файл
-e Активизация режима редактирования crontab–файла
-1 Отображение содержимого crontab–файла
-r Удаление crontab–файла

Если вы хотите работать с собственным crontab–файлом, то указывать опцию — и нет необходимости.

3.1.4. Создание crontab–файла

Сначала, еще до того, как crontab–файл будет помещен в очередь заданий программы cron, необходимо установить переменную среды editor. Благодаря этому планировщик получит указание относительно того, какой редактор следует использовать при обработке crontab–файлов. Если вы предпочитаете редактор vi, откройте файл .profile или .bash_profile, находящийся в вашем начальном каталоге, и поместите в него следующие команды:

EDITOR=vi; export EDITOR

Далее создайте новый файл <имя_полъзователя>cron, где <имя_польэователя> — ваше регистрационное имя. Вот пример содержимого такого файла:

   • вывод текущей даты на экран

   • каждые 15 минут между 18:00 и 6:00

0,15,30,45 18-06 * * * /bin/echo 'date' > /dev/console

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

Чтобы поместить в очередь заданий планировщика cron свой crontab–файл, выполните команду crontab, указав в ней имя созданного файла:

$ crontab davecron

Копия файла будет помещена в каталог /var/spool/cron, а имя копии совпадет с вашим регистрационным именем (в данном случае — dave).

3.1.5. Вывод на экран содержимого crontab–файла

Для вывода на экран содержимого crontab–файла предназначена команда crontab -l:

$ crontab -l

#(davecron installed on Tue Kay 4 13:07:43 1999)

#вывод текущей даты на экран

#каждые 15 минут между 18:00 и 6:00

0,15,30,45 18-06 * * * /bin/echo "date" > /dev/console

Вот как легко можно создать резервную копию crontab–файла в своем начальном каталоге:

$ crontab -1 > $HOME/davecron

3.1.6. Редактирование crontab–файла

Для добавления, редактирования или удаления записей в crontab–файле используется тот редактор, который указан в переменной среды editor. Чтобы отредактировать файл, выполните команду

$ crontab -e

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

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

   • удаление файлов дампа в 3.30 утра в 1–й, 7–й, 14–й,

   • 21–й и 2б–й день каждого месяца

30 3 1,7,14,21,26 * * /bin/find -name "core" — exec rm {} \;

Желательно размещать перед каждой записью комментарий, объясняющий ее назначение.

Теперь сохраним файл, выйдем из редактора и проверим результат:

$ crontab -1

#(davecron installed on Tue May 4 13:07:43 1999)

#вывод текущей даты на экран

#каждые 15 минут между 18:00 и 6:00

0,15,30,45 18-06 * * * /bin/echo "date" > /dev/console

#удаление файлов дампа в 3.30 утра в 1–й, 7–й, 14–й,

#21–й и 26–й день каждого месяца

30 3 1,7,14,21,26 * * /bin/find -name "core" — exec rm {} \;

3.1.7. Удаление crontab–файла

Для удаления своего crontab–файла введите команду $ crontab -r

3.1.8. Восстановление утерянного crontab–файла

Если crontab–файл случайно удален, инсталлируйте заново исходный файл из вашего начального каталога:

$ crontab <имя_файла>

Именно по этой причине в документации к программе cron говорится о том, что не рекомендуется прибегать к непосредственному редактированию crontab–файла. Следует вносить все изменения в копию файла и инсталлировать ее заново.

3.2. Команда at

Команда at позволяет передавать задания демону cron для одноразового выполнения в назначенное время. Выдавая задание, команда at сохраняет в отдельном файле как его текст, так и все текущие переменные среды. Заметим, что команда crontab не делает этого. По умолчанию все результаты выполнения задания направляются пользователю в виде электронного сообщения.

Как и в случае с программой cron, пользователь root может контролировать, кому разрешено или запрещено выполнять команду at. Соответствующие списки пользователей содержатся в файлах at.allow и at.deny, находящихся в каталоге /etc.

Базовый формат команды at таков:

at [-f файл] [-l -d -m] время


Опция Назначение
-f файл Список заданий должен быть взят из указанного файла
-l Вывод на экран списка заданий, которые ожидают выполнения; аналогична команде atq
-d Удаление задания с указанным номером; аналогична команде atrm (в некоторых системах заменяется опцией -r)
-m Выдача пользователю электронного сообщения о завершении задания
время Спецификация времени, когда будет выполнено задание. Эта спецификация может быть довольно сложной. Допускается указание не только времени в формате часы: минуты, но и даты, а также многочисленных ключевых слов, таких как названия дней недели, месяцы, наречий today (сегодня), tomorrow (завтра), now (сейчас) и др. Наиболее удобна запись вида now + 3 hours (через три часа).

3.2.1. Запуск команд и сценариев с помощью команды at

Текст задания можно передать команде at двумя способами: в файле или в режиме командной строки at. Если задание состоит из одной команды или двух — трех команд, объединенных каналом, то удобнее воспользоваться вторым способом. Для запуска сценариев интерпретатора shell предпочтительнее первый вариант.

В случае необходимости выполнить одиночную команду вызовите команду at, указав требуемое время. Отобразится приглашение at>. Введите свою команду, а затем нажмите [Enter] и [Ctrl+D]. Рассмотрим пример.

$ at 21:10

at> find / -name "passwd" — print

at> <EOT>

warning: commands will be executed using /bin/sh job 1 at 1999-05-05 21:10

Запись <ЕОТ> появляется после нажатия [Ctrl+D]. Теперь в 21:10 будет выполнена команда find, ищущая в системе файлы с именем passwd. Обратите внимание на то, что команда at присваивает заданию уникальный идентификатор 1. Результаты выполнения команды find будут направлены вам по электронной почте. Вот фрагмент соответствующего электронного сообщения:

Subject: Output from your job 1

/etc/passwd

/etс/pam.d/passwd

/etc/uucp/passwd

/tmp/passwd

/root/passwd

/usr/bin/passwd

/usr/doc/uucp-1.06.1/sample/passwd

Ниже приведены примеры корректного указания времени при вызове команды at:

at 6.45am May 12 — 12–го мая в 6:45 утра

at 11.10pm — в 23:10 (сегодня или завтра, если

это время уже прошло)

at now + 1 hour — через час at 9am tomorrow — завтра в 9:00 утра

at 15:00 May 24 — 24 мая в 15:00 at 4am + 3 days — через 3 дня в 4:00 утра

Если необходимо запустить с помощью команды at файл сценария, укажите его имя после опции -f, как это сделано ниже:

$ at 3.00pm tomorrow -f /apps/bin/db_table.sh

warning: commands will be executed using /bin/sh job 8 at 1999-05-06 15:00

Сценарий db_table.sh будет выполнен завтра в 15:00. Передать задание команде at позволяет также команда echo? $ echo find /etc -name "passwd" — print | at now +1 minute

3.2.2. Просмотр списка запланированных заданий

Для того чтобы просмотреть полный список запланированных заданий, введите команду at -l или atq:

$ atq

   1. 1999-05-05 23:00 а

   2. 1999-05-06 06:00 а

   3. 1999-05-21 11:20 а

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

Получив задание, команда at создает в каталоге /var/spoot/at файл, в который помещает текст задания и заносит текущие установки всех переменных среды:

$ pwd

/var/spool/at

$ ls

a0000200eb7ae4 a0000400ebd228 a0000800eb7ea4 spool

3.2.3. Удаление запланированного задания

Для удаления задания предназначена команда atrm (синоним команды at -d или at -r), имеющая следующий формат:

atrm номер_задания

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

$ at -l

   1. 1999-05-05 23:00 а

   2. 1999-05-06 06:00 а

   3. 1999-05-21 11:20 а

$ atrm 3

$ at -l

2 1999-05-05 23:00 а

4 1999-05-21 11:20 а

3.3. Оператор &

При выполнении задания в экранном режиме происходит "захват" терминала на весь этот период. Перевод задания в фоновый режим позволяет освободить терминал для других целей. Чтобы выполнить команду в фоновом режиме, укажите после нее оператор &:

команда &

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

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

команда > выходной файл 2>&1 &

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

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

3.3.1. Запуск команды в фоновом режиме

Выполним в фоновом режиме команду find, которая ищет в каталоге /etc файл srm.conf, и перенаправим ее вывод в файл find .dt

$ find /etc -name "srm.conf" — print > find .dt 2>&1 &

[1] 27015

Номер процесса в данном случае равен 27015. После завершения выполнения задания, когда вы в очередной раз нажмете клавишу [Return], на экране отобразится такое сообщение:

[1]+ Done find /etc "srm.conf" — print

3.3.2. Получение списка выполняющихся процессов с помощью команды ps

Предположим, имеется сценарий psl, который выполняется довольно долго: $ p$1 &

[2] 28305

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


$ ps
PID TTY TIME CMD
679 pts/0 00:00:01 bash
28305 pts/0 00:02:07 psl
28310 pts/0 00:00:00 ps

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

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

$ ps | grep 28305

28305 pts/0 00:02:20 psl

Обратите внимание: команда ps не показывает, в каком режиме выполняется задание — в фоновом или экранном.

3.3.3. Уничтожение фонового задания

Сигнал о завершении посылается процессу командой kill:

kill [-сигнал] номер_процесса

Далее в этой книге мы рассмотрим, какие существуют сигналы. Пока же достаточно знать, что по умолчанию команда kill посылает сигнал номер 1 — HUP (hang‑up -oтбой). На экран выводится сообщение о прекращении задания:

$ kill 28305

[1]+ Terminated ./psl

Многие команды и сценарии перехватывают сигнал HUP, поэтому команда kill -1 не уничтожает их. В этом случае нужно воспользоваться командой kill -9, которая посылает процессу сигнал KILL (уничтожить). Этот сигнал не перехватывается и означает безусловное уничтожение процесса.

$ kill -9 28305

[1] + Killed ./psl

3.4. Команда nohup

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

nohup команда &

3.4.1. Запуск задания с помощью команды nohup

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

nohup команда > выходной файл 2>&1 &

'Давайте проверим работу команды nohup на примере упомянутого выше сценария psl.

$ nohup psl &

[1] 179

nohup; appending output to 'nohup.out'

Теперь выйдите из интерпретатора shell, выполнив команду logout, снова зарегистрируйтесь и введите следующую команду:

$ ps x | grep psl

30004 ? RN 4:01 sh ./psl

30006 pts/1 S 0:00 grep psl

Опция x предназначена для вывода списка заданий, не связанных с терминалом (обратите внимание на знак вопроса во втором столбце). В третьем столбце указан статус задания. Статус R означает, что процесс выполняется, статус N — это признак снижения

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

Если система не поддерживает команду ps x, воспользуйтесь опцией -e, которая предназначена для получения списка всех системных процессов:

$ ps -e | grep psl

30004 ? 00:04:01 sh ./psl

3.4.2. Одновременный запуск нескольких заданий

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

cat /home/acccmnts/qtr_04 99 | /apps/bm/trials.awk | sort | lp

Поместим ее в файл:

$ cat > quarterend

cat /home/accounts/qtr_0499 | /apps/bin/trials.awk | sort | lp <CTRL‑D>

Сделаем файл исполняемым:

$ chmod 744 quarterend

Теперь запустим этот файл в фоновом режиме посредством команды nohup, направив результаты работы сценария в файл qtr.out.

$ nohup ./quarterend > qtr.out 2>&l &

[5] 182

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

3.5. Заключение

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

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

ГЛАВА 4

Подстановка имен файлов

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

Вот список основных метасимволов:

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

? Соответствует любому символу

[…] Соответствует любому символу из числа заключенных в скобки

[!…] Соответствует любому символу за исключением тех, которые указаны в скобках

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

4.1. Применение метасимвола '*'

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

$ ls app*

appdva app_tapes

appdva_SLA

Вот как можно получить список файлов, имеющих расширение doc:

$ ls *.doc

accounts.doc qtr_end.doc

Представленная ниже команда находит файлы, у которых имя начинается со строки "cl", а расширение равно sed:

$ ls cl*.sed

cleanlogs.sed cleanmeup.sed

cleanmessages.sed

Звездочку удобно применять, когда, например, не хочется вводить полное имя каталога при переходе в него:

$ cd /etc

$ ls -l | grep ^d

drwxr‑xr‑x 2 root root 1024 Jan 26 14:41 cron.daily

drwxr‑xr‑x 2 root root 1024 Jan 27 1998 cron.hourly

drwxr‑xr‑x 2 root root 1024 Jan 27 1998 cron.monthly

drwxr‑xr‑x 2 root root 1024 Jan 26 14:37 cron.weekly

$ cd cron.w* S pwd

/etc/cron.weekly

4.2. Применение метасимвола '?'

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

$ ls ??R*

BAREAD

Следующая команда ищет файлы с именами, начинающимися со строки "conf", за которой следуют два произвольных символа и расширение log:

$ ls conf??.log .

confl2.log conf.2.log

conf25.log

Вот как можно найти файлы, имена которых состоят не менее чем из четырех символов, причем первым является символ 'f', а последним — 's':

$ ls f??*s

ftpaccess ftphosts ftpconversions ftpusers ftpgroups

4.3. Применение метасимволов […] и [!…]

Метасимволы [… ] соответствуют любому символу из числа тех, что указаны в квадратных скобках. Представленная ниже команда выводит список файлов, имена которых начинаются с символа 'i' или 'o':

$ ls [io]*

inetd.conf ioctl.save outputrc

info‑dir inputrc

initrunlvl issue

inittab issue.net

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

$ ls log.[0-9]*

log.0323 log.0325 log.0324 log.0326

Метасимволы [!…] соответствуют любому символу из числа тех, что не указаны

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

$ ls log.[!0-9]*

log.sybase

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

$ ls LPS??[!0-9]*

LPSILP LPSOSI LPSOPS LPSPOPQTR

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

$ ls [A‑Z]*

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

$ 1а [а–z]*

А эта команда находит файлы, в начале имени которых стоит цифра:

$ ls [0-9]*

Вот как можно найти все скрытые файлы (такие как .profile, .rhosts, .history и т. д.):

$ ls .*

4.4. Заключение

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

ГЛАВА 5

Ввод и вывод данных в интерпретаторе shell

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

В этой главе рассматриваются следующие темы:

   • работа со стандартными потоками ввода, вывода и ошибок;

   • переадресация ввода и вывода.

5.1. Команда echo

Команда echo отображает на экране указанную строку текста. Общий ее формат таков:

echo строка

В строке могут встречаться различные управляющие символы, ниже перечислены основные из них: \c запрет отображения концевого символа новой строки \f прогон страницы \n новая строка \t горизонтальная табуляция

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

$ echo "Как вас зовут?\t\c"

Как вас зовут? $

Здесь $ -cимвол приглашения.

По умолчанию подразумевается, что в конце строки находится символ новой строки:

$ echo "Как вас зовут?"

Как вас зовут? $

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

$ echo "Ваш начальный каталог $HOME, вы подключены к терминалу `tty`"

Ваш начальный каталог /home/dave, вы подключены к терминалу /dev/ttyp1

В Linux…

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

$ echo -n "Как вас зовут?"

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

$ echo -e "Как вас зовут?\t\c"

Как вас зовут? $

Для вывода дополнительных пустых строк используйте управляющий символ \n:

$ echo "Выводим 3 пустые строхи\n\n\nОК"

Выводим 3 пустые строки

OK

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

$ echo "Один символ табуляции\tДве табуляции\t\tOK"

Один символ табуляции Два символа табуляции ОК

Чтобы перенаправить результаты работы команды echo в файл, воспользуйтесь оператором '>':

$ echo "Строка занесена в файл." > myfile

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

$ echo "Отчет создал пользователь $LOGNAME. `date` " >> myfile

Здесь используется переменная среды $LOGNAME, которая содержит регистрационное имя текущего пользователя.

Рассмотрим содержимое файла myfile:

$ cat myfile

Строка занесена в файл.

Отчет создал пользователь root. Sat May 22 18:25:06 GMT 1999

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

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

$ echo "\"/dev/rmt0\""

"/dev/rmt0"

5.2. Команда read

Команда read читает одну строку из стандартного входного потока и записывает ее содержимое в указанные переменные. Если задана единственная переменная, в нее записывается вся строка. В результате ввода команды read без параметров строка помешается в переменную среды $reply. При указании нескольких переменных в первую из них записывается первое слово строки, во вторую — второе слово и т. д. Последней переменной присваивается остаток строки.

Общий формат команды таков:

read переменная1 переменная2…

В следующем примере в переменную name записывается весь вводимый с клавиатуры текст до тех пор, пока не будет нажата клавиша [Enter]:

$ read name Джон Алан Доу $ echo $name

Джон Алан Доу

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

$ read name surname

Джон Доу

$ echo $name $surname

Джон Доу

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

$ read name surname Джоя Алан Доу

$ echo $name

Джон

$ echo $surnane

Алан Доу

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

$ cat var_test

#!/bin/sh

# var_test

echo "Имя:\c"

read name

echo "Отчество:\c"

read middle

echo "Фамилия:\c"

read surname

В Linux…

Вместо управляющего символа \с в команде echo следует указывать опцию -n:

$ cat var_test

#!/bin/sh

# var_test

echo -n "Имя:"

read name

echo -n "Отчество:"

read middle

echo -n "Фамилия:"

read surname

5.3. Команда cat

Команда cat довольно проста, но универсальна. Эту команду удобно применять как для отображения файла, так и для его создания, а также при отображении файлов, содержащих управляющие символы. Используя команду cat, следует учитывать, что процесс вывода не останавливается по достижении конца страницы — файл пролистывается до конца. Если необходимо просмотреть файл постранично, передайте вывод команды cat какой‑нибудь программе постраничной разбивки:

$ cat myfile | more

или

$ cat myfile | pg

Общий формат команды cat таков:

cat [опции] имя_файла1…имя_файла2…

Из опций команды cat в первую очередь заслуживает внимания опция -v, активизирующая режим отображения непечатаемых символов. Вывести файл myfile позволяет вот такая простая команда:

$ cat myfile

Для отображения сразу трех файлов — myfile1, myfile2 и myfile3 — нужно выполнить команду

$ cat myfile1 myfile2 myfile3

Чтобы сформировать файл bigfile, включающий содержимое файлов myfile1, myfile2 и myfile3, следует перенаправить выходной поток предыдущей команды в новый файл:

$ cat myfile1 myfile2 myfile3 >> bigfile

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

$ cat >> myfile

Это новый файл

<CTRL‑D>

$ pg myfile

Это новый файл

По завершении ввода данных нажмите [Ctrl+D].

Для просмотра управляющих символов в файле воспользуйтесь опцией -v. Следующая команда отображает содержимое файла, в котором встречаются символы <CTRL‑M> (представлены как ^M):

$ cat -v life.tct

ERROR ON REC A$12^M ERROR ON REC AS31^M

5.4. Каналы

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

команда1 | команда2

В следующем примере команда ls формирует список всех файлов из текущего каталога. Этот список был бы выведен на экран, если бы не символ канала. Интерпретатор shell обнаруживает канал, перехватывает все выходные потоки команды, стоящей слева от оператора [, и направляет их команде, которая расположена справа от оператора. В данном случае утилита фильтрации grep отбирает в списке файл с именем quarter1.doc:

$ ls | grep quarter1.doc

quarter1.doc

Представим этот пример схематически:


$ ls Канал grep quarter1.doc | Выходные данные команды ls

$ ls accounts.doc acc_LPSO.doc quarter1.doc

quarter1.doc quarter2.doc

При обработке строковых данных можно объединять, каналами такие мощные программы фильтрации, как потоковый редактор sed, редактор awk и утилита grep, создавая сложные критерии отбора информации. В показанной ниже командной строке команда who выводит информацию о пользователях, зарегистрированных в

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

$ who | awk '{print $1"\t"$2}'

matthew pts/0 louise pts/1

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

$ df | awk '{print $1}' | grep -v "Filesystem"

/dev/hda5 /dev/hda8 /dev/hda6 /dev/hdb5 /dev/hdb1 /dev/hda7 /dev/hda1

С помощью редактора sed можно удалить из полученного списка повторяющуюся подстроку /dev/, оставив только имя раздела. Вот как это делается:

$ df | awk '(print $1}' | grep -v "Filesystem" | sed s'/\/dev\///g'

hda5 hdа8 hda6 hdb6 hdb1 hda7 hda1

Команда s редактора sed предназначена для замены указанного шаблона (в данном случае \/dev\/; символы '/' имеют специальное назначение, поэтому защищены символами \) заданной строкой (в нашем случае это пустая строка). Флаг g означает, что замену нужно производить каждый раз, когда обнаружено совпадение, а не только первый раз.

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

$ sort myfile | lp

5.5. Команда tee

Команда tee функционирует следующим образом: входные данные копируются, при этом одна копия направляется в стандартный поток вывода, а другие копии — в указанные файлы. Общий формат этой команды таков:

tee [-a] файлы

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

Рассмотрим пример. Команда who формирует список пользователей, которые зарегистрированы в данный момент в системе, а команда tee отображает этот список на экране, направляя копию в файл who.out.

$ who | tee who.out

louise pts/l May 20 12:58 (193.132.90.9)

matthew pts/0 May 20 10:18 (193.132.90.1)

$ cat who.out

louise pts/1 May 20 12:58 (193.132.90.9) matthew pts/0 May 20 10:18 (193.132.90.1)

В следующем примере команда cpio выполняет резервирование файлов из каталога /home на магнитную ленту, а список помещаемых в архив файлов фиксируется в файле tape.log. Поскольку с помощью команды cpio производится последовательное добавление данных в архив, воспользуйтесь опцией '-a' команды tee:

$ find /home -depth -print | cpio -ov -0 /dev/rmt0 | tee -a tape.log

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

$ echo "Сценарий myscript запушен пользователем dave" | tee -a myscript.log $ myscript | tee -a myscript.log

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

$ sort myfile | tee -a accounts.log $ myscript | tee -a accounts.log

5.6. Стандартные потоки ввода, вывода и ошибок

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


Файл Дескриптор
Стандартный поток ввода (stdin) 0
Стандартный поток вывода (stdout) Стандартный поток ошибок (stderr) 1 2

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

5.6.1. Стандартный поток ввода

Файл стандартного потока ввода (stdin) имеет дескриптор 0. Из этого файла процессы извлекают свои входные данные. По умолчанию входной поток ассоциирован с клавиатурой (устройство /dev/tty), но чаще всего он поступает по каналу от других процессов или из обычного файла.

5.6.2. Стандартный поток вывода

Файл стандартного потока вывода (stdout) имеет дескриптор 1. В этот файл записываются все выходные данные процесса. По умолчанию данные выводятся на экран терминала (устройство /dev/tty), но их можно также перенаправить в файл или послать по каналу другому процессу.

5.6.3. Стандартный поток ошибок

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

5.7. Файловый ввод–вывод

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

Во время перенаправления стандартного потока ошибок следует указывать дескриптор файла (2). Для потоков ввода и вывода делать это не обязательно.

Таблица 5.1. Основные операторы переадресации


команда > файл Направляет стандартный поток вывода в новый файл
команда 1> файл Направляет стандартный поток вывода в указанный файл
команда >> файл Направляет стандартный поток вывода в указанный файл (режим присоединения)
команда > файл 2>&1 Направляет стандартные потоки вывода и ошибок в указанный файл
команда 2> файл Направляет стандартный поток ошибок в указанный файл
команда 2» файл Направляет стандартный поток ошибок в указанный файл (режим присоединения)
команда >> файл 2>&1 Направляет стандартные потоки вывода и ошибок в указанный файл (режим присоединения)
команда < файл1 > файл2 Получает входные данные из первого файла и направляет выходные данные во второй файл
команда < файл В качестве стандартного входного потока получает данные из указанного файла
команда << разделитель Получает данные из стандартного потока ввода до тех пор, пока не встретится разделитель
команда <&m В качестве стандартного входного потока получает данные из файла с дескриптором m
команда > &m Направляет стандартный поток вывода в файл с дескриптором m

5.7.1. Переадресация стандартного потока вывода

Рассмотрим, как осуществляется переадресация стандартного потока вывода. В следующей командной строке из файла /etc/passwd извлекаются имена пользователей, известных в системе, полученный список сортируется по алфавиту, а результаты направляются в файл sort.out.

$ cat /etc/passwd | awk -F: '{print $1}' | sort > sort.out

Опция -f программы awk свидетельствует о том, что указанный после нее символ двоеточия является разделителем полей в файле /etc/passwd.

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

$ ls -l | grep ^d >> files.out

$ 1s account* >> files.out

В первой строке в файл files.out помещается список каталогов, а во второй -cписок файлов, имена которых начинаются со строки "account".

Для создания пустого файла нулевой длины выполните следующую команду:

$ > myfile

5.7.2. Переадресация стандартного потока ввода

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

$ mail louise < contents.txt

Переадресация вида команда << разделитель называется конструкцией "документ здесь". Более подробно мы поговорим о ней позже. Пока же рассмотрим общие принципы ее функционирования. Встречая в командной строке оператор «, интерпретатор shell воспринимает все данные, вводимые с клавиатуры, как входной поток, пока в отдельной строке не будет введено слово–разделитель, указанное в командной строке после оператора. Разделителем может служить любое слово. Вот как, например, можно создать файл в режиме командной строки:

$ cat >> myfile << Пока

> Привет! Я работая за терминалом $TERM

и мое имя $LOGNAME.

Пока!

Пока

$ cat myfile

Привет! Я работаю за терминалом vt100

и мое имя dave.

Пока!

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

5.7.3. Переадресация стандартного потока ошибок

При переадресации стандартного потока ошибок указывается дескриптор 2. Рассмотрим пример. Утилита grep ищет в файле missiles строку "trident":

$ grep "trident" missiles

grep: missiles: No such file or directory

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

$ grep "trident" missiles 2> /dev/null

Теперь никакие сообщения на экране отображаться не будут.

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

$ grep "trident" missiles 2> grep.err

$ cat grep.err

grep: missiles: No such file or directory

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

$ grep "LPSO" * 2>> account.err

$ grep "SILO" * 2>> account.err

5.7.4. Переадресация обоих выходных потоков

В одной командной строке можно последовательно переадресовывать как стандартный поток вывода, так и стандартный поток ошибок. Ниже приведен пример, в котором команда cat обрабатывает два файла, направляя вывод в файл accounts.out, а сообщения об ошибках — в файл accounts.err.

$ cat account_qtr.doc account_end.doc 1> accounts.out 2> accounts.err

$ cat accounts.out

AVBD 34HJ OUT AVFJ 31KO OUT

$ cat accounts.err

cat: account_end.doc: No such file or directory

Просмотрев файл accounts.err, обнаруживаем, что исходного файла account_end.doc не существует.

5.7.5. Объединение выходных потоков в файле

Оператор n>&m позволяет перенаправить файл с дескриптором n туда, куда направлен файл с дескриптором m. Подобных операторов в командной строке может быть несколько, в этом случае они вычисляются слева направо. Рассмотрим пример:

$ cleanup > cleanup.out 2>&1

Здесь сценарий cleanup направляет все свои выходные данные (как поток вывода, так и поток ошибок) в файл cleanup.out.

В следующем примере все результаты работы команды grep направляются в файл grep.out.

$ grep "standard" * > grep.out 2>&1

5.8. Команда exec

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

exec < файл

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

exec <&-

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

5.9. Применение дескрипторов файлов

Рассмотренная в предыдущем параграфе команда 'exec < файл' не только назначает файл стандартным входным потоком всех команд сценария, но и перезаписывает указатель на файл с дескриптором 0 (stdin). Восстановить этот указатель можно будет только по завершении работы сценария. Если же в сценарии предполагается продолжить чтение данных с клавиатуры, то необходимо каким‑то образом сохранить указатель на прежний входной поток. Ниже приведен небольшой сценарий, в котором демонстрируется, как это сделать.

$ cat f_desc

#!/bin/sh

# f_desc

exec 3<&0 0<stock.txt

read line1

read line2

exec 0<&3

echo $LINEl

echo $LINE2

Первая команда exec сохраняет указатель на стандартный входной поток (stdin) в файле с дескриптором 3 (допускается любое целое число в диапазоне от 3 до 9), а затем открывает файл stock.txt для чтения. Следующие две команды read читают из файла две строки текста. Вторая команда exec восстанавливает указатель на стандартный входной поток: теперь он связан с файлом stdin, а не stock.txt. Завершающие команды echo отображают на экране содержимое прочитанных строк, которые были сохранены в переменных line1 и line2.

Предположим, файл stock.txt содержит такой текст:

$ cat stock.txt

Crayons Assorted 34

Pencils Light 12

Ниже показаны результаты работы сценария:

$ f_desc

Crayons Assorted 34

Pencils Light 12

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

5.10. Заключение

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

ГЛАВА 6

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

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

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

Интерпретатор shell располагает операторами && и, которые группируют команды по принципу логического И/ИЛИ. Существуют также операторы () и {}, объединяющие заключенные в них команды в группу, выполняемую в текущем или порожденном интерпретаторе shell.

6.1. Оператор &&

Общий формат оператора && таков:

команда1 && команда2

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

Рассмотрим простой пример:

$ cp justice.doc justice.bak && echo "копирование прошло успешно"

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

Сообщение, заданное в команде echo, появилось на экране, значит, команда cp успешно выполнила копирование файла. А вот более практичный пример:

$ mv /apps/bin /apps/dev/bin && rm -r /apps/bin

Каталог /apps/biп с помощью команды mv перемешается в каталог /apps/dev/bin. Если перемещение завершится удачно, то исходный каталог - /apps/biп — будет удален.

В следующем примере команда sort сортирует содержимое текстового файла quarter_end.txt, записывая результат в файл quarter, sorted. Если запись прошла успешно, полученный файл выводится на печать с помощью команды lp.

$ sort quarter_end.txt > quarter.sorted && lp quarter. sorted

6.2. Оператор ||

Рассмотрим общий формат оператора | |:

команда1 || команда2

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

Приведем простой пример, иллюстрирующий применение оператора | |:

$ cp wopper.txt oops.txt || echo "копирование не выполнено"

cp: wopper.txt: No such file or directory копирование не выполнено

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

В следующем примере из файла acc.qtr извлекаются первое и пятое поля, а результат помещается во временный файл qtr.tmp. Если по какой‑то причине извлечь нужные данные не удастся, пользователь dave получит электронное сообщение.

$ awk ' {print$1,$5}' acc.qtr > qtr.tmp || echo "Получить данные не удалось." | \

mail dave

6.3. Группирование команд с помощью скобок

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

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

{команда1; команда2; …}

Чтобы выполнить группу команд в порожденном интерпретаторе shell, поступите аналогичным образом, но вместо фигурных скобок поставьте круглые скобки:

(команда1; команда2; …)

Сам по себе подобный метод применяется не часто. Обычно группа команд выполняется в составе более крупных конструкций с операторами && или. Результатом работы группы команд считается результат выполнения последней команды в группе.

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

$ comet month_end.txt || exit

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

$ comet month_end.txt || (echo "Привет! Твой сценарий не работает." | \

mail dave; exit)

Символ '\' в конце первой строки означает, что команда будет продолжена в следующей строке.

Вспомним рассмотренный ранее пример, в котором команда sort сортирует содержимое текстового файла quarter_end.txt, записывая результат в файл quarter.sorted с последующим выводом его на печать:

$ sort quarter_end.txt > quarter.sorted && lp quarter.sorted

Применив метод группировки команд, можно одновременно с печатью файла скопировать его в каталог /logs.

$ sort quarter_end.txt > quarter.sorted && \

(cp quarter.sorted /logs/quarter.sorted; lp quarter.sorted)

6.4. Заключение

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

ЧАСТЬ 2

Фильтрация текста

ГЛАВА 7

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

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

В этой главе рассматриваются следующие темы:

   • создание шаблонов для поиска выражений, стоящих в начале или в конце строки;

   • создание шаблонов для поиска символов, встречающихся неопределенное число раз;

   • создание шаблонов для поиска специальных символов;

   • создание шаблонов для поиска символов из указанного набора или диапазона;

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

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

Регулярные выражения в той или иной форме используются всеми основными текстовыми редакторами и утилитами, выполняющими фильтрацию текста. К сожалению, наборы поддерживаемых выражений несколько различаются от программы к программе, но существуют так называемые базовые регулярные выражения, которые во всех программах обрабатываются одинаково. Именно их мы и рассмотрим в настоящей главе. Единственное исключение -oператор \{ \}, который поддерживается в программах sed и grep, но не в awk.

В табл. 7.1 перечислены метасимволы и операторы, применяемые в базовых регулярных выражениях.

Таблица 7.1. Метасимволы и операторы базовых регулярных выражений

^ Соответствует началу строки
$ Соответствует концу строки
[] Соответствует любому символу из числа заключенных в скобки; чтобы задать диапазон символов, укажите первый символ диапазона, дефис и последний символ (например, вместо шаблона [12345] можно ввести
[1-5])
[^] Соответствует любому символу, кроме тех, что указаны в скобках
\ Отменяет специальное значение следующего за ним метасимвола
. Соответствует любому отдельному символу
* Указывает на то, что предыдущий шаблон встречается ноль или более раз; в программах awk и egrep, где испольуются расширенные регулярные выражения, существует два дополнительных оператора:
? (означает, что предыдущий шаблон встречается не более одного раза) и
+ (означает, что предыдущий шаблон встречается один или более раз)
\{n\} Указывает на то, что предыдущий шаблон встречается ровно n раз
\{n,\} Указывает на то, что предыдущий шаблон встречается не менее п раз
\{,m\} Указывает на то, что предыдущий шаблон встречается не более т раз
\{n, m\} Указывает на то, что предыдущий шаблон встречается не менее n раз и не более т раз

7.1. Поиск одиночных символов с помощью метасимвола '.'

Метасимвол '.' соответствует любому одиночному символу. Если, например, требуется найти слово, начинающееся с подстроки "beg", после которой стоит произвольный символ, а за ним -cимвол 'n', задайте шаблон beg.n. Будут найдены такие слова, как "begin", "began" и т. д.

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

…x..x..x

Вот примеры отбора строк режима по этому шаблону:

drwxrwxrw- — не соответствует

-rw‑rw‑rw- — не соответствует

-rwxrwxr‑x -cоответствует

-rwxr‑xr‑x -cоответствует

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

….ХС….

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

1234ХС9088 -cоответствует

4523ХХ9001 — не соответствует

0011ХА9912 — не соответствует

9931ХС3445 -cоответствует

7.2. Поиск выражений в начале строки с помощью метасимвола '^'

Метасимвол '^' позволяет искать слова или символы, стоящие в начале строки. Например, благодаря шаблону ^d можно отобрать из списка, выводимого командой ls -l, только те записи, которые соответствуют каталогам:

drwxrwxrw- - соответствует

-rw‑rw‑rw- - не соответствует

drwxrwxr‑x -cоответствует.

-rwxr‑xr‑x — не соответствует

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

1234ХС9088 — не соответствует

4523ХХ9001 — не соответствует

0011ХА9912 -cоответствует

993IXC3445 — не соответствует

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

^…1

В результате получим:

1234ХС9088 — не соответствует

4b23ХХ9001 — не соответствует

0011XA9912 -cоответствует

993ДХС3445 -cоответствует

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

^comp

Давайте немного усложним этот шаблон. Предположим, после символов "comp" могут идти любые две буквы, но завершать последовательность должны символы "ing":

^comp..ing

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

7.3. Поиск выражений в конце строки с помощью метасимвола '$'

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

trouble$

Следующий шаблон соответствует пустой строке, не содержащей символов:

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

7.4. Поиск символов, встречающихся неопределенное число раз, с помощью метасимвола '*'

Метасимвол '*' означает, что предыдущий символ в регулярном выражении либо отсутствует, либо встречается произвольное число раз подряд (1, 2 и т. д.). Например, шаблон

сотрu*t

отвечает таким словам:

computer

computing

compuuuuute

А шаблон

10133*

соответствует следующему:

101333

10133

10134

7.5. Поиск специальных символов с помощью метасимвола '\'

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

$ . ' " * [ ] ^ | ( ) \ + ? { }

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

\.

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

\*\.pas

7.6. Поиск символов, входящих в заданный набор или

диапазон

Шаблон [] соответствует списку или диапазону символов, указанных в квадратных скобках.

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

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

[0123456789]

Однако проще задать диапазон:

[0-9]

Следующий шаблон соответствует любой строчной букве:

[a‑z]

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

[A‑Za‑z]

Здесь формируется список из двух диапазонов: прописные буквы от 'А' до 'Z' и строчные буквы от 'а' до 'z'.

Представленный ниже шаблон соответствует любому алфавитно–цифровому символу:

[A‑Za‑z0-9]

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

s[a‑zA‑Z]t

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

s[a‑z]t

Чтобы найти слово "computer" независимо от того, расположено оно в начале предложения или нет, примените такой шаблон:

[Сс]omputer

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

[S,s]ystem\.

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

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

[A‑Za~z]*

Метасимвол '^' после открывающей квадратной скобки — это признак того, что шаблон соответствует любым символам, кроме указанных в скобках. Так, шаблон

[^a‑zA‑Z]

соответствует всем символам, кроме букв, а шаблон отвечает всем символам, которые не являются числами.

7.7. Поиск символов, встречающихся заданное число раз

Метасимвол '*' позволяет находить символы, встречающиеся несколько раз подряд, но число повторений при этом не определяется. Если же необходимо в процессе поиска учитывать точное количество последовательных вхождений символа в строку, следует применить шаблон \{ \}. Существует четыре варианта этого шаблона:


шаблон\{n\} Соответствует шаблону, встречающемуся ровно n раз подряд
шаблон\(n,\} Соответствует шаблону, встречающемуся не менее n раз подряд
шаблон\{,m\} Соответствует шаблону, встречающемуся не более m раз подряд
шаблон\{n, m\} Соответствует шаблону, встречающемуся не менее n и не более m раз подряд, где n и m — целые числа из интервала от 0 до 255

Представленный ниже шаблон соответствует последовательности из двух букв 'А', за которыми следует буква 'В':

А\{2\}В

В результате получим "ААВ".

В следующем шаблоне задано, что буква 'А' встречается не менее четырех раз подряд:

А\{4,\)В

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

А\{2,4\}В

Будут найдены строки "ААВ", "АААВ", "ААААВ", но не "АВ" или "АААААВ". Вернемся к уже рассматривавшемуся примеру фильтрации текстового файла, фрагмент которого представлен ниже:

1234ХС9088 4523XX90D1 0011ХА9912 9931ХС3445

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

[0-9]\{4\}ХХ[0-9]\(4\}

Применив этот шаблон к приведенному выше фрагменту, получим:

1234ХС9088 - не соответствует

4523XX900i - соответствует

0Q11XA9912 - не соответствует

9931ХС3445 - не соответствует

7.8. Примеры

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

Таблица 7.2. Полезные регулярные выражения


^the Соответствует строкам, которые начинаются символами "the"
[Ss]igna[lL] Соответствует словам "signal", "signaL", "Signal" и "SignaL"
[Ss]igna[lL]\. То же, что и в предыдущем случае, но слово, к тому же, должно завершаться точкой
tty$ Соответствует строкам, которые завершаются символами "tty"
^USER$ Соответствует слову "USER", которое является единственным в строке
\. Соответствует точке
^d..x..x..x Соответствует каталогам с установленным правом на выполнение для владельца, группы и других пользователей
^[^l] Исключает из списка файлов записи, соответствующие символическим ссыпкам
00* Находит строки, содержащие два или больше нулей подряд
[lL] Соответствует прописной и строчной букве 'l'
[iInN] Соответствует прописным и строчным буквам 'i' и 'n'
^S Соответствует пустой строке
^.*$ Соответствует строке, состоящей из любого числа символов
^……$ Соответствует строке, состоящей из шести символов
[a‑zA‑Z] Соответствует любой прописной или строчной букве
[a‑z][a‑z]* Соответствует по крайней мере одной строчной букве
[^0-9\$] При рассмотрении цифры и знаки доллара не учитываются
[^0-9A‑Za‑z] При рассмотрении не учитываются буквы и цифры
[123] Соответствует цифрам 1, 2 и 3
[Dd]evice Соответствует словам "Device" и "device"
De..ce Соответствует слову, в котором первые два символа — "De", за ними идут любые два символа, а затем -cимволы "се"
\^q Соответствует символам "^q"
^.$ Соответствует строке, содержащей только один символ
^\.[0-9][0-9] Соответствует строке, которая начинается с точки и двух цифр
"Device" Соответствует слову "Device"
De[Vv]ice\. Соответствует слову "DeVice" или "Device", после которого стоит точка
[0-9]\{2\}-[0-9]\ {2\}-[0-9]\{4\} Соответствует шаблону даты в формате dd‑mm‑yyyy
[0-9]\{3\}\.[0-9]\{3\}\ .[0-9]\{3\}\.[0-9]\{3\} Соответствует шаблону IP–адреса в формате ппп. ппп. ппп. ппп

7.9. Заключение

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

В следующих главах мы рассмотрим примеры применения регулярных выражений в программах grep, sed и awk.

ГЛАВА 8

Семейство команд grep

Команда grep (global regular expression print — печать глобальных регулярных выражений) является наиболее известным инструментальным средством в UNIX и Linux. Она выполняет в текстовых файлах или стандартном входном потоке поиск выражений, соответствующих шаблону, с последующим отображением результата на экране. Команда grep может работать как с базовыми, так и с расширенными регулярными выражениями. Существует три разновидности этой команды:

   • grep -cтандартный вариант, которому уделено основное внимание в данной главе.

   • egrep -pаботает с расширенными регулярными выражениями (не поддерживает только оператор \ { \}).

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

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

grep, и с этой ролью, в принципе, справляется GNU–версия grep. К сожалению,

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

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

   • параметры команды grep;

   • применение регулярных выражений в команде grep;

   • особенности поиска алфавитно–цифровых символов.

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

Прежде чем вы приступите к прочтению главы, создайте представленный ниже файл с именем data.f, содержащий информацию о заказах товаров. Структура записей этого файла такова:

1–й столбец — код города;

2–й столбец — код месяца, когда был сделан заказ;

3–й столбец — код заказа, включающий год, когда он был сделан;

4–й столбец — код товара;

5–й столбец — цена за единицу товара;

6–й столбец — код фирмы;

7–й столбец — количество заказанного товара.


$ cat : data .f

48 dec 3ВС1997 LPSX 68.00 LVX2A 138

483 sept 5AP1996 USP 65.00 LVX2C 189

47 oct 3ZL1998 LPSX 43.00 KVM9D 512

219 dec 2CC1999 CAD 23.00 PLV2C 68

484 nov 7PL1996 CAD 49.00 PLV2C 234

483 may 5PA1998 USP 37.00 KVM9D 644

216 sept 3ZL1998 USP 86.00 KVM9E 234

Разделителем полей является символ табуляции.

8.1. Команда grep

Общий формат команды grep таков:

grep [параметры] базовое_регулярное_выражение [файл]

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

8.1.1. Употребление кавычек

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

Если образец поиска состоит из какой‑нибудь системной переменной, например $PATH, рекомендуется тоже взять ее в двойные кавычки. Это связано с тем, что, прежде чем передавать аргументы команде grep, интерпретатор shell выполняет подстановку переменных, и команда grep получает значение переменной, которое может содержать пробелы. В этом случае будет выдано то же сообщение об ошибке, о котором говорилось в предыдущем абзаце.

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

8.1.2. Параметры команды grep

Ниже перечислены основные параметры команды grep:


-c Задает отображение только числового значения, указывающего, сколько строк соответствуют шаблону
-i Дает указание игнорировать регистр символов
-h Подавляет вывод имен файлов, включающих найденные строки (по умолчанию в выводе команды grep каждой строке предшествует имя файла, в котором она содержится)
-1 Задает отображение только имен файлов, содержащих найденные строки
-n Задает нумерацию выводимых строк
-s Подавляет вывод сообщений о несуществующих или нетекстовых файлах
-v Задает отображение строк, не соответствующих шаблону

8.1.3. Поиск среди нескольких файлов

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

$ grep sort *.doc

Следующая команда осуществляет поиск фразы "sort it" во всех файлах текущего каталога:

$ grep "sort it" *

8.1 4. Определение числа строк, в которых найдено совпадение

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

$ grep -с "48" data.f

4

Команда grep возвращает число 4. Это означает, что в файле data.f обнаружены 4 строки, содержащие последовательность символов "48". Следующая команда отображает эти строки:

$ grep "48" data.f

48 dec 3BC1997 LPSX 68.00 LVX2A 138

453 sept 5AP1996 USP 65.00 LVX2C 189

484 nov 7PL1996 CAD 49.00 PLV2C 234

4 83 may 5PA1998 USP 37.00 KVM9D 644

8.1.5. Вывод номеров строк

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

$ grep -n "48" data.f

1: :48 dec 3BC1997 LPSX 68.00 LVX2A 138

2: :483 sept. 5AP1996 USP 65.00 LVX2C 189

5: :484 nov 7PL1996 CAD 49.00 PLV2C 234

6: :483 may 5PA1998 USP 37.00 KVM9D 644

Номера строк отображаются в первом столбце.

8.1.6. Поиск строк, не соответствующих шаблону

Благодаря опции -v можно отобрать те строки, которые не соответствуют шаблону. Следующая команда извлекает из файла data.f строки, не содержащие последовательность символов "48":

$ grep -v "48" data.f

47 oct 3ZL1998 LPSX 43.00 KVM9D 512

219 dec 2CC1999 CAD 23.00 PLV2C 68

216 sept 3ZL1998 USP 86.00 KVM9E 234

8.1.7. Поиск символов на границе слов

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

$ grep "48<tab>" data.f

48 Dec 3BC1997 LPSX 68.00 LVX2A 138

Здесь запись <tab> означает нажатие клавиши [Tab].

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

$ grep '48\>' data.f

48 Dec ЗВС1997 LPSX 68.00 LVX2A 138

8.1.8. Игнорирование регистра символов

По умолчанию команда grep чувствительна к изменению регистра символов. Чтобы провести поиск без учета регистра, воспользуйтесь опцией -i. В файле data.f обозначение месяца Sept встречается как в верхнем, так и в нижнем регистре. Поэтому для отбора строк обоих видов следует применить такую команду:

$ grep -i "sept" data.f

483 Sept 5AP1996 USP 65.00 LVX2C 189

216 sept 3ZL1998 USP 86.00 KVM9E 234

8.2. Команда grep и регулярные выражения

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

8.2.1. Выбор символов из списка

В предыдущей главе мы уже отмечали, что с помощью оператора [ 3 можно задать диапазон или список символов, включаемых в шаблон поиска. Предположим, требуется извлечь из файла data.f строка заказов, сделанных в городах, код которых равен 483 или 484. Поставленную задачу решает следующая команда:

$ grep '48[34]' data.f

493 Sept 5AP1996 USP 65.00 LVX2C 189

484 nov 7PL1996 CAD 49.00 PLV2C 234

483 may 5PA1998 asp 37.00 KVM9D 644

8.2.2. Инверсия шаблона с помощью метасимвола ""

Следующая команда находит в файле data.f строки, не начинающиеся с цифры 4 или 8:

$ grep '^[^48]' data.f

219 dec 2СС1999 CAD 23.00 PLV2C 68

216 sept 321Л998 USP 86.00 KVM9E 234

Символ '^' заключенный в квадратные скобки, говорит о том, что шаблон соответствует любому символу, кроме цифр 4 и 8. Символ 'Л' в начале шаблона — это признак того, что поиск производится с начала каждой строки.

8.2.3. Шаблон, соответствующий любому символу

Предположим, в файле data.f требуется найти коды фирм, которые начинаются на букву 'К' и заканчиваются буквой 'D'. Реализуется это следующим образом:

$ grep 'K…D' data.f

47 Oct 3ZL1998 LPSX 43.00 KVM9D 512

483 may 5PA199S USP 37.00 KVW9D 644

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

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

$ grep '[A‑Z][A‑Z]..C' data.f

483 Sept 5AP1996 USP 65.00 LVX2C 189

219 dec 2CC1999 CAD 23.00 PLV2C 68

484 nov 7PL1996 CAD 49.00 PLV2C 234

8.2.4. Поиск по дате

Представленная ниже команда находит все заказы, которые были сделаны в 1996 или 1998 году и коды которых начинаются с цифры 5:

$ grep '5..199[68]' data.f

483 Sept 5АР1996 USP 65.00 LVX2C 189

483 may 5РА1998 USP 37.00 KVM9D 644

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

Поиск всех заказов, сделанных в 1998 году, выполняется посредством команды:

$ grep '[0-9]\{3\}8' data.f

47 Oct 3ZL1998 LPSX 43.00 KVM9D 512

483 may 5PA1998 USP 37,00 KVM9D 644

216 sept 3ZL1998 USP 86.00 KVM9E 234

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

8.2.5. Комбинированные диапазоны

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

$ grep ' [0-9][0-5][0-63' data.f

47 Oct 3ZL1998 LPSX 43.00 KVM9D 512

484 nov 7PL1996 CAD 49.00 PLV2C 234

483 may 5PA199B USP 37.00 KVM9D 644

216 sept 3ZL1998 USP 86.00 KVM9E 234

Как видите, отображается больше информации, чем нам необходимо. Значит, в. шаблоне поиска недостаточно уточнен критерий отбора информации. Очевидно, следует указать, что поиск нужно начинать в начале строки. Для этого применим метасимвол'^':

$ grep '^[0-9][0-5][0-6]' data.f

216 sept 3ZL1998 USP 86.00 KVM9E 234

8.2.6. Поиск повторяющихся последовательностей

Если необходимо найти какую‑либо строку, которая содержит цифру 4, повторенную минимум дважды, задайте такую команду:

$ grep '4\{2,\}' data.f

483 may 5PA1998 USP 37.00 KVM9D 644

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

$ grep '9\{3,\}' data.f

219 dec 2СС1999 CAD 23.00 PLV2C 68

Иногда точное число повторений символа не известно. В таких случаях окажется полезной команда со следующей структурой:

$ grep '8\{2,6\}3' myfile

83 — не соответствует

888883 -cоответствует

8884 — не соответствует

88883 -cоответствует

Здесь задан поиск строк, в которых цифра 8 встречается от двух до шести раз подряд и предшествует цифре 3.

8.2.7. Выбор из нескольких шаблонов

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

$ grep -E '219|216' data.f


219 dec 2СС1999 CAD 23.00 PLV2C 68

216 sept 3ZL1998 USP 86.00 KVM9E 234

8.2.8. Поиск пустых строк

Для поиска в файле пустых строк можно составить шаблон из метасимволов '^' и '$':

$ grep '^$' myfile

8.2.9. Поиск специальных символов

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

$ grep '\.' myfile

Следующая команда ищет в файле myfile двойные кавычки:

$ grep "\" ' myfile

А вот эта команда отбирает в листинге команды ls -l запись, соответствующую файлу control.conf.

$ ls -l | grep 'control\.conf'

8.2.10. Поиск имен файлов, соответствующих заданному формату

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

$ grep '[а–z]\{1,6\}\.[А–Z]\{1,2\}' filename.deposit

yrend. AS - соответствует

mothdf — не соответствует

sca.PP - соответствует

qp.RR - соответствует

8.2.11. Поиск IP–адресов

Администратору DNS–сервера приходится поддерживать большое количество IP–адресов, относящихся к различным сетям. В моей системе файл ipfile может содержать свыше 200 адресов. Мне часто приходится выполнять поиск всех адресов в формате "nnn.nnn" (т. е. адресов, содержащих две трехзначные последовательности, оканчивающиеся точкой). Для этой дели я пользуюсь следующей командой:

S grep '[0-9]\{3\}\.[0-9]\{3\}\.' ipfile

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

Предположим, имеется такой файл:

$ cat testfile

looks likes looker long

Следующая команда находит в нем слова, начинающаяся с буквы 'l', после которой идет произвольное число символов, а за ними — буква 's':

$ grep 'l.*s' testfile

looks likes

Показанная ниже команда отбирает слова, начинающиеся с буквы 'l', после которой идет произвольное число символов, затем — буква 'к', а после нее — еще один символ:

$ grep 'l.*k.' testfile

looks likes

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

$ grep 'ооо*' testfile

looks

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

$ grep 'device$' *

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

8.3. Классы символов

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

Таблица 8.1. Основные классы символов и эквивалентные им регулярные выражения


Класс Эквивалентное регулярное выражение
[:upper:] [A‑Z]
[:lower:] [a‑z]
[:digit:] [0-9]
[:alnum:] [0-9a‑zA‑Z]
[:space:] символы пробела
[:alpha:] [a‑zA‑Z]

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

Рассмотрим несколько примеров на базе нашего файла data.f. Предположим, требуется найти все коды заказов, которые содержат цифру 5, сопровождаемую по крайней мере двумя буквами в верхнем регистре:

$ grep '5[[:upper:]][[:upper]] ' data.f

483 Sept 5АР1996 USP 65.00 LVX2C 189

483 may 5РА1998 USP 37.00 KVM9D 644

Вот как осуществляется поиск всех кодов товара, которые оканчиваются буквой 'P' или 'D':

$ grep '[[:upper:]][[:upper:]][PD]' data.f

483 Sept 5AP1996 USP 65.00 LVX2C 159

219 dec 2CC1999 CAD 23.00 PLV2C 68

484 nov 7PL1996 CAD 49.00 PLV2C 234

483 may 5PA1998 USP 37.00 KVM9D 644

216 sept 3ZL1998 USP 86.00 KVM9E 234

8.4. Дополнительные примеры использования команды grep

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

8.4.1. Фильтрация списка файлов

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

$ 1s -l | grep '^d'

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

S ls -l | grep '^[d]'

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

$ ls -1 | grep '^d.....x..x'

8.4.2. Подавление вывода сообщений об ошибках

Допустим, вы хотите найти запись пользователя louise в системном файле паролей:

$ grep louise /etc/passwd

louise:lxAL6GW9G.ZyY:501:501:Accounts Sect1С:/home/accts/louise:/bin/sh

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

$ grep louise /etc/password

grep: /etc/password: No such file or directory

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

$ grep louise /etc/*

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

В подобной ситуации можно воспользоваться опцией -s, которая подавляет вывод сообщений об ошибках:

$ grep -a louise /etc/*

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

$ grep louise /etc/* 2> /dev/null

Эта команда направляет поток ошибок (2>) в системную корзину (устройство /dev/null). На жаргоне системных администраторов это устройство называется битодробилкой.

8.4.3. Фильтрация списка процессов

Совместное применение команд grep и ps а позволяет выяснить, выполняется ли в системе некоторый процесс. Опция а команды ps задает вывод списка всех процессов, включая процессы других пользователей. Например, следующая команда проверяет, выполняется ли в данный момент процесс named:

$ ps а | grep named

211 ? S 4.56 named

303 3 s 0.00 grep named

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

$ ps ах | grep named | grep -v "grep"

211 ? S 4.56 named

8.5. Команда egrep

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

$ cat grepstrings

484

47

$ egrep -f grepstrings data.f

В этом случае в файле data.f осуществляется поиск записей, которые содержат последовательность символов "484" или "47". Создание файла шаблонов удобно в том случае, когда шаблонов очень много и вводить их в командной строке затруднительно. Если шаблонов немного, воспользуйтесь метасимволом '|', который позволяет сделать выбор между несколькими шаблонами. Например, следующая команда ищет в файле data.f записи, содержащие последовательность символов "3ZL" или "2СС":

$ egrep '3ZL|2CC' data.f

47 Oct 3ZL1998 LPSX 43 .00 KVM9D Ы2

219 dec 2СС1999 CAD 23 .00 PLV2C 68

216 sept 3ZL1998 USP 86 .00 KVM9E 234

Метасимвол '|' можно применять более одного раза. Например, чтобы узнать, зарегистрированы ли в системе пользователи louise, matty и pauline, выполните команду who и направьте результаты ее работы команде egrep:

$ who | egrep 'louise|matty|pauline'

louise pty8

mattу tty02

pauline pty2

Круглые скобки позволяют представить выражение с несколькими шаблонами как один шаблон. Так, с помощью представленной ниже команды можно найти в текущем каталоге файлы, в которых встречаются слова из ряда "shutdown", "shutdowns", "reboot" и "reboots":

$ egrep '[shutdown|reboot]s?' *

8.6. Заключение

В настоящей главе продемонстрированы лишь некоторые из многочисленных возможностей команды grep, которая является универсальным инструментом фильтрации, очень популярным среди пользователей UNIX и Linux. Существует несколько ее разновидностей, которые в некоторых системах заменены единой GNU–командой grep. Как будет показано далее в этой книге, команда grep также является важным инструментом shell–программирования, используемым в связке с другими утилитами UNIX.

ГЛАВА 9

Утилита awк

При форматировании отчетов и извлечении информации из больших текстовых файлов неоценимую помощь оказывает утилита awk, которая обладает мощными средствами обработки текста. Как показывает опыт, среди всех инструментов фильтрации, имеющихся в интерпретаторе shell, труднее всего освоить работу именно с awk. Причина этого явления не ясна. Может быть, дело в синтаксисе утилиты или не совсем понятных сообщениях об ошибках, таких как bailing out и awk: cmd. line:. Подобные сообщения довольно часто встречаются при программировании на языке awk. Именно так — язык awk, поскольку это совершенно самостоятельный язык программирования. Возможно, изучать его нелегко, но его совместное применение с другими инструментальными средствами, такими как команда grep и редактор sed, позволяет значительно упростить программирование в интерпретаторе shell.

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

   • выборка текстовых полей;

   • использование регулярных выражений;

   • сравнение текстовых полей;

   • передача параметров утилите awk;

   • базовые команды и сценарии awk.

Название утилиты awk составлено из начальных букв фамилий разработчиков языка: Ахо (Aho), Вайнбергера (Weinberger) и Кернигана (Kernighan). Существуют также утилиты nawk и gawk, обладающие усовершенствованными возможностями обработки текста. Но эти утилиты здесь не рассматриваются.

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

9.1. Вызов awk

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

awk [-F разделитель_полей] 'сценарий' входной_файл…

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

Задавать разделитель полей с помощью опции -F не обязательно, так как по умолчанию утилита awk использует для этих целей пробел. Но, например, в файле /etc/passwd поля отделяются друг от друга двоеточием. В данном случае вызов утилиты выглядит так:

awk -F: 'сценарий' входной_файл…

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

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

awk -f файл_сценария входной_файл…

Опция -f свидетельствует о том, что инструкции awk содержатся в файле сценария.

Утилита awk анализирует информацию, содержащуюся в одном или нескольких входных файлах.

9.2. Сценарии

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

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

Таблица 9.1. Образец анализа входного файла


Поле 1 Разделитель Поле 2 Разделитель Поле 3 Разделитель Поле 4 и символ новой строки.
P. Bunny (запись 1) # 02/99 # 48 # Yellow\n
J. Troll (запись 2) # 07/99 # 4842 # Brovm-3\n

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

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

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

9.2.2. Работа с полями и записями

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

$1, $3

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

$1, $2, $3, $4, $5

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

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

Чтобы вывести на экран содержимое записи, задайте в теле процедуры команду print со списком нужных полей.

Тестовый файл

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

$ cat grade.txt


M. Tansley 05/99 48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

Назначение полей таково:

1 — имя;

2 — дата получения пояса;

3 — порядковый номер ученика;

4 — полученный пояс;

5 — возраст;

6 — текущий рейтинг;

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

Поля разделяются пробелами, поэтому при вызове утилиты awk можно не указывать опцию -F.

Сохранение выходных данных

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

$ awk '{print $0}' grade.txt > grade.out

При этом выходные данные не будут отображаться на экране.

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

$ awk '{print $0}' grade.txt | tee grade.out

Отображение всех записей

В приведенной ниже команде утилита awk просматривает файл grade.txt и, поскольку шаблон не указан, отображает содержимое всех записей:

$ awk '{print $0}' grade.txt


M. Tansley 05/99 48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
S. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

Отображение отдельных полей всех записей

Предположим, требуется отобразить на экране только имена спортсменов и названия поясов, которыми они обладают. Соответствующие данные хранятся в полях $1 и $4, поэтому введем такую команду:

$ awk '{print $1, $4}' grade.txt

M. Tansley Green

J. Lulu green S. Bunny Yellow J. Troll Brown-3

L. Tansley Brown-2

Отображение заголовка отчета

Результат работы предыдущей команды выглядит не слишком привлекательно. Рассмотрим, какие шаги можно предпринять, чтобы улучшить его. Прежде всего выровняем границы полей посредством символов табуляции. Табуляция создается с помощью Escape–последовательности \t (об управляющих последовательностях речь пойдет ниже). Кроме того, для придания отчету солидности добавим к нему заголовок, включающий названия полей, а также разделительную линию, которая отображается

в отдельной строке благодаря Escape–последовательности \n.: Заголовок отчета формируется в процедурной части шаблона begin.

$ awk 'BEGIN {print "Name Belt\n -"} \

{print $1 " \t" $4}' grade.txt

Name Belt
M. Tansley Green
J. Lulu green
P. Bunny Yellow
J. Troll Brown-3
L. Tansley Brown-3

Отображение резюме отчета

Чтобы добавить в конец отчета строку "end‑of‑report", следует воспользоваться шаблоном end. Этот шаблон употребляется для обозначения действий, которые выполняются после обработки последней записи входного файла.

$ awk 'BEGIN {print "Name\n "} {print $1} \

END {print "\nend‑of‑report"}' grade.txt

Name

M. Tansley

J. Lulu

P. Bunny , .

J. Troll L. Tansley

end‑of‑report

Обработка сообщений об ошибках

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

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

$ awk 'BEGIN {print "Name\n "} {print $1} \

END {print "\nend‑of‑report}' grade.txt

awk: cmd. line:2: END {print "\nend‑of‑report}

awk: end. line:2: ^ unterminated string

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

   • убедитесь, что сценарий awk целиком заключен в одинарные кавычки;

   • удостоверьтесь, что все кавычки внутри сценария являются парными;

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

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

$ awk 'END {print "End‑of‑report"}' grades.txt

awk: cmd. line":2: fatal: cannot open file 'grades.txt' for reading (No such file or directory)

Ввод данных с клавиатуры

Давайте посмотрим, что произойдет, если не указать файл grade.txt в командной строке:

$ awk 'BEGIN {print "Name Belt\n "} \

{print $1" \t"$4}'

Name Belt

>

С помощью шаблона begin на экран выводится заголовок отчета, при этом сам отчет пуст, а утилита awk ожидает получения входных данных с клавиатуры (об этом свидетельствует строка приглашения >). Вы должны ввести их вручную. После нажатия клавиши [Enter] введенная строка интерпретируется как входная запись и по отношению к ней выполняются соответствующие инструкции. По завершении ввода данных нажмите [Ctrl+D]. Подобный метод работы применяется довольно редко, поскольку чреват большим количеством опечаток и ошибок.

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

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

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

9.2.4. Метасимволы

Перечисленные ниже метасимволы могут встречаться в регулярных выражениях утилиты awk:

\ ^ $ . [ ] | ( ) * + ?

Следует остановиться на описании двух метасимволов, которые не рассматривались в главе 7, поскольку они специфичны для awk и не применяются в команде grep и редакторе sed.

+ Указывает на то, что предыдущий символ встречается один или несколько раз. Например, выражение /t+/ соответствует одной или нескольким буквам 't', а выражение /[а–z]+/ — любой последовательности строчных букв.

? Указывает на то, что предыдущий символ встречается не более одного раза. Например, выражение /xy?z/ соответствует строкам "XYZ" и "XZ".

9.2.5. Операторы

В awk существует достаточно много операторов, манипулирующих числами, строками, переменными, полями и элементами массива. Ниже приведен список основных операторов.


=, += *= /= %= Операторы присваивания (простого и составного)
? ; Условный оператор
|| && ! Логические операторы ИЛИ, И, НЕ
~ !~ Операторы сравнения с регулярным выражением (совпадение, несовпадение(
< <= == != > >= Операторы простого сравнения
+ - * / % Арифметические операторы (сложение, вычитание, умножение, деление, деление по модулю)
++ -- Инкремент и декремент (могут быть префиксными и пост-

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

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

Таблица 9.2. Операторы сравнения утилиты awk


Оператор Проверка
< Меньше
<= Меньше или равно
== Равно
! = Не равно
> Больше
>= Больше или равно
~ Соответствие регулярному выражению (фрагмент строки совпадает с шаблоном)
! ~ Несоответствие регулярному выражению (в строке не обнаружено совпадений с шаблоном)

Проверка на совпадение

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

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

$ awk '{if($3 ~ /Brown/) print $0}' grade.txt

J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

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

$ awk '$0 ~ /Brown/' grade.txt

J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

Приведенная команда означает следующее: если в строке встречается слово "Brown", вывести ее на экран.

Проверка на равенство

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

$ awk '{if($3 ~ /48/) print $0}' grade.txt

М. Tansley 05/99 "48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26

Чтобы найти точное совпадение, воспользуйтесь оператором ==:

$ awk '$3 == "48"' grade.txt

P. Bunny 02/99 48 Yellow 12 35 28

Проверки на несовпадение и неравенство

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

$ awk '$0 !~ /Brown/' grade.txt

M. Tansley 05/99 48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28

He забывайте, что по умолчанию утилита awk выводит на экран все записи, отвечающие указанному критерию отбора, поэтому нет необходимости задавать какое‑либо действие. Если вместо предыдущей команды указать

$ awk '$4 != "Brown"' grade.txt

получим ошибочный результат. Эта команда означает, что требуется найти строки, в которых четвертое поле не равно "Brown". Такому критерию удовлетворяют все строки в файле. Конечно, если нужно найти обладателей поясов, отличных от "Brown-2", можно применить следующую команду:

$ awk '$4 != "Brown-2"' grade.txt

M. Tansley 05/99 48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28
S. Trnll 07/99 4842 Brown-3 12 26 26

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

Проверка "меньше чем"

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

$ awk '{if($6 < $7) print $1 " — try better at the next competition"}' grade.txt

M. Tansley — try better at the next competition J. Lulu — try better at the next competition

Проверка "меньше или равно"

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

$ awk '{if($6 <= $7) print $1}' grade.txt

M. Tansley

J. Lulu J. Troll

Проверка "больше чем"

Следующая команда формирует список лидеров соревнования:

$ awk '{if ($6 > $7) print $1}' grade.txt

L. Tansley P. Bunny

9.2.7. Логические операторы

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

&& И: чтобы результат был истинным, оба операнда должны быть истинными | | ИЛИ: чтобы результат был истинным, хотя бы один из операндов должен

быть истинным! НЕ: результат проверки инвертируется

Оператор логического И

Предположим, перед нами поставлена задача выяснить, кому был присвоен зеленый пояс в мае 1999 года. Строки, отвечающие этому условию, должны в первом поле содержать значение "05/99", а во втором — "Green":

$ awk '{if($2 == "05/99" && $4 == "Green") print $0}' grade.txt

M. Tansley 05/99 48311 Green 8 40 44

Оператор логического ИЛИ

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

$ awk '{if ($4 == "Yellow" ) || $4 ~ /Brown/} print $0}' grade.txt

P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 , 12 30 28

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

$ awk '$4 ~ /Yellow|Brown/' grade.txt

P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

9.2.8. Операторы присваивания и арифметические операторы

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

Создание локальных переменных

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

имя_переменной= $n

где n -cуществующий номер поля.

В следующем примере мы создадим две переменные: name, содержащую имена учеников (поле 1), и belts, содержащую названия поясов (поле 4). Затем будет произведен поиск учеников, обладающих желтым поясом.

$ awk '(name=$1; belts=$4; if(belts ~ /Yellow/) print name" is belt "belts}' grade.txt

P.Bunny is belt Yellow

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

Проверка значения поля

В следующем примере мы проверим, кто из учеников набрал в соревнованиях менее 27 очков. В первом варианте команды значение поля $6 непосредственно сравнивается с числом 27:

$ awk '$6 < 27' grade.txt

J.Lulu 06/99 48317 green 9 24 26
J.Troll 07/99 4842 Brown-3 12 26 26

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

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

$ awk 'BEGIN {BASELINE=27} {if ($6 < BASELINE) print $0}' grade.txt

J.Lulu 06/99 48317 green 9 24 26

J.Troll 07/99 4842 Brown 3 12 26 26

Изменение значения числового поля

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

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

$ awk '{if($1="M. Tansley") $6=$6-1; print $1, $6}' grade.txt

M.Tansley 39

J.Lulu 24

P.Bunny 35

J.Troll 26

L.Tansley 30

Изменение значения текстового поля

Для изменения значения текстового поля достаточно применить к нему оператор присваивания. Следующая команда при выводе на экран добавляет к имени ученика J. Troll дополнительный инициал:

$ awk '{if($1="J. Troll") $1="J. L.Troll"; print $1}' grade.txt

M.Tansley

J.Lulu

P.Bunny

J.L.Troll

L.Tansley

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

Отображение только измененных записей

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

$ awk '{if($1=="J. Troll") {$1="J. L.Troll"; print $1)}' grade.txt

J.L.Troll

Создание нового поля

Аналогично локальным переменным в сценарии awk можно создавать новые поля. Например, мы можем создать поле $8, содержащее разницу полей $6 и $7 в том случае, если значение в поле $7 больше, чем в поле $6:

$ awk 'BEGIN {print "Name\t\tDifference"} {if($6 < $7) \ {$8=$7-$6; print $1" \t"$8}}' grade.txt

Name Difference

L.Tansley 4

J.Lulu 2

Суммирование столбцов

Для вычисления суммарного рейтинга учеников секции мы создадим переменную tot и с помощью выражения tot+=$6 будем прибавлять к ней значение поля $6 при обработке каждой записи. По завершении обработки записей в процедурной части шаблона end итоговое значение переменной tot будет выведено на экран.

$ awk 'tot+=$6; END {print "Club student total points: " tot}' grade.txt

M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansley 05/99 4712 Brown-2 12 30 28

Club student total points: 155

Вероятно, вы заметили, что утилите awk не было дано указание выводить на экран все записи -oна сделала это сама. Причина такого поведения заключается в том, что выражение tot+=$6 относится к шаблонной части инструкции и не задает критерия отбора строк, т.е. применяется ко всем записям. А поскольку процедурная часть этого шаблона отсутствует, выполняется действие по умолчанию — команда print SO.

Если файл велик, можно не выводить на экран все записи, а лишь отобразить итог. Для этого достаточно взять выражение tot+=$6 в фигурные скобки, чтобы перенести его в процедурную часть инструкции:

$ awk '{tot+=$6}; END {print "Club student total points: " tot}' grade.txt

Club student total points: 155

Суммирование размеров файлов

При просмотре содержимого каталога часто требуется узнать общий размер всех файлов в нем, исключая файлы в подкаталогах и скрытые файлы. Алгоритм решения этой задачи таков: результаты работы команды ls -l (формирует список файлов с расширенной информацией о них; см. главу 1) направляются утилите awk, которая удаляет записи, начинающиеся с символа 'd' (признак каталога), и вычисляет сумму по 5–му столбцу (содержит размер файла).

Представленная ниже команда отображает список файлов текущего каталога (имя файла берется из 9–го столбца), указывая размер каждого из них, а в конце выводит суммарный размер файлов, накопленный в переменной tot:

$ ls -l | awk '/^[^d]/ {print $9"\t"$5; tot+=$5} END {print "total KB: "tot}' dev_pkg.fail 345 failedlogin 12416

messages 4260
зи1од 12810
utap 1856
wtap 7104
total KB: 38791

Если необходимо включить в список скрытые файлы, следует вместо команду ls -l задать команду 1s -la.

9.2.9. Встроенные переменные

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

Таблица 9.3. Встроенные переменные awk

Переменная Что содержит
ARGC Количество аргументов в командной строке (поддерживается только: в nawk и gawk)
ARGV Массив аргументов командной строки (поддерживается только в nawk и gawk)
ENVIRON Массив переменных среды (поддерживается только в nawk и gawk)
FILENAME Имя файла, обрабатываемого в текущий момент
FNR Количество уже обработанных записей в текущем файле (поддерживается только в nawk и gawk)
FS Разделитель полей во входном потоке (по умолчанию пробел); аналогична опции -F командной строки
NF Количество полей в текущей записи
NR Количество обработанных записей во входном потоке
OFS Разделитель полей в выходном потоке (по умолчанию пробел)
ORS Разделитель записей в выходном потоке (по умолчанию символ новой строки)
RS Разделитель записей во входном потоке (по умолчанию символ
новой строки)

Переменная ARGC хранит число аргументов командной строки, переданной сценарию awk (точнее, nawk или gawk, т. к. эта переменная появилась только в новых версиях утилиты). Переменная argv хранит значения аргументов командной строки. Доступ к нужному аргументу осуществляется с помощью ссылки ARGV[n], где п — порядковый номер аргумента в командной строке.

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

ENVIRON["EDITOR"]=="vi"

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

Переменная fnr хранит номер записи, которую утилита awk обрабатывает в текущий момент; ее значение меньше или равно значению переменной NR, которая отслеживает общее число обработанных записей входного потока. Если сценарий получает доступ более чем к одному файлу, переменная FNR сбрасывается в ноль при открытии каждого нового файла. В переменную NF записывается количество полей текущей записи. Ее значение сбрасывается по достижении конца записи.

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

В переменной ORS хранится разделитель записей в выходном потоке. По умолчанию им является символ новой строки (\n). Переменная RS содержит разделитель записей во входном потоке (в большинстве случаев это тоже символ \n).

Переменные NF, NR и FILENAME

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

$ awk 'END {print NR}' grade.txt

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

$ awk '{print NF, NR, $0} END {print FILENAME}' grade.txt

1 M. Tansley 05/99 48311 Green 8 40 44
2 J. Lulu 06/99 48317 green 9 24 26
3 P. Bunny 02/99 48 Yellow 12 35 28
4 J. Troll 07/99 4842 Brown-3 12 26 26
5 L. Tansley 05/99, 4712 Brown-2 12 30 28

grade.txt

Переменную NF удобно использовать, когда требуется извлечь из путевого имени последнюю часть, т. е. имя файла или каталога. В этом случае необходимо указать, что разделителем полей является символ '/' и использовать ссылку $NF, которая является обозначением последнего поля текущей записи. Например:

$ pwd

/usr/local/etc

$ echo $PWD | awk -F/ '{print $NF}'

Переменная среды $PWD хранит путевое имя текущего каталога.

9.2.10. Встроенные функции работы со строками

Утилита awk располагает набором универсальных функций преобразования строк. В табл. 9.4 перечислены основные из них.

Таблица 9.4. Функции работы со строками

Функция Назначение
gsub(r,.s) Выполняет глобальную замену каждой строки, соответствующей регулярному выражению г, строкой s в пределах текущей записи; появилась в nawk
index(s, t) Возвращает позицию первого вхождения подстроки t в строку s
length(s) Возвращает длину строки s
match(s, r) Проверяет, содержит ли строка s подстроку, соответствующую, регулярному выражению r; появилась в nawk
split(s, a,fs) Разбивает строку s на элементы, разделенные символом fs, и помещает полученные элементы в массив а
sub(r, s) Выполняет замену самой первой строки, соответствующей регулярному выражению r, строкой s в пределах текущей записи; появилась в nawk
substr(s, p[,n]) Возвращает подстроку строки s, начинающуюся с позиции p и имеющую длину n; если аргумент п не задан, концом подстроки считается символ \0 (признак конца строки)

Функция gsub()

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

$ awk 'gsub(4842,4899) {print $0}' grade.txt

J. Troll 07/99 4899 Brown-3 12 26 26

Функция index()

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

$ awk 'BEGIN {print index("Bunny","ny")}' grade.txt

4

Функция length()

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

$ awk '$3=4842 {print length($1)" "$1}' grade.txt

7 J.Troll

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

$ awk 'BEGIN {print length("A FEW GOOD MEN")}'

14

Функция match()

Функция match() позволяет проверить, содержит ли строка заданную подстроку. Последняя может быть представлена как литералом в двойных кавычках, так и регулярным выражением. Если поиск прошел успешно, возвращается число, определяющее позицию, с которой начинается вхождение подстроки в искомую строку. В случае неудачи возвращается ноль. Следующая команда проверяет, содержит ли имя ученика с номером 48317 символ 'u':

$ awk '$3=48317 {print match ($1, "u"), $1} ' grade.txt

4 J. Lulu

Функция split()

Функция split() преобразует переданную ей строку в массив и возвращает число элементов в полученном массиве. В следующем примере заданная строка разбивается на три элемента, которые помещаются в массив myarray. Разделителем элементов в данном случае является символ '#'.

$ awk 'BEGIN {print split("123#456#678", myarray,"#"))'

3

Массив myarray будет иметь такую структуру:

mуarray[1]="123" myarray[2]="4 56"

myarray[3]="678"

Функция sub()

Функция sub() применяется для поиска строки, соответствующей заданному шаблону, и ее замены при первом появлении. В этом состоит отличие данной функции от функции gsub(), которая находит все случаи вхождения подстроки в строку, производя соответствующее число замен. Приведенная ниже команда находит запись ученика J. Troll и меняет его рейтинг с 26 на 29 (поле 6), при этом значение поля 7 (тоже 26) остается неизменным:

$ awk '$1=="J. Troll" {sub(26,29,$0) )' grade.txt

J. Troll 07/99 4842 Brown-3 12 29 26

Функция substr()

Функция substr() возвращает указанную часть строки. Вам нужно задать позицию, с которой начинается вхождение подстроки в искомую строку, и длину подстроки. Рассмотрим пример:

$ awk '$1=="L. Tansley" {print substr($1,1,5))' grade.txt

L. Tan

Эта команда возвращает из строки "L. Tansley" подстроку, начинающуюся с первого символа и занимающую пять символов.

Если значение третьего аргумента значительно превышает реальную длину строки, функция substr () возвращает все символы строки, начиная с указанной позиции:

$ awk '$1=="L. Tansley" {print substr ($1,3, 99) ) ' grade.txt

Tansley

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

$ awk '{print substr($1,3)}' grade.txt

Tansley

Lulu

Bunny

Troll

Tansley

Передача строк из интерпретатора shell утилите awk

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

В первом примере команда echo передает строку "Stand‑by" утилите awk, которая вычисляет ее длину:

$ echo "Stand‑by" | awk '{print length($0)}'

8

Во втором примере утилита awk получает строку с именем файла и возвращает имя файла без расширения:

$ echo "mydoc.txt" | awk '{print substr($STR,1,5)}'

mydoc

Следующая команда возвращает только расширение файла:

$ echo "mydoc.txt" | awk '{print substr($STR,7)}'

txt

9.2.11. Escape–последовательности

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

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

/\|/

В табл. 9.5 перечислены Escape–последовательности, распознаваемые утилитой awk.

Таблица 9.5. Escape–последовательности утилиты awk

\b Возврат на одну позицию (забой)
\f Прокрутка страницы
\n Новая строка
\r Возврат каретки
\t Горизонтальная табуляция
\ddd Восьмеричный код символа
\c Любой другой специальный символ. Например, запись \\ соответствует символу обратной косой черты

В следующей команде сначала отображается фраза "May Day", в которой слова разделены символом табуляции, а затем выводятся два символа новой строки, вследствие чего образуется пустая строка. Потом отображается слово "May", а за ним -cлово "Day", каждая буква которого представлена ASCII–кодом: 'D' — 104, 'а'- 141,'у'- 171.

$ awk BEGIN {print "May\tDay\n\nMay \104\141\171"}'

May Day

May Day

9.2.12. Команда printf

Во всех примерах, с которыми мы ознакомились, данные выводились на экран с помощью команды print без какого‑либо форматирования. В awk имеется намного более мощная команда printf, аналог одноименной функции языка С, позволяющая задавать правила форматирования выходных данных.

Базовый синтаксис команды таков:

printf "строка_формaтирования", аргументы

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

Таблица 9.6. Спецификаторы форматирования

%c Символ ASCI I; если аргумент является строкой, выводится первый символ строки
%d, %i Целое число
%e Число с плавающей точкой в формате [-]d.dddddde[+-]dd
%E Аналогичен спецификатору %e, но знак экспоненты представлен символом 'E', а не 'e'
%f Число с плавающей точкой в формате [~}ddd.dddddd
%G Тип преобразования будет %e или %f в зависимости от того, какой результат короче; вывод незначащих нулей подавляется Аналогичен спецификатору %g, но экспоненциальный формат будет представлен спецификатором %E, а не %e .
%o Восьмеричное число без знака
%s Строка символов
%x Шестнадцатеричное число без знака (используются шестнадцатеричные
цифры а, b, с, d, e, f)
Аналогичен спецификатору %x, но используются шестнадцатеричные
цифры А, В, С, D, E, F
%% Отображается символ '%', интерпретации аргумента не происходит

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

Таблица 9.7. Дополнительные модификаторы

- Аргумент выравнивается по левому краю поля вывода (по умолчанию принято правостороннее выравнивание)
пробел Если аргумент является положительным числом, перед ним ставится
пробел, а если отрицательным — знак минус
+ Если аргумент является числом, ему всегда предшествует знак плюс,
даже если это положительное число
# Выбирается альтернативная форма спецификатора:
%о — восьмеричному числу предшествует ведущий ноль;
%x — шестнадцатеричному числу предшествует запись 0x;
%x — шестнадцатеричному числу предшествует запись Оx;
%e, %E, %f — число всегда содержит десятичную точку;
%g, %G — вывод незначащих нулей не подавляется
0 Если длина поля вывода больше, чем число символов в представлении
аргумента, аргумент дополняется нулями, а не пробелами
ширина Ширина поля вывода; если длина поля больше, чем число символов
в представлении аргумента, аргумент по умолчанию дополняется
пробелами
. точность Точность представления аргумента:
%е, %е, %f — число символов после десятичной точки;
%g, %g — максимальное число значащих цифр;
%d, %o, %i, %u, %x, %X — минимальное число выводимых цифр;
% s — максимальное число выводимых символов

Преобразование символов

Чтобы узнать, ASCII–код какого символа равен 65, можно с помощью команды echo направить строку "65" утилите awk и затем передать ее в качестве аргумента команде printf со спецификатором %с. Команда printf выполнит преобразование автоматически:

$ echo "65" | awk '{printf "%c\n", $0}'

A

Как видите, это символ 'A'. Обратите внимание на наличие в команде символа новой строки (\n). Необходимость в нем объясняется тем, что команда printf по умолчанию не записывает этот символ в выходной поток. Если не указать Escape–последовательность \n, сразу после буквы 'А' в той же строке будет отображено приглашение интерпретатора shell, что может смутить пользователя, работающего в данный момент за терминалом.

Конечно, код символа может быть указан непосредственно в команде printf:

$ awk 'BEGIN {printf "%c\n", 65}'

A

Форматированный вывод

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

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

$ awk '{printf "%-l5s %d\n", $1, $3}' grade.txt

M. Tansley 48311

J. Lulu 48311

P. Bunny 4 8

J. Troll 4842

L. Tansley 4712

9.2.13. Передача переменных утилите awk

Переменные можно создавать не только в сценарии awk, но и непосредственно в командной строке. Формат вызова утилиты awk в этом случае таков:

awk 'сценарий' переменная=значение входной_файл

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

$ awk '{if($5 < AGE) print $0}' AGE=10 grade.txt

M. Tansley 05/99 48311 Green 8 40 44

J. Lulu 06/99 48317 green 9 24 26

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

Имя Число блоков . Занято блоков Свободно блоков Процент Точка монтирования
файловой системы занятых
блоков
Столбец 1 2 3 4 5 6

Чтобы узнать, в какой из имеющихся файловых систем объем свободного пространства ниже критической отметки, следует передать выходные данные команды df утилите awk и последовательно сравнить значения в четвертом столбце с пороговым значением. В следующей командной строке пороговое число задано в виде переменной TRIGGER, которая равна 56000:

$ df -k | awk '$4 ~ /^[0-9]/ {if($4 < TRIGGER) print $1"\t"$4)' TR1GGER=56000

/dos 55808 /apps 51022

Опция — к команды df устанавливает размер блока равным 1 Кб (наиболее привычный для пользователя режим), так как в системе может быть задан другой размер блока. Проверка $4 ~ /^[0-9]/ позволяет отсечь заголовок отчета команды df, содержащий в четвертом столбце строку "Available".

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

отфильтровать результаты работы команды who и узнать к какому терминалу подключены: ,

$ who | awk '{if($1==user) print $1 " you are connected to " $2}' user=$LOGNAME

root you are connected to ttyp1

Здесь локальная переменная user инициализируется значением переменой среды $LOGNAME, которая хранит регистрационное имя текущего пользователя.

9.2.14. Файлы сценариев

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

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

$ awk 'tot+=$6; END {print "Club student total points; " tot}' grade.txt

Создадим на ее основе файл, который назовем student_tot.awk. Расширение awk является общепринятым соглашением относительно именования файлов сценариев awk. Вот текст этого файла:

#!/bin/awk -f

#Все строки комментариев должны начинаться с символа '#'.

#Имя файла: student_tot.awk

#Командная строка: student_tot.awk grade.txt

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

#Сначала выводим заголовок.

BEGIN {

print "Student Date Member Grade Age Points Max"

print "name joined number gained point available"

print "================================================================="

}

# Суммируем рейтинг учеников.

tot+=$6

# В завершение выводим суммарный и средний рейтинг.

END {

print "Club student total points: " tot print "Average club student points: " tot/NR }

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

Ключевым моментом сценария является первая строка, выглядящая как комментарий:

#!/bin/awk -f

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

#!/путь/программа [командная_строка]

Выражение #! называется "магической" последовательностью. Подразумевается, что, во–neрвых, система, в которой запускается, сценарий, распознает эту последовательность, а, во–вторых, указанная программа воспринимает символ '#' как признак комментария. Это справедливо в отношении всех современных интерпретаторов shell, а также программ perl, awk. и sed, для которых чаще всего создаются автономные сценарии.

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

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

   2. Предыдущая командная строка передается новой командной строке в качестве аргумента.

В нашем случае это означает, что при запуске сценария вместо команды

$ student_tot.awk grade.txt

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

$ /bin/awk -f student_tot.awk grade.txt

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

После создания файл student_tot.awk необходимо сделать исполняемым с помощью команды

$ chmod u+x student_tot.awk

Вот результаты работы сценария:

$ student_tot.awk grade.txt

Student name Date joined Member number Grade Age Points gained Max
point available
M. Tansley 05/99 18311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown~2 12 30 28
Club student total points: 155
Average Club Student points: 31

Использование переменной FS в сценариях awk

Если просматривается файл, в котором разделителем полей является символ, отличный от пробела, например '#' или ':', это легко учесть, указав в командной строке опцию ' -F:'

$ awk -F: '{print $0}' входной_файл

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

$ cat passwd.awk

#!/bin/awk -f

# Имя файла: student_tot.awk

# Командная строка: passwd.awk /etc/passwd

# Вывод содержимого первого и пятого полей файла паролей.

BEGIN {

FS=":" }

{print $1"\t"$5}

Вот возможные результаты работы этого сценария:

$ passwd.awk /etc/passwd

root Special Admin login

xdm Restart xda Login

sysadm Regular Admin login

daemon Daemon Login for daemons needing permissions

Передача переменных сценариям awk

При передаче переменной сценарию awk формат командной строки таков:

awk файл_сценария переменная=значение входной_файл

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

$ cat fieldcheck.awk

#!/bin/awk -f

#Имя файла: fieldcheck.awk

#Командная строка: fieldcheck.awk MAX=n [FS=<разделитель>] имя_файла

#Проверка числа полей в записях файла.

{if(NF != МАХ)

print "line " NR " does not have " MAX " fields"}

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

$ fieldcheck.awk МАХ=7 FS=":" /etc/passwd

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

$ cat age.awk

#!/bin/awk -f

# Имя файла: age.awk

# Командная строка: age.awk AGE=n grade.txt

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

{if($5 < AGE) print $0}

$ age.awk AGE=10 grade.txt

M. Tansley 05/99 48311 Green В 4 0 4 4 J. Lulu 06/99 18317 green 9 24 26

9.2.15. Массивы

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

$ awk 'BEGIN {print split("123#456#678", myarray, "#")}'

3

Функция split() возвращает число элементов созданного массива myarray. Внутренняя структура массива myarray в этом случае такова:

myarray[1]="123" myаrray[2]="456" myarray[3]="678"

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

for (элемент in массив) print массив[элемент]

В показанном ниже сценарии функция split() разбивает заданную строку на элементы и записывает их в массив myarray, а в цикле for содержимое массива выводится на экран:

$ cat arraytest.awk

#! /bin/awk -f

#Имя файла: arraytest.awk

#Командная строка: arraytest.awk /dev/null

#Вывод элементов массива.

BEGIN {

record="l23#456#789";

split(record, myarray, "#") } END {

for (i in myarray) print myarray[i]

}

Для запуска сценария в качестве входного файла следует указать устройство /dev/null. Если этого не сделать, утилита awk будет ожидать поступления данных с клавиатуры.

$ arraytest.awk /dev/null

123

789

Статические массивы

В предыдущем примере массив формировался динамически. Следующий пример является более сложным и демонстрирует, как создавать в сценарии статические массивы. Ниже показан входной файл grade_student.txt, включающий информацию об учениках нашей секции каратистов. Записи файла содержат два поля: первое — название пояса, которым владеет ученик, второе — категория ученика (взрослый, юниор). Разделителем полей служит символ '#'.

$ cat grade_student.txt

Vellow#Junior

Orange#Senior

Yellow#Junior

Purple#Junior

Brown-2#Junior

White#Senior

Drange#Senior

Red#Junior

Brown-2#Senior

Yellow#Senior

Red#Junior

Blue#Senior

Green#Senior

Purple#Junior

White#Junior .

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

   1. Сколько учеников имеют желтый, оранжевый и красный пояса?

   2. Сколько взрослых и детей посещают секцию? Рассмотрим такой сценарий:

$ cat belts.awk

#!/bin/awk -f

#Имя файла: belts.awk

#Командная строка: belts.awk grade_student.txt

#Подсчет числа учеников, имеющих желтый, оранжевый и красный пояса,

#а также количества взрослых и юных членов секции.

# Задание разделителя и создание массивов.

BEGIN (

FS="#"

# Создание массива, индексами которого являются

#названия поясов. belt["Yellow"] belt["Orange"] belt["Red"]

#Создание массива, индексами которого являются

#названия категорий учеников. student["Junior"]

student["Senior"]

}

#Если значение первого поля совпадает с индексом массива belt,

#содержимое соответствующего элемента массива увеличивается на единицу. {for(colour in belt)

{if ($1==colour)

belt[colour]++ ) }

# Если значение второго поля совпадает с индексом массива student,

# содержимое соответствующего элемента массива увеличивается на единицу, { for (senior_or_junior in student) { if ($2==senior_or_junior)

student [senior_or_junior] ++

) }

# Вывод полученных результатов

END {

for (colour in belt)

print "The club has", belt[colour], colour, "belts" for(senior_or_junior in student)

print "The club has", 'student[senior_or_junior], \ senior_or_junior, "students" }

В процедурной части шаблона begin в переменную FS записывается символ '#" -pазделитель полей во входном файле. Затем создаются два массива, belt и student, индексами которых являются соответственно названия поясов и названия категорий учеников. Оба массива остаются неинициализированными, их тип не определен.

В первом цикле for значение первого поля каждой записи входного файла сравнивается с названием индекса массива belt (индекс равен "Yellow", "Orange" или "Red"). Если обнаруживается совпадение, выполняется приращение элемента, хранящегося в массиве по этому индексу. Поскольку операция ++ (инкремент) определена для целочисленных значений, утилита awk считает, что массив целочисленный, и инициализирует его элементы значением 0.

Во втором цикле for значение второго поля сравнивается с названием индекса массива student (индекс равен "Junior" или "Senior"). Результат операции вычисляется так же, как и в первом цикле.

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

$ belts.awk grade_student.txt

The club has 2 Red belts

The club has 2 Orange belts

The club has 3 Yellow belts

The club has 7 Senior students

The club has 8 Junior students

9.3. Заключение

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

ГЛАВА 10

Работа с редактором sed

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

В этой главе рассматриваются следующие темы:

   • синтаксис команд sed;

   • поиск строк по номерам и с использованием регулярных выражений;

   • изменение входного текста и добавление текста в выходной поток;

   • примеры команд и сценариев sed.

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

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

10.1. Чтение и обработка данных в sed

Общая схема работы редактора sed такова:

   1. Редактор последовательно извлекает строки текста из файла или стандартного входного потока и копирует их в буфер редактирования.

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

   3. Второй пункт повторяется до тех пор, пока не будет исчерпан список команд.

10.2. Вызов редактора sed

Вызвать редактор sed можно тремя способами:

   1. Ввести набор команд sed в командной строке.

   2. Поместить набор команд sed в файл и передать его редактору sed в командной строке.

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

командной строки будет таким:

sed [опции] 'команды' входной_файл

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

Если команды sed помещены в отдельный файл, командная строка примет следующий вид:

sed [опции] — f файл_сценария входной_файл

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

файл_сценария [опции] входной_файл

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

Ниже перечислены основные опции редактора sed и описано их назначение:

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

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

-f Эта опция используется при подключении файла сценария.

10.2.1. Сохранение выходных данных

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

$ sed 'команды' входной_файл > выходной_файл

10.2.2. Синтаксис команд

Общий синтаксис команд редактора sed таков:

[адрес1 [, адрес2] ] [ ! ] команда [аргументы]

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

Просмотр входного файла по умолчанию начинается с первой строки. Существует два способа адресации строк:

   1. По номерам.

   2. С помощью регулярных выражений (о них рассказывалось в главе 7).

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

Таблица 10.1. Правила отбора строк в редакторе sed

Адрес Отбираемые строки
нет адреса Все строки входного файла
X Строка с номером x
х, у Все строки с номерами в диапазоне от x до y
/шаблон/ Все строки, соответствующие шаблону
/шаблон1/, /шаблон 2/ Группа строк, начиная от строки, соответствующей первому шаблону, и заканчивая строкой, которая соответствует второму шаблону; подобных групп во входном файле может быть несколько
/шаблон/,х Группа строк, начиная от строки, соответствующей шаблону, и заканчивая строкой с указанным номером
x,/шаблон/ Группа строк, начиная от строки с указанным номером и заканчивая строкой, соответствующей шаблону
! Все строки, не соответствующие заданному адресу
$ Последняя строка входного файла

Некоторые команды, в частности, a, i, r, q и =, требуют указания только одного адреса.

10.2.3. Основные команды редактирования

Ниже представлен список основных команд, имеющихся в редакторе sed (табл. 10.2).

Таблица 10.2. Основные команды sed

p Вывод адресуемых строк
ж Вывод номеров адресуемых строк
а Добавление заданного текста после каждой адресуемой строки
i Вставка заданного текста перед каждой' адресуемой строкой
с Замена адресуемого текстового блока заданным текстом
d Удаление адресуемых строк
s Замена указанного шаблона заданным текстом в каждой адресуемой строке
w Добавление адресуемых строк в указанный файл
r Чтение текста из указанного файла и добавление его после каждой адресуемой строки
q Завершение работы после того, как достигнута адресуемая строка
l Вывод адресуемых строк с отображением непечатаемых символов в виде ASCII-
кодов и переносом длинных строк

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

[адрес1[, адрес2]]{ команда 1

командаN

}

или

[адрес1[,адрес2]] {команда1; …командаN; }

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

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

$ cat quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

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

Редактор sed распознает базовые регулярные выражения, которые мы уже рассматривали в главе 7. Дополнительные особенности появляются только в шаблонах поиска и замены в команде s. С помощью операторов \ ( и \) можно сохранить до девяти шаблонов поиска во временном буфере, с тем чтобы в шаблоне замены обратиться к ним с помощью оператора \n, где п — номер сохраненного шаблона. Метасимвол & позволяет в шаблоне замены сослаться на фрагмент строки, соответствующий шаблону поиска.

10.4. Вывод строк (команда p)

Рассмотрим, как в редакторе sed осуществляется поиск строк и вывод их на экран.

10.4.1. Отображение строки по номеру

Команда p (print) имеет такой формат:

[адрес1[,адрес2]]p

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

$ sed '2р' quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

Что было сделано неправильно? Ведь требовалось отобразить только строку номер 2, однако в результате были выведены на экран все строки файла, причем вторая строка — дважды. Причина подобного поведения заключается в том, что по умолчанию редактор sed отображает каждую просматриваемую строку. Чтобы избежать этого, воспользуемся опцией -n:

$ sed -n '2р' quote.txt

It was an evening of splendid music and company.

10.4.2. Отображение строк из заданного диапазона

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

$ sed -n '1,3p' quote.txt

The honeysuckle band played all night long for only §90.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

10.4.3. Поиск строк, соответствующих шаблону

В следующем примере показано, как найти строку, содержащую слово "Neave":

$ sed -n '/Neave/p' quote.txt

The local nurse Miss P. Neave was in attendance.

10.4.4. Поиск пo шаблону и номеру строки

Если адрес представлен в виде шаблона, редактор sed находит все строки, соответствующие этому шаблону. Как можно уточнить местонахождение строки? Рассмотрим пример. Предположим, требуется найти слово "The" в последней строке файла quote.txt. Если воспользоваться поиском по шаблону, то будет получено две строки:

$ sed -n '/The/p' quote.txt

The honeysuckle band played‑all night long for only $90.

The local nurse Miss P. Neave was in attendance.

Чтобы остановить свой выбор на последней строке, следует указать ее номер перед шаблоном:

$ sed -n '4,/The/p' quote.txt

The local nurse Miss P. Neave was in attendance.

10.4.5. Поиск специальных символов

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

$ sed -n /\$/р' quote.txt

The honeysuckle band played all night long for only $90.

10.4.6. Поиск первой строки

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

$ sed -n '1р' quote.txt

The honeysuckle band played all night long for only $90.

10.4.7. Поиск последней строки

Чтобы сослаться на последнюю строку входного файла, воспользуйтесь метасимволом '$':

$ sed -n '$p' quote.txt

The local nurse Miss P. Neave was in attendance.

10.4.8. Отображение всего файла

Если требуется отобразить весь файл, задайте диапазон строк от первой до последней:

$ sed -n '1,$p' quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

10.5. Вывод номеров строк (команда =)

Команда = имеет следующий формат:

[адрес]=

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

$ sed '/music/=' quote.txt

The honeysuckle band played all night long for only $90.

2

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

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

Если же требуется узнать только номер строки, задайте опцию -n:

$ sed -n '/music/=' quote.txt

2

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

$ sed -n -e '/music/p' -e '/music/=' quote.txt

It was an evening of splendid music and company. 2

10.6. Добавление текста (команда а)

Для добавления текста предназначена команда a (append), которая вставляет одну или несколько строк текста после адресуемой строки. Формат команды таков:

[адрес]a\

текст\

текст\

текст

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

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

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

10.7. Создание файла сценария

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

Создайте новый файл с именем append.sed и добавьте в него показанные ниже команды:

$ cat append.sed

#!/bin/sed -f

/company/a\

Then suddenly it happened.

Теперь сделайте этот файл исполняемым:

$ chmod u+x append.sed

и запустите его на выполнение:

$ append.sed quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

Then suddenly it happened.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

Если вместо показанного результата будет выдано сообщение об ошибке "command not found" (команда не найдена), значит, переменная среды $PATH, которая содержит список имен каталогов, просматриваемых в поиске исполняемых файлов, не включает имя текущего каталога. В этом случае необходимо явно указать, что исполняемый файл находится в текущем каталоге:

$ ./append.sed quote.txt

Рассмотрим, что делает сценарий append.sed. Первая его строка является системной командой, которая указывает, какая программа выполняет данный сценарий. Формат этой команды мы уже рассматривали при знакомстве с файлами сценариев awk в предыдущей главе. Как и утилита awk, редактор sed, как правило, находится в каталоге /bin.

Далее в сценарии находится команда а, которая ищет во входном файле строку, содержащую слово "company", и вставляет после нее предложение "Then suddenly it happened".

10.8. Вставка текста (команда i)

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

[адрес]i\

текст\ текст\

текст

В следующем сценарии предложение "Utter contusion followed" вставляется перед строкой, содержащей слово "attendance":

$ cat insert.sed

#! /bin/sed -f /attendance/i\ "Utter confusion followed.

Результаты работы данного сценария будут такими:

$ insert.sed quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

Utter confusion followed.

The local nurse Miss P. Neave was in attendance.

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

#!/bin/sed -f

4i\

Utter confusion followed.

10.9. Изменение текста (команда с)

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

[адрес1[,адрес2]]c\ текст\

текст\

текст

В следующем примере первая строга файла quote.txt заменяется новой строкой:

$ cat change.sed

#! /bin/sed -f

1c\

The Office Dibble band played well.

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

$ chmod u+x change.sed $ change.sed quote.txt

The Office Dibble band played well. It was an evening of splendid music and company. Too bad the disco floor fell through at 23.10. The local nurse Miss P. Neave was in attendance.

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

$ cat mix.sed

#! /bin/sed -f

# Изменяем строку номер 1

1c\

The Dibble band were grooving.

# Вставляем строку

/evening/i\

They played some great tunes.

# Изменяем последнюю строку

3c\

Nurse Neave was too tipsy to help.

# Добавляем строку после строки номер 3

3a\


Вот что получится в результате выполнения этого сценария:

$ mix.sed quote.txt

The Dibbse band were grooving.

They played some great tunes.

It was an evening of splendid music and company

Too bad the disco floor fell through at 23:10.

Where was the nurse to help?

Nurse Neave was too tipsy to help.


10.10. Удаление текста (команда d)

Для удаления текста предназначена команда d (delete), имеющая следующий

формат:

'адрес1[, адрес2]'d

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

файла:

$ sed '1d' quote.txt

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.


В следующем примере удаляются строки 1—3:

$ sed '1,3d' quote.txt

The local nurse Miss P. Neave was in attendance.

В этом примере удаляется последняя строка:

$ sed '$d' quote.txt

The honeysuckle band played all night long for only $90

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.


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

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

$ sed '/Neave/d' quote.txt

The honeysuckle band played all night long for only $90

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.


10.11. Замена подстроки (команда s)

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

таков:

[адрес1[, адрес2]]s/шаблои_поиска/шаблбон_замены/[флаги]

Ниже перечислены возможные флаги:


g Замена в адресуемой строке каждой подстроки, соответствующей шаблону (по умолчанию заменяется лишь самая первая подстрока каждой адресуемой строки)
n Замена n–й подстроки, соответствующей шаблону (n — любое число в диапазоне от 1 до 512)
p Вывод на экран строки, в которой была произведена замена; если в строке сделано несколько замен, она будет отображена соответствующее число раз
w имя_файла Запись измененной строки в указанный файл

В следующем примере осуществляется замена слова "night" словом "NIGHT":

$ sed -n 's/night/NIGHT/p' quote.txt

The honeysuckle band played all NIGHT long for only $90.

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

$ sed -n 's/\$//р' quote.txt

The honeysuckle band played all night long for only 90.

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

$ sed 's/\./!/' quote.txt

The honeysuckle band played all night long for only $90!

It was an evening of splendid music and company!

Too bad the disco floor fell through at 23:10!

The local nurse Miss P.Neave was in attendance.

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

$ sed 's/\./!/g' quote.txt

The honeysuckle band played all night long for only $90!

It was an evening of splendid music and company!

Too bad the disco floor fell through at 23:10!

The local nurse Miss P! Neave was in attendance!

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

"splendid" словом "SPLENDID", а все строки, где была выполнена эта замена, помещаются в файл sed.out.

$ sed -n 's/splendid/SPLENDID/w sed.out' quote.txt

Вот каким будет содержимое этого файла:

$ cat sed.out

It was an evening of SPLENDID music and company.

Ссылка на искомую подстроку с помощью метасимвола &

Метасимвол & позволяет сослаться в шаблоне замены на подстроку, соответствующую шаблону поиска. Например, в следующей команде слово "Miss" либо "miss" заменяется фразой "lovely Miss Joan" или "lovely miss Joan" соответственно:

$ sed -n 's/[Mm]iss/lovely & Joan/p' quote.txt

The local nurse lovely Miss Joan P. Neave was in attendance

Заметьте, что пробелы также являются частью шаблона замены.

10.12. Вывод строк в файл (команда w)

Подобно тому как оператор > применяется для перенаправления результатов работы программы в файл, команда w (write) редактора sed позволяет записать в указанный файл строки, отобранные по заданному шаблону адреса. Формат этой команды таков:

[адрес1[, адрес2]]w имя_файла

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

Рассмотрим пример:

$ sed '1,2w sed.out' quote.txt

Здесь содержимое файла quote.txt выводится на экран, а строки с номерами 1 и 2 отправляются в файл с именем sed.out.

$ cat sed.out

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company,

В следующем примере осуществляется поиск строки, содержащей слово "Neave", и если такая строка найдена, она записывается в файл sed.out.

$ sed -n '/Neave/w sed.out' quote.txt

$ cat sed.out

The local nurse Miss P. Neave was in attendance.

10.13. Чтение строк на файла (команда r)

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

[адрес]r имя_файла

Давайте создадим небольшой файл с именем sedex.txt.

$ cat sedex.txt

Boom boom went the music.

В следующем примере содержимое этого файла выводится на экран после строки файла quote.txt, содержащей слово "company":

$ sed '/company/r sedex.txt' quote.txt

The honeysuckle band played all night for only $90.

It was an evening of splendid music and company.

Boom boom went the music.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.


10.14. Досрочное завершение работы (команда q)

Иногда требуется завершить работу редактора sed сразу же после нахождения первого совпадения с шаблоном. Эту задачу решает команда q (quit), имеющая следующий формат:

[адрес]q

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

/\<.a.\{0,2\}\>/

Этому шаблону соответствует любое слово (выражение \< обозначает начало слова, а выражение \> — его конец(, в котором вторым символом является буква 'a', а за ней идет не более двух символов. В файле quote.txt таких слов четыре:

   • строка 1 — band,

   • строка 2 — was,

   • строка 3 — bad,

   • строка 4 — was.

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

$ sed */\<.a.\{0,2\)\>/q' quote.txt

The honeysuckle band played all night long for only $90.

10.15. Отображение управляющих символов (команда l)

Иногда даже в текстовых файлах содержатся различного рода непечатаемые символы. Это может быть следствием неправильного ввода данных в текстовом редакторе или ошибок конвертации при загрузке файлов из других систем. При выводе таких файлов. на экране могут быть получены странные результаты, когда вместо непечатаемого символа отображается один или несколько обычных символов непонятного происхождения. Разобраться в таких ситуациях помогает команда cat -v, которая помечает начало замещающей последовательности символом '^' (знак крышки). Предположим, вы обнаружили незнакомый файл func.txt и хотите узнать его содержимое:

$ cat func.txt

This is the F1 key:P This is the F2 key:Q

Символы 'P' и 'Q' на концах строк кажутся подозрительными. Попробуем применить команду cat -v:

$ cat -v func.txt

This is the Fl key:^[OP This is the F2 key:^[OQ

Так и есть! Это не буквы 'Р и 'Q', а управляющие символы, хотя и непонятно, с помощью каких клавиш они были сгенерированы.

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

$ sed -n '1,$p' func.txt

This is the F1 key:P This is the F2 key:Q

В редакторе существует команда l (list}, аналог рассмотренной выше команды cat -v. Формат команды l таков:

[адрес1[,адрес2]]l

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

$ sed -n '1,$1' func.txt

This is the Fl key:\033OP$ This is the F2 key:\033OQ$

Теперь ситуация немного проясняется. По таблице ASCII–кодов можно узнать, что восьмеричный код 033 соответствует непечатаемому символу esc. Именно он в выводе команды cat -v обозначается как ^ [. За ним идут два обычных символа: сначала 'О', затем 'Р' либо 'Q'. Таким образом, мы имеем дело с двумя клавишами, каждая из которых сгенерировала трехсимвольную последовательность: одна — esc‑O–р, а вторая — esc‑o–q. Когда файл отображается в обычном режиме, каждому символу соответствует одно знакоместо, поэтому первые два символа последовательности отбрасываются и остается последний: 'P' и 'Q' соответственно.

Нет системной команды, которая позволяла бы узнать, какие клавиши генерируют эти коды. Подобные сведения находятся в базах данных termcap и terminfo, хранящих установки терминала, но знакомство с этими базами данных выходит за рамки нашей книги. Проще всего пойти экспериментальным путем: ввести команду cat или cat -v без указания входного файла, с тем чтобы попробовать самостоятельно определить, нажатия каких клавиш приводят к отображению на экране нужных последовательностей символов. В нашем случае последовательность ESC‑O–P генерируется клавишей [F1], а последовательность ESC‑O–Q — клавишей [F2], хотя в общем это зависит от установок терминала.

Если вам интересно, как можно создать файл func.txt, ниже описана возможная процедура:

   1. Загрузите редактор vi.

   2. Перейдите в режим вставки с помощью команды i и введите первую строку файла вплоть до двоеточия.

   3. Нажмите [Ctrl+V], при этом появится символ '^'

   4. Нажмите клавишу [F1], при этом отобразится последовательность [ор.

   5. Нажмите клавишу [Enter] и повторите пункты 2—4 для второй строки файла (в конце нажимается клавиша [F2]).

   6. Нажмите клавишу [Esc], чтобы выйти из режима вставки.

   7. Введите команду w func.txt, чтобы сохранить файл.

   8. Введите команду q, чтобы выйти из редактора vi.

10.16. Дополнительные примеры использования редактора sed

Выше были описаны основные команды sed. Далее мы рассмотрим ряд практических примеров применения редактора sed.

10.16.1. Обработка управляющих символов

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

$ cat -v dos.txt

12332##DISO##45.12^M C0332##LPSO##23.11^M

01299##USPD##34.46^M

Вот что необходимо сделать:

   1. Заменить все знаки решетки ('#'( пробелом.

   2. Удалить из первого поля каждой строки все ведущие нули.

   3. Удалить в конце каждой строки последовательность ^M, генерируемую непечатаемым символом CR — возврат каретки (ASCII–код 13).

Примечание:

В ряде систем, в которых мне приходилось выполнять подобные преобразования, в конце строки стоит символ перевода строки LF (ASCII–код 10, отображается как ^@). Подобная проблема нередко возникает при передаче файлов между двумя системами, в одной из которых символ новой строки (\n) заменяется последовательностью CR/LF, a в другой — только одним из этих двух управляющих символов.

Задача 1. Удаление всех символов решетки реализуется без особого труда. Для этого достаточно выполнить команду замены s с флагом глобальной подстановки д, указав при этом, что один или более символов '#', идущих подряд, должны быть заменены пробелом:

$ sed 's/##*/ /g' dos.txt | cat -v

12332 DISO 45.12^М

00332 LPSO 23.11^М

01299 USPD 34.4б^M

Задача 2. Для удаления всех ведущих нулей следует в команде s оставить шаблону замены пустым, а шаблон поиска задать таким: /^0*/. Он означает, что требуется найти любое количество нулей, стоящих в начале строки.

$ sed 's/^0*//' dos.txt | cat -v

12332##DISO##45.12^M

332##LPSO##23.11^M

1299##USPD ##34.46^M

Задана 3. Чтобы избавиться от управляющих символов ^M в конце строк, необходимо. также применить команду s, оставив шаблон замены пустым. А вот при формировании шаблона поиска следует учесть, что мы имеем дело с непечатаемым символом. Нельзя просто ввести символы '^' и 'M', так как полученный шаблон будет означать, что мы ищем букву 'М', стоящую в начале строки. Чтобы действительно создать нужный нам непечатаемый символ, необходимо нажать [Ctrl+V], а затем — клавишу [Enter]. Результат будет выглядеть как регулярное выражение ^M, но на самом деле это экранное представление символа CR.

$ sed 's/^M//' dos.txt | cat -v

12332##DIS0##45.12 0D332##LPSO##23.11 01299##U5PD#t34.46

Теперь можно попробовать объединить три команды в одну с помощью опции -e;

$ sed -e 's/^0*//' -e 's/^M//' -e 's/##*/ /g' dos.txt | cat -v

12332 DISO 45.12

332 LPSO 23.11

1299 USPD 34.46

Выходные данные редактора sed передаются по каналу команде cat -v, которая позволяет убедиться, что вся работа, включая удаление непечатаемых символов, выполнена правильно.

Приведенные выше команды удобнее поместить в файл сценария. Назовем его dos.sed. Вот его текст:

$ cat dos.sed

#! /bin/sed -f

#Имя: dos.sed

#Командная строка: dos.sed dos.txt

#Избавляемся от символа решетки

s/##*/ /g

#Удаляем ведущие нули

s/^0*//

#Удаляем символы возврата каретки

s/^M//

На входе данного сценария следует указать файл dos.txt. Сценарий "исправит" этот файл и выведет результат на экран. Если же ввести следующую командную строку:

$ dos.sed dos.txt | tee dos.txt

то результат будет записан обратно в файл dos.txt.

10.16.2. Обработка отчетов

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


Database Size (MB) Date created
GOSOUTH 2244 5632 12/11/9? 8/9/99
TRISUD

(2 rows affected) Из этой информации нас, предположим, интересуют только имена баз данных, находящиеся в первой колонке. Чтобы их извлечь, необходимо выполнить следующую последовательность действий:

   1. Удалить пунктирную линию с помощью команды s/--*//g.

   2. Удалить все пустые строки с помощью команды /^$/d.

   3. Удалить последнюю строку с помощью команды $d.

   4. Удалить первую строку с помощью команды 1d.

   5. Отобразить первый столбец с помощью команды awk '{print $1}'. Ниже приведена соответствующая цепочка команд:

$ sed 's/--*//g' -e '/^$/d' -e '$d' -e '1d' sql.txt | awk '{print $1}'

GOSOUTH TRISUD

10.16.3. Добавление текста

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

$ cat ok.txt

АС456

АС492169

АС9967

АС88345

Наша задача состоит в добавлении слова "Passed" (обработано) в конец каждой строки. Решить ее несложно. Достаточно в шаблоне поиска указать метасимвол '$', означающий конец строки, а в шаблоне замены — пробел и искомое слово:

$ sed 's/$/ Passed/' ok.txt

АС456 Passed

АС492169 Passed

AC9967 Passed

AC8B345 Passed

10.16.4. Удаление начальной косой черты в путевом имени

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

$ cd /usr/local

$ echo $PWD | sed 's/^\///g'

usr/local

Имя текущего каталога хранится в переменной среды Spwd. Эта переменная обновляется всякий раз, когда выполняется команда cd. Команда echo передает значение переменной по каналу редактору sed, который выполняет несложную обработку: находит в начале строки (метасимвол '^'( символ косой черты (защищен от интерпретации обратной косой чертой) и заменяет его пустой подстрокой.

10.17. Заключение

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

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

ГЛАВА 11

Дополнительные утилиты работы с текстом

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

   • sort;

   • uniq;

   • join;

   • cut;

   • paste;

   • split,

11.1. Сортировка файлов с помощью команды sort

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

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

11.1.1. Опции команды sort

Команда sort имеет следующий формат:

sort [опции] [входные_файлы]

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

Таблица 11.1. Основные опции команды sort

Проверка того, отсортирован ли файл; сортировка не производится
-m Объединение отсортированных файлов; сортировка не производится
-u Удаление повторяющихся строк
Вывод результата не на экран, а в указанный файл
-b Игнорирование начальных пробелов в полях сортировки
-n Включение режима числовой сортировки
-t Задание разделителя полей
-r Сортировка в обратном порядке
+поз1[-поз2] Ключом сортировки становится строка, начинающаяся в позиции
поз1 и заканчивающаяся перед позицией поз2 (или в конце текущей строки, если второй параметр не указан)*; номера полей и позиции начальных символов отсчитываются от нуля
-k поз1[, поз2] Ключом сортировки становится строка, начинающаяся в позиции
поз1 и заканчивающаяся в позиции поз2 (или в конце текущей строки, если второй параметр не указан)[2]; номера полей и позиции начальных символов отсчитываются от единицы
-n Поле с номером я не должно сортироваться; значение n отсчитывается от нуля

11.1.2. Сохранение результатов сортировки

Чтобы сохранить результаты сортировки, укажите опцию -o и выходной файл. Можно также воспользоваться традиционным методом переадресации с помощью оператора >. В следующем примере результаты сортировки сохраняются в файле results.out

$ sort video.txt > results.out

11.1.3. Тестовый файл

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

   1. Название фильма.

   2. Код фирмы–дистрибьютора.

   3. Количество заказов за последний квартал.

   4. Количество заказов за последний год.

$ cat video.txt

Boys in Company C:HK:192:2192 Alies:HK:119:1982

The Hill:KL:63Yf972

Aliens:НК:532:4892

Star Wars:HK:301:4102

A Few Good Men:KL:445:5851

Toy Story:HK:239:3972

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

11.1.4. Индексация полей

При работе с командой sort не следует забывать, что команда обращается к первому полю как к полю 0, следующее поле имеет номер 1 и т. д. Если номера полей не указаны, вся строка считается единым полем. Обратимся к тестовому файлу и уточним, каким образом команда sort разбивает файл на поля:


Поле 0 Поле 1 Поле 2 Поле 3
Star Wars HK 301 4102
A Few Good Men KL 445 5851

11.1.5. Проверка факта сортировки файла

Каким образом можно узнать, отсортирован ли данный файл? Если он содержит, например, около 30 строк, то достаточно его просмотреть. А если в нем 400 строк? Примените команду sort -c, которая сама определит, отсортирован ли файл:

$ sort -с video.txt

sort: disorder on video.txt

Команда sort считает, что файл не отсортирован. Давайте отсортируем его и посмотрим, что будет:

$ sort video.txt | sort -c $

Сообщение не появилось, таким образом, файл является отсортированным.

11.1.6. Простейшая сортировка

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

$ sort video.txt

A Few Good Men:KL:445:5851

A. Iien:HK:119:1982

Aliens:HK:532:4892

Boys in Company C:HK:192:2]92

Star Wars:HK:301:4102

The tfili:KL:63:2972

Toy Story. HK:239:3972

11.1.7. Сортировка а обратном порядке

Если необходимо отсортировать строки не по возрастанию, а по убыванию, задайте опцию -r:

$ sort -r video.txt

Toy Story:HK:239:3972

The H111:KL:63:2972

Star Wars:HK:301:4102

Boys in Company С:HK:192:2192

Aliens:HK:532:4892

Alien:HK:119:1982

A Few Good Men:KL:445:5851

11.1.8. Сортировка по заданному полю

В следующем примере файл сортируется по кодам фирм–дистрибьюторов. Поскольку требуемая информация находится во втором поле (ключ сортировки 1(, следует указать опцию +1. Кроме того, необходимо задать разделитель полей с помощью опции -t:, чтобы команда sort знала, как найти второе поле.

$ sort -t: +1 video.txt

Alien:HK:119:1982

Boys in Company С:HK:192:2192

Toy Story:HK:239:3972

Star Wars:HK:301;4102

Aliens:HK:532:4892

A Few Good Men:KL:445:5851

The Hill; KL:63:2972

Обратите внимание на то, что третье и четвертое поля также были отсортированы. Такова стандартная процедура: все последующие поля по умолчанию считаются ключами сортировки, расположенными в порядке убывания приоритета. Причем если вы посмотрите на конечные две строки, то заметите, что к этим полям применялась не числовая, а текстовая сортировка, учитывающая расположение символов в таблице ASCII–кодов. Поэтому поле со значением 445 оказалось расположенным раньше поля со значением 63.

11.1.9. Сортировка по числовому полю

Чтобы корректно отсортировать файл по четвертому, числовому, полю, укажите не только ключ сортировки (+3), но и опцию -n, включающую режим числовой сортировки. Следующая команда сортирует список фильмов по объемам проката видеокассет за год:

$ sort -t: +3n video.txt

Alien:HK:119:1982

Boys in Company C:HK:192:2192

The Hill:KL:63:2972

Toy Story:HK:239:3972

Star Wars:HK:301:4102

Aliens:HK:532:4B92

A Few Good Men:KL:445:5851

Таким образом, можно заключить, что фильм "A Few Good Men" ("Несколько хороших парней", 1992 г.) является лидером видеопроката в текущем году.

Примечание:

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

11.1.10. Сортировка с отбрасыванием повторяющихся строк

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

$ cat video.txt

Boys in Company С:HK:192:2192

Alien:HK:119:1982

The Hill:KL:63:2972

Aliens:HK:532:4892

Star Wars:HK:301:4102

A Few Good Men:KL.445:5851

Toy Story:HK:239:3972

Alien:HK:119:1982

Вот что получится в результате применения команды sort -u:

$ sort -и video.txt

A Few Good Men:KL:445:5851

Alien:HK:119:1982

Aliens:HK:532;4892

Boys in Company С: НК:192:2192

Star Wars:HK:301:4102

The Hill:KL:63:2972

Toy Story:HK:239:3972

11.1.11. Задание ключа сортировки с помощью опции -k

Команда sort позволяет задать ключ сортировки немного по–другому. Если воспользоваться опцией — к, то поля (ключи сортировки) можно будет нумеровать, начиная с единицы, а не с нуля, что, в принципе, удобнее. Таким образом, чтобы выполнить сортировку по полю 4, достаточно задать опцию -k4n. Это позволит упорядочить список фильмов по объемам видеопроката за год.

$ sort -t: — k4n video.txt

Alien:HK:119:l982

Boys in Company C:HK:192:2192

The Hill:KL:63:2972

Toy Story:HK:239:3972

Star Wars:HK:30l:4102

Aliens:HK:532:4892

A Few Good Men:KL:445:5851

11.1.12. Несколько ключей сортировки

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

Схожая проблема возникает при работе с несколькими ключами сортировки. Рассмотрим такой пример. Предположим, требуется отсортировать список фильмов по кодам дистрибьюторов (второе поле), а затем по названиям фильмов (первое поле). Если сослаться на поля по номерам, получим следующее:

$ sort -t: — k2 -kl video.txt

Alien:HK.119:1982

Boys in Company C:HK:192:2192

Toy Story:HK:239:3972

Star Wars:HK:301:4102

Aliens:HK:532:4892

A Few Good Men:KL:445:5851

The Hill:KL:63:2972

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

$ sort -t: — k2,2 -k1,1 video.txt

Alien:HK:119:1982

Aliens:HK:532:4892

Boys in Company C:HK:132:2192

Star Wars:HK:301;4102

Toy Story:HK:239:3972

A Few Good Men:KL:445:5851

The Hill:KL:63:2972

Опция -k2,2 ограничивает ключ сортировки вторым полем, а опция -kl,1 — первым.

11.1.13. Указание позиции, с которой начинается сортировка

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

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

$ cat video.txt

Boys in Company C:HK48:192:2192 Alien:HK57:H9:1982

The Hill:KL23:63:2972

Aliens:НК11: — 5Э2г4892

Star Wars:HK38:301:4102

A Few Good Men:KL87:445:5851

Toy Story:HK65:239:3972

Теперь мы хотим отсортировать файл по кодам регионов. Вот как можно это сделать:

$ sort -t: — k2.3,2,4n video.txt

Aliens:HK13:532:4892

The Hill:KL23:63:2972

Star Wars:HK38:301:4102

Boys in Company C:HK48:192:2192

Alien:HK57:119:1982

Toy Story:HK65:239:3972

A Few Good Men:KL87:445:5851

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

11.1.14. Обработка результатов сортировки с помощью команд head и tail

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

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

$ sort -t: — k4 video.txt | head -1

Alien:HK:119:1982

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

$ sort -t: — k4 video.txt | tail -1

A few Good Men:KL:445:5851

11.1.15. Передача результатов сортировки утилите awk

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

$ sort -t: — k4 video.txt | head -1 | \

awk -F: '{print "Worst rental", $1, "has been rented", $3, "times"}'

Worst rental Alien has been rented 119 times

11.1.16. Объединение двух отсортированных файлов

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

$ cat video2.txt

Crimson Tide:134:2031 Die Hard:152:2981

Необходимо объединить его с файлом video.txt. Для этого нужно предварительно создать отсортированную версию файла video.txt, которую назовем video.sort, а затем применить команду sort с опцией -m:

$ sort -t: — m -k1 video2.txt video.sort

A Few Good Men:KL:445:5851

Alien:HK:119:1982

Aliens:HK:532:4892

Boys in Company C:HK:192:2192

Crimson Tide:134:2031

Die Hard:152:2981

Star Wars:HK:301:4102

The Hill:KL:63:2972

Toy Story:HK:239:3972

11.1.17. Дополнительные примеры команды sort

Команда sort может применяться для сортировки имен пользователей в файле /etc/passwd. Достаточно выполнить сортировку содержимого этого файла по первому полю, которое включает регистрационные имена, а затем по каналу передать полученный результат утилите awk. Последняя отобразит содержимое только первого поля данного файла:

$ cat /etc/pasawd | sort -t: — k1 | awk -F: '{print $1}'

adm bin daemon

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

$ df

Filesystem Ik‑blocks Used Available Use% Mounted on

/dev/hda5 495714 291027 179086 62% /

/dev/hda1 614672 558896 55776 91% /dos

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

$ df | sed '1d' | sort -b -r -k5

/dev/hda1 614672 558896 55776 91% /dos

/dev/hda5 495714 291027 179086 62% /

11.2. Удаление повторяющихся строк с помощью команды uniq

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

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

Рассмотрим пример. Имеется следующий файл:

$ cat myfile.txt

May Day

May Day

May Day

Going Down

May Day

В данном случае команда uniq будет рассматривать первые три строки как повторяющиеся. Пятая строка таковой не считается, потому что не совпадает с четвертой строкой.

11.2.1. Синтаксис

Общий формат команды uniq таков:

uniq опции входной_файл выходной_файл

Ниже перечислены некоторые из ее опций:

-u Отображение только не повторяющихся строк

-d Отображение одной копии каждой повторяющейся строки

-c Удаление повторяющихся строк с выводом перед каждой из оставшихся строк

числа повторений -f n Игнорирование первых п полей; полем считается последовательность непробельных символов, завершающаяся пробелом или табуляцией

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

Давайте применим команду uniq к показанному выше файлу myfile.txt.

$ uniq myfile.txt

May Day

Going Down'

May Day

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

$ sort -u myfile.txt

Going Down

May Day

11.2.2. Определение количества повторений

Указав в команде uniq опцию -c, можно не только отбросить повторяющиеся строки, но и узнать, сколько раз повторяется каждая строка. В следующем примере команда uniq сообщает о том, что первая строка "May Day" встречается три раза подряд:

$ uniq -с myfile.txt

3 May Day

1 Going Down

1 May Day

11.2.3. Отображение только повторяющихся строк

Опция -d позволяет отобразить только те строки, которые встречаются несколько раз подряд:

$ uniq -d myfile.txt

Mау Day

11.2.4. Проверка уникальности отдельных полей

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

$ cat parts.txt

AK123 OP

OК122 OP

OК999 OP

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

$ uniq parts.txt

AК123 OP

OK122 OP

OK999 OP

Если же выполнить проверку только по второму полю, получим иной результат. Команда uniq сравнит повторяющиеся поля "ОР" и отобразит только одну строку:

$ uniq -f1 parts.txt

OK123 OP

11.3. Объединение файлов с помощью команды join

Команда join выполняет соединение строк из двух текстовых файлов на основании совпадения указанных полей. Ее действие напоминает операцию join языка SQL. Механизм работы команды таков:

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

   2. Из первого файла извлекается первая строка, а из нее — первое поле (можно указать другое поле).

   3. Во втором файле ищется строка, имеющая такое же первое поле.

   4. Найденная строка, из которой удаляется первое поле, присоединяется к исходной строке, и результат записывается в выходной поток.

   5. Пункты 3 и 4 повторяются до тех пор, пока во втором файле есть строки с совпадающим первым полем.

   6. Пункты 2—6 повторяются для каждой строки первого файла.

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

Общий формат команды join таков:

join [опции] входной_файл1 входной_файл2

Рассмотрим некоторые наиболее важные опции этой команды:

-a n Задает включение в выходной поток строк из файла n (n -l или 2), для которых не было найдено ни одного совпадения по указанному полю.
-o формат Задает формат выводимой строки. Параметр формат представляет собой разделенный запятыми или пробелами список спецификаций, каждая из которых, в свою очередь, имеет формат номер_файла. поле. По умолчанию формат выводимой строки таков:
1 — поле, по которому производится объединение;
2 -oставшаяся часть первой строки;
3 -oставшаяся часть второй строки.
-1 поле Объединять строки по указанному полю первого файла (по умолчанию таковым является первое поле)
-2 поле Объединять строки по указанному полю второго файла (по умолчанию таковым является первое поле)
-t символ Задает разделитель полей во входном и выходном потоках

11.3.1. Объединение двух файлов

Предположим, имеется два текстовых файла: один называется names.txt и содержит имена пользователей с указанием улиц, на которых они проживают, а другой называется town.txt и содержит имена пользователей с указанием городов, в которых они живут.

$ cat names.txt

M. Golls 12 Hidd Rd

P. Heller The Acre

P. Willey 132 The Grove

T. Norms 84 Connaught Rd

K. Fletch 12 Woodlea

$ cat town.txt

M. Golls Norwich NRD

P. Willey Galashiels GDD

T. Norms Brandon BSL

K. Fletch Mildenhall MAF

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

$ join names.txt town.txt

M. Golls 12 Hidd Rd Norwich NRD

P. Willey 132 The Grove Galashiels GDD

T. Norms 6-3 Connaught Rd Brandon BSL

K. Fletch 12 Woodlea Mildenhall MAF

Как видите, пользователь P. Heller, для которого нет строки во втором файле, в результаты работы команды join не попал.

11.3.2. Включение несовпадающих строк

Если требуется все‑таки включить информацию о пользователе P. Heller в выходные данные, воспользуйтесь опцией -a. Поскольку исходная строка находится в первом файле, параметром данной опции будет цифра 1:

$ join -a1 names.txt town.txt

M. Golls 12 Hidd Rd Norwich NRD

P. Heller The Acre

P. Willey 132 The Grove Galashiels GDD

T. Norms 84 Connaught Rd Brandon BSL

K. Fletch 12 Woodlea Mildenhall MAF

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

11.3.3. Задание формата вывода

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

$ join -о 1.1,2.2 names.txt town.txt

M. Golls Norwich

P. Willey Galashiels

T. Norms Brandon

K. Fletch Mildenhall

11.3.4. Выбор ключевого поля

He всегда первое поле является общим для обоих файлов. Рассмотрим пример. Имеются два файла:

$ cat pers

P. Jones Office Runner ID897

S. Round UNIX admin ID667

L. Clip Personl Chief ID982

$ cat pers2

Dept2C ID897 6 years

Dept3S ID667 2 years

Dept5Z ID982 1 year

Файл pers содержит имена, названия должностей и личные идентификационные номера служащих фирмы. Файл pers2 содержит для каждого из служащих код отдела, в котором он работает, идентификационный номер и стаж работы на фирме. В данном случае требуется выполнить соединение строк по номеру служащего. Он хранится в четвертом поле первого файла и во втором поле второго файла. Задать их в команде join можно с помощью опции -n т, где п — номер файла, a m — номер поля.

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

$ join -1 4 -2 2 pers pers2

ID897 P. Jones Office Runner Dept2C 6 years

ID667 S. Round UNIX admin Dept3S 2 years

ID982 L. Clip Personl Chief Dept5Z 1 year

При работе с командой join следует быть внимательным, вычисляя номер нужного поля. Можно посчитать, что доступ реализуется к полю 4, а с точки зрения команды join это поле 5. В итоге будут получены неправильные результаты. Чтобы проверить, содержит ли поле с указанным номером предполагаемые данные, воспользуйтесь утилитой awk:

$ awk '{print $4}' имя_файла

11.4. Вырезание текста с помощью команды cut

Команда cut позволяет вырезать фрагменты строк из текстовых файлов или из стандартного входного потока. Извлеченный подобным образом текст направляется в стандартный выходной поток. Общий формат команды cut таков:

cut [опции] файлы…

Рассмотрим основные опции этой команды:

-c список Определяет, какие символы извлекаются из каждого входного файла

-f список Определяет, какие поля извлекаются из каждого входного файла

-d Задает разделитель полей

Параметр список в опциях -c и -f представляет собой разделенный запятыми список диапазонов символов или полей соответственно. Диапазон может быть задан в одной из четырех форм:

-n В выходной поток включается каждый n–й символ (поле) каждой строки

каждого входного файла

n- Диапазон формируется от n–го символа (поля) до конца строки

n–m Диапазон формируется от n–го символа (поля) до m–го символа (поля) включительно

-m Диапазон формируется от начала строки до m–го символа (поля)

11.4.1. Задание разделителя полей

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

$ cat pers

P. Jones:Office Runner:ID897

S. Round:UNIX admin:ID667

L. Clip:Personl Chief:ID982

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

$ cut -d: — f3 pers

ID897

ID667

ID982

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

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

$ cut -d: — f1,3 pers

P. Jones:ID897

S. Round:ID667

L. Clip:ID982

А вот как можно извлечь из каталога /etc/passwd регистрационные имена пользователей и имена их начальных каталогов, хранящиеся в полях 1 и 6 соответственно:

$ cut -d: — f1,6 /etc/passwd

gopher:/usr/lib/gopher‑data

ftp:/home/ftp

peter:/home/apps/peter

dave:/home/apps/dave

11.4.2. Вырезание отдельных символов

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

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

Идентификатор источника содержится в последних трех символах имени файла. Вот примерный список имен файлов:

2231DG

2232DP

2236DK

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

$ 1s 223* | cut -с4–6

1DG

2DP

6DK

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

$ who! cut -c1-8

root

dave

peter

11.5. Вставка текста с помощью команды paste

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

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

Формат команды paste таков:

paste [опции] файл…

Рассмотрим опции команды paste:

-d список Сообщает команде paste о необходимости применять вместо табуляции другой разделитель полей. Допускается указывать список разделителей, В этом случае разделители используются циклически: между первыми двумя строками вставляется первый разделитель из списка, между следующими двумя — второй и т. д. Когда список заканчивается, осуществляется возврат к началу списка и процедура повторяется.
-s Задает режим последовательного слияния строк каждого входного файла по отдельности
- Означает выборку строки из стандартного входного потока

11.5.1. Определение порядка вставки столбцов

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

$ cat pas1

ID897

ID667

ID9B2

$ cat раs2

P. Jones

S. Round

L. Clip

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

$ paste pas1 pas2

ID897 P. Jones

ID667 S. Round

ID982 L. Clip

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

$ paste pas2 pas1

P. Jones ID897

S. Round ID667

L. Clip ID982

11.5.2. Выбор разделителя полей

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

$ paste -d: pas2 pas1

P. Jones:ID897

S. Round:ID667

L. Clip:ID982

11.5.3. Слияние строк

Наличие опции -s заставляет команду paste работать немного по–другому: для каждого входного файла она выполняет слияние всех его строк, записывая результат в выходной поток. Представленная ниже команда сначала отображает все имена служащих, а затем — их идентификационные номера.

$ paste -a pas2 pas1

P. Jones S. Round L. Clip

ID897 ID667 ID982

11.5.4. Чтение данных и» стандартного входного потока

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

$ cd /etc

$ ls | paste -d" " - - - -

init.d rc rc.local rc.sysinit rc0.d rc1.d rc2.d rc3.d rc4.d rc5–d rc6.d

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

$ ls | paste -

init. d

rc

rc.local

rc.sysinit

rc0.d

rc1.d

11.6. Разделение файла на части с помощью команды split

Команда split позволяет разделять крупные текстовые файлы на более мелкие, Это может оказаться удобным, например, при передаче файлов по сети. Общий формат команды split таков:

split [-размер_выходного_файла] входной_файл [префикс]

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

Имя каждого созданного файла представляется в формате от префикс[аа] до префикс[zz]. По умолчанию префиксом является буква 'x'. Таким образом, команда split создает такую последовательность файлов:

хаа, xab, … xzy, xzz

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

Следующий пример поможет разъяснить сказанное. Допустим, имеется файл bigone.txt, содержащий 2800 строк. В результате выполнения команды split будут сформированы три выходных файла:

Имя файла Размер

xaa 1000

xab 1000

xac 800

Теперь рассмотрим, как изменить размер создаваемых файлов. Ниже показан файл split1, содержащий шесть строк:

$ cat split1

this ls line1

this ls line2

this ls line3

this ls line4

this ls line5

this ls line6

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

$ split -2 split1

Давайте проверим, что было создано (команда ls -lt сортирует список файлов по дате создания, а команда head отбирает из этого списка первые десять элементов):

$ ls -lt | head

total 205

-rw‑r--r-- 1 dave admin 28 Apr 30 13:12 xaa

-rw‑r--r-- 1 dave admin 28 Apr 30 13:12 xab

-rw‑r--r-- 1 dave admin 28 Apr 30 13:12 xac

Исходный файл состоит из шести строк. В результате применения к нему команды split были сформированы три файла, содержащих по две строки каждый. Чтобы убедиться в правильности работы команды, рассмотрим содержимое файла хаc, который должен включать последние две строки:

$ cat xac

this ls line5

this ls line6

11.7. Заключение

В настоящей главе были рассмотрены различные стандартные утилиты (sort, unique, join, cut, paste и split, а также head и tail), имеющие отношение, главным образом, к сортировке, разделению и объединению текстовых файлов. Применение каждой из них иллюстрировалось многочисленными примерами, которые позволят вам сформировать четкое представление о возможностях этих утилит. Я надеюсь, что благодаря изложенным сведениям вы смогли пополнить свой багаж знаний об инструментах работы с текстом, имеющихся в UNIX и Linux.

ГЛАВА 12 Утилита tr

12.1. Применение утилиты tr

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

В этой главе рассматриваются следующие темы:

   • преобразование строчных символов в прописные;

   • очистка содержимого файлов от управляющих символов;

   • удаление пустых строк.

Формат утилиты tr с наиболее часто применяемыми параметрами таков:

tr -c -d -s ["строка1"] ["строка2"] входной_файл

где

-c Задает замену набора символов, указанных в строке1 их собственным дополнением при условии, что значение этих символов находится в диапазоне значений кодов ASCII -d Задает удаление во входном файле всех символов, указанных в строке1

-s Задает удаление в последовательности повторяющихся символов всех символов, кроме первого, благодаря чему удаляются повторяющиеся символы

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

12.1.1. Диапазоны символов

При использовании утилиты tr можно указать диапазоны или списки символов в виде шаблонов, которые образованы строками. Эти шаблоны подобны регулярным выражениям, однако на самом деле они таковыми не являются. При указании в утилите tr содержимого строк строка1 или строка2 используются только диапазоны и последовательности символов либо отдельные символы.

[a‑z] Строка символов, находящихся в диапазоне a‑z
[A‑Z] Строка символов, находящихся в диапазоне A‑Z
[0-9] Строка чисел
/octal Восьмеричное число, состоящее из трех чисел и представляющее любой действительный символ в коде ASCII
[0*n] Означает символ '0', встречающийся столько раз, сколько указывает значение 'n'. Таким образом, [0*2] означает 00, причем в любой строке, включая и 00

В большинстве вариантов утилиты tr поддерживаются классы символов и сокращенная запись управляющих символов. В формат класса символов [:class:] среди прочего входят следующие обозначения: alnum (буквенно–цифровые символы), alpha (буквы), blank (пропуски), upper (прописные буквы), lower (строчные буквы), cntrl (управляющие символы), space (пробелы), digit (цифры), graph (графические символы) и т. д. В табл. 12.1 представлен способ сокращенного представления некоторых наиболее распространенных управляющих символов, используемый вместо восьмеричного их представления в виде трех чисел, которое также приведено в данной таблице.

Таблица 12.1. Различные способы указания управляющих символов в утилите tr

Сокращение Значение Восмеричное значение
Control‑G — звонок \007
\b Control‑H — клавиша возврата на одну позицию \010
\f Control‑L — прокрутка страницы \014
\n Comrol‑J — новая строка \012
\r Control‑M — клавиша возврата каретки \015
\t Control‑I — клавиша табуляции \011
\v Control‑X \030

При замене строки или диапазона символов одним символом следует иметь в виду, что этот символ не указывается в квадратных скобках ([]). В некоторых системах допускается применение квадратных скобок, причем в данном случае можно для указания символа новой строки воспользоваться шаблоном ["\0l2"] или "\012". Утилита tr не предъявляет строгих требований к виду кавычек. Поэтому не следует удивляться, если эта утилита действует даже в том случае, если вместо одинарных кавычек используются двойные кавычки.

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

12.1.2. Сохранение выходного результата

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

$ tr -s "[a‑z]" < oops.txt > results.txt

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

12.1.3. Устранение повторяющихся символов

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

$ pg oops.txt

And the cowwwwws went homeeeeeeee Or did theyyyy

Если нужно избавиться от повторяющихся букв или сократить число подобных букв до одной, можно воспользоваться параметром ' -s'. Также можно воспользоваться шаблоном [a‑z], поскольку в данном случае все символы являются буквенными. При этом входной файл перенаправляется команде tr.

$ tr -s "[a‑z]"< oops.txt

And the cows went home Or did they

Все повторяющиеся символы устраняются. При необходимости файл oops.txt можно перенаправить с помощью команды cat. Результат будет тот же.

$ cat oops.txt | tr -s "[a‑z]"

And the cows went home Or did they

12.1.4. Удаление пустых строк

Для удаления пустых строк их следует просто "вытеснить" из файла. Ниже приведен файл с именем plane.txt, содержащий ряд пустых строк.

$ pg plane.txt

987932 Spitfire



190992 Lancaster


238991 Typhoon

В данном случае применяется параметр ' -s', который приводит к удалению пустых строк. Также используется восьмеричное значение для символа новой строки \012. Ниже приведена соответствующая команда.

$ tr -s "[\012]" < plane.txt

987932 Spitfire

190992 Lancaster

238991 Typhoon

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

$ tr -s "[\n]" < plane.txt

987932 Spitfire

190992 Lancaster

238991 Typhoon

12.1.5. Преобразование прописных букв в строчные

Изменение регистра символов является наряду с процедурой удаления управляющих символов одним из наиболее распространенных случаев применения утилиты tr. Чтобы выполнить подобное преобразование, достаточно указать шаблон строчных букв '[a‑z]' для входных данных и шаблон прописных букв '[A‑Z]' для выходных преобразованных данных.

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

$ echo "May Day, May Day, Going Down.." | tr " [a‑z]" "[A‑Z]"

MAY DAY, MAY DAY, GOING DOWN..

С другой стороны, можно воспользоваться классами символов [:lower:] и [:upper:].

$ echo "May Day, May Day, Going Down.." | tr ":lower:" ":upper:"

MAY DAY, MAY DAY, GOING DOWN..

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

cat file‑to‑translate | tr "[A‑Z]" "[a‑z]" > new‑file‑name

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

cat myfile | tr "[A‑Z]" "[a‑z]" > lower_myfile

12.1.6. Преобразование строчных букв в прописные

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

$ echo "Look for the route, or make the route" | tr "[a‑z]" "[A‑Z]"

LOOK FOR THE ROUTE, OR MAKE THE ROUTE

$ echo "May Day, May Day, Going Down.." | tr ":lower:" ":upper:"

may day, may day, going down..

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

cat file‑to‑translate | tr "[a‑z]" "[A‑Z]" > new‑file‑name

где 'файл–для–преобразования' - преобразуемый файл, а 'имя–нового–файла' — имя, которое нужно присвоить новому файлу. Например:

cat myfile | tr "[a‑z]" "[A‑Z]" > upper_myfile

12.1.7. Удаление определенных символов

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

Приведенный ниже файл содержит часть составленного на неделю личного календаря. Задача заключается в устранении чисел. В личном календаре остаются в наличии только дни недели. Поскольку дни недели используются в формате как прописных, так и строчных букв, в этом случае применяют оба диапазона символов: [a‑z] и [A‑z]. При этом команда 'tr ‑cs "[a‑z] [A‑z]" "[\012*]"' выбирает все содержимое файла, которое не находится в пределах [a‑z] или [A-Z] (буквенных символов), содержащихся в строка1, и преобразует их в символы новой строки. В приведенной выше команде tr параметр ' -s' сообщает о необходимости "сокращения" всех символов новой строки, а параметр '-с' сохраняет без изменения все буквенные символы. Ниже приведен файл, содержащий данные личного календаря, после чего следует строка с утилитой tr и результат ее выполнения.

$ pg diary.txt

Monday 10:50

Tuesday 15:30

Wednesday 15:30

Thursday 10:30

Friday 09.20

$ tr -cs "[a‑z][A‑Z]""[\Q12*]" < diary.txt

Monday

Tuesday

Wednesday

Thursday

Friday

12.1.8. Преобразование управляющих символов

Чаще всего утилита tr применяется для преобразования управляющих символов, особенно во время загрузки файлов из DOS в UNIX. Если в команде ftp не задан параметр, выполняющий преобразование символов возврата каретки в символы новой строки, обычно применяют утилиту tr.

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

$ cat -v stat.txt

Boxes рарег^^^^^^12^M

Clips metal^^^^^^^50^M

Pencils‑medium^^^^^^10^M ^Z

В этом файле последовательность символов '^^^^^^' кодирует символы табуляции, каждая строка завершается управляющей последовательностью control‑m, а в конце файла находится управляющая последовательность Control‑Z. Ниже показано, как можно исправить положение.

В данном случае придется воспользоваться параметром '~s'. Если обратиться к таблице кодов ASCII, то восьмеричный код символа '^' равен 136. Соответствующее значение для управляющей последовательности ^M равно '015', для символа табуляции — '011', а для управляющей последовательности ^Z — '032'. Данная задача выполняется поэтапно.

Для замены в рассматриваемой команде последовательности символов '^^^^^^' символами табуляции используется следующий шаблон: "\136" "[\011*]". Затем полученные результаты перенаправляются во временный рабочий файл с именем stat.tmp.

$ tr -s '[\136]" "[\011*]" < stat.tr > stat.tmp

Boxes paper 12^M

Clips metal 50^M.

Pencils‑medium 10^М

^Z

Для замены управляющих последовательностей ^M, расположенных в конце каждой строки, символом новой строки и устранения управляющей последовательности ^Z применяется шаблон \n. Не следует забывать, что входные данные поступают из временного файла stat.tmp.

$ tr -s "[\015\032]" "\n" < stat.tmp

Boxes paper 12

Clips metal 50

Pencils‑medium 10

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

12.1.9. Быстрые преобразования

Если из файла необходимо удалить только управляющие последовательности ^M и заменить их символами новой строки, для этого применяется команда:

$ tr -s "[\015]" "\n" < файл_ввода

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

$ tr -s "[\r]" "[\n]" < файл_ввода

То же самое преобразование можно выполнить и с помощью команды:

$ tr -s "\r" "\n" < файл_ввода

Еще один распространенный вариант преобразования файлов, перенесенных из DOS в UNIX, иллюстрирует команда:

$ tr -s "[\015\032]" "[\012*]" < файл_ввода

Эта команда удаляет управляющие последовательности ^M и ^Z и заменяет их символами новой строки.

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

$ tr -s "[\011]" "[\040*]" < файл_ввода

Для замены в файле пароля passwd всех двоеточий символами табуляции, двоеточие следует заключить в кавычки и указать в строке замены восьмеричное значение символа табуляции, которое равно '011'. Файл станет более удобным для чтения. Сначала приводится файл passwd, а затем команда с утилитой tr, которая выполняет задачу.

$ pg passwd

halt:*:7:0:halt:/sbin:/sbin/halt

mail:*:8:12.mail:/var/spool/mail:

news:*:9:13:news:/var/spool/news:

uucp:*:10:14:tmcp:/var/spool/uucp:

$ tr -s "[:]" "[\011]" < passwd


halt * 7 0 halt /sbin /sbin/halt
mail * 8 12 mail /var/spool/mail
news * 9 13 news /var/spool/news
uucp * 10 14 uucp /var/spool/uucp

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

$ tr "[:]" "[\t]" < passwd

12.1.10. Сравнение с несколькими символами

Для выполнения сравнения с несколькими символами применяется формат [character*n]. Ниже приводится содержимое файла, описывающего жесткие диски системы. В файле содержатся диски, которые зарегистрированы или распознаны системой. Первый столбец содержит числа. Если этот столбец не состоит из одних нулей, то регистрируется соответствующий диск во втором столбце.

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

$ pg hdisk.txt

1293 hdisk3

4512 hdisk12

0000 hdisk5

4993 hdisk12

2994 hdisk7

При просмотре файла становится ясно, что имеется один жесткий диск, который не зарегистрирован. Для замены всех нулей, допустим, звездочками можно воспользоваться шаблоном [0*4], который означает поиск соответствия, по крайней мере, четырем нулям. При этом строка замены содержит только звездочки. Ниже приводится соответствующая команда и результат выполнения подобной фильтрации:

$ tr "[0*4]" "*" < hdisk.txt

1293 hdisk3

4512 hdisk12

**** hdisk5

4993 hdisk12

2994 hdisk7

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

12.2. Заключение

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

Часть 3

Регистрация в системе

ГЛАВА 13

Регистрация в системе

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

В этой главе рассматриваются следующие темы:

   • процесс регистрации

   • файл /etc/profile

   • файл $HOME/.profile

   • настройка пользовательского файла $HOME/.profile

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

[1] [ 2 ] [3] [4] [ 5 ] [ 6 ] [ 7 ]

kvp:JFqMmk9.uRioA:405:413:К.V. Pally:/home/sysdev/kvp:bin/sh dhw:hi/G4UlCUd9aI, B/OJ;407:401:D. Whitley:/home/dept47/dhw:/bin/sh aec:IL.gHtxJ9x. Xt, B/GI:408:401:A. E.Cloody:/b_user/dept47/aec:/bin/sh gdw:iLFu9BB8RNjpc, B/MK:409:401:G. D.Wiicom:/b_user/dept47/gdw:/bin/sh

Рассмотрим некоторые поля файла. Первое поле содержит регистрационное имя, второе поле — зашифрованный пароль, пятое поле — полное имя пользователя, шестое поле — начальный каталог данного пользователя, а седьмое поле описывает интерпретатор shell, применяемый пользователем. В данном случае параметр "/bin/sh" определяет интерпретатор shell, устанавливаемый по умолчанию. Обычно используется интерпретатор Bourne shell.

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

После успешной регистрации выполняются два файла среды. Первым является файл /etc/profile, а вторым — файл .profile, расположенный в начальном каталоге $HOME.

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

13.1. Файл /etc/profile

Информация файла профиля, находящегося в каталоге /etc, автоматически считывается при регистрации каждого пользователя. Этот файл обычно содержит: • глобальные или локальные переменные среды;

   • информацию о пути к файлам в переменной path;

   • параметры терминала; а также

   • меры безопасности;

а совет дня или сведения о причинах отказа.

Далее приводится краткое объяснение перечисленных выше пунктов.

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

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

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

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

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

Ниже приводится содержимое файла /etc/profile, который рассматривается далее.

$ pg /etc/profile

#!/bin/sh

#

trap "" 2 3

#Установка LOGNAME export LOGNAME

#установка дополнительных путей MAN MANPATH=/usr/opt/sybase/man

export MANPATH

# Установка TZ

if [ -f /etc/TIMEZONE ]

then

. /etc/TIMEZONE

fi

# Установка TERM

if [ -z "$TERM"]

then

TERM=vt220 # для стандартного асинхронного терминала/консоли

export TERM

fi

# Разрешение пользователю на прерывание отображения исключительно совета дня

trap "trap ' '2" 2

if [ -f /usr/bin/cat ] ; then

cat -s /etc/motd

fi

trap "" 2

if [ -f /usr/bin/mail]; then

if mail -e; then

echo "Hey guess what? you have mail"

fi

fi

#установка значения umask для более безопасного осуществления операций

umask 022

#установка сред

SY$HOME=/appdvb/menus

ASLBIN=/asl_b/bin

UDTKOME=/dbms_b/ud

UDTBIN=/dbms_b/ud/bin

PAGER=pg

NOCHKLPREQ=1

PATH=$PATH:$UDBIN:$ASLBIN

export PATH UDTHOME UDTBIN PAGER NOCHKLPREQ SY$HOME

trap 2 3

# Установка переменной SAVEDSTTY так, чтобы переменную можно было

# использовать для восстановления

# настроек stty при выходе из системы аудита.

SAVEDSTTY=`stty -g`; export SAVEDSTTY

# регистрация всех связей в syslog

logger -p lосаl7.info -t login $LOGNAME 'tty'

trap 'logger -p local7.info -t logout $LOGNAME 'tty'' 0

# отмена создания файлов дампа ядра

ulimit -с D

#

# проверка, зарегистрированы ли пользователи более двух раз, не считая…

case $LOGNAME in

idink | psalon | dave)

;;

*)

PID-$($); export PID

Connected=`who | awk '{print $1}' | fgrep -xc ${LOGNAME}`

if [ "$Connected" -gt 2 ]

then

echo

echo 'You are logged in more than twice.'.

echo `who -u | grep $LOGNAME`

echo

echo 'Enter <CR> to exit \c'

read FRED

kill -15 $PID

fi

;;

esac

# установка приглашения, включающего идентификатор пользователя

P$1="$LOGNAME>"

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

Рассмотрим последовательно, какие действия выполняются в приведенном листинге.

Первая строка является командой, задающей перехват двух сигналов, которые не дают пользователям возможности прервать выполнение файла с помощью нажатия клавиш [Quit] или [Ctrl+C].

Затем экспортируется значение переменной LOGNAME. В данной системе введены дополнительные справочные страницы (страницы man). Переменная MANPATH вносит их в список поиска по страницам уже имеющегося справочного руководства.

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

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

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

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

Далее инициализируются переменные среды. После установки и экспорта они становятся доступными каждому пользователю.

В следующей строке листинга восстанавливается перехват сигналов, соответствующих клавишам [Quit] или [Ctrl+C].

Затем происходит сохранение заданных по умолчанию параметров стандартного терминала stty. В результате этого при выходе пользователей из системы аудита можно повторно инициализировать параметры терминала.

Потом регистрируются все подключения пользователей в файле /var/adm/mes‑sages, который по умолчанию является журнальным файлом системы.

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

После этого следует небольшой фрагмент кода, который дает пользователям возможность регистрироваться дважды, но только одновременно для двух пользователей. Это правило не распространяется на следующих трех пользователей: idink, psalom, dave. Остальным пользователям будет отказано в доступе, если они попытаются зарегистрироваться более двух раз.

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

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

13.2. Пользовательский файл $НОМЕ/.profile

После выполнения файла /etc/profile пользователь попадает в собственный начальный каталог $HOME. Если вернуться к файлу passwd, то видно, что имя этого каталога находится во втором поле от конца файла.

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

Если имеется файл .profile, система использует его в качестве исходного файла. Значит, данный процесс обращается к существующему интерпретатору shell. В результате этого все существующие среды {/etc/profile) не будут изменяться под влиянием информации файла (.profile). При создании другого процесса локальные переменные интерпретатора shell фактически переписываются.

Вернемся к файлу .profile. Как правило, если учетная запись уже установлена, профильный файл в каком‑то виде уже существует. Важно помнить, что установки файла /etc/profile могут быть переопределены при добавлении в файл .profile нового элемента с другим значением либо при выполнении команды unset. Специальная настройка данного файла остается в ведении пользователя, поэтому рассмотрим стандартный файл .profile:

$ pg .profile

#.profile

set -a

MAIL=/usr/mail/$(LOGNAME:?)

PATH=$PATH export PATH

#

А теперь приступим к настройке данного файла.

Необходимо установить ряд переменных среды, подобных editor, чтобы утилита cron и другие приложения были осведомлены о применяемом редакторе. Кроме того, нас не устраивает режим эмуляции терминала vt220. Поэтому, изменим это значение на режим эмуляции терминала vt100. Это обстоятельство связано с переменной TERM.

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

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

$ P$1="`имя_сервера`>"

dns‑cepвep>

Или для текущего каталога, в котором вы находитесь,

$ P$1="\`pwd\`>"

\home\dave>

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

P$1='$PWD >';

В качестве дополнительной командной строки (которая обычно применяется при выполнении многострочной команды в командной строке) можно применять знак авторского права ©. Значение этого символа в коде ASCII равно восьмеричному числу 251, или десятичному числу 169.

$ PS2=" `echo "\251" `:"

/home/dave> while read line

©:do

©:echo $LINE

©:done

В Linux…

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

$ PS2="`echo -e "\251"`:"

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

ADMIN=/usr/adm

Кроме того, сразу же после регистрации желательно знать, сколько пользователей находится в системе. Для этого можно воспользоваться командой who или wc.

$ echo "`who | wc -l` users are on today"

19 users are on today

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

. /имя_пути/имя_файла

Чтобы использовать файл .profile в качестве исходного, достаточно ввести следующую команду:

$ . .profile

Если приведенный вариант не действует, можно попробовать следующий: $ . ./profile

Ниже показан измененный файл .profile.

$ pg .profile

#.profile

MAIL=/usr/mai1/${LOGNAME:?}

PATH=$PATH:$HOME:bin

#

EDITOR=vi

TERM=vt100

ADMIN=/usr/adm

P$1=" `hostname`>"

PS2=" `echo "\0251"`:"

export EDITOR TERM ADMIN PATH PS1

echo " `who | wc -l` users are on to‑day"

13.3. Применение команды stty

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

$ stty -а

speed 9600 baud; rows 24; columns 80; line=0

intr=^C; quit=^\; erase=^?; kill=^U; eof=^D; eol=<undef>;

eol2=<undef>; start=^Q; stop=^S; susp=^Z, rprnt=^R; werase=^W;

next=^V; flush=^0; min=1, time=0;

-parenb -parodd cs8 -hupci -cstopb cread -clocal -crtscts

-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -ignor icrnl ixon

-ixoif -iuclc -ixany -imaxbel

opcst -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel n10 cr0 tab0 bs0

vt0 ff0 isig icanon iexcen echo echoe echok -echonl

-noflsh -xcase

-tostop -echoprt echoctl echoke

Довольно часто при настройке терминала не работает клавиша [Backspace], т. е. при ее нажатии не происходит удаление символа. Данная команда stty сообщает, что в качестве клавиши [Backspace] используется комбинация клавиш ^?. Однако эта комбинация не действует. С помощью комбинации клавиш [Ctrl+H] можно вернуться на одну позицию и удалить предыдущий символ. Поэтому для устранения данной проблемы воспользуемся командой stty. Общий формат установки параметров команды stty в командной строке:

stty символ

Ниже показана установка управляющей последовательности ^H, соответствующей коду клавиши [Backspace]:

$ stty erase '\^H'

При использовании приведенной выше команды stty в файле .profile могут возникнуть затруднения. Команда stty на самом деле может предполагать наличие управляющей последовательности <CONTROL‑H>. Для устранения этого недостатка необходимо во время работы в редакторе vi поступить следующим образом: нажмите клавишу [Ctrl] и одновременно с ней клавишу [V], затем отпустите клавишу [V] и нажмите клавишу [Н].

Ниже приводятся наиболее распространенные наименования команды stty.

Наименование Клавиша Значение
intr ^C Завершение процесса
echo Подключение режима отображения
-echo Отключение режима отображения
eof ^D Конец файла, выход из системы
kill ^Y Удаление строки
start ^Q Начало прокрутки текста на экране
Stop ^S Завершение прокрутки текста на экране

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

stty -g

Этот параметр позволяет сохранить результаты выполнения команды stty в удобочитаемом формате. Затем эти результаты можно назначить команде stty, как показано выше на примере листинга файла /etc/profile. Для этого достаточно разместить содержимое команды stty -g в соответствующей переменной, ввести необходимые изменения в команду stty, а по завершении передать эти изменения команде stty.

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

$ pg password

#!/bin/sh

#пароль

#отображение процесса восстановления среды

stty SAVEDSTTY=`stty -g`

stty -echo

echo "\nGive me that password :\c"

read PASSWD

echo "\nyour password is $PASSWD

stty $SAVEDSTTY

$ sttypass

Give me that password : your password is bong

В Linux …

Чтобы сообщить Linux, что в строках предполагается применение управляющих символов, команду echo следует вводить как "echo -e".

SAVEDSTTY=`stty -g`

stty -echo

echo "\nGive me that password :\c"

read PASSWD

echo "\nyour password ls $PASSWD"

stty $SAVEDSTTY

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

13.4. Создание файла .logout

Одним из недостатков интерпретатора Bourne shell является отсутствие в нем файла .logout. Этот файл содержит команды, которые требуется выполнить непосредственно перед завершением сеанса работы в системе, т. е. до выполнения команды

exit.

Однако собственный файл .logout интерпретатора Bourne shell можно создать с помощью команды trap (более подробные сведения о команде trap и сигналах приведены далее в книге). Рассмотрим, как это делается. Отредактируйте свой файл .profile и разместите на последней строке приведенную ниже команду. Затем сохраните этот файл и выйдите из режима редактирования.

trap "$HOME/.logout" 0

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

$ pg .logout

rm -f $HOME/*.log

rm -f $HOME/*.tmp

echo "Bye…bye $LOGNAME"

Файл .logout вызывается при выходе пользователя из системы. Когда пользователь выходит из среды интерпретатора shell, система посылает сигнал 0, который и означает выход из текущего интерпретатора shell. Прежде чем управление передается обратно интерпретатору shell, для продолжения процесса выхода из системы, упомянутый выше сигнал перехватывается командой trap. Эта команда находится в соответствующей строке файла .profile. Затем выполняется файл .logout.

13.5. Заключение

Каждый пользователь может настроить свой файл $HOME/.profile в соответствии с требуемыми задачами. В главе было показано, каким образом можно переопределять общесистемные параметры согласно своим предпочтениям. Существует большое количество способов настройки пользовательской рабочей среды. Можно указывать приветственные сообщения и вносить существенные изменения в параметры терминала.

ГЛАВА 14

Переменные среды и интерпретатора shell

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

Существуют переменные двух типов — переменные интерпретатора (shell) и переменные среды. На самом деле имеется четыре типа переменных, но оставшиеся предназначены только для чтения. Эти переменные считаются специальными и применяются при передаче параметров в shell–сценарии.

В этой главе рассматриваются следующие темы:

   • переменные интерпретатора shell;

   • переменные среды;

   • подстановка переменных;

   • экспорт переменных;

   • специальные переменные;

   • передача информации в сценарии;

   • применение позиционных параметров в системных командах.

14.1. Понятие о переменных интерпретатора shell

Переменные позволяют выполнить настройку среды. Они содержат информацию, которая применяется определенным пользователем. Благодаря этому система получает более подробные сведения о пользователях. Кроме того, переменные используются для хранения констант. Рассмотрим, например, переменную под именем editor. Существует довольно много текстовых редакторов, но как выбрать наиболее подходящий из них? Присвойте имя нужного редактора переменной editor, и тогда именно этот редактор будет применяться, когда используется программа cron или другие приложения. Система обращается к значению переменной editor и применяет указанный редактор как заданный по умолчанию.

Для регистрации в системе sybase следует ввести команду:

$ isql -Udavet -Pabcd -Smethsys

где -S — имя сервера, с которым установлена связь. Переменная, содержащая имя сервера, называется dsquery. Имя сервера присваивается переменной dsquery. При регистрации в системе в случае, если имя сервера не указывается вместе с " — S", приложение обращается к переменной dsquery и использует значение этой переменной в качестве имени сервера. Для регистрации нужно ввести

$ isql -Odawet -Pabcd

Так функционирует большинство приложений.

14.2. Локальные переменные

Переменные интерпретатора shell могут использоваться сценариями в период функционирования интерпретатора shell. После завершения выполнения интерпретатора действие этих переменных прекращается. Например, локальная переменная имя_файла может иметь значение hops.doc. Это значение важно для пользователя только во время выполнения текущего интерпретатора команд; если запускается на выполнение другой процесс или происходит выход из среды интерпретатора команд, текущее значение локальной переменной не сохраняется. Переменные этого типа недоступны для других интерпретаторов shell или процессов, что имеет свои преимущества.

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

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

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

$ имя_переменной=значение или ${иня_перемениой=значение}

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

Таблица 14,1. Различные способы присваивания значений переменным

Имя переменной = значение Значение переменной присваивается переменной имя_переменной
Имя переменной + значение Значение переменной присваивается переменной
имя переменной, если оно установлено
Имя_переменной:?значение На экран выводится сообщение об ошибке, если не установлена переменная имя_переменной
Имя переменной? значение На экран выводится сообщение о системной ошибке, если не установлена переменная имя_переменной
Имя_переменной:=значение Значение переменной присваивается переменной имя_переменной, если она не установлена
Имя переменной: — значение Как и выше, но значение переменной имя_переменной не присваивается; оно может быть получено подстановкой

14.2.1. Отображение значения переменной

Чтобы отобразить значение отдельной переменной, достаточно применить команду echo и предварить имя переменной знаком $. Рассмотрим несколько примеров.

$ GREAT_PICTURE="die hard"

$ echo ${GREAT_PICTURE}

die hard

$ DOLLAR=99

$ echo $(DOLLAR)

99

$ LAST_FILE=ZLPSO.txt

$ echo ${LAST_FILE}

ZLPSO.txt

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

$ ERROR_MSG="Sorry this file does not exist user $LOGNAME"

$ echo ${ERROR_MSG}

Sorry this file does not exist user dave

В приведенном выше примере интерпретатор shell сначала выводит текст, затем рассматривает переменную $LOGNAME и отображает ее значение.

14.2.2. Удаление значения переменной

Чтобы удалить значение переменной, достаточно применить команду unset: unset имя_переменной

$ PC=enterprise

$ echo ${РС}

enterprise

$ unset PC

$ echo ${PC)

$

14.2.3. Отображение значений всех переменных интерпретатора shell

Чтобы просмотреть значения всех переменных интерпретатора shell, достаточно воспользоваться командой set.

$ set

PWD=/root

SHELL=/bin/sh

SHLVL=1

TERM=vt100

UID=7

USER=dave

dollar=99

great_picture=die hard

last_file=ZLPSO.txt

Вывод команды set может быть довольно обширен; при его изучении можно заметить, что интерпретатор shell значительно облегчил работу в среде.

14.2.4. Объединение значений переменных

Чтобы объединить значения переменных, достаточно последовательно расположить переменные:

echo ${имя_переменной}${имя_переменной}. . ,

$ FIRST="Bruce "

$ SURNAME=Willis

$ echo ${FIRST}${SURNAME}

Bruce Willis

14.2.5. Проверка на наличие значения переменной (подстановка)

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

${переменная: — значение}

Если переменная установлена, она применяется. Если переменная не установлена, вместо нее используется значение, например:

$ COLOUR=blue

$ echo "The sky is ${COLOUR:=grey} today"

The sky is blue today

Переменная colour имеет значение blue. Когда команда echo выводит на экран значение переменной colour, выполняется проверка, была ли установлена переменная ранее. Если переменная установлена, используется это значение. Выполним сброс значения переменной и рассмотрим результат.

$ COLOUR=blue

$ unset COLOUR

$ echo "The sky is ${COLOUR:=grey} today"

The sky is grey today

В приведенном примере переменной не присваивается действительное значение; для присваивания значения следует применить формат:

${переменная:=значение}

Рассмотрим более полезный пример, в котором содержится запрос о времени создания платежной ведомости (payroll), а также относительно ее типа. Если в обоих случаях при указании времени и типа нажать клавишу [Enter], то значения этих переменных не будут установлены пользователем. В этом случае применяются новые значения, заданные по умолчанию (03:00 и Weekly). Затем значения передаются команде at, что свидетельствует о переносе выполнения задания на более поздний срок.

$ pg vartest

#!/bin/sh

# vartest

echo "what time do you wish to start the payroll [03:00]:"

read TIME

echo " process to start at ${TIME:=03:00} OK"

echo "Is it a monthly or weekly run [Weekly]:"

read RUN_TYPE

echo "Run type ls ${RUN_TYPE:=Keekly}"

at -f $RUN_TYPE $TIME

Ниже при выводе данных выбирается клавиша [Enter] в обоих случаях.

$ vartest

what time do you wish to start the payroll [03:00]:

process to start at 03:00 OK ls it a monthly or weekly run [Weekly]:

Run type is Weekly

warning: commands will be executed using /bin/sh job 15 at 1999-05-14 03:00

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

$ echo "The file is ${FILES:?}"

sh: files: parameter null or not set

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

$ echo "The file is $(FILES:?} sorry cannot locate the variable files")"

sh: files: sorry cannot locate the variable files

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

${переменная:+значение)

Для инициализации переменной значением пустой строки примените команду:

variable=""

$ DESTINATION=""

14.2.6. Применение переменных, содержащих аргументы системных команд

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

$ SOURCE="/etc/passwd"

$ DEST="/tmp/passwd.bak"

$ cp ${SOURCE} ${DEST}

В примере ниже с помощью переменной device получаем путь к накопителю на магнитной ленте. Эти сведения используются затем в команде mt при перемотке ленты.

$ DEVICE="/dev/rmt/0n"

$ mt -f ${DEVICE} rewind

14.2.7. Как сделать переменную доступной только для чтения

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

имя_переменной=значение readonly имя_переменной

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

$ TAPE_DEV="/dev/rmt/0n"

$ echo ${TAPE_DEV}

/dev/rmt/0n

$ readonly TAPE_DEV

$ TAPE_DEV="/dev/rmt/1n"

sh: TAPE_DEV: read‑only variable

Чтобы просмотреть все переменные, которые доступны только для чтения, примените команду readonly.

$ readonly

declare -r FILM="Crimson Tide"

declare -ri PPID="1"

declare -r TAPE_DEV="/dev/rmt/0n"

declare -ri UID="0"

14.3. Переменные среды

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

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

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

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

14.3.1. Присваивание значений переменным среды

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

VARIABLE_NAME=значение; export VARIABLE_NAME

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

VARIABLE_NAME = значение export VARIABLE_NAME

14.3.2. Отображение значений переменных среды

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

$ CONSOLE=tty1; export CONSOLE

$ echo $CONSOLE

tty1

$ MYAPPS=/usr/local/application; export MYAPPS

$ echo $MYAPPS /usr/local/application

Чтобы просмотреть глобально определенные переменные среды, достаточно воспользоваться командой env.

$ env

HISTSIZE=1000

HOSTNAME=localhost.localdomain

LOGNAME=dave

MAIL=/var/spool/mail/root

TERM=vt100

HOSTTYPE=i386

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/XllR6/bin:/root/bin:

CONSOLE=tty1

HOME=/home/dave

ASD=sdf

SHELL=/bin/sh

P$1=$

USER=dave

14.3.3. Удаление значений переменных среды

Чтобы удалить значение переменной, следует применить команду unset.

$ unset MYAPPS $ echo $MYAPPS

$

14.3.4. Встроенные переменные интерпретатора shell

Интерпретатор Bourne shell располагает именами, которые зарезервированы для переменных среды. Эти названия переменных нельзя использовать иначе. Обычно они содержатся в файле /etc/profile. Однако так бывает не всегда, поэтому пользователь может самостоятельно устанавливать значения этих переменных. Ниже приводится перечень переменных, встроенных в интерпретатор shell.

Переменная CDPATH

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

$ CDPATH=/home/dave/bin:/usr/local/apps; export CDPATH

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

$ cd apps

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

Переменная EXINIT

При работе с редактором vi переменная EXINIT предоставляет опции для инициализации этого редактора. Например, для установки нумерации всех строк и шага табуляции, равного 10 пробелам, примените следующую команду:

$ EXINIT=='set nu tab=10'; export EXINIT

Переменная HOME

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

$ HOME=/home/dave; export HOME

$ pwd

$ /usr/local

$ cd

$ pwd

$ /home/dave

Можно также применить команду:

$ cd $НОНЕ

Переменная IFS

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

$ export IFS=:

$ echo $PATH

/sbin /bin /usr/sbin /usr/bin /usr/X11R6/bin /root/bin

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

$ IFS=<space><tab>; export IFS

где значения <space><tab> задают использование в качестве разделителей полей символов пробела и табуляции.

Переменная LOGNAME

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

$ LOGNAME=`whoami` ; export LOGNAME

$ echo $LOGNAME

dave

Переменная MAIL

Переменная mail хранит имя пути доступа и имя файла для вашего почтового ящика. По умолчанию ее значение /var/spool/mail/<регистрационное имя>. Интерпретатор shell периодически проверяет почтовый ящик на предмет наличия новых сообщений. Если пользователь получает электронное сообщение, он узнает об этом из командной строки. Если к почтовому ящику имеется другой путь, установите его с помощью переменной mail:

$ MAIL=/usr/mail/dave; export MAIL

Переменная MAILCHECK

Значение переменной mailcheck задается по умолчанию таким образом, чтобы наличие новой электронной почты проверялось каждые 60 секунд. Если электронную почту следует запрашивать реже, допустим каждые 2 минуты, примените команду:

$ MAILCHECK=120; export MAILCHECK

Переменная MAILPATH

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

$ MAILPATH=/var/apool/dave:/var/epool/admin; export MAILPATH

В данном примере переменная mail проверяет два почтовых ящика — dave и admin.

Переменная PATH

Переменная path хранит сведения о каталогах, где находятся выполняемые команды или сценарии. Важно, чтобы эта последовательность указывалась правильно, поскольку при обращении к этой переменной существенно сокращается время выполнения команды или сценария. Например, если в каком‑либо каталоге нет заданной команды, то не следует его просматривать. В общем случае лучше сначала задать каталог НОМЕ, за которым будет следовать перечень каталогов. Перечень включает каталоги, отсортированные по частоте применения. Сначала указываются наиболее часто применяемые каталоги, а затем те из них, которые применяются реже всего. Если нужно найти текущий рабочий каталог, то независимо от места его нахождения используйте точку. Каждый каталог отделяется двоеточием. Рассмотрим пример.

$ PATH=$HOME/bin: . :/bin:/usr/bin; export PATH

Согласно приведенному примеру, сначала производится поиск в каталоге НОМЕ/ bin, затем — в текущем каталоге, каталоге /bin и в каталоге /usr/bin.

Значение переменной path для системных каталогов задается в файле /etc/profile. Чтобы воспользоваться переменной path, определяя собственный вариант переменной path, достаточно в конце указать точку с запятой (;}.

$ PATH=$PATH:/$HOME/bin:. ; export PATH

В данном случае используется переменная path, определенная в файле /etc/profile, а также указан каталог $HOME/bin и текущий каталог. В общем случае нежелательно использовать текущий каталог при записи первого пути поиска. Если допустить подобное, то текущий каталог станет доступным и для других пользователей.

Переменная P$1

Основная командная строка включает символ приглашения интерпретатора shell. По умолчанию для основной командной строки используется символ #, а для любой другой командной строки применяется символ $. В командной строке можно использовать любые символы. Ниже приводятся два примера.

$ P$1="star trek:"; export P$1

star trek:

$ P$1=" ->" ; export P$1

->

Переменная PS2

Значение этой переменной задает вид вторичной командной строки. По умолчанию этот тип командной строки определяется символом >. Подобная командная

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

$ PS2="@:"; export PS2

$ for loop in *

@:do

@:echo $LOOP

Переменная SHELL

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

$ echo $SHELL

/bin/sh

Переменная TERMINFO

Переменная, определяющая инициализацию терминала, хранит сведения о местонахождении файлов конфигурации терминала. Обычно эти файлы находятся либо в каталоге /usr/lib/terminfo, либо в каталоге /usr/share/terminfo.

$ TERMIHFO=/uer/lib/terminfo; export TERMIHFO

Переменная TERM

Переменная term представляет тип терминала. Эта переменная служит для указания типа управляющих последовательностей, необходимых для правильного функционирования экрана и клавиатуры. Самыми распространенными типами терминалов являются vt100, vt220, vt220-8, wyse и т. д.

$ TERM=vt100; export TERM

Переменная TZ

Эта переменная позволяет определить часовой пояс. Значение переменной TZ устанавливает администратор. Если для интерпретатора shell потребуется изменить значение этой переменной, обратите внимание на следующие примеры:

$ echo $TZ

GMT2EDT

В данном случае возвращается значение, которое свидетельствует о том, что отсчет времени происходит по Гринвичу (Greenwich Mean Time) (временное смещение относительно GMT составляет 0 часов) в диапазоне летнего времени (Eastern Daylight Saving).

14.3.5. Другие переменные среды

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

Переменная EDITOR

Присвойте этой переменной название предпочитаемого вами текстового редактора.

$ EDITOR=vi; export EDITOR

Переменная PWD

Эта переменная представляет имя пути доступа для текущего каталога. Обычно

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

Переменная PAGER

Данная переменная хранит сведения о командах программы постраничной разбивки. К подобным командам можно отнести, например, pg, more. Система проверяет значение этой переменной, когда пользователь выполняет просмотр страниц man.

$ PAGER='pg -f -p%d'; export PAGER

Переменная MANPATH

Представляет каталоги, которые включают страницы справочной программы man. Имя каждого каталога отделяется двоеточием.

$ MANPATH=/usr/apps/man:/usr/local/man; export MANPATH

Переменная LPDEST или переменная PRINTER

Эта переменная представляет имя принтера, заданного по умолчанию. При выводе на печать сохраняется имя принтера, заданное пользователем.

$ LPDEST=hp3si_systems

14.3.6. Применение команды set

Экспорт переменных среды можно задать при установке этих переменных в файле $HOME .profile. Воспользуйтесь командой set с параметром " -a" в виде "set -а". Это означает, что выполняется экспорт всех переменных. Не применяйте этот метод при работе с файлом /etc/profile. Данный метод применим только при работе с файлом $HOME/.profile.

$ pg .profile

#.profile

set -a

MAIL=/usr/mail/${LOGNAME:?}

PATH=$PATH:$HOME:bin

#

EDITOR=vi

TERM vt220

ADMIN=/usr/adm

P$1="`hostname` >>"

14.3.7. Экспорт переменных в дочерние процессы

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

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

Рассматриваемый листинг включает два небольших сценария под именами father и child. Сценарий father задает переменную с именем film, значение которой "A Few Good Men". Эта переменная отображается на экране, затем вызывается другой сценарий под названием child. Данный сценарий отображает переменную film из первого сценария. Перед отображением на экране значением переменной film становится "Die Hard". Затем управление передается назад, сценарию father, и значение переменной снова выводится на экран.

$ pg father

#!/bin/sh

# сценарий father.

echo "this ls the father"

FILM="A Few Good Men"

echo "I like the film: $FILM"

# вызов сценария child

child

echo "back to father"

echo "and the film ls :$FILM"

$ pg child

#!/bin/sh

# сценарий child

echo "called from father..! am the child"

echo "film name is :$FILM"

FILM="Die Hard"

echo "changing film to :$FILM"

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

$ father

this ls the father

I like the film :A Few Good Men

called from father… i am the child

film name is :

changing film to :Die Hard

back to father

and the film ls :A Few Good Men

Сценарий child не может возвратить переменную film, поскольку эта переменная не может быть экспортирована в сценарий father.

Теперь, если команду export добавить в сценарий father, сценарий child получит всю информацию о переменной film.

$ pg father

#!/bin/sh

# сценарий father.

echo "this ls the father"

FILM="A Few Good Men"

echo "I like the film :$FILM"

#вызов сценария child

#сначала экспортируйте переменную

export FILM

child

echo "back to father"

echo "and the film is :$FILM"

$ father2

this ls the father

I like the film :A Few Good Men

called from father… i am the child

film name ls :A Few Good Men

changing film to :Die Hard

back to father

and the film ls :A Few Good Men

После того как команда export включена в состав сценария, можно выполнить произвольное число процессов. И всем этим процессам известно о наличии переменной FILM.

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

14.4. Позиционные параметры командной строки

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

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

В приведенной таблице в качестве примера демонстрируется, как можно получить доступ к каждому параметру, если в сценарий передается строка "Did You See The Full Moon"


$0 $1 $2 $3 $4 $5 $6 $7 $8 $9
Наименование сценария Did You See The Full Moon

14.4.1. Применение в сценариях позиционных параметров

Воспользуемся приведенным выше примером в следующем сценарии.

$ pg param

#!/bin/sh # параметры

echo "This ls the script name : $0"

echo "This ls the first parameter : $1"

echo "This ls the second parameter : $2"

echo "This ls the third parameter : $3"

echo "This ls the fourth parameter : $4"

echo "This ls the fifth parameter : $5"

echo "This ls the sixth parameter : S6"

echo "This ls the seventh parameter : $7"

echo "This ls the eighth parameter : $8"

echo "This ls the ninth parameter : $9"

$ param Did You See The Full Moon

This ls the script name : ./param

This ls the first parameter : Did

This ls the second parameter : You

This ls the third parameter : See

This ls the fourth parameter : The

This ls the fifth parameter : Full

This ls the sixth parameter : Moon

This ls the seventh parameter :

This ls the eighth parameter :

This ls the ninth parameter :

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

$ pg param2 .

#!/bin/sh

echo "Hello world this ls $0 calling"

$ param2

Hello world this ls ./param2 calling

Заметьте, что параметр $0 также позволяет получить путь доступа к текущему каталогу. Для получения названия сценария предварите параметр $0 командой

basename.

$ pg param2

#!/bin/sh

echo "Hello world this ls `basename $0` calling"

$ param2

Hello world this ls param2 calling

14.4.2. Передача параметров в системные команды

Параметры можно передавать внутри сценария в системную команду. В следующем примере используется команда find . Параметр $1 указывает имя искомого файла.

$ pg findfile

#!/bin/sh

# findfile

find / -name $1 -print

$ findfile passwd

/etc/passwd

/etc/uucp/passwd

/usr/bin/passwd

Ниже приводится другой пример. В виде параметра $1 файл user‑id передается команде grep; команда grep использует эти сведения для поиска файла passwd, где содержится полное имя пользователя.

$ pg who_is

#!/bin/sh

# who_is

grep $1 passwd | awk -F: {print $4}'

$ who_is seany

Seany Post

14.4.3. Специальные параметры

Теперь, когда вы изучили, как получить доступ к параметрам shell–сценариев и применять их, было бы полезным узнать об этих параметрах больше. Рассмотрим, каким образом сценарий применяет специальные параметры. Существует семь специальных параметров, которые представлены в табл. 14.2.

Таблица 14.2. Специальные параметры интерпретатора shell

$# Число аргументов, передаваемых сценарию
$* В отдельной строке отображаются все аргументы, которые передаются сценарию. Здесь может содержаться более девяти параметров, в отличие от позиционных параметров
$$ Текущий идентификатор PID для выполняющегося сценария
$! Идентификатор PID для последнего процесса, который выполняется в фоновом режиме
$@ Означает то же самое, что и параметр $#, но, если параметр заключен в кавычки, то и каждый аргумент отображается в кавычках
$- Отображение текущих опций интерпретатора команд; аналогично применению команды set
$? Показывает код завершения последней команды. Значение 0 свидетельствует об отсутствии ошибок, любое другое значение -o, их наличии

Преобразуем сценарий param, применяя некоторые специальные параметры. Затем выполним сценарий.

$ pg param

#!/bin/sh

# все параметры

echo "This ls the script name : §0"

echo "This ls the first parameter : $1"

echo "This is the second parameter : $2"

echo "This ls the third parameter : $3"

echo "This ls the fourth parameter : $4"

echo "This ls the fifth parameter : $5"

echo "This ls the sixth parameter : $6"

echo "This ls the seventh parameter : $7"

echo "This ls the eighth parameter : $8"

echo "This ls the ninth parameter : $9"

echo "The number of arguments passed : $#"

echo "Show all arguments : $*"

echo "Show me my process ID : $$"

echo "Show me the arguments in quotes :" "$@"

echo "Did my script go with any errors : $?"

$ param Merry Christmas Mr Lawrence

This ls the script name : ./param

This ls the first parameter : Merry

This ls the second parameter : Christmas

This ls the third parameter : Mr Lawrence

This ls the fourth parameter :

This ls the fifth parameter :

This ls the sixth parameter :

This ls the seventh parameter ;

This ls the eighth parameter :

This ls the ninth parameter :

The number of arguments passed ;3

Show all arguments : Merry Christmas Mr Lawrence

Show me my process ID : 630

Show me the arguments in quotes :"Merry" "Christmas" "Mr Lawrence"

Did my script go with any errors :0

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

14.4.4. Код завершения последней команды

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

Ниже приводится пример. Сначала файл копируется в каталог /tmp и с помощью параметра $? проверяется результат.

$ cр ok.txt /tmp

$ echo $?

0

Скопируем файл в несуществующий каталог:

$ cp ok.txt /usr/local/apps/dsf

cp: cannot create regular file '/usr/local/apps/dsf': No such file or

directory

$ echo $?

1

При проверке состояния возврата с помощью параметра $? отображается 1. Это свидетельствует о наличии ошибок. Появляется сообщение о системной ошибке "cp: cannot…", поэтому не требуется проверять код завершения последней команды. Сценарии функционируют с помощью системных команд. Обычно пользователь не заинтересован в отображении большого количества значений. Поэтому выходной поток перенаправляется в системную корзину /dev/null Каким образом пользователь узнает, что последняя команда была успешной? С помощью кода завершения последней команды. Рассмотрим пример, иллюстрирующий изложенный материал.

$ cp ok.txt /uer/local/appe/dsf >/dev/null 2>&1

$ echo $?

1

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

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

$ cp ok.txt /usr/local/apps/dsf >/dev/null 2>&1

$ cp_status=$?

$ echo $cp_status

1

14.5. Заключение

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

ГЛАВА 15

Использование кавычек

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

В этой главе рассматриваются следующие темы:

   • правила применения кавычек;

   • двойные, одинарные и обратные кавычки;

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

15.1. Правила применения кавычек

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

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

$ echo Hit the star button to exit *

Hit the star button to exit DIR_COLORS HOSTNAME Muttrc X11 adjtime aliases alias

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

$ echo "Bit the star button to exit *"

Hit the star button to exit *

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

Таблица 15.1. Использование кавычек интерпретатором shell


"" Двойные кавычки
' ' Одинарные кавычки
`` Символ "тупого ударения" или обратные кавычки
\ Символ обратной косой черты

15.2. Двойные кавычки

Двойные кавычки применяются, когда необходимо буквальное восприятие всех символов, за исключением символов: $, ` , \. Символы доллара, обратной кавычки и символ обратной косой черты сохраняют свое специальное значение при функционировании интерпретатора shell. Если при выводе на экран переменной присваивается текстовая строка, заключенная в двойные кавычки, речь идет о выводе на экран самой переменной.

$ STRING="MAY DAY, MAY DAY, GOING DOWN"

$ echo "$STRING"

MAY DAY, MAY DAY, GOING DOWN

$ echo $STRING

MAY DAY, MAY DAY, GOING DOWN

Предположим, что сведения о системной дате присваиваются переменной с именем mydate.

$ MYDATE="date"

$ echo $MYDATE

date

Интерпретатор команд воспринимает информацию в строке "как есть", т. е. дате не присваивается специальное значение. Поэтому в качестве даты фигурирует слова data

Двойные кавычки следует применять при поиске строки, содержащей пробелы. Воспользуемся командой grep для поиска имени Davey Wire. Если не применять кавычки, команда grep воспримет Davey как строку для поиска, a Wire — как файл.

$ grep Davey Wire /etc/passwd

grep: Wire: No such file or directory

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

$ grep "Davey Hire" /etc/passwd

davyboy:9sdJUK2s:106:Davey Wire:/home/ap

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

$ ВОY="boy"

$ echo " The $BOY did well"

The boy did well

$ echo "The "$BOY" did well"

The boy did well

15.3. Одинарные кавычки

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

$ GIRL='girl'

$ echo "The '$GIRL' did well"

The 'girl' did well

15.4. Обратные кавычки

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

В следующем примере интерпретатор shell пытается выполнить подстановку слова hello. Но, поскольку нет ни команды, ни сценария с таким именем, отображается сообщение об ошибке.

$ echo `hello`

sh: hello: command not found

Применим снова команду date.

$ echo `date`

Sun May 16 16:40:19 GMT 1999

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

Присвоим переменной mydate данные вывода команды date. Ниже приводится формат команды date:

$ date +%A" the "%e" of "%B" "%Y

Sunday the 16 of May 1999

Присвоим значение переменной mydate и отобразим его.

$ mydate=`date +%A" the "%e" of "%В" "%Y`

$ echo $mydate

Sunday the 16 of May 1399

Конечно, переменной mydate можно присвоить весь вывод команды date:

$ mydate=`date`

$ echo $mydate

Sun May 16 16:48:16 GMT 1999

Ниже приводится другой пример. Обратные кавычки содержатся внутри двойных кавычек:

$ echo "The date today ls `date` "

The date today ls Sun May 16 16:56:53 GMT 1999

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

$ echo "There are `who | wc -l` users on the system"

There are 13 users on the system

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

15.5. Обратная косая черта

Символ обратной косой черты указывает интерпретатору shell, что следующий за ним символ не имеет специального значения. Специальное значение могут иметь такие символы: & *=^ $ ` " | ?.

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

$ echo *

conf.linuxconf conf.modules cron.daily cron.hourly cron.monthly cron.weekly crontab csh.cshrc default dosemu.conf dosemu.users exports fdprm fstab gettydefs gpm‑root.c onf group group- host.conf hosts hosts.allow hosts.deny httpd inetd

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

$ echo \*

*

Аналогичный результат можно получить с помощью команды $$. Интерпретатор shell воспринимает ее как текущий PID (ID процесса). Чтобы отключить специальное значение и вывести на экран символ $$, нужно перед этим символом поместить символ обратной косой черты:

$ echo $$

284

$ echo \$$

$$

Для вывода на экран символов, заданных восьмеричными ASCII–кодами, перед ними следует размещать символ обратной косой черты. В противном случае интерпретатор shell воспримет их как обычные числа.

$ echo "This ls a copyright 251 sign"

This is a copyright \251 sign"

$ echo "This is a copyright \251 sign"

This ls a copyright © sign"

В Linux…

He забывайте при выводе управляющих символов указывать параметр " -e".

$ echo -e "This ls a copyright \251 sign"

This ls a copyright © sign

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

$ expr 12 * 12

expr; syntax error

$ expr 12 \* 12

144

Чтобы оператор echo включал метасимволы, воспользуйтесь символом обратной косой черты. В следующем примере отображается цена $19.99. Поскольку не применяется символ обратной косой черты, интерпретатор shell трактует эту цену иначе.

$ echo * "That video looks a good price for $19.99"

That video looks a good price for 9.99

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

$ echo "That video looks a good price for \$19.99"

That video looks a good price for $19.99

15.6. Заключение

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

   1. Всегда применяйте двойные кавычки при выводе на экран строк командой echo.

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

Часть 4

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

ГЛАВА 16

Понятие о shell–сценарии

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

В этой главе рассматриваются следующие темы:

   • цель создания shell–сценариев;

   • основные компоненты shell–сценария;

   • способы запуска shell–сценария.

16.1. Зачем создаются shell–сценарии

Применение shell–сценариев позволяет экономно расходовать рабочее время при выполнении важных и сложных заданий. В конце концов, почему бы для выполнения определенного задания не использовать листинг команд? Затем можно только просмотреть данные вывода. Если результаты удовлетворительные, можно переходить к выполнению следующего задания. Если же результаты не устраивают вас, следует внимательно изучить листинг. Shell–сценарии производят сортировку файлов, вставку текста в файлы, перемещение файлов, удаление строк из файлов, а также удаление старых файлов из системы. Shell–сценарии также выполняют в системе некоторые административные задания. В этих сценариях используются переменные, условные, арифметические и циклические конструкции, которые можно отнести к системным командам. За счет этих возможностей сценарии создаются довольно быстро. Интерпретатор команд может применять в качестве вводных данных для одной команды информацию, полученную при выполнении другой команды. Чтобы shell–сценарий применялся в различных системах UNIX и Linux, в него нужно внести лишь небольшие изменения. Это связано с тем, что интерпретатор shell обладает высокой степенью универсальности. Определенные трудности возникают лишь вследствие ориентации системных команд на определенные системы.

16.1.1. Не отказывайтесь от новых идей

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

16.2. Структура сценария

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

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

#!/bin/sh

Система получает указание, где следует искать интерпретатор Bourne shell.

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

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

16.3. Выполнение сценария

Ниже рассматривается пример, который уже обсуждался ранее. В данном случае, файл называется cleanup.

$ pg cleanup

#!/bin/sh

#имя: cleanup

#это общий сценарий, выполняющий очистку echo "starting cleanup…wait"

rm /usr/local/apps/log/*.log

tail -40 /var/adm/messages /tmp/messages

rm /var/adm/messages

mv /tmp/messages /var/adm/messages

echo "finished cleanup"

Приведенный выше сценарий отменяет отображение сообщений /var/adm/ путем усечения файла сообщений. В задачи этого сценария также входит удаление всех журнальных файлов в каталоге /usr/local/apps/log.

Для выполнения сценария применим команду chmod:

$ chmod u+x cleanup

Чтобы запустить сценарий на выполнение, введите его название:

$ cleanup

При отображении сообщения об ошибке, например:

$ cleanup

sh: cleanup: command not found

воспользуйтесь командой: $ ./cleanup

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

$ pwd

$ /home/dave/bin

Если последняя часть команды pwd включает название подкаталога /bin, его следует использовать при указании имени пути. Измените файл .profile и добавьте в файл .profile каталог $HOME/bin:

PATH=$PATH:$HOME/bin

В случае если подкаталог /bin отсутствует, создайте его; сначала удостоверьтесь, что находитесь в начальном каталоге.

$ cd $HOME

$ mkdir bin

После этого добавьте каталог bin в переменную PATH в файле .profile, затем заново инициализируйте файл .profile

$ . .profile

Теперь все должно получиться.

Если же проблемы остались, просмотрите главы 2 и 13. В этих главах содержатся сведения о командах find и xargs, а также о настройках переменных среды, которые окажутся полезными при создании и выполнении сценариев.

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

16.4. Заключение

Глава служит кратким введением в методику работы с shell–сценариями. Вероятно, вам пока нет необходимости подробно изучать вопросы, связанные с функционированием сценариев, вводом/выводом данных интерпретатора shell и т. п. Необходимые знания придут после внимательного изучения материала книги. Глава помогает пользователю уяснить, как начать работу с shell–сценарием.

ГЛАВА 17

Проверка условий

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

В главе рассматриваются следующие темы:

   • применение команды test для обработки файлов, строк и чисел;

   • использование команды expr при проверке численных значений и строк. Команда expr выполняет проверку и вывод численных данных. Команды test

и expr с помощью кода завершения последней команды $? выводят на экран значение 0, если заданное условие выполняется, и 1, если условие не выполняется.

17.1. Проверка прав доступа к файлу

Основные форматы команды test:

test условие

или

[ условие ]

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

Таблица 17.1. Проверка прав доступа к файлу


-d Каталог
-f Обычный файл
-L Символическая связь
-r Файл для чтения
-s Файл имеет ненулевой размер, он не пуст
-w Файл для записей
-u Файл имеет установленный бит suid
-x Исполняемый файл

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

$ ls -l scores.txt

-rw‑r—г -l dave admin 0 May 15 11:29 scores.txt

$ [ -w scores.txt ]

$ echo $?

0

$ test -w scores.txt

$ echo $?

0

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

$ [ -х scores.txt ]

$ echo $?

1

Нет, не является, что и следовало ожидать после изучения списка прав доступа для файла scores.txt.

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

drwxr‑xr‑x 2 dave admin 1024 May 15 15:53 appsbin

$ [ -d appsbin ]

$ echo $?

0

Ответ утвердительный, каталог appsbin присутствует. Чтобы проверить, установлен ли бит suid, примените:

-rwsr‑x--- 1 root root 28 Apr 30 13:12 xab

$ [ -u xab ]

$ echo $?

0

Из этого примера следует, что бит suid установлен.

17.2. Применение логических операторов при осуществлении проверки

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


-a Логическое AND, возвращает истину, если обе части оператора принимают истинное значение
-o Логическое OR, возвращает истину, если какая‑либо из частей оператора может принимать истинное значение
! Логическое NOT, возвращает истину, если условие ложно

А теперь выполним сравнение следующих файлов:

-rw‑r--r-- 1 root root 0 May 15 11:29 scores.txt

-rwxr‑xr—- 1 root root 0 May 15 11:49 results.txt

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

$ [ -w results.txt -a -w scores.txt ]

$ echo $?

0

Ответ звучит утвердительно.

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

$ [ -х results.txt -о -x scores.txt ]

$ echo $?

0

Файл scores.txt не является исполняемым, а файл results.txt — исполняемый. Чтобы проверить, установлены ли для файла results.txt права записи и выполнения, примените следующую команду:

$ [ -w results.txt -a -x results.txt ]

$ echo $?

0

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

17.3. Проверка строк

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

test "строка"

test оператор_строки "строка"

test "строка" оператор_строки "строка"

[ оператор_строки строка ]

[ строка оператор_строки строка ]

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

== Две строки равны

!= Две строки не равны

-z Эта строка нулевая

-n Эта строка не является нулевой

Чтобы проверить, присвоено ли переменной среды editor нулевое значение, примените команду:

$ [ -х $EDITOR ]

$ echo $?

1

Ответ отрицательный. Присвоено ли этой переменной значение vi?

$ [ $EDITOR= "vi" ]

$ echo $?

0

Ответ утвердительный. Отобразим это значение на экране:

$ echo $EDITOR

vi

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

$ TAPE="/dev/rmt0"

$ TAPE2="/dev/rmt1"

$ [ "$TAPE" = "$ТАРЕ2" ]

$ echo $?

1

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

Чтобы проверить "неравенство" переменных таре и таре2, примените следующую команду:

$ [ »$TAPE" != "$ТАРЕ2" ]

$ echo $?

0

Значения этих переменных не равны.

17.4. Проверка чисел

Для сравнения чисел можно воспользоваться операторами другого рода. Общий формат:

"число" числовой_оператор "число" или

[ "число" числовой_оператор "число" ]

где в качестве выражения числовой_оператор могут фигурировать следующие операторы:

-eq Два числа равны
-ne Два числа не равны
-gt Первое число больше второго числа
-lt Первое число меньше второго числа
-le Первое число меньше или равно второму числу
-gt Первое число больше или равно второму числу

Выясним, равно ли одно число другому (в данном случае рассматривается, равно ли число 130 числу 130):

$ NUMBER=130

$ [ "$NUMBER" -eq "130" ]

$ echo $?

0

Превосходно! Результатом сравнения явилось значение "истина". Теперь изменим второе число и проверим, отобразится ли сообщение об ошибке. Возвращается значение 1 (130 не равно 100(.

$ [ "$NUMBER" —eq "100" ] $ echo $?

1

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

$ [ "$NUMBER" —gt "100" ]

$ echo $?

0

Ответ утвердительный.

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

$ SOURCE_COUNT=13

$ DEST_COUNT-15

$ [ "$DEST_COUHT" -gt "$SOURCE_COUNT" ]

$ echo $?

0

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

$ [ "990" -le "995" ] $ echo $?

0

Можно также комбинировать и тестировать выражения с помощью логических операторов. При этом следует пользоваться только одной парой квадратных скобок — не применяйте две пары скобок. Если не учитывать этого замечания, отобразится сообщение об ошибке — "too many arguments" (слишком много аргументов):

$ [ "990" -le "995" ] —а [ "123" —gt "33" ]

sh:[: too many arguments

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

$ [ "990" -le "995" -a "123" -gt "33" ] $ echo $?

0

17.5. Применение команды expr

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

expr аргумент оператор аргумент

Команду expr удобно использовать для подсчета количества строк:

$ expr 10 + 10

20

$ expr 900 + 600

1500

$ expr 30/3

10

$ expr 30/3/2

5

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

$ expr 30 \*.3

90

17.5.1. Приращение переменной цикла


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

$ loop=0

$ LOOP=`expr $LOOP + 1`

17.5.2. Проверка численных значений

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

$ expr rr + 1

expr: нечисловой аргумент

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

$ VALUE=12

$ expr $VALUE + 10 > /dev/null 2>&1

$ echo $?

0

Это — число.

$ VALUE=hello

$ expr $VALUE + 10 > /dev/null 2>&1

$ echo $?

2

А это — не численное значение.

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

$ VALUE=hello

$ expr $VALUE="hello"

1

$ echo $?

0

Команда expr возвращает единицу. Не беспокойтесь, все идет хорошо. Теперь выполним проверку с помощью кода завершения последней команды: возвращается значение нуль. В ходе проверки выяснилось, что строка "hello" действительно равна строке "hello".

17.5.3. Поиск по шаблону

С помощью команды expr можно реализовать поиск по шаблону. Подсчет количества символов строки возможен с помощью команды expr. При этом нужно дополнительно указать опцию после двоеточия. Комбинация '. *' означает, что в кавычках может указываться любое количество произвольных символов.

$ VALUE=accounts.doc

$ expr $VALUE : October 8, '.*'

12

Команду expr можно также использовать при поиске совпадающих строк; ниже показано, как применяется шаблон ".doc" для извлечения оставшейся части имени файла.

$ expr $VALUE : '\(.*\).doc'

accounts

17.6. Заключение

В настоящей главе рассматриваются основные возможности команд test и expr. Показано, как проверяются права доступа к файлу, как тестируются строки. С помощью других условных операторов, типа "if then else" и "case", можно реализовать всестороннюю проверку. Подобный подход позволяет предпринимать определенные действия по результатам проверки.

ГЛАВА 18

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

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

   • коды завершения;

   • циклы while, for и until;

   • операторы if then else;

   • принятие решений в сценариях;

   • создание меню.

18.1. Коды завершения

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

$ echo $?

Существует четыре основных типа кодов завершения. Два из них уже упоминались, а именно: код завершения последней команды $? и команды, изменяющие ход выполнения сценария (&&,||). Две оставшиеся разновидности команд имеют отношение к завершению shell–сценария или интерпретатора shell, а также связаны с кодом завершения или кодами возврата функции. Эти коды рассматриваются в главе 19, посвященной изучению функций.

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

exit n

где n — числовое значение.

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

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

Код завершения 0 Успешное завершение, ошибок нет Код завершения 1 Неудачное завершение, имеется ошибка

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

Примечание:

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

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

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

#!/bin/sh

# создание каталога

mkdir /home/dave/mydocs

# копирование всех файлов с расширением doc

cp *.docs /home/dave/docs

# удаление всех файлов с расширением doc

rm *.docs

Рассматриваемый сценарий выполняет определенные задачи. Каковы же могут быть причины возможных неприятностей? Проблема возникнет, например, в том случае, если нельзя будет создать данный каталог. Как поступить в иной ситуации, если каталог может быть создан, но при копировании файлов появляется сообщение об ошибке? Что произойдет, если применить команду cp к разным файлам из различных каталогов. Продуманные решения нужно принимать до применения команды, а еще лучше, если они реализуются при получении результатов выполнения последней команды. Интерпретатор shell приходит здесь на помощь, поддерживая наборы командных операторов, которые помогают принять верное решение в зависимости от успеха или неудачи при выполнении команды либо при обработке списка. Существует два вида таких командных операторов:

   • операторы цикла;

   • операторы, изменяющие ход выполнения сценария.

18.2.1. Операторы, изменяющие ход выполнения сценария

Операторы if, then, else позволяют реализовать условное тестирование. Проверить условия можно самыми различными способами. Например, может производиться оценка размера файла, проверка установленных прав доступа к файлу, сравнение каких‑либо числовых значений или строк. В результате выполнения сравнений возвращается значение "истина" (0) либо "ложь" (1), и затем предпринимаются действия на основании полученного результата. Перед тем как мы приступим к обсуждению условного тестирования, стоит отметить, что некоторые понятия из этой области были рассмотрены ранее.

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

18.2.2. Циклические операторы

Цикл, или итерация, — это процесс повторного выполнения наборов команд. В распоряжении пользователя имеется три вида операторов цикла:


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

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

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

С этого момента все операторы echo в сценариях имеют отношение к Linux или UNIX BSD. То есть при выводе на экран используется метод "echo -e -n", при котором не выполняется создание новой строки в конце вывода. В главе 19 будет показано» как можно воспользоваться универсальной командой echo, которая выполняется для обеих разновидностей системы UNIX (System V и BSD).

18.3. Операторы if then else

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

if условие1

then

команды1

elif условие2

then

команды2

else

командыЗ

fi

Рассмотрим подробно, какие действия выполняются при вызове оператора if.


if условие1 если условие1 истинно
than тогда
команды1 выполняйте команды1
elif условие2 если условие1 ложно
then тогда
команды2 выполняйте команды2
else если условие1 или условие2 не выполняется
командыЗ тогда выполняйте командыЗ
fi конец

Оператор if обязательно завершается ключевым словом fi. Довольно распространенной ошибкой является пропуск слова f i при закрытии оператора if. Следует отметить, что подобную ошибку могут допускать даже опытные программисты.

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

А теперь рассмотрим несколько примеров.

18.3.1. Простые операторы if

Базовая структура оператора if выглядит следующим образом:

if условие

then команды

fi

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

if условие; then

команды fi

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

В следующем примере тестовый оператор используется для проверки того, меньше ли число "10" числа "12". Конечно, это условие истинно, и поэтому выполняются операторы, следующие за частью then; в данном случае, на экран просто выводится соответствующее утверждение. Если условие ложно, сценарий завершается, поскольку этот оператор не содержит части else.

$ pg iftest

#!/bin/sh

#iftest

#это строка комментария, все строки комментария начинаются символом # if [ "10" — lt "12" ]

then

# да, 10 меньше 12

echo "Yes, 10 ls less than 12"

fi

18.3.2. Проверка значений переменных

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

$ pg iftest2

#!/bin/sh

# если test2

echo -n "Enter your name :"

read NAME

# правильно ли пользователь ввел данные ????

if [ "$NAME"="" ] ; then

echo "You did not enter any information" fi

$ iftest2

Enter your name :

You did not enter any information

18.3.3. Проверка вывода команды grep

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

Чтобы выяснить, была ли выполнена команда grep, можно применить оператор if. В приведенном ниже примере команда grep используется для уточнения, содержится ли в файле data.file слово "Dave". Обратите внимание на то, что при поиске соответствия используется шаблон "Dave\>".

$ pg grepif

#!/bin/sh

# grepif

if grep 'Dave\>' data.file > /dev/null 2>&l

then

echo "Great Dave ls in the file" else

echo "No Dave ls not in the file"

fi

$ grepif

No Dave is not in the file

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

значение 0. В этом случае происходит (естественная интеграция с оператором if; если команда grep успешно завершилась, часть if принимает значение "истина".

18.3.4. Проверка вывода команды grep с помощью переменной

Как уже упоминалось, команду grep можно применять в строке. В следующем сценарии пользователь вводит список имен; затем команда grep ищет переменную, которой присвоено имя некого лица (Peter).

$ pg grepstr

#!/bin/sh

# grepstr

echo -n "Enter a list of names:"

read list

if echo $1ist | grep "Peter" > /dev/null 2>&1

then

echo "Peter ls here"

# можно ли выполнить обработку здесь

else

echo "Peter's not in the list. No comment!"

fi

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

$ grepstr

Enter a list of names:John Louise Peter James

Peter is here

18.3.5. Проверка результата копирования файла

А теперь осуществим проверку того, успешно ли прошло копирование файла. Если команда cp не скопировала файл myfile в файл myfile.bak, отображается сообщение об ошибке. Обратите внимание, что в сообщении об ошибке фигурирует команда `basename $0` которая выводит на экран название сценария.

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

$ pg ifcp

#!/bin/sh

# ifcp

if cp myfile myfile.bak; then

echo "good copy"

else

echo "`basename $0`: error could not copy the files" >&2 fi

$ ifcp

cp: myfile: No such file or directory

ifcp: error could not copy the files

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

отразиться на выводимых данных; сценарий уже отображает сообщения об ошибках, поэтому известно, что он функционирует неверно. Зачем же нам повторное уведомление? Чтобы избавиться от ошибок, генерируемых системой, и системных данных вывода, достаточно применить перенаправление стандартного потока ошибок и потока вывода. Для этого немного перепишем сценарий: > /dev/null 2>&1. В этом случае получим следующее:

$ pg ifcp

#!/bin/sh

# ifcp

if cp myfile myfile.bak >/dev/null 2>&1; then

echo "good copy"

else

echo "`basename $0`: error could not copy the files" >&2

fi

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

$ ifcp

ifcp: error could not copy the files

18.3.6. Проверка текущего каталога

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

$ pg ifpwd

#!/bin/sh

# ifpwd DIRECTORY=`pwd`

# захват текущего каталога

if [ "$DIRECTORY" != "/" ]; then

#это корневой каталог ?

#нет, перенаправление вывода в стандартный поток ошибок, который

#отображается на экране по умолчанию.

echo "You need to be in the root directory not $DIRECTORY to run this script" >&2

# выход из сценария со значением 1, ошибка

exit 1

fi

18.3.7. Проверка прав доступа к файлу

Вы можете также осуществлять контроль прав доступа к файлу. Ниже приводится несложная проверка на предмет того, можно ли вести записи в файле test.txt, который переприсвоен переменной logfile.

$ pg ifwr

#!/bin/sh

# ifwr

LOGFILE=test.txt echo $LOGFILE

if [ ! —w "$LOGFILE" ]; then

echo " You cannot write to $LOGFILE " >&2

fi

18.3.8. Проверка параметров, передаваемых сценарию

Оператор if может применяться при определении числа параметров, которые передаются сценарию. Чтобы проверить, соответствует ли количество необходимых параметров количеству вызываемых параметров, используется специальная переменная $#, содержащая число вызываемых параметров.

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

$ pg ifparam

#!/bin/sh

# ifparam

if [ $# -lt 3 ] ; then

#вызывается меньше, чем 3 параметра, на экран выводится сообщение, затем

#прерывается выполнение сценария

echo "Usage: `basename $0` arg1 arg2 arg3" >&2

exit 1

fi

# хорошо, получено 3 параметра, отображаются на экране

echo "arg1: $1"

echo "arg2: $2"

echo "arg3: $3"

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

$ ifparam cup medal

Usage:ifparam argl arg2 arg3

При передаче трех параметров происходит следующее:

$ ifparam cup medal trophy

argl: cup arg2: medal arg3: trophy

18.3.9. Определение интерактивного режима выполнения сценария

Иногда требуется выяснить, выполняется сценарий в интерактивном режиме (режим терминала( либо не в интерактивном режиме (команды cron или at). Такая информация необходима для того, чтобы сценарий мог определить, где можно получить вводимые данные и куда направлять выводимые данные. Чтобы уточнить режим выполнения сценария, достаточно воспользоваться командой test с опцией -t. Если возвращается значение "истина", сценарий выполняется в интерактивном режиме.

$ pg ifinteractive

#! /bin/sh

# ifinteractive

if [ -t ]; then

echo "We are interactive with a terminal"

else

echo "We must be running from some background process probably cron or at " fi

18.3.10. Простые операторы if else

Следующая форма оператора if применяется чаще всего:

if условие

then команды1

else

команды2

fi

Если условие не удовлетворяет тестированию, часть else оператора if позволяет перейти к соответствующей операции.

18.3.11. Проверка установок переменных

Ниже проверяется установка переменной среды editor. Если переменной EDITOR не присвоено значение, пользователь информируется о том, что переменная editor не установлена. Если переменная editor установлена, тип редактора отображается на экране:

$ рg ifeditor

#!/bin/sh

# ifeditor

if [ -z $EDITOR ]; then

#переменная не установлена

echo "Your EDITOR environment is not set"

else

#посмотрим, что же это

echo "Using $EDITOR as the default editor"

18.3.12. Проверка пользователя, выполняющего сценарий

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

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

Если строка "root" равна значению переменной LOGNAME, выполняется оператор, который находится после else.

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

$ pg ifroot

#!/bin/sh

# ifroot

if [ "$LOGNAME" != "root" ]

# если пользователь не является пользователем root

echo "You need to be root to run this script" >&2

exit 1

else

# да, это пользователь root

echo "Yes indeed you are $LOGNAME proceed"

fi

# выполнение операторов в обычном режиме

18.3.13. Передача параметров сценария системной команде

Позиционные параметры можно передать сценарию, а затем проверить значение переменной. Если при этом пользователь указывает после названия сценария наименование каталога, сценарий заново присваивает специальному параметру $1 более содержательное название, в данном случае directory. С помощью команды ls -A проверяется, не является ли каталог пустым. Если каталог пуст, эта команда не возвращает данные. Затем отображается соответствующее сообщение.

$ pg ifdirec

#!/bin/sh

#ifdirec

#присваивание $1 переменной DIRECTORY DIRECTORY=$1

if [ "`ls -A $DIRECTORY/`"="" ] ; then

# если строка пуста, каталог пуст

echo "$DIRECTORY is indeed empty" else

# в противном случае, нет

echo "$DIRECTORY” is not empty"

fi

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

$ pg ifdirec2

#!/bin/sh

# ifdirec2

DIRECTORY=$1

if [ -z "`ls -A $DIRECTORY`" ] then

echo "$DIRECTORY is indeed empty" else

echo "$DIRECTORY is not empty"

fi

18.3.14. Применение команды null

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

К сожалению, нельзя оставлять незаполненными части оператора if — здесь должен находиться какой‑либо оператор. Чтобы разрешить это затруднение, интерпретатор shell поддерживает команду null ':'. Команда null всегда возвращает значение "истина", что в данном случае нас удовлетворяет. Возвращаясь к предыдущему примеру, заметим, что если каталог пуст, команды можно размещать только в части then.

$ pg ifdirectory

#!/bin/sh

# ifdirectory

DIRECTORY=$1

if [ "`ls -A $DIRECTORY`"="" ]

then

echo "$DIRECTORY is indeed empty"

else : # не выполняет ничего

fi

18.3.15. Проверка на предмет создания каталога

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

if [ "$DIRECTORY" = "" ]

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

if [ $# -lt 1 ]

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

Пользователь получает запрос, действительно ли нужно создавать каталог. Если он вводит символ, отличный от Y или у, выполняется команда null, в результате чего не предпринимается никаких действий. Каталог создан.

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

$ pg ifmkdir

#!/bin/sh

#ifmkdir

#параметр передается как $1, но заново присваивается переменной DIRECTORY DIRECTORY=$1

#является ли строка пустой ??

if [ "$DIRECTORY"="" ]

then

echo "Usage: `basename $0` directory to create" >&2

exit 1

fi

if [ -d $DIRECTORY ] then :

# ничего не выполняет

else

echo "The directory does exist"

echo -n "Create it now? [y..n] :"

read ANS

if [ "$ANS"="y" ] || [ "$ANS"="Y" ]

then

echo "creating now"

# создайте каталог и перешлите все данные вывода в /dev/null mkdir $DIRECTORY >/dev/null 2>&1 if [ $? != 0 ]; then

echo "Errors creating the directory $DIRECTORY" >&2

exit 1

fi

else :

# ничего не выполняет

fi

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

$ ifmkdir dt

The directory does exist Create it now? [y..n]: у

creating now

18.3.16. Другие возможности копирования

С помощью команды cp сценарию передается два параметра (они должны содержать имена файлов). Затем системная команда cp копирует значение параметра $1 в параметр $2, а поток вывода перенаправляется в /dev/null. Если команда выполнилась успешно, никаких действий не предпринимается, т. е. применяется команда null.

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

$ pg ifcp2

#!/bin/sh

#ifcp2

if cp $1 $2 > /dev/null 2>&1

# успешно, ничего делать не надо

then :

else

# плохо, покажем пользователю, какие файлы здесь были.

echo "`'basename $0`: ERROR failed to copy $1 to $2"

exit 1

fi

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

$ ifср2 myfile.lex myfile.lex.bak

Сценарий выполняется при наличии ошибок в команде ср:

$ ifcp2 myfile.lexx myfile.lex.bak

ifcp2: ERROR failed to copy myfile.lexx myfile.lex.bak

В следующем примере для сортировки файла под именем accounts.qtr применяется команда sort.

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

$ pg ifsort

#!/bin/sh

# ifsort

if sort accounts.qtr > /dev/null

# отсортировано. Прекрасно

then :

else

# лучше сообщим об этом пользователю

echo "`basename $0`: Oops..errors could not sort accounts.qtr"

18.3.17. Применение нескольких операторов if

Операторы if можно вкладывать; при этом нужно следить, чтобы каждому ключевому слову if соответствовало слово fi.

18.3.18. Проверка и установка переменных среды

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

$ pg ifseted

#!/bin/sh

#ifseted

#установлена ли переменная EDITOR ?

if [ -z $EDITOR ] ; then

echo "Your EDITOR environment ls not set"

echo "2 will assume you want to use vi..OK"

echo -n "Do you wish to change it now? [y..n] :"

read ANS

# проверка верхнего или нижнего регистра для 'у'

if [ "$ANS"="у" ] || [ "$ANS"="Y" ]; then

echo "enter your editor type :"

read EDITOR

if [ -z $EDITOR ] || [ "$EDITOR"="" ]; then

#если переменная EDITOR не установлена, ей не присвоено значение,

#тогда присвоим ей значение vi

echo "No, editor entered, using vi as default"

EDITOR=vi; export EDITOR

fi

# берется значение и присваивается переменной EDITOR

EDITOR=$EDITOR

export EDITOR

echo "setting $EDITOR"

fi

else

# пользователь

echo "Using vi as the default editor"

EDITOR=vi; export EDITOR

fi

Рассмотрим, как работает приведенный сценарий. Сначала проверим, установлена ли эта переменная. Если это так, появляется сообщение, что редактор vi применяется как редактор, заданный по умолчанию. Затем vi устанавливается в качестве редактора, и сценарий завершается.

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

На данном этапе пользователю предлагается ввести тип редактора. Затем выполняется проверка, не установлен ли данный редактор, а также уточняется, не нажимал ли пользователь во время проверки $editor ="" клавишу [Return]. Действительно эта проверка реализована лучше, чем -z $editor, но оба этих метода приводятся лишь в качестве иллюстрации. Если результаты проверки будут отрицательны, на экран выводится сообщение, что применяется редактор vi, причем значение vi присваивается переменной editor.

Если пользователь вводит имя для переменной editor, происходит присвоение и экспорт этого имени.

18.3.19. Проверка кода завершения последней команды

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

$ pg ifmkd±r2

#!/bin/sh

#ifmkdir2

DIR_NAME=testdirec

#где мы находимся?

THERE=`pwd`

# перенаправление потока вывода в системную корзину

mkdir $DIR_NAME > /dev/null 2>&1

# каталог ли это ?

if [ -d $DIR_NAME ] ; then

# можно ли применить к каталогу команду cd

cd $DIR_NAME

if [ S? = 0 ]; then

# да, можно

HERE=`pwd`

cp $THERE/*.txt $HERE

else

echo "Cannot cd to $DIR_NAME" >&2

exit 1

fi

else

echo "cannot create directory $DIR_NAME" >&2

exit 1

fi

18.3.20. Добавление и проверка целых значений

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

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

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

$ pg ifcounter

#!/bin/sh

# ifcounter

COUNTER=100

echo "Do you wish to change the counter value currently set at $COUNTER ? [y...n] :"

read ANS

if [ "$ANS"="y" ] || [ "$ANS"="Y" ]; then

# да, пользователь желает изменить значение

echo "Enter a sensible value "

read VALUE

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

#добавим к VALUE любое число, проверим код возврата

expr $VALUE + 10 > /dev/null 2>&1

STATUS=$?

# проверим код возврата для expr

if [ "$VALUE"="" ] || [ "$STATUS" != "0" ]; then

# направим ошибки в стандартный поток ошибок

echo " You either entered nothing or a non‑numeric " >&2

echo " Sorry now exiting..counter stays at. $COUNTER" >&2 exit 1 fi

# если мы здесь, значит, это — число, добавим его к COUNTER

COUNTER=`expr $COUNTER + $VALUE`

echo " Counter now set to $COUNTER" else

#если мы здесь, значит, пользователь вместо того, чтобы ввести число,

#нажал клавишу ввода

#или ответим n для изменения значения приглашения

echo " Counter stays at $COUNTER"

fi

Рассмотрим результаты выполнения приведенного сценария.

$ ifcount

Do you wish to change the counter value currently set at 100? [y..n]:n Counter stays at 100

$ ifcount

Do you wish to change the counter value currently set at 100? [y..n]:y

Enter a sensible value: fdg

You either entered nothing or a non‑numeric

Sorry now exiting..counter stays at 100

$ ifcount

Do you wish to change the counter value currently set at 100? [y..n]:y Enter a sensible value: 250

Counter now set to 350

18.3.21. Простой сценарий, обеспечивающий безопасность при регистрации

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

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

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

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

Если вводится корректный ID пользователя и пароль (паролем является mayday), переменные invalid_user и invalid_passwd для недействительного пользователя или пароля имеют значение "по". Затем производится тестирование, и если какая‑либо из переменных принимает значение yes, сценарий для этого пользователя завершается по умолчанию.

К работе допускаются пользователи с действительными ID и паролями. В регистрационном сценарии удобно применять описанное структурное дополнение. В данном примере действительными ID пользователя служат dave или pauline.

$ pg ifpass

#!/bin/sh

#ifpass

#установим значения переменных в "ложь" INVALID_USER=yes

INVALID_PASSWD=yes

# сохранение текущих установок команды stty

SAVEDSTTY=`stty -g`

echo "You are logging into a sensitive area"

echo -n "Enter your ID name :"

read NAME

# скройте символы, введенные в терминале

stty -echo

echo "Enter your password :"

read PASSWORD

# попробуем снова

stty $SAVEDSTTY

if [ "$NAME"="dave" ] || [ "$NAME"="pauline" ]; then

# если действительно, установите переменную

INVALID_USER=no

fi

if [ "$PASSWORD"="mayday" ]; then

# если пароль действителен, установите переменную

INVALID_PASSWD=no

fi

if [ "$INVALIDUSER"="yes" -o "$INVALID_PASSWD"="yes" ]; then

echo " `basename $0 :` Sorry wrong password or userid"

exit 1

fi

# если вы здесь, ваш ID и пароль в порядке.

echo "correct user id and password given"

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

$ ifpass

You are logging into a sensitive area

Enter your ID name : dave

Enter your password :

ifpass :Sorry wrong password or userid

Введем верное имя регистрационное имя и пароль.

$ ifpass

You are logging into a sensitive area

Enter your ID name : dave

Enter your password :

correct user id and password given

18.3.22. Применение elif

Часть elif оператора if then else применяется для проверки при наличии более чем двух условий,

18.3.23. Несколько проверок, реализуемых с помощью elif

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

Сначала в сценарии проверяется, действительно ли пользователь ввел имя; если имя не введено, то проверка не выполняется. Если имя введено, с помощью части elif проверяется, совпадает ли имя с root, louise или dave. В случае несовпадения имени ни с одним из перечисленных имен на экран выводится сообщение, что пользователь не является пользователем root, louise или dave.

$ pg ifelif

#!/bin/sh

# ifelif

echo -n "enter your login name :"

read NAME

# имя не введено, рассмотрение прекращается

if [ -z $NAME ] || [ "$NAME"="" ]; then

echo "You did not enter a name"

elif

# является ли именем root

[ "$NAME"="root" ]; then echo "Hello root"

elif

# именем является louise

[ $NAME="louise" ]; then echo "Hello louise"

elif

# именем является dave

[ "$NAME"="dave" ]; then echo "Hello dave"

else

# нет, это какое‑то другое имя

echo "You are not root or louise or dave but hi $NAME"

fi


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

$ ifelif

enter your login name : dave Hello dave

$ ifelif

enter your login name : You did not enter a name

$ ifelif2

enter your login name : peter

You are not root or louise or dave but hi peter

18.3.24. Проверка нескольких вариантов размещения файла

Предположим, что к файлу проверки регистрации требуется применить команду cat. Файл в зависимости от того, кто из пользователей выполнял инсталляцию. находится либо в каталоге /usr/opts/audit/logs, либо в каталоге /usr/Iocal/audit/logs. Перед применением к файлу команды cat следует убедиться в том, что его можно просматривать; именно это и будет уточняться при проверке. Если файл нельзя найти или же его нельзя просматривать, на экран выводится сообщение об ошибке. Ниже приводится соответствующий сценарий:

$ pg ifcataudit

#!/bin/sh

#ifcataudit

#размещение файла регистрации

LOCAT_1=/usr/opts/audit/logs/audit.log

LOCAT_2=/usr/local/audit/audit, logs

if [ -r $LOCAT_1]; then

#если файл находится в этом каталоге и может просматриваться,

#применим к нему команду cat echo "Using L0CAT_1"

cat $LOCAT_1

elif

# иначе, файл должен находиться в этом каталоге и можно его просматривать

[ -r §L0CAT_2 ]

then

echo "Using LOCAT_2"

cat $LOCAT_2

else

# нет ни в одном каталоге…

echo `basename $0`: Sorry the audit file ls not readable or cannot be located." >S2

exit 1

fi

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

$ ifcataudit

ifcataudit: Sorry the audit file ls not readable or cannot be located.

18.4. Оператор case

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

case значение in шаблон1)

команды1

;;

шаблон2)

команды2

;;

esac

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

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

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

* Произвольные символы

? Произвольный отдельный символ

[. . ] Произвольный символ из класса или диапазона

А теперь рассмотрим несколько примеров.

18.4.1. Простой оператор case

Следующий сценарий отображает приглашение для ввода чисел от 1 до 5. Число передается оператору case, переменной ans присваивается значение ans оператора case, и значение ANS сравнивается с каждым шаблоном.

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

Далее выполняются операции, находящиеся после оператора case.

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

$ pg caseselect

#!/bin/sh

# caseselect

echo -n "enter a number from 1 to 5 :"

read ANS

case $ANS in

1) echo "you select 1"

;;

2) echo "you select 2"

;;

3) echo "you select 3"

;;

4) echo "you select 4"

;;

5) echo "you select 5"

;;

*) echo "`basename $0`: This is not between 1 and 5" >&2

;;

exit 1

esac

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

$ caseselect

enter a number from 1 to 5 : 4 you select 4

С помощью шаблона * выполним прием информации, с которой не установлено соответствия:

$ caseselect

enter a number from 1 to 5 :pen

caseselect: This ls not between 1 and 5

18.4.2. Применение символа | при поиске по шаблону

При использовании оператора case в качестве команды or можно указывать символ. Например, vt100|vt102) соответствует шаблону vt100 или vt102.

В следующем примере у пользователя запрашивают тип терминала. Если пользователь вводит vt100 или vt102, выполняется сравнение с шаблоном "vtl00| vtl02)". В данном случае переменной term присваивается значение vt100. Если пользователь

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

$ pg caseterm

#!/bin/sh

# caseterm

echo " choices are.. vt100, vtl02, vt220"

echo -n "enter your terminal type :"

read TERMINAL

case $TERMINAL in

vtl00|vt102) TERM=vt100

;;

vt220) TERM=vt220

;;

*) echo "`basename $0`: Unknown response" >&2

;;

echo "setting it to vt100 anyway, so there"

TERM=vt100

esac

export TERM

echo "Your terminal ls set to $TERM"

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

$ caseterm

choices are.. vt100, vtl02, vt220 enter your terminal type :vt900

caseterm: Unknown response setting it to vt100 anyway, so there

Your terminal ls set to vt100

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

$ case2

choices are.. vt100, vtl02, vt220 enter your terminal type :vt220

Your terminal ls set to vt220

В любом случае пользователь устанавливает тип терминала.

18.4.3. Приглашение для ввода y или n

Оператор case удобно применять при отображении запроса на продолжение обработки. Ниже приводится сценарий, в котором пользователю предлагается указать в качестве ответа либо 'у', что означает продолжение обработки, либо 'n' — выход из сценария. Если пользователь введет у, Y, yes или Yes, обработка будет продолжена в соответствии с частью сценария после оператора case. Если пользователь введет n, N, no или какой‑либо другой ответ, работа со сценарием для него завершается.

$ pg caseans

#!/bin/sh

# caseans

echo -n "Do you wish to proceed [y..n] :"

read ANS

case $ANS in

y|Y|yes|Yes) echo "yes is selected"

;;

n|N) echo "no is selected"

exit 0 # нет ошибки, поэтому для выхода укажите 0

;;

*) echo "`basename S0`" : Unknown response" >&2

exit 1

;;

esac

# если мы оказались здесь, были выбраны значения y|Y|yes|Yes.

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

$ caseans

Dо you wish to proceed [y..n] :df

caseans : Unknown response

В случае корректного ответа:.

$ caseans

Dо you wish to proceed [y..n] :y

yes is selected

18.4.4. Оператор case и передача командных параметров

Можно также использовать оператор case при передаче параметров в сценарии.

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

Затем оператор case выполняет прием следующих параметров: passwd, start, stop или help. В дальнейшем код реализуется для каждого совпадения с этими шаблонами. При передаче неизвестного значения на экран выводится стандартное сообщение об ошибке.

$ pg caseparam

#!/bin/sh

# caseparam

if [ $# != 1 ]; then

echo "Usage:`basename $0` [start | stop | help] >&2

exit 1

fi;

# присвойте переменной ОРТ параметр

OPT=$1

case $OPT in

start) echo "starting… `basename $0`"

# здесь коды для начала процесса

;;

stop) echo "stopping..`basenarae $0`"

# здесь колы для прекращения процесса

;;

help)

# здесь находится код для отображения справочной страницы

;;

*) echo "Usage: `basename $0` [start | stop | help]"

;;

esac

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

$ caseparam what

Usage:caseparam [start|stop | help]

В случае передачи действительного параметра:

$ caseparam stop

stopping..caseparam

18.4.5. Прием потока ввода без применения шаблонных команд

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

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

В следующем сценарии указано, что если пользователь вводит номер отдела, который не совпадает со значением 234, 453, 655 или 454, то пользователь выйдет из сценария. Если номер отдела указан правильно, аналогичный подход применяется при определении типа отчета. По окончании выполнения оператора case остаются действительный номер отдела и правильный тип отчета. Ниже приводится соответствующий сценарий.

$ pg casevalid

#!/bin/sh

# casevalid

TYPE=""

echo -n "enter the account dept No: :"

read ACC

case $ACC in

234);;

453);;

655);;

454) ;;

*) echo "`basename $0`: Unknown dept No:" >&2

echo "try..234,453,655,454" exit, 1

esac

# если оказались здесь, получен верный номер отдела

echo " 1. post"

echo " 2. prior"

echo -n "enter the type of report: "

read ACC_TYPE

case $ACC_TYPE in

l)TYPE=post;;

2}TYPE=prior;;

*) echo "`basename $0`: Unknown account type." >&2

exit 1

;;

esac

# если оказались здесь, значит все указано правильно!

echo "now running report for dept $ACC for the type $TYPE"

# выполняем отчет о команде.

Если вводимые данные достоверны, получим:

$ casevalid

enter the account dept No: :234

   1. . post

   2. . prior

enter the type of report:2

now running report for dept 234 for the type prior

Если номер отдела введен неверно, получим:

$ casevalid

enter the account dept No: :432

casevalid: Unknown dept No: try..234,453,655,454

При вводе неправильного типа отчета, получим:

$ casevalid

enter the account dept No: :655

   1. . post

   2. . prior

enter the type of report:4

casevalid: Unknown account type.

18.4.6. Значения переменных, заданные по умолчанию

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

В следующем сценарии для выполнения отчета пользователю предлагают ввести название дня недели. Если пользователь нажимает клавишу [Return], используется день недели, заданный по умолчанию, а именно "Saturday". Это название и присваивается переменной when.

Если пользователь вводит название другого дня, с помощью оператора case выполняется проверка, совпадает ли введенное название с названием одного из дней недели, предназначенных для выполнения сценария, а именно "Saturday", "Sunday"

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

Ниже приводится соответствующий сценарий.

$ pg caserep

#!/bin/sh

# caserep

echo " Weekly Report"

echo -n "What day do you want to run report [Saturday] :"

#если нажать клавишу ввода, принимается заданное по умолчанию название Saturday

read WHEN

echo "validating..

${WHEN:="Saturday"}"

case $WHEN in Monday|MONDAY|mon);;

Sunday|SUNDAY|sun);;

Saturday|SATURDAY}sat);;

*) echo " Are you nuts!, this report can only be run on " >&2

echo " on a Saturday, Sunday or Monday" >&2

exit 1

esac

echo "Report to run on $WHEN"

# здесь команда для выполнения действительного отчета

При корректных начальных данных получим:

$ caserep

Weekly Report What day do you want to run report [Saturday] : validating..Saturday Report to run on Saturday

Если начальные данные были неправильны, получим:

$ caserep

Weekly Report What day do you want to run report [Saturday] :Tuesday validating..Tuesday

Are you nuts! this report can only be run on a Saturday, Sunday or Monday

Можно заключить, что оператор case функционирует так же, как и несколько операторов if then else. Такой вывод вполне правомерен.

18.5. Цикл for

Общий формат цикла:

for имя_перемениой in list

do

команда1 команда…

done

Цикл for однократно обрабатывает всю информацию для каждого значения, включенного в список list. Чтобы получить доступ к каждому значению в списке, достаточно задать параметр имя_переменной. Командой служит любая действительная команда или оператор интерпретатора shell. В качестве параметра имя_переменной можно указать любое слово.

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

Опция in list может содержать подстановки, строки и имена файлов. Рассмотрим несколько примеров.

18.5.1. Простой цикл for

Этот цикл просто выводит на экран список, который состоит из " 1 2 3 4 5". Чтобы получить доступ к каждой переменной, в качестве параметра имя_переменной указывается "loop".

$ pg for_i

#!/bin/sh

# for_i

for loop in 1 2 3 4 5

do

echo $LOOP done

Приведенный выше сценарий выводит следующие данные:

$ for_i

1

2

3

4

5

18.5.2. Вывод на экран строки списка

Ниже приводится цикл for, список которого содержит строку значений "orange red blue grey". Для каждой переменной указана команда echo, в качестве параметра имя_переменной указывается loop. Команда echo с помощью части $LOOP выводит на экран каждое значение списка до тех пор, пока список не окажется пустым.

$ pg forlist

#!/bin/sh

# forlist

for LOOP in orange red blue grey

do

echo $LOOP

done

$ forlist

orange

red

blue

grey

Также с помощью цикла имя_переменной можно комбинировать строки (в данном случае речь идет о цикле loop).

echo "this ls the fruit $LOOP"

Результат:

This ls the fruit orange red blue grey

18.5.3. Использование команды ls совместно с циклом for

Этот цикл оценивает команду ls интерпретатора shell и отображает сведения о файлах текущего каталога.

$ pg forls

#!/bin/sh

# forls

for loop in `ls`

do

echo $LOOP

done

$ forls

array

arrows

center

center1

center2

centerb

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

Если в цикле for опустить часть in list, позиционные параметры командной строки становятся аргументами. Действительно, этот подход аналогичен следующему:

for params in "$@"

или

for params in "$*"

Ниже приводится пример, который показывает, как можно избежать применения конструкции in list. Цикл for обращается к специальному параметру $@ или $* для получения аргументов из командной строки.

$ pg forparam2

#/bin/sh

# forparam2

for params in $*

do

echo "You supplied $params as a command line option"

done

echo $params done

$ forparam2 myfile1 myfile2 myfile3

You supplied myfile1 as a command line option

You supplied myfile2 as a command line option

You supplied myfile3 as a command line option

Следующий сценарий содержит часть in "$@" и образует тот же самый поток вывода, что и предыдущий сценарий.

$ pg forparam3

#!/bin/sh

# forparam3

for params in $@

do

echo "You supplied $params as a command line option"

done

echo $params done

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

$ pg forfind

#!/bin/sh

# forfind

for LOOP in $@

do

find / -name $LOOP -print

done

Значения передаются с помощью параметра командной строки и образуют часть -name команды find .

$ forfind passwd LPSO.AKSOP

/etc/passwd

/etс/pam.d/passwd

/etc/uucp/passwd

/usr/bin/passwd

/usr/local/accounts/LPSO.AKSOP

18.5.5. Посылка сигналов серверам с помощью цикла for

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

$ pg forping

#!/bin/sh

# forping

HOSTS=”1itserv dnssevr acctsmain ladpd ladware"

for loop in $HOSTS

do

ping -c 2 $LOOP

done

18.5.6. Создание резервных копий файлов с помощью цикла for

Цикл for можно использовать для создания резервных копий файлов. При этом переменная просто добавляется к целевому аргументу команды ср. Ниже применяется переменная под названием ВАК. Эта переменная добавляется к каждому имени целевого файла при использовании цикла с помощью команды ср. Список включает shell–команду ls.

$ pg forcp

#!/bin/sh

# forcp

BAK=".bak"

for loop in `ls`

do

echo "copying $LOOP to $LOOP$BAK"

cp $LOOP $LOOP$BAK done

$ forcp

copying array to array.bak

copying arrows to arrows.bak

copying center to center.bak

copying center1 to center1.bak

18.5.7. Массовое преобразование

Чтобы найти все файлы, которые начинаются символами "LPSO", и преобразовать их содержимое в символы верхнего регистра, используются команды ls и cat. Команда ls отображает список файлов, а команда cat применяется для передачи списка команде tr. Передаваемые файлы получают расширение UC. Обратите внимание, что при использовании в цикле for команды ls применяются обратные кавычки.

$ pg forUC

#!/bin/sh

# forUC

for files in Us LPSO*'

do

cat $files | tr "[a‑z]" "[A‑Z]" > $files.UC

done

18.5.8. Удаления, выполняемые с помощью редактора sed

В следующем примере для удаления всех пустых строк применяется потоковый редактор sed. Выходной поток данных направляется в новые файлы с расширением .HOLD. Затем команда mv возвращает файлам их исходные имена.

$ pg forsed

#!/bin/sh

# forsed

for files in `ls LPSO*` do

sed -e "/^$/d" $files > $files. HOLD

mv $flies. HOLD $files

done

18.5.9. Подсчет с помощью циклов

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

$ pg forcount

#!/bin/sh # forcount counter=0

for files in *

do

# increment

counter=`expr $COUNTER + 1`

done

echo "There are $COUNTER files in `pwd` we need to process"

$ forcount

There are 45 files in /apps/local we need to process

Аналогичный результат можно получить с помощью команды wc.

$ ls |wc -l

45

18.5.10. Циклы for для обработки документов

С циклом for можно комбинировать любые команды. В приведенном примере переменная содержит имена всех зарегистрированных пользователей. Для реализации этой конструкции обращаются к команде who и утилите awk. Затем цикл for обрабатывает имена этих пользователей и каждому высылает электронное сообщение. При отправке сообщения используется конструкция "документ здесь".

$ pg formallit

#!/bin/sh

# formallit

WHOS_ON=`who -u | awk '{print $1}'`

for user in $WHOS_ON

do

mail $user << MAYDAY

Dear Colleagues,

It's my birthday today, see you down the

club at 17:30 for a drink.

See ya.

$LOGNAME

MAYDAY

done

Ниже приводится электронное сообщение для данного сценария.

$ pg mbox

Dear Colleagues,

It's my birthday today, see you down the

club at 17:30 for a drink.

See ya. dave

18.5.11. Вложенные циклы for

Чтобы вложить, циклы, достаточно цикл for поместить в другой цикл:

for имя_переменной in list

do

for имя_переменной2 in list2

do

команда1

done

done

В следующем сценарии представлен вложенный цикл for. Имеются два списка, APPS и SCRIPTS, первый из которых содержит путь к приложениям на сервере, а второй включает административные сценарии, которые выполняются для каждого приложения. Для каждого приложения из списка apps имеется соответствующее название сценария в списке SCRIPTS. Обычно сценарии выполняются в фоновом режиме (с помощью указания префикса &). Данный сценарий посредством команды tee размещает также запись в журнальном файле, поэтому наряду с файлом на экране отображается поток выходных данных. Обратите внимание на выходные данные, чтобы понять, как цикл for использует список SCRIPTS при обработке элементов списка apps.

$ pg audit_run

#!/bin/sh

# audit_run

APPS="/apps/accts /apps/claims /apps/stock /apps/serv"

SCRIPTS="audit.check report.run cleanup"

LOGFILE=audit.log

MY_DATE=`date +%H:%M" on "%d/%m%Y`

# внешний цикл

for loop in $APPS

do

# внутренний цикл

for 1оор2 in $SCRIPTS

do

echo "system $LOOP now running $LOOP2 at $MY_DATE" | tee -a $LOGFILE

$LOOP $LOOP2 &

done

done

$ audit_run

system /apps/accts now running audit.check at 20:33 on 23/051999

system /apps/accts now running report.run at 20:33 on 23/051999

system /apps/accts now running cleanup at 20:33 on 23/051999

system /apps/claims now running audit.check at 20:33 on 23/051999

system /apps/claims now running report.run at 20:33 on 23/051999

system /apps/claims now running cleanup at 20:34 on 23/051999

system /apps/stock now running audit.check at 20:34 on 23/051999

system /apps/stock now running report.run at 20:34 on 23/051999

system /apps/stock now running cleanup at 20:34 on 23/051999

system /apps/serv now running audit.check at 20:34 on 23/051999

system /apps/serv now running report.run at 20:34 on 23/051999

system /apps/serv now running cleanup at 20:34 on 23/051999

18.6. Цикл until

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

until условие

команда1

done

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

18.6.1. Простой цикл until

Этот сценарий непрерывно ищет пользователя "root" и выполняет команду who. Результат выполнения команды grep содержится в переменной IS_ROOT.

Если пользователь "root" обнаружен, цикл завершает обработку. Затем пользователю simon направляется электронное сообщение, в котором отмечается, что пользователь "root" зарегистрирован. Обратите внимание на применение команды sleep, которая довольно часто используется в циклах until. Эта команда дает возможность приостановить выполнение цикла на несколько секунд, что позволяет указать дополнительные команды.

$ pg until_who

#!/bin/sh

# until_who

ls_ROOT=`who | grep root`

until [ "$IS_ROOT" ]

do

sleep 5

done

echo "Watch it. roots in " | mail simon

18.6.2. Контроль наличия файла

В этом примере в цикле until выполняется команда "sleep 1" до тех пор, пока не будет удален файл под именем /tmp/monitor. LCK. После удаления файла сценарий продолжает выполняться в обычном режиме.

$ pg until_lck

#!/bin/sh

# until_lck

LOCK_FILE=/trap/process. LCK

until [ ! — f $1OCK_FILE ]

do

sleep 1 done echo "file deleted "

# обычная обработка, файл имеется

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

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

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

Чтобы устранить эти затруднения, сценарий process_main создает файл lck при запуске и удаляет его при завершении работы.

Рассматриваемый сценарий ожидает удаления файла LCK. После того как файл lck удален, сценарий может обрабатывать содержимое файла отчета.

18.6.3. Мониторинг дисковой памяти

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

В следующем сценарии просматривается система файлов /logs. С помощью утилиты awk и команды grep информация постоянно извлекается из переменной $LOOK_out. Значения этой переменной характеризуют объем, занимаемый системой файлов /logs.

Если занятый объем превышает 90%, опция command запускает триггер, пользователю "root" направляется сообщение и сценарий завершает работу. Сценарий следует завершить, поскольку в противном случае, если условие истинно (например, занятый объем по–прежнему превышает 90%(, электронное сообщение пользователю "root" будет высылаться непрерывно.

$ pg until_mon

#!/bin/sh

#until_ mon

# получите столбец со значениями процентов и удалите заголовок строки из df

LOOK_OUT=`df | grep /logs | awk '{print $5}' | sed 's/%//g'`

echo $LOOK_OUT

until [ "$LOOK_OUT" -gt "90" ]

do

echo "Filesystem..logs ls nearly full" | mail root

done

exit 0

18.7. Цикл while

Цикл while выполняет ряд команд до тех пор, пока истинно условие. Этот цикл используется также для просмотра данных из файла ввода. Формат цикла while:

while команда

do

команды1

команды2

done

Между конструкциями while и do находится несколько команд, хотя в общем случае применяется только одна команда. Обычно команда выполняет проверку условия.

Команды, размещенные между ключевыми словами do и done, выполняются только в том случае, если код завершения command равен нулю; если код завершения принимает какое‑либо другое значение, цикл заканчивается.

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

18.7.1. Простой цикл while

Ниже приводится основная форма цикла while. Условие тестирования состоит в том, что если "COUNTER is less than 5", условие останется истинным. Переменная counter имеет начальное значение нуль, и ее значение увеличивается на постоянную величину при выполнении цикла.

$ pg whilecount

#!/bin/sh # whilecount COUNTER=0

#счетчик равен 5?

while [ $COUNTER -lt 5 ] do

#прибавление к счетчику единицы

COUNTER=`expr $COUNTER + 1`

echo $COUNTER

done

Указанный сценарий выводит на экран числа от 1 до 5, затем завершает работу.

$ whilecount

1 2 3

4

5

18.7.2. Применение цикла while при вводе с клавиатуры

Цикл while может применяться для ввода информации с клавиатуры. В следующем примере введенная информация присваивается переменной film. Если нажать клавиши [Ctrl+D], цикл завершает выполнение.

$ pg whileread

#!/bin/sh

# whileread

echo " type <CTRL‑D> to terminate"

echo -n "enter your most liked film :"

while read FILM

do

echo "Yeah, great film the $FILM"

done

Когда сценарий выполняется, вводимыми данными могут быть:

$ whileread

enter your most liked film: Sound of Music

Yeah, great film the Sound of Music

<CTRL‑D>

18.7.3. Применения цикла while для считывания данных из файлов

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

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

$ pg names.txt

Louise Conrad:Accounts:ACC8987

Peter James:Payroll:PR489

Fred Terms:Customer:CUS012

James Lenod:Accounts:ACC887

Frank Pavely:Payroll:PR489

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

$ pg whileread

#!/bin/sh

# whileread

while read LINE

do

echo $LINE

done < names.txt

$ whileread

Louise Conrad:Accounts:ACC8987

Peter James:Payroll:PR489

Fred Terras:Customer:CUS012

James Lenod:Accounts:ACC887

Frank Pavelу:Payroll:PR4 8 9

18.7.4. Считывание данных из файлов с помощью IFS

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

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

$ pg whilereadifs

#!/bin/sh

#whilereadifs

#сохраните установку IFS

SAVEDIFS=$IFS

#присвоим переменной IFS новый разделитель

IFS=:

while read NAME DEPT ID

do

echo -e "$NAME\t $DEPT\t $ID" done < names.txt

# восстановим установки переменнойIFS

IFS=$SAVEDIFS

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


$ whilereadifs

Louise Conrad Accounts ACC8987

Peter James Payroll PR489

Fred Terms Customer CUS012

James Lenod Accounts ACC887

Frank Pavely Payroll PR489

18.7.5. Обработка файла с помощью проверок условий

Большинство циклов while включает некоторый оператор проверки, который уточняет последовательность действий.

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

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

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

$ pg whileread_file

#!/bin/sh

#whileread_file

#инициализация переменных

SAVEDIFS=$IFS

IFS=:

HOLD_FILE=hold_file

NAME_MATCH="James Lenod"

INPUT_FILE=names.txt

# создавайте каждый раз новый HOLD_FILE, в случае, когда сценарий

непрерывно выполняется

>$HOLD FILE

while read NAME DEPT ID

do

#выводит на экран всю информацию в holdfile с помощью перенаправления

echo $NAME $DEPT $ID >>$HOLD_FILE

#имеется ли соответствие ???

if [ "$NAME"="$NAME_MATCH" ]; then

# да, тогда удобно завершить работу

echo "all entries up to and including $NAME_MATCH are in $HOLD_FILE"

exit 0

fi

done < $INPUT_FILE

# восстановление IFS

IFS=$SAVEDIFS

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

$ pg whileread_cond

#!/bin/sh

# whileread_cond

# инициализация переменных ACC_LOOP=0; CUS_LOOP=0; PAY_LOOP=0;

SAVEDIFS=$IFS

IFS=:

while read NAME DEPT ID

do

# счетчик увеличивается на единицу для каждого совпадающего названия отдела.

case $DEPT in

Accounts)

ACC_LOOP=`expr $ACC_LOOP + 1`

ACC="Accounts"

;;

Customer)

CUS_LOOP=`expr SCUS_LOOP + 1`

CUS="Customer"

;;

Payroll)

PAY_LOOP=`expr $PAY_LOOP + 1`

PAY="Pay roll"

;;

*) echo "`basename $0`: Unknown department $DEPT" >&2

;;

esac

done < names.txt

IFS=$SAVEDIFS

echo "there are $ACC_ LOOP employees assigned to $ACC dept"

echo "there are $CUS_LOOP employees assigned to $CUS dept"

echo "there are $PAY_LOOP employees assigned to $PAY dept"

При выполнении сценария получим следующий вывод:

$ whileread_cond

there are 2 employees assigned to Accounts dept

there are 1 employees assigned to Customer dept

there are 2 employees assigned to Payroll dept

18.7.6. Выполнение суммирования

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

$ pg total.txt


STAT 3444
GIFT 233
GIFT 252
GIFT 932
STAT 212
STAT 923
GIFT 129

Задача состоит в подсчете общей суммы всех записей отдела gift. Чтобы сохранить общие значения сумм, применим оператор expr. Как показано в следующем операторе expr, переменным loop и total первоначально вне цикла присваивается значение ноль. Когда сценарий выполняет цикл, значение переменной items добавляется к значению переменной total. В первую итерацию цикла входит только первый пункт, но в дальнейшем к накапливающимся значениям переменной total добавляются значения переменной items.

Следующий оператор expr увеличивает значение счетчика.

LOOP=0

TOTAL=0

while…

TOTAL=`expr $TOTAL + $ITEMS`

ITEMS=`expr $ITEMS + 1`

done

Очень распространенной является такая ошибка: при работе с оператором expr забывают сначала инициализировать переменную.

LOOP=0

TOTAL=0

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

TOTAL=`expr ${TOTAL:=0} + ${ITEMS}`

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

Рассмотрим следующий сценарий.

$ pg total

#!/bin/sh

#общая сумма

#инициализация переменных

LOOP=0

TOTAL=0

COUNT=0

echo "items Dept"

echo " "

while read DEPT ITEMS do

# сохраните результаты подсчета при просмотре общих записей

COUNT=`expr $COUNT + 1`

if [ "$DEPT"="GIFT" ]; then

# сохраните выполнение суммирования TOTAL=`expr $TOTAL + $ITEMS` ITEMS=`expr $ITEMS + 1`

echo -e "$ITEMS\t$DEPT"

fi

#echo $DEPT $ITEMS done < total.txt

echo $TOTAL

echo "There were $COUNT entries altogether in the file"

При выполнении сценария получим:

$ total

Items Dept
234 GIFT
253 GIFT
933 GIFT
130 GIFT
====== ======
1546

There were 7 entries altogether in the file

18.7.7. Одновременный просмотр двух записей

В некоторых случаях возникает необходимость в одновременной обработке двух записей, например, если нужно сравнить различные поля двух записей. Чтобы просматривать одновременно по две записи, достаточно после первой конструкции "while read" поместить оператор "do" с другим оператором "read". Применяя эту методику, следует помнить, что количество записей для просмотра составляет четное число. Не забывайте проверять число просматриваемых записей!

Ниже приводится файл, содержащий шесть записей: record1, record2 и т. д.

$ pg record.txt

record 1

record 2

record 3

record 4

record 5

record 6

В этом примере одновременно просматривается по две записи. Никакой проверки записей в примере не производится.

Ниже приводится соответствующий сценарий.

$ pg readpair

#!/bin/sh

#readpair

#первая запись

while read rec1 do

#вторая запись

read rec2

#выполняется дальнейшая обработка/тестирование для тестирования или

#сравнения обеих записей

echo "This ls record one of a pair :$rec1"

echo "This ls record two of a pair :$rec2"

done < record.txt

С помощью команды wc сначала проверим, что было выполнено четное число записей.

$ cat record.txt | wc -l

б

Имеется шесть записей, поэтому приступим к их обработке.

$ readpair

This ls record one of a pair :record 1

This ls record two of a pair :record 2

This ls record one of a pair :record 3

This ls record two of a pair :record 4

This ls record one of a pair :record 5

This ls record two of a pair :record 6

18.7.8. Игнорирование символа #

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

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

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

Ниже приводится типичный файл конфигурации.

$ pg config

#ЭТО КОНФИГУРАЦИОННЫЙ ФАЙЛ ПОДСИСТЕМЫ АУДИТА

#НЕ РЕДАКТИРУЙТЕ ЕГО! ОН РАБОТАЕТ

#

# задание административного доступа

AUDITSCM=full

#местонахождение подсистем

AUDITSUB=/usr/opt/audit/sub

#серийный номер хэша для продукта

HASHSER=12S90AB3

#КОНЕЦ ФАЙЛА КОНФИГУРАЦИИ!!!

#

Ниже приводится сценарий, где игнорируются символы хэша:

$ pg ignore_hash

#!/bin/sh

# игнорируйте_хэш

INPUT_FILE=config

if [ -s $INPUT_FILE ]; then

while read LINE

do

case $LINE in

\#*) ;; # игнорировать все символы хэша

*) echo $LINE

;;

esac

done < $INPUT_FILE

else

echo "`basename $0` : Sorry $INPUT_FILE does not exist or ls empty"

exit 1

fi

При выполнении получим:

$ ignore_hash

AUDITSCM=full

AUDITSUB=/usr/opt/audit/sub

HASHSER=12890AB3

18.7.9. Работа с форматированными отчетами

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

$ pg order

RE‑ORDER REPORT
ITEM ORDERLEVEL LEVEL
Pens 14 12
Pencils 15 15
Pads 7 3
Disks 3 2
Sharpeners 5 1

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

Ранее уже рассматривалось, каким образом можно игнорировать строки, начинающиеся определенными символами. Сначала производится чтение из файла. При этом игнорируются все строки, которые начинаются символами # и строками вида "ITEM". После прочтения осуществляется перенаправление данных во временный файл. Затем команда sed применяется для удаления пустых строк. На самом деле все, что делается в этом случае, напоминает фильтрацию текстового файла. Описанные действия реализованы с помощью следующего кода.

$ pg whileorder

#!/bin/sh

# whileorder

INPUT_FILE=order

HOLD=order.tmp

if [ -s $INPUT_FILE ]; then

# пустой файл вывода, добавление не производится!

>$HOLD

while read LINE

do

case $LINE in

\#*|ITEM*) ;; # игнорирование строк, содержащих символы # или ITEM

*)

# перенаправление вывода во временный файл

echo $LINE >$HOLD

;;

esac

done <$INPUT_FILE

# применение команды sed для удаления пустых строк

sed -e '/^$/d' order.tmp > order.$$

mv order.$$ order.tmp

else

echo "`basename $0` : Sorry $INPUT_FILE does not exist or ls empty"

fi

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

$ pg order.tmp

Pens 14 12

Pencils 15 15

Pads 7 3

Disks 3 2

Sharpeners 5 1

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

$ pg whileorder2

#!/bin/sh

#whileorder2

#инициализация переменных

HOLD=order.tmp

RE_ORDER=0

ORDERS=0

STATIONERY_TOT=0

if [ -s $HOLD ]; then

echo "=========== STOCK RE_ORDER REPORT ================"

while read ITEM REORD LEVEL

do

#находимся ли мы ниже уровня переупорядочивания для данного пункта??

if [ "$LEVEL" — lt "$REORD" ]; then

да, выполняется заказ другого количества товаров

NEW_ORDER=`expr $REORD + $REORD`

# подсчет итогов по заказам

ORDERS=`expr $ORDERS + 1`

# подсчет итогов по уровням запасов

STATIONERY_TOT=`expr $STATIQNERY_TOT + $LEVEL`

echo "$ITEM need reordering to the amount $NEW_ORDER"

done <$HOLD

echo "$ORDERS new items need to be ordered"

echo "Our reorder total ls $STATIONERY_TOT"

else

echo "`basename $0` : Sorry $HOLD does not exist or ls empty"

fi

Результат выполнения сценария при обработке файла заказов.

$ whileorder

========= STOCK REORDER REPORT ===============

Pens need reordering to the amount 28

Pads need reordering to the amount 14

Disks need reordering to the amount 6

Sharpeners need reordering to the amount 10

4 new items need to be ordered

Our reorder total is 18

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

18.7.10. Цикл while и дескрипторы файлов

При изучении дескрипторов файлов в главе 5 уже упоминалось о том, что для считывания данных в файл применяется цикл while. С помощью дескрипторов файлов 3 и 4 следующий сценарий создает резервную копию файла myfile.txt под именем myfile.bak. Обратите внимание, что в начале сценария осуществляется проверка, которая позволяет убедиться в наличии файла. Если файл отсутствует или не содержит данные, выполнение сценария немедленно прекращается. Также обратите внимание на то, что в цикле while имеется команда null (:). Из‑за наличия этой команды цикл может выполняться бесконечно, поскольку null всегда возвращает значение "истина". При осуществлении попытки считывания по достижении конца файла отображается сообщение об ошибке. При этом выполнение сценария прекращается.

$ pg copyfile

#!/bin/sh

# copyfile

FILENAME=myfile.txt

FILENAME_BAK=myfile.bak

if [ -s $FILENAME ]; then

#открыть FILENAME для записи

#открыть FILENAME для считывания'

exec 4>$FILENAME_BAK

exec 3<$FILENAME

#бесконечный цикл до тех пор, пока имеются данные или пока не возникает

#ошибка, связанная с достижением конца файла while :

do

read LINE <&3

if [ "$?" -ne 0 ]; then

# ошибки при закрытии

exec 3<&-

exec 4<&-

exit fi

# запись в файл FILENAME_BAK

echo $LINE>&4

done else

echo "`basename $0` : Sorry, $FILENAME is not present or is empty" >&2

fi

18.8. Управление ходом выполнения циклов с помощью команд break и continue

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

   • break;

   • continue.

18.8.1. Команда break

Команда break позволяет прервать выполнение цикла. Эта команда обычно'; используются для выхода из цикла или прекращения выполнения оператора case' после осуществления некоторой обработки. Если вы окажетесь внутри вложенного цикла, можно указать количество прерываемых циклов: например, если существуют два вложенных цикла, для их прерывания используется команда break 2.

18.8.2. Прекращение выполнения оператора case

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

$ pg breakout

#!/bin/sh

   • breakout

   • while : бесконечный цикл while :

do

echo -n "Enter any number [1..5] :"

read ANS

case $ANS in

1|2|3|4|5) echo "great you entered a number between 1 and 5"

;;

*) echo "Wrong number..bye"

break

;;

esac

done

18.8.3. Команда continue

Команда continue по своему действию напоминает команду break, за исключением одной существенной детали: она не прерывает цикл, а лишь приводит к пропуску текущей итерации цикла.

18.8.4. Пропуск строк в файлах

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

$ pg names2.txt

LISTING OF PERSONNEL FILE

TAKEN AS AT 06/1999

Louise Conrad:Accounts:ACC8987

Peter James:Payroll:PR489

Fred Terms:Customer:CUS012

Janes Lenod:Accounts:ACC887

Frank Pavely:Payroll:PR4S9

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

Также не следует принимать во внимание сведения о сотруднике с именем Peter

james Этот человек уволился из компании, но запись о нем осталась в файле.

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

Вот сценарий, выполняющий описанные задачи

$ pg whilecontinue

#!/bin/sh

# whilecontinue

SAVFDIFS=$IFS

IFS=:

INPUT_FlLE=names2.txt

NAME_HOLD="Peter James"

LINE_NO=0

if [ -s $INPUT_FILE ]; then

while read NAME DEPT ID

do

LINE_NO=`expr $LINE_NO + 1`

if [ "$LINE_NO" — le 2 ]; then

# допуск, если номер строки меньше 2 continue

if [ "$NAME"="$NAME_HOLD" ], then

# пропуск, если переменной NAME_HOLD присвоено имя Peter James

continue

else

echo " Now processing $NAME $DEPT $ID"

# обработка файла

fi

done < $INPUT_FILE

IFS=$SAVEDIFS

else

echo "`basename $0` : sorry file not found or there is no data in the file" >&2

exit 1

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

$ whilecontinue

Luise Conrad Accounts ACC8987

Fred Terns Customer CJS012

James Lenod Accounts ACC887

Frank Pavely Payroll PR389

18.9. Меню

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

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

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

Ниже приводится примерный вид меню.

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

$ date +%d/%B/%Y

32/05/1999

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

Присвоим переменным осмысленные имена:

MYDATE=`date +%d/%m/%Y`

THIS_H0ST=`hostname -s`

USER=`whoami`

В цикле while команда null помещена непосредственно после слова "while". Вот формат бесконечного цикла:

while : do

команды..

done

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

команда << СЛОВО

любые вводимые данные

СЛОВО

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

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

1: List files in current directory

2: Use the vi editor

3: See who ls on the system

H: Help screen

Q: Exit Menu

Оператор case должен обрабатывать все шаблоны, переводя все строчные символы шаблона в прописные. При этом устраняется риск случайного нажатия пользователем клавиши [Caps Lock], в результате чего могут искажаться вводимые данные. В случае, когда сценарий меню выполняет бесконечный цикл, пользователь нуждается в реализации элегантного выхода. В связи с этим, если пользователь выбирает клавишу [Q] или [q], сценарий должен осуществить выход с нулевым значением.

Если пользователь выполняет некорректный ввод, раздается звуковой сигнал и отображается предостерегающее сообщение. Хотя в начале этой главы упоминалось о том, что будут применяться операторы echo Linux BSD, для генерации звукового сигнала будет применена команда из версии System V:

echo '"\007 the bell rang"

Конструкции echo и read применяются для задержки отображения экрана до тех пор, пока пользователь не нажмет клавишу [Enter]. При этом могут просматриваться любые сообщения или вывод команд.

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

$ pg menu

#!/bin/sh # меню

# установка даты, имени пользователя и хоста

MYDATE=`date +%d/%m/%Y`

THIS_HOST= `hostname -s` USER=`whoami`

# бесконечный цикл!

while :

do

# очистка экрана с помощью команды tput

# здесь начинается конструкция "документ здесь"

cat <<MAYDAY

# завершение конструкции "документ здесь"

echo -e -n "\tYour Choice [1,2,3,H, Q] >"

read CHOICE

case $CHOICE in

1) ls

;;

2) vi

;;

3) who

;;

H|h)

# использование конструкции "документ здесь" для экрана помощи

cat <<MAYDAY

This "ls the help screen, nothing here yet to help you!

MAYDAY

;;

Q|q) exit 0

*) echo -e "\t\Q07unknown user response"

;;

esac

echo -e -n "\tHit the return key to continue"

read DUMMY

done

18.10. Заключение

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

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

ГЛАВА 19

Функции интерпретатора shell

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

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

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

   • определение функций;

   • работа с функциями в сценарии;

   • использование функций, определенных в файле функций;

   • примеры функций.

Функция состоит из двух частей:

Метка функции

Тело функции

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

Формат, применяемый для определения функций:

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

{

Команда1}

или

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

команда 1 \

}

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

function имя_функции ()

{

….

}

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

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

19.1. Объявление функций в сценарии

Вот пример простой функции:

hello ()

{

echo "Hello there today's date is `date`"

}

Перед использованием функций их необходимо объявить. Суть объявления заключается в том, что все функции должны быть размещены в начале кода сценария. Невозможно сослаться на функцию до тех пор, пока она не попадет в "поле зрения" интерпретатора команд. Для вызова функции требуется просто ввести ее имя. В предыдущем примере функция называлась "hello"; тело функции включало конструкцию echo, которая, в свою очередь, отображала текущую дату.

19.2. Использование функций в сценарии

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

$ pg fund

#!/bin/sh

# func1

hello ()

{

echo "Hello there today's date ls `date`"

)

echo "now going to the function hello"

hello

echo "back from the function"

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

$ fund

now going to the function hello

Hello there today's date ls Sun Jun 6 10:46:59 GMT 2000

back from the function

В предыдущем примере функция была объявлена в начале сценария. Для обращения к функции просто вводится ее имя, которое: в данном случае звучит как "hello". После завершения выполнения функции управление возвращается следующей конструкции, которая размещена после вызова функции. В приведенном примере речь идет о конструкции echo "back from the function".

19.3. Передача параметров функции

Порядок передачи параметров функции аналогичен передаче параметров обычному сценарию. При этом используются специальные переменные $1, $2, … $9. При получении функцией переданных ей аргументов происходит замена аргументов, изначально переданных сценарию интерпретатора shell. В связи с этим неплохо было бы повторно присвоить значения переменным, получаемым функцией. В любом случае это стоит сделать, поскольку при наличии ошибок в функциях их можно будет легко обнаружить, воспользовавшись именами локальных переменных. Для вызывающих аргументов (переменных), находящихся Внутри функции, имя каждой переменной начинается с символа подчеркивания, например: _FILENAME или _filename.

19.4. Возврат значения функции

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

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

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

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

19.5. Проверка значений, возвращаемых функцией

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

check it ls_a directory $FILENAME

# вызов функции и проверка

if [ $?=0 ]

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

then

echo "All ls OK" else

echo "Something went wrong!"

fi

Лучшим методом является использование оператора if, с помощью которого осуществляется проверка возвращаемого значения (0 или 1). Встраивание вызова функции в структуру оператора if значительно улучшает читабельность программного кода. Например:

if check_it_is_a_directory $FILENAME; then

echo "All is OK"

# действия

else

echo "Something went wrong!"

# действия

fi

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

имя_переменной=`имя_функции`

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

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

19.6. Файл функций

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

В начале файла функции должна находиться конструкция #!/bin/sh. Этому файлу можно присвоить любое имя, но все же лучше использовать какое‑либо осмысленное наименование, например functions.main.

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

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

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

19.7. Создание файла функций

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

Создаваемый файл функций functions.main будет содержать следующий код:

$ pg functions.main

#!/bin/sh

# functions.main

#

findit: интерфейс для базовой команды find

findit () (

#findit

if [ $# -lt 1 ]; then

echo "usage: findit file"

return 1

find / -name $1 -print

Код, приведенный выше, ранее уже упоминался в книге, но теперь он включен в состав функции. Этот код лежит в основе интерфейса для базовой команды find . Если команде не передаются аргументы, то возвращается значение 1 (что свидетельствует о возникновении ошибки). Обратите внимание, что ошибочная конструкция фактически является отображенным именем функции (если же была использована команда $0, интерпретатор команд просто возвращает сообщение sh). Причина отображения подобного сообщения заключается в том, что файл не является файлом сценария. В любом случае это сообщение не несет много информации для пользователя.

19.8. Подключение файла функций

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

. /путь/имя_файла

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

$. functions.main

Если в результате выполнения этой команды возвращается сообщение 'file not found' (файл не найден), попытайтесь воспользоваться следующей командой:

$ . ./functions.main

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

19.9. Проверка загруженных функций

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

$ set

USER=dave

findit=()

{

if [ $# -lt 1 ]; then

echo "usage findit file";

return 1; fi; find / -name $1 -print

}

19.10. Вызов функций интерпретатора shell

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

$ findit groups

/usr/bin/groups

/usr/local/backups/groups.bak

19.10.1. Удаление shell–функций

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

unset имя_функции $ unset findit

Если вы сейчас введете команду set, функция не будет найдена.

19.10.2. Редактирование shell–функций

А теперь изменим файл fiinctions.main. Добавим в функцию цикл for, в результате чего сценарий сможет считывать более одного параметра из командной строки. Функция приобретет следующий вид:

$ pg functions.main

#!/bin/sh findit ()

{

# findit

#if [ $# -lt 1 3; then

echo "usage: findit file"

return 1 fi

for loop do

find / -name $LOOP -print done }

Снова загрузим исходный файл:

$ . ./functions.main

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

$ set

findit=()

{

if [ $# -lt 1 ]; then

echo "usage :`basename $0` file";

return 1; fi;

for loop in "$@"; do

find / -name $LOOP -print; done }

Далее вызывается измененная функция findit. При этом поддерживаются два файла с целью осуществления поиска:

$ findit LPSO.doc passwd

/usr/local/accounts/LPSO.doc

/etc/passwd

19.10.3. Примеры функций

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

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

Подтверждение ввода

Рассмотрим небольшой сценарий, который запрашивает имя и фамилию пользователя:

$ pg func2

#!/bin/sh

# func2

echo -n "What ls your first name :"

read F_NAME

echo -n "What ls your surname:"

read S_NAME

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

char_name()

{

#char_name

#вызов: char_name string

#назначение аргумента новой переменной

_LETTERS_ONLY=$1

#использование awk для проверки на наличие символов!

_LETTERS_ONLY=`echo $1 | awk '{if [$0~/[^a‑zA‑Z]/} print "1"}'`

if [ "$_LETTERS_ONLY" != "" ] then

# ошибки

return 1

else

# содержит только символы

return 0

fi }

Сначала переменной $1 будет присвоено более осмысленное имя. Затем применяется утилита awk, осуществляющая проверку, состоит ли переданная строка из одних литер. В результате выполнения возвращается код, включающий 1 (для символов, не являющихся литерами) и 0 — для символов–литер. Этот код присваивается переменной _LETTERS_ONLY.

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

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

if char_name $F_NAME; then

echo "OK"

else

echo "ERRORS"

fi

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

name_error ()

# name_error

# отображение сообщения об ошибке

{

echo " $@ contains errors, it must contain only letters"

}

Функция name_error будет отображать сообщения об ошибках, игнорируя при этом все некорректные записи. Применение специальной переменной $@ позволяет отображать на экране значения всех аргументов. В рассматриваемом случае будет отображено либо значение f_name, либо значение $NAME. А теперь приведем завершенный сценарий, созданный с применением функций:

$ pg func2

#!/bin/sh

char_r.ame {)

# наименование_символа

# вызов: char_name строка

# проверка на предмет того, действительно ли $1 содержит только символы a‑z.,A‑Z

{

# присвоение аргумента новой переменной

_LETTERS_ONLY=$1

_:LETTERS_ONLY=`echo $1|awk '{ if ($0~/[^a‑zA‑Z]/) print "1"}`

if [ "$_LETTERS_ONLY" != "" ]

then

# присутствуют ошибки

return 1

else

# содержит только символы

return 0

fi }

name_error()

# отображение сообщения об ошибке

{

echo " $@ contains errors, it must contain only letters"

}

while : do

echo -n "What ls your first name :"

read F_NAME

if char_name $F_NAME

then

# все OK, завершение выполнения break else

name_error $F_NAME fi done

while : do

echo -n "What ls your surname :"

read S_NAME

if char_narae $S_NAME

then

# все OK, завершение выполнения

break else

name_error $S_NAME

fi

done

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

Ниже приведены результаты выполнения описанного сценария:

$ func2

What ls your first name: Davi2d

Davi2d contains errors, it must contain only letters

What ls your first name: David

What ls your surname: Tansley1

Tansley1 contains errors, it must contain only letters

What ls your surname: Tansley

Проблемы с конструкцией echo

В системах Linux, BSD или System V конструкция echo по–разному интерпретирует служебные символы. Создадим функцию, определяющую систему, в которой используется конструкция echo.

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

Для реализации описанного поведения в системах Linux и BSD совместно с командой echo применяется опция -n. Ниже приводится пример конструкции echo LINUX (BSD), когда командная строка продолжает отображаться на экране до момента завершения сценария:

$ echo -n "Your паше :"

Your name : {{?}}

В System V в этом случае применяется параметр \с:

$ echo "Your name :\c"

Your name : []

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

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

При использовании первого метода проверка управляющего символа происходит внутри конструкции echo. Если после ввода команды echo \007 прозвучал звуковой сигнал, значит, перед нами System V. Если отображается строка "\007", значит, текущей операционной системой является Linux.

Вот первая функция, выполняющая проверку на наличие управляющих символов.

uni_prompt ()

   • uni_prompt

   • универсальная конструкция echo

{

if [ `echo "\007"` ="\007" ] >/dev/null 2>&1

# слышен звуковой сигнал либо отображаются символы?

then

# отображаются символы, это LINUX/BSD

echo -e -n "$@"

else

# это System V

echo "$@\c"

fi }

И снова обратите внимание на применение специальной переменной $@ для отображения строки. Для вызова функции в сценарии можно использовать следующую команду:

uni_prompt "\007There goes the bell. What is your name :"

В результате выполнения этой команды выдается звуковой сигнал и отображается строка "What is your name", которая остается на экране.

Для проверки на наличие новой строки можно отобразить любой символ, используя версию команды echo \c системы System V. Если символ "зависает" в конце строки, мы имеем дело с System V; если же нет, значит у нас система Linux/BSD.

Второй метод заключается в проверке того, будет ли литера Z "зависать" в конце строки. При этом используется опция \с системы System V.

uni_prompt ()

#uni_prompt

#универсальная командная строка

{

if [ `echo "Z\c"`="Z" ] >/dev/null 2>&1

then

# System V

echo "$@\c"

else

# LINUX/BSD

echo -e -n "$@"

fi }

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

uni_prompt "\007 There goes the bell, What ls your name :"

Вызов любой из описанных выше функций возможен с помощью следующего кода:

uni_prompt "\007 There goes the bell, What is your name :"

read NAME

В результате мы получим:

There goes the bell, What ls your name :

Чтение одиночного символа

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

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

dd if=/dev/zero of=myfile count=512 bs=2048

Команда dd также может интерпретировать результаты ввода с клавиатуры и использоваться для чтения символов. В данном случае ожидается появление лишь одного символа. Команда dd должна завершать выполнение после нахождения символа новой строки; этот управляющий символ появляется после нажатия пользователем клавиши [Return]. Команда dd в данном случае также посылает один символ. Перед тем как произойдет одна из описанных ситуаций, необходимо установить терминал в исходный режим. Для этого применяется команда stty. Настройки в приведенном ниже коде сохраняются перед вызовом команды dd и затем восстанавливаются после завершения выполнения команды dd.

read_a_char()

#read_a_char {

#сохранение настроек SAVEDSTTY=`stty -g`

#задание параметра терминала stty cbreak

#чтение и вывод лишь одного символа

dd if=/dev/tty bs=l count=l 2> /dev/null

# восстановление параметра терминала и настроек

stty -cbreak

stty $SAVEDSTTY }

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

echo -n "Hit Any Key To Continue"

character=`read_a_char`

echo " In case you are wondering you pressed $character"

Проверка наличия каталога

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

is_it_a_directory()

{

# is_it_a_directorу

# вызов: is_it_a_directory имя_каталога

if [ $# -lt 1 ]; then

echo "is_it_a_directory: I need an argument" return 1 fi

# это каталог ?

_DIRECTORY_NAME=$1

if [ ! -d $_DIRECTORY_NAME ]; then

# нет

return 1

else

# да

return 0

fi

}

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

echo -n "enter destination directory :"

read DIREC

if is_it_a_directory $DIREC;

then :

else

echo "$DIREC does not exist, create it now? [y..n]"

# здесь должны находится команды для создания каталога или для выхода

fi

Запрос на ввод Y или N

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

Create a directory

Do you wish to delete this file

Run the backup now

Confirm to save a record

Этот перечень может быть достаточно длинным.

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

continue_prompt ()

#continue_prompt

#вызов: continue_prompt "отображаемая_строка" ответ_по_умолчанию

{

_STR=$1

_DEFAULT=$2

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

if [ $# -lt 1 ]; then

echo "continue_prompt: I need a string to display"

return 1

fi

# бесконечный цикл

while :

do

echo -n "$_STR [Y..N] [$_DEFAULT]:" read _ANS

#если пользователь нажал [Return], устанавливаются настройки

#по умолчанию и определяется возвращаемое значение,

#ниже находится пробел, а затем символ $

: ${_ANS:=S_DEFAULT}

if [ "$_ANS" = "" ]; then

case $_ANS in

Y) return 0 ;;

N) return 1 ;;

esac

fi

# пользователь что‑то выбрал

case $_ANS in

y|Y|Yes|YES)

return 0

;;

n|N|No|NO)

return 1

;;

*) echo "Answer either Y or N, default is $_DEFAULT"

;;

esac

echo $_ANS

done }

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

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

if continue_prompt "Do you want to delete the var filesytem" "N"; then

echo "Are you nuts!!"

else

echo "Phew, what a good answer!"

fi

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

Do you really want to delete the var filesystem [Y..N] [N] :

Phew, what a good answer!

Do you really want to delete the var filesystem [Y..N] [N] :y

Are you nuts!!

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

Ниже приводится другой способ вызова функции:

#if continue_prompt "Do you really want to print this report" "Y"; then

lpr report

else:

fi

Функцию можно также вызвать с использованием переменной $1, содержащей строку:

if continue_prompt $1 "Y"; then

lpr report

else :

fi

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

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

А сейчас мы рассмотрим функцию, которая позволит избежать просмотра файла /etc/passwd с помощью команды grep.

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

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

Программный код функции:

whois ()

#whois

#вызов: whois идентификатор_пользователя

{

# проверка на наличие корректных параметров

if [ $# -lt 1 ]; then

echo "whois : need user id's please"

return 1

fi

for loop do

_USER_NAME=`grep $LOOP /etc/passwd | awk -F: '(print $4}'`

if [ "$_USER_NAME"="" ]; then

echo "whois: Sorry cannot find $LOOP"

else

echo "$LOOP is $_USER_NAME" fi

done

}

Функция whois может быть вызвана следующим образом:

$ whois davs peters superman

dave ls David Tansley — admin accts

peter ls Peter Stromer -customer services

whois: Sorry cannot find superman

Использование нумерации в текстовом файле

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

number_file ()

#number_file

#вызов: number_file имя_файла {

_FILENAME=$1

# проверка наличия корректных параметров

if [ $# -ne 1 ]; then

echo "number_flie: I need a filename to number"

return 1

fi

loop=1

while read LINE do

echo "$LOOP: $LINE" loop=`expr $LOOP + 1`

done < $_FILENAME

}

Для вызова функции number_file просто укажите имя файла, подлежащего нумерации, в качестве аргумента функции. Вызов функции может также осуществляться из среды интерпретатора shell путем указания имени файла. Например:

$ number_file myfile

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

$.number_file $1

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

$ number_file /home/dave/file_listing

1: total 105

2: -rw‑r—r—- 1 dave admin 0 Jun 6 20:03:DT

3: -rw‑r--r-- 1 dave admin 306 May 23 16:00 LPSO. AKS

4: -rw‑r--r-- 1 dave admin 306 May 23 16:00 LPSO. AKS. UC

5: -rw‑r--r-- 1 dave admin 324 May 23 16:00 LPSO. MBB

6: -rw‑r--r-- 1 dave admin 324 May 23 16:00 LPSO. MBB. UC

7: -rw‑r--r-- 1 dave admin 315 May 23 16:00 LPSO. MKQ

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

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

Ниже приводится соответствующая функция. Эта функция имитирует действие команды tr:

str_to_upper ()

#str_to_upper

# вызов: str_to_upper $1

{

STR=$1

# проверка на наличие корректных параметров

if [ $# -ne 1 ]; then

echo "number_file: I need a string to convert please"

return 1

fi

echo $@ |tr '[a‑z]' '[A‑Z]'

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

UPPER=`str_to_upper "documents.live"`

echo $UPPER

либо указать аргумент функции вместо строки:

UPPER=`str_to_upper $1`

echo $UPPER

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

is_upper

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

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

Программный код функции:

is_upper ()

#is_upper

#вызов: is_upper $1

{

# проверка на наличие корректных параметров

if [ $# -ne 1 ]; then

echo "is_upper: I need a string to test OK"

return 1

fi

# применение awk для проверки на наличие прописных символов

_IS_UPPER=`echo $1 | awk '{if($0~/[^A‑Z]/) print "1"}`

if [ "$_IS_UPPER" != "" ]

then

# нет, не все символы являются прописными

return 1

else

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

return 0

fi }

При вызове функции is_upper укажите строковый аргумент. На примере показано, как вызывается функция.

echo -n "Enter the filename :"

read FILENAME

if ls_upper $FILENAME; then

echo "Great it's upper case" else

echo "Sorry it's not upper case" fi

Для проверки наличия в строке строчных символов просто замените существующую конструкцию awk а функции is_upper и измените имя функции на is_lower.

_IS_LOWER=`echo $1 |awk '{ if ($0~/[^a‑z] /) print "1"}`

Преобразование символов строки в строчные символы

В предыдущем разделе мы рассмотрели функцию str_to_upper, а теперь речь пойдет о функции str_to_lower. Вот код самой функции:

str_to_lower ()

#str_to_lower

#вызов: str_to_lower $1

{

#проверка на наличие корректных параметров

if [ $# -ne 1 ]; then

echo "str_to_lower: I need a string to convert please"

return 1

fi

echo $@ | tr '[A‑Z]' '[a~z]'

}

Переменная lower хранит возвращенное значение строки, содержащей строчные символы. Обратите внимание на повторное использование специального параметра $@ для передачи всех аргументов. Функция str_to_lower может быть вызвана двумя способами. Во–neрвых, можно указать строку в сценарии:

LOWER=`str_to_lower "documents.live"`

echo $LOWER

Альтернативный вариант — указать аргумент для функции вместо задания строки:

LOWER=`str_to_lower $1`

echo $LOWER

Определение длины строки

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

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

Вот сама функция:

check_length()

#check_length

#вызов: check_length строка максимальная_длина_строки

{

_STR=$1

_МАХ=$2

# проверка на наличие корректных параметров

if [ $# -ne 2 ]; then

echo "check_length: I need a string and max length the string should be" return 1

fi

# проверка длины строки

_LENGTH=`echo $_STR |awk '{print length ($0)}'`

if [ "$_LENGTH" -gt "$_MAX" ]; then

# длина строки слишком велика

return 1

else

# строка имеет обычную длину

return О

fi }

Функция check length может быть вызвана следующим образом:

$ pg test_name

#!/bin/sh

# test name

while : do

echo -n "Enter your FIRST name :"

read NAME

if check_length $NAME 10

then break.

# ничего не происходит, если все условия выполнены else

echo "The name field ls too long 10 characters max"

fi

done

Цикл продолжает выполняться до тех пор, пока данные, вводимые для переменной NAME, меньше, чем значение переменной MAX (эта переменная содержит количество разрешенных символов; в данном случае речь идет о 10 символах). Команда break позволяет завершить выполнение цикла.

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

$ val_max

Enter your FIRST name :Pertererrrrrrrrrrrrrrr

The name field ls too long 10 characters max

Enter your FIRST name :Peter

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

Ниже приведен пример, иллюстрирующий сказанное:

echo -n "name :"

read NAME

echo $NAME | wc -c

Результат выполнения описанного фрагмента сценария (здесь [] является пробелом):

name : Peter[][]

Функция chop

Функция chop удаляет символы в начале строки. Этой функции передается строка; пользователь указывает, сколько символов необходимо "обрезать", начиная с первого символа. Предположим, что имеется строка mydocument.doc и требуется "обрезать" часть mydocument, в результате чего функция будет возвращать только часть .doc. При этом функции chop могут быть переданы следующие параметры:

MYDOCUMENT.DOC 10

Код функции chop:

chop () # chop

# вызов: chop строка количество_обрезаемых_символов

#

STR=$1

_CHOP=$2

# подстрока awk, начинается с 0, нам потребуется прирастить ее на единицу # для отображения того, что если пользователь задал обрезание 2 символов, 2 символа будут удалены а не 1

CHOP=`expr $_CHOP + 1`

#проверка на корректность параметров

if [ $# -ne 2 ]; then

echo "check_length: I need a string and how many characters to chop"

return 1

fi

# первоначальная длина строки

# мы не можем обрезать больше символов, чем содержится в строке!!

_LENGTH=`echo $_STR | awk '{print length ($0)}'`

if [ "$_LENGTH" — lt "$_CHOP" ]; then

echo "Sorry you have asked to chop more characters than there are in the string"

return 1

fi

echo $_STR | awk '(print substr ($1, '$_CHOP')}'

}

Возвращаемая строка, которая была "обрезана", присваивается переменной chopped. Для вызова функции chop используется следующая последовательность:

CHOPPED=`chop "Honeysuckle" 5`

echo $CHOPPED

suckle

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

echo -n "Enter the Filename :"

read FILENAME

CHOPPED=`chop $FILENAME 1`

# первый символ будет обрезан !

Функция months

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

Например, использование в качестве аргумента значения 3 либо 03 приведет к возврату значения "March". Вот описание самой функции:

months () {

# months

_MONTH=$1

# проверка на наличие корректных параметров

if [ $# -ne 1 ]; then

echo "months: I need a number 1 to 12 "

return 1

fi

case $_MONTH in

1|01|Jan)_FULL="January";;

2|02|Feb)_FULL="February";;

3|03|Mar)_FULL="March";;

4|04|Apr)_FULL="April";;

5|05|May)_FULL="May";;

6|06|Jun)_FULL="June";;

7|07|Jul)_FULL="July";;

8|08|Aug)_FULL="August;;

9|09|Sep|Sept)_FULL="September";;

10|Oct)_FULL="October";;

ll|Nov)_FULL="November;;

l2|Dec)_FULL="December";;

*) echo "months: Unknown month"

return 1

;;

esac

echo $_FULL

}

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

months 04

В результате отобразится наименование месяца "April". Можно также вызвать функцию из сценария:

MY_MONTH=`months 06`

echo "Generating the Report for Month End $MY_MOMTH"

В результате отобразится название месяца "June".

19.10.4. Подведение итогов

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

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

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

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

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

19.11. Вызов функций

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

19.11.1. Вызов функций, размещенных в сценариях

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

$ pg direc_check

#!/bin/sh

# файл функций

is_it_a_directory()

{

#is_it_a_directory(

#вызов: is_it_a_directory имя_каталога

_DIRECTORY_NAME=$1

if [ $# -lt 1 ]; then

echo "is_it_a_directory: I need a directory name to check"

return 1

fi

# это каталог?

if [ ! —d $_DIRECTORY_NAME ]; then

return 1

else

return 0

fi

}

#

error_msg

{

#error_msg

#сигнал; сообщение; повторный сигнал

echo -e "\007"

echo $@

echo -e "\007"

return 0

}

### END OF FUNCTIONS

echo -n "enter destination directory :"

read DIREC

if is_it_a_directory $DIREC

then :

else

error_msg "$DIREC does not exist…creating it now"

mkdir $DIREC > /dev/null 2>&1

if [ $? != 0 ]

then

error_msg "Could not create directory: check it out!"

exit 1

else :

fi

fi # не каталог

echo "extracting files…"

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

19.11.2. Вызов функций из файла функций

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

А теперь воспользуемся снова описанной выше функцией, но в этом случае поместим ее в файле функций. Назовем этот файл functions.sh, где "sh" означает "shell scripts" (сценарии интерпретатора shell).

$ pg functions.sh

#!/bin/sh

#functions.sh

#основные функции

is_it_a_directory () (

#is_it_a_directory

#вызов: is_it_a_directory имя_каталога #

if [ $# -lt 1 ]; then

echo "is_it_a_directory: I need a directory name to check"

return 1

fi

# это каталог ?

DIRECTORY_NAME=$1

if [ ! -d $DIRECTORY_NAME ]; then

return 1 else

return D fi )

error_msg ()

{

echo -e "\007"

echo $@

echo -e "\007"

return 0

}

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

. /<путь к файлу>

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

$ pg dlrec_check

#!/bin/sh

# direc_check

# загрузка файла функций functions.sh # ниже точка, пробел и косая черта. /home/dave/bin/functions.sh

# теперь могут использоваться функции

echo -n "enter destination directory :"

read DIREC

if is_it_a_directory $DIREC

then : .

else

error_msg "$DIREC does not exist… creating it now"

mkdir $DIREC > /dev/null 2>$1

if [ $? ! — 0 ]

then

error_msg "Could not create directory: check it out!"

exit 1

else :

fi

fi # не является каталогом

echo "extracting files…"

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

S direc_check

enter destination directory :AUDIT

AUDIT does not exist… creating it now

extracting files…

19.12. Загрузка файлов, которые состоят не только из функций

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

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

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

$ pg backfunc

#!/bin/sh

   • name: backfunc

   • конфигурационный файл содержит настройки по умолчанию для систем архивации _CODE="comet"

_FULLBACKUP="yes" _LOGF1LE="/logs/backup/" _DEVICE="/dev/rmt/0n" _INFORM="yes" _PRINT_STATS="yes"

Комментарии разъясняют суть программы. Первое поле, _code, содержит кодовое слово. Для просмотра его содержимого и изменения значений пользователь должен ввести код, соответствующий значению code. В данном случае указывается слово "comet".

Ниже приведен сценарий, который в ответ на ввод пароля отображает конфигурацию, заданную по умолчанию:

$ pg readfunc

#!/bin/sh

# readfunc

if [ -r backfunc ]; then

# указание файла с параметрами

. /backfunc else

echo "`basename $0` cannot locate backfunc file" fi

echo -n "Enter the code name :"

# соответствует ли указанный код коду из файла backfunc?

if [ "${CODE}" != "${_CODE}" ]; then

echo "Wrong code…exiting..will use defaults" exit 1 fi

echo ." The environment config file reports"

echo "Full Backup Required : $_FULLBACKUP"

echo "The Logfile is : $_LOGFILE"

echo "The Device To Backup To is : $_DEVICE" echo "You Are To Be Informed by Mail : $_INFORM" echo "A Statistic Report To Be Printed: $_PRINT_STATS"

После запуска сценария на выполнение отобразится запрос на ввод кода. Если введенный код соответствует заданному кодовому слову, можно будет просмотреть настройки, заданные по умолчанию. Рабочий вариант сценария предоставит возможности для изменения настроек, заданных по умолчанию.

$ readfunc

Enter the code name :comet

The environment config file reports

Full Backup Required : yes

The Logfile ls : /logs/backup/

The Device To Backup To ls : /dev/rmt/0n You Are To Be Informed by Mail : yes A Statistic Report To Be Printed: yes

19.13. Заключение

Применение функций позволяет сэкономить массу времени, затрачиваемого на разработку сценариев. Благодаря созданию универсальных и многократно используемых функций отпадает необходимость в технической поддержке разработанных сценариев, использующих эти функции.

После разработки набора требуемых функций необходимо разместить их в файле функций. В результате этого функциями смогут воспользоваться другие сценарии.

ГЛАВА 20

Передача параметров сценарию

В предыдущих главах рассматривались способы передачи параметров сценариям с помощью специальных переменных $1...$9. Специальная переменная $# указывает количество передаваемых параметров. Также обсуждалась конструкция usage. Эта конструкция применяется для информирования пользователя о том, как вызвать сценарий или функцию с помощью соответствующих параметров вызова. В этой главе будут рассмотрены следующие темы:

   • применение команды shift;

   • работа с командой getopts;

   • примеры использования команд shift и getopts.

Для проверки степени усвоения материала рассмотрим схему сценария, в котором используются параметры start и stop. При запуске сценария необходимо указать два параметра. Если они не заданы, отображается предупреждающее сообщение. Обратите внимание, что для обработки различных параметров, передаваемых сценарию, применяется конструкция case.

$ pg opt

#!/bin/sh

# opt

usage ()

{

echo "usage:`basename $0` start|stop process name"

}

OPT=$1

PR0CESSID=$1

#if [ $# -ne 2 ]

then

usage

exit 1 fi

case $OPT in

start|Start) echo "Starting..$PROCESSID"

# выполняется некоторая обработка

;;

stop|Stop) echo "Stopping..$PROCESSID" # выполняется некоторая обработка

;;

*) usage

;;

esac

Приведенный сценарий при вводе данных выдает такие результаты:

$ opt start named

Starting…named

$ opt start

usage:opt start|stop process name

Общий формат произвольной команды UNIX или Linux: команда опции файлы

Часть опции может принимать до 12 различных значений. Как показано в примере со сценарием opt, для работы с командными опциями следует создавать большой объем программного кода. В данном случае мы имеем дело лишь с двумя опциями, start и stop.

К счастью, интерпретатор команд поддерживает команду shift, с помощью которой можно выбирать различные опции. Команда shift позволяет устранить ограничение, состоящее в том, что при передаче параметров применяются только специальные переменные $1…$9.

20.1. Команда shift

При передаче сценарию параметров потребуется соответствующий метод обработки, использующий возможности команды shift. В результате выполнения этой команды позиционные аргументы смещаются влево на один элемент. Чтобы уточнить принцип действия команды shift, рассмотрим простой сценарий. Здесь применяется цикл while, обеспечивающий отображение на экране всех аргументов, переданных сценарию.

$ pg opt2

#!/bin/sh

# opt2

loop=0

while [ $# -ne 0 ] # цикл выполняется до тех пор, пока остаются аргументы

do

echo $1 done

Создается впечатление, что указанный сценарий будет выполняться до тех пор, пока в командной строке не останется аргументов. К сожалению, это не так. При запуске сценария на экран выводится только первый аргумент, поскольку в сценарии не предусмотрен переход к следующему параметру. Вот результат выполнения вышеприведенного сценария:

$ opt2 файл1 файл2 файлЗ

файл1 файл1 файл1

20.1.1. Простой способ использования команды shift

Для обработки каждого передаваемого аргумента достаточно воспользоваться командой shift. Ниже приводится соответствующий сценарий:

$ pg opt2

#!/bin/sh

# oPt2

1оор=0

while [ $# -ne 0 ] # цикл выполняется до тех пор, пока остаются аргументы do

echo $1

shift

done

Теперь, при выполнении этого сценария можно получить более приемлемый результат:

$ opt2 файл1 файл2 файл3

файл1 файл2 файл3

20.1.2. Последний параметр командной строки

Несмотря на то что команда eval еще не обсуждалась, можно воспользоваться ею, если требуется уточнить последний параметр командной строки (обычно в качестве этого параметра используется имя файла). Получить последний параметр командной строки вы можете двумя способами. Во–neрвых, с помощью команды eval echo \$$#, а во–вторых, путем применения команды shift `expr $# - 2`.

20.1.3. Преобразования файла с помощью команды shift

Использование возможностей команды shift значительно облегчает работу с опциями командной строки. Рассмотрим сценарий, выполняющий преобразование регистра символов. При этом будет применяться команда tr.

При выполнении сценария используются две опции:

-l для нижнего регистра -u для верхнего регистра

С помощью команды shift можно запустить на выполнение сценарий, работающий с опциями -l и -u. Ниже приводится первый вариант этого сценария.

$ pg tr_case

#!/bin/sh

#tr_case

#преобразование регистра usage ()

{

# сообщение usage

echo "usage:`basename $0` -[l|u] file [files]" >&2

exit 1

}

if [ $# -eq 0 ]; then

# параметры не переданы!

usage

fi

while [ $# -gt 0 ]

do

case $1 in

-u|-U) echo " -uoption specified"

# здесь укажите все установки переменных для нижнего регистра, затем

# примените команду shift shift

;;

-l|-L) echo " -l option specified"

# здесь укажите все установки переменных для верхнего регистра, затем

# примените команду shift shift

;;

*) usage

;;

esac

done

Вначале выполняется проверка, имеются ли аргументы в сценарии. Если они отсутствуют, выводится сообщение usage. При обработке аргументов с помощью конструкции case отображается значение каждой передаваемой опции. После этого применяется команда shift, выполняющая вывод в командной строке очередной опции. Если все опции обработаны, отображается сообщение usage.

А теперь посмотрим, как выглядят выводимые результаты при передаче сценарию двух некорректных аргументов.

$ tr_case -u -l -к

— и option specified

-l option specified

usage: tr_case —[l|u] file [files]

На следующем этапе обрабатываются файлы, передающиеся после обработки опций с помощью конструкции case. Для реализации этой задачи достаточно выполнить небольшие изменения. В конструкции case шаблон * заменяется шаблоном -*, что позволяет передавать некорректные опции, например -p или -q.

Чтобы с помощью цикла for обрабатывать каждый файл, следует для всех передаваемых имен файлов указывать шаблон *. Кроме того, с помощью опции -f проверяется наличие всех требуемых имен файлов.

Итак, улучшенный вариант конструкции case имеет следующий вид:

case

...

-*) usage

*) if [ -f $1 ]; then

FILES=$FILES" "$1 # присвоить переменной имена файлов

else

echo "`basename $0` cannot find the file $1"

fi

shift # получите следующий параметр !

esac

Следует также задать значения некоторых переменных в зависимости от указанной опции (-1, — u). При этом используются следующие переменные:


TRCASE Указывает тип преобразования регистра (верхний или нижний регистр)
ЕХТ Все преобразованные файлы имеют либо расширение .UC (для верхнего регистра), либо расширение .LC (для нижнего регистра). В исходный файл изменения не вносятся
OPT При передаче опций принимает значение yes, в противном случае — no. Только тогда, когда опции не передаются, можно перехватить их значение и вывести на экран сообщение

Для выполнения реального преобразования достаточно добавить еще один фрагмент кода, включающий команду tr. Этот фрагмент кода добавляется в конец сценария. При этом для считывания имен файлов используется цикл for и конструкция case. Ниже приводится код созданного сценария:

$ pg tr_case

#!/bin/sh

#tr_case

#преобразование символов файлов либо в верхний, либо в нижний регистр

FILES=""

TRCASE=""

EXT=""

OPT=no

# вызывается при неудачном преобразовании

<