Это опечатка в разделе перенаправления руководства Bash?


13
Note that the order of redirections is significant.  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 duplicated from the standard output before the standard
   output was redirected to dirlist.

Теперь эта последняя часть сбивает меня с толку. В этом случае любая стандартная ошибка будет распечатана на терминале, а любой STDOUT будет отправлен в файл dirlist. Вот что произойдет, но я не так понимаю руководство.

Похоже, что он должен сказать «потому что стандартная ошибка была продублирована со стандартного выхода ПОСЛЕ того, как стандартный вывод был перенаправлен на dirlist». Если STDERR был отправлен в STDOUT до того, как STDOUT был направлен в файл, то не будет ли файл содержать STDOUT AND STDERR?

Может кто-нибудь прояснить это для меня? Это просто плохое понимание с моей стороны? Использование слова «дублирование» мне кажется немного странным в этом контексте. Возможно, это бросает меня.



1
Классический случай смешивания операций, которые «по значению» против «по ссылке». Когда вы дублируете файловый дескриптор, это операция по значению . В программировании после a = 1; b = a; a = 2того , как вы ожидаете, a == 2 && b == 1чтобы быть правдой. Перенаправление 2>&1аналогично b = aназначению - оно по значению, а не по ссылке. 2>&1не связывает файловый дескриптор 2 с файловым дескриптором 1 на протяжении всей вечности - они по-прежнему являются двумя различными файловыми дескрипторами, которые указывают на один и тот же файл.
jw013

Ответы:


23

Дублирование действительно важная часть здесь.

Давайте посмотрим, куда идут файловые дескрипторы до перенаправления. Обычно это текущий терминал, например:

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

Теперь, если мы вызываем ls -lбез перенаправления, выходные сообщения и сообщения об ошибках отправляются в мой терминал под /dev/pts/1.

Если мы сначала перенаправим STDOUTфайл ( ls -l > dirlist), он будет выглядеть так:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Когда мы затем перенаправить STDERRк дубликату из STDOUTфайлового дескриптора «S ( ls -l > dirlist 2>&1), STDERRидет к дубликату /home/bon/dirlist:

STDOUT ---> /home/bon/dirlist
STDERR ---> /home/bon/dirlist

Если мы сначала перенаправим STDERRна дубликат STDOUTдескриптора файла ( ls -l 2>&1):

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

а затем STDOUT в файл ( ls -l 2>&1 > dirlist), мы получили бы это:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Здесь STDERRвсе еще идет в терминал.

Видите ли, порядок на странице руководства правильный.


Тестирование перенаправления

Теперь вы можете проверить это сами. Используя ls -l /proc/$$/fd/, вы видите, куда STDOUT(с fd 1) и STDERR(с fd 2) идут текущие процессы:

$ ls -l /proc/$$/fd/
total 0
lrwx------ 1 bon bon 64 Jul 24 18:19 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 07:41 2 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 255 -> /dev/pts/1

Давайте создадим небольшой сценарий оболочки, который показывает, куда указываются ваши файловые дескрипторы. Таким образом, мы всегда получаем состояние при вызове ls, включая любое перенаправление из вызывающей оболочки.

$ cat > lookfd.sh
#!/bin/sh
ls -l /proc/$$/fd/
^D
$ chmod +x lookfd.sh

(С помощью CtrlDвы отправляете конец файла и прекращаете catчтение команды STDIN.)

Теперь вызовите этот скрипт с различными комбинациями перенаправления:

$ ./lookfd.sh 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:08 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:08 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh 2>&1 > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out 2>&1
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:11 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:11 1 -> /home/bon/foo.out
l-wx------ 1 bon bon 64 Jul 24 19:11 2 -> /home/bon/foo.out
lr-x------ 1 bon bon 64 Jul 24 19:11 255 -> /home/bon/lookfd.sh

Вы можете видеть, что файловые дескрипторы 1 (для STDOUT) и 2 (для STDERR) различаются. Ради интереса вы также можете перенаправить STDINи посмотреть результат:

$ ./lookfd.sh < /dev/zero
total 0
lr-x------ 1 bon bon 64 Jul 24 19:18 0 -> /dev/zero
lrwx------ 1 bon bon 64 Jul 24 19:18 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:18 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:18 255 -> /home/bon/lookfd.sh

(Вопрос, оставленный читателю: где дескриптор файла 255 указывает? ;-))


+1 - отличный ответ. Чрезвычайно хорошо написанные и потрясающие примеры. Спасибо!!!
SLM

Я понимаю, я думаю, что мое недоразумение заключалось в том, что перенаправление будет постоянным для всех следующих команд, так что любой STDERR для остальной части строки будет переходить в STDOUT.
Грегг Левенталь

2

Нет, руководство верно.

Если сначала 1 указывает на терминал, а 2 также на терминал, то:

command  2>&1   1>somewhere

Оценка перенаправления будет происходить слева направо.

