xargsとは、標準入力から受け取った文字列を「コマンドの引数」に組み立てて実行するコマンドです。 find . -name '*.log' -print0 | xargs -0 rm のように、前のコマンドの出力をまとめて別のコマンドに渡すときに使います。パイプ(|)は「標準入力」として渡しますが、rm や ls のように「引数」で受け取るコマンドにはつなげられないため、その橋渡しをするのが xargs です。

この記事では、GNU xargs(findutils 4.9.0)の全オプションを一覧表にし、それぞれの実行結果を載せています。さらに、空白を含むファイル名で引数が分かれてしまう例を再現し、-print0 と -0 で直す手順、-P の並列数を変えたときの処理時間の実測(sleep と gzip)、-I と -n を一緒に指定したときの警告、Argument list too long を xargs で回避する方法、macOS(BSD 版)との違いまで扱います。

xargsのオプション早見表

よく使うのは -0(NUL区切り)、-n(1回に渡す数)、-I(置き換え)、-P(並列)、-r(空なら実行しない)、-t(実行内容を表示)の6つです。 下の表は GNU findutils 4.9.0 で確認した内容です。

オプション 長い形式 働き 使いどころ
-0 --null 入力を NUL 文字(\0)で区切る find -print0 と組み合わせて空白入りファイル名に対応
-d 区切り --delimiter 区切り文字を指定(クォート処理もしない) 改行区切りの一覧(-d '\n')、CSV の1行
-n 数 --max-args 1回のコマンドに渡す引数の最大数 1件ずつ処理(-n1)、2件ずつペア
-L 数 --max-lines 1回に渡す入力の行数 1行を1回のコマンドに
-I 文字列 --replace 入力1行ごとに文字列を置き換えて実行 引数を途中に入れる(cp {} /backup/)
-i --replace -I{} と同じ(非推奨) 既存スクリプトの読み替え
-P 数 --max-procs 同時に実行するプロセス数(0は可能な限り) 並列処理
-r --no-run-if-empty 入力が空ならコマンドを実行しない 対象が0件のとき何もしない
-t --verbose 実行するコマンドを標準エラーに表示 動作確認・ログ
-p --interactive 1回ごとに実行してよいか確認 削除などを慎重に
-a ファイル --arg-file 標準入力の代わりにファイルから読む 一覧ファイルを使う
-s バイト数 --max-chars 1回のコマンドラインの最大長 長さの調整
-x --exit -s を超えたら終了 引数を分割させたくないとき
-E 文字列 --eof その文字列が来たら入力を終える 区切りまでだけ処理
― --process-slot-var=名前 並列時のスロット番号を環境変数に入れる 並列ジョブごとの出力先分け
― --show-limits 引数長の上限を表示 上限の確認
― --open-tty(-o) 子プロセスの標準入力を端末にする vim などを xargs から開く

終了ステータス早見表

終了ステータス 意味(GNU) 実行して確認した例
0 すべて成功 ―
123 どれかのコマンドが 1〜125 で終了 sh -c 'exit 3' → 123
124 コマンドが 255 で終了し、xargs が中断 sh -c 'exit 255' → 124
125 コマンドがシグナルで終了 ―
126 コマンドを実行できない ―
127 コマンドが見つからない nocmd → 127
1 その他のエラー(-x で長さ超過など) -x -s 12 → 1

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「xargsの使い方とオプション一覧|-0・-I・-P並列をコピペ例で解説」

xargsの基本の使い方

xargs は、入力を空白と改行で区切り、できるだけまとめて1回のコマンドに渡します。 コマンドを省略すると echo が使われます。

$ echo a b c d e | xargs -n 2 echo
a b
c d
e
flowchart LR
    A["find / ls / cat の出力"] -->|"標準入力"| B["xargs"]
    B -->|"引数に組み立て"| C["rm a b c ..."]
    B -->|"上限を超えたら分割"| D["rm d e f ..."]
よくある組み合わせ コマンド例
見つけたファイルを削除 find . -name '*.tmp' -print0 | xargs -0 rm -f
見つけたファイルから文字列を探す find . -name '*.py' -print0 | xargs -0 grep -n 'TODO'
一覧ファイルの各行を処理 xargs -a list.txt -n1 echo
1件ずつ別名でコピー ls *.conf | xargs -I{} cp {} {}.bak
並列で圧縮 find . -name '*.log' -print0 | xargs -0 -n1 -P4 gzip

全オプションの実行結果(GNU findutils 4.9.0)

