Вы уже получили несколько очень хороших ответов. Позвольте мне подчеркнуть, что здесь задействованы две разные концепции, понимание которых очень помогает:
Справочная информация: дескриптор файла против таблицы файлов
Ваш дескриптор файла это просто число 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
fork
от нового процесса.
- Откройте
file.txt
и сохраните его указатель в дескрипторе файла 1 (STDOUT).
- Укажите STDERR (дескриптор файла 2) на то, на что указывает fd 1 прямо сейчас (который,
file.txt
разумеется, уже открыт ).
exec
<command>
По-видимому, это сначала перенаправляет stderr на стандартный вывод, а затем полученный вывод перенаправляется в файл file.txt.
Это имело бы смысл, если бы была только одна таблица, но, как объяснено выше, их две. Файловые дескрипторы не указывают рекурсивно друг на друга, нет смысла думать «перенаправить STDERR в STDOUT». Правильная мысль: «Направь STDERR туда, куда указывает STDOUT». Если вы измените STDOUT позже, STDERR останется там, где он есть, он волшебным образом не согласуется с дальнейшими изменениями в STDOUT.