Таким образом, он сначала оценит 2>&1, и, таким образом, сначала скопирует то, 1на что указывает fd (т. Е. Файловый дескриптор the terminal, обычно / dev / tty), в fd 2.

Таким образом, в этот момент fd 2теперь указывает на то, где fd 1раньше указывал ( the terminal)

И ТОГДА он оценивает 1>somewhereчасть и, таким образом, скопирует файловый дескриптор somewhereв fd 1(поэтому в этот момент fd 1теперь указывает на somewhere, а fd 2все еще указывает the terminal)

Таким образом, он действительно выводит 1 в «где-то» и 2 в терминал, так как 2 дублировалось с 1 ДО 1, было изменено.

Другой порядок:

command  1>somewhere 2>&1

сначала перенаправит fd 1в somewhere, а затем скопирует ту же ссылку в fd 2, поэтому в конце 2 также указывает на somewhere. Но они не «связаны» с этого момента. Каждый может быть перенаправлен отдельно.

например:

command  1>somewhere 2>&1
exec 2>/dev/null

В конце этого, fd 1указывает на somewhere, а fd указывает 2на/dev/null

Обычные имена для fd 1- это STDOUT (стандартный вывод), а обычное имя для fd 2- это STDERR (стандартная ошибка, поскольку обычно она используется для отображения ошибок без вмешательства в STDOUT).


@ Michael-mrozek: спасибо за редактирование, но я настаиваю на том, чтобы сказать «копировать» вместо «дублировать», так как «дубликат» может заставить поверить, что отныне оба являются «одним и тем же», что не соответствует действительности. пример:: cmd 1>somewhere 2>&1 ; exec 2>/dev/nullпосле exec только 2 были перенаправлены в / dev / null (1 все еще собирается куда-то). Мне нужна помощь, чтобы придумать способ сказать «на что указывает 1» вместо «fd 1», однако ... поскольку это также сбивает с толку ...
Оливье Дюлак

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

дох ... ^^ извините. И я снова отредактировал, чтобы переформулировать, чтобы сделать более точным то, что копируется в то, что ^^
Оливье Дюлак

1

Я думаю, что запутанной частью здесь является неправильное представление о том, что перенаправление stderr на стандартный вывод фактически связывает два потока.

Совершенно разумная идея, но то, что происходит, когда вы пишете, 2>&1это то, что stderr заглядывает в то, что пишет stdout и записывает в то же самое место. Поэтому, если вы впоследствии скажете stdout идти писать куда-нибудь еще, это не повлияет на назначение stderr, которое уже было перемещено.

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


0

РАЗМНОЖЕНИЕ ...

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

Принятый ответ хороший, но слишком длинный, и он подчеркивает «дублирование».

Вопрос мудро заканчивается:

Использование дублирования слов кажется немного странным для меня в этом контексте. Возможно, это бросает меня.

Я использую нотацию bash и определяю переменные "one" и "two" как дескрипторы файлов "1" и "2". (Выходной) оператор перенаправления >является назначением =. &и $означает «значение».

Примеры man bash (по умолчанию добавлено «1»)

ls 1>dirlist 2>&1      # both to dirlist
ls 2>&1 1>dirlist      # 1 to dirlist, 2 stays on tty/screen 

стать:

one=dirlist  two=$one

и

two=$one   one=dirlist

И даже это не является автоматическим для меня и некоторых других, я думаю. Первая строка оставляет вас, $oneи $twoоба содержат "dirlist". Конечно.

Вторая строка начинается с бесполезного назначения. Оба начинаются по определению с «TTY» (немного символично) в качестве направления ; это назначение не изменяет значения, а с переменными, как с файловыми дескрипторами, ничто не связано магическим образом. На переменную twoне влияет следующее one=dirlist. Конечно, нет.

Кто-то здесь (6 лет назад) предложил «указать на» вместо «копировать» или «дублировать», а затем понял: это тоже может сбить с толку.

Эта семантика дублирования или указателя даже не нужна. Может быть, это амперсанд, который требует большего внимания. Оператор "значение" / токен / что угодно.

Если - и только если - вы ищете способ получить неожиданный номер задания на своей консоли , тогда сообщение «выполнено» плюс в качестве бонуса файл с именем «2», тогда вы идете:

ls 1>2& 2>/dev/null

Он естественно читается как « копировать» / «дублировать» 1 к 2, а затем оба вместе к нулю . Но идея неверна, а также синтаксис. (но нет синтаксической ошибки, она действительна)

Правильный способ спланировать это состоит в том, чтобы перенаправить любое из двух значений на ноль, а затем перенаправить ДРУГОЕ в то же место:

ls 1>/dev/null 2>&1
# or 
ls 2>/dev/null 1>&2

(ведущая "1" может быть оставлена ​​в стороне)

(ОК. A не слишком длинный, но слишком большой список - или: очень хорошая визуализация, не очень хорошее объяснение)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.