Что такое grep и с чем его едят

от автора

Эта заметка навеяна мелькавшими последнее время на хабре постами двух тематик — «интересные команды unix» и «как я подбирал программиста». И описываемые там команды, конечно, местами интересные, но редко практически полезные, а выясняется, что реально полезным инструментарием мы пользоваться и не умеем.
Небольшое лирическое отступление:
Года три назад меня попросили провести собеседование с претендентами на должность unix-сисадмина. На двух крупнейших на тот момент фриланс-биржах на вакансию откликнулись восемь претендентов, двое из которых входили в ТОП-5 рейтинга этих бирж. Я никогда не требую от админов знания наизусть конфигов и считаю, что нужный софт всегда освоится, если есть желание читать, логика в действиях и умение правильно пользоваться инструментарием системы. Посему для начала претендентам было даны две задачки, примерно такого плана:
— поместить задание в крон, которое будет выполняться в каждый чётный час и в 3 часа;
— распечатать из файла /var/run/dmesg.boot информацию о процессоре.
К моему удивлению никто из претендентов с обеими вопросами не справился. Двое, в принципе, не знали о существовании grep.
image
Поэтому… Лето… Пятница… Перед шашлыками немного поговорим о grep.

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

# grep --version | grep grep grep (GNU grep) 2.5.1-FreeBSD 

Это важно в связи с

# man grep | grep -iB 2 freebsd        -P, --perl-regexp               Interpret PATTERN as a Perl regular expression.  This option  is               not supported in FreeBSD. 

Для начала о том как мы обычно grep’аем файлы.
Используя cat:

root@nm3:/ # cat /var/run/dmesg.boot | grep CPU: CPU: Intel(R) Core(TM)2 Quad CPU    Q9550  @ 2.83GHz (2833.07-MHz K8-class CPU) 

Но зачем? Ведь можно и так:

root@nm3:/ # grep CPU: /var/run/dmesg.boot CPU: Intel(R) Core(TM)2 Quad CPU    Q9550  @ 2.83GHz (2833.07-MHz K8-class CPU) 

Или вот так (ненавижу такую конструкцию):

root@nm3:/ # </var/run/dmesg.boot grep CPU: CPU: Intel(R) Core(TM)2 Quad CPU    Q9550  @ 2.83GHz (2833.07-MHz K8-class CPU) 

Зачем-то считаем отобранные строки с помощью wc:

root@nm3:/ # grep WARNING /var/run/dmesg.boot | wc -l        3 

Хотя можно:

root@nm3:/ # grep WARNING /var/run/dmesg.boot -c 3 

Сделаем тестовый файлик:

test.txt

root@nm3:/ # grep ".*" test.txt one two three seven eight one eight three thirteen fourteen fifteen   sixteen seventeen eighteen seven sixteen seventeen eighteen         twenty seven one 504 one one 503 one one     504     one one     504 one #comment UP twentyseven         #comment down twenty1 twenty3 twenty5 twenty7  

И приступим к поискам:
Опция -w позволяет искать по слову целиком:

root@nm3:/ # grep -w 'seven' test.txt seven eight one eight three  sixteen seventeen eighteen seven         twenty seven 

А если нужно по началу или концу слова?

root@nm3:/ # grep '\<seven' test.txt seven eight one eight three  sixteen seventeen eighteen seven sixteen seventeen eighteen         twenty seven root@nm3:/ # grep 'seven\>' test.txt seven eight one eight three  sixteen seventeen eighteen seven         twenty seven twentyseven 

Стоящие в начале или конце строки?

root@nm3:/ # grep '^seven' test.txt seven eight one eight three root@nm3:/ # grep 'seven$' test.txt  sixteen seventeen eighteen seven         twenty seven twentyseven root@nm3:/ # 

Хотите увидеть строки в в окрестности искомой?

root@nm3:/ # grep -C 1 twentyseven test.txt #comment UP twentyseven         #comment down 

Только снизу или сверху?

root@nm3:/ # grep -A 1 twentyseven test.txt twentyseven         #comment down root@nm3:/ # grep -B 1 twentyseven test.txt #comment UP twentyseven 

А ещё мы умеем так

