How to fix the error 'bash :! D': event not found "in Bash command substitution

I am trying to parse the output of a VNC server startup event and have encountered a problem while parsing using sed in command substitution. In particular, the remote VNC server starts as follows:

address1="user1@lxplus.cern.ch" VNCServerResponse="$(ssh "${address1}" 'vncserver' 2>&1)" 

Then you should analyze the standard error output generated by this start event to retrieve the server and display the information. Currently, the contents of the VNCServerResponse variable look something like this:

 New 'lxplus0186.cern.ch:1 (user1)' desktop is lxplus0186.cern.ch:1 Starting applications specified in /afs/cern.ch/user/u/user1/.vnc/xstartup Log file is /afs/cern.ch/user/u/user1/.vnc/lxplus0186.cern.ch:1.log 

This output can be analyzed as follows to retrieve the server and display information:

 echo "${VNCServerResponse}" | sed '/New.*desktop.*is/!d' \ | awk -F" desktop is " '{print $2}' 

The result is something like the following:

 lxplus0186.cern.ch:1 

I want to use this parsing in command substitution like this:

 VNCServerAndDisplayNumber="$(echo "${VNCServerResponse}" \ | sed '/New.*desktop.*is/!d' | awk -F" desktop is " '{print $2}')" 

When I try to do this, I get the following error:

 bash: !d': event not found 

I am not sure how to solve this problem. It seems the problem is how sed is used in command substitution. I would be grateful for the leadership.

+5
bash parsing sed command-substitution
source share
5 answers

The problem is that in double quotes, bash tries to expand !d before passing it to a subshell. You can work around this problem by removing double quotes, but I would also suggest a simplification for your script:

 VNCServerAndDisplayNumber=$(echo "$VNCServerResponse" | awk '/desktop/ {print $NF}') 

It just prints the last field on the line containing the word "desktop".

In the newer bash, you can use the header rather than laying out echo :

 VNCServerAndDisplayNumber=$(awk '/desktop/ {print $NF}' <<<"$VNCServerResponse") 
+6
source share

Bash The history extension is a very strange angle in the bash command line syntax, and you are clearly facing an unexpected history extension, which is explained below. However, any story extension in the script is unexpected, because usually the story extension is not included in scripts; even scripts do not run with the built-in source (or . ).

How story extension is enabled (or disabled)

There are two shell options that control story expansion:

  • set -o history : required to record history.

  • set -H (or set -o histexpand ): Additionally, the history extension must be enabled.

Both of these parameters must be set to recognize the extension of the story. (I found the manual unclear in this interaction, but it is logical enough.)

According to the bash manual, these parameters are not configured for non-interactive shells, so if you want to enable the history extension in the script (and I can not imagine the reason why you need it), you will need to install them:

 set -o history -o histexpand 

