I'll attempt a summary of existing answers to similar questions:
! is expanded by bash as part of the history expansion feature, which is by default on in interactive shells, but off by default in scripts (non-interactive shells).
- Specifically,
! together with the following characters is interpreted as an expression that recalls a previous command; e.g., !! recalls the most recently executed command, and !l recalls the most recently executed command starting with l.
An easy way to avoid expansion of ! altogether is to turn history expansion off, which is advisable, given that the feature can be disruptive and given that the readline library's features, which were introduced later, are a superior replacement.
- To turn history expansion off, add
set +H (or set +o histexpand) to your ~/.bashrc file (or ~/.bash_profile file on OS X) - this is recommended in the highest-voted answer to "-bash: !": event not found"
- An alternative, though probably ill-advised, is to choose an alternative character for history expansion, by setting the special
histchars shell variable to the desired character.
With history expansion ON, ! is expanded:
always:
- in unquoted strings:
echo hi!
- in double-quoted strings, irrespective of embedded command substitutions:
echo "hi!"
- Note that this means that
! is even expanded inside a single-quoted string inside a command substitution embedded in a double-quoted string: apparently, history expansion occurs very early during parsing, before the internals of the double-quoted strings are parsed to recognize embedded command substitutions; e.g.:
echo "$(echo 'hi!')" # '!' is still expanded(!) - this is the gist of question "How to escape history expansion exclamation mark ! inside a double quoted " command substitution like "$(echo '!b')"?"
- Strictly speaking,
! parsing doesn't even respect double-quotes per se, and considers any run of non-whitespace characters following ! the history-expansion argument, including "; for instance, echo foo!" - despite imbalanced double-quotes - is considered a valid command, as is echo foo!bar"baz.
never:
- in single-quoted strings:
echo 'hi!'