Этот раздел посвящён нескольким примерам использования стандартных утилит для решения разных типичных (и не очень) задач. Эти примеры не следует воспринимать как исчерпывающий список возможностей, они приведены просто для демонстрации того, как можно организовать обработку данных при помощи конвейера. Чтобы освоить их, нужно читать руководства и экспериментировать.
Подсчёт
В европейской культуре очень большим авторитетом пользуются точные числа и количественные оценки. Поэтому пользователю часто бывает любопытно и даже необходимо точно посчитать что-нибудь многочисленное. Компьютер как нельзя более удобен для такой процедуры. Стандартная утилита для подсчёта строк, слов и символов — wc
(от англ. «word count» — «подсчёт слов»). Однако Мефодий запомнил, что в Linux многое можно представить как слова и строки, и решил с её помощью посчитать свои файлы.
[methody@localhost methody]$ find . | wc -l
42
[methody@localhost methody]$
Пример 10. Подсчёт файлов при помощи find
и wc
Удовлетворённый Мефодий получил желаемое число — «42
». Для этого ему потребовалась команда find
— рекомендованный ему Гуревичем инструмент поиска нужных файлов в системе. Мефодий вызвал find
с одним параметром — каталогом, с которого начинать поиск. find
выводит список найденных файлов по одному на строку, а поскольку критерии поиска в данном случае не уточнялись, то find
просто вывела список всех файлов во всех подкаталогах текущего каталога (домашнего каталога Мефодия). Этот список Мефодий передал утилите wc
, попросив её посчитать количество полученных строк «-l
». wc
выдала в ответ искомое число.
Задав find
критерии поиска, можно посчитать и что-нибудь менее тривиальное, например, файлы, которые создавались или были изменены в определённый промежуток времени, файлы с определённым режимом доступа, с определённым именем и т. п. Узнать обо всех возможностях поиска при помощи find
и подсчёта при помощи wc
можно из руководств по этим программам.
Отбрасывание ненужного
Иногда пользователя интересует только часть из тех данных, которые собирается выводить программа. Мефодий уже пользовался утилитой head
, которая нужна, чтобы вывести только первые несколько строк файла. Не менее полезна утилита tail
(англ. «хвост»), выводящая только последние строки файла. Если же пользователя интересует только определённая часть каждой строки файла — поможет утилита cut
.
Допустим, Мефодию потребовалось получить список всех файлов и подкаталогов в «/etc
», которые принадлежат группе «adm
». И при этом ему почему-то нужно, чтобы найденные файлы в списке были представлены не полным путём, а только именем файла (скорее всего, это требуется для последующей автоматической обработки этого списка).
[methody@localhost methody]$ find /etc -maxdepth 1 -group adm 2> /dev/null \
> | cut -d / -f 3
syslog.conf
anacrontab
[methody@localhost methody]$
Пример 11
. Извлечение отдельного поля
Если команда получается такой длинной, что её неудобно набирать в одну строку, можно разбить её на несколько строк, не передавая системе: для этого в том месте, где нужно продолжить набор со следующей строки, достаточно поставить символ «\
» и нажать Enter. При этом в начале строки bash
выведет символ «>
», означающий, что команда ещё не передана системе и набор продолжается.
Вид этого приглашения определяется значением переменной окружения «PS2
», описанной в лекции Возможности командной оболочки.
Для системы безразлично, в сколько строк набрана команда, возможность набирать в несколько строк нужна только для удобства пользователя. Мефодий получил нужный результат, задав параметры find
— каталог, где нужно искать и параметр поиска — наибольшую допустимую глубину вложенности и группу, которой должны принадлежать найденные файлы. Ненужные диагностические сообщения о запрещённом доступе он отправил в /dev/null
, а потом указал утилите cut
, что в полученных со стандартного ввода строках нужно считать разделителем символ «/
» и вывести только третье поле. Таким образом от строк вида «/etc/filename
» осталось только «filename
».
Как уже указывалось в разделе Структурные единицы текста, первым полем считается текст от начала строки до первого разделителя, в приведённом примере первое поле — пусто, «etc
» — содержимое второго поля, и т. д.
Выбор нужного
Поиск
Зачастую пользователю нужно найти только упоминания чего-то конкретного среди данных, выводимых утилитой. Обычно эта задача сводится к поиску строк, в которых встречается определённое слово или комбинация символов. Для этого подходит стандартная утилита grep
. grep
может искать строку в файлах, а может работать и как фильтр: получив строки со стандартного ввода, она выведет на стандартный вывод только те строки, где встретилось искомое сочетание символов. Мефодий решил поинтересоваться процессами bash
, которые выполняются в системе:
[methody@susanin methody]$ ps aux | grep bash
methody 3459 0.0 3.0 2524 1636 tty2 S 14:30 0:00 -bash
methody 3734 0.0 1.1 1644 612 tty2 S 14:50 0:00 grep bash
Пример 12. Поиск строки в выводе программы
Первый аргумент команды grep
— та строка, которую нужно искать в стандартном вводе, в данном случае это «bash
», а поскольку ps
выводит сведения по строке на каждый процесс, то Мефодий получил только процессы, в имени которых есть «bash
». Однако Мефодий неожиданно нашёл больше, чем искал: в списке выполняющихся процессов присутствовали две строки, в которых встретилось слово «bash
», т. е. два процесса: один — искомый — командный интерпретатор bash
, а другой — процесс поиска строки «grep bash
», запущенный Мефодием после ps
. Это произошло потому, что после разбора командной строки bash
запустил оба дочерних процесса, чтобы организовать конвейер, и на момент выполнения команды ps
процесс grep bash
уже был запущен и тоже попал в вывод ps
. Чтобы в этом примере получить правильный результат, Мефодию следовало бы добавить в конвейер ещё одно звено: | grep -v grep
, эта команда исключит из конечного вывода все строки, в которых встречается «grep
».
Поиск по регулярному выражению
Очень часто точно не известно, какую именно комбинацию символов нужно будет найти. Точнее, известно только то, как примерно должно выглядеть искомое слово, что в него должно входить и в каком порядке. Так обычно бывает, если некоторые фрагменты текста имеют строго определённый формат. Например, в руководствах, выводимых программой info, принят такой формат ссылок: «*Note название_узла::
». В этом случае нужно искать не конкретное сочетание символов, а «Строку «*Note
», за которой следует название узла (одно или несколько слов и пробелов), оканчивающееся символами «::»
». Компьютер вполне способен выполнить такой запрос, если его сформулировать на строгом и понятном ему языке, например, на языке регулярных выражений. Регулярное выражение — это способ одной формулой задать все последовательности символов, подходящие пользователю.
[methody@susanin methody]$ info grep > grep.info 2> /dev/null
[methody@susanin methody]$ grep -on "\*Note[^:]*::" grep.info
324:*Note Grep Programs::
684:*Note Invoking::
[methody@susanin methody]$
Пример 13. Поиск ссылок в файле info
Первый параметр grep
, который взят в кавычки — это и есть регулярное выражение для поиска ссылок в формате info
, второй параметр — имя файла, в котором нужно искать. Ключ «-o
» заставляет grep
выводить строку не целиком, а только ту часть, которая совпала с регулярным выражением (шаблоном поиска), а «-n
» — выводить номер строки, в которой встретилось данное совпадение.
В регулярном выражении большинство символов обозначают сами себя, как если бы мы искали обыкновенную текстовую строку, например, «Note
» и «::
» в регулярном выражении соответствуют строкам «Note
» и «::
» в тексте. Однако некоторые символы обладают специальным значением, самый главный из таких символов — звёздочка. «*
», поставленная после элемента регулярного выражения обозначает, что могут быть найдены тексты, где этот элемент повторён любое количество раз, в том числе и ни одного, т. е. просто отсутствует. В нашем примере звёздочка встретилась дважды: в первый раз потребовалось включить в регулярное выражение именно символ «звёздочка», для этого потребовалось лишить его специального значения, поставив перед ним «\
».
Вторая звёздочка обозначает, что стоящий перед ней элемент может быть повторён любое количество раз от нуля до бесконечности. В нашем случае звёздочка относится к выражению в квадратных скобках — «[^:]
», что означает «любой символ, кроме «:»
». Всё регулярное выражение можно прочесть так: «Строка «*Note
», за которой следует ноль или больше любых символов, кроме «:
», за которыми следует строка «::»
». Особенность работы «*
» состоит в том, что она пытается выбрать совпадение максимальной длины. Именно поэтому элемент, к которому относилась «*
», был задан как «не «:»
». Выражение «ноль или более любых символов» (оно записывается как «.*
») в случае, когда, например, в одной строке встречается две ссылки, вбирает подстроку от конца первого «*Note
» до начала последнего «::
» (символы «:
», поместившиеся внутри этой подстроки, отлично распознаются как «любые»).
На языке регулярных выражений можно также обозначить «любой символ» («.
»), «одно или более совпадений» («+
»), начало и конец строки («^
» и «$
» соответственно) и т. д. Благодаря регулярным выражениям можно автоматизировать очень многие задачи, которые в противном случае потребовали бы огромной и кропотливой работы человека. Более подробные сведения о возможностях языка регулярных выражений можно получить из руководства regex(7)
. Однако руководство — это не учебник по использованию, поэтому чтобы научиться экономить время и усилия при помощи регулярных выражений, полезно прочесть соответствующие главы книги [Курячий:2004] и книгу [Фридл:2000].
Регулярные выражения в Linux используются не только для поиска программой grep
. Очень многие программы, так или иначе работающие с текстом, в первую очередь текстовые редакторы, поддерживают регулярные выражения. К таким программам относятся два «главных» текстовых редактора Linux — Vi и Emacs, о которых речь пойдёт в следующей лекции (Текстовые редакторы). Однако нужно учитывать, что в разных программах используются разные диалекты языка регулярных выражений, где одни и те же понятия имеют разные обозначения, поэтому всегда нужно обращаться к руководству по конкретной программе.
В заключение можно сказать, что регулярные выражения позволяют резко повысить эффективность работы, хорошо интегрированы в рабочую среду в системе Linux, и есть смысл потратить время на их изучение.
Замены
Удобство работы с потоком не в последнюю очередь состоит в том, что можно не только выборочно передавать результаты работы программ, но и автоматически заменять один текст другим прямо в потоке.
Для замены одних символов на другие предназначена утилита tr
(сокращение от англ. «translate», «преобразовывать, переводить»), работающая как фильтр. Мефодий решил употребить её прямо по назначению и выполнить при её помощи транслитерацию — замену латинских символов близкими по звучанию русскими.
[methody@localhost methody]$ cat cat.info | tr abcdefghijklmnopqrstuvwxyz абцдефгхийклмнопкрстуввсиз \
> | tr ABCDEFGHIJKLMNOPRSTUVWXYZ АБЦДЕФГХИЙКЛМНОПКРСТУВВСИЗ | head -4
Филе: цореутилс.инфо, Ноде: цат инвоцатион, Нест: тац инвоцатион, Тп: Оутпут оф ентире филес
`цат': Цонцатенате анд врите филес
==================================
[methody@localhost methody]$
Пример 14. Замена символов (транслитерация)
Мефодий потрудился, составляя два параметра для утилиты tr
: соответствия латинских букв кириллическим. Первый символ из первого параметра tr
заменяет первым символом второго, второй — вторым и т. д. Мефодий обработал поток фильтром tr
дважды: сначала чтобы заменить строчные буквы, а затем — прописные, он мог бы сделать это и за один проход (просто добавив к параметрам прописные после строчных), но не захотел выписывать столь длинные строки. Полученному на выходе тексту вряд ли можно найти практическое применение, однако транслитерацию можно употребить и с пользой. Если не указать tr
второго параметра, то все символы, перечисленные в первом, будут заменены на «ничто», т. е. попросту удалены из потока. При помощи tr
можно также удалить дублирующиеся символы (например, лишние проблелы или переводы строки), заменить пробелы переводами строк и т. п.
Помимо простой замены отдельных символов, возможна замена последовательностей (слов). Специально для этого предназначен потоковый редактор sed
(сокращение от англ. «stream editor»). Он работает как фильтр и выполняет редактирование поступающих строк: замену одних последовательностей символов на другие, причём можно заменять и регулярные выражения.
Например, Мефодий с помощью sed
может сделать более понятным для непривычного читателя список файлов, выводимый ls
:
[methody@localhost methody]$ ls -l | sed s/^-[-rwx]*/Файл:/ | sed s/^d[-rwx]*/Каталог:/
итого 124
Файл: 1 methody methody 2693 Ноя 15 16:09 cat.info
Файл: 1 methody methody 69 Ноя 15 16:08 cat.stderr
Каталог: 2 methody methody 4096 Ноя 15 12:56 Documents
Каталог: 3 methody methody 4096 Ноя 15 13:08 examples
Файл: 1 methody methody 83459 Ноя 15 16:11 grep.info
Файл: 1 methody methody 26 Ноя 15 13:08 loop
Файл: 1 methody methody 23 Ноя 15 13:08 script
Файл: 1 methody methody 33 Ноя 15 16:07 textfile
Каталог: 2 methody methody 4096 Ноя 15 12:56 tmp
Файл: 1 methody methody 32 Ноя 15 13:08 to.sort
[methody@oblomov methody]$
Пример 15. Замена по регулярному выражению
У sed
очень широкие возможности, но довольно непривычный синтаксис, например, замена выполняется командой «s/что_заменять/на_что_заменять/
». Чтобы в нём разобраться, нужно обязательно прочесть руководство sed(1)
и знать регулярные выражения.
Упорядочение
Для того, чтобы разобраться в данных, нередко требуется их упорядочить: по алфавиту, по номеру, по количеству употреблений. Основной инструмент для упорядочивания — утилита sort
— уже знакома Мефодию. Однако теперь он решил использовать её в сочетании с несколькими другими утилитами:
[methody@localhost methody]$ cat grep.info | tr "[:upper:]" "[:lower:]" | tr "[:space:][:punct:]" "\n" \
> | sort | uniq -c | sort -nr | head -8
15233
720 the
342 of
251 to
244 a
213 and
180 or
180 is
[methody@localhost methody]$
Пример 16. Получение упорядоченного по частотности списка словоупотреблений
Мефодий (вернее, компьютер по плану Мефодия) подсчитал, сколько раз какие слова были употреблены в файле «grep.info
» и вывел несколько самых частотных с указанием количества употреблений в файле. Для этого потребовалось сначала заменить все большие буквы маленькими, чтобы не было разных способов написания одного слова, затем заменить все пробелы и знаки препинания концом строки (символ «n
»), чтобы в каждой строке было ровно по одному слову (Мефодий всюду взял параметры tr
в кавычки, чтобы bash
не понял их неправильно). Потом список был отсортирован, все повторяющиеся слова заменены одним словом с указанием количества повторений («uniq -c
»), затем строки снова отсортированы по убыванию чисел в начале строки («sort -nr
») и выведены первые 8 строк («head -8
»).
Запуск команд
Полученные в конвейере данные можно превратить в руководство к действию для компьютера. Например, для каждой полученной со стандартного ввода строки можно запустить какую-нибудь команду, передав ей эту строку в качестве параметра. Для этой цели служит утилита xargs
.
[methody@localhost methody]$ find /bin -type f -perm +a=x \
> | xargs grep -l -e '^#!/' 2> /dev/null
/bin/egrep
/bin/fgrep
/bin/unicode_start
/bin/bootanim
[methody@localhost methody]$
Пример 17. Поиск всех исполняемых файлов, которые точно являются сценариями
Здесь Мефодий решил определить, какие из исполняемых файлов в каталоге «/bin
» являются сценариями. Для этого он нашёл все обычные исполняемые файлы (указывать «-type f
» — «обычные файлы» потребовалось, чтобы в результат не попали каталоги, которые все исполняемые), а затем для каждого найденного файла вызвал grep
, чтобы поискать в нём сочетание символов «#!/
» в начале строки. Ключ «-l
» велел grep
выводить не обнаруженную строку, а имя файла, в котором найдено совпадение. Так Мефодий получил список исполняемых файлов, в которых есть строка с указанием интерпретатора — несомненных сценариев.
Возможны сценарии, в которых не указана программа-интерпретатор, но для автоматического обнаружения такого сценария потребуются более сложные инструменты.