xargs 実践活用 - 標準入力をコマンド引数に変換する

xargs 実践活用 - 標準入力をコマンド引数に変換する

この記事で解決できること

  • xargs正しい使い分け が分かる(パイプで渡すだけでは動かないコマンドが動くようになる)
  • find | xargs rm でファイル名にスペースが入ってると壊れる」定番事故 を回避できる
  • -I {} / -n / -P複雑な置換・分割・並列実行 ができるようになる

結論(実務の型)

  • まずは find ... -print0 | xargs -0 コマンド を覚える(スペース対応の安全形)
  • 引数を中間に挟みたいなら -I {}
  • 並列で速くしたいなら -P N
  • 空入力で全件爆発を防ぐなら -r(GNU 拡張)

前提(対象環境)

  • OS:Linux(GNU findutils)/ macOS は BSD 版なので一部オプションが異なる
  • 動作確認は Ubuntu 22.04 / GNU xargs (findutils) 4.8.0 系
  • パイプ・標準入出力の基本は パイプとリダイレクト入門 を参照

xargs とは何か?なぜ必要なのか?

xargs標準入力を読み取って、それを別コマンドの引数として組み立てる ためのツール。パイプは「標準出力 → 標準入力」しか渡せないため、引数として受け取りたいコマンド(rm / mv / cp 等)と直接つなぐと動かない。

# NG: rm は標準入力からファイル名を読まない(何も削除されない / エラー)
find . -name "*.tmp" | rm

# OK: xargs が標準入力を引数に変換して rm に渡す
find . -name "*.tmp" | xargs rm

1 行で言うとxargs は「パイプの中身を、次のコマンドの 引数欄 に貼り付けてくれる」ツール。

1. 基本形:パイプから引数を組み立てる

$ echo "a b c" | xargs echo
a b c

xargs は標準入力をスペース/改行で区切り、まとめて次のコマンドに渡す。

1-1. find と組み合わせる定番形

$ find . -name "*.log" | xargs ls -lh

find で見つけたファイル群を ls -lh の引数として渡す。

1-2. ARG_MAX を意識せず大量ファイルを処理

シェルの直接展開(ls *.log)は ARG_MAX で打ち止めになるが、xargs は適切に分割実行する。

# 数十万ファイルを処理しても "Argument list too long" にならない
$ find /var/log -name "*.gz" | xargs gzip -t

2. なぜ -print0 と -0 を使うのか?

ファイル名に スペース・タブ・改行・引用符 が含まれると、デフォルトの xargs は誤った区切りで分解する。これが xargs 最大の事故源。

2-1. 危険な例

# ファイル名「my file.txt」がスペースで分割され、
# "my" と "file.txt" の 2 ファイルを削除しようとする
$ find . -name "*.txt" | xargs rm

2-2. 安全形:NUL 区切りペア

$ find . -name "*.txt" -print0 | xargs -0 rm
  • find -print0:区切り文字を NUL (\0) にする
  • xargs -0:区切り文字を NUL として読む

NUL はファイル名に含めることが不可能な唯一の文字なので、確実に区切れる。

find と xargs を使うなら、ペアで -print0 / -0 を付けるのを反射にする。 これだけで事故の 8 割は防げる。

2-3. grep -l / grep -rl も同様

# 危険
$ grep -rl "TODO" . | xargs sed -i 's/TODO/DONE/g'

# 安全形
$ grep -rlZ "TODO" . | xargs -0 sed -i 's/TODO/DONE/g'

grep -Z も NUL 区切り出力。grep -rlxargs の組み合わせ時は -Z / -0 ペアを使う。

3. -I {} プレースホルダ:引数を任意位置に挿入する

デフォルトの xargs引数を末尾に追加 する。中間や複数箇所に置きたいなら -I {} を使う。

3-1. 末尾固定の限界

# NG: mv は「移動元 移動先」の順なので、末尾追加だと壊れる
$ ls *.bak | xargs mv archive/
# → mv archive/ file1.bak file2.bak ... のようになる(archive/ が移動元扱い)

3-2. -I {} で引数位置を指定

$ ls *.bak | xargs -I {} mv {} archive/
# → mv file1.bak archive/
#    mv file2.bak archive/
#    (1 件ずつ実行される)

{} の部分が標準入力の 1 件ずつ に置換される。

3-3. 複数回置換も可能

$ cat hosts.txt | xargs -I {} ssh {} "hostname && uptime"

-I {} を使うと 自動的に 1 件ずつ実行 される(バッチ実行されない)。大量実行時は次の -n / -P を併用。

4. -n / -P でバッチサイズと並列度を制御

