Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

“`〜`”と“$(〜)”の挙動の違いまとめ

More than 3 years have passed since last update.

``”と“$()”は、単純には置き換えられない!

``”と“$()”は、どちらも中の文字列をコマンドとして解釈し、その実行結果を返す文法であるが、必ずしも素直に置き換えることはできない。

その例をまとめることにした。

$()”は素直に入れ子にできるが、“``”は酷く複雑になる

例えば、“$()”の中に“$()”を用いて、次のようなコマンドを書いても正しく動作するが、

$ $(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が表示されないのである。

これは悪条件が重なると起こるようだ。→参考

だが、発症する条件は推測に過ぎないのでやはり、変数“$$”を中で使いたい場合は“$()”ではなく、“``”を使う方がよい。

richmikan@github
困った時はすぐQiitaを始めとしたTipsを表面的に鵜呑みにし、使えそうなプロダクトを拾ってきてマニュアル通りに対応するプロダクト至上主義者達よ。そんなことでは、想定外の事態に見舞われた時すぐ死ぬぞ。想定外とは、マニュアルには載ってないから想定外なのだ。マニュアル通りにしか動けぬ者は、典型的なコンビニ店員の如く薄給に喘ぐだけ。頭を使え!UNIX哲学に目覚めよ!そしてPOSIX原理主義を崇拝せよ!
https://github.com/ShellShoccar-jpn
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away