xargsの並列処理-Pで一括処理を高速化する|-n・-I・-d・-rの実務テクニック

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Linuxtips > xargsの並列処理-Pで一括処理を高速化する|-n・-I・-d・-rの実務テクニック
「xargsで数万ファイルを処理しているが時間がかかりすぎる」 「-P オプションが速くなると聞いたが、いくつ並列にすればいいか分からない」 数千〜数万件規模のファイル処理を1台のサーバーで回す現場では、xargsを「ただ繋ぐだけ」で終わらせるか、-P-n-I を組み合わせて性能を引き出すかで実行時間が大きく変わります。 この記事では、xargsの高度なオプション(-P 並列処理、-n バッチサイズ、-I 引数挿入、-d/-0 区切り文字制御、--max-procs-r 空入力スキップ)を、現場で詰まりやすいポイントとあわせて解説します。 findとの基本的な組み合わせよりも一段深い、xargs単体としての高速化・安全化テクニックに絞った内容です。 RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みです。

この記事のポイント

-P N でN並列実行できる、CPUコア数(nproc)と同数が出発点
-n M で1プロセスあたりの引数数を制御し、起動コストと並列度を最適化
-I {} で挿入位置を指定、-0/-d で区切り文字をstdin安全に扱う
-r--no-run-if-empty)で空入力時の意図せぬ実行を防ぐ
・findとの基本的な組み合わせ(find | xargs grep 等)は別記事で扱っています


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

この記事と「find|xargs|grepの組み合わせ記事」との使い分け

xargsの基本動作と、findとの組み合わせ・grepへの引数渡しといった「最頻出パターン」については、 xargsコマンドの使い方|findやgrepの出力を引数に渡して一括処理する方法 を参照してください。 本記事は、その先にある「xargsそのもののオプションを使いこなして処理を高速化・安全化する」ことに特化しています。 具体的には、次のような場面を想定しています。 ・数千〜数万ファイルの圧縮・変換・コピーを1台で回したい ・find | xargs cmd をそのまま走らせると遅すぎる ・stdinの区切り文字(改行・スペース・タブ・NUL)を意識して扱いたい ・-I-P を組み合わせて、引数の挿入位置を保ちつつ並列で動かしたい 最初に基本動作の確認だけ短くおさらいし、そこから先は -P-n-I・区切り文字制御の話に集中します。

xargsの動作モデルを30秒で確認する

xargsは、標準入力から受け取った文字列をコマンドの引数として渡し、コマンドを実行します。 このとき、xargsは「1回のコマンド実行に何個の引数を渡すか」を内部的に決めて、入力を分割して実行します。

# 標準入力の3行を、1回のechoでまとめて渡す $ printf 'a\nb\nc\n' | xargs echo a b c # 標準入力の3行を、1回ごとに分けて渡す(-n 1で1引数ずつ) $ printf 'a\nb\nc\n' | xargs -n 1 echo a b c

ここでの分割単位を決めているのが -n オプションで、何プロセスを同時に起動するかを決めるのが -P オプションです。 本記事の主役はこの2つに、引数の挿入位置を制御する -I と、stdinの安全な扱いを決める -0/-d を加えた4つです。

-P オプションで並列処理を行う(基本)

-P Nで同時実行プロセス数を指定する

-P N(または --max-procs N)を付けると、xargsは最大N個のコマンドを同時に起動します。 N=1(既定)は逐次実行、Nを増やすと並列度が上がります。

# まずCPUコア数を確認する $ nproc 8 # 8コア中、4プロセスで並列圧縮する $ find /var/log/archive -name '*.log' -print0 | xargs -0 -n 1 -P 4 gzip

-P 4 は「同時に最大4プロセスのgzipを動かす」という指示です。 私はサーバーで -P を最初に使った時、実行時間が約3分の1になり、CPU利用率が一気に8コア中5〜6コアまで上がるのを見て驚きました。 逆に言えば、それまで7コアを遊ばせていたわけです。

並列度の出発点はnprocの値

