В каком порядке оболочка выполняет команды и перенаправляет поток?

32

Я пытался перенаправить оба stdoutи stderrв файл сегодня, и я столкнулся с этим:

<command> > file.txt 2>&1

Это, по-видимому, перенаправляет stderrв stdoutпервую очередь, а затем в результате stdoutперенаправляется в file.txt.

Однако почему не заказ <command> 2>&1 > file.txt? Естественно, можно было бы прочитать это как (при условии выполнения слева направо) сначала выполняемую команду stderr, stdoutа затем перенаправлять в полученную stdoutзапись file.txt. Но вышеперечисленное только перенаправляет stderrна экран.

Как оболочка интерпретирует обе команды?

Поезд Heartnet
источник
7
TLCL говорит: «Сначала мы перенаправляем стандартный вывод в файл, а затем перенаправляем дескриптор файла 2 (стандартная ошибка) в дескриптор файла один (стандартный вывод)» и «Обратите внимание, что порядок перенаправлений является значительным. Перенаправление стандартной ошибки должен всегда происходить после перенаправления стандартного вывода, иначе он не работает "
Zanna
@Zanna: Да, мой вопрос возник из-за прочтения этого в TLCL! :) Мне интересно знать, почему это не сработает, то есть как оболочка интерпретирует команды в целом.
Поезд Heartnet
Что ж, в своем вопросе вы говорите обратное: «Это, по-видимому, перенаправляет stderr сначала в stdout ...» - что я понимаю из TLCL, это то, что оболочка отправляет stdout в файл, а затем stderr в stdout (то есть в файл). Моя интерпретация заключается в том, что если вы отправляете stderr в stdout, то он будет отображаться в терминале, и последующее перенаправление stdout не будет включать stderr (т. Е. Перенаправление stderr на экран завершается до того, как произойдет перенаправление stdout?)
Zanna
7
Я знаю, что это старомодная вещь, которую нужно сказать, но ваша оболочка поставляется с руководством, объясняющим эти вещи - например, перенаправление в bashруководстве . Кстати, перенаправления не являются командами.
Reinier Post
Команда не может быть выполнена до того , как переназначения устанавливаются: Когда execv-семейство системного вызова вызываются на самом деле передать подпроцесс прочь к команде быть запущен, оболочка затем выходит из цикла (больше не имеет код , выполняемый в этом процессе ) и не имеет возможности контролировать, что происходит с этого момента; Таким образом, все перенаправления должны быть выполнены до начала выполнения, в то время как оболочка имеет свою собственную копию, запущенную в процессе, в котором она fork()выполняла вашу команду.
Charles Duffy

Ответы:

41

Когда ты бежишь <command> 2>&1 > file.txt stderr, вы перенаправляетесь 2>&1туда, куда в данный момент идет stdout, на ваш терминал. После этого stdout перенаправляется в файл >, но stderr не перенаправляется с ним, поэтому остается выводом терминала.

С <command> > file.txt 2>&1 STDOUT сначала перенаправляется в файл с помощью >, а затем 2>&1перенаправляет стандартный вывод, где стандартный вывод будет, что файл.

На первый взгляд может показаться нелогичным, но когда вы думаете о перенаправлениях таким образом и помните, что они обрабатываются слева направо, это имеет гораздо больше смысла.

Arronical
источник
Это имеет смысл, если вы думаете с точки зрения файловых дескрипторов и вызовов «dup / fdreopen», выполняемых слева направо
Mark K Cowan
20

Это может иметь смысл, если вы проследите это.

В начале stderr и stdout идут в одно и то же (обычно это терминал, который я здесь называю pts):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

Я имею в виду stdin, stdout и stderr по номерам их дескрипторов файлов : они являются дескрипторами файлов 0, 1 и 2 соответственно.

Теперь в первом наборе перенаправлений у нас есть > file.txtи 2>&1.

Так:

  1. > file.txt: В fd/1настоящее время идет file.txt. С >, 1подразумевается дескриптор файла, когда ничего не указано, так что это 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2теперь идет туда, куда fd/1 сейчас идет:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt

