GNU版, BSD版 xargsの挙動の違い

細いところを見ていくと膨大な違いがあるんでしょうけど,
ハマりやすそうなところだけ.


GNU版は Linuxディストリビューションにインストールされているもので,
BSD版は MacOSX, 各種 BSDディストリビューションにインストールされて
いるものとしています.

入力に非空白文字が含まれない場合の挙動

GNU版は最低 1回はコマンドが実行されますが, BSD版は入力に非空白文字が
なければコマンドが実行されません.


例えば以下のコマンドを実行したとき,

% perl -wl -e 'print "\n" for 1..100 | xargs ls'

GNU版では lsが 1回実行されますが, BSD版では何も出力されません.

--no-run-if-empty, -rオプション

GNU版 xargsには, --no-run-if-empty(-r)オプションがあり, これを
指定することで, BSD版と同じ挙動となります. FreeBSD, NetBSD, OpenBSD
xargsでは dummyオプションとして, -rを実装しているのですが, Mac
xargsでは -rオプションを実装していないので, -rをつけておけばポータブルに
なるというわけではありません.


なので, 各種 OSで動かす場合以下のようなことが必要に
なるかと思います.

#!/bin/sh

set -e

OS=$(uname)
EMPTY_OPTION=
if [ "x$OS" = "xDarwin" ]; then
    EMPTY_OPTION=''
else
    EMPTY_OPTION='-r'
fi

command1 | xargs $EMPTY_OPTION command2

STDINの /dev/ttyとして再オープン

BSD版 xargsの '-o'オプションなのですが, GNU版にはそのオプションは
実装されていません.

問題となる場面
 % echo -n somehost | xargs ssh

とすると

Pseudo-terminal will not be allocated because stdin is not a terminal.

のようなエラーが出て sshできません. このような場合 BSD版では

 % echo -n somehost | xargs -o ssh

とすると STDINの再オープンが行われて sshが問題なく行えるように
なります.

GNU版での実現

若干長くなるのですが, 以下で実現できます.

 % echo -n somehost | xargs sh -c 'ssh "$@" </dev/tty' ssh # 最後の sshは何でもよい


BSD版でもこれで動きますので, ポータブルにしたい場合は
こちらの書き方の方が良いかと思います.

おわりに

ポータビリティを保つ場合, xargsは注意して利用する必要がある
ことがわかりました.