ファイルがあります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
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は邪魔にならない程度の高さですが、一部のシェルでは制限を下回るのに十分な低さです。プラスその素敵なラウンド数...
McNisse が his/her answer で指摘しているように、OpenSSHクライアントには-n
オプションがあり、標準入力を読み取ることができません。これはssh
の特定の場合にうまく機能しますが、もちろん他のコマンドにはこれがない場合があります。他のソリューションは、どのコマンドがstdinを食べているかに関係なく機能します。
あなたは明らかに(私が試したように、少なくとも私のバージョンのBashで動作します...)次のような2回目のリダイレクトを行うことができます:
while read Host ; do ssh $Host "uname -a" < /dev/null; done < servers.txt
これは任意のコマンドで使用できますが、実際に端末入力をコマンドに送信する場合は困難です。
デロベルトが説明するようにssh
はあなたのstdin
を読みます。
この動作を変更するには、-n no sshを追加して、stdinを読み取らないようにします。
ssh -n $Host "uname -a"
parallel-ssh プロジェクトのpsshを使用する方が実際には良いでしょう。
pssh -h $hostfile -t $timeout -i $commands
-i
はインタラクティブです。 psshには、並列scpと並列rsyncも付属しています。何がいいのかというと、非同期で実行され、要求した数のスレッドが実行されるということです。デフォルト(-i/interactive以外)は、stdout/stderrの個別のディレクトリに出力することで、$ outputdir/$ hostnameによって実行されます。
この種のタスクを頻繁に実行している場合は、 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
を実行すると、必要な結果が得られます。
Sshコマンドは、whileステートメントによって供給される標準入力からすべてのストリームを取得するため、
パイプを使用して、sshのstdinを別のソースに切り替えることができます。
echo "" | ssh ...
例:
while read Host ; do echo "" | ssh $Host "uname -a" ; done < servers.txt
whileループ内のすべてのsshコマンドのstdin入力は、別のソースに切り替える必要があります。
次のようなコマンドを使用する方が簡単です。
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
は、スタックしているサーバーまたは接続できないサーバーを確認するためのものです。