С другой стороны, с 2>&1 > file.txtизменением порядка:

  1. 2>&1: fd/2теперь идет туда, куда идет в fd/1данный момент, что означает, что ничего не меняется:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: В fd/1настоящее время идет file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts

Важным моментом является то, что перенаправление не означает, что дескриптор перенаправленного файла будет следовать за всеми будущими изменениями дескриптора целевого файла; это только примет текущее состояние.

Мур
источник
Спасибо, это кажется более естественным объяснением! :) Вы сделали небольшую опечатку во 2.второй части; fd/1 -> file.txtи нет fd/2 -> file.txt.
Поезд Heartnet
11

Я думаю, что это помогает думать, что оболочка сначала установит перенаправление слева, и завершит его перед установкой следующего перенаправления.

Командная строка Linux Уильяма Шоттса говорит

Сначала мы перенаправляем стандартный вывод в файл, а затем перенаправляем дескриптор файла 2 (стандартная ошибка) в дескриптор файла один (стандартный вывод)

это имеет смысл, но потом

Обратите внимание, что порядок перенаправлений является значительным. Перенаправление стандартной ошибки всегда должно происходить после перенаправления стандартного вывода, иначе оно не работает

но на самом деле мы можем перенаправить stdout в stderr после перенаправления stderr в файл с тем же эффектом

$ uname -r 2>/dev/null 1>&2
$ 

Таким образом command > file 2>&1, оболочка отправляет стандартный вывод в файл, а затем отправляет стандартный вывод в стандартный вывод (который отправляется в файл). Принимая во внимание, что в command 2>&1 > fileоболочке сначала перенаправляет stderr на стандартный вывод (т.е. отображает его в терминале, куда обычно идет стандартный вывод), а затем после этого перенаправляет стандартный вывод в файл. TLCL вводит в заблуждение, говоря, что сначала мы должны перенаправить stdout: так как сначала мы можем перенаправить stderr в файл, а затем отправить ему stdout. Чего мы не можем сделать, так это перенаправить стандартный вывод в стандартный поток ошибок или наоборот перед перенаправлением в файл. Другой пример

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

Мы могли бы подумать, что это избавит от stdout в том же месте, что и stderr, но это не так, сначала он перенаправляет stdout на stderr (экран), а затем перенаправляет только stderr, как когда мы пробовали его наоборот ...

Я надеюсь, что это приносит немного света ...

Занна
источник
Гораздо красноречивее!
Arronical
Ах, теперь я понимаю! Большое вам спасибо, @Zanna и @Arronical! Просто отправляюсь в командное путешествие. :)
Поезд Heartnet
@TrainHeartnet - это удовольствие! Надеюсь, вам это нравится так же, как и мне: D
Zanna
@Zanna: Действительно, я! : D
Поезд Heartnet
2
@TrainHeartnet не беспокойтесь, мир разочарования и радости ждет вас!
Arronical
10

Вы уже получили несколько очень хороших ответов. Позвольте мне подчеркнуть, что здесь задействованы две разные концепции, понимание которых очень помогает:

Справочная информация: дескриптор файла против таблицы файлов

Ваш дескриптор файла это просто число 0 ... n, которое является индексом в таблице дескрипторов файлов в вашем процессе. По соглашению, STDIN = 0, STDOUT = 1, STDERR = 2 (обратите внимание, что термины STDINи т. Д. Здесь являются просто символами / макросами, используемыми соглашением в некоторых языках программирования и на страницах руководства, не существует фактического «объекта», называемого STDIN; целью этого обсуждения является STDIN 0 и т. д.).

Эта таблица дескрипторов файлов сама по себе не содержит никакой информации о том, что представляет собой файл. Вместо этого он содержит указатель на другую таблицу файлов; последний содержит информацию о реальном физическом файле (или блочном устройстве, или канале, или обо всем, что Linux может адресовать через файловый механизм) и дополнительную информацию (т. е. для чтения или записи).

