Обработка текста: sed, awk, xargs

TL;DR: sed — автоматическая замена и удаление строк. awk — извлечение полей (колонок). xargs — превратить stdin в аргументы команды. Все три работают как фильтры в конвейерах.

sed — потоковый редактор

sed (stream editor) читает текст построчно, применяет операции и выводит результат в stdout. Не изменяет исходный файл (если не указать -i).

Замена: s/pattern/replacement/

# Заменить первое вхождение в каждой строке
sed 's/old/new/' file.txt
 
# Заменить ВСЕ вхождения в строке (флаг g — global)
sed 's/old/new/g' file.txt
 
# Практические примеры
sed 's/:/%/' /etc/passwd              # первое двоеточие → %
sed 's/:/%/g' /etc/passwd             # все двоеточия → %
sed 's/^#//' config.conf              # раскомментировать (убрать # в начале)
sed 's/^/    /' code.py               # добавить отступ (4 пробела)
sed 's/[[:space:]]*$//' file.txt      # удалить trailing whitespace

Удаление строк: d

# По номеру строки
sed '3d' file.txt                     # удалить 3-ю строку
sed '3,6d' file.txt                   # удалить строки 3–6
 
# По regex-паттерну
sed '/^#/d' config.conf               # удалить комментарии
sed '/^$/d' file.txt                  # удалить пустые строки
sed '/DEBUG/d' app.log                # удалить строки с DEBUG

Адресация

Адрес определяет, к каким строкам применяется операция:

АдресЗначениеПример
(пусто)Все строкиsed 's/a/b/g'
5Строка 5sed '5d'
3,6Строки 3–6sed '3,6d'
$Последняя строкаsed '$d'
/regex/Строки, соответствующие regexsed '/error/d'
/start/,/end/Диапазон между паттернамиsed '/BEGIN/,/END/d'

Полезные комбинации

# Несколько операций через -e
sed -e 's/foo/bar/g' -e '/^$/d' file.txt
 
# Редактирование файла на месте (-i)
sed -i 's/localhost/127.0.0.1/g' config.conf
 
# С бэкапом (безопаснее)
sed -i.bak 's/localhost/127.0.0.1/g' config.conf
# Создаст config.conf.bak с оригиналом
 
# Вывести только совпавшие строки (-n + p)
sed -n '/ERROR/p' app.log             # аналог grep, но с поддержкой адресов
 
# Вставить строку после совпадения
sed '/\[server\]/a bind_address = 0.0.0.0' config.ini

sed vs другие инструменты: для простого поиска → grep. Для замены в одном файле → sed. Для сложной логики с условиями → awk или Python.

awk — извлечение полей

awk — полноценный язык программирования, но в 90% случаев используется для одного: вытащить нужную колонку из текстового вывода. awk автоматически разбивает каждую строку на поля по пробелам/табам.

Базовое использование: '{print $N}'

# Поля нумеруются с $1. $0 — вся строка
ls -l | awk '{print $5}'              # размер файла (5-е поле)
ls -l | awk '{print $9}'              # имя файла (9-е поле)
ls -l | awk '{print $5, $9}'          # размер и имя
 
# Практические примеры
df -h | awk '{print $5, $6}'          # % занятости и точка монтирования
ps aux | awk '{print $1, $2, $11}'    # user, PID, command
free -m | awk '/Mem:/ {print $3}'     # used memory (строка Mem, поле 3)
ip route | awk '/default/ {print $3}' # IP шлюза

Разделитель полей: -F

# По умолчанию разделитель — пробелы/табы
# -F меняет разделитель
 
awk -F: '{print $1, $3}' /etc/passwd  # login и UID (разделитель :)
awk -F, '{print $2}' data.csv         # второе поле CSV
awk -F'|' '{print $1}' table.txt      # разделитель |

Фильтрация строк

# Условие перед действием
awk '$3 > 1000 {print $1, $3}' /etc/passwd    # UID > 1000 (с -F:)
awk -F: '$3 > 1000 {print $1}' /etc/passwd    # пользователи с UID > 1000
awk '/error/ {print $0}' app.log               # строки с "error"
awk 'NR > 1 {print $0}' data.csv              # пропустить заголовок (строки > 1)
awk 'NF > 0' file.txt                         # непустые строки (NF = кол-во полей)

Встроенные переменные

