Menggunakan while loop ke ssh ke beberapa server

75

Saya punya file servers.txt, dengan daftar server:

server1.mydomain.com
server2.mydomain.com
server3.mydomain.com

ketika saya membaca file baris demi baris dengan whiledan menggema setiap baris, semua berfungsi seperti yang diharapkan. Semua garis dicetak.

$ while read HOST ; do echo $HOST ; done < servers.txt
server1.mydomain.com
server2.mydomain.com
server3.mydomain.com

Namun, ketika saya ingin ssh ke semua server dan menjalankan perintah, tiba-tiba whileloop saya berhenti berfungsi:

$ while read HOST ; do ssh $HOST "uname -a" ; done < servers.txt
Linux server1 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux

Ini hanya terhubung ke server pertama dalam daftar, bukan ke semuanya. Saya tidak mengerti apa yang terjadi di sini. Bisakah seseorang tolong jelaskan?

Ini bahkan lebih aneh, karena menggunakan forloop berfungsi dengan baik:

$ for HOST in $(cat servers.txt ) ; do ssh $HOST "uname -a" ; done
Linux server1 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux
Linux server2 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux
Linux server3 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux

Itu harus sesuatu yang spesifik ssh, karena perintah lain berfungsi dengan baik, seperti ping:

$ while read HOST ; do ping -c 1 $HOST ; done < servers.txt
Martin Vegter
sumber
4
gunakanansible
Matt

Jawaban:

98

ssh sedang membaca sisa input standar Anda.

while read HOST ; do … ; done < servers.txt

readdibaca dari stdin. The <pengalihan stdin dari file.

Sayangnya, perintah yang Anda coba jalankan juga membaca stdin, sehingga akhirnya memakan sisa file Anda. Anda dapat melihatnya dengan jelas dengan:

$ while read HOST ; do echo start $HOST end; cat; done < servers.txt 
start server1.mydomain.com end
server2.mydomain.com
server3.mydomain.com

Perhatikan bagaimana catmemakan (dan menggema) dua baris yang tersisa. (Sudah membaca melakukannya seperti yang diharapkan, setiap baris akan memiliki "mulai" dan "berakhir" di sekitar host.)

Kenapa forberhasil?

Anda forgaris tidak redirect ke stdin. (Bahkan, ia membaca seluruh isi servers.txtfile ke dalam memori sebelum iterasi pertama). Jadi sshterus membaca stdin-nya dari terminal (atau mungkin tidak sama sekali, tergantung pada bagaimana script Anda dipanggil).

Larutan

Setidaknya dalam bash, Anda dapat readmenggunakan deskriptor file yang berbeda.

while read -u10 HOST ; do ssh $HOST "uname -a" ; done 10< servers.txt
#          ^^^^                                       ^^

seharusnya bekerja. 10hanyalah nomor file sewenang-wenang yang saya pilih. 0, 1, dan 2 telah mendefinisikan makna, dan biasanya membuka file akan dimulai dari angka pertama yang tersedia (sehingga 3 akan digunakan berikutnya). Dengan demikian 10 cukup tinggi untuk menghindar, tetapi cukup rendah untuk berada di bawah batas dalam beberapa cangkang. Ditambah dengan angka bulat yang bagus ...

Solusi Alternatif 1: -n

Seperti yang ditunjukkan McNisse dalam jawabannya , klien OpenSSH memiliki -nopsi yang akan mencegahnya membaca stdin. Ini bekerja dengan baik dalam kasus tertentu ssh, tetapi tentu saja perintah lain mungkin kekurangan ini — solusi lain bekerja terlepas dari perintah mana yang memakan stdin Anda.

Solusi Alternatif 2: pengalihan kedua

Anda ternyata dapat (seperti pada, saya mencobanya, setidaknya berfungsi dalam versi Bash saya ...) melakukan redirect kedua, yang terlihat seperti ini:

while read HOST ; do ssh $HOST "uname -a" < /dev/null; done < servers.txt

Anda dapat menggunakan ini dengan perintah apa pun, tetapi akan sulit jika Anda benar-benar ingin input terminal pergi ke perintah.

derobert
sumber
1
dari mana nomor itu 10berasal?
Martin Vegter
2
@ MartinVegter saya mengada-ada. 0/1/2 adalah stdin, stdout, dan stderr. Anda dapat memilih nomor apa saja yang Anda suka — bash memungkinkan Anda menjadi cukup tinggi, mungkin hingga batas OS. Kerang lain dapat membatasi Anda menjadi lebih sedikit ...
derobert
@ MartinVegter Saya telah mengedit untuk menjawab kedua komentar Anda.
derobert
Alternatif lain: exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- Ini membuat file deskriptor 3 menjadi cadangan stdin asli, kemudian menggunakannya untuk stdin ssh, lalu menutup cadangan FD setelah selesai. Ini berfungsi pada semua shell yang kompatibel dengan POSIX.
Richard Hansen
8
The -uopsi tidak didukung oleh POSIX, dan dengan demikian tidak boleh digunakan untuk #!/bin/shscript; gunakan read HOST <&10saja. Selain itu, POSIX hanya membutuhkan shell untuk mendukung deskriptor file 0 hingga 9, jadi 10<servers.txttidak dapat digunakan jika skrip harus benar-benar sesuai.
Richard Hansen
28

Seperti yang dijelaskan derobert, sshbacaan Anda stdin.

Untuk mengubah perilaku ini, Anda dapat menambahkan -n no ssh untuk mencegahnya membaca stdin.

ssh -n $HOST "uname -a"
McNisse
sumber
10

Anda mungkin sebenarnya lebih baik menggunakan pssh dari proyek paralel-ssh .

pssh -h $hostfile -t $timeout -i $commands

-iberarti interaktif. pssh juga dilengkapi dengan scp paralel dan rsync paralel. Apa yang baik adalah itu berjalan secara tidak sinkron dan akan menjalankan sebanyak utas yang Anda minta. Default (non -i / interaktif) adalah untuk output ke direktori terpisah untuk stdout / stderr, yang dilakukan oleh $ outputdir / $ hostname.

infowolfe
sumber
3

Jika Anda sering melakukan tugas semacam ini, Anda harus mencoba Fabric

Instal Fabric mengikuti instruksi , sebagian besar kasus yang Anda butuhkansudo apt-get install fabric

Buat file fabfile.pydengan nama kode berikut:

from fabric.api import env, run

env.hosts = ['server1.mydomain.com',
             'server1.mydomain.com',
             'server1.mydomain.com']

def mytask():
    run('uname -a')

Kemudian jalankan fab mytaskakan memberi Anda hasil yang Anda inginkan.

nomor 5
sumber
1

Lebih mudah menggunakan perintah seperti ini:

for f in `cat servers.txt`; do ssh $f uname -a; done

Saya biasanya suka ini:

for f in `cat servers.txt`; do echo "### $f ###"; ssh $f uname -a; done

The echoadalah untuk melihat mana server terjebak, atau tidak dapat menyambung ke.

Ionut Ciucanu
sumber
1

Karena perintah ssh mengambil semua aliran dari input standar, diumpankan oleh pernyataan while,

Anda dapat menggunakan pipa untuk mengalihkan stdin ssh ke sumber lain:

echo "" | ssh ...

contoh:

while read HOST ; do echo "" | ssh $HOST "uname -a" ; done < servers.txt

input stdin dari semua perintah ssh dalam loop sementara harus dialihkan ke sumber lain.

Ali ISSA
sumber