¿Es esto un error tipográfico en la sección de redirección del manual de Bash?

13
Note that the order of redirections is significant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error to the file dirlist, 
   while the command

          ls 2>&1 > dirlist

   directs  only  the  standard  output  to  file  dirlist,  because the 
   standard error was duplicated from the standard output before the standard
   output was redirected to dirlist.

Ahora, esa última parte es confusa para mí. En ese caso, cualquier error estándar se imprimiría en el terminal y cualquier STDOUT iría al archivo dirlist. Eso es lo que sucedería, pero no es así como entiendo el manual.

Parece que debería decir "porque el error estándar fue duplicado de la salida estándar DESPUÉS de que la salida estándar fue redirigida a dirlist". Si STDERR fue enviado a STDOUT antes de que STDOUT fuera dirigido a un archivo, ¿no contendría STDOUT AND STDERR?

¿Alguien puede aclarar esto por mí? ¿Es solo mala comprensión de lectura de mi parte? El uso de la palabra duplicación me parece un poco extraño en este contexto. Quizás eso me está arrojando.

Gregg Leventhal
fuente
1
Un caso clásico de mezclar operaciones que son "por valor" vs "por referencia". Cuando duplica un descriptor de archivo, es una operación por valor . En programación, después de a = 1; b = a; a = 2que esperas a == 2 && b == 1ser verdad. La redirección 2>&1es similar a la b = aasignación: es por valor, no por referencia. 2>&1no combina el descriptor de archivo 2 con el descriptor de archivo 1 para toda la eternidad; siguen siendo 2 descriptores de archivo distintos, que pueden apuntar al mismo archivo.
jw013

Respuestas:

23

La duplicación es realmente la parte importante aquí.

Veamos a dónde van los descriptores de archivos antes de la redirección. Este es normalmente el terminal actual, por ejemplo:

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

Ahora, si llamamos ls -lsin redireccionamiento, los mensajes de salida y error van a mi terminal debajo /dev/pts/1.

Si primero redirigimos STDOUTa un archivo ( ls -l > dirlist), se ve así:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Cuando luego redirigimos STDERRa un duplicado del STDOUTdescriptor de archivo ( ls -l > dirlist 2>&1), STDERRva a un duplicado de /home/bon/dirlist:

STDOUT ---> /home/bon/dirlist
STDERR ---> /home/bon/dirlist

Si primero redirigiéramos STDERRa un duplicado del STDOUTdescriptor de archivo ( ls -l 2>&1):

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

y luego STDOUT a un archivo ( ls -l 2>&1 > dirlist), obtendríamos esto:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Aquí, STDERRtodavía va a la terminal.

Verá, el orden en la página del manual es correcto.


Prueba de redireccionamiento

Ahora, puedes probar eso tú mismo. Usando ls -l /proc/$$/fd/, verá dónde STDOUT(con fd 1) y STDERR(con fd 2) van para el proceso actual:

$ ls -l /proc/$$/fd/
total 0
lrwx------ 1 bon bon 64 Jul 24 18:19 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 07:41 2 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 255 -> /dev/pts/1

Creemos un pequeño script de shell que muestre dónde apuntan los descriptores de sus archivos. De esta forma, siempre obtenemos el estado al llamar ls, incluida cualquier redirección desde el shell de llamada.

$ cat > lookfd.sh
#!/bin/sh
ls -l /proc/$$/fd/
^D
$ chmod +x lookfd.sh

(Con CtrlD, envía un fin de archivo y así detiene la catlectura del comando STDIN).

Ahora, llame a este script con diferentes combinaciones de redireccionamiento:

$ ./lookfd.sh 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:08 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:08 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh 2>&1 > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out 2>&1
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:11 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:11 1 -> /home/bon/foo.out
l-wx------ 1 bon bon 64 Jul 24 19:11 2 -> /home/bon/foo.out
lr-x------ 1 bon bon 64 Jul 24 19:11 255 -> /home/bon/lookfd.sh

Puede ver que los descriptores de archivo 1 (para STDOUT) y 2 (para STDERR) varían. Por diversión, también puede redirigir STDINy ver el resultado:

$ ./lookfd.sh < /dev/zero
total 0
lr-x------ 1 bon bon 64 Jul 24 19:18 0 -> /dev/zero
lrwx------ 1 bon bon 64 Jul 24 19:18 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:18 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:18 255 -> /home/bon/lookfd.sh

(Pregunta dejada al lector: ¿Dónde apunta el descriptor de archivo 255? ;-))

Dubu
fuente
+1 - excelente respuesta. Ejemplos extremadamente bien escritos y fabulosos. ¡¡¡Gracias!!!
slm
Ya veo, creo que mi malentendido fue que la redirección sería persistente para todos los comandos siguientes, de modo que cualquier STDERR para el resto de la línea iría a STDOUT.
Gregg Leventhal
2

No, el manual es correcto.

Si al principio 1 apunta a la terminal y 2 también a la terminal, entonces:

command  2>&1   1>somewhere

La redirección evaluatino ocurrirá de izquierda a derecha.

Por lo tanto, PRIMERO evaluará 2>&1y, por lo tanto, PRIMERO copiará lo que fd 1solía señalar (es decir, el descriptor de archivo the terminal, generalmente / dev / tty) en fd 2.

