bashを使用してファイルから行を読み取る:for vs. while


36

テキストファイルを読み取って、bashスクリプトを使用して各行で何かを実行しようとしています。

そのため、次のようなリストがあります。

server1
server2
server3
server4

次のように、whileループを使用してこれをループできると考えました。

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

whileループは1回の実行後に停止するためuname -a、server1でのみ実行されます

ただし、catを使用したforループでは正常に動作します。

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

さらに私を困惑させるのは、これも機能するということです。

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

最初の反復後に最初の例が停止するのはなぜですか?

回答:


25

ここでforループは正常です。ただし、ファイルにはマシン名が含まれており、マシン名には空白文字やグロビング文字が含まれていないためです。一般に、シェルはコマンドの出力を空白がある場所で分割し、次に各単語をグロブパターンとして扱い、さらに展開されるため、一般的にfor x in $(cat file); do …行を反復処理しません。あなたがそれに取り組むならば、あなたは安全にすることができます:filecat file\[?*for x in $(cat file)

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

関連資料名前にスペースが含まれるファイルをループしますか?; bashの変数から行ごとに読み取るにはどうすればよいですか?; なぜではwhile IFS= readなく、頻繁に使用されるのIFS=; while read..ですか?を使用する場合while read、行を読み取るための安全な構文はwhile IFS= read -r line; do …です。

それでは、あなたのwhile read試みで何がうまくいかないかを見てみましょう。サーバーリストファイルからのリダイレクトは、ループ全体に適用されます。そのため、ssh実行時の標準入力はそのファイルから取得されます。sshクライアントは、リモートアプリケーションが標準入力からの読み取りをいつ必要とするかを知ることができません。そのため、sshクライアントは何らかの入力を認識するとすぐに、その入力をリモート側に送信します。そこにあるsshサーバーは、必要に応じて、その入力をリモートコマンドに送る準備ができています。あなたの場合、リモートコマンドは入力をまったく読み取らないため、データは破棄されますが、クライアント側はそれについて何も知りません。あなたの試みは、入力をまったく読み取らず、標準入力をそのままにしechoているため、うまくechoいきました。

これを回避する方法はいくつかあります。-nオプションを使用して、sshに標準入力から読み取らないように指示できます。

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

-n実際にはオプションが告げるsshから入力をリダイレクトします/dev/null。これはシェルレベルで行うことができ、どのコマンドでも機能します。

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

ファイルからのsshの入力を回避する魅力的な方法は、readコマンドにリダイレクトを設定することですwhile read server </home/kenny/list_of_servers.txt; do …。これは機能しません。readコマンドが実行されるたびにファイルが再び開かれるためです(したがって、ファイルの最初の行を何度も読み取ります)。ファイルがループの期間中に一度開かれるように、リダイレクトはwhileループ全体で行う必要があります。

一般的な解決策は、標準入力以外のファイル記述子でループに入力を提供することです。シェルには、1つの記述子番号から別の記述子番号への入出力をフェリーする構造があります。ここでは、ファイル記述子3でファイルを開き、ファイル記述子3 readからコマンドの標準入力をリダイレクトします。sshクライアントは開いている非標準記述子を無視するため、すべて正常です。

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

bashでは、readコマンドには異なるファイル記述子から読み取るための特定のオプションがあるため、を書き込むことができますread -u3 server

関連読書:ファイルディスクリプタ&シェルスクリプトいつ追加のファイル記述子を使用しますか?


5
男、このstackexchangeは確かにserverfaultとは異なります。ここの人々は本当に答えるだけでなく、教育するために邪魔になりません。どうもありがとう。
ケニーラッシャールト

26

whileではなくforを使用する必要があります。このようなループでコマンドが標準入力を飲み込むのを避ける方法は、別のファイル記述子を使用することです

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

詳細については、help [r]ead実際に)および理由を説明する別の記事を参照してください


1
興味深いことに、私はこれまでにファイル記述子を使用したことがありません。-uフラグは何をしますか?どのマニュアルページで調べるべきかわかりません。
ケニーラッシャールト

読み取りコマンドはシェルbashの一部であるため、読み取りパラグラフの「SHELL BUILTINコマンド」セクションのbashのマニュアルページにあります。「-u fdファイル記述子fdから入力を読み取ります」が見つかります。この段落で
f4m8

6
またはhelp read- シェル組み込みhelpmanページに相当するものを取得するコマンドです。
l0b0

22

最初のコードでsshは、からSTDINを「盗み」ますwhile。回避する-nオプションを追加しますssh。からman ssh

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).


7
forループはデータを入力としてではなくパラメーターとして受け取るためです。
マナトワーク

1
あなたは紳士であり学者です。
ケニーラッシャールト

0

デフォルトの標準入力処理はssh、whileループから残りの行を排出します。

この問題を回避するには、問題のあるコマンドが標準入力を読み取る場所を変更します。標準入力をコマンドに渡す必要がない場合は、特殊/dev/nullデバイスから標準入力を読み取ります。

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