並列度Nの目安は次の通りです。 ・CPUバウンド処理(圧縮・変換・暗号化)→ nproc と同数 ・I/Oバウンド処理(ファイルコピー・ネットワーク送信)→ nproc × 2 程度まで ・本番サーバーで他サービスと共存している → nproc ÷ 2 を上限にする

# CPUコア数と同じ並列度(CPUバウンド向け) $ find /data -name '*.csv' -print0 | xargs -0 -n 1 -P "$(nproc)" gzip # 半分の並列度(本番共存) $ HALF=$(( $(nproc) / 2 )) $ find /data -name '*.csv' -print0 | xargs -0 -n 1 -P "$HALF" gzip

-P 0 を指定すると「システムが許す限り全部並列起動」になりますが、本番サーバーでは絶対に避けてください。 ロードアベレージが一瞬で跳ね上がり、他のサービスから「サーバーが固まった」と連絡が来ます。

並列実行時はCPU・メモリ・ディスクI/Oを必ず監視する

並列度を上げると、ボトルネックがCPUからI/Oに移ることがあります。 別ターミナルで以下を流しながら、どこが詰まっているかを見るのが現場のやり方です。

# CPUとロードアベレージを1秒間隔で観察する $ vmstat 1 # ディスクI/Oを観察する(sysstatパッケージ) $ iostat -x 1 # 個別プロセスのCPU使用率を観察する $ top -b -n 1 | head -20

%iowait が常時20%を超えるなら、並列度を上げてもディスクが追いつきません。 r(実行待ちプロセス数)が nproc を大きく超えるなら、CPUの取り合いになっています。

-n オプションでバッチサイズを最適化する

-n の意味と既定の挙動

-n N は「1回のコマンド実行に最大N個の引数を渡す」という指示です。 未指定の場合、xargsはOSの引数長上限(getconf ARG_MAX、典型値2MB)に収まる範囲で可能な限りまとめて1回で渡します。

# 引数長上限を確認する $ getconf ARG_MAX 2097152 # まとめて渡す(既定動作・gzipには使えない、後述) $ ls /tmp/*.txt | xargs echo # 1引数ずつ渡す $ ls /tmp/*.txt | xargs -n 1 echo # 100引数ずつ渡す $ ls /tmp/*.txt | xargs -n 100 echo

-n と -P の組み合わせがチューニングの本丸

-P-n は単独で使うよりも、組み合わせて初めて効果が出ます。 処理特性によって最適値が違うため、現場では小さなサンプルで時間計測してから本番に流します。

# 1ファイルにつき1プロセス起動・8並列(gzipのように1引数=1プロセスが妥当な場合) $ find /data -name '*.csv' -print0 | xargs -0 -n 1 -P 8 gzip # 100ファイル単位でまとめてrm・8並列(rmは多数の引数を受け取れる) $ find /tmp/cache -type f -print0 | xargs -0 -n 100 -P 8 rm # 既定のまとめ動作・並列なし(最も起動コストが低い) $ find /tmp/cache -type f -print0 | xargs -0 rm

ポイントは「コマンドが1引数しか取らない(gzip、convert、独自スクリプト)」のか「複数引数を一度に処理できる(rm、cp、chmod)」のかを分けることです。 1引数しか取れないコマンドに対しては -n 1 がほぼ必須で、ここに -P を効かせて並列化します。 複数引数を取れるコマンドでは、-n を大きめにしつつ -P も併用するとプロセス起動コストが下がります。

time コマンドで効果を実測する

time を付けて、どの組み合わせが速いかを実測する習慣を付けると、勘ではなく数字でチューニングできるようになります。

# 逐次・1引数ずつ $ time find /data -name '*.csv' -print0 | xargs -0 -n 1 gzip real 1m23.456s # 4並列・1引数ずつ $ time find /data -name '*.csv' -print0 | xargs -0 -n 1 -P 4 gzip real 0m22.123s # 8並列・1引数ずつ $ time find /data -name '*.csv' -print0 | xargs -0 -n 1 -P 8 gzip real 0m13.987s

私は以前、何も考えずに -P 16 にして「速くなるはず」と思い込んでいた時期があります。 実際に iostat を見たら %util が99%に張り付いていて、ディスクが完全に頭打ちでした。 そこから先は並列度を上げても1秒も縮まないので、-P 8 に落として運用するように変えました。

