Execute command inside for loop from variable in bash - 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.

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

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

Instead of eval \$$cmd you need to use:
res=$(eval "$cmd")
ssh server1 "$res"

Related

How to expand a variable "read" from terminal

For example,
create a bash tmp.sh script with the following,
export tmp=abc
read _test
echo "$_test"
Execute bash tmp.sh
Input '$tmp/def'.
Expected result: 'abc/def'
Actual result: '$tmp/def'
check this
eval "echo $_test"
or
bash -c "echo $_test"
Edit Latter (bash -c) uses sub-shell which is safe in comparison with eval
You can use the envsubst command to substitute environment variables like this:
echo "$_test" | envsubst
or, since this is in bash:
envsubst <<<"$_test"
This is significantly safer than either eval or bash -c, since it won't do anything other than replacing instances of $var or ${var} with the corresponding variable values.

Preventing pre-mature shell parameter expansion in chained commands

I have a system that allows commands to be executed from a host to various external machines, from a bash shell script using either ssh or 'sersh', which is similar to ssh but sends commands over a serial port. (The details of these commands don't matter.)
I'm trying to chain the commands together, from one external machine to
yet a 3rd machine. I'm having a hard time figuring out how to get the shell to expand parameters only on the final machine.
function do_cmd () {
case $TRANSPORT in
ssh)
ssh -i ${SSH_KEY} ${LOGIN}#${IPADDR} "$#"
;;
serial)
sersh ${SER_LOGIN}#{SERIAL_DEV} "$#"
;;
ssh2serial)
ssh -i ${SSH_KEY} ${LOGIN}#{IPADDR} \
"sersh ${SER_LOGIN}#${SERIAL_DEV} $#"
;;
*)
echo "Unknown transport $TRANSPORT"
;;
esac
}
do_cmd "echo hello"
do_cmd "echo \"my pid is \$\$\""
do_cmd "cd /proc ; for pid in 1* ; do echo \$pid, ; done"
All three of these calls work correctly when TRANSPORT is 'ssh' or 'serial'. For the TRANSPORT 'ssh2serial', in the second call to do_cmd, the $$ is expanded prematurely (on the intermediate machine, not on the final machine). And for the third call to do_cmd, $pid ends up
being expanded to the empty string before the loop executes on the
final machine.
I thought about double-escaping the dollar signs, but the
caller doesn't know how many levels of intermediate machines there are.
Is there a way to prevent the parameter expansion on the intermediate
machine, and only do it on the final machine?
[#MadPhysicist reminds me that I'd still have to escape the variable expansions. My answer is left here below for reference but #MadPhysicist is right: my answer probably fails to answer the question asked.]
Use eval.
Try this exercise, typing the following four commands at your shell's command line. I believe that the exercise is likely to answer your question.
$ X=55
$ A='echo $(( $X + $X ))'
$ echo "$A"
$ eval "$A"
Once the exercise has shown you what the shell's built-in command eval does, you can get the effect you want by using eval on the final machine in your chain.
[Please notice that the exercise says, A='echo $(( $X + $X ))'. It does not say, A=`echo $(( $X + $X ))`. Observe the way the quotation marks slant. You want ordinary single quotes here, not backticks.]

bash invoked via ssh does not store variables

there is a problem with the invoked via ssh bash, although i have read mans about it i still can't explain the following:
Here is a script, very simple
#!/bin/bash
theUser=$1
theHost=$2
ssh -tt $theUser#$theHost 'bash' << EOF
a=1
echo 'dat '$a
exit
EOF
and here is the result:
victor#moria:~$ bash thelast.sh victor 10.0.0.8
victor#10.0.0.8's password:
a=1
echo 'dat '
exit
victor#mordor:~$ a=1
victor#mordor:~$ echo 'dat '
dat
victor#mordor:~$ exit
exit
Connection to 10.0.0.8 closed.
As you may see, the environment doesn't store the value of the variable "a" so it can't echo it, but any other commands like ls or date return the result.
So the question is what i am doing wrong and how to avoid such behavior?
p.s. i can't replace ssh -tt, but any other command may be freely replaced.
Thanks in advance
Inside the here document, the $a is expanded locally before feeding the input to the ssh command. You can prevent that by quoting the terminator after the << operator as in
ssh -tt $theUser#$theHost 'bash' << 'EOF'
$a is being expanded in the local shell, where it is undefined. In order to prevent this from happening, you should escape it:
echo "dat \$a"
Escaping the $ causes it to be passed literally to the remote shell, rather than being interpreted as an expansion locally. I have also added some double quotes, as it is good practice to enclose parameter expansions inside them.

Using spaces in bash scripts

I have a script foo.sh
CMD='export FOO="BAR"'
$CMD
echo $FOO
It works as expected
>./foo.sh
"BAR"
Now I want to change FOO variable to BAR BAR. So I get script
CMD='export FOO="BAR BAR"'
$CMD
echo $FOO
When I run it I expect to get "BAR BAR", but I get
./foo.sh: line 2: export: `BAR"': not a valid identifier
"BAR
How I can deal with that?
You should not use a variable as a command by just calling it (like in your $CMD). Instead, use eval to evaluate a command stored in a variable. Only by doing this, a true evaluation step with all the shell logic is performed:
eval "$CMD"
(And use double quotes to pass the command to eval.)
Just don't do that.
And read Bash FAQ #50
I'm trying to save a command so I can run it later without having to repeat it each time
If you want to put a command in a container for later use, use a
function. Variables hold data, functions hold code.
pingMe() {
ping -q -c1 "$HOSTNAME"
}
[...]
if pingMe; then ..
The proper way to do that is to use an array instead:
CMD=(export FOO="BAR BAR")
"${CMD[#]}"

IO redirection of string-assembled command

I'm writing a bash script that is intended to execute some command, and depending on some flag, this command should be either executed locally or remotely. This command's output should be redirected to some file, and this file should be on the box that executes the command, that is, on the remote box if the command is executed remotely.
I'm trying things like
#!/bin/bash
REMOTE=1
function f
{
CMD="$#"
if [ "${REMOTE}" == "1" ]
then
ssh some_host "$CMD"
else
$CMD
fi
}
# This executes "echo huhu" remotely and redirects the output into "out" on the remote box.
REMOTE=1 f echo huhu \> out
# This executes "echo haha > out" remotely (without redirection).
REMOTE=0 f echo haha \> out
When I don't escape the > sign, any output of f is redirected to "out" on the local box, of course.
How could I avoid this behavior?
Don't use eval; use arrays instead. And a solution for the SSH command.
Write eval $CMD instead of $CMD. When $CMD is expanded the interpretation of redirection has already happened and redirections operations will simply passed as ordinary arguments.

Resources