Múltiples comandos y ejecución de subshell después de la canalización

Múltiples comandos y ejecución de subshell después de la canalización

Ok, sé que en Bash (de forma predeterminada, sin la opción bash 'lastpipe' habilitada) cada variable asignada después de una canalización se ejecuta, de hecho, en un subshell y la variable en sí muere después de la ejecución del subshell. No permanece disponible para el proceso principal. Pero al hacer algunas pruebas, se me ocurrió este comportamiento:

A) El segundo comando (a=2) asigna el valor y se devuelve:

[root@centos01]# a=1; a=2; a=10 | echo $a
2

B) El tercer comando (a=10) asigna el valor y se devuelve:

[root@centos01]# a=1; a=2; a=10; a=20 | echo $a
10

C) El cuarto comando (a=20) asigna el valor y se devuelve:

[root@centos01]# a=1; a=2; a=10; a=20; touch fileA.txt | echo $a
20

Entonces:

  • ¿Por qué siempre no se ejecuta la última asignación de variable en la secuencia del comando? (o si es así, ¿por qué el subshell no lo captura y lo devuelve mediante el comando echo?)

  • En la prueba C, el comando 'touch' en realidad creó el archivo 'fileA.txt' en el directorio. Entonces, ¿por qué el último comando de la secuencia para la asignación de variables realizada en el paso A y en el conjunto B no funcionó? ¿Alguien sabe la explicación técnica para eso?

Respuesta1

Primero, para acordar algunos nombres, así es como el shell interpreta su entrada:

$ a=1; a=2; a=10 | echo $a
  ^^^  ^^^  ^^^^^^^^^^^^^^
    \    \         \_ Pipeline
     \    \_ Simple command
      \_ Simple command

La canalización se compone de dos comandos simples:

$ a=10 | echo $a
  ^^^^   ^^^^^^^
     \       \_ Simple command
      \_ Simple command

(Tenga en cuenta que, si bien es posible que no esté claramente indicado en el manual de Bash, elGramática del shell POSIXpermite constituir un comando simple a partir de una mera asignación de variables).

a=1;y a=2;no son parte de ningún oleoducto. A ;terminaría una tubería, excepto cuando aparezca como parte de unacomando compuesto. Como en, por ejemplo:

{ a=1; a=2; a=10; } | echo $a

En su ejemplo, a=10y echo $ase ejecutan endos distintos, entornos de subcapa independientes 1 , ambos creados como copias del entorno principal. Se requiere que las subcapas no alteren su entorno de ejecución principal 2 . Citando lo relevantesección POSIX:

Se creará un entorno de subshell como un duplicado del entorno de shell [...] Los cambios realizados en el entorno de subshell no afectarán el entorno de shell.

y

Además, cada comando de una canalización de comandos múltiples se encuentra en un entorno de subcapa; Sin embargo, como extensión, cualquiera o todos los comandos de una canalización se pueden ejecutar en el entorno actual. Todos los demás comandos se ejecutarán en el entorno de shell actual.

Por lo tanto, si bien todos los comandos en sus ejemplos realmente se ejecutan, las asignaciones en la parte izquierda de sus canalizaciones no tienen ningún efecto visible: solo alteran las copias aen sus respectivos entornos de subcapa, que se pierden tan pronto como terminan las subcapas.

La única forma en que las subcapas en los dos extremos de una tubería pueden interactuar directamente entre sí es por medio de la tubería misma: la salida estándar del lado izquierdo conectada a la entrada estándar del lado derecho. Como a=10no envía nada a través de la tubería, no hay forma de que pueda afectar echo $a.


1 Si la lastpipeopción está configurada (está desactivada de forma predeterminada y se puede habilitar usando la shoptfunción incorporada), Bash puede ejecutar el último comando de una canalización en el shell actual. VerTuberíasen el manual de Bash. Sin embargo, esto no es relevante en el contexto de su pregunta.

2 Puede encontrar más detalles desde una perspectiva práctica/histórica sobre U&L, por ejemplo enesta respuestaa¿Por qué la última función ejecutada en una canalización de script de shell POSIX no conserva valores variables?

Respuesta2

Por favor, disculpe mi inglés, todavía lo estoy aprendiendo. También soy un principiante de Bash, así que corrija cualquier error que haya cometido en mi respuesta, gracias.

