¿En qué orden el shell ejecuta comandos y redirige la transmisión?

32

Intenté redirigir ambos stdouty stderra un archivo hoy, y me encontré con esto:

<command> > file.txt 2>&1

Esto aparentemente redirige stderra stdoutprimero, y luego el resultado stdoutse redirige a file.txt.

Sin embargo, ¿por qué no es el pedido <command> 2>&1 > file.txt? Uno naturalmente leería esto como (suponiendo la ejecución de izquierda a derecha) el comando que se ejecuta primero, el que stderrse redirige stdouty luego stdoutse escribe el resultado file.txt. Pero lo anterior solo redirige stderra la pantalla.

¿Cómo interpreta el shell ambos comandos?

Train Heartnet
fuente
77
TLCL dice esto "Primero redirigimos la salida estándar al archivo, y luego redirigimos el descriptor de archivo 2 (error estándar) al descriptor de archivo uno (salida estándar)" y "Observe que el orden de las redirecciones es significativo. La redirección del error estándar siempre debe ocurrir después de redirigir la salida estándar o no funciona "
Zanna
@Zanna: ¡Sí, mi pregunta precisamente vino de leer eso en TLCL! :) Estoy interesado en saber por qué no funcionaría, es decir, cómo el shell interpreta los comandos en general.
Train Heartnet
Bueno, en su pregunta dice lo contrario "Esto aparentemente redirige stderr a stdout primero ...", lo que entiendo de TLCL, es que el shell envía stdout al archivo y luego stderr a stdout (es decir, al archivo). Mi interpretación es que si envía stderr a stdout, entonces se mostrará en el terminal, y la redirección posterior de stdout no incluirá stderr (es decir, la redirección de stderr a la pantalla se completa antes de que ocurra la redirección de stdout)
Zanna
77
Sé que esto es algo anticuado, pero su shell viene con un manual que explica estas cosas , por ejemplo, redirección en el bashmanual . Por cierto, las redirecciones no son comandos.
Reinier Post
El comando no se puede ejecutar antes de que se configuren las redirecciones: cuando execvse invoca la llamada -familia syscall para entregar el subproceso al comando que se está iniciando, el shell queda fuera del bucle (ya no se ejecuta el código en ese proceso ) y no tiene forma posible de controlar lo que sucede a partir de ese momento; redirecciones por lo tanto todo debe ser realizado antes de que comience la ejecución, mientras que la cáscara tiene una copia de sí mismo se ejecuta en el proceso, fork()ed para ejecutar su comando en.
Charles Duffy

Respuestas:

41

Cuando ejecuta <command> 2>&1 > file.txtstderr se redirige 2>&1a donde va stdout actualmente, su terminal. Después de eso, stdout se redirige al archivo >, pero stderr no se redirige con él, por lo que permanece como salida de terminal.

Con <command> > file.txt 2>&1stdout primero se redirige al archivo >, luego 2>&1redirige stderr a donde va stdout, que es el archivo.

Puede parecer contradictorio para empezar, pero cuando piensa en las redirecciones de esta manera y recuerda que se procesan de izquierda a derecha, tiene mucho más sentido.

Arronico
fuente
Tiene sentido si piensa en términos de descriptores de archivo y llamadas "dup / fdreopen" ejecutadas en orden de izquierda a derecha
Mark K Cowan
20

Puede tener sentido si lo trazas.

Al principio, stderr y stdout van a lo mismo (generalmente el terminal, al que llamo aquí pts):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

Me refiero a stdin, stdout y stderr por sus números de descriptor de archivo aquí: son descriptores de archivo 0, 1 y 2 respectivamente.

Ahora, en el primer conjunto de redirecciones, tenemos > file.txty 2>&1.

Asi que:

  1. > file.txt: fd/1ahora va a file.txt. Con >, 1es el descriptor de archivo implícito cuando no se especifica nada, así que esto es 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2ahora va a donde quiera fd/1 que vaya actualmente :

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt

Por otro lado, con 2>&1 > file.txt, el orden se invierte:

  1. 2>&1: fd/2ahora va a donde quiera fd/1que vaya, lo que significa que nada cambia:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1ahora va a file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts

El punto importante es que la redirección no significa que el descriptor de archivo redirigido seguirá todos los cambios futuros en el descriptor de archivo de destino; solo tomará el estado actual .

muru
fuente
¡Gracias, esto parece una explicación más natural! :) Hiciste un ligero error tipográfico en 2.la segunda parte; fd/1 -> file.txty no fd/2 -> file.txt.
Train Heartnet
11

Creo que ayuda pensar que el shell configurará primero la redirección a la izquierda y la completará antes de configurar la siguiente redirección.

La línea de comandos de Linux por William Shotts dice

Primero redirigimos la salida estándar al archivo, y luego redirigimos el descriptor de archivo 2 (error estándar) al descriptor de archivo uno (salida estándar)

esto tiene sentido, pero luego

Tenga en cuenta que el orden de las redirecciones es significativo. La redirección del error estándar siempre debe ocurrir después de redirigir la salida estándar o no funciona

pero en realidad, podemos redirigir stdout a stderr después de redirigir stderr a un archivo con el mismo efecto

$ uname -r 2>/dev/null 1>&2
$ 

Entonces, en command > file 2>&1, el shell envía stdout a un archivo, luego envía stderr a stdout (que se envía a un archivo). Mientras que, en command 2>&1 > fileel shell, primero redirige stderr a stdout (es decir, lo muestra en el terminal donde normalmente va stdout) y luego, redirige stdout al archivo. TLCL es engañoso al decir que primero debemos redirigir stdout: ya que primero podemos redirigir stderr a un archivo y luego enviarle stdout. Lo que no podemos hacer es redirigir stdout a stderr o viceversa antes de redirigir a un archivo. Otro ejemplo

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

Podríamos pensar que esto eliminaría stdout al mismo lugar que stderr, pero no lo hace, redirige stdout a stderr (la pantalla) primero, y luego solo redirige stderr, como cuando lo intentamos al revés ...

Espero que esto traiga un poco de luz ...

Zanna
fuente
¡Mucho más elocuente!
Arronico
Ah, ahora entiendo! Muchas gracias, @Zanna y @Arronical! Recién comenzando mi viaje en línea de comando. :)
Train Heartnet
@TrainHeartnet es un placer! Espero que lo estés disfrutando tanto como yo: D
Zanna
@Zanna: ¡De hecho, lo estoy! : D
Train Heartnet
2
@TrainHeartnet no te preocupes, ¡un mundo de frustración y alegría te espera!
Arronico
10

Ya tienes algunas respuestas muy buenas. Permítanme enfatizar que hay dos conceptos diferentes involucrados aquí, cuya comprensión ayuda enormemente:

Antecedentes: descriptor de archivo versus tabla de archivo

Su descriptor de archivo es solo un número 0 ... n, que es el índice en la tabla de descriptores de archivo en su proceso. Por convención, STDIN = 0, STDOUT = 1, STDERR = 2 (tenga en cuenta que los términos, STDINetc., aquí son solo símbolos / macros utilizados por convención en algunos lenguajes de programación y páginas de manual, no hay un "objeto" real llamado STDIN; para El propósito de esta discusión, STDIN es 0, etc.).

Esa tabla de descriptores de archivo en sí misma no contiene ninguna información sobre cuál es el archivo real. En cambio, contiene un puntero a una tabla de archivo diferente; este último contiene información sobre un archivo físico real (o dispositivo de bloque, o tubería, o cualquier otra cosa que Linux pueda abordar a través del mecanismo de archivo) y más información (es decir, ya sea para leer o escribir).

Entonces, cuando use >o <en su shell, simplemente reemplace el puntero del descriptor de archivo respectivo para apuntar a otra cosa. La sintaxis 2>&1simplemente señala el descriptor 2 a donde sea 1 punto. > file.txtsimplemente se abre file.txtpara escribir y deja que STDOUT (descifrador de archivos 1) apunte a eso.