ПеременнаяЗначение
$0Вся строка
$1, $2, …Поля
NFКоличество полей в текущей строке
NRНомер текущей строки
FSРазделитель полей (input)
OFSРазделитель полей (output)
# Последнее поле (какое бы оно ни было)
awk '{print $NF}' file.txt
 
# Предпоследнее
awk '{print $(NF-1)}' file.txt
 
# Подсчёт строк (аналог wc -l)
awk 'END {print NR}' file.txt
 
# Сумма значений в колонке
awk '{sum += $5} END {print sum}' data.txt

awk vs Python: для извлечения полей в CLI и простых конвейерах — awk быстрее и короче. Для сложной логики, обработки JSON/XML, работы с сетью — Python.

xargs — stdin → аргументы команды

xargs читает stdin и передаёт прочитанное как аргументы указанной команде. Нужен, когда входных данных слишком много для одной командной строки, или когда нужно применить команду к каждой строке из stdin.

Базовое использование

# Без xargs: echo передаёт текст, но не запускает команду для каждого
# С xargs: каждая строка из stdin → аргумент команды
 
echo -e "file1\nfile2\nfile3" | xargs rm        # rm file1 file2 file3
cat urls.txt | xargs wget                        # wget url1 url2 ...

xargs + find (основной паттерн)

# НЕПРАВИЛЬНО — проблемы с пробелами в именах файлов
find . -name '*.gif' -print | xargs file
 
# ПРАВИЛЬНО — NULL-разделитель (всегда используйте эту форму)
find . -name '*.gif' -print0 | xargs -0 file
 
# Почему: -print0 разделяет файлы символом \0 (а не \n)
# xargs -0 читает по \0. Пробелы и спецсимволы в именах не ломают команду.

Альтернатива: find -exec

# -exec запускает команду для КАЖДОГО файла отдельно
find . -name '*.gif' -exec file {} \;
#                                 ↑↑
#                        {} = имя файла, \; = конец команды
 
# -exec с + (группировка, быстрее)
find . -name '*.gif' -exec file {} +
# Передаёт файлы пачкой, как xargs
ПодходКогда использовать
find -print0 | xargs -0Много файлов, нужна скорость
find -exec {} \;Нужно выполнить для каждого файла отдельно
find -exec {} +Золотая середина: группировка без xargs

Полезные флаги xargs

# -n N — по N аргументов за раз
echo "a b c d" | xargs -n 2 echo
# a b
# c d
 
# -I {} — подставить аргумент в произвольное место
cat hosts.txt | xargs -I {} ssh {} uptime
# ssh host1 uptime, ssh host2 uptime, ...
 
# -P N — параллельное выполнение (N процессов)
find . -name '*.jpg' -print0 | xargs -0 -P 4 -I {} convert {} -resize 50% {}
# 4 параллельных процесса конвертации
 
# -- — конец опций (защита от файлов, начинающихся с -)
find . -print0 | xargs -0 -- rm

Производительность: xargs запускает отдельный процесс для каждой пачки аргументов. Для тысяч файлов это приемлемо, но не ожидайте мгновенной работы.

Комбинирование: конвейеры на практике

# Найти 10 самых больших файлов
find /var/log -type f -exec du -b {} + | sort -rn | head -10 | awk '{print $2}'
 
# Заменить текст во всех конфигах
find /etc -name '*.conf' -print0 | xargs -0 sed -i 's/old_host/new_host/g'
 
# Убить все процессы по имени
ps aux | awk '/zombie_process/ {print $2}' | xargs kill
 
# Посчитать строки кода в проекте
find src/ -name '*.py' -print0 | xargs -0 wc -l | tail -1
 
# Извлечь уникальные IP из лога
awk '{print $1}' access.log | sort -u
 
# Логи: только ошибки, без timestamps
grep ERROR app.log | sed 's/^[0-9-]* [0-9:]* //' | sort -u

Типичные ошибки

ОшибкаПравильно
find | xargs без -print0 -0find -print0 | xargs -0 (пробелы в именах!)
sed 's/foo/bar' (без закрывающего /)sed 's/foo/bar/'
awk {print $1} (без кавычек)awk '{print $1}' (shell раскроет $1!)
sed -i без бэкапа на продеsed -i.bak или проверить через sed ... | head сначала
xargs rm для файлов с - в началеxargs -- rm или xargs rm --