-I で引数の挿入位置を指定する

-I {} の基本動作

xargsは既定でコマンドの末尾に引数を追加しますが、コマンドの途中に置きたい場合は -I を使います。 -I {} と書くと、{} が標準入力から受け取った値に置換されます。

# .conf を .conf.bak としてコピーする(拡張子を後ろに付け足す) $ find /etc -name '*.conf' -print0 | xargs -0 -I {} cp {} {}.bak # logファイルを別ディレクトリに移動する(引数が中央にくる) $ find /var/log -name '*.log.1' -print0 | xargs -0 -I {} mv {} /archive/2026/

-I を使うと、自動的に「1入力ごとに1コマンド起動」(-n 1 相当)になります。 これは仕様で、-I-n N(N≥2)の併用は不可です。

-I と -P を組み合わせて並列化する

-I は1引数ずつしか処理できませんが、-P は併用できるので、プロセスを並べることで実行時間を縮められます。

# 設定ファイルを.bak付きでバックアップ・8並列 $ find /etc -name '*.conf' -print0 | xargs -0 -I {} -P 8 cp {} {}.bak # 画像をリサイズして別ディレクトリに保存・4並列 $ find ./photos -name '*.jpg' -print0 | xargs -0 -I {} -P 4 \ convert {} -resize 1200x900 ./resized/{}

ファイル名にディレクトリパスが含まれていると、最後の convert の出力先で問題が起きることがあります。 2つ目の例は ./photos/sub/a.jpg という入力に対して ./resized/./photos/sub/a.jpg という存在しないパスへ書こうとして失敗するため、実務ではbasename抽出のラッパースクリプトをかます形になります。 xargsだけで完結しようとせず、複雑になったらシェルスクリプトに切り出す判断も必要です。

-I のプレースホルダ名は変更できる

{} は単なるデフォルトの記号で、別の文字に変えられます。 コマンド内に {} 自体を含めたい場合に有効です。

# プレースホルダを %% に変更する $ find . -name '*.txt' -print0 | xargs -0 -I %% mv %% /archive/ # プレースホルダを FILE に変更する(読みやすさ重視) $ find . -name '*.log' -print0 | xargs -0 -I FILE bash -c 'gzip "FILE" && echo "done: FILE"'

ただし、3つ目の bash -c の中で変数として使う書き方には注意が必要です。 "FILE" はクォートで囲まれた中の文字列としてxargsに置換されるため、ファイル名にスペースや記号が入っても安全です(後述の -0 と組み合わせている前提)。

stdinの区切り文字を制御する:-0 と -d

-0 でNUL文字区切りにする(鉄則)

xargsは既定で「スペース・タブ・改行」を区切り文字として扱います。 そのため、ファイル名にスペースが含まれていると、そこで分割されて誤ったファイル名が渡されます。 これは何度も事故の元になっているので、findと組み合わせる場合は -print0-0 の併用がほぼ必須です。

# NG: スペース入りファイル名でエラー $ find . -name '*.txt' | xargs rm rm: cannot remove 'my': No such file or directory rm: cannot remove 'document.txt': No such file or directory # OK: NUL文字区切りで安全 $ find . -name '*.txt' -print0 | xargs -0 rm

-d で任意の1文字を区切りにする

入力データがCSVのように特定の文字で区切られている場合、-d でその文字を区切りに指定できます。

# カンマ区切りデータを1要素ずつechoする $ echo 'apple,banana,cherry' | xargs -d ',' -n 1 echo apple banana cherry # 改行のみを区切りにする(スペースは区切らない、ファイル名にスペース入りでも安全) $ printf 'my document.txt\nanother file.txt\n' | xargs -d '\n' -I {} ls -l '{}'

-d '\n'find -print0 | xargs -0 ほど厳密ではありませんが、stdinが改行区切りで来る前提のスクリプト処理では便利です。 ただし、ファイル名に改行を含むケースは -0 しか対応できません(ext4/xfsともに改行入りファイル名は仕様上作成可能)。

-0 と -d を併用する場合の優先順位

