Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I realize that ! has special significance on the commandline in the context of the commandline history, but aside from that, in a runing script the exclamation mark can sometimes cause a parsing error.
I think it has something to do with an event, but I have no idea what an event is or what it does.  Even so, the same command can behave differently in different situations.
The last example, below, causes an error; but why, when the same code worked outside of the command substitution? .. using GNU bash 4.1.5

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found
share|improve this question
1  
    
@Warren.. Thanks. I had seen that QA, but it really only talks about how to escape the backslash... My issue relates more to why the seemingly already escaped code works in one situation and not another... –  Peter.O Aug 23 '11 at 12:51
    
@fred: "seemingly already escaped"? I don't see any escapes at all and you are using double quotes. See my (revised) answer. What part do you think is escaped? –  Caleb Aug 23 '11 at 13:07
    
@Caleb. Yes, I used the wrong term.. protected would have been more appropriate. (protected by 'single quotes') –  Peter.O Aug 23 '11 at 13:17
    
If you are only concerned with simple assignments, then you can use var=$(…) (no double quotes), and it will work like (I think) you expect. This is still “safe” because the value part of a simple assignment is not subject to word splitting or globbing (though this may not be true of assignments done through builtins (e.g. export, local, etc.) under all shells). Unfortunately, this does not extend beyond simple assignments since the double quotes are the way to protect against word splitting and globbing while still getting other types of expansion in other contexts. –  Chris Johnsen Aug 24 '11 at 3:37
add comment

2 Answers

up vote 5 down vote accepted

The ! character invokes bash's history substitution. When followed by a string (as in your failing example) it tries to expand to the last history event that began with that string. Just like $var gets expanded to the value of that string, !echo would expand to the last echo command in your history.

Space is a breaking character in such expansions. First note how this would work with variables:

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

The same thing will happen for history expansion. The bang character (!) starts off a history replacement sequence, but only if followed by a string. Following it with a space make it literal bang instead of part of a replace sequence.

You can avoid this kind of replacement for both variable and history expantion by using single quotes. Your first examples used single quotes and so ran fine. Your last examples are in double quotes and thus bash scanned them for expantion sequences before it did anything else. The only reason the first one didn't trip is that that the space is a break character as shown above.

share|improve this answer
    
Thanks Caleb.. Another of my pre-conceptions dismissed... I thought that bash parsing was done from the innermost bracket or brace, and then worked outwards... It seems that bash parses differently to my assumption. –  Peter.O Aug 23 '11 at 13:01
    
Quoting is confusing enough in bash without it changing in nested strings. As it is, the replacements happen very early on in the process. Consider this example: var=word; echo "test '$var'"; echo 'test "$var"' –  Caleb Aug 23 '11 at 13:12
    
.. Yes undestood. I was aware of nesting quotes within quotes... My misunderstanding was that I thought that the code within the brackets of the command substitution would be parsed seperately, to what surrounds those brackets; but apparently not.. thanks. –  Peter.O Aug 23 '11 at 13:30
add comment

As already said by Caleb, ! is used to invoke bash's history substitution.

If like me you feel you don't need such a feature, you can disable it inserting the following line in ~/.bashrc:

set +H

I don't need it because the history can be recovered by the up arrow and Ctrl-r incremental reverse search. See bash's manual page, section Commands for Manipulating the History for a detailed list of shortcuts.

share|improve this answer
    
How do you live without !!? –  Caleb Aug 23 '11 at 12:54
    
Thanks., I would think that could be a problem, portability-wise, but using set +H in the script works just as well :) +1 –  Peter.O Aug 23 '11 at 12:57
1  
@fred: strange, generally history expansion is "on" only for interactive shells. –  enzotib Aug 23 '11 at 13:03
    
@enzo.. Thanks again.. I had tested it from the commandline.. Ah! If learning wasn't so much fun, it would be tedious... did I mention coffee? it helps too :) –  Peter.O Aug 23 '11 at 13:08
    
Ya, that's a gotcha. I've copy pasted code from scripts that failed on the command line for just this reason. History expansion wasn't a concern in the script but it is on an interactive shell. –  Caleb Aug 23 '11 at 13:16
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.