ちょっと最近やり始めた(正確に言うとやり直し始めた)ツールづくりで、どうやったかを毎回調べてしまうので、備忘録的に書いておきます。

pipeしたときの標準出力とかの状態

Unix(Linux)で、shellからプログラムをpipeで繋いで起動した場合に、標準入出力がどうなるか?というのは、以下の記事にわかりやすく載っています。

Qiita Linuxのパイプをちょっとだけ理解する

さて、pipeは標準入出力、特にinteractiveな操作を必要とする場合、標準入力が別コマンドの標準出力が繋がっていることが問題になります。上の記事にもある通り、キーボードを受け取る口が無くなるので、pipeの右側にあるコマンドは、そのままではdaemonみたいな感じになっています。

また、この状態でさらにpipeで繋がれると、当然ですが標準出力が別のコマンドの標準出力につながることになります。進捗などを表示したりするときに標準出力に出したりすると、別のコマンドに余計な出力をすることにもなり、あんまりうれしくありません。こういうのをどうやって解決したら良いか?

/dev/tty を使う

そんなときには、character deviceである /dev/tty を使います。

http://tldp.org/HOWTO/Text-Terminal-HOWTO-7.html#ss7.3

この辺りに説明が書いてありますが、 /dev/tty は、 現在のprocessに対するterminalの制御 を行うためのデバイスです。つまり、このデバイスファイルに対して出力するとターミナルに対して入力したものとして扱われ、このデバイスファイルを読み込むと、書き込んだものを読める、ということになります。

試してないのであれですが、異なるプロセスが同時にこのファイルを開いても、最終的には /dev/pty/N というキャラクターデバイスに割り当てられるはずです。なので、競合とかは考えなくていい、と思います。

簡単に書くとこんな感じになります。入力のみ受け付ける、とかであれば、 RDWRRDONLY にすると良いです。

(* OCamlで書くとこんな感じ *)
let () =
  let fd = Unix.openfile "/dev/tty" [Unix.RDWR] 0o666 in
  let stdin' = fd and stdout' = fd in
  (* stdin' stdout'を使う *)
  Unix.close fd

かなりシンプルですが、これでちゃんと動きます。Windowsでは確か仕組みが異なるため、それも対応するとなると、また別の方法が必要ですが。実際に別のptyが割り当てられていることを確認する場合、以下のような手段があります。

特にpsコマンドの方だと、shell scriptの中から起動されたコマンドのttyは、起動元のshellのttyと同一であったりと、仕組みが思い浮かぶような感じにもなっていて面白いので、覚えておくといいと思います。

最後に

今回は久しぶりにhow to 系の記事でした。またこういうのも書いていこうと思います。How To系ってちゃんと書こうとするとハードルが高いのと、調べればわかるので書く気力が湧けば、ですが・・・。