Перенаправление только stderr на канал

3

Этот фрагмент кода взят из Руководства по расширенному написанию сценариев Bash .

# Redirecting only stderr to a pipe.

exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

# Thanks, S.C.

В комментариях объясняется, что код закрывает fd 3 только для «grep». но закрывает FD 3 дважды. Почему мы должны делать это? Это неправильно закрывать fd 3 только один раз для «grep», как это?

ls -l 2>&1 >&3 | grep bad 3>&-    
MS.Kim
источник

Ответы:

5

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

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

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

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

Лучший способ написать это было бы:

{ ls -l 2>&1 >&3 3>&- | grep bad 3>&-; } 3>&1

Таким образом, fd 3 временно перенаправляется только на время действия этой группы команд (и восстанавливается впоследствии или закрывается).

Увидеть разницу:

$ { ls -l /proc/self/fd 2>&1 >&3 3>&- | grep bad 3>&-; } 3>&1
total 0
lrwx------ 1 stephane stephane 64 Apr  2 09:29 0 -> /dev/pts/4
lrwx------ 1 stephane stephane 64 Apr  2 09:29 1 -> /dev/pts/4
l-wx------ 1 stephane stephane 64 Apr  2 09:29 2 -> pipe:[575886]
lr-x------ 1 stephane stephane 64 Apr  2 09:29 3 -> /proc/20918/fd/
$ { ls -l /proc/self/fd 2>&1 >&3  | grep bad 3>&-; } 3>&1
total 0
lrwx------ 1 stephane stephane 64 Apr  2 09:29 0 -> /dev/pts/4
lrwx------ 1 stephane stephane 64 Apr  2 09:29 1 -> /dev/pts/4
l-wx------ 1 stephane stephane 64 Apr  2 09:29 2 -> pipe:[575900]
lrwx------ 1 stephane stephane 64 Apr  2 09:29 3 -> /dev/pts/4
lr-x------ 1 stephane stephane 64 Apr  2 09:29 4 -> /proc/20926/fd/

Во втором вызове lsfd 3 также был открыт для терминала (который был открыт на stdout во время запуска конвейера) без веской причины.

Обратите внимание, что в ksh93, с exec, вам не нужно закрывать эти fds, поскольку fds, кроме 0, 1, 2, автоматически закрываются при выполнении команды.

$ ksh93 -c 'exec 3>&1; ls -l /dev/fd/'
total 0
lrwx------ 1 stephane stephane 64 Apr  2 09:34 0 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 1 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 2 -> /dev/pts/16
lr-x------ 1 stephane stephane 64 Apr  2 09:34 3 -> /proc/21105/fd
$ ksh93 -c 'exec 3>&1; ls -l /dev/fd/ 3>&3'
total 0
lrwx------ 1 stephane stephane 64 Apr  2 09:34 0 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 1 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 2 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 3 -> /dev/pts/16
lr-x------ 1 stephane stephane 64 Apr  2 09:34 4 -> /proc/21108/fd

Я не знаю, почему он говорит (но не «ls») выше, звучит как ошибка (возможно, моя ;-).

Стефан Шазелас
источник
3
Эй, так что же #Thanks, S.Cтам значит?
Златовласка
Правильно ли я понимаю, что канал создается до закрытия fd3 3>&-(для lsчасти). Описано ли где-нибудь, в каком порядке создаются дескрипторы и / или каналы?
Выгоров
2
@vyegorov, канал должен быть создан до разветвления (в противном случае две команды не могут получить к нему доступ), так что до повторного ввода. Порядок - это труба, вилка, а затем перенаправление, применяемое в каждой части канала (в другом процессе), и затем выполняются команды (после разветвлений разностные процессы работают независимо, поэтому можно все еще выполнять перенаправления, пока другой уже выполнил свою команду).
Стефан Шазелас
3

fd3Дважды закройте, потому что вы раскручиваете две подоболочки в скрипте, каждая подоболочка наследуется и копируется дескриптор файла родителя. Закрытие fd3в каждой подоболочке не влияет на других (и на родителя тоже).

Таким образом, строка комментария очень непонятна и вызывает заблуждение.

Если вы хотите перенаправить только stderна канал, вы можете использовать process substitution:

ls -l 2> >(grep bad)

или поменяйте местами stderrи stdout:

ls -l 3>&1 1>&2 2>&3 | grep bad
cuonglm
источник