xargs は「パイプで受け取った文字列」をコマンドの 引数 として渡すためのコマンドです。
find や grep などと組み合わせることで、複数のファイルに対して同じ操作をまとめて実行できます。
なぜ xargs が必要か
多くのコマンドは 標準入力 ではなく 引数 でファイルを受け取ります。
例えば rm はパイプで渡しても動きません。
# これはエラー(rm は標準入力を読まない)
find . -name "*.tmp" | rm
ここで xargs を使うと、パイプの出力を引数として渡せます。
find . -name "*.tmp" | xargs rm
基本的な使い方
標準入力を引数に変換する
echo "file1.txt file2.txt file3.txt" | xargs ls -l
# ls -l file1.txt file2.txt file3.txt と同じ
複数行の入力も自動でまとめてくれます。
cat filelist.txt | xargs wc -l
# filelist.txt に書かれたファイルそれぞれの行数を表示
-I オプション ― 引数の位置を指定する
デフォルトでは引数はコマンドの末尾に追加されます。
-I {} を使うと、{} の位置に引数を挿入できます。
find . -name "*.log" | xargs -I {} cp {} /backup/
# 各 .log ファイルを /backup/ にコピー
{} は慣習的によく使われますが、別の文字列でも構いません。
find . -name "*.txt" | xargs -I FILE mv FILE FILE.bak
find との組み合わせ
find -exec vs xargs
find には -exec オプションがありますが、xargs を使う方法とどう違うのでしょうか。
# find -exec:ファイルごとに rm を呼び出す(遅い)
find . -name "*.tmp" -exec rm {} \;
# xargs:まとめて rm を一度だけ呼び出す(速い)
find . -name "*.tmp" | xargs rm
xargs はファイルをまとめて渡すため、ファイル数が多いときに高速です。
スペースや改行を含むファイル名への対応
ファイル名にスペースが含まれると誤動作する場合があります。
-print0 と -0 を組み合わせると安全です。
find . -name "*.txt" -print0 | xargs -0 rm
-print0 は null 文字(\0)区切りで出力し、-0 はそれを正しく解釈します。
便利なオプション
-n ― 一度に渡す引数の数を制限する
echo "a b c d e f" | xargs -n 2 echo
# echo a b
# echo c d
# echo e f
引数を 2 個ずつ渡して echo を 3 回実行します。
-P ― 並列実行
-P オプションで同時実行数を指定できます。大量のファイルを処理するときに有効です。
find . -name "*.jpg" | xargs -P 4 -I {} convert {} -resize 800x600 {}_resized.jpg
# 4 プロセス並列で画像をリサイズ
-t ― 実行コマンドを表示する(確認用)
find . -name "*.log" | xargs -t rm
# rm file1.log file2.log ... と表示してから実行
実際に削除する前に -t で確認するのがおすすめです。
—no-run-if-empty(-r) ― 入力が空のときコマンドを実行しない
find . -name "*.tmp" | xargs -r rm
# .tmp ファイルが0件でも rm を呼び出さない
GNU xargs では -r でも指定できます(BSD/macOS では --no-run-if-empty)。
実践例
特定のパターンを含むファイルを grep で絞り込んで削除
grep -rl "TODO" ./src | xargs rm
# src 以下で "TODO" を含むファイルを全削除(注意して使うこと)
圧縮されたログファイルをまとめて展開
find /var/log -name "*.gz" | xargs -P 2 gunzip
全 .py ファイルに flake8 を実行
find . -name "*.py" | xargs flake8
ファイルの所有者をまとめて変更
find /var/www/html -name "*.php" | xargs chown www-data:www-data
練習問題
問題 1
カレントディレクトリ以下にある .bak ファイルをすべて削除するコマンドを書いてください。
ファイル名にスペースが含まれる可能性があるため、null 区切りで安全に処理してください。
問題 2
/var/log 以下の .log ファイルの 行数 をまとめて表示するコマンドを書いてください。
ヒント:wc -l と組み合わせます。
問題 3
カレントディレクトリ以下の .txt ファイルに対して、sed -i 's/foo/bar/g' をまとめて実行するコマンドを書いてください。
一度に 1 ファイルずつ処理してください(-I オプションを使います)。
答え
答え 1
find . -name "*.bak" -print0 | xargs -0 rm
答え 2
find /var/log -name "*.log" | xargs wc -l
答え 3
find . -name "*.txt" | xargs -I {} sed -i 's/foo/bar/g' {}
-I {} を指定することで、各ファイルパスが {} の位置に展開され、1 ファイルずつ sed が実行されます。