4-1. -n N:N 個ずつ引数を渡す

$ seq 1 10 | xargs -n 3 echo
1 2 3
4 5 6
7 8 9
10

1 行 3 引数ずつ echo に渡される。

4-2. -P N:N 並列で実行

# 4 並列で .gz ファイルを検証
$ find . -name "*.gz" -print0 | xargs -0 -n 1 -P 4 gzip -t
  • -n 1:1 件ずつ独立して
  • -P 4:同時に最大 4 プロセス

-P 0 で「コア数まで自動」(GNU 拡張)。CPU バウンドな処理を一気に速くできる。

並列実行の注意点:

  • 出力順は 不定 になる(並列でログを出すと混ざる)
  • DB 接続・I/O を伴う処理は 接続数の上限 に注意
  • 失敗時のリカバリが難しいので、本番では --dry-run 相当の確認を先に

5. 事故防止:-r / -t / -p で安全に動かす

5-1. -r:空入力時は実行しない(必須レベル)

# 危険: find が何もマッチしなくても rm が呼ばれる(引数なしで暴発する可能性)
$ find . -name "存在しない条件" | xargs rm

# 安全: 入力が空なら何もしない
$ find . -name "存在しない条件" | xargs -r rm

GNU xargs はデフォルトで空入力を渡すが、--no-run-if-empty-r)で抑止できる。BSD xargs(macOS)はデフォルトで空入力時に実行しないため、移植性を意識して -r を明示するのが安全。

5-2. -t:実行コマンドを表示(dry-run の代替)

$ find . -name "*.log" | xargs -t rm
rm ./access.log ./error.log

-t は実行前に組み立てたコマンドを stderr に出力する。実際には実行する ので、本番前は次の echo トリックで確認。

5-3. echo トリック:dry-run

# 本当に実行する前に、組み立て結果を確認
$ find . -name "*.tmp" -print0 | xargs -0 echo rm
rm ./a.tmp ./b.tmp ./c.tmp

頭に echo を挟むと、組み立て結果が「出力されるだけ」になる。問題なければ echo を外して実行。

5-4. -p:1 件ずつ実行確認(対話モード)

$ find . -name "*.tmp" -print0 | xargs -0 -p rm
rm ./a.tmp ./b.tmp ?...y

y 入力時のみ実行。慎重に進めたい時に。

6. xargs と find -exec の使い分けは?

find だけでも -exec で同等のことが可能。どちらを使うべきか。

観点 find -exec xargs
パフォーマンス 1 件ずつ exec(遅い) バッチで exec(速い)
+ 終端 -exec ... + でバッチ化可 デフォルトでバッチ
ファイル名安全性 標準で安全(NUL 不要) -print0 / -0 必須
並列実行 不可 -P で可能
任意の標準入力 不可(find 限定) 可(任意のコマンドの出力)
可読性 1 行で完結 パイプで明示的

6-1. find だけで完結するなら -exec で十分

# シンプルで安全
$ find . -name "*.tmp" -exec rm {} +

-exec ... + を使えば内部的にバッチ化される。ファイル名にスペースがあっても安全。

6-2. xargs を選ぶケース

  • find 以外の出力(grep -l / ls / cat list.txt)を引数化したい
  • 並列実行 (-P) で高速化したい
  • パイプの中で他コマンドの出力と組み合わせたい

判断基準:find 単体で済むなら -exec ... +、並列・他コマンドと組み合わせるなら xargs

7. 実務テンプレート集

安全テンプレ(コピペ用)

# 1. 大量ファイル削除(スペース対応)
find /tmp -name "*.cache" -mtime +7 -print0 | xargs -0 -r rm

# 2. ファイル一覧から移動(プレースホルダ)
cat filelist.txt | xargs -I {} mv {} /archive/

# 3. 並列でファイル圧縮(4 並列)
find . -name "*.log" -print0 | xargs -0 -n 1 -P 4 gzip

# 4. 複数ファイルに一括置換
grep -rlZ "old-string" ./src | xargs -0 sed -i 's/old-string/new-string/g'

# 5. リモートホストへ並列 ssh
cat hosts.txt | xargs -I {} -P 8 ssh {} "uptime"

# 6. 本番前の dry-run
find . -name "*.bak" -print0 | xargs -0 echo rm

やってはいけないこと

  • find | xargs rm-print0 / -0 を付け忘れる
  • xargs -r 無しで空入力に対する保険を取らない
  • -P で並列実行する処理を本番でいきなり試す(先に小規模で確認)
  • 出力順が重要な処理に -P を使う(並列で混ざる)

次に読む