Cómo canalizar la entrada a un bucle Bash while y conservar las variables después de que termina el bucle

87

Bash permite usar: cat <(echo "$FILECONTENT")

Bash también permite usar: while read i; do echo $i; done </etc/passwd

para combinar los dos anteriores, esto se puede usar: echo $FILECONTENT | while read i; do echo $i; done

El problema con el último es que crea un sub-shell y después de que el ciclo while termina iya no se puede acceder a la variable .

Mi pregunta es:

Cómo lograr algo como esto: while read i; do echo $i; done <(echo "$FILECONTENT")o en otras palabras: ¿Cómo puedo estar seguro de que isobrevive while loop?

Tenga en cuenta que soy consciente de incluir la instrucción while en, {}pero esto no resuelve el problema (imagine que desea usar el bucle while en la función y la ivariable de retorno )

Wakan Tanka
fuente
Relacionado: stackoverflow.com/questions/37229058/… . Explica todas las opciones, incluida la sustitución de procesos que se menciona a continuación, lastpipey sus pros y contras.
ivan_pozdeev

Respuestas:

115

La notación correcta para la sustitución de procesos es:

while read i; do echo $i; done < <(echo "$FILECONTENT")

El último valor de iasignado en el bucle está disponible cuando el bucle termina. Una alternativa es:

echo $FILECONTENT | 
{
while read i; do echo $i; done
...do other things using $i here...
}

Las llaves son una operación de agrupación de E / S y no crean por sí mismas una subcapa. En este contexto, son parte de una canalización y, por lo tanto, se ejecutan como una subcapa, pero se debe a |, no a { ... }. Mencionas esto en la pregunta. AFAIK, puede hacer un retorno desde dentro de estos dentro de una función.


Bash también proporciona la función shoptincorporada y una de sus muchas opciones es:

lastpipe

Si se establece y el control del trabajo no está activo, el shell ejecuta el último comando de una canalización no ejecutado en segundo plano en el entorno de shell actual.

Por lo tanto, usar algo como esto en un script hace que el modificado sumesté disponible después del ciclo:

FILECONTENT="12 Name
13 Number
14 Information"
shopt -s lastpipe   # Comment this out to see the alternative behaviour
sum=0
echo "$FILECONTENT" |
while read number name; do ((sum+=$number)); done
echo $sum

Hacer esto en la línea de comando generalmente implica una falta de 'control de trabajo no está activo' (es decir, en la línea de comando, el control de trabajo está activo). No se pudo probar esto sin usar un script.

Además, como señaló Gareth Rees en su respuesta , a veces puede usar una cadena aquí :

while read i; do echo $i; done <<< "$FILECONTENT"

Esto no requiere shopt; es posible que pueda guardar un proceso usándolo.

Jonathan Leffler
fuente
Perdona mi ignorancia. Sé que esta es la solución correcta y la he marcado como respuesta, así que funcionó para mí. Pero ahora, cuando ejecuto while read i; do echo $i; done < <(cat /etc/passwd); echo $i, no devolvió la última línea dos veces. ¿Que estoy haciendo mal?
Wakan Tanka
@WakanTanka: Tuve que experimentar un poco ... Creo que la respuesta es que la lectura fallida se restablece ia vacía, por lo que el eco después del ciclo hace eco de una línea en blanco.
Jonathan Leffler
1
Felicitaciones por la referencia de sustitución de procesos. Yo no estaba al tanto.
Atcold el
@Wakan Tanka: obtuve el mismo resultado que el tuyo, yo uso while read i; do x=$i; done < <(cat /etc/passwd); echo i=$i; echo x=$xtrabajado, // ¿tal vez esos días el comportamiento de bash cambió?
yurenchen
¡Eres un salvavidas! Llevo horas buscando una solución similar. El tuyo es el único que funcionó.
Pat
0

Esta función hace duplicados $ NUM veces de archivos jpg (bash)

function makeDups() {
NUM=$1
echo "Making $1 duplicates for $(ls -1 *.jpg|wc -l) files"
ls -1 *.jpg|sort|while read f
do
  COUNT=0
  while [ "$COUNT" -le "$NUM" ]
  do
    cp $f ${f//sm/${COUNT}sm}
    ((COUNT++))
  done
done
}
Steve
fuente