3

Trying to run commands defined in variables inside a for loop:

somevar="Bit of text"
cmd1="command \"search '$somevar' here\""
cmd2="command \"search '$somevar' there\""
for cmd in cmd1 cmd2 ; do
    eval \$$cmd
    ssh server1 eval \$$cmd
done

I've put in the variations I have to consider such as the ssh inside the loop etc as these are needed in my script. I think the eval is the right direction, but the way that the quotes inside the command get interpreted comes through wrong.

1

3 Answers 3

5

Consider this broken example:

$ cmd1="touch \"file with spaces\""
$ $cmd1

Quoting is handled before $cmd1 is expanded, so instead of one file this will create three files called "file, with, and spaces". One can use eval $cmd to force quote removal after the expansion.

Even though it uses eval, the line eval \$$cmd has that same quoting problem since \$$cmd expands to $cmd1, which is then evaluated by eval with the same behaviour as the broken example.

The argument to eval must be the actual command, not the expression $cmd1. This can be done using variable indirection: eval "${!cmd}".

When running this through SSH there is no need for the eval because the remote shell also performs quote removal.

So here is the fixed loop:

for cmd in cmd1 cmd2 ; do
    eval "${!cmd}"
    ssh server1 "${!cmd}"
done

An alternative to indirection is to iterate over the values of cmd1 and cmd2 instead of their names:

for cmd in "$cmd1" "$cmd2" ; do
    eval "$cmd"
    ssh server1 "$cmd"
done
Sign up to request clarification or add additional context in comments.

3 Comments

Unfortunately, the command that is being run requires those quotes to be passed through intact as they form part of the arguments (command to run a DB query). I didn't mean "command" as the built in, just the particular command I want to run.
Works perfectly! At least for ssh to remote host. I just need to have run locally as well. Also, if you can expand on what that is doing I'd appreciate it. Thanks matey!
@delronhubbard I expanded on the expansions :)
1

I see two solutions, either you change your loop to:

for cmd in "$cmd1" "$cmd2" ; do
    ssh server1 $cmd
done

or to:

for cmd in cmd1 cmd2 ; do
    ssh server1 ${!cmd}
done

6 Comments

Seems to ignore the $($cmd) and just throws a login back.
Unless you set up an SSH-key, it will ask you the login and password.
Sorry, key already setup. It gives me a prompt for the remote server, ignoring the command I sent.
Thanks, Tom beat you to it on that one! But thanks ;)
Yeah, I didn't understood your question the first time, it's a shame because I thought about ${!cmd}.
|
0

Instead of eval \$$cmd you need to use:

res=$(eval "$cmd")
ssh server1 "$res"

1 Comment

Thanks, but still doesn't interpolate the strings in the command properly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.