¿Por qué la entrada de tuberías a "read" solo funciona cuando se introduce en la construcción " while read?"?
He estado tratando de leer la entrada en variables de entorno desde la salida 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í sobre bash ejecutando comandos canalizados en sub-shell y que básicamente impide que uno de entrada de tuberías para leer. Sin embargo, lo siguiente:
echo first second | while read A B ; do echo $A-$B ; done
Parece funcionar, el resultado es:
first-second
¿Puede alguien explicar cuál es la lógica aquí? Es que los comandos dentro de la while
... done
construct se ejecutan realmente en el mismo shell que echo
y no en un sub-shell?
4 answers
Cómo hacer un bucle contra stdin y obtener un resultado almacenado en una variable
Bajo bash (y otro shell también), cuando canaliza algo usando |
a otro comando, creará implícitamente una bifurcación , una subcapa que es hija 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 se calculó EL TOTAL no pudo ser reutilizado en el script principal.
Invertir la horquilla
Usando bash Sustitución de Procesos, Here Documents or Here Strings , podrías invertir la bifurcación:
Aquí cadenas
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 bucle:
echo : $C
: third-fourth
Aquí 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 puedes usar $TOTAL
en tu script principal .
Canalización a una lista de comandos
Pero para trabajando solo contra stdin , puede 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 rizado a la derecha }
).
Usando lastpipe opción bash
Como @CharlesDuffy señaló correctamente, hay una opción bash utilizada para cambiar este comportamiento. Pero para esto, tenemos que primero desactivar trabajo control :
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 (personalmente) no me gusta esto porque no es estándar y no ayudará a hacer que el script sea legible. También deshabilitar el control de trabajo parece costoso para acceder a este comportamiento.
Nota: Control de trabajos está habilitado de forma predeterminada solo en las sesiones interactive. Así que set +m
no es necesario en scripts normales.
So forgotten set +m
in a script would create different behaviours if run in a consola o si se ejecuta en un script. Esto no va a hacer esto fácil de entender o de depurar...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-22 11:49:48
Primero, se ejecuta esta cadena de tuberías:
echo first second | read A B
Entonces
echo $A-$B
Debido a que el read A B
se ejecuta en una subcapa, A y B se pierden.
Si haces esto:
echo first second | (read A B ; echo $A-$B)
Entonces ambos read A B
y echo $A-$B
se ejecutan en la misma subcapa (ver página de manual de bash, buscar (list)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-07 13:35:42
Una solución mucho más limpia...
read -r a b < <(echo "$first $second")
echo "$a $b"
De esta manera, read no se ejecuta en un sub-shell (lo que borraría las variables tan pronto como ese sub-shell haya terminado). En su lugar, las variables que desea usar se repiten en un sub-shell que hereda automáticamente las variables del shell padre.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-17 16:52:21
Lo que está viendo es la separación entre procesos: el read
se produce en una subcapa - un proceso separado que no puede alterar las variables en el proceso principal (donde los comandos echo
se producen más tarde).
Una canalización (como A | B
) coloca implícitamente cada componente en un sub-shell (un proceso separado), incluso para built-ins (como read
) que normalmente se ejecutan en el contexto del shell (en el mismo proceso).
La diferencia en el caso de "piping into while" es una ilusión. Lo mismo la regla se aplica allí: el bucle es la segunda mitad de una tubería, por lo que está en una subcapa, pero el todo bucle está en el mismo subcapa, por lo que la separación de procesos no se aplica.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-25 23:20:13