Говорим на UNIX, часть 1: Овладей мощью командной строки

Mar 01, 2009 00:44

подбираем и комбинируем команды UNIX, создавая импровизированные программы

Уровень: вводный
Оригинал здесь
Перевод мой, весьма вольный

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

Говорим на UNIX: Привет, оболочка

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

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

find . -type f -print | sort | uniq

Эта строка комбинирует три команды:
- find рекурсивно просматривает содержимое директорий, начиная с указанной (в нашем случае указана точка, это удобное сокращение для текущей рабочей директории) и печатает содержимое при условии выполнения указанного условия. В нашем примере -type f указывает find, что печатать следует только имена обычных файлов.
- sort, как и следует из её имени, сортирует полученный список по алфавиту.
- uniq просматривает список, сравнивая соседние элементы и удаляя дубликаты. Продемонстрируем работу uniq на примере. Пусть исходный список выглядит так:

Groucho
Groucho
Chico
Chico
Groucho
Harpo
Zeppo
Zeppo

Применение uniq сократит этот список до следующего:

Groucho
Chico
Groucho
Harpo
Zeppo

Однако, если бы мы предварительно отсортировали список братьев Маркс, результат выполнения uniq был бы таким:

Chico
Groucho
Harpo
Zeppo

Для изучения дополнительных возможностей find, sort и uniq вам будет полезно ознакомиться со страницами руководства на вашей системе (например, man find).

Данные входят и данные выходят. Вот и всё, что о них можно сказать

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

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

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

% sort
mustache
horn
hat
Control-D

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



Рисунок 1. Типичное поведение UNIX программы. Чтение из stdin и вывод в stdout

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



Рисунок 2. Некоторые программы получают информацию из системных ресурсов и выводят её в stdout

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



Рисунок 3. Утилиты UNIX пишут сообщения об ошибках в stderr

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

Регулируем движение

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

Изменение источника (приёмника) исходных данных называется перенаправлением ввода (вывода). Вы можете перенаправить ввод так, что программа будет читать данные из файла. Или вы можете перенаправить вывод так, что программа будет записывать результаты в файл. Но также вы можете подать вывод одной программы на вход другой. Это может быть сделано с помощью оператора "|". Символ "вертикальная линия" будем для краткости называть "пайп". Английское "pipe" обычно ассоциируется с трубопроводными трубками, позволяющими соединять детали (например водогрей и смеситель) в единую работающую систему. Точно такую же роль играют пайпы в нашем примере, соединяя find, sort и uniq в единый конвейер обработки данных, как показано на рисунке 4.



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

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

При необходимости вы можете нарастить эту цепочку, перенаправив вывод uniq на вход ещё какой-нибудь программы. Например, можно добавить "| less" и получить на выходе постраничный список. Или добавить "| wc -l", чтобы автоматически подсчитать количество уникальных имён файлов (wc - сокращение от word count; используется для подсчета строк, слов или символов).

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

Ещё один удобный символ перенаправления -- "<" ("обратная воронка"). Она позволяет направить файл (точнее, содержимое файла) на стандартный вход. На рисунке 5 показано как это работает. Здесь команда sort получает список слов из файла, сортирует их и отправляет на теминал.



Рисунок 5. Перенаправление стандартного входа на чтение из файла

Часто требуется захватить и stdout и stderr. Например вы запустили длительную задачу по поиску данных и хотели бы наблюдать промежуточные результаты и сообщения об ошибках в процессе работы. Для этого вы можете использовать следующие варианты операторов перенаправления: "|&", ">&", ">>&". Они позволяют перенаправить stdout и stderr одновременно. На рисунке 6 показана схема объединения этих двух потоков сообщений в один.



Рисунок 6. Объединение устройств стандартного выхода и сообщений об ошибках

Введение в Z shell

Большинство современных командных оболочек, включая bash и ksh, поддерживают операции перенаправления. Синтаксис если и отличается, то очень несущественно.

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

