3>&4-
es una extensión ksh93 que también es compatible con bash y que es la abreviatura de 3>&4 4>&-
3 ahora apunta a donde solía 4 y 4 ahora está cerrado, por lo que lo que señaló 4 ahora se ha movido a 3.
El uso típico sería en casos donde ha duplicado stdin
o stdout
para guardar una copia y desea restaurarlo, como en:
Suponga que desea capturar el stderr de un comando (y solo stderr) mientras deja stdout solo en una variable.
Sustitución de comandos var=$(cmd)
, crea una tubería. El final de escritura de la tubería se convierte cmd
en stdout (descriptor de archivo 1) y el otro extremo es leído por el shell para completar la variable.
Ahora, si quieres stderr
ir a la variable, se podría hacer: var=$(cmd 2>&1)
. Ahora tanto fd 1 (stdout) como 2 (stderr) van a la tubería (y eventualmente a la variable), que es solo la mitad de lo que queremos.
Si lo hacemos var=$(cmd 2>&1-)
(abreviatura de var=$(cmd 2>&1 >&-
), ahora solo cmd
stderr va a la tubería, pero fd 1 está cerrado. Si cmd
intenta escribir alguna salida, eso devolvería un EBADF
error, si abre un archivo, obtendrá el primer fd libre y se le asignará el archivo abierto a stdout
menos que el comando lo proteja. No es lo que queremos tampoco.
Si queremos que el stdout de cmd
se quede solo, es decir, apuntar al mismo recurso que apuntó fuera de la sustitución del comando, entonces necesitamos de alguna manera llevar ese recurso dentro de la sustitución del comando. Para eso podemos hacer una copia de stdout
fuera de la sustitución del comando para llevarlo dentro.
{
var=$(cmd)
} 3>&1
Cuál es una forma más limpia de escribir:
exec 3>&1
var=$(cmd)
exec 3>&-
(que también tiene el beneficio de restaurar fd 3 en lugar de cerrarlo al final).
Luego, sobre {
(o el exec 3>&1
) y hasta el }
, tanto fd 1 como 3 apuntan al mismo recurso al que fd 1 apuntó inicialmente. fd 3 también apuntará a ese recurso dentro de la sustitución del comando (la sustitución del comando solo redirige el fd 1, stdout). Así que arriba, para cmd
, tenemos para fds 1, 2, 3:
- la pipa a var
- intacto
- igual a lo que apunta 1 fuera de la sustitución del comando
Si lo cambiamos a:
{
var=$(cmd 2>&1 >&3)
} 3>&1-
Entonces se convierte en:
- igual a lo que apunta 1 fuera de la sustitución del comando
- la pipa a var
- igual a lo que apunta 1 fuera de la sustitución del comando
Ahora, tenemos lo que queríamos: stderr va a la tubería y stdout se deja intacto. Sin embargo, estamos filtrando ese fd 3 a cmd
.
Mientras que los comandos (por convención) asumen que los fds 0 a 2 están abiertos y son entrada, salida y error estándar, no asumen nada de otros fds. Lo más probable es que dejen ese fd 3 intacto. Si necesitan otro descriptor de archivo, simplemente harán uno open()/dup()/socket()...
que devolverá el primer descriptor de archivo disponible. Si (como un script de shell que lo hace exec 3>&1
) necesitan usarlo fd
específicamente, primero lo asignarán a algo (y en ese proceso, el recurso en poder de nuestro fd 3 será liberado por ese proceso).
Es una buena práctica cerrar ese fd 3 ya cmd
que no lo utiliza, pero no es gran cosa si lo dejamos asignado antes de llamar cmd
. Los problemas pueden ser: eso cmd
(y potencialmente otros procesos que genera) tiene un fd menos disponible. Un problema potencialmente más serio es si el recurso al que apunta fd puede terminar retenido por un proceso generado por eso cmd
en segundo plano. Puede ser una preocupación si ese recurso es una tubería u otro canal de comunicación entre procesos (como cuando su script se ejecuta como script_output=$(your-script)
), ya que eso significará que la lectura del proceso desde el otro extremo nunca verá el final del archivo hasta que el proceso en segundo plano finaliza.
Entonces, aquí, es mejor escribir:
{
var=$(cmd 2>&1 >&3 3>&-)
} 3>&1
Que, con bash
se puede acortar a:
{
var=$(cmd 2>&1 >&3-)
} 3>&1
Para resumir las razones por las que rara vez se usa:
- no es estándar y solo azúcar sintáctico. Debe equilibrar el ahorro de unas pocas pulsaciones de teclas para que su script sea menos portátil y menos obvio para las personas que no están acostumbradas a esa característica poco común.
- La necesidad de cerrar el fd original después de duplicarlo a menudo se pasa por alto porque la mayoría de las veces, no sufrimos las consecuencias, por lo que simplemente lo hacemos en
>&3
lugar de >&3-
o >&3 3>&-
.
La prueba de que rara vez se usa, como descubrió es que es falso en bash . En bash compound-command 3>&4-
o any-builtin 3>&4-
deja fd 4 cerrado incluso después compound-command
o any-builtin
ha regresado. Ya está disponible un parche para solucionar el problema (2013-02-19).