root@nm3:/ # grep "twenty[1-4]" test.txt twenty1 twenty3 

И наоборот исключая эти

root@nm3:/ # grep "twenty[^1-4]" test.txt         twenty seven twentyseven twenty5 twenty7 

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

root@nm3:/ # cat /etc/resolv.conf #options edns0 #nameserver 127.0.0.1 nameserver 8.8.8.8 nameserver 77.88.8.8 nameserver 8.8.4.4 

Отбираем только строки с ip:

root@nm3:/ # grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" /etc/resolv.conf #nameserver 127.0.0.1 nameserver 8.8.8.8 nameserver 77.88.8.8 nameserver 8.8.4.4 

Работает, но так симпатичнее:

root@nm3:/ # grep -E '\b[0-9]{1,3}(\.[0-9]{1,3}){3}\b' /etc/resolv.conf #nameserver 127.0.0.1 nameserver 8.8.8.8 nameserver 77.88.8.8 nameserver 8.8.4.4 

Уберём строку с комментарием?

root@nm3:/ # grep -E '\b[0-9]{1,3}(\.[0-9]{1,3}){3}\b' /etc/resolv.conf | grep -v # nameserver 8.8.8.8 nameserver 77.88.8.8 nameserver 8.8.4.4 

А теперь выберем только сами ip

root@nm3:/ # grep -oE '\b[0-9]{1,3}(\.[0-9]{1,3}){3}\b' /etc/resolv.conf | grep -v # 127.0.0.1 8.8.8.8 77.88.8.8 8.8.4.4 

Вот незадача… Закомментированная строка вернулась. Это связано с особенностью обработки шаблонов. Как быть? Вот так:

root@nm3:/ # grep -v # /etc/resolv.conf | grep -oE '\b[0-9]{1,3}(\.[0-9]{1,3}){3}\b' 8.8.8.8 77.88.8.8 8.8.4.4 

Здесь остановимся на инвертировании поиска ключом -v
Допустим нам нужно выполнить «ps -afx | grep ttyv»

root@nm3:/ # ps -afx | grep ttyv  1269 v1  Is+       0:00.00 /usr/libexec/getty Pc ttyv1  1270 v2  Is+       0:00.00 /usr/libexec/getty Pc ttyv2  1271 v3  Is+       0:00.00 /usr/libexec/getty Pc ttyv3  1272 v4  Is+       0:00.00 /usr/libexec/getty Pc ttyv4  1273 v5  Is+       0:00.00 /usr/libexec/getty Pc ttyv5  1274 v6  Is+       0:00.00 /usr/libexec/getty Pc ttyv6  1275 v7  Is+       0:00.00 /usr/libexec/getty Pc ttyv7 48798  2  S+        0:00.00 grep ttyv 

Всё бы ничего, но строка «48798 2 S+ 0:00.00 grep ttyv» нам не нужна. Используем -v

root@nm3:/ # ps -afx | grep ttyv | grep -v grep  1269 v1  Is+       0:00.00 /usr/libexec/getty Pc ttyv1  1270 v2  Is+       0:00.00 /usr/libexec/getty Pc ttyv2  1271 v3  Is+       0:00.00 /usr/libexec/getty Pc ttyv3  1272 v4  Is+       0:00.00 /usr/libexec/getty Pc ttyv4  1273 v5  Is+       0:00.00 /usr/libexec/getty Pc ttyv5  1274 v6  Is+       0:00.00 /usr/libexec/getty Pc ttyv6  1275 v7  Is+       0:00.00 /usr/libexec/getty Pc ttyv7 

Некрасивая конструкция? Потрюкачим немного:

root@nm3:/ # ps -afx | grep "[t]tyv"  1269 v1  Is+       0:00.00 /usr/libexec/getty Pc ttyv1  1270 v2  Is+       0:00.00 /usr/libexec/getty Pc ttyv2  1271 v3  Is+       0:00.00 /usr/libexec/getty Pc ttyv3  1272 v4  Is+       0:00.00 /usr/libexec/getty Pc ttyv4  1273 v5  Is+       0:00.00 /usr/libexec/getty Pc ttyv5  1274 v6  Is+       0:00.00 /usr/libexec/getty Pc ttyv6  1275 v7  Is+       0:00.00 /usr/libexec/getty Pc ttyv7 

