Bash Пайпы: Полный Гайд и Шпаргалка
-
Представьте, что вам нужно найти количество файлов в директории. Без пайпов пришлось бы делать так:
# Сначала список файлов сохраняем в файл ls -l > /tmp/files.txt # Потом считаем строки wc -l /tmp/files.txt # Потом удаляем временный файл rm /tmp/files.txtТри команды, временный файл, неудобно.
Решение с пайпами
С пайпом это становится одной строкой:
ls -l | wc -lПочему пайпы полезны:
- Эффективность — не нужны временные файлы на диске
- Скорость — данные передаются через оперативную память (быстрее, чем I/O на диск)
- Читаемость — понимаете логику цепочки команд с первого взгляда
- Масштабируемость — можете соединять сколько угодно команд в цепочку
- Unix философия — “Делай одно, делай хорошо” — пайпы объединяют маленькие команды в мощные решения
2. Основные концепции: потоки данных
В Linux каждый процесс имеет три стандартных потока:
Поток Номер FD Описание По умолчанию stdin 0 Стандартный ввод (входит в процесс) Клавиатура stdout 1 Стандартный вывод (выходит из процесса) Экран stderr 2 Стандартная ошибка (сообщения об ошибках) Экран Визуально:
┌─────────────────┐ │ Процесс │ │ │ │ ┌──────────┐ │ │ │ команда │ │ │ └──────────┘ │ │ ↑ ↓ ↓ │ │ │ │ │ │ │ 0 1 2 │ (file descriptors) │ │ │ │ │ └────┼──┼──┼──────┘ │ │ └─ stderr → экран │ └──── stdout → экран └─────── stdin ← клавиатура
3. Как работают пайпы?
Механизм пайпа
Пайп (|) соединяет stdout одной команды со stdin другой:
команда1 | команда2 ↓ stdout(команда1) → stdin(команда2)На системном уровне:
- Shell видит символ
|и создаёт канал (pipe) в памяти - Stdout первого процесса подключается к этому каналу
- Stdin второго процесса подключается к этому каналу
- Обе команды запускаются одновременно (параллельно!)
- Буферизация позволяет команде 2 работать, пока команда 1 пишет
Важно: Команды выполняются параллельно, но синхронизированы потоком данных.
Пример в реальном времени
# Медленный процесс производит данные (while true; do date; sleep 1; done) | \ # Быстрый процесс их обрабатывает (while read line; do echo ">>> $line"; done)Обе команды работают одновременно!
4. Редирекция потоков
Базовая редирекция stdout
# Перенаправить вывод в файл (перезаписать) command > file.txt # Добавить в конец файла command >> file.txt # Отправить в /dev/null (удалить вывод) command > /dev/nullРедирекция stderr
# Отправить ошибки в файл command 2> errors.txt # Отправить ошибки в файл (классическая форма) command 2> /tmp/errors.log # Объединить stderr со stdout command 2>&1 # Отправить оба потока в файл (современный синтаксис) command &> output.txt # Скрыть ошибки command 2> /dev/nullКомбинированная редирекция
# stdout → файл, stderr → экран command > output.txt 2>&1 # stdout → одна команда, stderr → другая команда command 1> >(process_stdout) 2> >(process_stderr) # Отправить оба потока в разные файлы command > stdout.txt 2> stderr.txt # stdout в файл, stderr к stdout, затем в пайп command 2>&1 | tee output.txtВажно про порядок! Обработка идёт слева направо:
# ПРАВИЛЬНО: сначала объединяем потоки, потом пайпим command 2>&1 | grep error # НЕПРАВИЛЬНО: пайпим только stdout command | grep error 2>&1 # ошибки не попадут в пайп!Редирекция stdin
# Передать файл как стандартный ввод command < input.txt # Передать строку как стандартный ввод cat <<EOF | command множественные строки текста EOF
5. Практические примеры
Пример 1: Поиск и подсчёт файлов
# Найти все .log файлы, созданные за последние 7 дней, и посчитать их find . -name "*.log" -mtime -7 | wc -l # Поток данных: # find → (список файлов в stdin) → wcПример 2: Фильтрация логов
# Найти ошибки в логе, исключить DEBUG, показать последние 10 cat app.log | \ grep "ERROR" | \ grep -v "DEBUG" | \ tail -10 # Или более компактно: grep "ERROR" app.log | grep -v "DEBUG" | tail -10Пример 3: Обработка данных с преобразованием
# Получить список портов, которые слушают, отсортировать и посчитать уникальные netstat -tlnp | \ awk '{print $4}' | \ cut -d: -f2 | \ sort | \ uniq -c | \ sort -rnРазбор:
netstat -tlnp— показать все слушающие TCP портыawk '{print $4}'— извлечь 4-е поле (адрес:порт)cut -d: -f2— вырезать только номер портаsort— отсортироватьuniq -c— посчитать одинаковыеsort -rn— отсортировать по убыванию
Пример 4: Обработка JSON
# Получить все email'ы из JSON API и сохранить в файл curl -s https://api.example.com/users | \ jq '.users[].email' | \ tr -d '"' | \ sort | \ uniq > emails.txtПример 5: Массовая переименование файлов
# Переименовать все .jpg в .png расширение ls *.jpg | \ awk '{print "mv " $1 " " substr($1, 1, length($1)-3) "png"}' | \ bash # Или более безопасно (echo для проверки): ls *.jpg | \ awk '{print "mv " $1 " " substr($1, 1, length($1)-3) "png"}' | \ head -5 # Проверить на первых 5Пример 6: Совмещение нескольких фильтров
# Найти топ-10 самых тяжёлых файлов find . -type f -exec ls -lh {} \; | \ awk '{print $5, $NF}' | \ sort -h -r | \ head -10 # Поток: # find → ls → awk (фильтруем) → sort → headПример 7: Работа с переменными окружения
# Получить список всех установленных пакетов, содержащих "mysql" apt list --installed 2>/dev/null | \ grep "mysql" | \ awk -F'/' '{print $1}' | \ tee installed-mysql.txt # tee — выводит в консоль И в файл одновременноПример 8: Пайпы с функциями в bash
# Определить функцию для обработки process_line() { echo "Обработан: $1" } # Использовать с пайпом echo -e "line1\nline2\nline3" | \ while read line; do process_line "$line" done
6. Пайпы vs xargs
Разница: stdout vs аргументы
Пайп (|) передаёт данные в stdin:
echo -e "file1.txt\nfile2.txt" | cat # cat получает текст в stdin # Вывод: # file1.txt # file2.txtxargs передаёт данные как аргументы команде:
echo -e "file1.txt\nfile2.txt" | xargs cat # cat получит аргументы # Аналогично: cat file1.txt file2.txtКогда использовать xargs?
xargs нужен когда команда НЕ читает stdin:
# ❌ НЕПРАВИЛЬНО: rm не читает stdin echo "file1.txt" | rm # ✅ ПРАВИЛЬНО: xargs преобразует stdin в аргументы echo "file1.txt" | xargs rm # Аналогично: echo "file1.txt" | xargs -I {} rm {}Примеры xargs
# Удалить все временные файлы find . -name "*.tmp" | xargs rm # Скопировать множество файлов ls *.jpg | xargs -I {} cp {} /backup/ # Запустить в параллель (-P 4 = 4 процесса одновременно) cat file_list.txt | xargs -P 4 -I {} bash -c 'process_file "$1"' _ {} # Обработать с пользовательским разделителем find . -name "*.txt" -print0 | xargs -0 wc -l # -print0 и -0: для файлов с пробелами в имениСравнительная таблица
Операция Пайп xargs Передача данных stdin аргументы Примечание “Команда читает с клавиатуры” “Команда берёт от shell” cat ✓ ✓ (нужна) grep ✓ ✓ (нужна) rm ✗ ✓ cp ✗ ✓ echo ✓ ✓ Параллелизм ✗ ✓ (-P флаг)
7. Питфоллы и решения
Проблема 1: Буферизация (данные не появляются)
Проблема: При пайпировании медленного процесса вывод задерживается.
# Команда запущена, но вывода не видно 10 секунд slow_command | grep patternПричина: Многие программы используют full buffering при пайпировании (вместо line buffering при выводе на экран).
Решение 1: stdbuf
slow_command | stdbuf -oL grep pattern # -oL = line buffering для stdoutРешение 2: unbuffer
slow_command | unbuffer grep patternРешение 3: script/expect
slow_command 2>&1 | tee output.txt | grep pattern # tee заставляет команду работать в line buffering режимеПроблема 2: Обработка ошибок в пайпах
Проблема: При ошибке в середине пайпа не видно, где именно.
cmd1 | cmd2 | cmd3 # Если cmd2 упал, cmd3 может не выполниться корректноРешение:
# Проверить код возврата каждой команды cmd1 | cmd2 | cmd3 # BASH покажет код возврата последней команды: echo $? # код возврата cmd3, НЕ cmd1 и cmd2! # Правильная проверка: set -o pipefail # Если хоть одна упала, весь пайп упал cmd1 | cmd2 | cmd3 echo $? # Покажет ошибку если хоть где-то была ошибка # Или в отдельной команде: if cmd1 | cmd2 | cmd3; then echo "Успешно" else echo "Ошибка в пайпе" fiПроблема 3: Потери данных с пробелами/спецсимволами
Проблема:
# Файл с пробелом в имени echo "my file.txt" | xargs rm # ❌ Попытается удалить "my" и "file.txt" отдельноРешение:
echo "my file.txt" | xargs -I {} rm "{}" # Или с print0: find . -name "*.txt" -print0 | xargs -0 rmПроблема 4: Слишком много аргументов
Проблема:
ls *.txt | xargs grep "pattern" # При 10000+ файлов: Argument list too longРешение:
# Ограничить количество аргументов за раз ls *.txt | xargs -n 100 grep "pattern" # Или более правильно: find . -name "*.txt" -print0 | xargs -0 -n 100 grep "pattern"Проблема 5: Потеря переменных окружения
Проблема:
MY_VAR="hello" && echo "test" | while read line; do echo $MY_VAR; done # ❌ MY_VAR пуста! while запущен в подоболочкеРешение:
MY_VAR="hello" echo "test" | ( export MY_VAR while read line; do echo $MY_VAR; done ) # ✓ РаботаетПроблема 6: Данные из пайпа недоступны после цикла
Проблема:
echo -e "1\n2\n3" | while read num; do ((sum += num)); done echo "Sum: $sum" # ❌ sum пуст!Причина: while запущен в подоболочке, переменная не передаётся.
Решение:
sum=0 while read num; do ((sum += num)); done < <(echo -e "1\n2\n3") echo "Sum: $sum" # ✓ 6
8. Шпаргалка
Базовый синтаксис
# Пайп cmd1 | cmd2 # Цепочка пайпов cmd1 | cmd2 | cmd3 | cmd4 # Редирекция stdout cmd > file cmd >> file cmd > /dev/null # Редирекция stderr cmd 2> file cmd 2>> file cmd 2> /dev/null # Объединить потоки cmd 2>&1 cmd &> file # Отдельные потоки cmd 1> out.txt 2> err.txt # stdin из файла cmd < file # stdin из строки cmd <<< "string" # stdin из блока cmd <<EOF line1 line2 EOFЧасто используемые инструменты
# grep — фильтровать текст cmd | grep "pattern" cmd | grep -v "exclude" # инверсия cmd | grep -i "case-insensitive" # awk — парсинг и обработка cmd | awk '{print $2}' # 2-е поле cmd | awk -F: '{print $1}' # с разделителем : cmd | awk 'NR>2' # кроме первых 2 строк # sed — замена текста cmd | sed 's/old/new/' # заменить первое cmd | sed 's/old/new/g' # заменить все # sort, uniq cmd | sort # отсортировать cmd | sort -u # уникальные cmd | sort | uniq -c # подсчитать # cut — вырезать колонки cmd | cut -d: -f1 # разделитель :, 1-е поле # tr — трансформация символов cmd | tr 'a-z' 'A-Z' # в верхний регистр cmd | tr -d '"' # удалить кавычки # head, tail cmd | head -10 # первые 10 строк cmd | tail -5 # последние 5 строк # wc — подсчёт cmd | wc -l # строк cmd | wc -w # слов cmd | wc -c # символов # tee — дублировать вывод cmd | tee file.txt # в консоль И в файл # xargs — передать как аргументы cmd | xargs rm # rm для каждого результата cmd | xargs -n 5 # по 5 элементов за раз cmd | xargs -P 4 # параллельно в 4 процессаBash скелет для надёжного скрипта
#!/bin/bash set -euo pipefail # выход при ошибке, undefined vars, pipe ошибки # Функция с пайпом process_data() { local input_file="$1" cat "$input_file" | \ grep "pattern" | \ awk '{print $1}' | \ sort | \ uniq -c | \ sort -rn } # Использование if output=$(process_data "data.txt"); then echo "$output" | head -10 else echo "Ошибка обработки" >&2 exit 1 fiОтладка пайпов
# Показать все команды перед выполнением set -x cmd1 | cmd2 | cmd3 set +x # Проверить каждый этап cmd1 > /tmp/step1.txt cat /tmp/step1.txt | cmd2 > /tmp/step2.txt cat /tmp/step2.txt | cmd3 # Или с tee: cmd1 | tee /tmp/step1.txt | cmd2 | tee /tmp/step2.txt | cmd3 # Подробная информация cmd 2>&1 | tee debug.log less debug.log
Примеры для реальных задач
Анализ лог-файлов
# Топ ошибок за день grep "ERROR" /var/log/app.log | \ awk '{print $NF}' | \ sort | uniq -c | \ sort -rn | head -10 # Статистика по IP адресам cat /var/log/nginx/access.log | \ awk '{print $1}' | \ sort | uniq -c | \ sort -rn | head -20Управление файлами
# Найти и удалить все .tmp файлы старше 7 дней find /tmp -name "*.tmp" -mtime +7 | xargs rm -f # Архивировать логи старше 30 дней find /var/log -name "*.log" -mtime +30 | \ xargs -I {} gzip {} # Скопировать структуру директорий find source -type d | \ sed 's/^source/dest/' | \ xargs mkdir -pОбработка данных
# CSV в JSON (простой пример) { IFS=, read -r header; \ while IFS=, read -r col1 col2 col3; do \ echo "{\"col1\":\"$col1\",\"col2\":\"$col2\",\"col3\":\"$col3\"}" done; \ } < data.csv # Парсинг конфиг файлов grep -v "^#" config.conf | \ grep -v "^$" | \ awk -F= '{gsub(/ /, "", $1); print $1 "=" $2}'
Итоги
Пайпы — это основа Unix философии:
- Простота — каждая команда делает одно
- Мощь — комбинирование создаёт сложные операции
- Эффективность — прямая передача данных через память
- Читаемость — логика цепочки видна в одной строке
Помните:
- Пайп передаёт stdout → stdin
- xargs передаёт данные как аргументы
- set -o pipefail для надёжности скриптов
- Проверяйте буферизацию при медленных процессах
- Экранируйте спецсимволы и пробелы в файлах
© 2024 - 2025 ExLends, Inc. Все права защищены.