スタートアップCTOによるITエンジニアのためのブログ

Bashのプロセス置換の使い方:一時ファイルとおさらばする技術

Bashのプロセス置換(Process Substitution)は、コマンドの実行結果を「ファイル」として扱えるようにする非常に便利な機能です。

通常、コマンド間でデータを渡すにはパイプ(|)を使いますが、パイプは「標準入力」にしかデータを渡せません。プロセス置換を使えば、ファイル引数を期待するコマンドに対しても、一時ファイルを作ることなく直接データを流し込めます。

注: プロセス置換はBashやZshの機能です。標準の/bin/sh(POSIX)では動作しないため注意してください。

目次

  • <(list):標準出力をファイルとして読み込む
  • >(list):書き込んだ内容を標準入力として使う
  • プロセス置換を使うメリット
  • プロセス置換の仕組み
  • パイプとの違い
  • まとめ

<(list):標準出力をファイルとして読み込む

最もよく使われる形式です。<(list)内のコマンドの実行結果(標準出力)を、あたかも読み取り専用のファイルのように扱い、メインコマンドの引数として渡します。

注)括弧内のlistとは、単一のコマンドだけでなく、以下のような複数のコマンドを組み合わせた「リスト」を記述できることを意味します。

  • 単一のコマンド: ls
  • パイプで繋がれたコマンド群: ls | grep .txt
  • セミコロンや改行で区切られた連続実行: cd dir; ls
  • 論理演算子による制御: make && ./a.out

利用例1:2つのコマンド結果をdiffで比較する

diffコマンドは通常2つの「ファイルパス」を引数に取ります。プロセス置換を使えば、一時保存の手間が省けます。

# Bash

# 「Aの内容」と「Bの内容」を直接比較
diff -u <(echo "Hello World") <(echo "Hello Bash")

# 出力
--- /dev/fd/63	2026-01-31 12:28:37.181682894 +0900
+++ /dev/fd/62	2026-01-31 12:28:37.181682894 +0900
@@ -1 +1 @@
-Hello World
+Hello Bash

利用例2:ソートした結果をcommで比較

commコマンドは「ソート済みファイル」を要求します。

# Bash
# file1とfile2をソートした状態で比較する
# 一時ファイルを作成する必要がありません
comm <(sort file1.txt) <(sort file2.txt)

>(list):書き込んだ内容を標準入力として使う

メインコマンドの書き込み先を、>(list)内のコマンドの「標準入力」へ送ります。

利用例:出力を分岐・加工させる

teeコマンドと組み合わせて、ログを保存しつつ、特定の文字列だけ抽出して別ファイルに保存する場合などに重宝します。

# Bash

# 実行結果を log.txt に保存しつつ、
# 同時に grep で "ERROR" だけを抽出して標準出力に出す
ls -l | tee >(grep "ERROR" > error_only.log) > log.txt

プロセス置換を使うメリット

1. 一時ファイルの管理が不要

tmp1.txttmp2.txt を作成し、実行後に rm で消すといったコードを書く必要がありません。スクリプトがスッキリし、ゴミファイルが残る心配もなくなります。

例えば、commコマンドは事前ソートされたファイルを引数に持つため、プロセス置換を使わないと4回コマンドを実行しなくてはなりません。

# Bash

# ソートするために一時ファイル作成
sort -u file1.txt > tmp1.txt
sort -u file2.txt > tmp2.txt

# コマンド実行
comm tmp1.txt tmp2.txt

# 一時ファイル削除
rm tmp1.txt tmp2.txt

プロセス置換を使うと一行で済みます。

comm <(sort -u file1.txt) <(sort -u file2.txt)

2. 複数の出力を一つのコマンドに集約

パイプ(|)は原則として「1対1」の接続ですが、プロセス置換なら <(cmd1) <(cmd2) <(cmd3) のように、複数のストリームを一つのコマンドに集約できます。

# Bash
cat <(echo 'Hello') <(echo 'World')

プロセス置換の仕組み

Bashはプロセス置換を使うとき、裏側で /dev/fd/63 のようなファイルディスクリプタを介したパスを作成しています。コマンド側はそれを「普通のファイル」だと思って読み書きしているだけ、というスマートな仕組みです。

プロセス置換の動作を分解すると以下のようになります。

<(list) の場合(読み込み)

  1. 内部 (list): 実行結果を自身の標準出力に書き出します。
  2. 仲介: Bashがその出力を「ファイルディスクリプタ(/dev/fd/xx)」に繋ぎます。
  3. 外部: 親コマンドは、その /dev/fd/xx普通のファイルパスとして開いて読み込みます。

実際に ls で正体を確認してみると、パイプへのシンボリックリンクになっていることがわかります。

Bash
$ ls -l <(true)

# 出力
# 読み取り権限付きのシンボリックリンク
lr-x------ ... /dev/fd/63 -> 'pipe:[3104286]'

>(list) の場合(書き込み)

  1. 外部: 親コマンドは、Bashが用意したファイルディスクリプタのパスを普通のファイルだと思って書き込みます。
  2. 仲介: ファイルディスクリプタに書き込まれたデータがパイプを通ります。
  3. 内部 (list): パイプから流れてきたデータを、自身の標準入力として受け取ります。
# Bash
$ ls -l >(true)

# 出力
書き込み権限があるシンボリックリンクでパイプに接続
l-wx------ ... /dev/fd/63 -> 'pipe:[3113053]'

各形式におけるコマンドの関係性をまとめると以下の表の通りです。

形式list側(カッコ内)親コマンド側(カッコ外)
<(list)標準出力へ出すファイルとして読み込む
>(list)標準入力から受け取るファイルとして書き込む

なお、プロセス置換(特に >(list))は非同期で実行されるため、メインコマンドが終わっても中の処理が終わっていないことがあります。上級者向けですが、注意点として知っておくと役立ちます。

# Bash

# echoコマンド終了後、3秒してから'Hello'が表示される。
echo 'Hello' > >(sleep 3; cat)

パイプとの違い

プロセス置換は通常のパイプ(|)と似ていますが、明確な違いがあります。

  • パイプ (A | B): Aの標準出力を、Bの標準入力に直接繋ぎます。
  • プロセス置換 (B <(A)): Aの標準出力を、「ファイルパス」としてBに渡します。

具体例で比較:

# Bash

# パイプの場合
# grepは標準入力を読む
cat file.txt | grep "search"

# プロセス置換の場合
# grepは「引数で渡されたファイル」を読む
grep "search" <(cat file.txt) 

もし、diffのように「2つの引数が必要なコマンド」を使いたいなら、標準入力が1つしかないパイプでは対応できず、プロセス置換が必要不可欠になります。

まとめ

プロセス置換は、「標準入出力をファイルパスに変換する魔法のアダプター」です。

「このコマンド、パイプで繋げないな…」とか「一時ファイルを作るのが面倒だな」と感じたときは、ぜひプロセス置換を思い出してみてください。