Sto cercando di leggere un file di testo e fare qualcosa con ogni riga, usando uno script bash.
Quindi, ho un elenco simile a questo:
server1
server2
server3
server4
Ho pensato di poter ripetere questo ciclo usando un ciclo while, in questo modo:
while read server; do
ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt
Il ciclo while si interrompe dopo 1 esecuzione, quindi eseguendo solo uname -a
sul server1
Tuttavia, con un ciclo for che usa cat funziona bene:
for server in $(cat /home/kenny/list_of_servers.txt) ; do
ssh $server "uname -a"
done
Ancora più sconcertante per me è che funziona anche questo:
while read server; do
echo $server
done < /home/kenny/list_of_servers.txt
Perché il mio primo esempio si interrompe dopo la prima iterazione?
Risposta accettata:
Il for
il ciclo va bene qui. Ma nota che ciò è dovuto al fatto che il file contiene nomi di macchine, che non contengono caratteri di spazi vuoti o caratteri di globbing. for x in $(cat file); do …
non funziona per scorrere le righe di file
in generale, perché la shell prima divide l'output dal comando cat file
ovunque ci sia uno spazio bianco, quindi tratta ogni parola come un pattern glob quindi \[?*
vengono ulteriormente ampliati. Puoi creare for x in $(cat file)
sicuro se ci lavori:
set -f
IFS='
'
for x in $(cat file); do …
Lettura correlata:scorrere i file con spazi nei nomi?; Come posso leggere riga per riga da una variabile in bash?; Perché while IFS= read
usato così spesso, invece di IFS=; while read..
? Nota che quando usi while read
, la sintassi sicura per leggere le righe è while IFS= read -r line; do …
.
Ora passiamo a cosa non va con il tuo while read
tentativo. Il reindirizzamento dal file di elenco dei server si applica all'intero ciclo. Quindi quando ssh
viene eseguito, il suo input standard proviene da quel file. Il client ssh non può sapere quando l'applicazione remota potrebbe voler leggere dal suo input standard. Quindi, non appena il client ssh nota un input, invia quell'input al lato remoto. Il server ssh è quindi pronto per inviare quell'input al comando remoto, se lo desidera. Nel tuo caso, il comando remoto non legge mai alcun input, quindi i dati finiscono per essere scartati, ma il lato client non ne sa nulla. Il tuo tentativo con echo
ha funzionato perché echo
non legge mai alcun input, lascia solo il suo input standard.
Ci sono alcuni modi per evitarlo. Puoi dire a ssh di non leggere dallo standard input, con -n
opzione.
while read server; do
ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt
Il -n
l'opzione infatti dice a ssh
per reindirizzare il suo input da /dev/null
. Puoi farlo a livello di shell e funzionerà per qualsiasi comando.
while read server; do
ssh $server "uname -a" </dev/null
done < /home/kenny/list_of_servers.txt
Un metodo allettante per evitare l'input di ssh proveniente dal file è inserire il reindirizzamento su read
comando:while read server </home/kenny/list_of_servers.txt; do …
. Questo non funzionerà, perché fa sì che il file venga aperto di nuovo ogni volta che read
il comando viene eseguito (quindi leggerebbe la prima riga del file più e più volte). Il reindirizzamento deve essere sull'intero ciclo while in modo che il file venga aperto una volta per la durata del ciclo.
La soluzione generale consiste nel fornire l'input al ciclo su un descrittore di file diverso dallo standard input. La shell ha costrutti per trasferire input e output da un numero di descrittore a un altro. Qui, apriamo il file sul descrittore di file 3 e reindirizziamo il read
input standard del comando dal descrittore di file 3. Il client ssh ignora i descrittori non standard aperti, quindi va tutto bene.
while read server <&3; do
ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt
In bash, il read
comando ha un'opzione specifica per leggere da un descrittore di file diverso, quindi puoi scrivere read -u3 server
.
Lettura correlata:Descrittori di file e script di shell; Quando useresti un descrittore di file aggiuntivo?