rcmdnk's blog
Last update

Xargs

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
if [ ! -v xargs_opt ];then
  if echo|xargs -r >/dev/null 2>&1;then
    xargs_opt="-r"
  else
    xargs_opt=""
  fi
fi
find /path/to/dir/ -maxdepth 1 -mindepth 1 -type f -name "*wanted*" -print0 2>/dev/null|xargs $xargs_opt -0 -n1 basename

みたいに-rが使えるかどうかチェックしたりしてオプションをセットします。

追記: 2017/06/13

-v(変数が定義済みチェック)はBash 4.1などちょっと前のBashだと使えない事があるので

1
2
3
4
5
6
7
8
if [ -z "$xargs_opt" ] && [ "${xargs_opt-A}" = "A" ];then
  if echo|xargs -r >/dev/null 2>&1;then
    xargs_opt="-r"
  else
    xargs_opt=""
  fi
fi
find /path/to/dir/ -maxdepth 1 -mindepth 1 -type f -name "*wanted*" -print0 2>/dev/null|xargs $xargs_opt -0 -n1 basename

みたいな感じで-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
function xargs_wrapper {
  if [ -z "$xargs_opt" ] && [ "${xargs_opt-A}" = "A" ];then
    if echo|xargs -r >/dev/null 2>&1;then
      xargs_opt="-r"
    else
      xargs_opt=""
    fi
  fi
  xargs "$@"
}
find /path/to/dir/ -maxdepth 1 -mindepth 1 -type f -name "*wanted*" -print0 2>/dev/null|xargs_wrapper -0 -n1 basename

追記: 2017/06/13

上に書いたように最初の分岐を-z...を使った方法に変更。

追記ここまで

みたいなラッパー関数を作ってしまって回すと便利です。

Sponsored Links
Sponsored Links

« 飛行機にモバイルバッテリーを持ち込む祭の注意点 Firefox 54 リリース: タブの動作が安定した? »

}