findコマンドなどと組み合わせてパイプから受け取る入力を
引数としてコマンドを実行するxargsですが、
GNU版とBSD版の間で違いが結構大きい様です。
ちょっと調べたら色々あるみたいなんですが、 今回ひっかかった特にはまりやすいかな、と思う点についてメモしておきます。
出力が空の場合に一回実行するかどうか
簡単な例として、echoで出力を作ってそれをそのままechoで受け流す様なxargsの使い方を考えます。
$ echo aaa| xargs echo "bbb"
bbb aaa
こんな感じ。パイプの後ろは
$ echo bbb aaa
に置き換わって実行されています。 これはGNUでもBSDでも同じように実行されます。
次に最初の出力を空にしてみます。
MacなどのBSD系では
$ echo | xargs echo "bbb"
$
と何も表示されません。
一方GNU版だと
$ echo | xargs echo "aaa"
aaa
$
と、echoが実行されます。
これは例えばある特定のディレクトリにある特定の名前のファイルを表示したい時、
$ find /path/to/dir/ -maxdepth 1 -mindepth 1 -type f -name "*wanted*" -print0 2>/dev/null|xargs -0 -n1 basename
こんなコマンドをやるとします。
もし何も見つからない場合、BSD版では何も表示されない状態で正常終了します。
$ find /path/to/dir/ -maxdepth 1 -mindepth 1 -type f -name "*wanted*" -print0 2>/dev/null|xargs -0 -n1 basename
$ echo $?
0
$
一方GNU版だと
$ find /path/to/dir/ -maxdepth 1 -mindepth 1 -type f -name "*wanted*" -print0 2>/dev/null|xargs -0 -n1 basename
basename: missing operand
Try `basename --help' for more information.
$ echo $?
123
$
な感じでbasenameが引数なしで一回実行されてしまうのでエラー終了になります。
–no-run-if-empty (-r) オプション
スクリプトを使いまわしたい場合にこれだとちょっと面倒ですが、
GNU版には--no-run-if-emptyというオプションがあり
これを与えるとBSDの様に何もパイプから受け取らない場合にはコマンドを実行しない様になります。
-rというオプションも同様の動作をします。
一方、BSD版の一部のものだと、この-rを擬似オプション(何も変更しないオプション)として導入し、
BSDでもGNUでもxargs -rとすれば同様の動作をする様に出来る事ができます。
ただ、Macに入ってるxargsなどは-rオプションが無いので使うとエラーが出ます。
なのでちゃんと調べて上げる必要があって、
| 1 2 3 4 5 6 7 8 |  | 
みたいに-rが使えるかどうかチェックしたりしてオプションをセットします。
追記: 2017/06/13
-v(変数が定義済みチェック)はBash 4.1などちょっと前のBashだと使えない事があるので
| 1 2 3 4 5 6 7 8 |  | 
みたいな感じで-vを使わずに未定義化どうかチェックした方が良いです。
上でやっているのは
- まず-zで未定義か空であることをチェック。
- そうであった場合、xargsが未定義かどうかをチェック。
${var-X}はvarが未定義である場合に限りXを返します。
上の場合ではxargs_optが未定義であるとAを返すので結果が正になります。
後者だけのチェックだと、もしxargs_optがもともとAという値だった場合にも
間違えて正になってしまうので
最初に未定義か空のチェックでAで無いことを確認し
その後で未定義かどうかのチェックをしています。
${var:-X}の様に:が入ると未定義もしくは空文字の場合にXが返る様になります。
(通常のシェルスクリプトではこちらの方がよく使います。)
追記ここまで
オプションを扱うラッパー関数
xargsを沢山使うような場合は
| 1 2 3 4 5 6 7 8 9 10 11 |  | 
追記: 2017/06/13
上に書いたように最初の分岐を-z...を使った方法に変更。
追記ここまで
みたいなラッパー関数を作ってしまって回すと便利です。
