7

I am trying to run a command from a variable in shell script. The shell being used is bash shell.

The file exp contains:

abcdef

Executing the following command:

sed s/b/\ / exp

...produces the output:

a  cdef

But executing:

cmd="sed s/b/\ / exp"
echo $cmd
$cmd

...produces the following error:

sed s/b/\ / exp
sed: -e expression #1, char 5: unterminated `s' command

I can see that adding eval in front of the execution works. But I cannot understand why. Can you explain why one method is working and the other is not working?

Tom Fenech
  • 69,051
  • 12
  • 96
  • 131
Yosha
  • 71
  • 2
  • 9
    Don't put commands into strings. As you have seen, it doesn't always work. Here is some [recommended reading](http://mywiki.wooledge.org/BashFAQ/050). – Tom Fenech Feb 18 '15 at 09:43
  • 1
    Thanks for your reply and the link – Yosha Feb 18 '15 at 10:03
  • 2
    possible duplicate of [how to put all command arguments in one variable](http://stackoverflow.com/questions/28542911/how-to-put-all-command-arguments-in-one-variable) – tripleee Feb 18 '15 at 11:22
  • 2
    @tripleee note the problem ended up being with the interpretation of `sed` itself. See my answer. – fedorqui Feb 18 '15 at 11:31
  • @Yosha Did any of the answers solve your problem? Since you're new here, please don't forget to mark an answer accepted if your problem is already solved. You can do it clicking on the check mark beside the answer to toggle it from hollow to green. See [Help Center > Asking](http://stackoverflow.com/helpcenter/someone-answers) if you have any doubt! – fedorqui Feb 19 '15 at 09:51

5 Answers5

6

The problem you are having is that the space itself is not being interpreted properly by Bash.

See how it works well if you replace the b with another character, say X:

$ cmd="sed s/b/X/ exp"
$ $cmd
aXcdef

So the workaround is to use the hexadecimal for the space, which is 20:

$ cmd="sed s/b/\x20/ exp"
$ $cmd
a cdef

Or to use eval to execute the command itself:

$ cmd="sed s/b/\ / exp"
$ eval "$cmd"
a cdef

As Tom Fenech suggested, storing commands in variables in not a good approach, as described in I'm trying to put a command in a variable, but the complex cases always fail!. It can work sometimes but in other cases can produce unpredictable results. An alternative is to consider using a function.

Finally, note eval can come handy in cases like this, only that being very careful on what is stored. Some good reading: Variable as command; eval vs bash -c.

Community
  • 1
  • 1
fedorqui
  • 252,262
  • 96
  • 511
  • 570
  • 1
    Well.. he did say "bash", but as a rule, I would not start by introducing to a beginner a bash extension (the \x20), without noting that it isn't POSIX. Rather, the types of escaping and reasons for these would be the usual place to start. – Thomas Dickey Feb 19 '15 at 11:41
3

It looks like a quoting issue:

cmd="sed s/b/\ / exp" makes $cmd hold a sequence of characters with no special meaning. So your \ does not escape your space.

eval treats that sequence of characters as a command, and re-assign the special meaning to your \.

See also: Preserving quotes in bash function parameters

Community
  • 1
  • 1
Chen Levy
  • 14,088
  • 16
  • 72
  • 90
1

If you need the output in the variable then use,

cmd=$(sed 's/b/ /' exp)

Like @thomas says, If you are using the variable you can use the double quotes.

Karthikeyan.R.S
  • 3,971
  • 1
  • 18
  • 31
0

This should do:

cmd=$(sed "s/b/\ /" exp)

To store data in variable use var=$(commands)

Jotne
  • 39,326
  • 11
  • 49
  • 54
  • 2
    If you are posting the answer to the question means then no one other should not post ah? – Karthikeyan.R.S Feb 18 '15 at 09:54
  • @Karthikeyan.R.S Off course everyone can post what they like, but posting 100% same as other are not needed. – Jotne Feb 18 '15 at 10:38
  • 1
    This answer does not address the original question, which was about storing the command itself (not its output) in a variable and then doing something with the variable to run the command. – Tom Barron Feb 18 '15 at 14:20
0

If you quote the sed parameters, the script is easier to read, and backslashes are needed only to escape special characters (such as backslash and the double-quotes):

cmd=$(sed "s/b/ /" exp)

If you use single-quotes, that reduces the need for escaping more -- but prevents you from using variables such as this:

xxx=something
cmd=$(sed "s/b/$xxx/" exp)
Thomas Dickey
  • 47,166
  • 7
  • 60
  • 95
  • 1
    This is just the same as I posted – Jotne Feb 18 '15 at 09:45
  • 4
    we were editing at the same time - so I pointed out some additional features which you have not (yet) added to your answer. – Thomas Dickey Feb 18 '15 at 09:48
  • 3
    @Jotne you all posted a similar question within seconds. And this one at least has some explanation. I don't think it is quite fair to downvote them just for this. – fedorqui Feb 18 '15 at 09:49
  • 1
    It seems that he downvoted while I was expanding my explanation - it's okay now. – Thomas Dickey Feb 18 '15 at 09:52
  • It seems like everyone is suggesting alternative answers. Please explain why the method is not working. – Yosha Feb 18 '15 at 09:59
  • 1
    This answer does not address the original question, which was about storing the command itself (not its output) in a variable and then doing something with the variable to run the command. – Tom Barron Feb 18 '15 at 14:20
  • Actually, the question was not how to use eval, but *why* one method works and the other does not. – Thomas Dickey Feb 19 '15 at 11:38