Leggere righe da un file con bash: per vs. while


36

Sto cercando di leggere un file di testo e fare qualcosa con ogni riga, usando uno script bash.

Quindi, ho un elenco che assomiglia a questo:

server1
server2
server3
server4

Ho pensato di poterci ripetere 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 solo uname -asul 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 questo funziona anche:

while read server; do
  echo $server
done < /home/kenny/list_of_servers.txt

Perché il mio primo esempio si interrompe dopo la prima iterazione?

Risposte:


25

Il forciclo qui va bene. Si noti che ciò è dovuto al fatto che il file contiene nomi di macchine, che non contengono caratteri di spazi bianchi o caratteri globbing. for x in $(cat file); do …non funziona per scorrere le righe filein generale, perché la shell prima divide l'output dal comando cat fileovunque ci sia uno spazio bianco, quindi tratta ogni parola come un modello glob così \[?*vengono ulteriormente espansi. Puoi for x in $(cat file)metterti al 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é viene while IFS= readusato così spesso, invece di IFS=; while read..? Si noti che quando si utilizza while read, la sintassi sicura per leggere le righe è while IFS= read -r line; do ….

Ora passiamo a ciò che non va nel tuo while readtentativo. Il reindirizzamento dal file elenco server si applica all'intero ciclo. Quindi, quando sshviene 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 alimentare 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 ha echofunzionato perché echonon 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 l' -nopzione.

while read server; do
  ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt

L' -nopzione infatti dice sshdi 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

Metodo tentati di ingresso evitano di ssh proveniente dal file è quello di mettere il reindirizzamento del readcomando: while read server </home/kenny/list_of_servers.txt; do …. Questo non funzionerà, perché fa riaprire il file ogni volta che readviene eseguito il comando (in modo da leggere più volte la prima riga del file). Il reindirizzamento deve essere nell'insieme ciclo while in modo che il file venga aperto una volta per la durata del ciclo.

La soluzione generale è fornire l'input al loop su un descrittore di file diverso dall'input standard. La shell ha costrutti per trasportare input e output da un numero descrittore all'altro. Qui, apriamo il file sul descrittore di file 3 e reindirizziamo l' readinput standard del comando dal descrittore di file 3. Il client ssh ignora i descrittori non standard aperti, quindi tutto va bene.

while read server <&3; do
  ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt

In bash, il readcomando 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?


5
Amico, questo stackexchange è sicuramente diverso da serverfault. Le persone qui fanno davvero di tutto per non solo rispondere, ma anche educare. Molte grazie.
Kenny Rasschaert,

26

Dovresti usare while, nofor . Il modo per evitare i comandi che inghiottono l'input standard in tale ciclo è semplicemente quello di utilizzare un altro descrittore di file :

while read -u 9 server; do
  ssh $server "uname -a"
done 9< /home/kenny/list_of_servers.txt

Per ulteriori informazioni, help [r]ead( davvero ) e un altro articolo che spiega il perché .


1
Interessante, non ho mai usato descrittori di file prima. Cosa fa la -ubandiera? Non sono sicuro in quale pagina man dovrei cercarlo.
Kenny Rasschaert,

Il comando read fa parte della shell bash, quindi è nella pagina man di bash nella sezione "SHELL BUILTIN COMMANDS" nel paragrafo read. Troverai "-u fd Leggi l'input dal descrittore di file fd." in questo paragrafo
f4m8

6
In alternativa help read- helpè il comando per ottenere l'equivalente di manpagine per i builtin della shell.
l0b0

22

Nel tuo primo codice ssh"rubare" lo STDIN dal while. Aggiungi -nun'opzione sshper evitarlo. Da man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).

Ok, hai idea del perché questo non accada con il ciclo for?
Kenny Rasschaert,

7
Perché il forloop riceve i dati come parametro, non come input.
arte

1
Signore, siete gentiluomini e studiosi.
Kenny Rasschaert,

0

La gestione di input standard predefinita di sshsvuota la riga rimanente dal ciclo while.

Per evitare questo problema, modificare da dove il comando problematico legge l'input standard. Se non è necessario passare un input standard al comando, leggere l'input standard dal /dev/nulldispositivo speciale .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.