テキスト処理 中級

xargs コマンド入門 ― パイプの出力を引数に変換する

xargs を使って、パイプやファイル一覧をコマンドの引数として渡す方法を学ぶ。find との組み合わせや並列実行まで丁寧に解説。

xargs は「パイプで受け取った文字列」をコマンドの 引数 として渡すためのコマンドです。 findgrep などと組み合わせることで、複数のファイルに対して同じ操作をまとめて実行できます。

なぜ 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 が実行されます。