Иногда может понадобится найти файл, в котором содержится определённая строка или найти строку в файле, где есть нужное слово. Это может понадобится для поиска логов, поиска конфигурационных файлов, если вы не знаете где они находятся или для поиска файлов с программным кодом.
Раньше для этих целей использовалась только утилита grep, однако сейчас существует огромное количество других программ, которые могут делать это быстрее и предоставляют более удобный интерфейс. В этой статье мы рассмотрим самые интересные из них и несколько примеров как ими пользоваться.
Все утилиты в этой статье работают только в терминале. Если вас интересуют приложения с графическим интерфейсом, посмотрите статью поиск файлов в Linux. Большинство описанных ниже команд имеют примерно одинаковый синтаксис:
$ команда опции паттерн /путь/к/папке
Паттерн для поиска может быть обычным словом, но большинство команд ожидают, что это будет регулярное выражение, поэтому если в вашем запросе есть спец. символы, вы можете получить в результате не то, что ожидаете. Путь к папке, как правило, указывать не обязательно, тогда команды используют текущую папку для поиска. А теперь давайте рассмотрим каждую команду подробнее.
1. Grep
Прежде чем перейти к современным утилитам, давайте рассмотрим команду grep. Она входит в набор программ GNU и используется для поиска строк в файлах и файлов по их содержимому уже очень давно.
По умолчанию grep фильтрует один файл или стандартный ввод. Для того чтобы выполнять поиск в папке по нескольким файлам, нужно включить рекурсивный поиск с помощью опции -r. Например, найдем все файлы в папке /etc/, которые содержат строку root:
sudo grep -r "root" /etc/
Команда grep не подсвечивает вхождения символов которое вы искали цветом, для этого можно использовать опцию —color=always. Но в большинстве дистрибутивов эта опция уже прописана в алиасе для этой команды, поэтому вывод будет выглядеть вот так:
sudo grep --color=always -r "root" /etc/
С помощью опции -С или —context вы можете включить отображение не только текущей строки, в которой было найдено вхождение но и нескольких строк до и после. В этом параметре надо указать количество строк для отображения:
sudo grep -r -С2 "root" /etc/
По умолчанию grep ожидает, что поисковый запрос может быть регулярным выражением, но поддерживается только базовый синтаксис. Для включения расширенного синтаксиса нужно использовать опцию -E. Например, для того чтобы найти все файлы содержащие переменные, начинающиеся на букву A в папке /etc/ выполните:
sudo grep -r -E "^A[A-Z_]+=" /etc/
А для того чтобы искать именно фиксированную строку, а не регулярное выражение используйте опцию -F или команду fgrep. Например, так можно найти все файлы содержащие секцию [Install] в папке /usr/:
sudo grep -r -F "[Install]" /usr/
2. Ripgrep
Это популярная альтернатива grep написанная на Rust. Она может делать всё то же самое что и grep, но быстрее, а ещё её гораздо удобнее использовать. Рекурсивный поиск включён по умолчанию, и подсветка вхождений и имени файла разными цветами тоже работает без дополнительных опций, а также она пропускает скрытые файлы, бинарные файлы и файлы, перечисленные в .gitignore. Для установки ripgrep в Ubuntu используйте такую команду:
sudo apt install ripgrep
Если вы выполните команду, указав путь к файлу, то она найдёт и отобразит все вхождения искомого слова в этом файле. Например:
sudo rg root /etc/passwd
Если же передать папку, то команда будет искать во всех файлах, которые находятся в этой папке. По умолчанию используется текущая папка. Например, для того чтобы найти все файлы, содержащие слово «root» в каталоге /etc/ используйте такую команду:
sudo rg root /etc/
Также, как и при использовании grep, можно отобразить не только строку со вхождением но и несколько строк до и после. Например, по две:
sudo rg -C2 root /etc/
А вот полный синтаксис регулярных выражений здесь работает по умолчанию. Например:
sudo rg "^A[A-Z_]+=" /etc/
Если же вы хотите указать что надо искать именно строку, а не регулярное выражение, используйте опцию -F:
sudo rg -F "[Install]" /usr/
3. Ack
Если вам нужно найти файл с исходным кодом зная строку, которая в нём есть, то для этого существуют более подходящие утилиты чем grep. Например, ack. Она появилась уже довольно давно и предназначена именно для работы с исходным кодом. Кроме всех возможностей grep она позволяет пропускать файлы резервных копий, внутренние файлы репозиториев .git и .svn, а также дампы памяти. Кроме того, вы можете выбрать типы файлов, в которых будет выполняться поиск и даже указать определённую часть файла. Для установки программы в Ubuntu используйте такую команду:
sudo apt install ack
Самый простой пример — поиск всех файлов содержащих слово root в папке /etc/:
sudo ack "root" /etc/
Или же регулярное выражение, как в предыдущем разделе для поиска файлов с переменными, начинающимися на букву A:
sudo ack "^A[A-Z_]+=" /etc/
Для того чтобы вывести не только строку с вхождением, но и строки до и после неё используйте опцию -C. По умолчанию выводится по две строки:
sudo ack -C "root" /etc/
Команда ack позволяет указать тип файлов, в которых надо выполнять поиск. Это очень удобно для поиска по исходникам. Вы можете выбрать только исходные файлы Си, JavaScript или PHP и так далее. Все доступные типы файлов можно посмотреть с помощью команды:
ack --help-types
Например, для того чтобы выполнять поиск только в XML файлах используйте опцию —type со значением xml:
sudo ack --type=xml "root" /etc/
Ещё одна интересная возможность утилиты ack — настройка частей файла, в которых будет выполняться поиск с помощью регулярного выражения. Для этого предназначены опции —range-start и —range-end и это будет работать как в рамках одной строки так и в рамках всего файла. Например, для поиска только в комментариях XML файлов можно использовать такую команду:
sudo ack --type=xml --range-start="<!--" --range-end="-->" "root" /etc/
4. Sliver Searcher
На данный момент это одна из самых популярных программ для поиска текста по файлам в Linux. Она была спроектирована в качестве альтернативы для ack, как инструмент для поиска кода. В дополнение к основным возможностям ack, она значительно быстрее и учитывает настройки исключений из файлов .gitignore и .hgignore. Для установки программы в Ubuntu используйте такую команду:
sudo apt install silversearcher-ag
Рассмотрим тот же пример, что и в предыдущих разделах. Для того чтобы найти все файлы содержащие слово root в папке /etc/ выполните:
sudo ag "root" /etc/
Аналогично grep и ripgrep, ag ожидает регулярное выражение в качестве паттерна поиска, поэтому вы можете использовать его без каких либо дополнительных опций. Например:
sudo ag "^A[A-Z_]+=" /etc/
Но если вы хотите чтобы поисковый запрос рассматривался именно как строка, используйте опцию -Q. Например, для поиска всех файлов, содержащих секцию [Install] в папке /usr/ выполните:
sudo ag -Q "[Install]" /usr/
Здесь тоже можно вывести несколько строк до и после строки со вхождением с помощью опции -С.
В отличие от grep и ripgrep, команда ag позволяет указать тип файлов в которых вы хотите выполнять поиск. Посмотреть все доступные типы файлов можно с помощью такой команды:
ag --list-file-types
Например, для того чтобы искать только по ini файлам используйте такую команду:
sudo ag --ini "root" /etc/
Утилита также позволяет использовать регулярное выражение для фильтрации файлов по имени перед тем как выполнять поиск по содержимому. Для этого нужно использовать опцию -G. Например для того чтобы выполнять поиск только по файлам, имя которых заканчивается на conf, выполните:
sudo ag -G .*.conf$ root /etc/
5. Skim
Это ещё одна интересная утилита для нечеткого поиска в реальном времени, написанная на Rust. По умолчанию она ищет файлы по имени, но её можно использовать вместе с одной из выше перечисленных утилит для поиска файлов по содержимому в реальном времени. Пакета с программой пока нет в официальных репозиториях, но вы можете установить её с помощью cargo:
cargo install skim
Команда выполняет поиск только в текущей папке и указать другую папку нельзя, поэтому нужно сначала перейти в нужную папку используя команду cd. Например:
cd /etc/
Затем используйте такую команду для того чтобы объединить sk с Silver Searcher для фильтрации файлов по содержимому:
sk --ansi -i -c 'ag --color "{}"'
Здесь опция —ansi включает отображение цветов, -i включает интерактивный режим, а опция -c задает команду, которая будет выполнена при вводе какого-либо запроса, строка {} будет заменена на то, что вы введете при поиске. Просто выполните команду и начинайте вводить слово которое хотите искать:
Дальше вы можете использовать все функции фильтрации нечеткого поиска для обработки результатов.
Производительность grep, rg, ack и ag
Перед завершением статьи хочу привести небольшой тест производительности выше перечисленных утилит для поиска текста по файлам в Linux. Я скачал и распаковал исходники ядра Linux версии 6.2.10, а затем использовал каждую из утилит для того чтобы найти все файлы, которые содержат слово ext4 и btrfs. Между разными утилитами система перезагружалась, для того чтобы исключить влияние кэша. Вот результаты:
Команда | Время, с | Команда | Время, с |
grep -r ext4 ./ | 23.167 | grep -r btrfs ./ | 3.860 |
rg ext4 ./ | 27.164 | rg btrfs ./ | 1.387 |
ack ext4 ./ | 36.141 | ack btrfs ./ | 7.206 |
ag ext4 ./ | 24.594 | ag btrfs ./ | 3.158 |
Как видите, сразу после запуска системы все команды работают примерно одинаково. Однако после того, утилиты, написанные на Rust эффективнее используют кэш, поскольку при повторных запусках для того же набора файлов работают быстрее аналогов на Си.
Выводы
В этой статье были рассмотрены самые часто используемые утилиты для поиска файлов по содержимому и поиска текста в файлах в Linux. Как видите, существует достаточно инструментов чтобы было из чего выбрать. Все команды довольно похожие, но каждая из них имеет свои особенности. А что используете вы? Напишите в комментариях!
Обнаружили ошибку в тексте? Сообщите мне об этом. Выделите текст с ошибкой и нажмите Ctrl+Enter.
Статья распространяется под лицензией Creative Commons ShareAlike 4.0 при копировании материала ссылка на источник обязательна .
Команда grep
означает «печать глобального регулярного выражения», и это одна из самых мощных и часто используемых команд в Linux.
grep
ищет в одном или нескольких входных файлах строки, соответствующие заданному шаблону, и записывает каждую соответствующую строку в стандартный вывод. Если файлы не указаны, grep
читает из стандартного ввода, который обычно является выводом другой команды.
В этой статье мы покажем вам, как использовать команду grep
на практических примерах и подробных объяснениях наиболее распространенных опций GNU grep
.
Командный синтаксис grep
Синтаксис команды grep
следующий:
grep [OPTIONS] PATTERN [FILE...]
Пункты в квадратных скобках необязательны.
OPTIONS
— Ноль или более вариантов. Grep включает ряд опций , управляющих его поведением.-
PATTERN
— Шаблон поиска. -
FILE
— Ноль или более имен входных файлов.
Чтобы иметь возможность искать файл, пользователь, выполняющий команду, должен иметь доступ для чтения к файлу.
Искать строку в файлах
Наиболее простое использование команды grep
— поиск строки (текста) в файле.
Например, чтобы отобразить все строки, содержащие строку bash
из файла /etc/passwd
, вы должны выполнить следующую команду:
grep bash /etc/passwd
Результат должен выглядеть примерно так:
root:x:0:0:root:/root:/bin/bash
linuxize:x:1000:1000:linuxize:/home/linuxize:/bin/bash
Если в строке есть пробелы, вам нужно заключить ее в одинарные или двойные кавычки:
grep "Gnome Display Manager" /etc/passwd
Инвертировать соответствие (исключить)
Чтобы отобразить строки, не соответствующие шаблону, используйте параметр -v
(или --invert-match
).
Например, чтобы распечатать строки, не содержащие строковый nologin
вы должны использовать:
grep -v nologin /etc/passwd
root:x:0:0:root:/root:/bin/bash
colord:x:124:124::/var/lib/colord:/bin/false
git:x:994:994:git daemon user:/:/usr/bin/git-shell
linuxize:x:1000:1000:linuxize:/home/linuxize:/bin/bash
Использование Grep для фильтрации вывода команды
Вывод команды может быть отфильтрован с помощью grep
через конвейер, и на терминал будут напечатаны только строки, соответствующие заданному шаблону.
Например, чтобы узнать, какие процессы выполняются в вашей системе как пользовательские www-data
вы можете использовать следующую команду ps
:
ps -ef | grep www-data
www-data 18247 12675 4 16:00 ? 00:00:00 php-fpm: pool www
root 18272 17714 0 16:00 pts/0 00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn www-data
www-data 31147 12770 0 Oct22 ? 00:05:51 nginx: worker process
www-data 31148 12770 0 Oct22 ? 00:00:00 nginx: cache manager process
Вы также можете объединить несколько каналов по команде. Как вы можете видеть в выходных данных выше, также есть строка, содержащая процесс grep
. Если вы не хотите, чтобы эта строка отображалась, передайте результат другому экземпляру grep
как показано ниже.
ps -ef | grep www-data | grep -v grep
www-data 18247 12675 4 16:00 ? 00:00:00 php-fpm: pool www
www-data 31147 12770 0 Oct22 ? 00:05:51 nginx: worker process
www-data 31148 12770 0 Oct22 ? 00:00:00 nginx: cache manager process
Рекурсивный поиск
Для рекурсивного поиска шаблона вызовите grep
с параметром -r
(или --recursive
). Когда используется этот параметр, grep
будет искать все файлы в указанном каталоге, пропуская символические ссылки, которые встречаются рекурсивно.
Чтобы следовать по всем символическим ссылкам , вместо -r
используйте параметр -R
(или --dereference-recursive
).
Вот пример, показывающий, как искать строку linuxize.com
во всех файлах внутри каталога /etc
:
grep -r linuxize.com /etc
Вывод будет включать совпадающие строки с префиксом полного пути к файлу:
/etc/hosts:127.0.0.1 node2.linuxize.com
/etc/nginx/sites-available/linuxize.com: server_name linuxize.com www.linuxize.com;
Если вы используете опцию -R
, grep
будет следовать по всем символическим ссылкам:
grep -R linuxize.com /etc
Обратите внимание на последнюю строку вывода ниже. Эта строка не печатается, когда grep
вызывается с -r
потому что файлы внутри каталога с sites-enabled
Nginx являются символическими ссылками на файлы конфигурации внутри каталога с sites-available
.
/etc/hosts:127.0.0.1 node2.linuxize.com
/etc/nginx/sites-available/linuxize.com: server_name linuxize.com www.linuxize.com;
/etc/nginx/sites-enabled/linuxize.com: server_name linuxize.com www.linuxize.com;
Показать только имя файла
Чтобы подавить вывод grep
по умолчанию и вывести только имена файлов, содержащих совпадающий шаблон, используйте параметр -l
(или --files-with-matches
).
Приведенная ниже команда выполняет поиск по всем файлам, заканчивающимся на .conf
в текущем рабочем каталоге и выводит только имена файлов, содержащих строку linuxize.com
:
grep -l linuxize.com *.conf
Результат будет выглядеть примерно так:
tmux.conf
haproxy.conf
Параметр -l
обычно используется в сочетании с рекурсивным параметром -R
:
grep -Rl linuxize.com /tmp
Поиск без учета регистра
По умолчанию grep
чувствителен к регистру. Это означает, что символы верхнего и нижнего регистра рассматриваются как разные.
Чтобы игнорировать регистр при поиске, вызовите grep
с параметром -i
(или --ignore-case
).
Например, при поиске Zebra
без какой-либо опции следующая команда не покажет никаких результатов, т.е. есть совпадающие строки:
grep Zebra /usr/share/words
Но если вы выполните поиск без учета регистра с использованием параметра -i
, он будет соответствовать как заглавным, так и строчным буквам:
grep -i Zebra /usr/share/words
Указание «Зебра» будет соответствовать «зебре», «ZEbrA» или любой другой комбинации букв верхнего и нижнего регистра для этой строки.
zebra
zebra's
zebras
Искать полные слова
При поиске строки grep
отобразит все строки, в которых строка встроена в строки большего размера.
Например, если вы ищете «gnu», все строки, в которых «gnu» встроено в слова большего размера, такие как «cygnus» или «magnum», будут найдены:
grep gnu /usr/share/words
cygnus
gnu
interregnum
lgnu9d
lignum
magnum
magnuson
sphagnum
wingnut
Чтобы вернуть только те строки, в которых указанная строка представляет собой целое слово (заключенное в символы, отличные от слов), используйте параметр -w
(или --word-regexp
).
Символы слова включают буквенно-цифровые символы ( az
, AZ
и 0-9
) и символы подчеркивания ( _
). Все остальные символы считаются несловесными символами.
Если вы запустите ту же команду, что и выше, включая параметр -w
, команда grep
вернет только те строки, где gnu
включен как отдельное слово.
grep -w gnu /usr/share/words
gnu
Показать номера строк
Параметр -n
(или --line-number
) указывает grep
показывать номер строки, содержащей строку, соответствующую шаблону. Когда используется эта опция, grep
выводит совпадения на стандартный вывод с префиксом номера строки.
Например, чтобы отобразить строки из файла /etc/services
содержащие строку bash
префиксом совпадающего номера строки, вы можете использовать следующую команду:
grep -n 10000 /etc/services
Результат ниже показывает нам, что совпадения находятся в строках 10423 и 10424.
10423:ndmp 10000/tcp
10424:ndmp 10000/udp
Подсчет совпадений
Чтобы вывести количество совпадающих строк в стандартный вывод, используйте параметр -c
(или --count
).
В приведенном ниже примере мы подсчитываем количество учетных записей, в которых в качестве оболочки используется /usr/bin/zsh
.
regular expression
grep -c '/usr/bin/zsh' /etc/passwd
4
Бесшумный режим
-q
(или --quiet
) указывает grep
работать в тихом режиме, чтобы ничего не отображать на стандартном выводе. Если совпадение найдено, команда завершает работу со статусом 0
. Это полезно при использовании grep
в сценариях оболочки, где вы хотите проверить, содержит ли файл строку, и выполнить определенное действие в зависимости от результата.
Вот пример использования grep
в тихом режиме в качестве тестовой команды в операторе if
:
if grep -q PATTERN filename
then
echo pattern found
else
echo pattern not found
fi
Основное регулярное выражение
GNU Grep имеет три набора функций регулярных выражений : базовый, расширенный и Perl-совместимый.
По умолчанию grep
интерпретирует шаблон как базовое регулярное выражение, где все символы, кроме метасимволов, на самом деле являются регулярными выражениями, которые соответствуют друг другу.
Ниже приведен список наиболее часто используемых метасимволов:
-
Используйте символ
^
(каретка) для сопоставления выражения в начале строки. В следующем примере строкаkangaroo
будет соответствовать только в том случае, если она встречается в самом начале строки.grep "^kangaroo" file.txt
-
Используйте символ
$
(доллар), чтобы найти выражение в конце строки. В следующем примере строкаkangaroo
будет соответствовать только в том случае, если она встречается в самом конце строки.grep "kangaroo$" file.txt
-
Используйте расширение
.
(точка) символ, соответствующий любому одиночному символу. Например, чтобы сопоставить все, что начинается сkan
затем имеет два символа и заканчивается строкойroo
, вы можете использовать следующий шаблон:grep "kan..roo" file.txt
-
Используйте
[ ]
(скобки) для соответствия любому одиночному символу, заключенному в квадратные скобки. Например, найдите строки, содержащиеaccept
или «accent
, вы можете использовать следующий шаблон:grep "acce[np]t" file.txt
-
Используйте
[^ ]
для соответствия любому одиночному символу, не заключенному в квадратные скобки. Следующий шаблон будет соответствовать любой комбинации строк, содержащихco(any_letter_except_l)a
, напримерcoca
,cobalt
и т. Д., Но не будет соответствовать строкам, содержащимcola
,grep "co[^l]a" file.txt
Чтобы избежать специального значения следующего символа, используйте символ (обратная косая черта).
Расширенные регулярные выражения
Чтобы интерпретировать шаблон как расширенное регулярное выражение, используйте параметр -E
(или --extended-regexp
). Расширенные регулярные выражения включают в себя все основные метасимволы, а также дополнительные метасимволы для создания более сложных и мощных шаблонов поиска. Вот несколько примеров:
-
Сопоставьте и извлеките все адреса электронной почты из данного файла:
grep -E -o "b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}b" file.txt
-
Сопоставьте и извлеките все действительные IP-адреса из данного файла:
grep -E -o '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' file.txt
Параметр -o
используется для печати только соответствующей строки.
Поиск нескольких строк (шаблонов)
Два или более шаблонов поиска можно объединить с помощью оператора ИЛИ |
.
По умолчанию grep
интерпретирует шаблон как базовое регулярное выражение, в котором метасимволы, такие как |
теряют свое особое значение, и необходимо использовать их версии с обратной косой чертой.
В приведенном ниже примере мы ищем все вхождения слов fatal
, error
и critical
в файле ошибок журнала Nginx :
grep 'fatal|error|critical' /var/log/nginx/error.log
Если вы используете опцию расширенного регулярного выражения -E
, то оператор |
не следует экранировать, как показано ниже:
grep -E 'fatal|error|critical' /var/log/nginx/error.log
Строки печати перед матчем
Чтобы напечатать определенное количество строк перед совпадающими строками, используйте параметр -B
(или --before-context
).
Например, чтобы отобразить пять строк ведущего контекста перед совпадающими строками, вы должны использовать следующую команду:
grep -B 5 root /etc/passwd
Печатать строки после матча
Чтобы напечатать определенное количество строк после совпадающих строк, используйте параметр -A
(или --after-context
).
Например, чтобы отобразить пять строк конечного контекста после совпадающих строк, вы должны использовать следующую команду:
grep -A 5 root /etc/passwd
Выводы
Команда grep
позволяет искать шаблон внутри файлов. Если совпадение найдено, grep печатает строки, содержащие указанный шаблон.
Подробнее о Grep можно узнать на странице руководства пользователя Grep .
Если у вас есть какие-либо вопросы или отзывы, не стесняйтесь оставлять комментарии.
Posted:
September 23, 2022
Searching for patterns of text in files or text streams is one of the most common tasks you’ll perform in your sysadmin career. This is a valuable skill that allows you to check a variety of system configurations, analyze data, troubleshoot logs, and perform many other activities.
The most common way to find text in a Linux system is using the command-line utility grep
. This utility was originally developed for the Unix operating system in the early 1970s. Grep evolved over the years, and the most common version available today for Linux, GNU grep, has additional features such as colored output. However, its main functionality is still the same.
Using grep
, you can quickly find text matching a regular expression in a single file, a group of files, or text coming from stdin
using the shell pipe operator.
This article covers how to use the grep
command to find text.
Find text in a file
The most basic way to use grep
is searching for text in a single file. To do this, type grep
followed by the text pattern to search for and the file name to search in. For example, to find which port the Secure Shell (SSH) daemon uses, search for Port
in file /etc/ssh/sshd_config
:
$ grep Port /etc/ssh/sshd_config
Port 22
#GatewayPorts no
Notice that grep
finds all lines that match the text pattern regardless of where the pattern is located.
[ Download the Linux grep command cheat sheet. ]
Extend grep with regular expressions
In the previous example, when you searched for Port
in the SSH configuration file, grep
returned two lines. The line you were looking for, Port 22, and an additional line containing the search pattern. In some cases, that’s exactly what you want. In other cases, grep
could find too many entries that you’re not interested in, requiring you to sort through them to find the desired information.
To avoid that, you can use regular expressions to be more specific about what you’re looking for. For example, to find only lines that start with the word Port
, you can use the regular expression operator ^
, like this:
$ grep ^Port /etc/ssh/sshd_config
Port 22
This time grep
returned only the line that started with Port
since, in the second line, the expression Port
is in the middle.
You can also use extended regular expressions with the command-line parameter -E
. For example, to search for a pattern that contains the word Port
followed by numbers, use this regular expression:
$ grep -E "Port [1-9]+" /etc/ssh/sshd_config
Port 22
You can also look for lines that end with a text pattern by using the $
operator. For example, to find all lines that end with none
in sshd_config
, use grep
like this:
$ grep none$ /etc/ssh/sshd_config
#RekeyLimit default none
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#ChrootDirectory none
#VersionAddendum none
#Banner none
Regular expressions are a big part of grep
, making it powerful and flexible. However, regular expressions are a huge topic. For additional information, look at Regular expression on Wikipedia or Regular expressions 101.
Find text in multiple files and directories
Similar to finding text patterns in a single file, you can use grep
to find text in multiple files or directories. To find text in multiple files simultaneously, specify which files to search from after the first file name, or use a shell wildcard such as *
for all files. For example, to search for a configuration in two files:
$ grep Port /etc/ssh/sshd_config /etc/ssh/ssh_config
/etc/ssh/sshd_config:Port 22
/etc/ssh/sshd_config:#GatewayPorts no
/etc/ssh/ssh_config:# Port 22
When you use multiple files, grep
shows the name of the file where it found a match before showing the matched line.
[ Keep your most commonly used commands handy with the Linux commands cheat sheet. ]
To run the search recursively in multiple subdirectories, use the command line flag -R
:
$ grep -R ^Port /etc
/etc/ssh/sshd_config:Port 22
The grep
command is fast and returns results quickly, but it may take a long time if you specify too many files or subdirectories to search.
Find text in another command’s output
Similar to other Unix utilities, grep
also acts on stdin
when you pipe the output of another command into it. This is a fast and useful way to filter a command’s output to match the text pattern you’re looking for.
For example, if you want to check whether the package openssh
is installed in your Fedora or Red Hat Enterprise Linux (RHEL) operating system, you can pipe the output of command rpm -qa
, which lists all installed packages, into grep
to search for the pattern:
$ rpm -qa | grep ssh
libssh-config-0.9.6-4.fc36.noarch
libssh-0.9.6-4.fc36.x86_64
openssh-8.8p1-1.fc36.1.x86_64
You can filter long command outputs with grep
, making finding useful information easier.
[ Get the guide to installing applications on Linux. ]
Additional useful options
The grep
command provides many options to change how it searches for patterns or displays results. So far in this article, you’ve seen some of them. While I can’t list all options, here are some other useful examples:
-
Use option
-i
for a case-insensitive search. - Use option
-v
to invert the search and display lines that do not match the pattern. - Use option
-w
to search for entire words only instead of patterns in the middle of other words. - Use option
--color
for colored output, making it easier to spot the matched pattern.
For a complete list of grep
options, consult the man pages.
What’s next?
The GNU grep utility is flexible and useful, helping you accomplish many tasks in your daily sysadmin activities. The more you use grep
, the more comfortable you will become, and soon you’ll notice you’re relying on it all the time.
For more information about grep
, look at some of these links:
- How to use grep
- Linux grep command cheat sheet
- Grep wiki
- Grep manual at GNU
You can also find more information about grep
in your Linux system by using man grep
or quick, valuable examples with the tldr
tool.
grep
(GNU or BSD)
You can use grep
tool to search recursively the current folder, like:
grep -r "class foo" .
Note: -r
— Recursively search subdirectories.
You can also use globbing syntax to search within specific files such as:
grep "class foo" **/*.c
Note: By using globbing option (**
), it scans all the files recursively with specific extension or pattern. To enable this syntax, run: shopt -s globstar
. You may also use **/*.*
for all files (excluding hidden and without extension) or any other pattern.
If you’ve the error that your argument is too long, consider narrowing down your search, or use find
syntax instead such as:
find . -name "*.php" -execdir grep -nH --color=auto foo {} ';'
Alternatively, use ripgrep
.
ripgrep
If you’re working on larger projects or big files, you should use ripgrep
instead, like:
rg "class foo" .
Checkout the docs, installation steps or source code on the GitHub project page.
It’s much quicker than any other tool like GNU/BSD grep
, ucg
, ag
, sift
, ack
, pt
or similar, since it is built on top of Rust’s regex engine which uses finite automata, SIMD and aggressive literal optimizations to make searching very fast.
It supports ignore patterns specified in .gitignore
files, so a single file path can be matched against multiple glob patterns simultaneously.
You can use common parameters such as:
-i
— Insensitive searching.-I
— Ignore the binary files.-w
— Search for the whole words (in the opposite of partial word matching).-n
— Show the line of your match.-C
/--context
(e.g.-C5
) — Increases context, so you see the surrounding code.--color=auto
— Mark up the matching text.-H
— Displays filename where the text is found.-c
— Displays count of matching lines. Can be combined with-H
.
Здесь представлен фрагмент будущей книги «Основные инструменты и практики для начинающего разработчика программного обеспечения» Бальтазара Рубероля и Этьена Броду. Книга должна помочь образованию подрастающего поколения разработчиков. Она охватит такие темы, как освоение консоли, настройка и эффективная работа в командной оболочке, управление версиями кода с помощью git
, основы SQL, инструменты вроде Make
, jq
и регулярные выражения, основы сетевого взаимодействия, а также лучшие практики разработки программного обеспечения и совместной работы. В настоящее время авторы упорно работают над этим проектом и приглашают всех поучаствовать в списке рассылки.
Содержание
- cat
- head
- tail
- wc
- grep
- cut
- paste
- sort
- uniq
- awk
- tr
- fold
- sed
- Реальные примеры
- Углубляемся: циклы for и xargs
- Итоги
- Что дальше
Обработка текста в командной оболочке
Одна из причин, которые делают командную оболочку бесценным инструментом, — это большое количество команд обработки текста и возможность легко объединять их в конвейер, создавая сложные шаблоны обработки. Эти команды делают тривиальными многие задачи по анализу текста и данных, преобразованию данных между разными форматами, по фильтрации строк и т. д.
При работе с текстовыми данными главный принцип заключается в том, чтобы разбить любую сложную проблему на множество более мелких — и решить каждую из них с помощью специализированного инструмента.
Заставьте каждую программу хорошо выполнять одну функцию — «Основы философии Unix»
Примеры из этой главы на первый взгляд могут показаться немного надуманными, но это сделано специально. Каждый из инструментов разработан для решения одной небольшой задачи. Однако в сочетании они становятся чрезвычайно мощными.
Мы рассмотрим некоторые из наиболее распространенных и полезных команд обработки текста в командной оболочке и продемонстрируем реальные рабочие процессы, соединяющие их вместе. Я предлагаю взглянуть на маны этих команд, чтобы увидеть всю широту возможностей в вашем распоряжении.
Файл CSV с примерами доступен в онлайне. Можете скачать его для проверки материала.
cat
Команда cat
используется для составления списка из одного или нескольких файлов и отображения их содержимого на экране.
$ cat Documents/readme
Thanks again for reading this book!
I hope you're following so far!
$ cat Documents/computers
Computers are not intelligent
They're just fast at making dumb things.
$ cat Documents/readme Documents/computers
Thanks again for reading this book!
I hope you are following so far!
Computers are not intelligent
They're just fast at making dumb things.
head
head
выводит первые n строк в файле. Это может быть очень полезно для того, чтобы заглянуть в файл неизвестной структуры и формата, не заваливая всю консоль кучей текста.
$ head -n 2 metadata.csv
metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name
mysql.galera.wsrep_cluster_size,gauge,,node,,The current number of nodes in the Galera cluster.,0,mysql,galera cluster size
Если -n
не указано, head
выводит первые десять строк указанного файла или входящего потока.
tail
tail
— аналог head
, только он выводит последние n строк в файле.
$ tail -n 1 metadata.csv
mysql.performance.queries,gauge,,query,second,The rate of queries.,0,mysql,queries
Если хотите вывести все строки, расположенном после n-й строки (включая её), можете использовать аргумент -n +n
.
$ tail -n +42 metadata.csv
mysql.replication.slaves_connected,gauge,,,,Number of slaves connected to a replication master.,0,mysql,slaves connected
mysql.performance.queries,gauge,,query,second,The rate of queries.,0,mysql,queries
В нашем файле 43 строки, поэтому tail -n +42
выводит только 42-ю и 43-ю строки из него.
Если параметр -n
не указан, tail
выведет последние десять строк в указанном файле или входном потоке.
tail -f
или tail --follow
отображают последние строки в файле и каждую новую строку по мере записи в файл. Это очень полезно для просмотра активности в реальном времени, например, что записывается в логи веб-сервера и т. д.
wc
wc
(word count) выводит количество символов (-c
), слов (-w
) или строк (-l
) в указанном файле или потоке.
$ wc -l metadata.csv
43 metadata.csv
$ wc -w metadata.csv
405 metadata.csv
$ wc -c metadata.csv
5094 metadata.csv
По умолчанию отображается всё вышеперечисленное.
$ wc metadata.csv
43 405 5094 metadata.csv
Если текстовые данные передаются по конвейеру или перенаправлены в stdin
, то отображается только счётчик.
$ cat metadata.csv | wc
43 405 5094
$ cat metadata.csv | wc -l
43
$ wc -w < metadata.csv
405
grep
grep
— это швейцарский нож фильтрации строк по заданному шаблону.
Например, можем найти все вхождения слова mutex в файле.
$ grep mutex metadata.csv
mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits
mysql.innodb.mutex_spin_rounds,gauge,,event,second,The rate of mutex spin rounds.,0,mysql,mutex spin rounds
mysql.innodb.mutex_spin_waits,gauge,,event,second,The rate of mutex spin waits.,0,mysql,mutex spin waits
grep
может обрабатывать либо файлы, указанные в качестве аргументов, либо поток текста, переданный на его stdin
. Таким образом, мы можем сцеплять несколько команд grep
для дальнейшей фильтрации текста. В следующем примере мы фильтруем строки в нашем файле metadata.csv
, чтобы найти строки, содержащие и mutex, и OS.
$ grep mutex metadata.csv | grep OS
mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits
Рассмотрим некоторые опции grep
и их поведение.
grep -v
выполняет инвертное сопоставление: фильтрует строки, которые не соответствуют шаблону аргументов.
$ grep -v gauge metadata.csv
metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name
grep -i
выполняет сопоставление без учёта регистра. В следующем примере grep -i os
находит как OS, так и os.
$ grep -i os metadata.csv
mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits
mysql.innodb.os_log_fsyncs,gauge,,write,second,The rate of fsync writes to the log file.,0,mysql,log fsyncs
grep -l
выводит список файлов, содержащих совпадение.
$ grep -l mysql metadata.csv
metadata.csv
Команда grep -c
подсчитывает, сколько раз найден образец.
$ grep -c select metadata.csv
3
grep -r
рекурсивно ищет файлы в текущем рабочем каталоге и всех его подкаталогах.
$ grep -r are ~/Documents
/home/br/Documents/computers:Computers are not intelligent
/home/br/Documents/readme:I hope you are following so far!
grep -w
показывает только совпадающие целиком слова.
$ grep follow ~/Documents/readme
I hope you are following so far!
$ grep -w follow ~/Documents/readme
$
cut
cut
извлекает часть файла (или, как обычно, входного потока). Команда определяет разделитель полей (который разделяет столбцы) с помощью опции -d
, а порядковые номера столбцов для извлечения с помощью опции -f
.
Например, следующая команда извлекает первый столбец из последних пяти строк нашего CSV-файла.
$ tail -n 5 metadata.csv | cut -d , -f 1
mysql.performance.user_time
mysql.replication.seconds_behind_master
mysql.replication.slave_running
mysql.replication.slaves_connected
mysql.performance.queries
Поскольку мы имеем дело с CSV, то столбцы разделяются запятой, а за извлечение первого столбца отвечает опция -f 1
.
Можно выбрать и первый, и второй столбцы, используя опцию -f 1,2
.
$ tail -n 5 metadata.csv | cut -d , -f 1,2
mysql.performance.user_time,gauge
mysql.replication.seconds_behind_master,gauge
mysql.replication.slave_running,gauge
mysql.replication.slaves_connected,gauge
mysql.performance.queries,gauge
paste
paste
объединяет вместе два разных файла в один многоколоночный файл.
$ cat ingredients
eggs
milk
butter
tomatoes
$ cat prices
1$
1.99$
1.50$
2$/kg
$ paste ingredients prices
eggs 1$
milk 1.99$
butter 1.50$
tomatoes 2$/kg
По умолчанию paste
использует разделитель табуляции, но его можно изменить с помощью параметра -d
.
$ paste ingredients prices -d:
eggs:1$
milk:1.99$
butter:1.50$
tomatoes:2$/kg
Ещё один распространённый способ использования paste
— объединение всех строк в потоке или файле с помощью заданного разделителя, используя комбинацию аргументов -s
и -d
.
$ paste -s -d, ingredients
eggs,milk,butter,tomatoes
Если в качестве входного файла указан параметр -
, то вместо него будет считываться stdin
.
$ cat ingredients | paste -s -d, -
eggs,milk,butter,tomatoes
sort
Команда sort
, собственно, сортирует данные (в указанном файле или входном потоке).
$ cat ingredients
eggs
milk
butter
tomatoes
salt
$ sort ingredients
butter
eggs
milk
salt
tomatoes
sort -r
выполняет обратную сортировку.
$ sort -r ingredients
tomatoes
salt
milk
eggs
butter
sort -n
сортирует поля по их арифметическому значению.
$ cat numbers
0
2
1
10
3
$ sort numbers
0
1
10
2
3
$ sort -n numbers
0
1
2
3
10
uniq
uniq
обнаруживает и отфильтровывает соседние одинаковые строки в указанном файле или входном потоке.
$ cat duplicates
and one
and one
and two
and one
and two
and one, two, three
$ uniq duplicates
and one
and two
and one
and two
and one, two, three
Поскольку uniq
отфильтровывает только соседние строки, в наших данных могут ещё остаться дубликаты. Чтобы отфильтровать все одинаковые строки из файла, нужно сначала отсортировать его содержимое.
$ sort duplicates | uniq
and one
and one, two, three
and two
uniq -c
в начале каждой строки вставляет количество её вхождений.
$ sort duplicates | uniq -c
3 and one
1 and one, two, three
2 and two
uniq -u
отображает только уникальные строки.
$ sort duplicates | uniq -u
and one, two, three
Примечание.
uniq
особенно полезен в сочетании с сортировкой, поскольку конвейер| sort | uniq
позволяет удалить все дублирующиеся строки в файле или потоке.
awk
awk
— это чуть больше, чем просто инструмент обработки текста: на самом деле у него целый язык программирования. В чём awk
действительно хорош — так это в разбиении файлов на столбцы, и делает это с особенным блеском, когда в файлах перемешаны пробелы и табы.
$ cat -t multi-columns
John Smith Doctor^ITardis
Sarah-James Smith^I Companion^ILondon
Rose Tyler Companion^ILondon
Примечание.
cat -t
отображает табы как^I
.
Как видим, столбцы разделены либо пробелами, либо табуляциями, и не всегда одинаковым количеством пробелов. cut
здесь бесполезен, потому что работает только с одним символом-разделителем. Но awk
легко разберётся с таким файлом.
awk '{ print $n }'
выводит n-й столбец в тексте.
$ cat multi-columns | awk '{ print $1 }'
John
Sarah-James
Rose
$ cat multi-columns | awk '{ print $3 }'
Doctor
Companion
Companion
$ cat multi-columns | awk '{ print $1,$2 }'
John Smith
Sarah-James Smith
Rose Tyler
Хотя awk
способен на гораздо большее, выдача колонок составляет, наверное, 99% вариантов использования в моём личном случае.
Примечание.
{ print $NF }
выводит последний столбец в строке.
tr
tr
расшифровывается как translate. Эта команда заменяет одни символы на другие. Она работает либо с символами, либо с классами символов, такими как строчные, печатные, пробелы, буквенно-цифровые и т. д.
На стандартных входных данных tr <char1> <char2>
заменяет все вхождения <char1> на <char2>.
$ echo "Computers are fast" | tr a A
computers Are fAst
tr
может переводить классы символов с помощью нотации [:class:]
. Полный список доступных классов описан на справочной странице tr
, но некоторые продемонстрируем здесь.
[:space:]
представляет все типы пробелов, от простого пробела до табуляции или символа новой строки.
$ echo "computers are fast" | tr '[:space:]' ','
computers,are,fast,%
Все символы, похожие на пробелы, переведены в запятую. Обратите внимание, что символ %
в конце выдачи означает отсутствие завершающей новой строки. Действительно, этот символ тоже переведён в запятую.
[:lower:]
представляет все строчные символы, а [:upper:]
— все прописные. Таким образом, преобразование между ними становится тривиальным.
$ echo "computers are fast" | tr '[:lower:]' '[:upper:]'
COMPUTERS ARE FAST
$ echo "COMPUTERS ARE FAST" | tr '[:upper:]' '[:lower:]'
computers are fast
tr -c SET1 SET2
преобразует любой символ, не входящий в набор SET1, в символы набора SET2. В следующем примере все символы, кроме указанных гласных, заменяются пробелами.
$ echo "computers are fast" | tr -c '[aeiouy]' ' '
o u e a e a
tr -d
удаляет указанные символы, а не заменяет их. Это эквивалент tr <char> ''
.
$ echo "Computers Are Fast" | tr -d '[:lower:]'
C A F
tr
также может заменить диапазоны символов, например, все буквы между a и e или все числа между 1 и 8, используя нотацию s-e
, где s
— начальный символ, а e
— конечный.
$ echo "computers are fast" | tr 'a-e' 'x'
xomputxrs xrx fxst
$ echo "5uch l337 5p34k" | tr '1-4' 'x'
5uch lxx7 5pxxk
Команда tr -s string1
сжимает все множественные вхождения символов в string1
в одно-единственное. Одним из наиболее полезных применений tr -s
является замена нескольких последовательных пробелов одним.
$ echo "Computers are fast" | tr -s ' '
Computers are fast
fold
Команда fold
сворачивает все входные строки до заданной ширины. Например, может быть полезно убедиться, что текст помещается на дисплеях небольшого размера. Так, fold -w n
укладывает строки по ширине n символов.
$ cat ~/Documents/readme | fold -w 16
Thanks again for
reading this bo
ok!
I hope you're fo
llowing so far!
Команда fold -s
будет разбивать строки только на символах пробела. Её можно объединить с предыдущей, чтобы ограничить строким заданным количеством символом.
Thanks again
for reading
this book!
I hope you're
following so
far!
sed
sed
— это неинтерактивный потоковый редактор, который используется для преобразования текста во входном потоке строка за строкой. В качестве входных данных используется или файл, или stdin
, а на выходе тоже или файл, или stdout
.
Команды редактора могут включать один или несколько адресов, функцию и параметры. Таким образом, команды выглядят следующим образом:
[address[,address]]function[arguments]
Хотя sed
выполняет множество функций, мы рассмотрим только замену текста как один из самых распространённых вариантов использования.
Замена текста
Команда замены sed
выглядит следующим образом:
s/PATTERN/REPLACEMENT/[options]
Пример: замена первого экземпляра слова в каждой строке в файле:
$ cat hello
hello hello
hello world!
hi
$ cat hello | sed 's/hello/Hey I just met you/'
Hey I just met you hello
Hey I just met you world
hi
Мы видим, что в первой строчке заменяется только первый экземпляр hello
. Чтобы заменить все вхождения hello
во всех строках, можно использовать опцию g
(означает global).
$ cat hello | sed 's/hello/Hey I just met you/g'
Hey I just met you Hey I just met you
Hey I just met you world
hi
sed
позволяет использовать любые разделители, кроме /
, что особенно улучшает читаемость, если в самих аргументах команды есть слэши.
$ cat hello | sed 's@hello@Hey I just met you@g'
Hey I just met you Hey I just met you
Hey I just met you world
hi
Адрес говорит редактору, в какой строке или диапазоне строк выполнять подстановку.
$ cat hello | sed '1s/hello/Hey I just met you/g'
Hey I just met you hello
hello world
hi
$ cat hello | sed '2s/hello/Hey I just met you/g'
hello hello
Hey I just met you world
hi
Адрес 1
указывает заменять hello
на Hey I just met you
в первой строке. Можем указать диапазон адресов в нотации <start>,<end>
, где <end>
может быть либо номером строки, либо $
, то есть последней строкой в файле.
$ cat hello | sed '1,2s/hello/Hey I just met you/g'
Hey I just met you Hey I just met you
Hey I just met you world
hi
$ cat hello | sed '2,3s/hello/Hey I just met you/g'
hello hello
Hey I just met you world
hi
$ cat hello | sed '2,$s/hello/Hey I just met you/g'
hello hello
Hey I just met you world
hi
По умолчанию sed
выдаёт результат в свой stdout
, но может отредактировать и оригинальный файл с опцией -i
.
$ sed -i '' 's/hello/Bonjour/' sed-data
$ cat sed-data
Bonjour hello
Bonjour world
hi
Примечание. В Linux достаточно только
-i
. Но в macOS поведение команды немного отличается, поэтому сразу после-i
нужно добавить''
.
Реальные примеры
Фильтрация CSV с помощью grep и awk
$ grep -w gauge metadata.csv | awk -F, '{ if ($4 == "query") { print $1, "per", $5 } }'
mysql.performance.com_delete per second
mysql.performance.com_delete_multi per second
mysql.performance.com_insert per second
mysql.performance.com_insert_select per second
mysql.performance.com_replace_select per second
mysql.performance.com_select per second
mysql.performance.com_update per second
mysql.performance.com_update_multi per second
mysql.performance.questions per second
mysql.performance.slow_queries per second
mysql.performance.queries per second
В этом примере grep
в файле metadata.csv
сначала фильтрует строки, содержащие слово gauge
, затем те, у которых query
в четвёртой колонке, и выводит название метрики (1-я колонка) с соответствующим значением per_unit_name
(5-я колонка).
Вывод адреса IPv4, связанного с сетевым интерфейсом
$ ifconfig en0 | grep inet | grep -v inet6 | awk '{ print $2 }'
192.168.0.38
Команда ifconfig <interface name>
выводит сведения по указанному сетевому интерфейсу. Например:
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
ether 19:64:92:de:20:ba
inet6 fe80::8a3:a1cb:56ae:7c7c%en0 prefixlen 64 secured scopeid 0x7
inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
Затем запускаем grep
для inet
, что выдаст две строки соответствия.
$ ifconfig en0 | grep inet
inet6 fe80::8a3:a1cb:56ae:7c7c%en0 prefixlen 64 secured scopeid 0x7
inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255
Затем с помощью grep -v
исключаем строку с ipv6
.
$ ifconfig en0 | grep inet | grep -v inet6
inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255
Наконец, с помощью awk
запрашиваем второй столбец в этой строке: это IPv4-адрес, связанный с нашим сетевым интерфейсом en0
.
$ ifconfig en0 | grep inet | grep -v inet6 | awk '{ print $2 }'
192.168.0.38
Примечание. Мне предложили заменить
grep inet | grep -v inet6
такой надёжной командойawk
:$ ifconfig en0 | awk ' $1 == "inet" { print $2 }' 192.168.0.38
Она короче и конкретно нацелена на IPv4 с условием
$1 == "inet"
.
Извлечение значения из файла конфигурации
$ grep 'editor =' ~/.gitconfig | cut -d = -f2 | sed 's/ //g'
/usr/bin/vim
В файле конфигурации git текущего пользователя ищем значение editor =
, обрезаем знак =
, извлекаем второй столбец и удаляем все пробелы вокруг.
$ grep 'editor =' ~/.gitconfig
editor = /usr/bin/vim
$ grep 'editor =' ~/.gitconfig | cut -d'=' -f2
/usr/bin/vim
$ grep 'editor =' ~/.gitconfig | cut -d'=' -f2 | sed 's/ //'
/usr/bin/vim
Извлечение IP-адресов из файла журнала
Следующий реальный код ищет в журнале БД сообщение Too many connections from
(за ним следует IP-адрес) и отображает десять главных нарушителей.
$ grep 'Too many connections from' db.log |
awk '{ print $12 }' |
sed 's@/@@' |
sort |
uniq -c |
sort -rn |
head -n 10 |
awk '{ print $2 }'
10.11.112.108
10.11.111.70
10.11.97.57
10.11.109.72
10.11.116.156
10.11.100.221
10.11.96.242
10.11.81.68
10.11.99.112
10.11.107.120
Давайте разберем, что делает этот конвейер. Во-первых, как выглядит строка в журнале.
$ grep "Too many connections from" db.log | head -n 1
2020-01-01 08:02:37,617 [myid:1] - WARN [NIOServerCxn.Factory:1.2.3.4/1.2.3.4:2181:NIOServerCnxnFactory@193] - Too many connections from /10.11.112.108 - max is 60
Затем awk '{ print $12 }'
извлекает из строки IP-адрес.
$ grep "Too many connections from" db.log | awk '{ print $12 }'
/10.11.112.108
...
Команда sed 's@/@@'
удаляет начальный слэш.
$ grep "Too many connections from" db.log | awk '{ print $12 }' | sed 's@/@@'
10.11.112.108
...
Примечание. Как мы уже видели ранее, в
sed
можно использовать любой разделитель. Хотя обычно в качестве разделителя используется/
, здесь мы заменяем именно этот символ, что слегка ухудшит читаемость выражения подстановки.sed 's////'
sort | uniq -c
сортирует IP-адреса в лексикографическом порядке, а затем удаляет дубликаты, добавляя перед IP-адресами количество вхождений каждого.
$ grep 'Too many connections from' db.log |
awk '{ print $12 }' |
sed 's@/@@' |
sort |
uniq -c
1379 10.11.100.221
1213 10.11.103.168
1138 10.11.105.177
946 10.11.106.213
1211 10.11.106.4
1326 10.11.107.120
...
sort -rn | head -n 10
сортирует строки по количеству вхождений, численно и в обратном порядке, чтобы главные нарушители выводились в первую очередь, из которых отображаются 10 строк. Последняя команда awk { print $2 }
извлекает сами IP-адреса.
$ grep 'Too many connections from' db.log |
awk '{ print $12 }' |
sed 's@/@@' |
sort |
uniq -c |
sort -rn |
head -n 10 |
awk '{ print $2 }'
10.11.112.108
10.11.111.70
10.11.97.57
10.11.109.72
10.11.116.156
10.11.100.221
10.11.96.242
10.11.81.68
10.11.99.112
10.11.107.120
Переименование функции в исходном файле
Представим, что мы работаем над проектом и хотели бы переименовать недачно названную функцию (или класс, переменную и т. д.) в исходном файле. Можно сделать это с помощью команды sed -i
, которая выполняет замену прямо в оригинальном файле.
$ cat izk/utils.py
def bool_from_str(s):
if s.isdigit():
return int(s) == 1
return s.lower() in ['yes', 'true', 'y']
$ sed -i 's/def bool_from_str/def is_affirmative/' izk/utils.py
$ cat izk/utils.py
def is_affirmative(s):
if s.isdigit():
return int(s) == 1
return s.lower() in ['yes', 'true', 'y']
Примечание. На macOS вместо
sed -i
используйтеsed -i ''
.
Однако мы переименовали функцию только в оригинальном файле. Это сломает импорт bool_from_str
в любом другом файле, поскольку эта функция больше не определена. Нужно найти способ переименовать bool_from_str
повсюду в нашем проекте. Такого можно добиться с помощью команд grep
, sed
, а также циклов for
или с помощью xargs
.
Углубляемся: циклы for
и xargs
Чтобы заменить в нашем проекте все вхождения bool_from_str
, сначала нужно рекурсивно найти их с помощью grep -r
.
$ grep -r bool_from_str .
./tests/test_utils.py:from izk.utils import bool_from_str
./tests/test_utils.py:def test_bool_from_str(s, expected):
./tests/test_utils.py: assert bool_from_str(s) == expected
./izk/utils.py:def bool_from_str(s):
./izk/prompt.py:from .utils import bool_from_str
./izk/prompt.py: default = bool_from_str(os.environ[envvar])
Поскольку нас интересуют только файлы c совпадениями, также необходимо использовать опцию -l/--files-with-matches
:
-l, --files-with-matches
Only the names of files containing selected lines are written to standard out-
put. grep will only search a file until a match has been found, making
searches potentially less expensive. Pathnames are listed once per file
searched. If the standard input is searched, the string ``(standard input)''
is written.
$ grep -r --files-with-matches bool_from_str .
./tests/test_utils.py
./izk/utils.py
./izk/prompt.py
Затем можем использовать команду xargs
для осуществления действий с каждой строки выходных данных (то есть всех файлов, содержащих строку bool_from_str
).
$ grep -r --files-with-matches bool_from_str . |
xargs -n 1 sed -i 's/bool_from_str/is_affirmative/'
Опция -n 1
указывает, что каждая строка в выходных данных должна выполнить отдельную команду sed
.
Затем выполняются следующие команды:
$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py
$ sed -i 's/bool_from_str/is_affirmative/' ./izk/utils.py
$ sed -i 's/bool_from_str/is_affirmative/' ./izk/prompt.py
Если команда, которую вы вызываете с помощью xargs
(в нашем случае sed
), поддерживает несколько аргументов, то следует отбросить аргумент -n 1
для производительности.
grep -r --files-with-matches bool_from_str . | xargs sed -i 's/bool_from_str/is_affirmative/'
Эта команда затем исполнит
$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py ./izk/utils.py ./izk/prompt.py
Примечание. Из синопсиса
sed
на ман-странице видно, что команда может принять несколько аргументов.SYNOPSIS sed [-Ealn] command [file ...] sed [-Ealn] [-e command] [-f command_file] [-i extension] [file ...]
Действительно, как мы видели в предыдущей главе,
file ...
означает, что принимаются несколько аргументов, представляющих собой имена файлов.
Мы видим, что произведены замены для всех вхождений bool_from_str
.
$ grep -r is_affirmative .
./tests/test_utils.py:from izk.utils import is_affirmative
./tests/test_utils.py:def test_is_affirmative(s, expected):
./tests/test_utils.py: assert is_affirmative(s) == expected
./izk/utils.py:def is_affirmative(s):
./izk/prompt.py:from .utils import is_affirmative
./izk/prompt.py: default = is_affirmative(os.environ[envvar])
Как это часто бывает, существует несколько способов достижения одного и того же результата. Вместо xargs
мы могли бы использовать циклы for
, чтобы перебирать строки по списку и выполнять действие над каждым элементом. У этих циклов такой синтаксис:
for item in list; do
command $item
done
Если обернуть нашу команду grep
в $()
, то оболочка выполнит её в подоболочке, результат чего затем будет повторён в цикле for
.
$ for file in $(grep -r --files-with-matches bool_from_str .); do
sed -i 's/bool_from_str/is_affirmative/' $file
done
Эта команда выполнит
$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py
$ sed -i 's/bool_from_str/is_affirmative/' ./izk/utils.py
$ sed -i 's/bool_from_str/is_affirmative/' ./izk/prompt.py
Синтаксис циклов for
кажется мне более чётким, чем у xargs
, однако последняя может выполнять команды параллельно, используя параметры -P n
, где n
— максимальное количество параллельных команд, выполняемых одновременно, что может дать выигрыш в производительности.
Резюме
Все эти инструменты открывают целый мир возможностей, так как позволяют извлекать и преобразовывать данные, создавая целые конвейеры из команд, которые, возможно, никогда не предназначались для совместной работы. Каждая из них выполняет относительно небольшую функцию (сортировка sort
, объединение cat
, фильтры grep
, редактирование sed
, вырезание cut
и т. д.).
Любую задачу, включающую текст, можно свести к конвейеру более мелких задач, каждая из которых выполняет простое действие и передаёт свои выходные данные в следующую задачу.
Например, если нам хочется узнать, сколько уникальных IP-адресов в файле журнала, и чтобы эти IP-адреса всегда появлялись в одном и том же столбце, то можно запустить следующую последовательность команд:
grep
строк, которые соответствуют шаблону строк с IP-адресами- найти столбец с IP-адресом, извлечь его с помощью
awk
- отсортировать список IP-адресов с помощью
sort
- устранить смежные дубликаты с помощью
uniq
- подсчитать количество строк (то есть уникальных IP-адресов) с помощью
wc -l
Поскольку есть множество нативных и сторонних инструментов обработки текста, также много способов решить любую задачу.
Примеры в этой статье были надуманными, но я предлагаю вам прочитать удивительную статью «Инструменты командной строки могут быть в 235 раз быстрее, чем ваш кластер Hadoop», чтобы получить представление о том, насколько полезны и мощны эти команды на самом деле и какие реальные проблемы они могут решить.
Что дальше
- Подсчитайте количество файлов и каталогов, расположенных в вашем домашнем каталоге.
- Отобразите содержимое файла только прописными буквами.
- Подсчитайте, сколько раз встречалось каждое слово в файле.
- Подсчитайте количество гласных в файле. Отсортируйте результат от наиболее распространённой до наименее распространённой буквы.
Будущая книга «Основные инструменты и практики для начинающего разработчика программного обеспечения» (Essential Tools and Practices for the Aspiring Software Developer) Бальтазара Рубероля и Этьена Броду поможет создать продуктивную среду разработки и познакомиться с полезными инструментами и практиками, которые нужны для профессионального роста. Как уже было сказано, она охватит такие темы, как освоение терминала, настройка и эффективная работа в командной оболочке, управление версиями кода с помощью git
, основы SQL, инструменты вроде Make
, jq
и регулярные выражения, основы сетевого взаимодействия, а также лучшие практики разработки программного обеспечения и совместной работы.
Если интересно поучаствовать в проекте, подписывайтесь на список рассылки!