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 -rl と xargs の組み合わせ時は -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
最悪のケース:find ... | xargs rm -rf で find が 0 件ヒット、かつ後続の rm -rf が引数なしで . を見るような状況は壊滅的。-r を反射で付ける。
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を使う(並列で混ざる)