とあるサーバで、こんなコードを見かけました。
ssh $main ls ~ |
while read dir; do
rsync -e ssh -avz --progress $main:~/$dir ~/ < /dev/null >> $logd/LOG.$(basename $0)_rsync.$today 2>&1
done
どうやら、対象のサーバ${main}
へsshしたあと、ホームディレクトリをlsした結果を、スクリプト実行側へrsyncで同期しているようです。
ただ、このrsyncに、なぜか< /dev/null
を標準入力として渡しています。
/dev/null
は標準出力を捨てる際に、リダイレクト先として指定するパスです。
それをわざわざ標準入力として扱う理由とは何なんでしょうと思い、調べてみました。
cp -i に対して < /dev/nullを使うと、プロンプトに対してnoを自動的に入力する
rsyncに直接関係あるわけではないですが、調べてみたらこんな記事がありました。
linux - /dev/nullの入力リダイレクトの意味 - スタック・オーバーフロー
プロンプトに対する入力を </dev/null が抑制し no を入力したことになる
どうやらcp -i < /dev/null
とコマンドを打った時に、通常は上書きをして良いか聞かれるのに対して、自動的にnoを入力してくれるらしいです。
試してみます。
$ cat TESTFILE
aaa
bbb
ccc
$ cat ABC
ABC
$ cp -iv TESTFILE ABC < /dev/null ;
cp: `ABC` を上書きしてもよろしいですか(yes/no)?
$
$ cat ABC
ABC
確かに、自動的にnoが入力され、上書きが防止されています。
ちなみに、yes/noが出た時に、何もタイプせずにReturnをしてもno扱いとなります。
$ cp -iv TESTFILE ABC
cp: `ABC` を上書きしてもよろしいですか(yes/no)?
# ただReturnを押すだけでも、no扱いとなる
$
$ cat ABC
ABC
つまりこれは、空の入力さえあれば、何でもいいのでは?
$ : > kara
cp -iv TESTFILE ABC < kara ;
cp: `ABC` を上書きしてもよろしいですか(yes/no)?
$
$ cat ABC
ABC
いけました。
ちなみに、私は初めて知ったのですが、無限にyを吐き出してくれるyes
コマンドというものがあるらしいです。
$ yes | head
y
y
y
y
y
y
y
y
y
y
$ yes | cp -iv TESTFILE ABC
cp: `ABC` を上書きしてもよろしいですか(yes/no)? `TESTFILE` -> `ABC`
$ cat ABC
aaa
bbb
ccc
こうしてみると、プロンプトのyes/noの入力も標準入力で受け付けられることがわかりました。
whileループ中のsshに< /dev/nullを使うと、ループが中断されることなく最後まで実行される
ssh絡みで、こんな記事がありました。
while内でsshを使うと1回で止まる - Miuran Business Systems
原因はsshコマンド実行に伴う標準入力の切替です。sshコマンドを実行すると、ローカルホストからの標準入力を停止し、sshで指定したリモートホストからの標準入力の受付を開始します。
つまり、上記のコードの例では、ローカルホストのファイルの読込みを終了させた上でsshコマンドを実行し、再びreadコマンドを実行しようとしているわけです。この時、既にファイルが閉じている為にwhileが終了してしまうわけです。
記事によると、sshした段階で、ローカルでwhile read
に渡していた標準入力は打ち切られてしまい、そのままループが終わってしまうそうです。
# ① listfileの結果を受け取り、readコマンドが読み取り、line変数にセット
cat listfile |
while read line
# ③ ②の後に標準入力が閉じてしまい、whileループが終わる(1回ループして終了)
do
echo ${line}
# ② ssh接続したあと、リモートホスト側で、echo実行。
# このとき、ローカルの標準入力は閉じる。
ssh ${remotehost} "echo ${line}"
done
1回で終わらせず、最後までループさせたい場合としては、-n
オプションを使う必要があるそうです。
これは、内部的には< /dev/null
をやっているらしく、
ssh -n
をmanコマンドで確認してみると、ガッツリ < /dev/null
について書かれています。
-n Redirects stdin from /dev/null (actually, prevents reading from stdin).
This must be used when ssh is run in the background.
A common trick is to use this to run X11 programs on a remote machine.
For example, ssh -n shadows.cs.hut.fi emacs & will start an emacs on shad- ows.cs.hut.fi,
and the X11 connection will be automatically forwarded over an encrypted chan- nel.
The ssh program will be put in the background. (This does not work if ssh needs to ask for a password or passphrase; see also the -f option.)
日本語での説明はこちら
ざっくり書いてある内容は、
「標準入力を/dev/nullからリダイレクトする=標準入力からの読み込みを禁止した状態にする」
ということらしいです。
以下はローカルのHOSTNAMEを標準入力として渡しつつ、リモートホストでcat+リモートのHOSTNAMEをechoするサンプルです。
< /dev/null
(または-nオプション)を指定すると、ローカル側の標準入力が受け取れなくなっているのがわかります。
# ローカルのHOSTNAMEをリモート側が標準入力として受け取り、catする
$ echo $HOSTNAME | ssh remote 'cat; echo $HOSTNAME'
local
remote
# sshが標準入力を禁止したので、ローカルのホスト名は受け取れない
$ echo $HOSTNAME | ssh remote 'cat; echo $HOSTNAME' < /dev/null
remote
# nオプションも同様
$ echo $HOSTNAME | ssh -n remote 'cat; echo $HOSTNAME'
remote
では、rsync -e sshは?
ここまで調べてみましたが、
では冒頭で疑問に思ったrsync -e ssh
はどうなのでしょう。
やはりssh経由になるから、標準入力が途中で閉じてしまい、ループが中断されるのでしょうか。
実際にターミナルで叩いて確認をしてみます。
$ ssh $main ls ~ | while read dir; do echo $dir ; rsync -e ssh -avz --progress $main:~/$dir ~/ < /dev/null ; done
DDS
receiving incremental file list
sent 14 bytes received 5443 bytes 10914.00 bytes/sec
total size is 2024791 speedup is 371.04
SHELL
receiving incremental file list
sent 12 bytes received 986 bytes 1996.00 bytes/sec
total size is 50163 speedup is 50.26
SYS
receiving incremental file list
sent 14 bytes received 4765 bytes 9558.00 bytes/sec
total size is 287499 speedup is 60.16
$ ssh $main ls ~ | while read dir; do echo $dir ; rsync -e ssh -avz --progress $main:~/$dir ~/ ; done
DDS
receiving incremental file list
sent 14 bytes received 5443 bytes 10914.00 bytes/sec
total size is 2024791 speedup is 371.04
SHELL
receiving incremental file list
sent 12 bytes received 986 bytes 1996.00 bytes/sec
total size is 50163 speedup is 50.26
SYS
receiving incremental file list
sent 14 bytes received 4765 bytes 9558.00 bytes/sec
total size is 287499 speedup is 60.16
特に< /dev/null
を指定しなくても、ループが中断されていないように見えます。
ssh接続を利用してるとはいえ、rsyncコマンドの仕様で標準入力が切り替わらないようになっているのでしょうか?
(この辺調べてもよくわからなかったので、誰か詳しい方いたらぜひとも教えて欲しいです……)
まとめ
-
< /dev/null
をyes/noと聞かれるコマンドに渡すと、自動的にno扱いにしてくれる - ssh接続時に
< /dev/null
を指定すると、リモートホスト側の標準入力を受け取るのを禁止する- そのため、ローカル側の標準入力が途切れず、whileループが途切れない。
- rsync -e sshに対しての
< /dev/null
は謎。- sshコマンドと同様にループが途切れると思って付けた……?
おまけ
whileループ中のssh〜で紹介していた記事の中に、whileでは無くてforで書けば、sshしてもループが途切れることが無いとのことです。
ただ、whileとforの明確な違いについては不明。
(誰か詳しい方教えて下さい……)
2019/11/28追記
プログラマーの君! 騙されるな! シェルスクリプトはそう書いちゃ駄目だ!! という話 - Qiita
こちらを読んでいて、 for ... in
の書き方だと、あらかじめリストを列挙してから、その分ループをさせるという挙動のため、sshで標準入力が切り替わっても、ループが止まらない、という理解をしました。
なにはともあれ、ループで処理するなら、可能であればfor。
whileになるのであれば、 -n
もしくは< /dev/null
を使うというやり方が良さそうです。
(あくまで、sshをループ内で扱うときのお話です)
参考
&>/dev/null と >/dev/null の違い - Qiita
linux - /dev/nullの入力リダイレクトの意味 - スタック・オーバーフロー