Membaca baris dari file dengan bash: for vs. while

36

Saya mencoba membaca file teks dan melakukan sesuatu dengan setiap baris, menggunakan skrip bash.

Jadi, saya punya daftar yang terlihat seperti ini:

server1
server2
server3
server4

Saya pikir saya bisa mengulanginya menggunakan loop sementara, seperti:

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

Loop sementara berhenti setelah 1 kali dijalankan, sehingga hanya berjalan uname -adi server1

Namun, dengan for for loop menggunakan cat berfungsi dengan baik:

for server in $(cat /home/kenny/list_of_servers.txt) ; do
  ssh $server "uname -a"
done

Yang lebih membingungkan bagi saya adalah ini juga bekerja:

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

Mengapa contoh pertama saya berhenti setelah iterasi pertama?

Kenny Rasschaert
sumber

Jawaban:

25

The forLoop baik-baik saja di sini. Tetapi perhatikan bahwa ini karena file tersebut berisi nama mesin, yang tidak mengandung karakter spasi putih atau karakter globbing. for x in $(cat file); do …tidak bekerja untuk mengulangi garis-garis filepada umumnya, karena shell pertama membagi output dari perintah di cat filemana saja ada spasi, dan kemudian memperlakukan setiap kata sebagai pola gumpalan sehingga \[?*diperluas lebih lanjut. Anda dapat membuat for x in $(cat file)aman jika Anda mengerjakannya:

set -f
IFS='
'
for x in $(cat file); do 

Bacaan terkait: Looping melalui file dengan spasi di namanya? ; Bagaimana saya bisa membaca baris demi baris dari variabel dalam bash? ; Mengapa while IFS= readsering digunakan, bukan IFS=; while read..? Perhatikan bahwa saat menggunakan while read, sintaks aman untuk membaca baris adalah while IFS= read -r line; do ….

Sekarang mari kita beralih ke apa yang salah dengan while readusaha Anda . Pengalihan dari file daftar server berlaku untuk seluruh loop. Jadi ketika sshdijalankan, input standarnya berasal dari file itu. Klien ssh tidak dapat mengetahui kapan aplikasi jarak jauh mungkin ingin membaca dari input standarnya. Jadi begitu klien ssh memperhatikan beberapa input, ia mengirimkan input itu ke sisi jarak jauh. Server ssh di sana kemudian siap untuk memasukkan input itu ke perintah jarak jauh, jika ia menginginkannya. Dalam kasus Anda, perintah jarak jauh tidak pernah membaca input apa pun, sehingga data akhirnya dibuang, tetapi pihak klien tidak tahu apa-apa tentang itu. Upaya Anda dengan echoberhasil karena echotidak pernah membaca input apa pun, ia meninggalkan input standarnya sendiri.

Ada beberapa cara untuk menghindari hal ini. Anda dapat memberitahu ssh untuk tidak membaca dari input standar, dengan -nopsi.

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

The -npilihan sebenarnya memberitahu sshuntuk mengarahkan input dari /dev/null. Anda dapat melakukannya di level shell, dan itu akan berfungsi untuk perintah apa pun.

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

Sebuah metode menggoda untuk masukan menghindari ssh ini berasal dari file tersebut adalah untuk menempatkan pengalihan pada readperintah: while read server </home/kenny/list_of_servers.txt; do …. Ini tidak akan berfungsi, karena menyebabkan file dibuka lagi setiap kali readperintah dieksekusi (sehingga akan membaca baris pertama file berulang-ulang). Pengalihan harus pada keseluruhan while sehingga file dibuka sekali selama durasi loop.

Solusi umum adalah memberikan input ke loop pada deskriptor file selain dari input standar. Shell memiliki konstruksi untuk mengangkut input dan output dari satu nomor deskriptor ke yang lain. Di sini, kita membuka file pada file deskriptor 3, dan mengarahkan readinput standar perintah dari file deskriptor 3. Klien ssh mengabaikan deskriptor non-standar terbuka, jadi semuanya baik-baik saja.

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

Di bash, readperintah memiliki opsi khusus untuk membaca dari deskriptor file yang berbeda, sehingga Anda bisa menulis read -u3 server.

Bacaan terkait: File deskriptor & skrip shell ; Kapan Anda akan menggunakan deskriptor file tambahan?

Gilles 'SANGAT berhenti menjadi jahat'
sumber
5
Man, stackexchange ini tentu berbeda dari serverfault. Orang-orang di sini benar-benar berusaha tidak hanya untuk menjawab, tetapi juga mendidik. Terima kasih banyak.
Kenny Rasschaert
26

Anda harus menggunakan while, bukanfor . Cara untuk menghindari perintah menelan input standar dalam loop seperti itu adalah dengan menggunakan deskriptor file lain :

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

Untuk informasi lebih lanjut, help [r]ead( sungguh ) dan artikel lain yang menjelaskan alasannya .

l0b0
sumber
1
Menarik, saya belum pernah menggunakan deskriptor file sebelumnya. Apa yang dilakukan -ubendera? Saya tidak yakin di halaman manual apa saya seharusnya mencarinya.
Kenny Rasschaert
Perintah baca adalah bagian dari shell bash, jadi ada di halaman manual bash di bagian "SHELL BUILTIN PERINTAH" di paragraf baca. Anda akan menemukan "-u fd Baca input dari deskriptor file fd." dalam paragraf ini
f4m8
6
Atau help read- helpadalah perintah untuk mendapatkan yang setara dengan manhalaman untuk builtin shell.
l0b0
22

Dalam kode pertama Anda sshakan "mencuri" STDIN dari while. Tambahkan -nopsi sshuntuk menghindarinya. Dari man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).
manatwork
sumber
Ok, ada yang tahu mengapa ini tidak terjadi dengan for loop?
Kenny Rasschaert
7
Karena forloop menerima data sebagai parameter, bukan sebagai input.
manatwork
1
Anda Tuan, adalah pria terhormat dan cendekiawan.
Kenny Rasschaert
0

Penanganan input standar standar dari sshsaluran yang tersisa mengalir dari loop while.

Untuk menghindari masalah ini, ubah dari mana perintah bermasalah membaca input standar. Jika tidak ada input standar yang perlu diteruskan ke perintah, baca input standar dari /dev/nullperangkat khusus .

nixguru
sumber