web-dev-qa-db-ja.com

whileループを使用して複数のサーバーにsshする

ファイルがありますservers.txt、サーバーのリスト:

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

whileを使用してファイルを1行ずつ読み取り、各行をエコーすると、すべて期待どおりに動作します。すべての行が印刷されます。

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

しかし、すべてのサーバーにsshしてコマンドを実行したい場合、whileループが突然機能しなくなります。

$ 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

これは、すべてではなく、リストの最初のサーバーにのみ接続します。ここで何が起こっているのか理解できません。誰か説明してもらえますか?

forループを使用すると正常に機能するため、これはさらに奇妙です。

$ 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

sshなどの他のコマンドは正常に機能するため、pingに固有の何かである必要があります。

$ while read Host ; do ping -c 1 $Host ; done < servers.txt
82
Martin Vegter

sshは残りの標準入力を読み取っています。

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

readはstdinから読み取ります。 <は、ファイルからstdinをリダイレクトします。

残念ながら、実行しようとしているコマンドはstdinも読み取るため、ファイルの残りの部分を食べてしまいます。あなたはそれをはっきりと見ることができます:

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

残りの2行をcatがどのように食べた(そしてエコーした)かに注意してください。 (期待どおりに読んだ場合、各行にはホストの周りに「開始」と「終了」があります。)

forが機能する理由

for行はstdinにリダイレクトされません。 (実際、最初の反復の前にservers.txtファイルの内容全体をメモリに読み込みます)。そのため、sshは引き続き端末から標準入力を読み取ります(スクリプトの呼び出し方法によっては、何もない場合もあります)。

解決

少なくともbashでは、readに別のファイル記述子を使用させることができます。

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

動作するはずです。 10は、私が選んだ任意のファイル番号です。 0、1、2には定義された意味があり、通常、ファイルを開くには、最初に使用可能な番号から開始します(したがって、3が次に使用されます)。したがって、10は邪魔にならない程度の高さですが、一部のシェルでは制限を下回るのに十分な低さです。プラスその素敵なラウンド数...

代替ソリューション1:-n

McNissehis/her answer で指摘しているように、OpenSSHクライアントには-nオプションがあり、標準入力を読み取ることができません。これはsshの特定の場合にうまく機能しますが、もちろん他のコマンドにはこれがない場合があります。他のソリューションは、どのコマンドがstdinを食べているかに関係なく機能します。

代替ソリューション2:2番目のリダイレクト

あなたは明らかに(私が試したように、少なくとも私のバージョンのBashで動作します...)次のような2回目のリダイレクトを行うことができます:

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

これは任意のコマンドで使用できますが、実際に端末入力をコマンドに送信する場合は困難です。

102
derobert

デロベルトが説明するようにsshはあなたのstdinを読みます。

この動作を変更するには、-n no sshを追加して、stdinを読み取らないようにします。

ssh -n $Host "uname -a"
33
McNisse

parallel-ssh プロジェクトのpss​​hを使用する方が実際には良いでしょう。

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

-iはインタラクティブです。 psshには、並列scpと並列rsyncも付属しています。何がいいのかというと、非同期で実行され、要求した数のスレッドが実行されるということです。デフォルト(-i/interactive以外)は、stdout/stderrの個別のディレクトリに出力することで、$ outputdir/$ hostnameによって実行されます。

10
infowolfe

この種のタスクを頻繁に実行している場合は、 Fabric を試してみてください。

手順 に従ってファブリックをインストールします。ほとんどの場合、必要なのはSudo apt-get install fabricだけです。

次のコードを使用して、fabfile.pyという名前のファイルを作成します。

from fabric.api import env, run

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

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

次に、fab mytaskを実行すると、必要な結果が得られます。

3
number5

Sshコマンドは、whileステートメントによって供給される標準入力からすべてのストリームを取得するため、

パイプを使用して、sshのstdinを別のソースに切り替えることができます。

echo "" | ssh ...

例:

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

whileループ内のすべてのsshコマンドのstdin入力は、別のソースに切り替える必要があります。

2
Ali ISSA

次のようなコマンドを使用する方が簡単です。

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

私は通常このようにします:

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

echoは、スタックしているサーバーまたは接続できないサーバーを確認するためのものです。

1
Ionut Ciucanu