У каждой запущенной программы три стандартных потока: ввод stdin (номер 0), вывод stdout (1) и поток ошибок stderr (2). По умолчанию оба выходных потока печатаются в терминал вперемешку, поэтому кажется, что поток один. Перенаправление меняет, куда течёт каждый из них, а конвейер соединяет вывод одной команды со входом другой. На этих двух механизмах держатся все однострочники командной строки.
2>&1 направляет stderr туда же, куда stdout
> out.log уводит в файл только stdout. 2>&1 отправляет stderr «туда же, куда сейчас смотрит поток 1» — и в терминале не остаётся ничего.Перенаправление вывода: > и >>
Знак > направляет stdout в файл вместо терминала. Одинарный перезаписывает файл, двойной дописывает в конец:
echo "молоко" > shopping.txt
echo "хлеб" >> shopping.txt
cat shopping.txt
молоко
хлеб
Если бы вторая команда тоже использовала >, в файле осталась бы одна строка хлеб. Важная деталь: файл создаётся и очищается до запуска команды — из этого растёт ошибка, разобранная ниже.
Почему ошибки «просачиваются» мимо файла
Возьмём ls с двумя аргументами, один из которых не существует. На экране два сообщения вперемешку:
ls app missing
ls: cannot access 'missing': No such file or directory
app:
main.py
Теперь перенаправим вывод в файл:
ls app missing > files.txt
ls: cannot access 'missing': No such file or directory
Ошибка осталась на экране, потому что > забрал только stdout. Сообщение про missing шло по другому потоку — stderr, и его > не трогает. Это удобно: запускаешь долгую команду с выводом в файл, а ошибки видишь сразу. Но в логах нужны оба потока.
2>&1: собрать оба потока в один файл
Конструкция 2>&1 читается как «поток 2 отправить туда же, куда сейчас смотрит поток 1»:
ls app missing > all.log 2>&1
cat all.log
ls: cannot access 'missing': No such file or directory
app:
main.py
Терминал молчит, в файле всё. Порядок критичен: сначала > all.log разворачивает stdout в файл, потом 2>&1 присоединяет stderr. В обратном порядке — 2>&1 > all.log — stderr успевает нацелиться на терминал (куда stdout смотрел в тот момент) и остаётся там, а файл получает только stdout.
Обратный приём — спрятать ошибки совсем. /dev/null — специальный файл-«чёрная дыра», всё записанное в него исчезает:
ls app missing 2>/dev/null
app:
main.py
Так глушат заведомый шум — например, Permission denied при поиске find по всей системе.
Конвейер: вывод одной команды — вход другой
Знак | соединяет stdout левой команды со stdin правой. Классическая связка — частотный словарь. В файле tags.txt шесть строк с повторами:
sort tags.txt | uniq -c | sort -nr
3 python
2 sql
1 linux
Три шага: sort собирает одинаковые строки рядом, uniq -c схлопывает соседние повторы и считает их, второй sort -nr сортирует по числу по убыванию. Каждая команда умеет одну вещь — конвейер собирает из них инструмент.
Вторая связка на каждый день — подсчёт: команда | wc -l печатает число строк вывода. А когда результат нужен и на экране, и в файле, есть tee:
echo "деплой прошёл" | tee deploy.log
деплой прошёл
Строка напечаталась и одновременно легла в deploy.log. С флагом -a tee дописывает, а не перезаписывает.
Ошибки, на которых спотыкаются
Перенаправление в тот же файл. sort list.txt > list.txt уничтожает данные: шелл очищает list.txt до запуска sort, и сортировать уже нечего — на выходе пустой файл. Сортируют во временный файл или используют sort -o list.txt list.txt.
2>&1 до перенаправления. command 2>&1 > out.log выглядит так же, но ошибки останутся на экране. Правильный порядок: сначала > out.log, потом 2>&1.
> вместо >> в скриптах-накопителях. Скрипт, который пишет лог через >, хранит только последний запуск. Для накопления — >>.
uniq без sort. uniq схлопывает только соседние повторы. На неотсортированном файле он молча оставит дубли, разбросанные по разным местам.
Частые вопросы
Чем > отличается от >>
> перезаписывает файл с нуля, >> дописывает в конец. Оба создают файл, если его не было.
Как записать в файл и вывод, и ошибки
command > out.log 2>&1. В bash есть короткая форма &> out.log — работает так же, но только в bash: в POSIX-скриптах (sh) она развалится на «запустить в фоне и перенаправить». Длинная форма надёжнее.
Что такое /dev/null
Псевдофайл, который молча поглощает всё записанное. 2>/dev/null прячет ошибки, >/dev/null — обычный вывод, >/dev/null 2>&1 — вообще всё. Читается из него только пустота.
Как посчитать, сколько строк в выводе команды
команда | wc -l. Например, ls | wc -l — сколько записей в каталоге, grep -r "TODO" . | wc -l — сколько пометок в проекте.
Где потренироваться
Конвейеры и перенаправления — почти треть вопросов скилл-теста по Linux. Рядом в справочнике: основные команды, из которых конвейеры собираются, и процессы и сигналы — что происходит с командами конвейера, когда одна из них падает.