< /dev/null とは何なのか

とあるサーバで、こんなコードを見かけました。


```bash

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の入力リダイレクトの意味 \- スタック・オーバーフロー](https://ja.stackoverflow.com/questions/8559/dev-null%E3%81%AE%E5%85%A5%E5%8A%9B%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%81%AE%E6%84%8F%E5%91%B3)

> プロンプトに対する入力を </dev/null が抑制し no を入力したことになる

どうやら`cp -i < /dev/null`とコマンドを打った時に、通常は上書きをして良いか聞かれるのに対して、自動的にnoを入力してくれるらしいです。

試してみます。

```bash
$ 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扱いとなります。

```bash:ただReturnするだけ
$ cp -iv TESTFILE ABC 
cp: `ABC` を上書きしてもよろしいですか(yes/no)? 
# ただReturnを押すだけでも、no扱いとなる
$
$ cat ABC
ABC
```

つまりこれは、`空の入力さえあれば、何でもいいのでは？`

```bash:空ファイル作って、それを標準入力で渡す
$ : > kara
cp -iv TESTFILE ABC < kara ; 
cp: `ABC` を上書きしてもよろしいですか(yes/no)? 
$
$ cat ABC
ABC

```

いけました。
ちなみに、私は初めて知ったのですが、無限にyを吐き出してくれる`yes`コマンドというものがあるらしいです。


```bash
$ yes | head
y
y
y
y
y
y
y
y
y
y
```

```bash:yesコマンドの結果を標準入力で渡すと、自動的にyesが入る
$ 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](http://www.m-bsys.com/error/whileread-ssh)

> 原因はsshコマンド実行に伴う標準入力の切替です。sshコマンドを実行すると、ローカルホストからの標準入力を停止し、sshで指定したリモートホストからの標準入力の受付を開始します。
> つまり、上記のコードの例では、ローカルホストのファイルの読込みを終了させた上でsshコマンドを実行し、再びreadコマンドを実行しようとしているわけです。この時、既にファイルが閉じている為にwhileが終了してしまうわけです。

記事によると、sshした段階で、ローカルで`while read`に渡していた標準入力は打ち切られてしまい、そのままループが終わってしまうそうです。

```bash:参考記事から抜粋
# ① 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`について書かれています。

```bash
 -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.)
```

日本語での説明は[こちら](https://euske.github.io/openssh-jman/ssh.html)

ざっくり書いてある内容は、

「標準入力を/dev/nullからリダイレクトする＝標準入力からの読み込みを禁止した状態にする」

ということらしいです。

以下はローカルのHOSTNAMEを標準入力として渡しつつ、リモートホストでcat+リモートのHOSTNAMEをechoするサンプルです。
`< /dev/null`（または-nオプション）を指定すると、ローカル側の標準入力が受け取れなくなっているのがわかります。

```bash:/dev/null無し
# ローカルのHOSTNAMEをリモート側が標準入力として受け取り、catする
$ echo $HOSTNAME | ssh remote 'cat; echo $HOSTNAME' 
local
remote

```

```bash:/dev/null有り
# 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経由になるから、標準入力が途中で閉じてしまい、ループが中断されるのでしょうか。

実際にターミナルで叩いて確認をしてみます。

```bash:dev/null有り
$ 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

```

```bash:dev/null無し
$ 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ループ中のsshに-devnullを使うとループが中断されることなく最後まで実行される)で紹介していた記事の中に、whileでは無くてforで書けば、sshしてもループが途切れることが無いとのことです。

ただ、whileとforの明確な違いについては不明。
（誰か詳しい方教えて下さい……）


### 2019/11/28追記

[プログラマーの君！ 騙されるな！ シェルスクリプトはそう書いちゃ駄目だ！！ という話 \- Qiita](https://qiita.com/piroor/items/77233173707a0baa6360#for--in%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AF%E6%A8%99%E6%BA%96%E5%85%A5%E5%8A%9B%E3%81%8B%E3%82%89%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AB%E3%81%AF%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84)


こちらを読んでいて、 `for ... in` の書き方だと、あらかじめリストを列挙してから、その分ループをさせるという挙動のため、sshで標準入力が切り替わっても、ループが止まらない、という理解をしました。

なにはともあれ、ループで処理するなら、可能であればfor。
whileになるのであれば、 `-n`もしくは`< /dev/null`を使うというやり方が良さそうです。
（あくまで、sshをループ内で扱うときのお話です）


## 参考

[&>/dev/null と >/dev/null の違い \- Qiita](https://qiita.com/kiyodori/items/a37cfc23f068a5c16b2c)

[linux \- /dev/nullの入力リダイレクトの意味 \- スタック・オーバーフロー](https://ja.stackoverflow.com/questions/8559/dev-null%E3%81%AE%E5%85%A5%E5%8A%9B%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%81%AE%E6%84%8F%E5%91%B3)

[while内でsshを使うと1回で止まる \- Miuran Business Systems](http://www.m-bsys.com/error/whileread-ssh)

[rsyncのサンプル例集 \- Qiita](https://qiita.com/nagais/items/7403411b4aa193d0fa0d)