Bash – Properly Escape Exclamation Points in Bash

bashcommand historyquoting

Today, I was caught redhanded while attempting to code golf a password generator for Twitter.

import string as s,random;print ''.join(random.sample(s.letters+s.digits+s.punctuation,9))

90 chars. Since that is a lot of spare space, I decided to raise the bar and make it executable too.

echo -e "#!/usr/bin/python\nimport string as s,random;print ''.join(random.sample(s.letters+s.digits+s.punctuation,9))">pg;chmod +x pg;./pg

139 chars. Nice, except obviously bash chokes on the exclamation point.

badp@delta:~$ echo -e "#!/usr/bin/python\nimport string as s,random;print ''.join(random.sample(s.letters+s.digits+s.punctuation,9))">pg;chmod +x pg;./pg
bash: !/usr/bin/python\nimport: event not found

Pesky exclamation point. "Let's escape it," I thought! I do have one spare character after all.

echo -e "#\!/usr/bin/python\nimport string as s,random;print ''.join(random.sample(s.letters+s.digits+s.punctuation,9))">pg;chmod +x pg;./pg

Obviously…

badp@delta:~$ echo -e "#\!/usr/bin/python\nimport string as s,random;print ''.join(random.sample(s.letters+s.digits+s.punctuation,9))">pg;chmod +x pg;./pg
./pg: line 2: syntax error near unexpected token `('
./pg: line 2: `import string as s,random;print ''.join(random.sample(s.letters+s.digits+s.punctuation,9))'
badp@delta:~$ cat pg
#\!/usr/bin/python
import string as s,random;print ''.join(random.sample(s.letters+s.digits+s.punctuation,9))

Leaving my asinine code golfing aside — I can't explain this.

With \!, the exclamation point was escaped, except it really wasn't, because the \! was left as-is for echo to pick up.

One solution could have been using \x21 instead, but I'm not convinced that's the proper way of escaping an exclamation point in a bash command.

tl;dr: How do you properly escape an exclamation point in a bash command?

Best Answer

Use single quotes:

echo -e '#!/usr/bin/python\nimport string as s,random;print "".join(random.sample(s.letters+s.digits+s.punctuation,9))'>pg;chmod +x pg;./pg

The rules for ! were sort of grafted onto the other quoting rules afterwards (from csh). They were very useful back when shells didn't have command line editing, but some people still use them now.

P.S. Since you're coding for bash:

echo $'#!/usr/bin/python\nimport string as s,random;print"".join(random.sample(s.letters+s.digits+s.punctuation,9))'>pg;chmod +x pg;./pg

This works on most unices:

echo python -c \''import string as s,random;print"".join(random.sample(s.letters+s.digits+s.punctuation,9))'\'>pg;chmod +x pg;./pg

(Not that I understand why you want to create a script or why the script name has to be two letters.)