Как проанализировать эту команду `{2> & 3« $ @ »&} 3> & 2 2> / dev / null`?

Несколько недель назад я видел странный ответ на вопрос « (Как) тихо запустить задачу (и) в фоновом режиме? ». Это решение кажется неправильным ( см. Мой ответ ), хотя shell, похоже, запускает задачу в фоновом режиме.

I. Проблема: можем ли мы на самом деле перенаправить стандартную ошибку оболочки?

С предлагаемым решением нет объяснения, и анализ не дает надежного ответа.

Ниже вы можете увидеть fragment кода.

# Run the command given by "$@" in the background silent_background() { if [[ -n $BASH_VERSION ]]; then { 2>&3 "$@"& } 3>&2 2>/dev/null fi } 

Проблемная команда: { 2>&3 "$@"& } 3>&2 2>/dev/null .

я) проанализировать

Группа команд ( { ... } ) задает два перенаправления ( 3>&2 и 2>/dev/null ) для одной команды ( # Run the command given by "$@" in the background ). Команда представляет собой asynchronous список ( cmd& ), имеющий одно redirect ( 2>&3 ).

Спецификация POSIX

Каждое redirect должно применяться ко всем командам в составной команде, которые явно не отменяют это redirect.

Стандартное redirect ошибок 2>/dev/null переопределяется redirectм, связанным с асинхронным списком 2>&3 .

Конкретные случаи

В первом случае стандартная ошибка перенаправляется в /dev/null тогда как во втором случае стандартная ошибка команды по-прежнему связана с терминалом.

 prompt% { grep warning system.log& } 2>/dev/null prompt% { 2>&3 grep warning system.log& } 3>&2 2>/dev/null grep: system.log: No such file or directory 

Ниже мы видим похожие случаи. В первом случае стандартный вывод команды по-прежнему подключен к терминалу. Во втором случае стандартное redirect вывода команды изменяется: >echo.txt переопределяется >print.txt .

 prompt% { >&3 echo some data...& } 3>&1 >echo.txt [1] 3842 some data... prompt% file echo.txt echo.txt: empty prompt% { >&3 echo some data...& } 3>print.txt >echo.txt [1] 2765 prompt% file echo.txt echo.txt: empty prompt% cat print.txt some data... 

II) Наблюдения

Как упоминалось ранее, команда запускается в фоновом режиме. Точнее, уведомление о фоновом задании, например [1] 3842 , не отображается.

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

{ redir_3 cmd& } redir_1 redir_2 эквивалентно cmd& .

iii) Интерпретация

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

Можете ли вы объяснить, как это происходит?

II. Hypothese (ы)

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

  • Сообщения оболочки могут быть отправлены на стандартную ошибку оболочки.

Другой контекст: asynchronous список выполняется в подоболочке. Команда g не существует.

 prompt% ( g& ) g: command not found 

Асинхронные команды, команды, сгруппированные в скобках, …, выполняются в среде подоболочки, которая является дубликатом среды оболочки …

Подshell наследует значение своего стандартного streamа ошибок от своей родительской оболочки.

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

Вы пропустили самый важный момент, redirect оболочки применяется в порядке слева направо.

В:

 { 2>&3 "$@"& } 3>&2 2>/dev/null 

Вся команда группы выполняется с:

  • Файловый дескриптор 3 => стандартная ошибка, которая на данный момент является терминальной .
  • Файловый дескриптор 2 (стандартная ошибка) => / dev / null

Итак, когда команда внутри группировки запускается:

  • Стандартная ошибка => Дескриптор файла 3, который указывает на терминал.

Так что если "$@"& распечатать что-либо со своей стандартной ошибкой, вывод выводится на терминал.


Для ваших конкретных случаев:

 { grep warning system.log& } 2>/dev/null 

{ grep warning system.log& } запускается со стандартной ошибкой, указывает на /dev/null . grep не отменяет никакого перенаправления, поэтому его стандартная ошибка такая же, как у {...} , и перенаправляется в /dev/null , вы не получаете вывод на терминал.

В:

 { 2>&3 grep warning system.log& } 3>&2 2>/dev/null 

Стандартная ошибка grep перенаправляется в файловый дескриптор 3, который указывает на терминал, как объяснено выше, поэтому вы получили вывод на терминал.

В интерактивном Bash foo & запускает foo в фоновом режиме и печатает его идентификатор задания и идентификатор процесса со стандартной ошибкой оболочки.

foo 2>/dev/null & запускает foo в фоновом режиме, его stderr перенаправляется в /dev/null , но все равно выводит идентификатор задания и идентификатор процесса в стандартную ошибку оболочки (не перенаправленный stderr из foo ).

{ foo & } 2>/dev/null сначала перенаправляет stderr в /dev/null , затем внутренняя часть {} обрабатывается с применением этого перенаправления. Затем foo запускается в фоновом режиме, и идентификатор задания и идентификатор процесса выводятся на перенаправленный теперь stderr оболочки (т. /dev/null ).

Руководство Bash подразумевает, что перенаправления вне группы применяются к группе в целом :

Когда команды сгруппированы, перенаправления могут применяться ко всему списку команд.

Мы можем проверить redirect, примененное к группе, а также перенаправить вывод идентификатора задания:

 $ {sleep 9 &} 2> temp
 [здесь нет вывода]
 $ cat temp
 [1] 8490

Когда команда в конечном итоге завершается, уведомление об этом появляется на терминале, однако: (Или, скорее, текущему stderr оболочки, что бы это ни было.)

 $ kill %1 [1]+ Terminated sleep 99 

В { ... } 3>&2 2>/dev/null , fd 3 перенаправляется туда, куда когда-либо указывает stderr, затем stderr перенаправляется в /dev/null . Предполагая, что stderr изначально указывал на терминал, результатом будет 3 -> terminal, 2 -> /dev/null . Эти перенаправления применяются к группе.

Если внутри группы { foo 2>&3 & } , foo запускается в фоновом режиме, его stderr указывает на fd 3 группы, а идентификатор задания и идентификатор процесса печатаются в stderr группы.

Соединяя эти два foo , stderr foo указывает на то, куда указывает fd 3 группы, то есть на исходный stderr, а идентификатор задания печатается в fd 2 группы, то есть /dev/null .

Таким образом, в действительности { 2>&3 foo & } 3>&2 2>/dev/null перенаправляет идентификатор задания и идентификатор процесса, напечатанные оболочкой, в /dev/null и направляет stderr foo вокруг этого перенаправления:

 foo's stdout -> group's fd 1 -> original stdout (never redirected) foo's stderr -> group's fd 3 -> original stderr job id from group -> group's fd 2 -> /dev/null 

Вам не хватает, что команда { выполняется с обнуляемым stderr , она запускает содержащиеся в фоновом режиме команды с восстановленным fd, но это та команда, которая запускает команды и выдает запущенные командой сообщения обратной связи, и она работает с обнуляемой командой.