Почему 2> & 1 нужно прийти перед | (pipe), но после "> myfile" (перенаправление на файл)?

При объединении stderr с stdout, почему 2>&1 нужно выполнить перед | (pipe), но после > myfile (перенаправление на файл)?

Чтобы перенаправить stderr на stdout для вывода файла:

  echo > myfile 2>&1

Чтобы перенаправить stderr в stdout для канала:

  echo 2>&1 | less



Мое предположение заключалось в том, что я мог просто сделать:

  echo | less 2>&1 

и это сработает, но это не так. Почему бы и нет?

Ответ 1

Конвейер представляет собой список команд, ограниченный | -delimited. Любые перенаправления, которые вы указываете, относятся к составным командам (простым или составным), но не к конвейеру в целом. Каждая труба объединяет одну команду stdout в stdin следующего, неявно применяя перенаправление к каждой подоболочке до того, как будут оценены любые перенаправления, связанные с командой.

cmd 2>&1 | less

Первая строка первой подоболочки перенаправляется на трубу, из которой читается less. Затем перенаправление 2>&1 применяется к первой команде. Перенаправление stderr в stdout работает, потому что stdout уже указывает на канал.

cmd | less 2>&1

Здесь перенаправление применяется к less. Меньше stdout и stderr, как предположительно, начали указывать на терминал, поэтому 2>&1 в этом случае не имеет эффекта.

Если вы хотите, чтобы перенаправление применялось ко всему конвейеру, группировало несколько команд как часть конвейера или встраивало конвейеры, затем используйте группу команд (или любую другую составную команду):

{ { cmd1 >&3; cmd2; } 2>&1 | cmd3; } 3>&2

Может быть типичным примером. Конечный результат: cmd1 и cmd2 stderr → cmd3; cmd2 stdout → cmd3; и cmd1 и cmd3 stderr, а cmd3 stdout → терминал.

Если вы используете тэг Bash -специфичный |&, все становится страннее, потому что каждый из перенаправлений stdout конвейера все еще встречается первым, но перенаправление stderr на самом деле происходит последним. Так, например:

f() { echo out; echo err >&2; }; f >/dev/null |& cat

Теперь, контринтуитивно, весь вывод скрыт. Сначала stdout f переходит в канал, следующий stdout f перенаправляется на /dev/null, и, наконец, stderr перенаправляется на stdout (/dev/null неподвижно).

Я рекомендую никогда не использовать |& в Bash - он используется здесь для демонстрации.

Ответ 2

Чтобы добавить в ormaaj ответ:

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

# print "hello" on stdout and "world" on stderr
{ echo hello; echo world >&2; }

# Redirect stdout to the file "out"
# Then redirect stderr to the file "err"
{ echo hello; echo world >&2; } > out 2> err

# Redirect stdout to the file "out"
# Then redirect stderr to the (already redirected) stdout
# Result: all output is stored in "out"
{ echo hello; echo world >&2; } > out 2>&1

# Redirect stderr to the current stdout
# Then redirect stdout to the file "out"
# Result: "world" is displayed, and "hello" is stored in "out"
{ echo hello; echo world >&2; } 2>&1 > out

Ответ 3

Мой ответ - понимание файловых дескрипторов. Каждый процесс имеет кучу дескрипторов файлов: записи в открываемые файлы. По умолчанию число 0 для stdin, номер 1 для stdout, а число 2 - для stderr.

Редиректоры ввода/выводa > и < по умолчанию подключайтесь к их наиболее разумным дескрипторам файлов, stout и stdin. Если вы переадресовываете stdout в файл (как в foo > bar), при запуске процесса "foo" файл "bar" открывается для записи и подключается к файловому дескриптору номер 1. Если вы хотите только stderr в "bar", вы должны использовать foo 2> bar, который открывает панель файлов и перехватывает ее в stderr.

Теперь перенаправление i/0 '2 > & 1'. Обычно я читаю это как "поместить дескриптор файла 2 в дескриптор файла 1. При чтении командной строки слева направо вы можете сделать следующее: foo 1>bar 2>&1 1>/dev/tty. При этом файловый дескриптор 1 устанавливается в файл "bar", дескриптор файла 2 устанавливается равным 1 (следовательно, "бар"), а после этого дескриптор файла 1 устанавливается в /dev/tty. Запуск foo отправляет свой вывод в /dev/tty, а stderr - в файл 'bar'.

Теперь идет конвейер: это не изменяет дескрипторы файла, однако он будет связывать их между процессами: stdout левого процесса от stdin следующего. Stderr передается. Следовательно, если вам нравится, что конвейер работает только с stderr, вы используете foo 2| bar, который связывает stderr foo с stdin bar. (Я не уверен, что происходит с stdout foo.)

С приведенным выше, если вы используете foo 2>&1 | bar, поскольку stderr из foo перенаправляется в stdout из foo, то и stdout, и stderr из foo поступают на stdin bar.