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


75

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

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

ファイルをwhile1行ずつ読み取り、各行をエコーすると、すべて正常に機能します。すべての行が印刷されます。

$ 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

回答:


98

ssh 標準入力の残りを読んでいます。

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

readstdinから読み取ります。<ファイルから標準入力リダイレクト。

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

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

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

なぜ機能するのforですか?

あなたのforラインは、標準入力にリダイレクトされません。(実際、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

以下のようMcNisseがで指摘し、彼/彼女の答えは、OpenSSHクライアントを持つ-n標準入力を読み込むことを防ぐだろうオプションを選択します。これはの特定の場合にうまく機能しsshますが、もちろん他のコマンドにはこれがない場合があります。他のソリューションは、どのコマンドが標準入力を使用しているかに関係なく機能します。

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

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

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

これはどのコマンドでも使用できますが、実際にコマンドへの端末入力が必要な場合は困難です。


1
番号は10どこから来たのですか?
マーティンVegter 14年

2
@MartinVegter作りました。0/1/2は、stdin、stdout、およびstderrです。任意の数を選択できます。bashを使用すると、おそらくOSの制限まで、かなり高くすることができます。その他のシェルは少ない...にあなたを制限するかもしれない
derobert

@MartinVegter両方のコメントに答えるように編集しました。
デロバート14年

別の代替方法:exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- これは、ファイル記述子3を元のstdinのバックアップにし、それをsshのstdinに使用し、完了したらバックアップFDを閉じます。これは、POSIX互換シェルで機能します。
リチャードハンセン14年

8
この-uオプションはPOSIXではサポートされていないため、#!/bin/shスクリプトには使用しないでください。read HOST <&10代わりに使用してください。また、POSIXでは、ファイル記述子0〜9をサポートするシェルのみが必要であるため10<servers.txt、スクリプトが厳密に準拠している場合は使用できません。
リチャードハンセン14年

28

derobertが説明するsshように、あなたのstdin

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

ssh -n $HOST "uname -a"

10

おそらく、parallel-sshプロジェクトのpsshを使用した方がよいでしょう。

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

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


3

この種のタスクを頻繁に実行している場合は、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すると、必要な結果が得られます。


1

次のようなコマンドを使用すると、より簡単になります。

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

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

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

echo "" | ssh ...

例:

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.