bash の プロセス置換

script コマンドを使った小技です。
(使っている Linux は Ubuntu 14.04.3 LTS です)

bash には 「プロセス置換」という機能があります。

Process Substitution

    Process  substitution is supported on systems that support named pipes (FIFOs) or the /dev/fd method of naming
    open files.  It takes the form of <(list) or >(list).  The process list is run with its input or  output  con‐
    nected  to a FIFO or some file in /dev/fd.  The name of this file is passed as an argument to the current com‐
    mand as the result of the expansion.  If the >(list) form is used, writing to the file will provide input  for
    list.   If  the  <(list)  form  is used, the file passed as an argument should be read to obtain the output of
    list.

    When available, process substitution is performed simultaneously with parameter and variable  expansion,  com‐
    mand substitution, and arithmetic expansion.

処理中の一時ファイルの作成を減らせる機能で、 無くても他の方法で代用は効くのですが 良い感じで使うとコマンドが読みやすくなったりします。

書き方は、次の 2つ。

<(コマンド)
>(コマンド)

名前の似てる「コマンド置換」と書き方も似てますね。

コマンド置換とプロセス置換

$(コマンド)   #コマンド置換

「コマンド置換」は、コマンドの結果を コマンドやコマンドの引数として使用することができました。

$ echo $(echo date)

date
$ $(echo date)

Thu Dec  8 22:58:19 JST 2018

「プロセス置換」は、コマンドの結果を ファイルの入力のように扱ったり 出力をコマンドに渡したりできます。

<(コマンド)

まずは "<(コマンド)" の方ですが、コマンドで入力ファイルのパスを 指定するところに書くことができます。

よく使われる例としては diff コマンドです。
diff コマンドは、2つのファイルを比較する際に 標準出力が 1つしか指定できないため 2つのコマンドの実行結果を比較したい場合 比較するコマンドの片方は実体が必要になります。

$ ls -l /var/xxxx > xxxx.txt
$ ls -l /var/yyyy > yyyy.txt
$ diff xxxx.txt yyyy.txt
$ ls -l /var/xxxx > xxxx.txt
$ ls -l /var/yyyy | diff - xxxx.txt

「プロセス置換」を使うと 一時ファイルを作成せずに 2つのコマンドの実行結果を そのまま比較することができます。

$ diff <(ls -l /var/xxxx) <(ls -l /var/yyyy)

コマンドも見やすいですね。
一時ファイルを削除する手間もなくなります。

echo で出力すると 「プロセス置換」の実体が ファイルデスクリプタだとわかります。

$ echo <(echo 1)

/dev/fd/63

わかりにくくなりますが 次のようなこともできます。

$ $(cat <(echo date))

Thu Dec  8 22:14:36 JST 2018

これは まず 次のように処理されて "date" が返ってきます。

$ echo date > /dev/fd/63
$ cat /dev/fd/63

返ってきた date を実行して 日時が出力されました。

$ date

Thu Dec  8 22:14:36 JST 2018

また 次のような do ... done のループで パイプでつなぐと 中の処理が別プロセスになり 環境変数を 上書きしてくれないような場合、、、

$ filename=none

$ ls | grep -v "test." | while read filepath
> do
>     filename=$(basename $filepath)
> done

$ echo $filename

none   #←別プロセスのため上書きされない

「プロセス置換」を使うと ファイルを指定するのと同じように 同じプロセスで処理することができます。

$ filename=none

$ while read filepath
> do
>     filename=$(basename $filepath)
> done < <(ls | grep -v "test.")

$ echo $filename

xxxxx.txt   #←同じプロセスのため上書きされた

>(コマンド)

次に ">(コマンド)" の方ですが、こちらは だいたいパイプでできてしまうため あまり良い例が思い浮かびませんでした。

よく使うのは次のように 標準エラー出力で 不要な行をオミットするケースすです。

$ command 2> >(grep -v ^Notice: >&2)

これは、標準エラー出力を一旦 プロセス置換で grep コマンドに渡して "Notice:" から 始まる行を省いて、再度標準エラー出力に渡しています。

他には tee コマンドと組み合わせて 特定の出力だけ振り分ける なんてことができます。

$ cat test.txt | \
>    tee >(grep ^case1 > case1.txt) \
>    tee >(grep ^case2 > case2.txt) \
>    tee >(grep ^case3 > case3.txt) \
>    > /dev/null

この例では、出力された行の先頭の文字によって 別のファイルに保存しています。

Google サイト内検索

Amazonアソシエイト