名前付きパイプでプログレスバーを操作する
上記日記からの続き。
CocoaDialogのサンプルコード
まずは論より証拠、CocoaDialogのサンプルコードのとおりにやってみる。
- 1. 名前付きパイプを作成する。重複する一時ファイル(/tmp/hpipe)が存在する場合は削除してから。
rm -f /tmp/hpipe mkfifo /tmp/hpipe
- 2. 名前付きパイプ(/tmp/hpipe)から入力データを受け取るプログレスバーを、バックグラウンドジョブとして起動する。
/Applications/CocoaDialog.app/Contents/MacOS/CocoaDialog progressbar < /tmp/hpipe &
- 3. ファイルディスクリプタの3番と、名前付きパイプ(/tmp/hpipe)を関連付ける。
exec 3<> /tmp/hpipe
- コマンドを実行すると、プログレスバーが表示される。
- 4. 割合数値とメッセージテキストを、ファイルディスクリプタの3番へ送ってみる。
echo 10 running...10% >&3
- 5. 割合数値とメッセージテキストは直接、名前付きパイプ(/tmp/hpipe)に送ってもOK。
echo 50 running...50% > /tmp/hpipe
exec 3>&-
- ファイルディスクリプタの3番と、名前付きパイプ(/tmp/hpipe)の関連付けが破棄されるのだ。
以降、手順2から手順6を繰り返すことで、プログレスバーを自由にコントロールできるのである!
- 7. 使い終わった名前付きパイプ(/tmp/hpipe)は、rmコマンドで削除できる。
rm -f /tmp/hpipe
- 新たなプログレスバーを利用する時は、再び手順1から実行する。
面白すぎる!
- もはや、ブロック内の処理を作って、パイプやリダイレクションで接続するまでもなく、
- 名前付きパイプ(/tmp/hpipe)= >(CocoaDialog progressbar) のように扱えるのだ!
- 名前付きパイプには、任意のパスを指定できる。(重複する既存のファイルが存在しない限り)
- cd ~; mkfifo my_progressbar なんてしておくと、より親しみある名前付きパイプとなりそう。
いったい何が起こっているのだろう?調べてみた。
まめ知識 名前付きパイプ
- mkfifoコマンドは、fifoを作成する。
- fifoとは、First In, First Outを意味する単語の頭文字である。
- つまりfifoとは、先入先出の構造を持つ、データを一時的に蓄えておく領域である。
- 先入先出なので、ホースのような管(パイプライン)をイメージすると良いかも。
- fifo(ホース)には、コマンド同士の入力と出力を接続する機能がある。
- 標準出力と標準入力を繋ぐパイプとほぼ同じ機能を持つので、名前付きパイプと呼ばれる。
- 一方、パイプ | やリダイレクションで使われている仕組みは、無名パイプと呼ばれる。
- ファイルと同じ様式のパスを指定して作成するが、確保されるのはメモリ上の領域である。
- ストレージにデータが保存されることはない。
無名パイプの例
- echoの標準出力とtrの標準入力が、パイプによって繋がれている。
$ echo hello | tr h H
Hello
- echo helloの標準出力が、無名パイプを経由して、trの標準入力に繋がっている。
- パイプの中を「hello」が流れて、trでhをHに変換して、「Hello」が出力される。
+-----------------------+ +-----------------------+ | |------------| | | echo hello 1→ 無名パイプ →0 tr h H | | |------------| | +-----------------------+ +-----------------------+
名前付きパイプの例
$ mkfifo /tmp/fifo $ echo hello > /tmp/fifo
- /tmp/fifo 宛のリダイレクトは、すぐには終了しない。
- ターミナルで新規タブを作成して、/tmp/fifo からデータを受信してみる。
$ tr h H < /tmp/fifo
Hello
- 先頭のhがHに変換されて出力された!
- 隣のタブに戻ってみると、echo hello > /tmp/fifo コマンドも終了していた。
$ cat /tmp/fifo
- 上記を実行しても、catコマンドはすぐには終了しない。
- ターミナルの別のタブで、/tmp/fifo 宛にリダイレクトしてみる。
$ echo hello > /tmp/fifo
- 元のタブに戻ると、先ほどのcatコマンドは「hello」を出力して、完了していた。
$ cat /tmp/fifo
hello
つまり...
- 名前付きパイプは、通常のファイルとは違ってストレージに何かが保存される訳ではない。
- 名前付きパイプは、入力側と出力側の2つのコマンドが同時に接続した時に、動作が始まる。
- どちらか一方のコマンドしか接続していない時は、もう一方が接続するまでI/O待ちしている。
- /tmp/fifoという名前のパイプラインを、「hello」というテキストが流れているイメージである。
+-----------------------+ +-----------------------+ | |------------| | | echo hello 1→ /tmp/fifo →0 cat /tmp/fifo | | |------------| | +-----------------------+ +-----------------------+
まめ知識 execコマンド
- execコマンドは、それに続くコマンドを即実行するコマンドである。
- 通常は、execなんて使わずに、好みのコマンドを入力して実行する。
その違いは何か?
- execを使わずに実行した時は、現在のシェルのプロセスをコピーして、それをコマンドのプロセスに置き換えて起動する。
- つまり、現在のシェルの子プロセスとして、コマンドが起動する。
- execを使って実行した時には、現在のシェルのプロセスをコマンドのプロセスに置き換えて起動する。
- つまり、現在のシェルのプロセスが、コマンドに変化して、起動する。
- アクティビティーモニタを見ながら、sleepコマンドを実行してみると、その動きがよく分かる。
- ターミナルで新規タブを開く。
- 新たにloginとbashのプロセスが起動した。
$ sleep 10
- bashの子プロセスとして、sleepのプロセスが起動した。
$ exec sleep 10
[プロセスが完了しました]
- bashのプロセスが、sleepに変化した。
- sleepが完了すると、「プロセスが完了しました」と表示される。
なぜ、プロセスが完了してしまったのか?
まめ知識 execによるオープンの継続
- 上記のようにexecには、実行時のシェルプロセスを指定したコマンドで置き換えて実行する役割がある。
- では、execにコマンドを指定せずに、リダイレクションのみ指定した時にはどうなるのか?
$ mkfifo /tmp/hpipe $ /Applications/CocoaDialog.app/Contents/MacOS/CocoaDialog progressbar < /tmp/hpipe & $ exec 3<> /tmp/hpipe
- すると、コマンドが指定されていないので、exec実行時のシェルプロセスに対するリダイレクションとなる。
- 見慣れない 3<> は、ファイルディスクリプタの3番に /tmp/hpipe の入力と出力を繋ぐリダイレクションと思われる。
- つまり、exec 3<> /tmp/hpipe = exec 3< /tmp/hpipe & exec 3> /tmp/hpipe
- なぜ入力と出力の両方を指定しているのかは謎。exec 3> /tmp/hpipe だけでOKだと思う。
- ところで、exec以外のリダイレクションは、そのコマンドの実行が終了したら無効になる。
- 一方、exec(コマンド指定なし)のリダイレクションについては、exec実行時のシェルプロセスに対するリダイレクションなので、シェルが起動している限りその効力が継続する。
- そのリダイレクションを無効にするには、execを実行したシェルプロセスを終了するか、以下のexecコマンドでファイルディスクリプタを閉じた時である。
$ exec 3>&- あるいは... $ exec 3<&-
まめ知識 ファイルディスクリプタ
- コマンドが外部とデータをやり取りする時に使う、出入口のようなイメージ。OSによって番号管理されている。
- デフォルトでは、コマンドには以下のファイルディスクリプタが設定されている。
- コマンドが実行された瞬間には、デフォルトでは上記3つのファイルディスクリプタしか利用可能な状態になっていない。
- 設定により、自由に追加することもできる。
- 但し、1プロセスが利用可能なファイルディスクリプタ数は制限されていて、自分が利用するOSX環境では256だった。
$ ulimit -n
256
- 参考:絵で見てわかるファイルディスクリプタ・パイプ・リダイレクト - あしのあしあと(感謝です!)