El bucle while deja de leer después de la primera línea en Bash
Tengo el siguiente script de shell. El propósito es recorrer cada línea del archivo de destino (cuya ruta es el parámetro de entrada al script) y trabajar en cada línea. Ahora, parece que solo funciona con la primera línea del archivo de destino y se detiene después de que se procesó esa línea. ¿Hay algún problema con mi guión?
#!/bin/bash
# SCRIPT: do.sh
# PURPOSE: loop thru the targets
FILENAME=$1
count=0
echo "proceed with $FILENAME"
while read LINE; do
let count++
echo "$count $LINE"
sh ./do_work.sh $LINE
done < $FILENAME
echo "\ntotal $count targets"
En do_work.sh
, ejecuto un par de ssh
comandos.
El problema es que do_work.sh
ejecuta ssh
comandos y, de forma predeterminada, ssh
lee desde stdin, que es su archivo de entrada. Como resultado, solo verá la primera línea procesada, porque el comando consume el resto del archivo y su ciclo while termina.
Esto sucede no solo para ssh
, sino para cualquier comando que lea stdin, incluidos mplayer
, ffmpeg
, HandBrakeCLI
, httpie
, brew install
y más.
Para evitar esto, pase la -n
opción a su ssh
comando para que se lea desde /dev/null
en lugar de stdin. Otros comandos tienen indicadores similares, o puedes usarlos universalmente < /dev/null
.
Una solución alternativa muy simple y sólida es cambiar el descriptor de archivo desde el cual el read
comando recibe la entrada.
Esto se logra mediante dos modificaciones: el -u
argumento to read
y el operador de redirección for < $FILENAME
.
En BASH, los valores predeterminados del descriptor de archivo (es decir, valores para -u
in read
) son:
- 0 = entrada estándar
- 1 = salida estándar
- 2 = estándar
Así que simplemente elija algún otro descriptor de archivo no utilizado, como 9
solo por diversión.
Por lo tanto, la siguiente sería la solución:
while read -u 9 LINE; do
let count++
echo "$count $LINE"
sh ./do_work.sh $LINE
done 9< $FILENAME
Observe las dos modificaciones:
read
se convierteread -u 9
< $FILENAME
se convierte9< $FILENAME
Como práctica recomendada, hago esto para todos while
los bucles que escribo en BASH. Si tiene bucles anidados usando read
, utilice un descriptor de archivo diferente para cada uno (9,8,7,...).
De manera más general, una solución alternativa que no es específica ssh
es redirigir la entrada estándar para cualquier comando que de otro modo podría consumir la while
entrada del bucle.
while read -r line; do
((count++))
echo "$count $line"
sh ./do_work.sh "$line" </dev/null
done < "$filename"
La adición de </dev/null
es el punto crucial aquí, aunque la cita corregida también es algo importante para la solidez; consulte también ¿Cuándo colocar comillas alrededor de una variable de shell? . Querrá usarlo read -r
a menos que requiera específicamente el comportamiento heredado ligeramente extraño que obtiene con las barras invertidas en la entrada sin -r
. Finalmente, evite las mayúsculas en sus variables privadas.
Otra solución alternativa que es algo específica ssh
es asegurarse de que cualquier ssh
comando tenga su entrada estándar vinculada, por ejemplo, cambiando
ssh otherhost some commands here
en su lugar, leer los comandos de un documento aquí, que convenientemente (para este escenario particular) vincula la entrada estándar de ssh
para los comandos:
ssh otherhost <<'____HERE'
some commands here
____HERE
La opción ssh -n evita verificar el estado de salida de ssh cuando se usa HEREdoc mientras se canaliza la salida a otro programa. Por lo tanto, se prefiere el uso de /dev/null como entrada estándar.
#!/bin/bash
while read ONELINE ; do
ssh ubuntu@host_xyz </dev/null <<EOF 2>&1 | filter_pgm
echo "Hi, $ONELINE. You come here often?"
process_response_pgm
EOF
if [ ${PIPESTATUS[0]} -ne 0 ] ; then
echo "aborting loop"
exit ${PIPESTATUS[0]}
fi
done << input_list.txt