3>&4-
é uma extensão ksh93 também suportada pelo bash e que é a abreviação de 3>&4 4>&-
3 pontos agora para onde 4 costumava ser e 4 agora está fechado, então o que foi apontado por 4 agora mudou para 3.
O uso típico seria nos casos em que você duplicou stdin
ou stdout
salvou uma cópia dela e deseja restaurá-la, como em:
Suponha que você queira capturar o stderr de um comando (e somente stderr) enquanto deixa o stdout sozinho em uma variável.
Substituição de comando var=$(cmd)
, cria um pipe. A extremidade de gravação do pipe torna-se cmd
stdout (descritor de arquivo 1) e a outra extremidade é lida pelo shell para preencher a variável.
Agora, se você quiser stderr
ir para a variável, você poderia fazer: var=$(cmd 2>&1)
. Agora, tanto o fd 1 (stdout) quanto o 2 (stderr) vão para o pipe (e eventualmente para a variável), que é apenas metade do que queremos.
Se o fizermos var=$(cmd 2>&1-)
(abreviação de var=$(cmd 2>&1 >&-
), agora apenas cmd
o stderr vai para o tubo, mas o fd 1 está fechado. Se cmd
tentar gravar qualquer saída, que retornaria com um EBADF
erro, se abrir um arquivo, ele receberá o primeiro fd livre e o arquivo aberto será atribuído a ele, a stdout
menos que o comando se proteja disso! Também não é o que queremos.
Se queremos que o stdout cmd
seja deixado sozinho, ou seja, aponte para o mesmo recurso que apontou para fora da substituição de comando, precisamos, de alguma forma, trazer esse recurso para dentro da substituição de comando. Para isso, podemos fazer uma cópia de stdout
fora da substituição de comando para levá-la para dentro.
{
var=$(cmd)
} 3>&1
Qual é uma maneira mais limpa de escrever:
exec 3>&1
var=$(cmd)
exec 3>&-
(que também tem o benefício de restaurar o fd 3 em vez de fechá-lo no final).
Em seguida, no {
(ou no exec 3>&1
) e no até }
, os pontos 1 e 3 apontam para o mesmo recurso apontado inicialmente por fd 1. O fd 3 também apontará para esse recurso dentro da substituição de comando (a substituição de comando redireciona apenas o fd 1, stdout). Então, acima, pois cmd
temos os fds 1, 2, 3:
- o tubo para var
- intocado
- igual ao que 1 aponta para fora da substituição de comando
Se mudarmos para:
{
var=$(cmd 2>&1 >&3)
} 3>&1-
Então se torna:
- igual ao que 1 aponta para fora da substituição de comando
- o tubo para var
- igual ao que 1 aponta para fora da substituição de comando
Agora, temos o que queríamos: stderr vai para o pipe e stdout é deixado intocado. No entanto, estamos vazando esse fd 3 para cmd
.
Enquanto os comandos (por convenção) assumem que os fds de 0 a 2 estão abertos e são entrada, saída e erro padrão, eles não assumem nada de outros fds. Muito provavelmente eles deixarão esse fd 3 intocado. Se eles precisarem de outro descritor de arquivo, eles farão um open()/dup()/socket()...
que retornará o primeiro descritor de arquivo disponível. Se (como um script de shell que faz isso exec 3>&1
) eles precisarem usar isso fd
especificamente, eles primeiro o atribuirão a algo (e nesse processo, o recurso mantido pelo nosso fd 3 será liberado por esse processo).
É uma boa prática encerrar o fd 3, pois cmd
não o utiliza, mas não é grande coisa se o deixarmos designado antes de ligar cmd
. Os problemas podem ser: que cmd
(e potencialmente outros processos que ele gera) tem um fd a menos disponível. Um problema potencialmente mais sério é se o recurso apontado por fd pode acabar retido por um processo gerado por ele cmd
em segundo plano. Pode ser uma preocupação se esse recurso é um canal ou outro canal de comunicação entre processos (como quando seu script está sendo executado como script_output=$(your-script)
), pois isso significa que a leitura do processo pela outra extremidade nunca verá o final do arquivo até que processo em segundo plano termina.
Então aqui é melhor escrever:
{
var=$(cmd 2>&1 >&3 3>&-)
} 3>&1
Com o qual, bash
pode ser reduzido para:
{
var=$(cmd 2>&1 >&3-)
} 3>&1
Para resumir os motivos pelos quais raramente é usado:
- é açúcar não-padrão e apenas sintático. Você precisa equilibrar a economia de algumas teclas para tornar seu script menos portátil e menos óbvio para as pessoas que não estão acostumadas a esse recurso incomum.
- A necessidade de fechar o fd original após duplicá-lo é muitas vezes negligenciada, porque na maioria das vezes não sofremos com a conseqüência, então apenas o fazemos em
>&3
vez de >&3-
ou >&3 3>&-
.
Prova de que raramente é usada, como você descobriu, é falso no bash . No bash compound-command 3>&4-
ou nas any-builtin 3>&4-
folhas, o fd 4 é fechado mesmo depois compound-command
ou any-builtin
voltou. Um patch para corrigir o problema está agora disponível (19/02/2013).