パイプ (コンピュータ)

出典: フリー百科事典『ウィキペディア(Wikipedia)』
ある端末上で3つのパイプで繋いだプログラムを実行する際の入出力の流れ

Unix系オペレーティングシステムパイプ(pipe)、もしくはパイプライン (pipeline) とは、複数のプログラムの入出力をつなぐための仕組み(プロセス間通信)の一つである。

概要[編集]

パイプを使うと、複数のプログラムを組み合わせることができるようになり、多様かつ複雑なデータ処理を効率よく柔軟に実行できる。また、現有のソフトウエア資産の再利用が可能になるため、プログラム生産性の面でも利点もある。

シェルにおける具体的な利用の例は #シェルからの使用を参照。

次にUNIX系OSとpipeとの関係について説明する。

データ処理の一連の過程を一般化すると、入力データ、これを処理するプログラム、出力データの3つの要素で構成される。特に処理が煩雑な場合、プログラムは複雑になり、バグ保守性の悪化を誘発する傾向にある。また特定の複雑な処理のみに特化したプログラムは再利用性も悪く生産性の観点でも劣る。

この問題を解決するために、「1つだけの仕事をうまくやる、道具のようなソフトウェア」(Software Tools)をパイプラインによって組み合わせる、というアイデアがUNIXUnix系のOSにある。これはまず、ユーザーが複雑なデータ処理を機能毎に小さく分割する事から始まる。次に、この分割された比較的単純な処理を小さくシンプルなソフトウエアで処理させる。ソフトウエアの出力データは、中間結果として、次の小さなソフトウエアの入力データにパスされる(パイプで繋ぐ)。このようにパイプで繋ぐ事を繰り返すことで、複雑な処理を小さくシンプルなソフトウエア群の数珠繋ぎで実現しようとするものである。パイプで連結することを前提とした構成のプログラムをフィルタとも呼ぶ。

直接の親子関係にあるプロセス間で通信をおこなうためfork前にあらかじめ共有しておく「無名パイプ」と、親子関係にないプロセス間で一時ファイルにディレクトリエントリする(接続自体はファイルを経由するわけではない)「名前付きパイプ」がある。ダグラス・マキルロイUnixシェル向けに考案したことから始まり、パイプライン輸送からの連想で名付けられた[1]

特に、シェルなどでは縦棒( | )の記号を使って無名パイプを簡単に利用でき、それを指して「パイプ」と言うことも多い。プロセス群の標準ストリームを連鎖的に相互接続するもので、あるプロセスの標準出力 (stdout) を直接別のプロセスの標準入力 (stdin) に接続する。

前述のシェルのコマンドラインにおけるパイプは、中置記法結合法則を満たす演算子と見ることができる(その時、演算子(オペレータ)のオペランドにあたるのは、各プログラムである)。これを一種の「合成」と見ることもできる。一般に数学で、f, g, h という関数があるとして h(g(f(x))) というような計算をすることを考える時、関数を関数合成の演算子 ∘ で合成した (h∘g∘f)(x) というものを考えることがあるが、パイプの演算子 | はこれに似ており ( progF < x ) | progG | progH あるいは ( progF | progG | progH ) < x となる[2]

パイプに関連したシグナルとして SIGPIPE がある。パイプの読み出し側が閉じられた後にパイプへの書き込みが行われると、パイプへ書き込もうとしたプロセスへ SIGPIPE を配送する。デフォルト動作では、SIGPIPE を受信したプロセスは異常終了する。パイプから読み出すプロセスがパイプを閉じ、かつパイプへ書き込むプロセスが書き込みを続けるとパイプのバッファは枯渇してしまい、後者のプロセスがブロックしてしまうため、SIGPIPE によりこれを防止している。なお、類似の機能を持つソケットにて同じ状況が発生した場合、書き込み側ではパイプと異なり、実際に書き込んだサイズをゼロとすることにより同様の状況をプロセスに伝える。また、パイプの書き込み側が先に閉じられた場合、読み出し側では End Of File に到達したのと同じ扱いとなり、パイプ独自の処理は行わない。

シェルからの使用[編集]

以下が典型的なパイプの利用例である。|(バーティカルバー) はシェルにパイプを指示する記号である。

grep 札幌市 Address.txt | a2ps | lpr 

これは

  1. ファイル Address.txt から "札幌市" が含まれる行を出力し
  2. その出力を入力として受け取って a2ps コマンドを用いて整形し
  3. その出力を印刷する

といった処理を指示している。

パイプを使用する方法に対して、中間ファイルを利用する方法

