Estoy tratando de leer un archivo de texto y hacer algo con cada línea, usando un script bash.
Entonces, tengo una lista que se ve así:
server1
server2
server3
server4
Pensé que podría repetir esto usando un ciclo while, así:
while read server; do
ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt
El ciclo while se detiene después de 1 ejecución, por lo que solo ejecuta uname -a
en servidor1
Sin embargo, con un bucle for usando cat funciona bien:
for server in $(cat /home/kenny/list_of_servers.txt) ; do
ssh $server "uname -a"
done
Aún más desconcertante para mí es que esto también funciona:
while read server; do
echo $server
done < /home/kenny/list_of_servers.txt
¿Por qué mi primer ejemplo se detiene después de la primera iteración?
Respuesta aceptada:
El for
el bucle está bien aquí. Pero tenga en cuenta que esto se debe a que el archivo contiene nombres de máquinas, que no contienen espacios en blanco ni caracteres globales. for x in $(cat file); do …
no funciona para iterar sobre las líneas de file
en general, porque el shell primero divide la salida del comando cat file
en cualquier lugar donde haya espacios en blanco, y luego trata cada palabra como un patrón global, por lo que \[?*
se amplían aún más. Puedes hacer for x in $(cat file)
seguro si trabajas en ello:
set -f
IFS='
'
for x in $(cat file); do …
Lectura relacionada:¿Recorriendo archivos con espacios en los nombres?; ¿Cómo puedo leer línea por línea de una variable en bash?; ¿Por qué while IFS= read
usado tan a menudo, en lugar de IFS=; while read..
? Tenga en cuenta que al usar while read
, la sintaxis segura para leer líneas es while IFS= read -r line; do …
.
Ahora pasemos a lo que va mal con su while read
intento. La redirección desde el archivo de la lista de servidores se aplica a todo el bucle. Así que cuando ssh
se ejecuta, su entrada estándar proviene de ese archivo. El cliente ssh no puede saber cuándo la aplicación remota podría querer leer desde su entrada estándar. Entonces, tan pronto como el cliente ssh nota alguna entrada, envía esa entrada al lado remoto. El servidor ssh allí está listo para enviar esa entrada al comando remoto, en caso de que lo desee. En su caso, el comando remoto nunca lee ninguna entrada, por lo que los datos terminan descartados, pero el lado del cliente no sabe nada al respecto. Tu intento con echo
funcionó porque echo
nunca lee ninguna entrada, deja solo su entrada estándar.
Hay algunas maneras de evitar esto. Puede decirle a ssh que no lea desde la entrada estándar, con -n
opción.
while read server; do
ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt
El -n
opción de hecho le dice a ssh
para redirigir su entrada desde /dev/null
. Puede hacerlo a nivel de shell y funcionará para cualquier comando.
while read server; do
ssh $server "uname -a" </dev/null
done < /home/kenny/list_of_servers.txt
Un método tentador para evitar que la entrada de ssh provenga del archivo es poner la redirección en read
comando:while read server </home/kenny/list_of_servers.txt; do …
. Esto no funcionará, porque hace que el archivo se vuelva a abrir cada vez que read
se ejecuta el comando (por lo que leería la primera línea del archivo una y otra vez). La redirección debe ser en todo el ciclo while para que el archivo se abra una vez durante la duración del ciclo.
La solución general es proporcionar la entrada al bucle en un descriptor de archivo que no sea la entrada estándar. El shell tiene construcciones para transportar entradas y salidas de un número de descriptor a otro. Aquí, abrimos el archivo en el descriptor de archivo 3 y redirigimos el read
entrada estándar del comando del descriptor de archivo 3. El cliente ssh ignora los descriptores no estándar abiertos, por lo que todo está bien.
while read server <&3; do
ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt
En bash, el read
El comando tiene una opción específica para leer desde un descriptor de archivo diferente, por lo que puede escribir read -u3 server
.
Lectura relacionada:descriptores de archivos y secuencias de comandos de shell; ¿Cuándo usaría un descriptor de archivo adicional?