-0-d を同時に書いた場合、後に書いたほうが有効になります。 混乱を避けるため、findと組み合わせるときは -0 一本で書く習慣にすることをおすすめします。

-r オプションで空入力時の誤実行を防ぐ

空入力でも1回コマンドが走る既定挙動

xargsの既定動作では、標準入力が空のときも引数なしでコマンドを1回起動します。 これが原因で、find結果が空のときに rm が引数なしで叩かれてエラーになる事故が現場では時々起きます。

# NG: find結果が空でもrmが起動してエラーになる $ find /tmp -name '*.notexist' | xargs rm rm: missing operand Try 'rm --help' for more information. # OK: -r(--no-run-if-empty)で空入力をスキップ $ find /tmp -name '*.notexist' | xargs -r rm (何も起きない・正常終了)

-r はGNU xargs拡張なので、BSD系(macOSやFreeBSD)では使えないことがあります。 LinuxサーバーのスクリプトではほぼGNU xargsなので、安全策として -r を付ける習慣をつけておくと、cronで動かしたときにエラーメールが飛ぶ事故を減らせます。

cronスクリプトには -r を必ず付ける

私はかつて、定期的に古いログを削除するcronで -r を忘れて、データの増減タイミングで毎週火曜だけrmからエラーメールが届く運用をしていました。 受信箱に毎週決まった曜日に届くエラーメールほど、最初は気になっても、しばらくすると慣れて見なくなります。 本物の障害メールが埋もれる原因になるので、自動処理では空入力ガードを最初から入れてください。

# cron用スクリプトの推奨形 #!/bin/bash set -euo pipefail find /var/log/app -name '*.log' -mtime +30 -print0 \ | xargs -0 -r rm

並列実行時のログ・出力の扱い

並列出力は順序が保証されない

-P 2 以上で動かすと、複数プロセスが同時に標準出力に書き込むため、出力行が混ざることがあります。 gzipやconvertのように標準出力を使わない処理ならあまり問題になりませんが、grepの結果を集めるような用途では注意が必要です。

# 並列grepの結果はファイルに集めて、後でsortする $ find /etc -name '*.conf' -print0 \ | xargs -0 -n 1 -P 4 grep -H 'Listen' > /tmp/listen.log # プロセスごとに別ファイルに書き出す(混在を完全に避ける) $ find /etc -name '*.conf' -print0 \ | xargs -0 -n 1 -P 4 -I {} bash -c \ 'grep -H "Listen" "{}" > "/tmp/grep_$$.log"'

シェル変数 $$ はプロセスIDなので、並列で起動された各 bash -c ごとに別のファイル名になります。 最後に cat /tmp/grep_*.log でまとめれば、行が混ざる心配はありません。

失敗の有無をxargsの終了コードで拾う

xargsは、起動した子プロセスが1つでも失敗するとexit code 123を返します(GNU xargsの場合)。 これを使えば、並列処理でどれかが失敗したかどうかをスクリプトで検知できます。

# 全部成功するとexit 0 $ find /tmp -name '*.tmp' -print0 | xargs -0 -r rm $ echo $? 0 # 1つでもgzip失敗するとexit 123 $ find /data -name '*.csv' -print0 | xargs -0 -n 1 -P 4 gzip $ echo $? 123

cronスクリプトでは set -euo pipefail と組み合わせて、xargsの失敗を黙って通さないようにしておくと運用が安定します。

並列処理の実務ワンライナー集

1. 大量CSVをgzip圧縮(CPUバウンド・コア数並列)

$ find /data/exports -name '*.csv' -mtime +1 -print0 \ | xargs -0 -n 1 -P "$(nproc)" -r gzip

2. 数万ファイルを別ディスクへコピー(I/Oバウンド・控えめ並列)

$ find /src -type f -print0 \ | xargs -0 -I {} -P 4 -r cp --parents {} /backup/

3. ホストリストへの並列ping疎通確認

$ cat hosts.txt | xargs -I {} -P 16 -r \ bash -c 'ping -c1 -W1 {} >/dev/null 2>&1 && echo "OK: {}" || echo "NG: {}"'

4. 画像の一括リサイズ(ImageMagick・コア数並列)

