GNU parallel 入門 - コマンドを並列実行して高速化する
この記事で解決できること
- 複数コマンドを CPU コアぶん同時に走らせて 処理時間を短縮できる
xargs -Pで困る 出力の混線・順序崩れ を回避できる--joblogと--resumeで 中断した残りのジョブを続きから実行 できる
結論(使いどころ)
- 1 件あたり重い処理を大量に回す(画像変換・ダウンロード・テスト)→
parallel - 出力を混ぜたくない / 入力順を保ちたい →
-k - 中断した処理を続きから再開したい →
--joblog+--resume
前提(対象環境)
- OS: Ubuntu / Debian 系を想定(他ディストリも考え方は同じ)
- GNU parallel(Ole Tange 作)が対象。
moreutils同梱の別物parallelとは互換性がない
GNU parallel とは何か?
結論: 標準入力やコマンド引数のリストを受け取り、各要素に対してコマンドを並列に実行するツール。既定では CPU コア数ぶんのジョブを同時に走らせる。
GNU parallel は、xargs の「リストを受け取ってコマンドを組み立てる」発想を、並列実行と出力制御に特化させたコマンドである。基本形は次の 2 通り。
# 1) 引数を ::: の後ろに並べる parallel echo ::: a b c # 2) 標準入力から渡す seq 1 3 | parallel echo
a b c
a b c のそれぞれに対して echo が別プロセスとして同時に起動する。既定の同時実行数は CPU コア数なので、コア数より多い入力は順にスロットへ流し込まれる。
入力 1 件ごとにコマンドが 1 回起動する(xargs の既定とは逆)。1 回の起動で複数引数をまとめたい場合は後述の -N を使う。
インストールと最初の一歩はどうするのか?
結論:
apt install parallelで導入。初回は学術引用の通知が出るので、parallel --citationを一度実行して了承を記録しておく。
sudo apt update sudo apt install parallel parallel --version
GNU parallel は学術論文での引用を求めるツールで、初回実行時に引用のお願いが表示される。CI やスクリプトで邪魔になる場合は、一度だけ次を実行して了承を記録する(~/.parallel/will-cite が作られ、以降は通知が消える)。
parallel --citation
ディストリによっては moreutils パッケージが別物の parallel を提供する。parallel --version で先頭行に GNU parallel と表示されるかを必ず確認する。表示されなければ GNU 版ではない。
なぜ xargs ではなく parallel なのか?
結論:
xargs -Pも並列実行できるが、出力が行単位で混ざる。parallel は出力をジョブ単位でまとめ、-kで入力順も保てる。
xargs -P 4 は手軽だが、複数ジョブの標準出力が1 行ずつ交互に混線しやすい。parallel は各ジョブの出力を内部でバッファし、ジョブが終わった単位でまとめて出力するため混ざらない。
| 観点 | xargs -P |
parallel |
|---|---|---|
| 出力の混線 | 起きやすい | ジョブ単位で防ぐ |
| 入力順の出力 | 保証なし | -k で保証 |
| 置換文字列の柔軟さ | {} のみ |
{} {.} {/} 等 |
| 実行ログ / 再開 | なし | --joblog --resume |
| 進捗表示 | なし | --bar --eta |
速度だけなら xargs -P で十分なことも多い。出力を壊したくない・再実行したいときに parallel の価値が出る。
置換文字列(プレースホルダ)はどう使うのか?
結論:
{}が入力そのもの。{.}は拡張子除去、{/}はファイル名、{//}はディレクトリ、{#}はジョブ番号。出力名の組み立てに多用する。
入力をコマンドのどこに差し込むかは置換文字列で指定する。省略すると末尾に {} が補われる。
# *.wav を同名 .mp3 に変換({.} で拡張子を除去)
parallel ffmpeg -i {} {.}.mp3 ::: *.wav主要な置換文字列は次のとおり。
| 記法 | 意味 | 例(入力 dir/file.txt) |
|---|---|---|
{} |
入力そのもの | dir/file.txt |
{.} |
拡張子を除去 | dir/file |
{/} |
ディレクトリを除いた名前 | file.txt |
{//} |
ディレクトリ部分 | dir |
{/.} |
名前から拡張子も除去 | file |
{#} |
ジョブ通し番号 | 1, 2, ... |
{%} |
ジョブスロット番号 | 1〜(同時実行数の範囲) |
# 入力ごとにジョブ番号を添えて出力先を作る
parallel 'echo job {#}: {}' ::: alpha beta gammajob 1: alpha job 2: beta job 3: gamma
ジョブ数と出力順はどう制御するのか?
結論: 同時実行数は
-jで指定。-j0は可能な限り多く、-j 200%はコア数の 2 倍。出力を入力順に揃えるなら-kを付ける。
# 同時 4 ジョブ
parallel -j 4 ./convert.sh ::: *.dat
# CPU コア数の 2 倍(I/O 待ちが多い処理向け)
parallel -j 200% curl -O ::: "${urls[@]}"
# 同時実行数の上限なし(注意して使う)
parallel -j0 echo ::: {1..100}並列実行すると終わった順に出力されるため、入力と出力の対応が崩れる。入力順で出力したいときは -k(--keep-order)を付ける。
seq 1 5 | parallel -k 'sleep $((RANDOM % 3)); echo {}'1 2 3 4 5
本番投入前は --dry-run を付けると、実際に走らせるコマンド列だけを表示できる。置換文字列の展開ミスをここで潰す。
複数の入力を組み合わせるには?
結論:
:::を複数並べると全組み合わせ(直積)。--linkで同じ位置どうしを対にする。1 行に複数列があるファイルは--colsepで{1}{2}として使う。
# 直積: a-1 a-2 b-1 b-2 c-1 c-2 の 6 ジョブ parallel echo ::: a b c ::: 1 2
# --link: a-1 b-2 c-3 のように位置で対応づけ parallel --link echo ::: a b c ::: 1 2 3
ファイルを入力にするなら ::::(または -a)。CSV のように列を分けたい場合は --colsep。
# hosts.txt の各行を引数にする
parallel ping -c1 {} :::: hosts.txt
# "user,host" 形式を列分割して使う
parallel --colsep ',' ssh {2} -l {1} uptime :::: targets.csv進捗・ログ・失敗時の再実行はどうするのか?
結論:
--barで進捗バー、--joblogで各ジョブの結果を記録。--resumeを併用すると、未完了ジョブだけを次回に引き継げる。
# 進捗バーを表示
parallel --bar ./task.sh ::: {1..50}
# 実行ログを記録(終了コード・所要時間が残る)
parallel --joblog run.log ./task.sh ::: *.dat--joblog で残したログがあれば、--resume ですでに成功したジョブを飛ばして続きから実行できる。失敗したジョブだけやり直すなら --resume-failed。
# 中断・失敗後、同じコマンドに --resume を足して再実行 parallel --joblog run.log --resume ./task.sh ::: *.dat
エラーで早めに止めたいときは --halt。
# 1 つでも失敗したら走行中のジョブを終えて停止 parallel --halt now,fail=1 ./task.sh ::: *.dat
--resume は 同じ --joblog ファイルと同じコマンド が前提。コマンドや入力を変えると正しく再開できない。
標準入力を分割する --pipe とは?
結論:
--pipeは引数ではなく標準入力そのものをブロックに分割し、各ブロックを並列のコマンドへ流す。巨大ログの集計などに向く。
これまでは「引数リストを並列化」していたが、--pipe は1 本の入力ストリームを分割して並列処理する。
# 巨大ファイルを 10MB ずつに分け、各ブロックを並列で grep cat huge.log | parallel --pipe --block 10M grep ERROR
--block で 1 ブロックのサイズを指定する。行の途中で切れないよう、parallel は改行境界で分割する。
実務でよく使うレシピ集
結論: 画像変換・一括ダウンロード・複数ホストへのコマンド実行が定番。
--dry-runで確認してから本番投入するのが安全。
コピペ用テンプレ
# 画像を一括リサイズ(出力名は {.}_small.jpg)
parallel convert {} -resize 50% {.}_small.jpg ::: *.jpg
# URL 一覧を 8 並列でダウンロード
parallel -j8 wget -q ::: $(cat urls.txt)
# 複数ホストへ同じコマンド(出力はホスト単位でまとまる)
parallel -k --tag ssh {} 'uptime' :::: hosts.txt
# まず確認 → 問題なければ --dry-run を外す
parallel --dry-run ./batch.sh {} ::: input/*--tag を付けると各出力行の先頭に入力(ホスト名など)が付き、どのジョブの結果かを追いやすくなる。