findとxargsは、手作業ならうんざりするような作業であっても、コマンドライン一発で済ませられる可能性を秘めている。 このエントリーではfindとxargsの基本的な使い方をtipsとしてまとめる。
findによる検索
findはあるディレクトリ以下の条件にマッチしたパスを検索し一覧を出力するコマンドだ。 最初の引数には検索対象のパスを与える。以下の例ではカレントディレクトリ以下のファイルとディレクトリが一覧される。
find .
findに条件を与えれば、条件にマッチしたパスのみ出力させることができる。
条件には色々書くことができるが、一番良く使われるのは-name
による名前の指定だろう。 名前の指定にはワイルドカードを使う。以下の例は.rbで終わるパスのみを出力させる。
find . -name "*.rb"
名前の指定に正規表現を使いたい場合は-regex
オプションだ。
find . -regex ".*\\.rb$"
ちなみに-name
と-regex
には、大文字と小文字の違いを無視するそれぞれ-iname
と-iregex
も用意されている。
条件は複数指定できる。 ディレクトリを除きファイルのみマッチさせる-type f
を追加してみよう。
find . -name "*.rb" -type f
複数の条件を指定した場合は、すべての条件を満たしたパスのみ出力される。これはAND検索と言える。 条件のうち、いずれかを満たしたパスを出力させるOR検索には-or
を使う。
find . -name "*.rb" -or -name "*.py"
findによるコマンド実行
検索されたパスを眺めるだけでもfindは十分に便利だが、findの真髄はマッチしたパスに対するコマンドの実行にある。
マッチしたパスにコマンドを実行するには-exec
オプションを使う。 chmodでパーミッションをすべて600にして、所有者だけが読み書きできるようにしてみよう。
find . -name "*.rb" -type f -exec chmod 600 {} \;
{}はマッチしたパスに置換されるプレースホルダーの役目をする記号で、マッチしたパスの回数だけchmodコマンドが実行される。 末尾の\によるエスケープが煩わしいが、;がシェルに解釈されないようにするために必要だ。
findとxargs
xargsは標準入力から一覧を受け取って、それを引数に任意のコマンドを実行するコマンドだ。良くfindとセットで使われる。 コマンドを実行するのはfind -exec
と同様だが、一番の違いはxargsは複数行をまとめてコマンドに渡してくれる点だろう。
例えばカレントディレクトリ以下の.rbファイルの行数を、wc -l
を使ってカウントする場合を考えてみよう。
find . -name "*.rb" -type f -exec wc -l \{\} \;
もしカレントディレクトリにfoo.rb、bar.rb、buz.rbがあったとして、このfind -exec
では以下のように3回コマンドが実行される。
wc -l foo.rb
wc -l bar.rb
wc -l buz.rb
これをxargsで書き換えてみよう。 通常は以下のようにfindの出力をxargsにパイプで渡す。
find . -name "*.rb" -type f | xargs wc -l
xargsには以下の3行が標準入力で渡される。
foo.rb
bar.rb
buz.rb
xargsはこの3行を解釈して、wc -l
を1回だけ実行する。 実行されるコマンドは以下のとおりだ。
wc -l foo.rb bar.rb buz.rb
ちなみにxargsでも-I
でプレースホルダーを指定して、任意の位置にパスを展開させることができる。 ただし、その場合はコマンドは1行毎に実行される。
find . -name "*.rb" -type f | xargs -I {} echo Hello, {} !
Hello, foo.rb !
Hello, bar.rb !
Hello, buz.rb !
Null文字によるセパレート
xargsはデフォルトでホワイトスペースを区切りとみなして動作するが、この挙動はパス中にスペースや改行が入っている場合に問題になる。 そのため、find -print0
とxargs -0
を組み合わせて、findの出力とxargsの区切り文字をNull文字にする事が多い。
find . -name "*.rb" -type f -print0 | xargs -0 wc -l
引数の上限数の制御
xargsでひとまとめに実行する引数の数は-n
オプションで変更できる。 たとえば以下の例ではechoに最大2つまで渡される。
find . -name "*.rb" -type f -print0 | xargs -0 -n 2 echo
foo.rb bar.rb
buz.rb
マルチプロセス実行
これまでxargsの例をいくつかあげてきたが、どれも1プロセスで実行していた。 xargsでは-P
オプションでコマンドを複数プロセスに分けて、並列に実行させることもできる。 マルチコア環境でのffmpegによるエンコードなど、並列化して早くなる見込みがあれば使ってみるのも良いだろう。
find . -name "*.mpg" -type f -print0 | xargs -0 -I {} -n 1 -P 4 ffmpeg -i {} {}.mp4