3>&4-
это расширение ksh93, также поддерживаемое bash, и оно сокращенно 3>&4 4>&-
, то есть 3 теперь указывает на то место, где раньше использовалось 4, а 4 теперь закрыто, поэтому то, на что указывал 4, теперь переместилось на 3.
Типичное использование будет в тех случаях, когда вы дублируете stdin
или stdout
сохраняете копию и хотите восстановить ее, как в:
Предположим, вы хотите перехватить stderr команды (и только stderr), оставив stdout в одной переменной.
Подстановка команды var=$(cmd)
, создает канал. Конец записи канала становится cmd
stdout (дескриптор файла 1), а другой конец читается оболочкой для заполнения переменной.
Теперь, если вы хотите , stderr
чтобы перейти к переменной, вы можете сделать: var=$(cmd 2>&1)
. Теперь и fd 1 (stdout), и 2 (stderr) переходят в канал (и, в конечном итоге, к переменной), что составляет лишь половину того, что мы хотим.
Если мы делаем var=$(cmd 2>&1-)
(сокращение от var=$(cmd 2>&1 >&-
), теперь только cmd
канал 's stderr' идет в канал, но fd 1 закрыт. Если cmd
попытается записать какой-либо вывод, который вернется с EBADF
ошибкой, если он откроет файл, он получит первый свободный fd, и открытый файл будет назначен ему, stdout
если только команда не защитит от этого! Не то, что мы хотим.
Если мы хотим, чтобы стандартный вывод cmd
был оставлен в покое, то есть указывал на тот же ресурс, на который он указывал вне подстановки команд, то нам нужно каким-то образом перенести этот ресурс в подстановку команд. Для этого мы можем сделать копию stdout
внешней подстановки команды, чтобы взять ее внутрь.
{
var=$(cmd)
} 3>&1
Какой способ написания чище:
exec 3>&1
var=$(cmd)
exec 3>&-
(который также имеет преимущество восстановления fd 3 вместо закрытия в конце).
Затем на {
(или exec 3>&1
) и до }
, оба fd 1 и 3 указывают на один и тот же ресурс, на который fd 1 указал изначально. fd 3 также будет указывать на этот ресурс внутри подстановки команд (подстановка команд перенаправляет только fd 1, stdout). Итак, выше, cmd
у нас есть для fds 1, 2, 3:
- труба к вар
- нетронутый
- так же, как то, что 1 указывает на замещение команды
Если мы изменим это на:
{
var=$(cmd 2>&1 >&3)
} 3>&1-
Тогда это становится:
- так же, как то, что 1 указывает на замещение команды
- труба к вар
- так же, как то, что 1 указывает на замещение команды
Теперь у нас есть то, что мы хотели: stderr идет в трубу, а stdout остается нетронутым. Тем не менее, мы просочились в фд 3 к cmd
.
Хотя команды (по соглашению) предполагают, что fds 0 - 2 открыты и являются стандартным вводом, выводом и ошибкой, они не предполагают ничего из других fds. Скорее всего, они оставят этот 3 нетронутым. Если им нужен другой файловый дескриптор, они просто сделают, open()/dup()/socket()...
который вернет первый доступный файловый дескриптор. Если (как и скрипт оболочки, который делает это exec 3>&1
) им нужно использовать это fd
специально, они сначала назначат его чему-то (и в этом процессе ресурс, удерживаемый нашим fd 3, будет освобожден этим процессом).
Рекомендуется закрывать этот fd 3, поскольку cmd
он не используется, но нет ничего страшного, если мы оставим его назначенным до вызова cmd
. Проблемы могут заключаться в том, что cmd
(и, возможно, другие процессы, которые он порождает) имеет меньше доступного fd. Потенциально более серьезная проблема заключается в том, что ресурс, на который указывает этот fd, может в конечном итоге удерживаться процессом, порожденным этим cmd
в фоновом режиме. Это может быть проблемой, если этот ресурс является каналом или другим каналом межпроцессного взаимодействия (например, когда ваш скрипт выполняется как script_output=$(your-script)
), так как это будет означать, что чтение процесса с другого конца никогда не будет видеть конец файла до тех пор, пока фоновый процесс завершается.
Итак, здесь лучше написать:
{
var=$(cmd 2>&1 >&3 3>&-)
} 3>&1
Который bash
может быть сокращен до:
{
var=$(cmd 2>&1 >&3-)
} 3>&1
Подводя итог, почему это редко используется:
- это нестандартный и просто синтаксический сахар. Вы должны сбалансировать сохранение нескольких нажатий клавиш, чтобы сделать ваш сценарий менее переносимым и менее очевидным для людей, не привыкших к этой необычной функции.
- Необходимость закрытия исходного fd после дублирования часто упускается из виду, потому что большую часть времени мы не страдаем от последствий, поэтому мы просто делаем
>&3
вместо >&3-
или >&3 3>&-
.
Доказательством того, что он редко используется, как вы узнали, является то, что это фальшивка в bash . В bash compound-command 3>&4-
или any-builtin 3>&4-
листья fd 4 закрыты даже после compound-command
или any-builtin
вернулся. Патч , чтобы исправить эту проблему сейчас (2013-02-19) доступен.