Bash Error Guide: 'event not found' — Fix History Expansion with '!'
Fix Bash '!: event not found': stop history expansion eating '!' in double-quoted strings by using single quotes, escaping, or disabling histexpand.
- #bash
- #automation
- #troubleshooting
- #errors
Stuck on this Bash & Python Automation error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
Bash prints event not found when history expansion sees a ! it interprets as a reference to a past command that doesn’t exist:
bash: !doctype: event not found
In an interactive shell, ! is the history-expansion character: !! reruns the last command, !42 recalls history entry 42, !ssh recalls the last command starting with ssh. When a ! appears in a double-quoted string followed by non-whitespace, Bash tries to expand it as a history event — and if no match exists, it aborts with event not found before your command ever runs. Single quotes are immune; double quotes are not.
Symptoms
- A command containing
!(a password, a URL fragment, HTML like<!DOCTYPE>, a shell negation in text) fails withevent not found. - It happens at the interactive prompt or in scripts run with history expansion on, but not in plain non-interactive script execution.
echo "hello!world"errors, whileecho 'hello!world'works.- Pasting a strong password containing
!into acurl -uorgitcommand breaks.
Common Root Causes
!inside double quotes followed by a word —"Deploy done!"at end of string is usually fine, but"!important"or"a!b"triggers expansion.- Passwords or tokens containing
!used unquoted or double-quoted on the command line. - HTML/XML with
<!or!DOCTYPEechoed or heredoc’d in double quotes. - History expansion enabled in a script —
set -H(on by default in interactive shells; off in non-interactive, but some invocations enable it). !immediately before{, letters, or digits, which look like history references.
Diagnostic Workflow
Confirm history expansion is the mechanism:
echo "$-" # a 'H' in the flags means histexpand is on
shopt -o -p 2>/dev/null | grep histexpand
Show that quoting changes the behavior:
echo 'value!here' # works: single quotes disable history expansion
echo "value!here" # bash: !here: event not found
For scripts, verify how they’re invoked — history expansion is normally off for bash script.sh, but on in bash -i or interactive sourcing.
Example Root Cause Analysis
An operator ran a one-liner to set a database password:
psql -c "ALTER USER app PASSWORD 'S3cr!tPass';"
It failed instantly:
bash: !tPass': event not found
The ! inside the double-quoted argument was interpreted as a history reference (!tPass), which didn’t exist. The command never reached psql. Because the surrounding quoting was double, history expansion fired first. The fixes:
# Option A: turn off history expansion for this shell
set +H
psql -c "ALTER USER app PASSWORD 'S3cr!tPass';"
# Option B: escape the bang
psql -c "ALTER USER app PASSWORD 'S3cr\!tPass';"
# Option C: keep the whole argument in single quotes where possible
psql -c 'ALTER USER app PASSWORD '"'"'S3cr!tPass'"'"';'
For interactive ops work, set +H is the least error-prone; for scripts, prefer single quotes around any value that may contain !.
Prevention Best Practices
- Use single quotes for strings containing
!— they suppress history expansion entirely. - Disable history expansion when it’s not needed —
set +Hin interactive sessions or at the top of scripts that echo!-heavy text. - Escape the bang —
\!inside double quotes prevents expansion of that character. - Keep secrets out of the command line — read passwords from files, env vars, or prompts (
read -rs) to avoid quoting hazards and shell-history leakage. - In non-interactive scripts, remember history expansion is off by default; the problem is mostly an interactive and
bash -iconcern. - Prefer here-documents with a quoted delimiter (
<<'EOF') for large text blocks containing!.
Quick Command Reference
echo 'text!with!bang' # single quotes: no history expansion
set +H # disable history expansion for this shell
echo "text\!with\!bang" # escape the bang inside double quotes
echo "$-" # check for 'H' flag (histexpand on)
read -rs -p 'Password: ' PW # read secrets without quoting them on the CLI
cat <<'EOF' # quoted delimiter: literal !, no expansion
<!DOCTYPE html>
EOF
Related Guides
- Bash Error Guide: ‘unexpected EOF while looking for matching quote’ — another quoting pitfall with a confusing error.
- Bash Error Guide: ‘bad substitution’ — a different expansion mechanism failing at parse time.
- Bash & Python Error Guide: ‘syntax error near unexpected token’ — related quoting and special-character failures.
Conclusion
event not found is Bash history expansion mistaking a ! in your command for a reference to a past command. It bites interactive ops work with passwords, URLs, and HTML in double quotes. Reach for single quotes around !-bearing values, set +H to disable expansion when you don’t need it, or \! to escape a single bang. Keeping secrets off the command line entirely sidesteps both this error and leaking them into shell history.
Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.