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