The situation for scripts running with source is more complicated (and what I'm going to say applies only to bash v4, and since it is not documented, it may change in the future). [Note 3]

History recording (and therefore extension) is disabled in source'd scripts, but through an internal flag, which, as far as I know, does not become visible. This, of course, does not appear in $SHELLOPTS . Since source d script works in the current bash context, it shares the current runtime, including shell parameters. Thus, when you run the source d script initiated from an interactive session, you will see both history and histexpand in $SHELLOPTS , but the history will not expand. To enable it, you need to:

 set -o history 

which is not no-op, since it has the side effect of discarding the internal flag, which suppresses the history record. The histexpand parameter of the membrane does not have this side effect.

In short, I'm not sure how you managed to enable the story extension in the script (if, indeed, the wrong command was in the script, and not in the interactive shell), but you might want to not do this unless you have a really good reason .

How is history expansion analyzed?

The implementation of the bash history extension is designed to work with readline , so that it can be executed while the command is being entered. (By default, this function is tied to Meta- ^ , usually Meta to ESC , but you can also configure it.) However, it also runs immediately after entering each line before any bash parsing is performed.

The default symbol is the extension of the story ! and, as a rule, documented, which will lead to an extension of the story, with the exception of:

  • when followed by spaces or =

  • if the extglob shell parameter is extglob , it follows ( [Note 1]

  • if it is displayed in one quotation mark

  • if preceded by \ [Note 2 and see below]

  • if preceded by $ or $ { [Note 1]

  • if preceded by [ [Note 1]

  • (Starting with bash v4.3) if this is the last character in a string with two quotes.

The immediate problem here is an accurate interpretation of the third case, ah ! - inside one quotation mark. Normally, bash launches a new citation context to substitute a command ( $(...) or an obsolete countdown entry). For example:

 $ s=SUBSTITUTED $ # The interior single quotes are just characters $ echo "'Echoing $s'" 'Echoing SUBSTITUTED' $ # The interior single quotes are single quotes $ echo "$(echo 'Echoing $s')" Echoing $s 

However, the story extension scanner is not so smart. It tracks quotes, but not command spoofing. So, as far as possible, both single quotes in the above example are single quotes with double quotes, i.e. ordinary characters. Thus, the expansion of the story occurs in both of them:

 # A no-op to indicated history expansion $ HIST() { :; } # Single-quoted strings inhibit history expansion $ HIST $ echo '!!' !! # Double-quoted strings allow history expansion $ HIST $ echo "'!!'" echo "'HIST'" 'HIST' # ... and it applies also to interior command substitution. $ HIST $ echo "$(echo '!!')" echo "$(echo 'HIST')" HIST 

So, if you have a completely normal command like sed '/foo/!d' file , where you expect single quotes to protect you from expanding the story, and you put it as a replacement for commands with two quotes:

 result="$(sed '/foo/!d' file)" 

you suddenly find that symbol ! is a symbol of the expansion of history. Worse, you cannot fix this, the backslash by avoiding the exclamation point, because although "\!" prevents the extension of the story, it does not remove the backslash:

 $ echo "\!" \! 

In this particular example - and in OP - double quotes are completely unnecessary because the right side of the variable assignment does not undergo either file name extension or word splitting. However, there are other contexts in which removing double quotes can change semantics:

 # Undesired history expansion printf "The answer is '%s'\n" "$(sed '/foo/!d' file)" # Undesired word splitting printf "The answer is '%s'\n" $(sed '/foo/!d' file) 

In this case, the best solution is probably to include the sed argument in the variable

 # Works sed_prog='/foo/!d' printf "The answer is '%s'\n" "$(sed "$sed_prog" file)" 

(Quotations around $ sed_prog were not necessary in this case, but usually they would have been, and they would not have hurt.)


Notes:

  • Inhibiting a story extension when the next character is some form of open parenthesis only works if there is a matching closing bracket in the rest of the line. However, it should not really match the open parenthesis. For example:

     # No matching close parenthesis $ echo "!(" bash: !: event not found # The matching close parenthesis has nothing to do with the open $ echo "!(" ")" !( ) # An actual extended glob: files whose names don't start with a $ echo "!(a*)" b 
  • As stated in the bash manual, a history extension character is considered a regular character if it is immediately preceded by a backslash. This is literally true; it doesn't matter if the backslash is later treated as an escape character or not:

     $ echo \! ! $ echo \\! \! $ echo \\\! \! 

    \ also prevents history from expanding inside double quotes, but \! is not a valid escape sequence inside a double-quoted string, so the backslash is not removed:

     $ echo "\!" \! $ echo "\\!" \! $ echo "\\\!" \\! 
  • I mean the source code for bash v4.2, as I write this, so any undocumented behavior can be completely different compared to v4.3.

+12
source share

Do not wrap the replacement of the $(...) command in double quotes. You ask the shell to evaluate the contents of the quotation marks and click on the extension function to replace the history. Throw away the quotes and stop telling the shell about it, and you will not run into this problem.

And yes , dumping these quotes is safe in this assignment line, even if the output may contain spaces or newlines or something else. Assignments of this kind are not going to be divided into how command substitution or variable evaluation will be performed on a regular shell execution line.

Alternatively, disable the story extension in your shell / script before starting it. (It should be turned off when running the default script, I still believe.)

+2
source share

This only happens when the story extension is turned on, which usually does not exist and should definitely not be for scripts.

Instead of trying to get around this, find out why story extension is included and what to do.

  • If you execute your script with . foo . foo or source foo , use ./foo .

  • If you are writing this as a function in .bashrc or similar, consider making it a separate script.

  • If your script (or BASH_ENV) explicitly does set -H , don't do this.

+2
source share

Send it with '' or \ or disable the story extension with set +H or shopt -u -o histexpand . See Extending History .

0
source share

All Articles