grep 札幌市 Address.txt > sapporo.txt
a2ps < sapporo.txt > print.ps
lpr < print.ps

もあるが、パイプを利用する方法に比べ記述が冗長になるだけでなく、一般に処理が遅くなる。

なぜならば、このように中間ファイルをつくる場合1つ目のプログラムがすべてのデータを処理し終えるのを待って、2つ目のプログラムが動き、さらに2つ目のプログラムが終了して初めて、3つめのプログラムを動かす必要があるためである。しかしパイプを使えば、3つのプログラムをマルチタスクにより同時に動かし、I/Oなど時間がかかる処理の待ち時間を有効に使うことができる。

さらに、逐次処理を行うため、データのサイズが大きい場合でも記憶領域を消費しない利点もある。また、データの受け渡しがメモリ上で行われるため、その点でも高速な処理が期待できる。

また、上記例はパイプや中間ファイルを利用する方法以外にも、プログラミング言語を利用して専用のソフトウエアを作成し処理する方法もある。しかしながら、パイプを利用すれば既存のUNIXコマンドのみで素早く実現したい処理を実現できる。特に一度しか実行しないような処理に対して専用のソフトウエアを作成するのは過剰であり、生産性の面でもパイプは優れている。

UNIXの設計思想である、それぞれの役割に特化したプログラムを組み合わせ、複雑な機能を実現する(ツールキットアプローチ分割統治法)として、パイプはその成功例であり、UNIXを利用する魅力のひとつである。

エラーストリーム[編集]

デフォルトでは、パイプ内のプロセスの標準エラーストリーム (stderr) はパイプを通して渡されず、もとのパイプを起動したコンソール(または端末)に出力される。ただし多くのシェルはこの動作を変更する追加構文を用意している。例えば csh の場合、"|" の代わりに "|&" を使えば、標準エラーストリームも標準出力と共にパイプでつながれた後続のプロセスに渡される。Bourne Shell では、まず 2>&1 と書くことで stderr をクローズして stdout に合流させ、さらにそれを(stdoutの)パイプにより次のプロセスに渡す。[3]

Pipemill[編集]

通常の単純なパイプ使用法では、シェルがパイプを設定して各コマンドを起動すると、後はそれらコマンドが自動的にパイプ処理を行う。従って、パイプ処理中はシェルがそのデータ処理に関与することはない。

しかし、シェルをパイプ処理に直接関与させることもできる。その場合、一般に次のような構文になる。

command | while read var1 var2 ...; do
   # $var1, $var2 といった変数を使って行毎に処理
   # (なお、この場合の while ループはサブシェルとして実行されるので
   # 変数はwhileループが終了した後は使えなくなる)
   done

このような技法を "pipemill" とも言う。

プログラムによるパイプの作成[編集]

pipe() システムコールオペレーティングシステムに新しいパイプ(無名パイプ)を生成するように要求する。この結果、2つの新しいファイル記述子が開かれ、プロセスに渡される。つまり、読み取り専用のパイプの口と書き込み専用のパイプの口である。これらパイプの口は、シークができないことを除いて通常の匿名ファイル記述子と同様に扱える。読み取り専用のパイプに対する読み出し命令は、書き込み専用のパイプに対してflushされるまでブロックされる。

デッドロックを避けたり並列処理をしたりするために、1つまたはそれ以上の新しいパイプをもつプロセスは、新しいプロセスを生成するのに、一般的に fork() を呼び出す。それぞれのプロセスは、データを作り出したり利用したりする前に、使われることのないパイプの口を閉じるだろう。または、最初のプロセスが単純に新しいスレッドを生成し、ただひとつのスレッドがひとつのパイプの口の利用を保証するようにするという方法もある。

書き込み用のパイプと読み出し用のパイプを異なるプロセスが持つことで、ストリームを共有し、協調的な動作を行わせることができる。これはforkした親子関係にあるプロセス間のプロセス間通信で使われる。

名前付きパイプ[編集]

また、POSIX(UNIX)では、mkfifo() または mknod() を使ってファイルシステムの名前空間に「名前付きパイプ」を作成できる。名前付きパイプは、あたかも入力ファイルまたは出力ファイルであるかのようにオープンでき、そのファイル記述子は、あたかもパイプのファイル記述子であるかのように扱える。これによって、親子関係にない任意のプロセス間でパイプ通信を行うことができる。