Также не забываем про | (ИЛИ)

root@nm3:/ # vmstat -z | grep -E "(sock|ITEM)" ITEM                   SIZE  LIMIT     USED     FREE      REQ FAIL SLEEP socket:                 696, 130295,      30,      65,   43764,   0,   0 

ну и тоже самое, иначе:

root@nm3:/ # vmstat -z | grep "sock\|ITEM" ITEM                   SIZE  LIMIT     USED     FREE      REQ FAIL SLEEP socket:                 696, 130295,      30,      65,   43825,   0,   0 

Ну и если о использовании регулярок в grep’e помнят многие, то об использовании POSIX классов как-то забывают, а это тоже иногда удобно.

POSIX

[:alpha:] Any alphabetical character, regardless of case
[:digit:] Any numerical character
[:alnum:] Any alphabetical or numerical character
[:blank:] Space or tab characters
[:xdigit:] Hexadecimal characters; any number or A–F or a–f
[:punct:] Any punctuation symbol
[:print:] Any printable character (not control characters)
[:space:] Any whitespace character
[:graph:] Exclude whitespace characters
[:upper:] Any uppercase letter
[:lower:] Any lowercase letter
[:cntrl:] Control characters

Отберём строки с заглавными символами:

root@nm3:/ # grep "[[:upper:]]" test.txt #comment UP 

Плохо видно что нашли? Подсветим:
image

Ну и ещё пару трюков для затравки.
Первый скорее академичный. За лет 15 ни разу его не использовал:
Нужно из нашего тестового файла выбрать строки содержащие six или seven или eight:
Пока всё просто:

root@nm3:/ # grep -E "(six|seven|eight)" test.txt seven eight one eight three  sixteen seventeen eighteen seven sixteen seventeen eighteen         twenty seven twentyseven 

А теперь только те строки в которых six или seven или eight встречаются несколько раз. Эта фишка именуется Backreferences

root@nm3:/ # grep -E "(six|seven|eight).*\1" test.txt seven eight one eight three  sixteen seventeen eighteen seven 

Ну и второй трюк, куда более полезный. Необходимо вывести строки в которых 504 с обеих сторон ограничено табуляцией.
Ох как тут не хватает поддержки PCRE…
Использование POSIX-классов не спасает:

root@nm3:/ # grep "[[:blank:]]504[[:blank:]]" test.txt one 504 one one     504     one one     504 one 

На помощь приходит конструкция [CTRL+V][TAB]:

root@nm3:/ # grep "     504     " test.txt one     504     one 

Что ещё не сказал? Разумеется, grep умеет искать в файлах/каталогах и, разумеется, рекурсивно. Найдём в исходниках код, где разрешается использование Intel’ом сторонних SFP-шек. Как пишется allow_unsupported_sfp или unsupported_allow_sfp не помню. Ну да и ладно — это проблемы grep’а:

root@nm3:/ # grep -rni allow /usr/src/sys/dev/ | grep unsupp /usr/src/sys/dev/ixgbe/README:75:of unsupported modules by setting the static variable 'allow_unsupported_sfp' /usr/src/sys/dev/ixgbe/ixgbe.c:322:static int allow_unsupported_sfp = TRUE; /usr/src/sys/dev/ixgbe/ixgbe.c:323:TUNABLE_INT("hw.ixgbe.unsupported_sfp", &allow_unsupported_sfp); /usr/src/sys/dev/ixgbe/ixgbe.c:542:     hw->allow_unsupported_sfp = allow_unsupported_sfp; /usr/src/sys/dev/ixgbe/ixgbe_type.h:3249:       bool allow_unsupported_sfp; /usr/src/sys/dev/ixgbe/ixgbe_phy.c:1228:                                if (hw->allow_unsupported_sfp == TRUE) { 

Надеюсь не утомил. И это была только вершина айсберга grep. Приятного Вам чтения, а мне аппетита на шашлыках!
Ну и удачного Вам grep’a!

ссылка на оригинал статьи http://habrahabr.ru/post/229501/


Комментарии

Добавить комментарий

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