bash while-loop pipe piping

Bash: ¿Por qué la entrada de tubería para "leer" solo funciona cuando se alimenta a la construcción "mientras se lee..."?



while-loop pipe (5)

Primero, este pipe-chain se ejecuta:

echo first second | read A B

entonces

echo $A-$B

Debido a que la read AB se ejecuta en una subshell, A y B se pierden. Si haces esto:

echo first second | (read A B ; echo $A-$B)

luego ambos read AB y echo $A-$B se ejecutan en la misma subcadena (consulte la página de manual de bash, búsqueda de (list)

He intentado leer las entradas en las variables de entorno desde el resultado del programa de esta manera:

echo first second | read A B ; echo $A-$B

Y el resultado es:

-

Tanto A como B están siempre vacíos. Leí acerca de bash ejecutando comandos de canalización en sub-shell y que básicamente impide que uno de los datos de entrada de la tubería sea leído. Sin embargo, lo siguiente:

echo first second | while read A B ; do echo $A-$B ; done

Parece que funciona, el resultado es:

first-second

¿Alguien puede explicar cuál es la lógica aquí? ¿Es que los comandos dentro de la construcción while ... done se ejecutan realmente en el mismo shell que echo y no en un subconjunto?


Lo que está viendo es la separación entre procesos: la read produce en una subcapa, un proceso separado que no puede alterar las variables en el proceso principal (donde se producen los comandos de echo más adelante).

Una canalización (como A | B ) coloca implícitamente cada componente en un subconjunto (un proceso separado), incluso para los integrables (como read ) que generalmente se ejecutan en el contexto del shell (en el mismo proceso).

La diferencia en el caso de "conectar con el tiempo" es una ilusión. La misma regla se aplica allí: el bucle es la segunda mitad de una tubería, por lo que se encuentra en una subcadena, pero todo el bucle está en la misma subcadena, por lo que la separación de procesos no se aplica.


un trabajo mucho más limpio ...

read -r a b < <(echo "$first $second") echo "$a $b"

De esta forma, la lectura no se ejecuta en un subconjunto (lo que borraría las variables tan pronto como haya terminado ese subconjunto). En su lugar, las variables que desea utilizar se repiten en un subconjunto que hereda automáticamente las variables del shell primario.


Cómo hacer un ciclo contra stdin y obtener el resultado almacenado en una variable

Bajo bash (y también otro shell ), cuando canalizas algo usando | para otro comando, creará implícitamente un fork , un subshell que es hijo de la sesión actual y que no puede afectar el entorno de la sesión actual.

Así que esto:

TOTAL=0 printf "%s %s/n" 9 4 3 1 77 2 25 12 226 664 | while read A B;do ((TOTAL+=A-B)) printf "%3d - %3d = %4d -> TOTAL= %4d/n" $A $B $[A-B] $TOTAL done echo final total: $TOTAL

no dará el resultado esperado! :

9 - 4 = 5 -> TOTAL= 5 3 - 1 = 2 -> TOTAL= 7 77 - 2 = 75 -> TOTAL= 82 25 - 12 = 13 -> TOTAL= 95 226 - 664 = -438 -> TOTAL= -343 echo final total: $TOTAL final total: 0

Donde TOTAL calculado no puede ser reutilizado en el script principal.

Invertir la horquilla

Al usar Bash Process Substitution , Here Documents o Here Strings , puedes invertir el tenedor:

Aquí cuerdas

read A B <<<"first second" echo $A first echo $B second

Aquí documentos

while read A B;do echo $A-$B C=$A-$B done << eodoc first second third fourth eodoc first-second third-fourth

fuera del circuito:

echo : $C : third-fourth

Aquí los comandos

TOTAL=0 while read A B;do ((TOTAL+=A-B)) printf "%3d - %3d = %4d -> TOTAL= %4d/n" $A $B $[A-B] $TOTAL done < <( printf "%s %s/n" 9 4 3 1 77 2 25 12 226 664 ) 9 - 4 = 5 -> TOTAL= 5 3 - 1 = 2 -> TOTAL= 7 77 - 2 = 75 -> TOTAL= 82 25 - 12 = 13 -> TOTAL= 95 226 - 664 = -438 -> TOTAL= -343 # and finally out of loop: echo $TOTAL -343

Ahora podría usar $TOTAL en su secuencia de comandos principal .

Tubería a una lista de comandos

Pero para trabajar solo contra stdin , puedes crear un tipo de script en la bifurcación :

printf "%s %s/n" 9 4 3 1 77 2 25 12 226 664 | { TOTAL=0 while read A B;do ((TOTAL+=A-B)) printf "%3d - %3d = %4d -> TOTAL= %4d/n" $A $B $[A-B] $TOTAL done echo "Out of the loop total:" $TOTAL }

Daré:

9 - 4 = 5 -> TOTAL= 5 3 - 1 = 2 -> TOTAL= 7 77 - 2 = 75 -> TOTAL= 82 25 - 12 = 13 -> TOTAL= 95 226 - 664 = -438 -> TOTAL= -343 Out of the loop total: -343

Nota: $TOTAL no se pudo usar en el script principal (después del último corchete derecho } ).

Usar la opción lastpipe bash

Como @CharlesDuffy señaló correctamente, hay una opción bash utilizada para cambiar este comportamiento. Pero para esto, primero tenemos que desactivar el control de trabajo :

shopt -s lastpipe # Set *lastpipe* option set +m # Disabling job control TOTAL=0 printf "%s %s/n" 9 4 3 1 77 2 25 12 226 664 | while read A B;do ((TOTAL+=A-B)) printf "%3d - %3d = %4d -> TOTAL= %4d/n" $A $B $[A-B] $TOTAL done 9 - 4 = 5 -> TOTAL= -338 3 - 1 = 2 -> TOTAL= -336 77 - 2 = 75 -> TOTAL= -261 25 - 12 = 13 -> TOTAL= -248 226 - 664 = -438 -> TOTAL= -686 echo final total: $TOTAL -343

Esto funcionará, pero a mí (personalmente) no me gusta esto porque esto no es estándar y no ayudará a que el guión sea legible. También el deshabilitar el control del trabajo parece costoso para acceder a este comportamiento.

Nota: El control de trabajos está habilitado de manera predeterminada solo en sesiones interactivas . Por lo tanto, no se requiere set +m en las secuencias de comandos normales.

Por lo tanto, el set +m olvidado en un script crearía diferentes comportamientos si se ejecuta en una consola o si se ejecuta en un script. Esto no va a hacer que esto sea fácil de entender o depurar ...


Si cambia shell a ksh , su primer ejemplo funciona, es decir,

echo first second | read A B ; echo $A-$B