Brian W. KernighanとRob Pikeの「Unixプログラミング環境」にはpick
と呼ばれるコマンドが紹介されている。
これは対話的にプログラムを処理するためにシェルスクリプトで書かれたツールである。pick args...
とすると、引数を一度に1つずつ表示しユーザーの入力を待つ。pick
はユーザーがy
を入力した引数のみ出力し、それ以外は捨てる。
$ ls file1 file2 file3 file4 $ rm $(pick *) file1? y file2? file3? q $ ls file2 file3 file4 # file1 has been removed!
その書籍の後半では、引数のかわりに標準出力を使うオプション-
を追加するためにCで書き直している(今回私が作成したpick
では-i
オプションにした)。当時のシェルでは実現しづらかったからである。
しかしながら、現代のシェル(POSIX shellでも)ではCを用いずに完全版pickを実装できる。それは僕が疑問であった「シェルスクリプトのwhile read...の中で対話的なreadをする方法」の回答でもある。(ネットであまりシンプルな回答を見つけることができなかった。)
では早速pickのメインルーチンを見てみよう。
if [ "$iflag" = true ] then cat -- ${1+"$@"} else for i do echo "$i" done fi | while read -r line do printf '%s ?' "$line" > /dev/tty read -r a < /dev/tty # On old Unix, it did not work case "$a" in y*) printf '%s\n' "$line";; q*) exit 2;; esac done
コメントのところに注目してほしい。「Unixプログラミング環境」にはread var < filename
はできないと書いてある。しかしながら現代のshellではこのプログラムは正常に作動する(dash, bash, kshで確認した)。ここで、read -r a < /dev/tty
は明示的に/dev/tty
を指定しなければならない。標準入力は一行目のwhile read -r line
が読んでいるからだ。
表題の答えは
「whlie read ...
の中で対話的なreadをする方法」はread var < /dev/tty
が答えとなる。ちなみに「確実に」対話的につかうプログラムを除いて、基本的に/dev/tty
を使うべきでない(ed(1)ですら使っていない)。出入力を固定すると他のプログラムと組み合わせることが不可能になるからである。ファイル名を指定してデータを入出力する方法も同様である。基本的にはstdin, stdout, stderrを使ってデータを入出力し、パイプを通して他のプログラムと通信できるように設計すべきだ。