Primero, señalaré un error.

  • En a=10 | echo $aestás canalizando (usando el operador de canalización; |) a=10para hacer eco del comando. La tubería conectará el stdoutcomando a stdindel comando2, es decir command | command2.

  • a=10es una asignación de variable, asumiré que no la hay stdout, ya que no es un comando. Si realiza una sustitución de comando en el valor de una asignación de variable, no funciona. Si intento lo siguiente:

    user@host$ a=$(b=10); echo $a
    

    el echo $ano devuelve el valor 10. Cuando lo modifiqué a

    user@host$ a=$(b=10; echo $b)
    

    una llamada de

    $ echo $a
    

    regresó 10. Entonces, probablemente tenga razón al suponer que la asignación de variables no es un comando (incluso según la definición manual de bash no es un comando).

En segundo lugar, el echocomando no recibe información de stdin, imprime sus argumentos.

user@host$ echo "I love linux" | echo

no devolverá nada. Puedes usar xargsel comando para superar esto:

user@host$ echo "I love linux" | xargs echo

regresará I love linux. Por lo tanto, la tubería no funciona directamente según echoel comando, ya que imprime sus argumentos y no stdin.

Ahora, a tus pruebas.

  • A tu mando

    user@host$ a=1; a=2; a=10 | echo $a
    

    a la variable ase le asigna el valor 1inicialmente, luego el valor de la variable se cambia a 2, ambos en el entorno de shell actual. Los comandos generalmente se ejecutan en un subshell. a=10 | echo $aes una lista, es decir, equivalente a (a=10 | echo $a)la que se ejecuta en un sub-shell, pero no funciona porque echono toma stdin, sino que solo imprime su argumento. Aquí, el argumento es $a, el valor de la variable aen el subnivel que es 2.

  • Además, a=10no produce ningún resultado ya que es una asignación variable. Entonces, en efecto, echo $aestá imprimiendo el valor de su argumento, que es, 2y no quitando nada a=10 | < ... >. Por lo tanto, el operador de tubería no debería usarse aquí; en su lugar, puede separar el nombre del comando y la asignación de variable con un terminador (, punto y coma) y funcionará bien, como en (a=10; echo $a).

Para comprender esto mejor, puede probar los siguientes ejemplos con la opción de depuración de bash habilitada:

user@host$ a=1; a=2; a=10; echo $a;
  • En la línea de comando anterior, echo $aproduce 10.

  • Si lo cambio a

    user@host$ a=1; a=2; (a=10; echo $a)
    

    la primera y segunda asignación de variables se configuran en el shell actual, la tercera asignación de variables y echoel comando se ejecutan en un subshell. Por lo tanto, el valor de aestá 10en el subshell en el que echotambién se ejecuta el comando, por lo que devuelve 10. Después de recibir el mensaje, si emite el comando echo $a, regresa 2ya que las asignaciones de variables del subshell no se trasladan al shell principal.

  • Una cosa más a tener en cuenta: el ;separador de comandos ejecuta comandos de forma secuencial.

En los casos de prueba "A" y "B", la última asignación de variable ( a=10en la prueba A y a=20en la prueba B) en realidad se ejecuta, pero se ejecuta después del echo $acomando, por lo que se obtiene el resultado del valor anterior de la variable aque está en el entorno del subshell, después del cual se ejecutan las últimas asignaciones de variables. En una tubería, los dos comandos stdiny stdoutestán conectados antes de que se ejecute el comando, además las asignaciones de variables no producen nada en la salida estándar.

tl; dr: La asignación de variables no debe usarse en una canalización. echono funciona directamente en una tubería.

Respuesta3

Aquí,

a=20 | echo $a

la pipa sólo añade confusión. La asignación de la izquierda no imprime nada en la salida estándar y echono lee nada de la entrada estándar, por lo que no se mueven datos a través de la tubería. El argumento de echosimplemente se amplía de lo que se estableció anteriormente.

Como dijiste, las partes de una tubería se ejecutan en subcapas distintas, por lo que la asignación en ael lado izquierdo no influye en la expansión en el lado derecho, y dicha comunicación tampoco ocurriría en el caso inverso.

En cambio, si hicieras esto:

{ a=999; echo a=$a; } | cat

la tubería tendría más sentido y la cuerda a=999pasaría a través de ella.

El touch fileA.txtúltimo ejemplo funciona porque afecta al sistema fuera del shell. De manera similar, puede escribir en stderr desde los comandos en la tubería y ver el resultado aparecer en la terminal:

$ echo a >&2 | echo b >&2 | echo c >&2
b
c
a

(Ese es, de hecho, el orden de salida que obtuve con Bash en mi sistema).

información relacionada