sed
コマンドには -i
というファイルを書き換える時に便利なオプションがある。
$ cat <<EOF > test.txt
foo
bar
baz
EOF
$ sed -i'.bak' 's/a/A/g' test.txt
$ ls
test.txt
test.txt.bak
# 書き換えられたファイル
$ cat test.txt
foo
bAr
bAz
# 元のファイル
$ cat test.txt.bak
foo
bar
baz
さて、バックアップが欲しい時には拡張子に空文字列を渡したいのだがここでGNUとBSDの互換性の問題が発生する。
Ubuntuだとこういう挙動をする
$ sed -i'' 's/a/A/g' test.txt
$ cat
foo
bAr
bAz
普通のようだ。次にOS Xで実行してみる。
$ sed -i'' 's/a/A/g' test.txt
sed: 1: "test.txt": undefined label 'est.txt'
これは -i
の直後に続いた''
がなかったことにされて -i
として扱われ、's/a/A/g'
が拡張子、 test.txt
がsedコマンドとして解釈されている。
これを避けるには -i
と ''
の間にスペースを開けてあげれば良い。
$ sed -i '' 's/a/A/g' test.txt
ところがUbuntuでスペース付きを実行するとこうなる。
$ sed -i '' 's/a/A/g' test.txt
sed: s/a/A/g を読み込めません: そのようなファイルやディレクトリはありません
Ubuntuのsedは -i
の直後に文字列が続かなかったらそのまま空文字列が渡されたとして解釈し、次の ''
をsedコマンド、 's/a/A/g'
以後をファイル名として解釈しようとする。
まとめると、こうだ。
-i'' |
-i '' |
|
---|---|---|
Ubuntu | 正常 |
'' がsedコマンドに |
OS X |
'' が無視される |
正常 |
空文字列を渡す互換性のある方法がない。
因みに空でない文字列を渡すとこうなる。
-i'str' |
-i 'str' |
|
---|---|---|
Ubuntu | 正常 |
'str' がsedコマンドに |
OS X | 正常 | 正常 |
Ubuntuの方は一貫性があるが、OS Xは何をとち狂った、といった感じだ。
まあ、これは -i''
は見た目上 -i''
に空文字列を渡しているが実際はシェルレベルで -i
と解釈されるので空文字列を渡したのか次に文字列が続くのかコマンドに渡った時には判断出来ないからだ。
BSDツールを作った人が空文字列を特別扱いした訳ではなくて逆に渡される文字列が空の時のエッジケースを考えなかったらこうなったのかもしれない。
GNUツールの方はそこも考慮して作ったのかもしれない。
シェルの非互換問題は根深い。