bash$ ls
tellme
bash$ cat tellme
echo Your current login, working directory, and system are...
whoami
pwd
systemname
bash$ bash < tellme |& tee log
Your current login and working directory are...
strike
/home/strike
bash: systemname: command not found
bash$ ls
tellme log
bash$ cat log
Your current login and working directory are
strike
/home/strike
bash: systemname: command not found

Несмотря на то, что оболочка чаще всего используется для интерактивного взаимодействия посредством клавиатуры, она также читает данные из stdin. Фраза "bash < tellme" заставляет bash выполнить команды, записанные в файле tellme. А фраза "|& tee log" перенаправляет вывод от bash (объединяя stdout и stderr) на вход программы tee, которая печатает то, что получила и на stdout и в указанный файл (в нашем случае log).

Но что делать, если нам понадобиться подать на вход bash несколько файлов для выполнения? Единственным работающим решением будет: "cat file1 file2 file3 | bash". К сожалению синтаксис вида: "bash < file1 < file2 < file3" этой оболочкой не поддерживается.

То же и с перенаправлением вывода более чем в одно место. В bash вы не сможете выполнить такую элегантную команду: bighairyscript > ~/log | mail -s "Important stuff" team .

Но вот относительно молодая оболочка Z shell (zsh) поддерживае множественные перенаправления ввода/вывода. В следующем примере команда сохраняет результат работы в файле журнала (log) и отправляет этот же результат вам по электронной почте.

zsh% bash < tellme > log | mail -s "Who you are" 'whoami'
bash: line 4: systemname: command not found
zsh% Your current login, working directory, and system are...
strike
/home/strike

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

Пройдёмся по вышеприведённой командной строке слева направо. Команда bash создаёт файл log и отправляет по почте stdout всех команд, найденных в файле tellme. Поскольку stderr не был никуда перенаправлен, сообщения об ошибках печатаются в окне терминала. Следующая команда " file" является эквивалентом "cat > file").
Прим.перев.: "
Z shell способна обработать более одного перенаправления ввода. Команда "cat < file1 < file2 < file3" делает тоже, что и команда "cat file1 file2 file3". Честно говоря, первый пример более громоздок, чем второй. И множественное перенаправление вывода гораздо популярнее у пользователей Z shell, чем множественное перенаправление ввода. Однако, если используемая вами программа не принимает несколько имён файлов в качестве аргумента, то такое множественное перенаправление ввода может оказаться удобным.

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

Маленькие хитрости

Есть несколько приёмов, которые сделают ваш труд более продуктивным. Это должно работать не только в Z shell.

* Создание резервной копии директории, включая символьные ссылки, с использованием tar:

tar cf - /path/to/original | (mkdir -p /path/to/copy; cd /path/to/copy; tar xvf -)

Первый tar архивирует директорию и передаёт результат на stdout. Здесь знак минус работает вместе с опцией "c" (create, создать) и означает вывод результата на stdout. Последовательность команд, заключённая в скобки, приводит к вызову subshell (дочерней оболочки). Команды, выполняемые в subshell не могут изменить окружение основной оболочки. Это приведёт к тому, что смена директории внутри скобок не приведёт к смене директории после выполнения всей команды. mkdir -p создаст требуемую директорию, включая все промежуточные. cd сменит текущую директорию на только что созданную. Второй tar прочитает архив со стандартного входа и распакует его на новом месте. Здесь знак минус сочетается с опцией "x" (expand, распаковать) и означает, что данные надо получить из stdin.

* Для того, чтобы одновременно видеть результат на экране и сохранять его в файл можно использовать "less -O file". Опция -O заставляет less копировать stdin в указанный файл. Например:

sort /etc/aliases | less -Osorted

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

foreach i (*)
...
end

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

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

% find / -name '*html' -print \
| xargs grep -l 'www.example.com' \
| less -Opages

xargs прочитает имена файлов, полученные от find, и для каждого из них запустит "grep -l". ("grep -l" печатат имя файла после первого же совпадения и прекращает обработку). less позволит вам пролистать результаты, а также сохранит их в файле pages. Результатом будет список имён файлов, содержащих строку "www.example.com".

unix command line shell zsh командная ст

Previous post Next post
Up