シェルからのコマンド操作でも、名前付きパイプの利用は有用である。シェルからは mkfifo コマンドで名前付きパイプを作ることができる。例えば、大規模なファイルツリーのコピーを、cp コマンドの再帰オプション cp -r で行うとうまくない場合がある。tar コマンドでテンポラリファイルに出力すると、ディスク全体のコピーなどでは巨大になり過ぎる。そんな場合に、テンポラリファイルではなく、名前付きパイプを出力先にするのである。通常のパイプでは、展開先に移動して展開するコマンドまで全部続けて書かねばならず、失敗しやすい。名前付きパイプを出力先にして一旦コマンドを実行し、展開先への移動や ls コマンドで状況を確認した後で、おもむろに名前付きパイプを入力元にして tar コマンドで展開すればよい。

実装[編集]

通常、OSはパイプのバッファリング機能を提供する。例えば、あるプロセスが毎秒5000バイトのデータをパイプに送り込み、受け取る側のプロセスが毎秒100バイトしか処理できないとする。それでもデータは失われない。実際にはカーネル内で送信側プログラムの出力がキューに保持され、受信側プログラムがデータ読み取り可能となったとき、カーネルがキューからデータを送り、キュー上で送信済みとなったデータを削除する。キューの容量上限までデータが溜まると、受信側が溜まったデータを受信するまで、送信側プロセスは送信でブロックされる。Linuxではこのバッファの大きさは65,536バイトとなっている。

ネットワークパイプ[編集]

netcatなどのツールを使えば、パイプをTCP/IPソケットに接続できる。

歴史[編集]

パイプラインの概念とバーティカルバーによる記法は、初期のUnixシェル開発に関わったダグラス・マキルロイが考案した。彼はプログラムの出力を別のプログラムの入力とするような処理が非常に多いことに気付き、パイプを考案するに至った。そのアイデアを1973年ケン・トンプソンUNIXにパイプとして実装した[4]。その後、MS-DOSOS/2Microsoft WindowsBeOSといったOSのコマンドラインシェルにほぼ同じ記法でパイプが採用されていった[5]

UNIXのパイプ以前に似たような機構として、1960年代にKen LochnerがDTSS上で'communication files'という機構を実装しているが[6]、UNIXのパイプはそれとは独立に開発された[7]

Apple社のmacOSもMac OS XからUNIX OSとなったので、pipeを同様に使えるようになった。 なお同社のAutomatorのアイコンも、パイプを持ったロボットで、これもパイプラインの概念を応用したソフトウェアである[8]

UNIX系以外のOSが持つパイプに類似する機能[編集]

UNIX系以外のOSのパイプについての情報を以下に記載する。

MS-DOS[編集]

MS-DOSにおけるパイプは、シングルタスクOSという制約のためパイプの動作は中間ファイルによってエミュレートされており、単なる略記法である。

Windowsの名前付きパイプ[編集]

Windowsでは、名前付きパイプを作成するサーバ側は、CreateNamedPipe() で「\\.\pipe\パイプ名」という形式の名前で作成すれば、パイプにつなぐクライアント側からファイルの読み書きと同じ操作で読み書きすることができる。また、Windows における匿名パイプは、ランダムな名前の名前付きパイプとして実装されている。

マシン間の通信にも名前付きパイプを利用することができるが、データを送るたびに、通信確認のためのデータが行き来するので、他のマシンと大量のデータを通信するときは、ソケットの方が高速である。ただし、マシン内のプロセス間通信の場合は、Windowsではカーネル内で動作するので名前付きパイプは高速である。

脚注[編集]

  1. ^ Advice from Doug Mcilroy
  2. ^ この例に使用するカッコは、サブシェルで実行することを意味する ( ) である必要はなく、単なるグルーピングである { } で構わないがそうすると最後に ; が必要であるなど構文的に煩雑なため、( ) を使っている。
  3. ^ パイプの場合はこのような順序で理解すれば良いが、ファイルへのリダイレクトにおいて、2>&1 >filename という順序ではうまくいかず、>filename 2>&1 という順で書かねばならない、というのがなぜなのか、を正しく理解するのは少々骨が折れる。
  4. ^ Pipes: A Brief Introduction by The Linux Information Project (LINFO)
  5. ^ シェルの構文とpipeシステムコールを、本来きちんと分けて書くべき。
  6. ^ http://www.cs.rit.edu/~swm/history/DTSS.doc
  7. ^ The Evolution of the Unix Time-sharing System”. Nokia Bell Labs (1996年). 2014年6月6日時点のオリジナルよりアーカイブ。2021年7月22日閲覧。
  8. ^ Sal Soghoian on MacBreak Episode 3 "Enter the Automatrix"

関連項目[編集]

外部リンク[編集]