Поэтому, когда вы используете >или <в своей оболочке, вы просто заменяете указатель соответствующего дескриптора файла, чтобы указать на что-то еще. Синтаксис 2>&1просто указывает дескриптор 2 на 1 балл. > file.txtпросто открывается file.txtдля записи и позволяет STDOUT (файл-дескриптор 1) указать на это.

Есть и другие полезности, например 2>(xxx) (например: создать новый процесс, работающийxxx , создайте канал, подключите дескриптор файла 0 нового процесса к концу чтения канала и подключите дескриптор файла 2 исходного процесса к концу записи труба).

Это также основа для "волшебства дескриптора файла" в другом программном обеспечении, кроме вашей оболочки. Например, вы можете в своем скрипте Perl dupлицензировать дескриптор файла STDOUT в другой (временный), а затем снова открыть STDOUT для вновь созданного временного файла. С этого момента все выходные данные STDOUT из вашего собственного сценария Perl и все system()вызовы этого сценария попадут в этот временный файл. Когда вы закончите, вы можете вернуть dupсвой STDOUT во временный дескриптор, в котором вы его сохранили, и до того, как все будет как раньше. Тем временем вы даже можете записать в этот временный дескриптор, так что, хотя ваш фактический вывод STDOUT переходит во временный файл, вы все равно можете фактически выводить материал в реальный STDOUT (обычно пользователь).

Ответ

Чтобы применить приведенную выше справочную информацию к вашему вопросу:

В каком порядке оболочка выполняет команды и перенаправляет поток?

Слева направо.

<command> > file.txt 2>&1

  1. fork от нового процесса.
  2. Откройте file.txtи сохраните его указатель в дескрипторе файла 1 (STDOUT).
  3. Укажите STDERR (дескриптор файла 2) на то, на что указывает fd 1 прямо сейчас (который, file.txtразумеется, уже открыт ).
  4. exec <command>

По-видимому, это сначала перенаправляет stderr на стандартный вывод, а затем полученный вывод перенаправляется в файл file.txt.

Это имело бы смысл, если бы была только одна таблица, но, как объяснено выше, их две. Файловые дескрипторы не указывают рекурсивно друг на друга, нет смысла думать «перенаправить STDERR в STDOUT». Правильная мысль: «Направь STDERR туда, куда указывает STDOUT». Если вы измените STDOUT позже, STDERR останется там, где он есть, он волшебным образом не согласуется с дальнейшими изменениями в STDOUT.

Anoe
источник
Голосование за бит "file handle magic" - хотя оно не дает прямого ответа на вопрос, который я узнал сегодня что-то новое ...
Floris
3

Порядок слева направо. Руководство Bash уже охватывает то, что вы спрашиваете. Цитата из REDIRECTIONраздела руководства:

   Redirections  are  processed  in  the
   order they appear, from left to right.

и несколько строк спустя:

   Note that the order of redirections is signifi
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli
   cated from the standard output before the stan
   dard output was redirected to dirlist.

Важно отметить, что перенаправление разрешается в первую очередь перед выполнением любых команд! См. Https://askubuntu.com/a/728386/295286.

Сергей Колодяжный
источник
3

Это всегда слева направо ... кроме случаев, когда

Как и в математике, мы делаем слева направо, за исключением того, что умножение и деление выполняются до сложения и вычитания, за исключением операций внутри круглых скобок (+ -), которые выполняются до умножения и деления.

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

  1. Расширение скобки "{}"
  2. Расширение тильды "~"
  3. Параметр оболочки и выражение переменной "$"
  4. Подстановка команд "$ (команда)"
  5. Арифметическое выражение "$ ((ВЫРАЖЕНИЕ))"
  6. Подстановка процессов, о чем мы здесь говорим "<(LIST)" или "> (LIST)"
  7. Разделение слов "'<пробел> <tab> <newline>'"
  8. Расширение имени файла "*", "?" И т. Д.

Так что всегда слева направо ... кроме случаев, когда ...

WinEunuuchs2Unix
источник
1
Для ясности: замена и перенаправление процесса - это две независимые операции. Вы можете сделать одно без другого. Замена процесса не означает, что bash решает изменить порядок, в котором он обрабатывает перенаправление
muru