Утилита grep.

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

grep [options] PATTERN [FILE...]

где PATTERN — регулярное выражение, а FILE — один или несколько файлов, к содержимому которых будет применено это регулярное выражение.

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

Рассмотрим некоторые примеры использования grep и регулярных выражений. Как говорилось в предыдущей лабораторной работе, команда ls выводит список файлов в каталоге. Команда ls /bin выведет список файлов из каталога /bin. Вывод команда ls осуществляет в stdout.

Предположим, нас интересуют те программы (файлы) из /bin, которые содержат подстроку zip. Этой подстроке соответствует простейшее регулярное выражение «zip». Перенаправляем вывод из ls в grep и получаем:

$ ls /bin | grep 'zip'

bunzip2

bzip2

bzip2recover

gunzip

gzip

Здесь регулярное выражение заключено в одиночные кавычки '', которые указывают bash, что внутри них — обычная строка. Такой синтаксис позволяет использовать в регулярном выражении пробелы, и его разумно придерживаться во всех случаях (например, регулярное выражение 'a b' описывает шаблон для строк, содержащих последовательно a, пробел и b. Если этот шаблон указать grep без кавычек, т.е. grep a b, то командный интерпертатор оболочки, разобрав строку, вызовет grep с двумя параметрами, и grep будет искать строки
с буквами а в файле b. При использовании кавычек командный интерпретатор будет считать выражение 'a b' одним параметром, и передаст его grep целиком, вместе с пробелом внутри).

Файлы из /bin, которые кончаются на 2:

$ ls /bin | grep '2$'

bash2

bunzip2

bzip2

Файлы из /bin, которые начинаются на b:

$ ls /bin | grep '^b'

basename

bash

bash2

bunzip2

bzcat

bzip2

bzip2recover

Файлы из /bin, начинающиеся на b и содержащие в своём имени букву a:

$ ls /bin | grep '^b.*a'

basename

bash

bash2

bzcat

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

• должно совпадать с началом строки — ^

• в начале строки должна быть буква b — ^b

• дальше может быть любой символ — ^b.

• и таких символов может быть сколько угодно — 0 или больше — ^b.*

• а дальше должна быть буква a — ^b.*a

Файлы из /bin, начинающиеся на b и содержащие в своём имени буквы a, e или k:

$ ls /bin | grep '^b.*[aek]'

basename

bash

bash2

bzcat

bzip2recover

Здесь используется описание набора символов — [aek].

Рассмотрим более полезный пример.

На предыдущей лабораторной работе производилась настройка сервера lighttpd. Его конфигурационный файл — /etc/lighttpd/lighttpd.conf. Как было видно, в нём (как и в большинстве других конфигурационных файлов) содержится большое количество комментариев, как с поясняющим текстом, так и с примерами различных опций настройки. Предположим, нам нужно посмотреть текущую конфигурацию сервера. Однако посмотреть её простой командой cat /etc/lighttpd/lighttpd.conf неудобно — текст не помещается на экране. Мы можем, конечно, использовать команду less для прокрутки текста, но комментарии при этом всё равно будут мешать. Мы можем удалить их из файла, но тогда сложно будет что-либо изменять в нём в дальнейшем.

Проще отфильтровать ненужный текст непосредственно при выводе файла на экран.

Комментарии в lighttpd.conf начинаются с символа # (октоторп). Перед ним в начале строки может или не быть ничего, или быть один или несколько пробелов.

Таким образом, регулярное выражение для выделения строк с комментариями — «^ *#»: начало строки, ноль или несколько пробелов, и затем — #.

Кроме того, нас не очень интересуют просто пустые строки, в которых нет никакого текста. Такие строки можно описать выражением «^$»: начало строки, и сразу — её конец. Может быть и другой вариант: строка, состоящая из одних пробелов, которая также не несёт никакой информации. Таким образом, общее регулярное выражение приобретает вид «^ *$».

Итого, строкам комментариев соответствует выражение «^ *#», а пустым строкам — «^ *$». Фильтру grep можно приказать выводить строки, которые не совпадают с регулярным выражением, вызвав его с ключом -v.

Выводим файл lighttpd.conf в stdout и последовательно пропускаем вывод через два фильтра:

# cat /etc/lighttpd/lighttpd.conf | grep -v '^ *#' | grep -v '^ *$'

Этот вариант не очень эффективен, хотя и приносит желаемый результат. Можно избежать двух последовательных вызовов grep, объединив шаблоны. Видно, что они очень похожи: возможные пробелы в начале строки и или # (октоторп), или конец строки. Т.е. общий шаблон — «^ *(#|$)».

grep поддерживает несколько вариантов синтаксиса регулярных выражений и в варианте по умолчанию рассматривает круглые скобки как обычные символы. Поэтому надо или приказать grep'у рассматривать их как оператор выбора, экранировав скобки символом \ (обратный слеш), или переключить grep в режим работы с расширенным синтаксисом регулярных выражений, вызвав его с ключом -E, или использовать версию grep с включённой по умолчанию поддержкой расширенных регулярных выражений — egrep:

# cat /etc/lighttpd/lighttpd.conf | grep -v '^ *\(#\|$\)'

# cat /etc/lighttpd/lighttpd.conf | grep -E -v '^ *(#|$)'

# cat /etc/lighttpd/lighttpd.conf | egrep -v '^ *(#|$)'

Ну и наконец, нам не обязательно передавать файл lighttpd.conf на стандарный вход grep/egrep, эти утилиты могут сами прочитать файл с диска:

# egrep -v '^ *(#|$)' /etc/lighttpd/lighttpd.conf