Порядок ввода/вывода
Я играю с перенаправлением оболочки i/o. Команды, которые я пробовал (в bash):
ls -al *.xyz 2>&1 1> files.lst
и
ls -al *.xyz 1> files.lst 2>&1
В текущей папке нет файла *.xyz
.
Эти команды дают мне разные результаты. Первая команда показывает сообщение об ошибке ls: *.xyz: No such file or directory
на экране. Но второй печатает это сообщение об ошибке в файле. Почему первая команда не смогла записать вывод ошибки в файл?
Ответы
Ответ 1
Эта ошибка:
ls: *.xyz: No such file or directory
записывается на stderr
на ls
двоичный.
Однако в этой команде:
ls -al *.xyz 2>&1 1> files.lst
Сначала вы перенаправляете stderr
в stdout
, который по умолчанию переходит на tty
(terminal)
И затем вы перенаправляете stdout
в файл files.lst
, однако помните, что stderr не перенаправляется в файл, поскольку у вас есть stderr
до stdout
перенаправление до stdout
to file
. В этом случае ваш stderr
все равно будет записан в tty
.
Однако во втором случае вы меняете порядок перенаправления (сначала stdout
на file
, а затем stderr
на stdout
) и это правильно переадресует stderr
на file
, который также используется stdout
.
Ответ 2
Bash manual имеет ясный пример (аналогичный вашему), чтобы показать, что порядок имеет значение, а также объясняет разницу. Здесь была взята соответствующая часть (выделено мной):
Обратите внимание, что порядок перенаправления значителен. Например, Команда
ls > dirlist 2 > & 1
направляет как стандартный вывод (дескриптор файла 1), так и стандартную ошибку (дескриптор файла 2) в файл dirlist, а команда
ls 2 > & 1 > dirlist
направляет только стандартный вывод в файл dirlist, потому что стандарт ошибка была сделана копией стандартного вывода перед стандартом выход был перенаправлен на dirlist.
Этот пост объясняет это с точки зрения POSIX.
Конфузии происходят из-за ключевой разницы. >
перенаправляет, не делая левый операнд (stderr
) указывать на правый операнд (stdout
), но сделав копию правильного операнда и назначив его влево. Концептуально присваивание копией, а не ссылкой.
Итак, чтение слева направо, как это интерпретируется Bash: ls > dirlist 2>&1
означает перенаправление stdout
в файл dirlist
, за которым следует перенаправление stderr
на все stdout
в настоящее время указывая на (который уже является файлом). OTOH ls 2>&1 > dirlist
перенаправляет stderr
на то, что в настоящее время указывает stdout
(это экран/терминал), а затем перенаправляет stdout
в dirlist
.
Ответ 3
Перенаправления:
- обрабатывается слева направо.
- интерпретируется итеративно:
- предыдущее перенаправление может повлиять на более поздний:
- если ранее перенаправление перенаправило данный поток (идентифицированный номером дескриптора файла, например
1
для stdout (по умолчанию) и 2
для stderr), последующие перенаправления, предназначенные для этого потока, относятся к уже существующему потоку, перенаправленная версия.
- но не наоборот - последующее перенаправление не оказывает обратного действия на цель предыдущего перенаправления:
- например, если вы укажете файловый дескриптор
1
как цель в более раннем перенаправлении, то что 1
означает, что в это время блокируется, даже если 1
перенаправляется позже.
- Обратите внимание, однако, что вывод фактически не отправляется до тех пор, пока все перенаправления не будут установлены, и что любые выходные файлы перенаправления создаются или усекаются до начала выполнения команды (это причина, по которой вы не можете читать и перенаправить вывод в тот же файл с помощью одной команды).
Применяется к примеру из вопроса:
-
>file 2>&1
:
-
>file
сначала перенаправляет stdout (дескриптор файла 1
, подразумеваемый не префиксом >
с номером дескриптора файла) для вывода файла file
-
2>&1
затем перенаправляет stderr (2
) на уже перенаправленный stdout (1
).
- Чистый эффект заключается в том, что оба исходных потока заканчиваются на
file
.
-
2>&1 >file
:
-
2>&1
сначала перенаправляет stderr на исходный stdout; поскольку файловый дескриптор 2
участвует в отсутствии дальнейших перенаправлений, вывод stderr будет идти к тому, что было определено stdout как в этой точке, то есть исходное stdout, учитывая, что это первое перенаправление.
- Технически, оригинальный дескриптор файла stdout дублируется, и этот дубликат - это то, что stderr ссылается на него, что объясняет, почему на него не влияет последующее перенаправление stdout.
-
>file
затем перенаправляет исходный stdout на file
- но это больше не влияет на уже заблокированное перенаправление stderr.
- Чистый эффект заключается в том, что только вывод sent-direct-to-stdout записывается в
file
, в то время как вывод send-to-stderr выводится на (исходный, unredirected) stdout.
Ответ 4
Потому что порядок имеет значение.
В первом случае вы сначала перенаправляете stderr (2) на stdout (1).
Затем вы перенаправляете (1) в файл. Но stderr (2) по-прежнему указывает на стандартный вывод оболочки, запускающей команду. Указание (1) на файл в этом случае не изменяет устройство вывода, на которое (2) направлено, поэтому оно все равно переходит к терминалу.
Во втором случае вы перенаправляете stdout (1) в файл. Затем вы указываете stderr (2) на то же место, на которое указывает 1, который является файлом, поэтому сообщение об ошибке переходит к файлу.