Orden de i / o de redirección de Shell

Estoy jugando con la redirección de i / o shell. Los comandos que he probado (en bash):

ls -al *.xyz 2>&1 1> files.lst 

y

 ls -al *.xyz 1> files.lst 2>&1 

No hay ningún archivo *.xyz en la carpeta actual.

Estos comandos me dan los diferentes resultados. El primer comando muestra un mensaje de error ls: *.xyz: No such file or directory en la pantalla. Pero el segundo imprime este mensaje de error en el archivo. ¿Por qué el primer comando no pudo escribir una salida de error en el archivo?

Este error:

 ls: *.xyz: No such file or directory 

está escrito en stderr por ls binary.

Sin embargo en este comando:

 ls -al *.xyz 2>&1 1> files.lst 

Primero está redirigiendo stderr a stdout que de forma predeterminada va a tty (terminal)

Y luego está redireccionando stdout a un archivo files.lst , sin embargo, recuerde que stderr no se redirige a un archivo ya que tiene stderr para redireccionar stdout antes de stdout para redireccionar file . Su stderr aún se escribe en tty en este caso.

Sin embargo, en el segundo caso, cambia el orden de las redirecciones (primero stdout a file y luego stderr a stdout ) y eso correctamente redirige stderr a un file que también está siendo utilizado por stdout .

El manual de Bash tiene un claro ejemplo (similar al suyo) para mostrar que el orden es importante y también explica la diferencia. Aquí está la parte relevante extraída (el énfasis es mío):

Tenga en cuenta que el orden de las redirecciones es significativo. Por ejemplo, el comando

ls> dirlist 2> y 1

dirige tanto la salida estándar (descriptor de archivo 1) como el error estándar (descriptor de archivo 2) al archivo dirlist , mientras que el comando

ls 2> & 1> dirlist

dirige solo la salida estándar al archivo dirlist , porque el error estándar se hizo una copia de la salida estándar antes de que la salida estándar se redirigiera a dirlist .

Esta publicación lo explica desde el punto de vista POSIX.

Las confusiones ocurren debido a una diferencia clave. > redirige no haciendo que el operando izquierdo ( stderr ) apunte al operando derecho ( stdout ) sino haciendo una copia del operando derecho y asignándolo a la izquierda. Conceptualmente, asignación por copia y no por referencia.

Entonces, leyendo de izquierda a derecha, así es como lo interpreta Bash: ls > dirlist 2>&1 significa redirigir stdout al archivo dirlist , seguido de redirección de stderr a lo que stdout es actualmente (que ya es el archivo dirlist ). Sin embargo, ls 2>&1 > dirlist redirigiría stderr a lo que stdout es actualmente (que es la pantalla / terminal) y luego redireccionar stdout a dirlist .

Las redirecciones son:

  • procesado de izquierda a derecha.
  • interpretado iterativamente:
    • una redirección anterior puede afectar a una posterior :
      • si una redirección anterior ha redirigido un flujo determinado (identificado por un número de descriptor de archivo, como 1 para stdout (el valor predeterminado) y 2 para stderr), las redirecciones posteriores que se dirigen a ese flujo se refieren a la versión ya redirigida.
    • pero no al revés : una redirección posterior no tiene efecto retroactivo sobre el objective de una redirección anterior:
      • por ejemplo, si especifica el descriptor de archivo 1 como el destino en una redirección anterior, lo que significa 1 en ese momento está bloqueado, incluso si 1 se redirige más tarde.
  • Sin embargo, tenga en cuenta que la salida no se envía hasta que todas las redirecciones estén en su lugar y que los archivos de salida de destino de redirección se creen o trunquen antes de que comience la ejecución del comando (esta es la razón por la que no puede leer y redireccionar la salida a el mismo archivo con un solo comando).

Aplicado al ejemplo de la pregunta:

  • >file 2>&1 :

    • >file first redirects stdout (descriptor de archivo 1 , implicado al no incluir un prefijo > con un número de descriptor de archivo) para generar el archivo de file
    • 2>&1 luego redirige stderr ( 2 ) a la salida estándar ya redirigida ( 1 ).
    • El efecto neto es que ambas transmisiones originales terminan en un file .
  • 2>&1 >file :

    • 2>&1 primero redirige stderr a la salida estándar original ; dado que el descriptor de archivo 2 participa en más redirecciones, la salida de stderr irá a lo que stdout se definió como en ese punto , es decir, el stdout original , dado que esta es la primera redirección.
      • Técnicamente, el descriptor de archivo stdout original está duplicado , y ese duplicado es a lo que se refiere stderr, lo que explica por qué no se ve afectado por una redirección posterior de stdout.
    • >file redirecciona el stdout original al file , pero ya no tiene efecto en la redirección de stderr ya bloqueada.
    • El efecto neto es que solo la salida enviada directamente a stdout se captura en el file , mientras que la salida enviada a stderr se envía a (stdout original, sin redireccionar).

Porque el orden sí importa En el primer caso, primero redirecciona stderr (2) a stdout (1). Luego redirige (1) a un archivo. Pero stderr (2) todavía apunta a stdout del shell que ejecuta el comando. Apuntar (1) a un archivo en este caso no cambia el dispositivo de salida al que se dirige (2), por lo que todavía va al terminal.

En el segundo caso, redirecciona stdout (1) a un archivo. Luego apunte stderr (2) al mismo lugar donde apunta 1, que es el archivo, de modo que el mensaje de error se dirige al archivo.