¿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?

Author: codeforester, 2012-12-07

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: $TOTALno 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...

 57
Author: F. Hauri,
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)

 19
Author: pbhd,
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.

 12
Author: immotus,
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.

 1
Author: Martin Kealey,
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