web-dev-qa-db-ja.com

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

テキストファイルを読み取って、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 on server1

ただし、猫を使用した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

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

36

forループはここでは問題ありません。ただし、これは、ファイルに空白文字やグロビング文字が含まれていないマシン名が含まれているためです。 for x in $(cat file); do …は、一般にfileの行を反復処理するようには機能しません。これは、シェルが最初にコマンド_cat file_からの出力を空白がある場所で分割し、次に各Wordを処理するためです。グロブパターンなので、_\[?*_はさらに拡張されます。作業する場合は、for x in $(cat file)を安全にすることができます。

_set -f
IFS='
'
for x in $(cat file); do …
_

関連資料: 名前にスペースが含まれるファイルをループしますか? ; bashの変数から1行ずつ読み取るにはどうすればよいですか? ; なぜ_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回開かれます。

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

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

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

関連資料: ファイル記述子とシェルスクリプト ; いつ追加のファイル記述子を使用しますか?

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

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

詳細については、 help [r]ead本当に )および 理由を説明する別の記事

26
l0b0

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

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

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

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

0
nixguru