5

I know that when passing a variable to sed, one should use double quote. So when I was using sed to display a line of a file given a line number:

sed '894!d' cloud.cpp

It works, but since I also want to pass the line number 894 to sed as a variable, so I tried:

lnum=894
sed "$lnum!d" cloud.cpp

But it does not work, and even single quote with same syntax does not work:

sed "894!d" cloud.cpp

So in this case how can I still pass a variable to sed.

3 Answers3

9

If your variable contains only digit, what you have done is correct:

sed "$lnum!d" cloud.cpp

For passing shell variable to sed in general, please read this answer.


Now, you have done it correctly in this case, but it still doesn't work. It dues to your shell, which probably bash, zsh or csh. Those shells support history expansion, which is denoted by ! (This feature is original from csh and copied later by bash and zsh).

In bash, zsh, history expansion is enabled by default in interactive session, and will be performed inside double quotes.

In zsh:

$ seq 10 | sed "10!d" # <-- press enter
$ seq 10 | sed "10dash"

as dash is my latest command in history start with d.

In bash:

$ seq 10 | sed "10!d"
bash: !d": event not found

as I don't have any command in bash history start with d.

You can turn off history expansion:

set +H

or:

set +o histexpand

in bash and:

set -K

or:

setopt nohistexpand

in zsh.


For more portable way, without relying on your shell options, you can always do:

sed "$lnum"\!d cloud.cpp

which causes your shell treating ! literally.

cuonglm
  • 153,898
7

As you mentioned, you must use double quotes because when you use single quotes for strings, its contents will be treated literally. But in your case, you have a combination of patern and variable. So you should use single quotes for the pattern, and double quotes for the variable:

sed "$lnum"'!d' cloud.cpp
GAD3R
  • 66,769
Dababi
  • 3,355
  • 24
  • 23
4

You didn't mention which shell you are using. The answer below assumes it's bash.


You are triggering history expansion in the shell. From man bash:

!string

Refer to the most recent command preceding the current position in the history list starting with string.

The weird thing about it is that:

If enabled, history expansion will be performed unless an ! appearing in double quotes is escaped using a backslash. The backslash preceding the ! is not removed.

Therefore I think quoting them separately is probably the best solution:

sed "$lnum"'!d' cloud.cpp

This also works (saving you a char):

sed "$lnum"\!d cloud.cpp

This also works (if you favor double quotes):

sed "$lnum""!""d" cloud.cpp
Toby Speight
  • 8,678
Cyker
  • 4,274
  • 7
  • 36
  • 46
  • Note that anything other than the second one won't work in (t)csh – cuonglm Dec 14 '16 at 08:26
  • @cuonglm I already mentioned the answer is for bash. It should apply to bash-compliant shells as well. Obviously csh is not one of them. – Cyker Dec 14 '16 at 08:28