$ find ./photos -name '*.jpg' -print0 \ | xargs -0 -I {} -P "$(nproc)" -r \ mogrify -resize 1200x900 -quality 85 {}

5. ホスト一覧へSSH並列実行(運用作業の高速化)

$ cat hosts.txt \ | xargs -I {} -P 8 -r \ ssh -o ConnectTimeout=5 {} 'uptime'

5番目のSSH並列はpdshやansibleと違って粗いやり方ですが、台数が10〜30台で軽いコマンドを叩くだけなら十分使えます。 私はansibleを起動するほどでもない確認作業に、この形をよく使います。

トラブルシュート:xargs並列処理でつまずく定番5パターン

1. -P を上げても速くならない(I/O頭打ち)

iostat -x 1%util が90%超なら、ディスク側が頭打ちです。 SSDかHDDかで限界が違うので、HDDサーバーで -P 16 にしても1コアぶんしか効果が出ないこともあります。

2. 並列度を上げたらメモリ不足で止まった

1プロセスが消費するメモリ × N並列 がサーバー搭載メモリを超えると、OOM Killerが走ります。 free -m でベースラインを取り、1プロセスあたりの消費量を top で確認してから並列度を決めてください。

3. 「unmatched single quote」エラー

入力にシングルクォートが含まれていて、xargsがクォート処理を試みて失敗しています。 -0(NUL区切り)にすればクォート処理が無効になり、解決します。

# NG: シングルクォート入りファイル名で失敗 $ echo "it's_a_file.txt" | xargs rm xargs: unmatched single quote;... # OK: -0 でクォート処理を無効化 $ printf "it's_a_file.txt\0" | xargs -0 rm

4. 「Argument list too long」が出る

シェル展開(rm /tmp/cache/*)で発生するエラーで、xargs自体は分割実行するため通常は出ません。 find ... -print0 | xargs -0 の形に書き換えれば回避できます。

5. -I {} と -0 の順序を逆にして動かない

GNU xargsでは順序の影響は基本的にありませんが、過去のバージョンや一部のBSD系では -0 を先に書かないと無視される事例があります。 無難に「-0-I {}-P N」の順で書くのを習慣にしておくと、環境差で詰まりません。

findとの組み合わせの基本に戻りたい場合

xargsを使う場面の8割は、findと組み合わせた検索結果への一括処理です。 find -exec との比較や、find | xargs grep の文字列検索パターンなど、findとの基本的な組み合わせ方は xargsコマンドの使い方|findやgrepの出力を引数に渡して一括処理する方法 にまとめてあります。 本記事の内容と合わせて読むと、xargsの使いどころを「網羅性」「安全性」「並列性」の3軸で押さえられます。

本記事のまとめ

xargsの真価は、findと繋ぐだけでなく -P-n-I を組み合わせて性能と安全性を引き出せることです。 最後にチートシートで振り返ります。
やりたいこと オプション
N並列で起動する -P N ... | xargs -n 1 -P 8 gzip
1プロセスあたり引数N個 -n N ... | xargs -n 100 rm
引数の挿入位置を指定 -I {} ... | xargs -I {} cp {} {}.bak
NUL区切り(安全) -0 find ... -print0 | xargs -0 ...
任意の文字を区切りに -d ... | xargs -d ',' -n 1 echo
空入力をスキップ -r ... | xargs -r rm
CPUコア数で並列 -P "$(nproc)" ... | xargs -n 1 -P "$(nproc)" gzip
実務での順序は「-0(安全)→ -r(空入力ガード)→ -n(バッチサイズ)→ -P(並列度)→ コマンド」の流れで書くと、読み返した時に意図が伝わりやすくなります。 並列処理は速さの裏でCPU・メモリ・ディスクI/Oに負荷がかかるので、vmstatiostat でボトルネックを確認しながら、サーバーに優しい並列度を選んでください。

xargsを使いこなせたら、次はサーバー構築の全体像を学びませんか?

xargsでファイル操作を自動化できるようになると、サーバー管理の効率が格段に上がります。その力を活かして、体系的にLinuxサーバー構築を学んでみませんか。
ネットの切れ端の情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。