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

この記事は [ゆめみ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/yumemi) の 16 日目の記事です。

Linuxのパイプを理解して、Linuxをコマンドを便利に使いこなそうとする記事です。


## Linuxのプロセスの入出力

パイプはプロセス間の入出力を繋げる仕組みなので、まずは、プロセスの入出力を知る必要があります。

### 標準ストリーム

プロセスは、明示的にファイルをオープンしなくても利用できる入出力があります。

- 標準入力 (stdin)
- 標準出力 (stdout)
- 標準エラー出力 (stderr)

Javaでは `System.in`、 `System.out`、 `System.err` 。PHPでは `STDIN`、`STDOUT`、 `STDERR` で扱えるやつです。

C言語で割り当てられるファイルディスクリプタ番号は 0番が標準入力、1番が標準出力、標準エラー出力が2番です。そして、標準入力はキーボード、標準出力と標準エラー出力はモニータが接続されています。

![標準ストリームの図](https://upload.wikimedia.org/wikipedia/commons/7/70/Stdstreams-notitle.svg)
上の図は wikipedia の [標準ストリーム](https://ja.wikipedia.org/wiki/標準ストリーム) にあるものです。

### 無名パイプ

無名パイプは、コマンド間の入出力を繋げるときによく使う `|` です。これを使うことで `|` の左側のコマンドの標準出力を右側の標準入力に繋げる仕組みです。

例えば `ls | more` は下の図になります。

![linux-pipe-4.png](https://qiita-image-store.s3.amazonaws.com/0/145742/7d1090e2-141b-3917-f11f-2d2847e492ab.png)


`ls`の#1と `more`の #0 をつなげているのがパイプです。`ls` コマンドは [ls(1)](https://linuxjm.osdn.jp/html/gnumaniak/man1/ls.1.html) を見ると下のように書かれています。

> 出力は標準出力に対して行われ、 -C オプションで複数列出力が要求されない限り、1 行に 1 エントリである。 しかし、端末に対する出力では、出力が 1 列または複数列の どちらになるかが定められていない。 オプション -1 と -C は、 それぞれ 1 列出力と複数列出力を強制させるために使用される。  

パイプで処理したいコマンドがあるときには、manを見て、入出力を確認すると良いです。

複数のパイプを利用することもできるので、ファイル名に *qiita* を含むファイル名を数えるために `ls |grep 'qiita' |wc -l ` を実行すると下のようになります。

![linux-pipe-2.png](https://qiita-image-store.s3.amazonaws.com/0/145742/c16567a0-fd73-a379-2365-a317e2e7dcf1.png)

### 名前付きパイプ
無名パイプは使い終わると廃棄されますが、名前付きパイプは `mkfifo`コマンドで ファイルシステムに作ることができます。作成した名前付きパイプはリダイレクトを利用して、標準入出力と繋げることができます。

```sh
$ echo "Regular file" > regular_file
$ mkfifo named_pipe
$ ls -l
total 4
prw-rw-r-- 1 user1 group1  0 Dec 14 03:22 named_pipe
-rw-rw-r-- 1 user1 group1 13 Dec 14 03:21 regular_file
$
```

上では、 *named_pipe* という名前付きパイプを作成しています。`ls -l` で(左端の「p」だから)ファイルタイプがパイプということが分かります。

この名前付きパイプを使って `ls -l |more` と同等のことを行なってみます。
二つのターミナルを使い、一つ目のターミナルでは、 `named_pipe`を作成したディレクトリで 下のコマンドを実行します。

```bash
$ more named_pipe
```

実行した直後は何も動きがありませんが、そのままにしておきます。`more` コマンドは第１引数に指定したファイルを読み込みます。今回は`named_pipe` を指定したので、名前付きパイプ `named_pipe` からの読み込みを待っている状態です。

二つ目のターミナルでは `named_name` を作成したディレクトリで下のコマンドを実行します。

```bash
$ ls -l /usr/bin/ > named_pipe
```

`ls -l` の結果を事前に作成した名前付きパイプ `named_pipe` に書き込みます。そうすると、 一つ目のターミナルに `more`コマンドの結果が表示され、1ページを表示すると待機します。あとは、スペースキーでページ送りや`q`でmoreを終了できます。

![linux-pipe-named-pipe.png](https://qiita-image-store.s3.amazonaws.com/0/145742/e4a21f9d-248c-15f0-95eb-efe183443149.png)


## 名前付きパイプの使い所

処理結果の書き込み先はコマンドごとにオプション指定があります。

- mysqldumpは --result-file (-r)でファイルを指定します。オプションが無ければ標準出力です。
- pg_dumpは --file(-f) でファイルを指定します。オプションがなければ標準出力です。
- tar は `c` オプションで指定するファイル名に `-` を指定すると標準入力、標準出力を使います。
- zip は ファイル名に `-` を指定すると、標準出力です。

これらの様にオプションで標準出力を指定できるコマンドもありますが、そうではないコマンドもあります。古いコマンドで恐縮ですが、 Oracleの ダンプファイルを作成する`exp` は標準出力に出力することができませんでした。そういう時に名前付きパイプを使いました。

```bash
$ cat named_pipe | zip > db_hoge.dmp.gz &
$ exp user_name file=named_pipe full=y
```

先に、バックグラウンドでzipコマンドを起動しておき、`exp`コマンドの `file`オプションに名前付きパイプを指定しています。

インポートする時はバックグラウンドで `unzip` を行い、名前付きパイプに書き込みます。`imp` ユーティリティは名前付きパイプを読み込みます。

```bash
$ cat db_hoge.dmp.gz | unzip - > named_pipe &
$ imp user_name file=name_pipe
```

ディスクを節約するためにお世話になりました。