“`〜`”と“$(〜)”は、単純には置き換えられない!
“`〜`”と“$(〜)”は、どちらも中の文字列をコマンドとして解釈し、その実行結果を返す文法であるが、必ずしも素直に置き換えることはできない。
その例をまとめることにした。
“$(〜)”は素直に入れ子にできるが、“`〜`”は酷く複雑になる
例えば、“$(〜)”の中に“$(〜)”を用いて、次のようなコマンドを書いても正しく動作するが、
$ $(echo $(echo echo x=)1)
x=1
$
単純に$(と)を`に置き換えると……、
$ `echo `echo echo x=`1`
sh: 1: command not found
echo x=
$
というようにして失敗する。“`”は、始まりと終わりが区別できないため、次に出現したら終わりと解釈するしかないからである。
というわけで、入れ子にしたい場合は“`〜`”ではなく“$(〜)”を使うことを勧める。
が、一応POSIX文書には「“`〜`”も入れ子にできる」と明記されている。内側のものをバックスラッシュでエスケープすればいいというのだ。
そこで、例えば二重、三重、四重の入れ子を作ってみると、こんなふうになった。
$ `echo \`echo \\\`echo \\\\\\\`echo echo HOGE\\\\\\\`\\\`\``
HOGE
$
この規則がわかるだろうか?$n$重にしたければ$n$重目のバッククォートは$2^{(n-1)}-1$個のバックスラッシュでエスケープすることになるのだ。いくらできるとはいえ、これはひどい。
“$(〜)”は中のバックスラッシュも特別扱いしないが、“`〜`”はエスケープ文字として解釈する
両者は、その中にバックスラッシュを含む文字列があった場合に挙動がかわる。
まずは“$(〜)”で、試してみる。
$ printf 'printf a\\nb' | $(sed 's/\\/<backslash>/g')
a<backslash>nb$
前半のprintfコマンドではprintf a\nbという文字列を標準出力に送り、後ろのsedコマンドでは\を<backslash>に変換して無効化するというものである。
ところがここで単純に$(と)を`に置き換えると……、
$ printf 'printf a\\nb' | `sed 's/\\/<backslash>/g'`
sed: -e expression #1, char 17: unterminated `s' command
$
というふうにsedコマンドでエラーになってしまう。理由は、`〜`が、sedコマンドの引数にある“\\”を解釈し、“\”に変換してからsedコマンドに引数を渡すことにより、sedコマンドは“'s/\/<backslash>/g'”の“\/”を正規表現の区切り文字ではなく単純なスラッシュと見なしてしまって、誤動作してしまうことによる。
従って、“`〜`”を使う場合は、次のようにし“\”をエスケープして記述すること。
$ printf 'printf a\\nb' | `sed 's/\\\\/<backslash>/g'`
a<backslash>nb$
“`〜`”の中にcase文を書いても害はないが、“$(〜)”の中に書くとエラーになる場合がある
これは使っているシェルがbash(バージョン3)の場合に起こる。
そのような例を書いてbashのバージョン3で実行すると、
$ echo $(case 1 in 1) echo OK;; esac)
-bash: syntax error near unexpected token `;;'
$
このようにエラーになる。これは、case文で条件指定に使う“)”が$(に対応する閉じ括弧であると誤認識されてしまうからだ。
bashバージョン3は、CentOS 5という2017/03までサポートすることが保証されているOSにおいて現役であるので、古いバージョンだからとはいえ無視するわけにはいかない。
従って、case文を中で使いたい場合は“$(〜)”ではなく、“`〜`”を使う方がよい。
“`〜`”の中に$$を書いても害はないが、“$(〜)”の中に書くと無視される場合がある
これも使っているシェルがbashの場合に起こる。しかもこちらは、バージョン4でも起こるのでcase文以上に注意が必要だ。
論より証拠、次のようなコードをbash上で実行すると……、
$ echo $(echo $$'')
$
表示されるはずのプロセスIDが表示されないのである。
これは悪条件が重なると起こるようだ。→参考
だが、発症する条件は推測に過ぎないのでやはり、変数“$$”を中で使いたい場合は“$(〜)”ではなく、“`〜`”を使う方がよい。