5

I'm trying to execute a string into my bash script. String contains variables (so I could substitute some CLI arguments depending on circumstances).

The problem is when I use variable containing --argument=value, everything breaks up, giving error:

Missing trailing-' in remote-shell command.

Example code:

#!/bin/bash

rsync_path="/usr/bin/rsync -azhrP"
rsync_ssh_cmd="--rsh='ssh -o Compression=no'"

`$rsync_path /path/to/file $rsync_ssh_cmd username@localhost:/tmp`

If I just echo the last string from this code, it looks fine. But if i try to execute this string from script, it doesn't executes as expected.

I found a workaround - everything became OK if I try

eval "my-command-string"

instead of

`my-command-string`

or

(my-command-string)

Why does it so? How could I (should I really?) avoid to use eval?

I use bash 4.3.11(1)-release (x86_64-pc-linux-gnu) (Kubuntu 14.04.3).

  • 1
    If you are a beginner in bash shell scripting, I recommend you next web: shellcheck.net – albertovar Dec 14 '15 at 9:05
  • again, sorry i was a jerk. but this gets asked as lot - and everybody has the wrong idea, it seems like. – mikeserv Dec 14 '15 at 10:50
5

OK. You can avoid using eval by coming up with some other method of twice-evaluating your string. Whatever you do, you need to twice-evaluate because your requirement is to parse a parse.

What I mean is:

this is 'a parsed" command string"'

^ that works out to 3 words:

1: this 2: is 3: a parsed" command string"

The quotes in 3 are not important—they don't matter at all because they're just some characters in a word. The ' syntax quotes used in the original command-line did matter because they were interpreted by the shell's parser to delimit the word. Anything within is just part of the word.

So, if you want to get more words from a single, pre-parsed word, you have to give it back to the shell as input. That's all there is to it—the shell processes input quotes on input. The quotes are used to escape and delimit input in meaningful ways. It would be pretty meaningless—and fairly scary—if the shell just kept at it. Eventually all input would get evaluated away to nothing!

So you need a second evaluation. You can't get that from $( command substitutions ) because they don't deal with input, they deal with output. Any side-effect of $word-expansion which you might equate to the generation of more words is really not the case - that is field-splitting. field-splitting on $IFS and *globbing are performed by the shell after input words have already been delimited during the parse process.

Now eval's whole purpose is to evaluate as input some string which the shell has already received as input. it is exactly the right tool for this, but we can do it otherwise if you insist.

One way is . source your own output.

string="printf '<%s>\n' 'these are' 'some words' 'i will evaluate again'"
. /dev/fd/0 <<!
$string
!

In that example, the shell evaluates as input its own output. It works:

<these are>
<some words>
<i will evaluate again>

You can do it by calling another shell to evaluate your output as its input:

sh -c "$string"

...the output is the same. You can delay your input by defining a shell alias:

alias evaled="$string"
evaled

...and again, same output.

The most direct way to do it is:

eval "$string"

The effect is nearly the same as in all of the other examples, but this time I actually did it with the command named for what I meant to do.

eval gets a bad rap because people who don't understand what it does or why use it anyway. The thing is, you shouldn't use it if you don't understand the effects of doing so. And it can be easy to lose track: the command is evaluated twice—that's once more than normal—and the one time is usually more than enough for most people to wind up at this web site.

So, you should use eval in this case to do this thing you want to do, because what you want to do is eval a string. I don't presume to judge why you want to eval the string, though I expect that there could be better ways of doing it, but when you want the contents of a shell string to be evaluated as shell input, you should use eval.

  • @terdon - grrrr. but i'll let it be. i've just grown really tired of SHIFT lately. – mikeserv Dec 14 '15 at 13:38
1

You are having this issue due to Embeded quotes : BashFAQ

This is the work arround:

#!/bin/bash

rsync_path="/usr/bin/rsync -azhrP"
rsync_ssh_cmd=(--rsh='ssh \-o Compression=no')
`$rsync_path /path/to/file $rsync_ssh_cmd  root@localhost:/tmp`

[PROOF of Concept]

[root@localhost ~]# bash -x  /tmp/c.sh
+ rsync_path='/usr/bin/rsync -azhrP'
+ rsync_ssh_cmd=(--rsh='ssh \-o Compression=no')
++ /usr/bin/rsync -azhrP /path/to/file --rsh=ssh '\-o' Compression=no root@localhost:/tmp
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/root/.ssh/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: /root/.ssh/id_rsa
root@localhost's password:
  • Your code don't work for me. Here is the output: rsync: change_dir "/path/to" failed: No such file or directory (2) rsync: link_stat "/home/username/\-o" failed: No such file or directory (2) rsync: link_stat "/home/username/Compression=no" failed: No such file or directory (2) rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1183) [sender=3.1.0] ./proof.sh: line 6: sending: command not found – AntonioK Dec 14 '15 at 8:11
  • Thats because the path /path/to surely does not exists as specified in your error No such file or directory (2) – Kheshav Sewnundun Dec 14 '15 at 9:22
  • of course I changed /path/to/file to existed and accessible file. Look, if I use your code, but echo "" the last line instead of backticks it, it outputs /usr/bin/rsync -azhrP /path/to/file --rsh=ssh \-o Compression=no root@localhost:/tmp - so ' is missing, and \-o is on it's place. – AntonioK Dec 14 '15 at 9:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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