← Весь справочник

Перенаправления и конвейеры

Куда идёт вывод команды — >, >>, 2>&1, &>, here-doc, конвейер | и pipefail

У каждой программы три стандартных потока: ввод (stdin, дескриптор 0), вывод (stdout, 1) и ошибки (stderr, 2). Перенаправления подменяют эти потоки файлами, а конвейер | соединяет вывод одной команды со вводом другой.

Синтаксис

команда > файл          # stdout в файл
команда 2> файл         # stderr в файл
команда < файл          # stdin из файла
команда1 | команда2     # stdout первой — на stdin второй

Перенаправления обрабатываются слева направо, ещё до запуска команды. Их можно писать в любом месте строки: > f echo hi тоже работает.

Вывод

ОператорЧто делает
> файлstdout в файл; файл создаётся или обрезается до нуля
>> файлstdout дописывается в конец файла
2> файл / 2>> файлто же для stderr
&> файл / &>> файлstdout и stderr в один файл: перезаписать / дописать (bash)
> файл 2>&1то же, переносимая форма для sh
>| файлперезаписать, даже если включён noclobber
> файл (без команды)создать пустой файл или обнулить существующий
>/dev/nullвыбросить вывод; 2>/dev/null — выбросить ошибки

Ввод

ОператорЧто делает
< файлstdin из файла
<<МАРКЕР … МАРКЕРhere-doc: строки до маркера идут на stdin, $var и $(…) раскрываются
<<'МАРКЕР'here-doc без подстановок: текст как есть
<<-МАРКЕРhere-doc, у строк срезаются ведущие табуляции (для отступов в скриптах)
<<< "строка"here-string: одна строка (с добавленным \n) на stdin
<(команда)подстановка процесса: вывод команды как «файл» для чтения
>(команда)наоборот: «файл», запись в который идёт на stdin команды

Дескрипторы

ОператорЧто делает
2>&1stderr туда же, куда сейчас указывает stdout
>&2 (1>&2)stdout в stderr — так пишут сообщения об ошибках в скриптах
n>&-закрыть дескриптор n
exec 3> файлоткрыть дескриптор 3 на запись для всего скрипта; echo x >&3 пишет в файл
exec 3< файлоткрыть дескриптор 3 на чтение; read -r line <&3
exec 3<> файлоткрыть на чтение и запись
exec > файл 2>&1перенаправить весь дальнейший вывод скрипта
{ …; } > файлперенаправить вывод группы команд разом

Конвейеры

ЗаписьЧто делает
a | bstdout a идёт на stdin b; обе команды работают одновременно
a |& bstdout и stderr a на stdin b (то же, что a 2>&1 | b)
${PIPESTATUS[@]}коды возврата всех частей последнего конвейера
set -o pipefailкод конвейера — код последней упавшей части, а не просто последней
! a | bинвертировать код возврата всего конвейера

Код возврата конвейера по умолчанию — код последней команды: false | true считается успехом, ошибка в середине теряется. pipefail это исправляет:

false | true; echo $?          # 0
set -o pipefail
false | true; echo $?          # 1

Примеры

# записать вывод в файл, затем дописать ещё строку
echo one > out.txt
echo two >> out.txt

# ошибки — отдельно, обычный вывод — на экран
ls /etc/hostname /nope 2> errors.txt

# всё в один файл: сначала stdout в файл, потом stderr туда же
ls /etc/hostname /nope > all.txt 2>&1
ls /etc/hostname /nope &> all.txt          # короче, только в bash

# спрятать ошибки доступа
find / -name '*.conf' 2>/dev/null

# посчитать строки, не печатая имя файла
wc -l < /etc/passwd

# сообщение об ошибке из скрипта — в stderr
echo "файл не найден" >&2

# here-doc: переменные внутри раскрываются
cat <<EOF
домашний каталог: $HOME
EOF

# here-doc с кавычками вокруг маркера: текст идёт как есть
cat <<'EOF' > script.sh
echo "$HOME"
EOF

# here-string
tr a-z A-Z <<< "hello"

# передать ошибки в конвейер вместе с выводом
ls /nope 2>&1 | wc -l

# сравнить вывод двух команд без временных файлов
diff <(ls dir1) <(ls dir2)

# читать вывод команды в цикле, не теряя переменные (без подоболочки)
while read -r line; do n=$((n+1)); done < <(ls /etc)

# коды возврата каждой части конвейера
false | true; echo "${PIPESTATUS[@]}"      # 1 0

# свой дескриптор для лога
exec 3>> /tmp/script.log
echo "шаг 1" >&3
exec 3>&-

Подводные камни

  • Порядок 2>&1 важен. cmd > f 2>&1 пишет оба потока в файл. cmd 2>&1 > f отправляет stderr туда, куда stdout указывал до перенаправления (на экран), а в файл идёт только stdout.
  • sort f > f уничтожает файл. Оболочка обрезает f до запуска sort, и та читает пустой файл. Пиши во временный файл и переименовывай его (у sort для этого есть sort -o f f).
  • > молча перезаписывает. set -o noclobber запрещает перезапись существующих файлов через >; принудительно — >|.
  • Каждая часть конвейера работает в подоболочке. echo "a b" | read x y не изменит x в текущей оболочке, счётчик в cat f | while … после цикла снова пуст. Используй read x y <<< "a b" или while … done < файл / < <(команда).
  • sudo echo текст > /root/файл не сработает: перенаправление выполняет твоя оболочка, а не sudo. Используй echo текст | sudo tee файл.
  • <<- срезает только табуляции, не пробелы. Закрывающий маркер должен стоять в строке один, без пробелов после.
  • &> — это bash. В #!/bin/sh (dash) cmd &> f значит «запусти cmd в фоне и создай пустой f». Пиши > f 2>&1.

См. также

tee · exit-codes · variables · xargs