いまさら findと xargsと parallelの便利さを主張してみる

hnwさん、細かいツッコミをいれます。生産性が上がるわけでもないので、あまりしらなくていいことですが。

コマンドを一度に起動する (find -exec +)

$ find . -name \*~ -exec rm {} \;

ただ、これだと100000ファイルがヒットした場合にrmコマンドが100000回実行されてしまうため、全部実行するまでに案外時間がかかる、なんてことがあります。

http://openlab.dino.co.jp/2008/02/20/133431188.html

これを回避するために xargsを使うのもまっとうですが、最近の find には + というのがあります (SUSV3 で定義されていて、 MacOS Xでも GNUでも (多分) BSDでも使えます)。

find . -name \*~ -exec rm {} +

これで、複数の引数つけて rmを呼んでくれます。空白があっても okです。

真ん中に展開したい (xargs -J)

でも mvのように真ん中に展開したい引数がある時はだめ。'{} +' で終わってなきゃいけないってルールなんですね。せこい。

% find . -name \*~ -exec mv {} bak/ +
find: -exec: no terminating ";"

この場合は GNU coreutilsを使わなきゃいけないかというと、それも抜け道があります。
BSD系 xargsには -J optionがあります:

% touch a~ b~ c~
% find . -name \*~ -print0 | xargs -0 -J {} echo mv {} /var/tmp
mv ./a~ ./b~ ./c~ /var/tmp

というように一つのコマンドに展開してくれます。これで GNUよりも BSDが好きな自分のようなやつでも、coreutilsを入れずに幸せになれます *1

このほうが cp とか mv とか、個々の commandに optionつけるよりエレガントじゃん、と思います。

ちなみに BSD系では template変数が2文字以上の時、"-J{}" とくっつけるとだめで "-J {}" と spaceをいれないといけないよう。普段は "-J@" って、一文字にしてるので気づかなかった。

追記: GNUの xargsでも -n をつければ複数展開してくれますね:

% find . -name \*~ -print0 | xargs -0 -I {} -n 100 mv {} /var/tmp

てな感じで。100 ってのが adhocな感じだけど。
MacOSではこの方法は使えませんでした。環境によって方法を選ばないといけないってのは面倒だな。間違いでした。GNUの xargsはそういう動作ないみたいですね..

もっと早くしたい (けど) (parallel)

でも昨今は multicoreや hyperthreadな CPUが主流でしょう。そんなときに一つの threadで順番にやるのはアホです。そういうときに使えるのがparallel です。最大 10個分プロセスを立ち上げて、文字通り並列に CPUを余すことなく使ってくれます。

% ls | ./parallel 'sleep 3 ; echo ' & (sleep 1; ps )
[1] 4503 4504
  PID  TT  STAT      TIME COMMAND
  769  p2  Ss     0:00.22 -bin/tcsh -i
 4504  p2  S      0:00.08 /usr/bin/perl -w ./parallel sleep 3 ; echo 
 4507  p2  S      0:00.01 sh -c sleep 3 ; echo  Makefile
 4508  p2  S      0:00.00 sh -c sleep 3 ; echo  gpl-3.0.txt
 4509  p2  S      0:00.00 sleep 3
 4510  p2  S      0:00.00 sleep 3
 4511  p2  S      0:00.01 sh -c sleep 3 ; echo  parallel
 4512  p2  S      0:00.00 sh -c sleep 3 ; echo  parallel.1
 4513  p2  S      0:00.00 sleep 3
 4514  p2  S      0:00.00 sleep 3
 4515  p2  S      0:00.01 sh -c sleep 3 ; echo  unittest
 4516  p2  S      0:00.00 sleep 3

いや、実のところ一つ一つの引数に対して processを立ち上げるので、全体としてはおそいっす。今まであげてきた rm や mvなどでは恩恵を受けられないでしょう。
マニュアルの例では wgetや convertなど、一つ一つそれなりに時間がかかるもっともな例をあげてます。さもありなん。

まあ、でも multicore時代には、こういう富豪的に CPUを使うような環境が普通になっていくんでしょう。そもそも CUIなんて富豪的じゃないってツッコミは置いといて。

*1:でも viじゃなくて Emacsを常用しちゃってるけどね!