ここからは、各オプションを Ubuntu(bash 5.2.21、GNU xargs 4.9.0)で実行した結果です。

-n:1回に渡す引数の数

$ echo a b c d e | xargs -n 2 echo
a b
c d
e

-n1 にすると1件ずつ実行になります。コマンドが引数を1つしか受け取らない場合や、並列処理(-P)と組み合わせる場合に使います。

-L:1回に渡す行数

$ printf 'a b\nc\nd e f\n' | xargs -L 1 echo
a b
c
d e f

-n は「単語の数」、-L は「行の数」で区切ります。 1行に複数の単語があるとき、行単位で処理したいなら -L 1 を使います。

-I:置き換え文字列(引数を好きな位置に入れる)

$ printf 'a\nb\n' | xargs -I{} echo "file-{}.txt"
file-a.txt
file-b.txt

$ printf 'a\n' | xargs -I{} echo "{} -> {}.bak"
a -> a.bak

-I を使うと入力1行ごとに1回実行され、{} が何度でも置き換わります。 また、-I では1行全体が1つの引数になるので、行の中の空白では分かれません。

$ printf 'a b\nc\n' | xargs -I{} echo "[{}]"
[a b]
[c]

{} の部分は好きな文字列にできます(-I% や -I FILE など)。

-t:実行するコマンドを表示

$ echo a b | xargs -t echo
echo a b
a b

1行目が -t による表示(標準エラー出力)、2行目がコマンドの出力です。 削除などの前に -t で中身を確かめると安心です。

-r:入力が空なら実行しない

$ printf '' | xargs echo "ran:"
ran:
$ printf '' | xargs -r echo "ran-r:"
$ echo "(end)"
(end)

GNU 版は、入力が空でもコマンドを1回実行します。 find ... | xargs rm で対象が0件だと、rm が引数なしで実行されます。

$ find . -name '*.nothing' -print0 | xargs -0 rm
rm: missing operand
Try 'rm --help' for more information.

GNU 版では -r を付けると、対象が0件のときは何もしません。

-d:区切り文字を指定

$ printf 'a,b,c' | xargs -d, -n1 echo
a
b
c

