user2295413
user2295413

Reputation:

Execute command inside for loop from variable in bash

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.

Upvotes: 3

Views: 15756

Answers (3)

tom
tom

Reputation: 22939

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

Upvotes: 5

Maxime Chéramy
Maxime Chéramy

Reputation: 18821

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

Upvotes: 1

anubhava
anubhava

Reputation: 784958

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

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

Upvotes: 0

Related Questions