Entonces, en ese punto, fd 2ahora apunta a donde 1solía señalar fd ( the terminal)

Y ENTONCES evalúa la 1>somewhereparte, y por lo tanto copiará el descriptor de archivo de somewherein fd 1(entonces, en ese punto, fd 1ahora apunta a somewhere, y fd 2todavía apunta a the terminal)

Por lo tanto, imprime 1 en "en algún lugar" y 2 en el terminal, ya que 2 se duplicó de 1 ANTES de que 1 haya cambiado.

El otro orden:

command  1>somewhere 2>&1

primero redirigirá fd 1a somewhere, y luego copiará esa misma referencia en fd 2, por lo que al final 2 también apunta a somewhere. Pero no están "vinculados" de ahora en adelante. Cada uno todavía puede ser redirigido por separado.

ex:

command  1>somewhere 2>&1
exec 2>/dev/null

Al final de ese, fd 1apunta a somewhere, y fd 2se dirige a/dev/null

Los nombres habituales para fd 1es STDOUT (salida estándar), y el nombre habitual para fd 2es STDERR (error estándar, ya que se usa comúnmente para mostrar errores sin interferir con STDOUT)

Olivier Dulac
fuente
@ Michael-mrozek: gracias por la edición, pero insisto en decir "copiar" en lugar de "duplicar", ya que "duplicar" podría llevar a creer que de ahora en adelante ambos son "lo mismo", lo cual no es cierto. Ej:: cmd 1>somewhere 2>&1 ; exec 2>/dev/nulldespués del ejecutivo, solo 2 han sido redirigidos a / dev / null (1 todavía va a "en algún lugar"). Sin embargo, necesito ayuda para encontrar una manera de decir "lo que 1 señala" en lugar de "el fd 1", ya que eso también es confuso ...
Olivier Dulac
1
No estoy seguro de lo que quieres decir; usted fue quien lo cambió de "copiar" a "duplicar". Todo lo que hice fue capitalizar y formatear las cosas, no cambié una palabra
Michael Mrozek
doh ... ^^ lo siento. Y volví a editar para reformular para hacer más preciso lo que se copia en lo que ^^
Olivier Dulac
1

Creo que la parte confusa aquí es la mala interpretación de que redireccionar stderr a stdout en realidad conecta las dos corrientes.

Una idea perfectamente razonable, pero lo que sucede cuando escribes 2>&1es stderr echa un vistazo a lo que stdout está escribiendo y escribe en el mismo lugar. Por lo tanto, si posteriormente le dice a stdout que vaya a escribir en otro lugar, no tiene ningún efecto sobre el destino de stderr que ya se ha movido.

Creo que es un poco contradictorio, pero así es como funciona. Configure dónde desea escribir primero y luego diga a todos "cópieme". Espero que eso aclare ...

jrichemont
fuente
0

DUPLICACIÓN...

es importante, sino más bien en el sentido de que es fuente de mucha confusión . Es realmente bastante simple. Esta respuesta es solo una ilustración "radical".

La respuesta aceptada es buena, pero demasiado larga y enfatiza la "duplicación".

La Q termina sabiamente con:

El uso de la palabra duplicación me parece un poco extraño en este contexto. Quizás eso me está arrojando.

Utilizo la notación bash y defino las variables "uno" y "dos" como manejadores de archivos "1" y "2". El operador de redireccionamiento (de salida) >es una asignación =. &y $significa "valor" de.

Los ejemplos de man bash (con el valor predeterminado "1" agregado)

ls 1>dirlist 2>&1      # both to dirlist
ls 2>&1 1>dirlist      # 1 to dirlist, 2 stays on tty/screen 

volverse:

one=dirlist  two=$one

y

two=$one   one=dirlist

E incluso esto no es automático para mí, y algunos otros, supongo. La primera línea te deja con $oney $twoambos contienen "dirlist". Por supuesto.

La segunda línea comienza con una asignación inútil. Ambos comienzan por definición con "TTY" (un poco simbólico) como su dirección ; esta asignación no cambia ningún valor, y con las variables como con los controladores de archivo, nada está mágicamente vinculado. La variable twono se ve afectada por lo siguiente one=dirlist. Por supuesto no.

Sombody aquí (hace 6 años) sugirió "señalar" en lugar de "copiar" o "duplicar", y luego se dio cuenta: eso también sería confuso.

Esta duplicación o puntero semántico ni siquiera es necesaria. Tal vez sea el ampersand que necesita más atención. El "valor de" operador / token / lo que sea.

Si, y solo si, está buscando una manera de obtener un número de trabajo sorprendente en su consola , entonces un mensaje de "hecho" más un bono llamado "2", entonces:

ls 1>2& 2>/dev/null

Se lee naturalmente como " copiar" / "duplicar" 1 a 2, y luego ambos juntos a nulo . Pero la idea es incorrecta, y también la sintaxis. (pero no hay error de sintaxis, es válido)

La forma correcta de planificarlo es redirigir cualquiera de los dos a nulo y luego redirigir el OTRO al MISMO lugar:

ls 1>/dev/null 2>&1
# or 
ls 2>/dev/null 1>&2

(el "1" principal se puede dejar de lado)

(OK, el acc. A no es demasiado largo, pero es demasiado de una lista, o: muy buena visualización, no tan buena explicación)

archivo de rasta
fuente