LoginSignup
75
63

More than 5 years have passed since last update.

[Bash] ダブルクォートで囲んだコマンド置換中で!を使うとエラーになる(例: echo "$(echo '!')")

Posted at

こうなる。

$ echo "$(echo '!')"
bash: !': event not found

何が起きているのか

Bashにはヒストリ展開という機能がある。
この機能によって、コマンド中の!fooのような文字列は、実行したことのあるfooから始まるコマンドの中で最も最近のものに置き換えられる。
例えば、以下は!ttから始まる最近のコマンドに置き換えられ、!ccから始まる最近のコマンドに置き換えられた例。

$ touch empty.txt
$ cat empty.txt
$ echo !t !c
echo touch empty.txt cat empty.txt
touch empty.txt cat empty.txt

実はこのヒストリ展開は非常に早いタイミングで行われる。
つまり、ダブルクォーテーションで囲まれた文字列では、コマンド置換よりも先にヒストリ展開が発生してしまう。

echo "$(echo '!')"

この場合、Bashはなんと!'の部分を、実行したことのある'から始まるコマンドの中で最も最近のものに置き換えようとする。
そのようなコマンドは今までに実行したことがないのでエラーになる。

対処法

  1. ヒストリ展開を一時的に無効化する
$ set +H
$ echo "$(echo '!')"
!
$ set -H

‌2. zshを使う

% echo "$(echo '!')"
!

おまけ: 詳しい挙動(の仮説)

Bashの処理の流れは厳密にはおそらく1次のようになっている。
‌1. |&;()<>、スペース、タブで入力行を区切ってトークンのリストを作る。
ただし'"でクォートされている部分は一塊のトークンになる。

echo "$(echo '!t ')"

echo, , "$(echo '!t ')"

echo $(echo "!t")

echo, , $, (, echo, , "!t", )

‌2. 'でクォートされたトークンを除き、トークンの文字列中からヒストリ展開可能な部分文字列をヒストリ展開する。

echo, , "$(echo '!t ')"

echo, , "$(echo 'touch empty.txt ')"

echo, , $, (, echo, , "!t", )

echo, , $, (, echo, , "touch empty.txt", )

‌3. コマンド置換などの通常の置換を実行する。

echo, , "$(echo 'touch empty.txt ')"

echo, , 'touch empty.txt '

echo, , $, (, echo, , "touch empty.txt", )

echo, , 'touch', , 'empty.txt'

なので、以下の例の場合はヒストリ展開が起こらない。

echo '!'
echo $(echo '!')

以下の例でも、!だけではヒストリ展開可能な文字列にならないので、ヒストリ展開が起こらない。

echo !
echo "!"
echo $(echo "!")

以下の例の場合はヒストリ展開が起こる。

echo !t            # !tで展開
echo "!t"          # !tで展開
echo $(echo "!t")  # !tで展開
echo "$(echo "!")" # !"で展開
echo "$(echo '!')" # !'で展開

参考リンク

bash - How to escape history expansion exclamation mark ! inside a double quoted " command substitution like "$(echo '!b')"? - Stack Overflow


  1. 詳しく解説したページが見つからなかったので挙動から逆算したが、多分不正確な説明になっていると思う 

75
63
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
75
63