bashには、それをまるごとなくせる構文があります。プロセス置換(Process Substitution)です。
<(command) と書くだけで、コマンドの出力をあたかもファイルのように別のコマンドへ渡せます。一時ファイルを作る必要はありません。この記事では、プロセス置換の仕組みから始め、
diff・paste・tee との組み合わせ、複数サーバーの設定差分確認やアーカイブ比較など実務パターンまでを解説します。動作確認環境はRHEL 9.4 / Ubuntu 24.04 LTSです。この記事のポイント
・<(command) でコマンド出力を一時ファイルなしに別コマンドのファイル入力として渡せる
・diff <(cmd1) <(cmd2) で2コマンドの出力を直接比較できる実務パターンが基本
・複数サーバーの設定差分確認やアーカイブ比較が1行のコマンドに収まる
・プロセス置換はbash専用(#!/bin/bash が必須)。#!/bin/sh では動作しない
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
プロセス置換とは何か
プロセス置換は、bashが提供する「コマンドの出力を名前付きファイルディスクリプタ経由で別コマンドに渡す」仕組みです。通常、2つのコマンド出力を比較しようとするとこうなります。# 従来の方法(一時ファイルが必要) ls /etc/nginx/conf.d/ > /tmp/nginx_sv01.txt ssh sv02.example.local "ls /etc/nginx/conf.d/" > /tmp/nginx_sv02.txt diff /tmp/nginx_sv01.txt /tmp/nginx_sv02.txt rm -f /tmp/nginx_sv01.txt /tmp/nginx_sv02.txt
# プロセス置換を使う(一時ファイル不要) diff <(ls /etc/nginx/conf.d/) <(ssh sv02.example.local "ls /etc/nginx/conf.d/")
/dev/fd/63 や /dev/fd/62 のような名前付きファイルディスクリプタを用意し、コマンドの出力をそこにパイプします。diff はこのFDをファイルとして開いて読み込むため、一時ファイルは一切生成されません。<(command) でコマンド出力をファイルとして渡す基本
1. diffで2つのコマンド出力を比較する
最も頻繁に使うパターンです。2つのコマンド出力を直接diff に渡せます。#!/bin/bash # ファイルをソートしてから比較する diff <(sort /etc/hosts) <(sort /etc/hosts.bak)
[miyazaki@web01 ~]$ diff <(sort /etc/hosts) <(sort /etc/hosts.bak) 3a4 > 192.168.1.20 old-host.example.local
sort で事前にソートしてから比較することで、「行の順番だけが違う」というノイズを除けます。従来なら一時ファイルを2つ用意する必要がありましたが、プロセス置換なら不要です。2. pasteで複数コマンドの出力を横並びにする
paste コマンドと組み合わせると、複数のコマンド出力を横並びで確認できます。#!/bin/bash # ファイル名とサイズを横並びで出力する paste <(ls /var/log/*.log) <(du -sh /var/log/*.log 2>/dev/null | cut -f1)
[miyazaki@web01 ~]$ paste <(ls /var/log/*.log) <(du -sh /var/log/*.log 2>/dev/null | cut -f1) /var/log/auth.log 1.2M /var/log/messages 4.8M /var/log/nginx/access.log 23M
3. 許可リストに含まれないユーザーを抽出する
diff とプロセス置換を組み合わせた応用例です。#!/bin/bash # /etc/passwdに存在するが許可リストにいないユーザーを抽出する diff <(awk -F: '{print $1}' /etc/passwd | sort) <(sort /etc/allowed_users.txt) | grep '^< ' | sed 's/^< //'
grep '^< ' で「左側(/etc/passwd)にだけある行」、すなわち許可リスト外のユーザーを抽出します。セキュリティ監査スクリプトによく使うパターンです。実務での活用パターン
1. ディレクトリ内容の差分確認(複数サーバー)
Webサーバーが複数台ある環境で、設定ファイルの差分を素早く確認できます。ls コマンドの基本オプションと組み合わせると、出力フォーマットを細かく制御できます。#!/bin/bash # 2台のWebサーバーのnginx設定ファイル一覧を比較する SV01="web01.example.local" SV02="web02.example.local" diff <(ssh "${SV01}" "ls /etc/nginx/conf.d/") <(ssh "${SV02}" "ls /etc/nginx/conf.d/")
[miyazaki@bastion ~]$ diff > <(ssh web01.example.local "ls /etc/nginx/conf.d/") > <(ssh web02.example.local "ls /etc/nginx/conf.d/") 3a4 > maintenance.conf
maintenance.conf が web02 にしか存在しないことが即座にわかります。従来は2回のSSH接続・2つの一時ファイル・diffの3ステップでしたが、1コマンドに収まります。2. アーカイブ内容の比較(tarファイル)
tar コマンドの実用例として、2つのアーカイブの内容リストを比較するパターンです。解凍せずに内容一覧だけを取得して比較するため、ディスク消費なしに差分を確認できます。#!/bin/bash # 前日と当日のバックアップアーカイブの内容を比較する diff <(tar tf /backup/app-20260725.tar.gz | sort) <(tar tf /backup/app-20260726.tar.gz | sort)
3. 設定テンプレートの展開結果と実ファイルを比較する
#!/bin/bash # envsubstで変数展開したテンプレートと実際の設定ファイルを比較する export APP_HOST="web01.example.local" export APP_PORT="8080" diff <(envsubst < /etc/myapp/config.template) /etc/myapp/config.conf
envsubst でテンプレートの変数を展開した結果と、実際の設定ファイルを直接 diff で比較します。デプロイ後の設定確認が1コマンドで完結します。プロセス置換の設計と合わせて、シェルスクリプト全体の設計力を体系的に身につけたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場で使える設計パターンを体系的に学べます。
>(command) でコマンドへの入力として渡す
<(command) が「読み取り用」であるのに対して、>(command) は「書き込み用」です。あるコマンドの出力先として別のコマンドを指定します。1. teeで出力を複数の処理に同時に振り分ける
tee と組み合わせることで、出力を画面表示と圧縮保存に同時に振り分けられます。#!/bin/bash # ログ収集:画面に流しながら同時にgzip圧縮して保存する LOGFILE="/var/log/app/$(date +%Y%m%d).log.gz" some-monitoring-command | tee >(gzip > "${LOGFILE}")
tee がコンソールに表示し、同時に >(gzip > logfile.gz) でgzip圧縮しながら保存します。2. アクセスログをエラー行と正常行に振り分ける
#!/bin/bash # nginxアクセスログをステータスコード別に振り分けて保存する tail -f /var/log/nginx/access.log | tee >(grep " [45][0-9][0-9] " >> /var/log/app/errors.log) >(grep " [23][0-9][0-9] " >> /var/log/app/ok.log) > /dev/null
tail -f で流れてくるアクセスログを、ステータスコード別にエラーログ・正常ログへ同時に振り分けます。> /dev/null で端末への出力は捨てています。プロセス置換の制約と注意点
【重要】#!/bin/bash が必須。#!/bin/sh では動かない
プロセス置換はbashの拡張機能であり、POSIX sh(dashなど)では利用できません。Ubuntuでは/bin/sh が実際にはdashのため、shebangが #!/bin/sh になっているスクリプトでプロセス置換を使うとエラーになります。#!/bin/sh # dash環境ではこのような構文エラーになる diff <(ls /etc/) <(ls /usr/) # dash: 1: Syntax error: "(" unexpected
#!/bin/bash で始めてください。プロセス置換内の変数はサブシェルで実行される
プロセス置換の内部はサブシェルで実行されます。内部で変数に代入しても、外側のスクリプトには反映されません。#!/bin/bash count=0 # プロセス置換内での変数変更は外側に反映されない diff <(cat file1.txt) <(cat file2.txt; count=1) echo "count: $count" # 0のまま変化しない
$())を使う方針に切り替えてください。トラブルシュート
「/dev/fd/N: そのようなファイルまたはディレクトリはありません」の原因と対処
プロセス置換を使った際に以下のエラーが出ることがあります。[miyazaki@web01 ~]$ diff <(ls /etc/) <(ls /usr/) diff: /dev/fd/63: そのようなファイルまたはディレクトリはありません
/dev/fd が存在しないか、FDの権限が制限されている・確認方法:
ls -la /dev/fd/ でFDエントリが存在するかを確認する・対処:Dockerコンテナ実行時は
--device /dev/fd の指定が必要な場合がある。または一時ファイル方式に切り替える「Syntax error: "(" unexpected」の原因と対処
・原因:スクリプトのshebangが#!/bin/sh になっており、dash等のPOSIX shで実行されている・確認方法:
head -1 スクリプト名 でshebang行を確認する・対処:shebangを
#!/bin/bash に変更する本記事のまとめ
| やりたいこと | プロセス置換パターン |
|---|---|
| 2コマンドの出力を差分比較 | diff <(cmd1) <(cmd2) |
| ソートして比較 | diff <(sort file1) <(sort file2) |
| 複数コマンド出力を横並びで表示 | paste <(cmd1) <(cmd2) |
| tarアーカイブ内容を比較 | diff <(tar tf arc1.tar.gz | sort) <(tar tf arc2.tar.gz | sort) |
| 出力を圧縮しながら画面に流す | cmd | tee >(gzip > out.gz) |
| 出力を複数の処理に振り分ける | cmd | tee >(filter1 >> log1) >(filter2 >> log2) > /dev/null |
#!/bin/bash のシェバングが必須です。コンテナやCIなど /dev/fd が制限された環境では動作しない場合があります。一時ファイルを生成しないため高速ですが、プロセス置換内の変数は外側のスクリプトに反映されない点は設計時に注意してください。
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:シェルスクリプトの死活監視設計|curlとncコマンドでサービス状態を自動確認してアラートを送る方法
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます