xargs が Linux と Mac 両方できちんと動くためには -i ではなく -I オプションを使う

LinuxMac で xargs の挙動が違うのは以前から認識していたが、詳細までは調べていなかった。今回、プライベートの git レポジトリをコミットして Mac で xargs を実行した際に illegail option -- i と怒られてしまったので、これを機会に xargs が LinuxMac 両方できちんと動作するように修正した。

Linux では動く、Mac では動かない

# da = dot apply
alias da="ls -A ~/git/$repo/t/dot/ | xargs -i cp {} $HOME && pushd $HOME && source $HOME/.bash_profile && popd"

Mac 版の xargs (BSD) では -i オプションがサポートされていないのが問題だった。

$ da
xargs: illegal option -- i
usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]
             [-L number] [-n number [-x]] [-P maxprocs] [-s size]
             [utility [argument ...]]

xargs 調査

Linux 版の xargs の man を読むと、そもそも -i オプションはすでに deprecated となっており -I オプションを使うことが推奨されている。単純に -i を -I に変えればいいのかと思ったら、少し事情は異なっていて -I オプションは -i と違ってデフォルトの replace-str に {} を設定してくれないので、明示的に {} を指定する必要がある。

$ man xargs
(.. snip ...)

       -I replace-str
              Replace  occurrences  of replace-str in the initial-arguments with names read from standard input.  Also, unquoted blanks do not terminate input items;
              instead the separator is the newline character.  Implies -x and -L 1.

       -i[replace-str], --replace[=replace-str]
              This option is a synonym for -Ireplace-str if replace-str is specified.  If the replace-str argument is missing, the effect is the same as -I{}.   This
              option is deprecated; use -I instead.

(.. snip ...)

上記を踏まえて以下のように修正した。(๑•̀ㅂ•́)و✧

Linux でも Mac でも動く(replace-str に伝統的(?) に {} を使用)

$ git diff -U0 HEAD~2 HEAD
(... snip ...)
-alias da="ls -A ~/git/$repo/t/dot/ | xargs -i cp {} $HOME && pushd $HOME && source $HOME/.bash_profile && popd"
+alias da="ls -A ~/git/$repo/t/dot/ | xargs -I {} cp {} $HOME && pushd $HOME && source $HOME/.bash_profile && popd"
(.. snip ...)

Linux でも Mac でも動く(replace-str に独自のトークンを使ってみた例)

$ git diff -U0 HEAD~2 HEAD~1
(.. snip ...)
-alias da="ls -A ~/git/$repo/t/dot/ | xargs -i cp {} $HOME && pushd $HOME && source $HOME/.bash_profile && popd"
+alias da="ls -A ~/git/$repo/t/dot/ | xargs -I _file_ cp _file_ $HOME && pushd $HOME && source $HOME/.bash_profile && popd"
(... snip ...)