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=10
y echo $a
se 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 a
en 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=10
no envía nada a través de la tubería, no hay forma de que pueda afectar echo $a
.
1 Si la lastpipe
opción está configurada (está desactivada de forma predeterminada y se puede habilitar usando la shopt
funció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 $a
estás canalizando (usando el operador de canalización;|
)a=10
para hacer eco del comando. La tubería conectará elstdout
comando astdin
del comando2, es decircommand | command2
.a=10
es una asignación de variable, asumiré que no la haystdout
, 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 $a
no devuelve el valor10
. Cuando lo modifiqué auser@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 echo
comando no recibe información de stdin
, imprime sus argumentos.
user@host$ echo "I love linux" | echo
no devolverá nada. Puedes usar xargs
el 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 echo
el 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
a
se le asigna el valor1
inicialmente, luego el valor de la variable se cambia a2
, ambos en el entorno de shell actual. Los comandos generalmente se ejecutan en un subshell.a=10 | echo $a
es una lista, es decir, equivalente a(a=10 | echo $a)
la que se ejecuta en un sub-shell, pero no funciona porqueecho
no tomastdin
, sino que solo imprime su argumento. Aquí, el argumento es$a
, el valor de la variablea
en el subnivel que es2
.Además,
a=10
no produce ningún resultado ya que es una asignación variable. Entonces, en efecto,echo $a
está imprimiendo el valor de su argumento, que es,2
y no quitando nadaa=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 $a
produce10
.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
echo
el comando se ejecutan en un subshell. Por lo tanto, el valor dea
está10
en el subshell en el queecho
también se ejecuta el comando, por lo que devuelve10
. Después de recibir el mensaje, si emite el comandoecho $a
, regresa2
ya 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=10
en la prueba A y a=20
en la prueba B) en realidad se ejecuta, pero se ejecuta después del echo $a
comando, por lo que se obtiene el resultado del valor anterior de la variable a
que está en el entorno del subshell, después del cual se ejecutan las últimas asignaciones de variables. En una tubería, los dos comandos stdin
y stdout
está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. echo
no 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 echo
no lee nada de la entrada estándar, por lo que no se mueven datos a través de la tubería. El argumento de echo
simplemente 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 a
el 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=999
pasarí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).