xargs コマンド 2015/05/13
ファイル名の一覧を標準入力から受け取って、そのファイル一覧を任意のコマンドに引数として渡すコマンド。ファイル一覧でなくてもなんでもよい。
わかりづらいので、例として、カレントディレクトリの配下にある拡張子が .txt
のファイルをすべて削除したいとする。
まずはfind
コマンドでリストアップする。
$ find . -name "*.txt"
この結果が以下のようだったとする。
foo.txt
sub/bar.txt
これらを削除するには
$ rm foo.txt sub/bar.txt
を実行すればよいが、find
の結果が大量にある場合はrm
コマンドを手で書くのは手間なので、
find
の結果をそのままrm
に渡したい。rm
コマンドが標準入力からファイル名を受け取ってくれるならパイプでつなぐだけだが、残念ながらrm
にはパラメータで渡す必要がある。こういうときにxargs
コマンドを使う。以下のような感じ。
$ find . -name "*.txt" | xargs rm
xargs
コマンドは標準入力から受け取ったデータをそのままrm
コマンドのパラメータとしてrm
を実行する。
ちなみに以下のようにしても同じようなことができる。
$ rm `find . -name "*.txt"`
ただし、この場合find
の結果があまりに大量だと、エラーになってしまう。コマンドに渡すパラメータの長さには制限があるからである。制限を超えると自分の環境では Argument list too long
というエラーが表示される。
しかし、xargs
を使えば、この制限を超える場合に、適度に分割して rm
を複数回実行することで自動でこの制限を回避してくれる。
ちなみに、このパラメータの長さの上限値はカーネルに依存し、以下のコマンドでバイト数で確認することができる。
getconf ARG_MAX
この上限値はカーネルソースのexec.c
のあたりに実装されていそうだが、よくわからない。
mv
に渡す場合は要注意
2015/07/14
xargs
は、コマンドの最後にファイル名を引数として渡す。
mv
コマンドはデフォルトでは、パラメータで渡したファイル名たちの最後のものが移動先になる。なので、移動元のたくさんのファイル名をxargs
に渡してmv
で移動させるには -t
オプションを付けて移動先を明示的に指定する必要がある。
find . -type f | xargs mv -t ~/dst
これで mv
コマンドは mv -t ~/dst a.txt b.txt dir/c.txt dir/d.txt
として実行され、
~/dst
にテキストファイルたちが移動されることになる。 -t
がないと、
mv ~/dst a.txt b.txt dir/c.txt dir/d.txt
となり、意図した動作にはならずに dir/d.txt
に上書き移動しようとしてしまう。
ただし、-t
オプションは環境によってはサポートされていないらしい。
別の使い方として、xargs
から移動元ファイル名と移動先ファイル名をペアでmv
に渡すやりかたもある。
こんな感じ。
find . -type f | perl -nle 'print "$_\n$_.bak"' | xargs -n2 mv
見つかったファイル一覧を、perlのワンライナーでもとのファイル名とそれに .bak
を付けたファイル名のペアにして、
xargs
は -n2
を付けることで2つずつ mv
に渡して、各ファイルをリネームする。
一度にコマンドに渡す引数の最大数を制御するには 2014/01/05
-n
オプションを使って最大数を設定できる。
例えば、-n 3
とすると、コマンドに最大で3つまでしか引数を渡さず、
4つ以上ある場合はコマンドを複数回実行される。
ディレクトリに a.txt
, b.txt
, c.txt
, test.sh
があって、
test.sh
が以下の内容とし、
echo "exec: test.sh"
echo "$@"
以下を実行すると、
ls | xargs sh test.sh
以下のように出力される
exec: test.sh
a.txt b.txt c.txt test.sh
-n
オプションで最大1に設定すると、
ls | xargs -n 1 sh test.sh
以下のように出力される
exec: test.sh
a.txt
exec: test.sh
b.txt
exec: test.sh
c.txt
exec: test.sh
test.sh
-n
の後ろに空白を入れなくてもよい。
ls | xargs -n1 sh test.sh