Text Processing в Linux
Text Processing — обработка текстовых потоков стандартными утилитами Linux. Команды можно применять к файлам или объединять через пайп |: стандартный вывод одной команды становится стандартным вводом следующей.
grep 'ERROR' app.log | sort | uniq -c | sort -nrЭтот конвейер находит ошибки, группирует одинаковые строки, считает повторы и сортирует результат по убыванию частоты.
grep и egrep
grep выводит строки, соответствующие шаблону.
grep [опции] 'шаблон' файлПримеры:
grep 'ERROR' app.log # найти ERROR
grep -i 'warning' app.log # игнорировать регистр
grep -n 'timeout' app.log # показать номера строк
grep -v '^#' config.conf # исключить комментарии
grep -c 'ERROR' app.log # количество строк
grep -Rni 'listen' /etc/nginx # рекурсивный поиск
grep -w 'root' /etc/passwd # целое слово
grep -F 'a.b[c]' file.txt # буквальная строка
grep -C 3 'ERROR' app.log # контекст до и после
grep -q '^enabled=true$' app.conf # тихая проверкаОсновные опции:
| Опция | Назначение |
|---|---|
-i |
Игнорировать регистр |
-v |
Инвертировать условие |
-n |
Показать номера строк |
-c |
Подсчитать подходящие строки |
-l |
Показать только имена файлов |
-r, -R |
Рекурсивный поиск |
-w |
Совпадение целого слова |
-x |
Совпадение всей строки |
-E |
Расширенные регулярные выражения |
-F |
Буквальный шаблон без regex |
-o |
Только совпавшие фрагменты |
-q |
Не выводить совпадения |
-A N |
N строк после совпадения |
-B N |
N строк перед совпадением |
-C N |
Контекст с обеих сторон |
grep возвращает код 0, если совпадение найдено, 1 — если совпадений нет, и значение больше 1 при ошибке:
if grep -q '^ENABLED=yes$' settings.conf; then
echo 'Функция включена'
fiegrep
egrep — историческое имя режима расширенных регулярных выражений. В новых командах и скриптах следует использовать grep -E:
grep -E 'ERROR|WARN|CRITICAL' app.logРегулярные выражения
Регулярное выражение описывает шаблон текста. Обычный grep использует базовые выражения BRE, а grep -E — расширенные ERE.
| Элемент | Значение |
|---|---|
. |
Любой один символ |
^ |
Начало строки |
$ |
Конец строки |
[abc] |
Один символ из набора |
[^abc] |
Символ вне набора |
[0-9] |
Символ из диапазона |
* |
Ноль или больше повторений |
+ |
Один или больше повторений в ERE |
? |
Ноль или одно повторение в ERE |
{n,m} |
От n до m повторений в ERE |
| ` | ` |
( ) |
Группа в ERE |
\ |
Экранирование |
grep '^ERROR' app.log # начало строки
grep '\.json$' files.txt # конец на .json
grep -E '^[0-9]+$' values.txt # только цифры
grep -Ei 'error|warning' app.log # один из вариантов
grep -E '^[A-Z]{2}-[0-9]{4}$' ids.txt # формат AB-1234Классы POSIX:
[[:digit:]] цифра
[[:alpha:]] буква
[[:alnum:]] буква или цифра
[[:space:]] пробельный символ
[[:lower:]] строчная буква
[[:upper:]] заглавная букваНапример, комментарий с необязательными пробелами в начале:
grep -E '^[[:space:]]*#' config.confGlob-шаблоны оболочки и regex — разные конструкции. *.log выбирает имена файлов в shell, а регулярное выражение для окончания .log выглядит как \.log$.
sed — потоковый редактор
sed читает текст построчно, преобразует его и выводит результат. Без -i исходный файл не меняется.
sed [опции] 'команда' файлЗамена
sed 's/old/new/' file.txt # первое совпадение в строке
sed 's/old/new/g' file.txt # все совпадения
sed -n 's/old/new/gp' file.txt # только изменённые строки
sed 's|/old/path|/new/path|g' paths.txtИзменение файла с резервной копией:
sed -i.bak 's/DEBUG=true/DEBUG=false/' app.confПеред -i рекомендуется проверить команду без изменения файла. Синтаксис -i может немного различаться между GNU и BSD sed.
Выбор и удаление строк
sed -n '5p' file.txt # пятая строка
sed -n '10,20p' file.txt # строки 10–20
sed -n '/BEGIN/,/END/p' file.txt # диапазон шаблонов
sed '/^[[:space:]]*$/d' file.txt # убрать пустые строки
sed '/^[[:space:]]*#/d' config.conf # убрать комментарии
sed -E 's/[[:space:]]+/ /g' file.txt # сжать пробелыОсновные команды:
| Команда | Назначение |
|---|---|
s/a/b/ |
Замена |
p |
Печать строки |
d |
Удаление строки из результата |
a\text |
Добавление после строки |
i\text |
Вставка перед строкой |
c\text |
Замена выбранной строки |
q |
Завершение обработки |
Группы и обратные ссылки:
printf '%s\n' 'Ivan Petrov' | sed -E 's/^([^ ]+) ([^ ]+)$/\2, \1/'Результат: Petrov, Ivan. Ссылки \1, \2 соответствуют найденным группам.
awk — обработка структурированного текста
awk рассматривает вход как записи и поля. По умолчанию запись — строка, а поля разделены пробелами или табуляцией.
$0 вся строка
$1 первое поле
$2 второе поле
$NF последнее поле
NF число полей
NR номер строки во всём потоке
FNR номер строки текущего файлаОбщий синтаксис:
awk 'условие { действие }' файлПоля и разделители
awk '{ print $1 }' file.txt
awk '{ print NR ":", $0 }' file.txt
awk -F: '{ print $1, $7 }' /etc/passwd
awk -F: 'BEGIN { OFS="\t" } { print $1, $3, $7 }' /etc/passwd-F: задаёт входной разделитель, а OFS — разделитель вывода.
Условия и вычисления
awk '$3 > 100 { print $1, $3 }' data.txt
awk '/ERROR/ { print NR, $0 }' app.log
awk '$2 == "ERROR" && $3 >= 500 { print }' events.txt
awk '{ sum += $2 } END { print sum }' values.txt
awk '{ sum += $2; n++ } END { if (n) print sum / n }' values.txtBEGIN выполняется до чтения данных, END — после:
awk 'BEGIN { print "START" } { print NR, $0 } END { print "Строк:", NR }' fileФорматированный вывод
awk -F, 'NR > 1 { printf "%-20s %10.2f\n", $1, $3 }' report.csvПолезные функции:
awk '{ print length($0) }' file.txt
awk '{ print toupper($0) }' file.txt
awk '{ gsub(/old/, "new"); print }' file.txtПодсчёт значений через ассоциативный массив:
awk '{ count[$1]++ }
END { for (value in count) print count[value], value }' data.txtПередача переменной из shell:
limit=100
awk -v min="$limit" '$2 >= min { print }' values.txtПростой awk -F, не является полноценным CSV-парсером: кавычки, запятые внутри полей и многострочные значения требуют специализированного инструмента.
cut, sort, uniq, wc, tr
cut
cut выбирает поля или позиции символов:
cut -d: -f1 /etc/passwd # первое поле
cut -d: -f1,7 /etc/passwd # поля 1 и 7
cut -d, -f2-4 data.csv # поля 2–4
cut -c1-10 file.txt # символы 1–10-d принимает простой разделитель. Для переменного количества пробелов и более сложной логики удобнее awk.
sort
sort names.txt
sort -r names.txt # обратный порядок
sort -n numbers.txt # числа
sort -k2,2nr report.txt # второе поле, число, убывание
sort -t: -k3,3n /etc/passwd # UID как число
sort -h sizes.txt # 10K, 20M, 1G
sort -V versions.txt # версииРезультат зависит от локали. Для воспроизводимой побайтовой сортировки:
LC_ALL=C sort file.txtНе записывайте результат через sort file > file: оболочка очистит файл до запуска команды. Используйте:
sort file.txt -o file.txtuniq
uniq объединяет только соседние одинаковые строки, поэтому обычно используется после sort:
sort values.txt | uniq
sort values.txt | uniq -c # количество повторений
sort values.txt | uniq -d # только повторяющиеся
sort values.txt | uniq -u # только неповторяющиеся
sort values.txt | uniq -c | sort -nrУдаление дублей с сохранением порядка первых появлений:
awk '!seen[$0]++' file.txtwc
wc -l file.txt # строки
wc -w file.txt # слова
wc -c file.txt # байты
wc -m file.txt # символы
wc -L file.txt # длина самой длинной строкиПодсчёт ошибок:
grep -c 'ERROR' app.log
# или
grep 'ERROR' app.log | wc -lwc -l фактически считает переводы строк, поэтому последняя строка без \n может не попасть в результат.
tr
tr заменяет, удаляет или сжимает символы и обычно читает stdin:
tr '[:lower:]' '[:upper:]' < file.txt
printf '%s\n' 'one,two,three' | tr ',' '\n'
tr -d '\r' < windows.txt > unix.txt
tr -s ' ' < file.txt
tr -s '[:space:]' ' ' < file.txttr работает с символами, а не с регулярными выражениями. Для шаблонной замены используйте sed или awk.
head, tail и tail -f
head показывает начало файла, tail — конец. По умолчанию выводится десять строк.
head file.txt
head -n 20 file.txt
head -c 100 file.bin
tail file.txt
tail -n 50 app.log
tail -n +100 file.txt # начиная со строки 100Наблюдение за дописываемым логом:
tail -f app.logПоследние 100 строк и дальнейшее наблюдение:
tail -n 100 -f app.logФильтрация новых строк в GNU-системах:
tail -f app.log | grep --line-buffered 'ERROR'При ротации лога удобнее следить за именем файла:
tail -F app.log-F повторно открывает файл, если он был заменён или временно исчез. Остановка наблюдения — Ctrl+C.
Для журналов systemd часто применяют:
journalctl -f
journalctl -u nginx -fПайплайны обработки текста
Пайп | связывает команды:
команда1 | команда2 | команда3Частые оболочки пользователей
awk -F: '{ print $7 }' /etc/passwd | sort | uniq -c | sort -nrЧастота слов
tr '[:upper:]' '[:lower:]' < document.txt \
| tr -cs '[:alnum:]' '\n' \
| sed '/^$/d' \
| sort \
| uniq -c \
| sort -nr \
| head -n 20Сохранение промежуточного результата через tee
grep 'ERROR' app.log | tee errors.txt | wc -ltee -a добавляет данные в файл, а не перезаписывает его.
Запись в защищённый файл:
printf '%s\n' 'key=value' | sudo tee /etc/example.conf > /dev/nullБезопасная обработка имён файлов
Имена файлов могут содержать пробелы и переводы строк. Используйте нулевой разделитель:
find . -type f -name '*.tmp' -print0 | xargs -0 rm --Или передавайте аргументы напрямую:
find . -type f -name '*.tmp' -exec rm -- {} +Перед удалением сначала проверьте результат с -print.
Ошибки в пайплайнах
Обычно код пайплайна определяется последней командой. В Bash включите pipefail, если ошибка любого этапа должна считаться ошибкой:
set -o pipefailВ скриптах часто встречается:
set -euo pipefailЭти режимы имеют нюансы, поэтому ожидаемые ненулевые коды нужно обрабатывать явно.
Работа с логами
Логи могут храниться в /var/log, каталоге приложения, контейнере или журнале systemd. Конкретные файлы зависят от дистрибутива и настроек:
/var/log/syslog
/var/log/messages
/var/log/auth.log
/var/log/secure
/var/log/nginx/access.log
/var/log/nginx/error.logПросмотр и поиск
tail -n 100 /var/log/nginx/error.log
tail -F /var/log/nginx/error.log
grep -Ei 'error|fail|critical|panic' app.log
grep -Ei -C 3 'error|critical' app.log
grep 'ERROR' app.log | grep -v 'known harmless message'Анализ HTTP access log
Для распространённого combined log format IP обычно находится в поле 1, путь — в поле 7, код — в поле 9. Формат нужно сначала проверить:
head -n 5 access.logЧастые IP:
awk '{ print $1 }' access.log | sort | uniq -c | sort -nr | headЧастые пути:
awk '{ print $7 }' access.log | sort | uniq -c | sort -nr | headКоды ответа:
awk '{ print $9 }' access.log \
| grep -E '^[1-5][0-9]{2}$' \
| sort | uniq -c | sort -nrПозиции полей могут быть другими при пользовательском формате логирования.
Сжатые логи
zgrep 'ERROR' app.log.gz
zcat app.log.gz | awk '/ERROR/ { count++ } END { print count }'При анализе больших файлов сначала ограничивайте объём точным фильтром, tail или временным диапазоном. sort может потребовать значительный объём памяти и временного места.
jq — обработка JSON
jq разбирает JSON, фильтрует значения и создаёт новые структуры.
Пример users.json:
{
"users": [
{"id": 1, "name": "Anna", "active": true, "roles": ["admin"]},
{"id": 2, "name": "Boris", "active": false, "roles": ["viewer"]}
]
}Форматирование и выбор данных
jq '.' users.json
jq '.users' users.json
jq '.users[0]' users.json
jq '.users[0].name' users.json
jq '.users[]' users.json
jq -r '.users[].name' users.json-r выводит строки без JSON-кавычек.
Фильтрация и преобразование
jq '.users[] | select(.active)' users.json
jq -r '.users[] | select(.active) | .name' users.json
jq '.users[] | {id, name}' users.json
jq '[.users[] | select(.active) | {id, name}]' users.json
jq '.users | sort_by(.name)' users.json
jq '.users | length' users.jsonЗначение по умолчанию:
jq '.user.email // "email не указан"' profile.jsonПеременные shell
Строка:
name='Anna'
jq --arg name "$name" '.users[] | select(.name == $name)' users.jsonЧисло или готовое JSON-значение:
minimum=2
jq --argjson minimum "$minimum" '.users[] | select(.id >= $minimum)' users.jsonНе вставляйте значение shell-переменной непосредственно в выражение jq.
CSV и TSV
jq -r '.users[] | [.id, .name, .active] | @tsv' users.json
jq -r '.users[] | [.id, .name, .active] | @csv' users.jsonJSON Lines и логи
Для файла с одним JSON-объектом в строке:
jq -r 'select(.level == "ERROR") | .message' app.jsonl
jq -r '.level' app.jsonl | sort | uniq -c | sort -nr
tail -F app.jsonl | jq --unbuffered -r 'select(.level == "ERROR") | .message'Проверка JSON
jq -e empty response.json > /dev/nullПроверка условия в скрипте:
if jq -e '.status == "ok"' response.json > /dev/null; then
echo 'Операция выполнена'
fijq не изменяет исходный файл автоматически. Безопасная запись:
jq '.settings.debug = false' config.json > config.json.tmp \
&& mv config.json.tmp config.jsonТипичные ошибки
Лишний cat
cat file.txt | grep 'ERROR'Проще:
grep 'ERROR' file.txtОтсутствие кавычек
grep -- "$pattern" file.txtЕсли текст должен восприниматься буквально:
grep -F -- "$text" file.txtОжидание глобальной уникальности от uniq
sort file.txt | uniqБез сортировки удаляются только соседние повторы.
Разбор сложного формата простым разделителем
Регулярные выражения не заменяют парсер JSON, а awk -F, — полноценный CSV-парсер. Выбирайте инструмент, понимающий формат данных.
Немедленное изменение оригинала
Сначала проверьте результат sed или jq, затем создайте резервную копию и только после этого заменяйте файл.
Краткая памятка
| Задача | Команда |
|---|---|
| Найти текст | grep 'text' file |
| Найти буквальную строку | grep -F 'a.b' file |
| Использовать ERE | `grep -E 'a |
| Рекурсивный поиск | grep -Rni 'text' dir |
| Заменить текст | sed 's/old/new/g' file |
| Вывести строки 10–20 | sed -n '10,20p' file |
| Вывести первое поле | awk '{ print $1 }' file |
Разделитель : |
awk -F: '{ print $1 }' file |
| Выбрать поля | cut -d: -f1,7 /etc/passwd |
| Числовая сортировка | sort -n file |
| Подсчитать повторы | `sort file |
| Подсчитать строки | wc -l file |
| Изменить регистр | tr '[:lower:]' '[:upper:]' |
| Начало файла | head -n 20 file |
| Конец файла | tail -n 20 file |
| Следить за логом | tail -F app.log |
| Форматировать JSON | jq '.' file.json |
| Извлечь строку JSON | jq -r '.name' file.json |
Рекомендации
- Заключайте шаблоны и значения переменных в кавычки.
- Используйте
grep -Fдля буквального поиска иgrep -Eвместоegrep. - Проверяйте команды
sedдо применения-i. - Помните, что
uniqобрабатывает только соседние повторы. - Проверяйте формат логов перед выбором полей через
awkилиcut. - Используйте
jq, а не регулярные выражения, для JSON. - Для имён файлов применяйте
find -print0иxargs -0. - В важных Bash-пайплайнах используйте
set -o pipefail. - Не перезаписывайте исходные данные до проверки результата.