Перенаправления и конвейеры
Куда идёт вывод команды — >, >>, 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>&1 | stderr туда же, куда сейчас указывает 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 | b | stdout a идёт на stdin b; обе команды работают одновременно |
a |& b | stdout и 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