-d を指定すると、クォート(' ")やバックスラッシュを特別扱いしなくなります。 改行区切りの一覧なら -d '\n' で空白入りの名前も1つとして扱えます(後述)。

-a:ファイルから読む

$ printf 'x\ny\n' > list.txt
$ xargs -a list.txt echo
x y

-a を使うと標準入力が空くので、実行するコマンドがキーボード入力を受け取れます。

-s と -x:コマンドラインの長さ

$ echo aaaa bbbb cccc dddd | xargs -s 15 echo
aaaa bbbb
cccc dddd

$ echo aaaa bbbb cccc dddd | xargs -x -s 12 -n 3 echo
xargs: argument list too long
$ echo $?
1

-s はコマンド名を含めた1回の長さの上限です。 -x を付けると、上限を超えたときに分割せずエラーで止まります。

-E:終わりの目印

$ printf 'a\nb\nSTOP\nc\n' | xargs -E STOP echo
a b

STOP の行で読み込みを終えるので、c は渡されません。

-0:NUL区切り

$ printf 'a b\0c\0' | xargs -0 -n1 echo
a b
c

-0 は区切りを NUL 文字だけにします。 ファイル名には NUL 文字を使えないので、どんなファイル名でも正しく1つずつ扱えます。

-p:1回ずつ確認

-p は端末(/dev/tty)から返事を読むため、端末がない環境では次のエラーになります。

xargs: failed to open /dev/tty for reading: No such device or address

script コマンドで疑似端末を用意し、y(実行)と n(実行しない)を順に答えた結果です。

echo a?...     ← y と入力
a
echo b?...     ← n と入力

y と答えた echo a だけが実行されました。

--process-slot-var:並列時のスロット番号

$ seq 4 | xargs -P2 -n1 --process-slot-var=SLOT sh -c 'echo "job $1 slot $SLOT"' _ | sort
job 1 slot 0
job 2 slot 1
job 3 slot 0
job 4 slot 1

並列数が2なら、スロット番号は0か1になります。 ジョブごとに出力ファイルを分けたいときに便利です。

--show-limits:引数長の上限を確認

$ xargs --show-limits < /dev/null
Your environment variables take up 21771 bytes
POSIX upper limit on argument length (this system): 2073333
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2051562
Size of command buffer we are actually using: 131072
Maximum parallelism (--max-procs must be no greater): 2147483647

GNU xargs は既定で 131072 バイト(128KiB)ずつにまとめてコマンドを実行します。 数値は環境変数の量などで変わります。

空白・改行入りのファイル名で起きることと直し方

find | xargs は、ファイル名の空白で引数を区切ってしまいます。 空白を含むファイルを作って確かめました。

mkdir sp2 && cd sp2
echo hi > 'my report.txt'
echo hi > normal.txt

直す前:空白で分かれる

$ find . -name '*.txt' | xargs wc -c
wc: ./my: No such file or directory
wc: report.txt: No such file or directory
3 ./normal.txt
3 total
$ echo $?
123

my report.txt が ./my と report.txt の2つに分かれ、存在しないファイルとして扱われました。 rm で同じことをすると、意図しない名前のファイルが対象になることがあります。

直した後:-print0 と -0

$ find . -name '*.txt' -print0 | xargs -0 wc -c
3 ./my report.txt
3 ./normal.txt
6 total
$ echo $?
0

find -print0 は名前を NUL 文字で区切って出力し、xargs -0 は NUL 文字で区切って受け取ります。 この組み合わせなら、空白・改行・クォートのどれを含んでも1つの名前として扱えます。

シングルクォート入りの名前

it's.txt のようにクォートを含む名前があると、GNU xargs は処理全体を止めます。

$ find . -name '*.txt' | xargs ls -l
xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option

エラー文のとおり、-0 を使うとクォートは特別扱いされません。

改行入りの名前と -d '\n' の限界

-d '\n' は空白入りの名前には対応できますが、改行を含む名前では分かれます。

$ find . -name '*.txt' | xargs -d '\n' wc -c
0 ./it's.txt
0 ./my report.txt
0 ./normal.txt
wc: ./new: No such file or directory
wc: line.txt: No such file or directory
0 total
$ find . -name '*.txt' -print0 | xargs -0 wc -c
0 ./it's.txt
0 ./my report.txt
0 ./normal.txt
0 './new'$'\n''line.txt'
0 total
入力の作り方 xargs側 空白 クォート 改行
find xargs 分かれる エラーで停止 分かれる
find / ls xargs -d '\n' 対応 対応 分かれる
find -print0 xargs -0 対応 対応 対応
printf '%s\0' * xargs -0 対応 対応 対応

ファイル名を渡すときは -print0 と -0 の組み合わせを基本にすると、名前の形を気にせず使えます。grep -l は -Z(--null)、git ls-files は -z で NUL 区切りの出力ができます。

-P並列の処理時間を実測する

-P 数 で同時に実行するプロセス数を指定できます。 並列にするときは -n1 などで1回あたりの量を決めておくと、仕事が均等に分かれます。CPU 2コア(nproc が 2)の環境で測りました。

sleep(待ち時間が中心の処理)

for p in 1 2 4 8; do
  s=$(date +%s.%N)
  seq 8 | xargs -n1 -P$p sh -c 'sleep 1' _
  e=$(date +%s.%N)
  printf 'sleep -P%s: %.2fs\n' $p $(echo "$e - $s" | bc)
done
sleep -P1: 8.03s
sleep -P2: 4.02s
sleep -P4: 2.02s
sleep -P8: 1.03s

gzip(CPU を使う処理)

約39MBのテキストファイル8個(合計310MB)を gzip -k で圧縮しました。

ls gz/*.txt | xargs -n1 -P$p gzip -k
gzip -P1: 16.82s
gzip -P2: 9.42s
gzip -P4: 12.11s
gzip -P8: 13.11s
gzip -P0: 10.29s
処理の種類 並列数を増やしたとき 並列数の目安
待ちが中心(sleep、ネットワーク応答) 並列数に比例して短くなった(8→1秒) 相手側の負荷を見て決める
CPU が中心(gzip、画像変換) コア数(2)で最短、それ以上は短くならなかった -P "$(nproc)"
ディスク I/O が中心 ディスクの性能で頭打ち 2〜4 から試す

CPU を使う処理では、コア数を超えて並列にしても速くならず、切り替えの分だけ時間が延びることもあります。 -P "$(nproc)" を基本にし、実際に測って調整するのが確実です。-P0 は「できるだけ多く同時に実行」という意味です。

並列時の出力順

$ seq 4 | xargs -P2 -I{} sh -c 'sleep 0.$((5-{})); echo done {}'
done 2
done 1
done 4
done 3

並列にすると、終わった順に出力されるので入力の順番とは変わります。 順番が必要なら、各ジョブの結果をファイルに書いてから最後に並べ替えます。

-Iと-nを一緒に指定したときの挙動

GNU xargs では -I と -n(または -L)は同時に使えず、後に書いた方が有効になります。 警告文も出ます。

$ printf 'a\nb\nc\n' | xargs -I{} -n 2 echo "[{}]"
xargs: warning: options --replace and --max-args/-n are mutually exclusive, ignoring previous --replace value
[{}] a b
[{}] c

$ printf 'a\nb\nc\n' | xargs -n 2 -I{} echo "[{}]"
xargs: warning: options --max-args and --replace/-I/-i are mutually exclusive, ignoring previous --max-args value
[a]
[b]
[c]

$ printf 'a\nb\nc\n' | xargs -L1 -n2 echo
xargs: warning: options --max-lines and --max-args/-n are mutually exclusive, ignoring previous --max-lines value
a b
c

1つ目は -I が無視され、{} がそのまま文字として出力されました。 2件ずつ渡しつつ途中に差し込みたい場合は、sh -c で位置引数として受け取ります。

$ printf 'a\nb\nc\nd\n' | xargs -n2 sh -c 'echo "from=$1 to=$2"' _
from=a to=b
from=c to=d

sh -c '...' _ の _ は $0 の位置を埋めるためのもので、続く引数が $1 $2 に入ります。{} を sh -c の文字列の中に直接埋め込むより、位置引数で渡す方がファイル名の記号に左右されません。

$ printf 'x.txt\ny.txt\n' | xargs -n1 sh -c 'echo "name=${1%.txt}"' _
name=x
name=y

Argument list too long を xargs で回避する

ls file_* のようにシェルのワイルドカードで大量のファイルを渡すと、OS の引数長の上限(ARG_MAX)を超えてエラーになります。 10万個のファイルで再現しました。

$ getconf ARG_MAX
2097152
$ mkdir many && cd many
$ seq -f 'file_%06g_with_a_long_name_for_test.txt' 1 100000 | xargs touch
$ ls | wc -l
100000
$ /bin/ls file_* > /dev/null
/bin/bash: line 9: /bin/ls: Argument list too long

xargs は上限に収まるように自動で分けて実行するので、同じ処理が通ります。printf は bash の組み込みコマンドなので上限の対象になりません。

$ printf '%s\0' file_* | xargs -0 ls | wc -l
100000
$ printf '%s\0' file_* | xargs -0 -t ls 2>&1 >/dev/null | wc -l
33

-t の表示行を数えると、33回に分けて ls が実行されたことがわかります。 1回あたりはおよそ 128KiB(--show-limits の値)です。

方法 上限への対応 備考
cmd file_* 超えるとエラー ファイルが少ないときだけ
printf '%s\0' file_* | xargs -0 cmd 自動で分割 bash 組み込みの printf を使う
find . -name 'file_*' -print0 | xargs -0 cmd 自動で分割 サブディレクトリも対象
find . -name 'file_*' -exec cmd {} + 自動で分割 xargs 不要

find -exec + との比較

find だけで完結するなら -exec cmd {} +、並列や件数の調整が必要なら xargs を選びます。find 側の詳しい使い方は「findコマンドの使い方」記事にまとめています。

$ find . -name '*.txt' -exec wc -c {} +
3 ./my report.txt
3 ./normal.txt
6 total
項目 find -exec cmd {} \; find -exec cmd {} + find -print0 | xargs -0 cmd
実行回数 1件ごと まとめて まとめて
空白入りファイル名 対応 対応 対応
並列実行 できない できない -P でできる
1回あたりの件数指定 ― できない -n でできる
find 以外の入力 ― ― 使える(grep -lZ、一覧ファイル)
0件のとき 実行しない 実行しない GNU は -r が必要

macOS(BSD版)xargsとの違い

macOS の xargs は FreeBSD 由来の BSD 版で、GNU 版とはオプションの一部が異なります。 この章は実行しておらず、FreeBSD の公式マニュアル(man xargs(1))で確認した内容です。macOS では man xargs で手元の版を確認してください。

項目 GNU(Linux) BSD(FreeBSD・macOS)
入力が空のとき コマンドを1回実行する 実行しない
-r 空なら実行しない GNU 互換のために受け付けるが、何もしない(既定で実行しないため)
-d 区切り あり なし(tr '\n' '\0' | xargs -0 で代用)
-a ファイル あり なし(xargs < file で代用)
-0 -n -L -P -t -p -s -x -E あり あり
-I の置き換え 制限なし -R(置き換える引数の数)、-S(置き換え後の長さ、既定255バイト)で制限
-J 文字列 なし あり(まとめた引数全体を1か所に差し込む)
-o --open-tty としてあり あり(子の標準入力を /dev/tty に)
-P 0 できるだけ多く できるだけ多く
--show-limits などの長い形式 あり なし
終了ステータス 123/124/125/126/127 見つからない 127、実行できない 126、その他 1

macOS と Linux の両方で動かすなら、-0・-n・-I・-P だけを使い、-d と -a を避けると共通で書けます。GNU 版を使いたい場合は、Homebrew の findutils を入れると gxargs として使えます。

コピペで使えるxargsの定番例

# 7日より古いログを削除(0件なら何もしない)
find /var/log/myapp -name '*.log' -mtime +7 -print0 | xargs -0 -r rm -f

# Pythonファイルから TODO を探す
find . -name '*.py' -print0 | xargs -0 grep -n 'TODO'

# 画像を CPU コア数で並列変換(名前は位置引数で渡す)
find . -name '*.png' -print0 | xargs -0 -n1 -P"$(nproc)" sh -c 'cwebp -quiet "$1" -o "${1%.png}.webp"' _

# 一覧ファイルの URL を4並列で取得
xargs -a urls.txt -n1 -P4 curl -sSO

# 実行内容を確認してから削除
find . -name '*.tmp' -print0 | xargs -0 -t -r rm -f

# grep で見つかったファイルだけ置換(NUL 区切り)
grep -rlZ 'old_name' src/ | xargs -0 -r sed -i 's/old_name/new_name/g'

find・grep・sed の例は動作を確認済みです。cwebp と curl の例は書き方の見本で、この記事の検証では実行していません。

xargsを使うときのチェックリスト

チェック項目 理由
ファイル名は -print0 と -0 で渡しているか 空白・改行・クォートに対応
0件の可能性があるなら -r を付けたか(GNU) 引数なし実行を防ぐ
削除の前に -t や echo で確認したか 対象を目で確かめる
-I と -n を同時に書いていないか 片方が無視される
-P の数はコア数や相手の負荷に合っているか 増やしすぎても速くならない
macOS でも動かすなら -d -a を避けたか BSD 版にない

よくある質問

Q. xargs と パイプ(|)だけの違いは何ですか? A. パイプは「標準入力」として渡し、xargs は「コマンドライン引数」に変えて渡します。 grep のように標準入力を読めるコマンドはパイプだけで足りますが、rm cp mkdir のように引数で対象を受け取るコマンドには xargs が必要です。

Q. {} を使うには -I が必須ですか? A. はい、{} は -I{} を指定したときだけ置き換わります。 -I を付けないと {} はただの文字列として渡されます。なお find -exec の {} は find 側の機能です。

Q. -P の数はいくつにすればよいですか? A. CPU を使う処理は nproc の値(コア数)が目安です。 今回の実測でも 2コアで -P2 が最短でした。待ち時間が中心の処理は並列数に比例して速くなるので、相手側の負荷を見ながら決めます。

Q. macOS で xargs -d が使えません。 A. BSD 版には -d がありません。 改行区切りなら tr '\n' '\0' < list.txt | xargs -0 cmd と NUL 区切りに変換してから渡すと、同じことができます。

まとめ

  • xargs は標準入力をコマンドの引数に組み立てて実行する。引数で受け取るコマンド(rm、cp など)とパイプをつなぐ
  • ファイル名は find -print0 | xargs -0 で渡すと、空白・改行・クォートを含む名前も1つとして扱える
  • -n は単語数、-L は行数、-I{} は1行ずつ置き換え。-I と -n は同時に使えず、後に書いた方が有効になる
  • -P は待ちが中心の処理なら並列数に比例して速く、CPU 処理はコア数で頭打ち(実測:sleep 8→1秒、gzip は -P2 が最短)
  • Argument list too long は xargs が自動で分けて回避できる(10万ファイルを33回に分けて実行)
  • GNU 版は空入力でも1回実行するので -r を付ける。BSD 版は既定で実行せず、-d と -a がない

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「xargsの使い方とオプション一覧|-0・-I・-P並列をコピペ例で解説」

株式会社RJCでは、xargsのようなコマンドを組み合わせる工夫を、仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?