Hay otras ventajas, por ejemplo 2>(xxx) (es decir: crear un nuevo proceso en ejecución xxx, crear una tubería, conectar el descriptor de archivo 0 del nuevo proceso al extremo de lectura de la tubería y conectar el descriptor de archivo 2 del proceso original al final de la escritura del tubo).

Esta es también la base para la "magia de manejo de archivos" en otro software que no sea su shell. Por ejemplo, podría, en su secuencia de comandos Perl, dupcolocar el descriptor de archivo STDOUT en otro (temporal) y luego volver a abrir STDOUT en un archivo temporal recién creado. A partir de este momento, toda la salida STDOUT de su propio script Perl y todas las system()llamadas de ese script terminarán en ese archivo temporal. Cuando haya terminado, puede volver a dupcolocar su STDOUT en el descriptor temporal en el que lo guardó, y listo, todo está como antes. Mientras tanto, incluso puede escribir en ese descriptor temporal, por lo que si bien su salida STDOUT real va al archivo temporal, aún puede enviar cosas al STDOUT real (comúnmente, el usuario).

Responder

Para aplicar la información de antecedentes dada a su pregunta:

¿En qué orden el shell ejecuta comandos y redirige la transmisión?

De izquierda a derecha.

<command> > file.txt 2>&1

  1. fork fuera de un nuevo proceso.
  2. Abra file.txty almacene su puntero en el descriptor de archivo 1 (STDOUT).
  3. Apunte STDERR (descriptor de archivo 2) a lo que sea que apunte fd 1 en este momento (que de nuevo es el ya abierto, file.txtpor supuesto).
  4. exec el <command>

Aparentemente, esto redirige stderr a stdout primero, y luego el stdout resultante se redirige a file.txt.

Esto tendría sentido si solo hubiera una tabla, pero como se explicó anteriormente, hay dos. Los descriptores de archivos no se apuntan recíprocamente, no tiene sentido pensar "redirigir STDERR a STDOUT". El pensamiento correcto es "señalar STDERR a cualquier punto STDOUT". Si cambia STDOUT más tarde, STDERR se queda donde está, no va mágicamente junto con más cambios a STDOUT.

AnoE
fuente
Votación a favor del bit "file handle magic", aunque no responde directamente a la pregunta, aprendí algo nuevo hoy ...
Floris
3

El orden es de izquierda a derecha. El manual de Bash ya ha cubierto lo que pides. Cita de la REDIRECTIONsección del manual:

   Redirections  are  processed  in  the
   order they appear, from left to right.

y unas pocas líneas después:

   Note that the order of redirections is signifi
   cant.  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  dupli
   cated from the standard output before the stan
   dard output was redirected to dirlist.

¡Es importante tener en cuenta que la redirección se resuelve primero antes de que se ejecute ningún comando! Ver https://askubuntu.com/a/728386/295286

Sergiy Kolodyazhnyy
fuente
3

Siempre se deja de derecha a derecha ... excepto cuando

Al igual que en Matemáticas, lo hacemos de izquierda a derecha, excepto que la multiplicación y la división se realizan antes de la suma y la resta, excepto que las operaciones dentro del paréntesis (+ -) se realizarían antes de la multiplicación y la división.

Según la guía para principiantes de Bash aquí ( Guía para principiantes de Bash ) hay 8 órdenes de jerarquía de lo que viene primero (antes de izquierda a derecha):

  1. Expansión de llaves "{}"
  2. Expansión de Tilde "~"
  3. Parámetro de shell y expresión variable "$"
  4. Sustitución de comando "$ (comando)"
  5. Expresión aritmética "$ ((EXPRESIÓN))"
  6. Proceso de sustitución de lo que estamos hablando aquí "<(LISTA)" o "> (LISTA)"
  7. División de palabras "'<espacio> <pestaña> <nueva línea>'"
  8. Expansión de nombre de archivo "*", "?", Etc.

Así que siempre se deja de derecha a derecha ... excepto cuando ...

WinEunuuchs2Unix
fuente
1
Para ser claros: la sustitución de procesos y la redirección son dos operaciones independientes. Puedes hacer uno sin el otro. Que tiene una sustitución proceso no significa que fiesta decide cambiar el orden en que se procesa la redirección
Muru