Bash: executing a command stored in a variable [duplicate] - bash

This question already has answers here:
How can I execute a command stored in a variable?
(4 answers)
Closed 2 years ago.
I am writing a script, and one part of it is not working as I would expect.
I have broken out this part in a simple example for simplicity:
echo 'echo "" > tmp' | while read cmd; do $cmd ; done
Here I would expect the full command, "echo "" > tmp" to be executed by $cmd.
But this happens:
"" > tmp
The command executed echoes out "" > tmp literally. Instead of echoing out "" and redirecting it to the file tmp. Obviously something is wrong when storing the command in $cmd and then later trying to execute it.
The result is the same even if I simplify it further:
cmd="echo "" > tmp"
$cmd
> tmp
I have tried to experiment with different usages of '' and "", but not solved it yet.

Use eval to execute the command stored in the variable:
echo 'echo "" > tmp' | while read cmd; do eval "$cmd" ; done
The value of cmd will be echo "" > tmp. Then when Bash resolves the parameter substitution as a command, the part "" > tmp will be the string arguments of echo, not be recognized as >(redirection). So it will just output the arguments part.
The same as: $(echo 'echo "" > tmp')

Change
do $cmd ;
to
do eval "$cmd" ;

Related

ps -x|grep works on command line but not in shell script, why?

I am trying to find whether a particular process ID is running. If I type on command line:
ps -x|cut -c1-5|grep 7032
I get 7032 as the answer. But if I put into shell script:
read pid_found < "ps -x|cut -c1-5|grep $old_pid"
I get the error message:
./test.sh: line 11: ps -x|cut -c1-5|grep 7032: No such file or directory
Note that the expansion of $old_pid is a not a problem -- the error message correctly says grep 7032. Every similar question on StackOverflow has answers suggesting to use
variable_name=$(command)
but this does not work for me either, with any command. At least
read variable_name < "command"
works elsewhere in the script
Edit: I managed to get it to work by removing all pipes within quotation marks and putting all intermediate values into files. It's ugly, but it works:
#!/bin/bash
cd /home/automan/vms_feed
read -d $'\x04' old_pid < "OldPID"
ps -x|cut -c1-5 > "Processes"
grep $old_pid "Processes"|wc -c > "PID_found"
read -d $'\x04' pid_found < "PID_found"
echo "Running: test, old_pid =" $old_pid
if [ $pid_found -gt 0 ]
then
echo "Running: test, old pid found" $pid_found
else
echo "Running: test, old pid not found"
fi
echo $$ > OldPID
echo "Finished: test"

How does quote in a cmd string work and how does the quote interact with eval for bash script?

I tried the commands in a bash script.
#!/bin/bash
cmd='pgrep -d " " -f "python test.py"'
$cmd
cmd2=(pgrep -d \" \" -f \"python test.py\")
${cmd2[#]}
Both don't work returning pgrep: only one pattern can be provided as you expect. however both work with eval command like the below.
eval $cmd
eval ${cmd2[#]}
The errors seem from using quote(") but I don't have any idea why and how eval command interacts with the quote in here though I have tried to understand with the below description.
DESCRIPTION
The eval utility shall construct a command by concatenating arguments together, separating
each with a <space>. The constructed command shall be read and executed by the shell.
EXAMPLES
foo=10 x=foo
y='$'$x
echo $y
$fooeval y='$'$x
echo $y
10
Could you give me the explanation?
Take a look at the output here and see if it helps.
showArgs(){
test $# = 0 && return
echo "arg:" "$1"
shift
showArgs "$#"
}
cmd='pgrep -d " " -f "python test.py"'
echo Without eval ---------
showArgs $cmd
echo With eval --------
eval showArgs $cmd
Also try set -x to enable debug output.

putting output of script to variable in loop bash

I have the following bash script and want to run other script from that and capture the results:
#!/bin/bash
while read line; do
echo "exit" | out=`python file.py`
if [[ $out == *"WORD"* ]]; then
echo $line >> out.txt
fi
done<$1
But this is not working for me. In each iteration out wouln't get value...
echo "exit" | out=`python file.py`
Should be something like (send "exit" to the result of assigning the output of file.py to out - seems odd):
echo "exit" && out=`python file.py`
or (send "exit" as input to file.py and assign output to out):
out=`echo "exit" | python file.py`
depends on what you're trying to achieve.
A pipeline runs in a subshell, so variable assignments within it aren't visible in the parent shell. It should be:
out=$(echo exit | python file.py)
Now the whole pipeline is inside the command substitution, but the variable assignment is in the original shell.
Keep python execution outside loop since that is not dependent upon any loop variable:
#!/bin/bash
# initialize output file
> out.txt
# execute python script
out=$(echo "exit" | python file.py)
# loop
while read -r line; do
[[ "$out" == *"WORD"* ]] && echo "$line" >> out.txt
done < "$1"
Also quoting seem to be missing at many point that I have added.

use of ssh variable in the shell script

I want to use the variables of ssh in shell script.
suppose I have some variable a whose value I got inside the ssh and now I want to use that variable outside the ssh in the shell itself, how can I do this ?
ssh my_pc2 <<EOF
<.. do some operations ..>
a=$(ls -lrt | wc -l)
echo \$a
EOF
echo $a
In the above example first echo print 10 inside ssh prints 10 but second echo $a prints nothing.
I would refine the last answer by defining some special syntax for passing the required settings back, e.g. "#SET var=value"
We could put the commands (that we want to run within the ssh session) in a cmdFile file like this:
a=`id`
b=`pwd`
echo "#SET a='$a'"
echo "#SET b='$b'"
And the main script would look like this:
#!/bin/bash
# SSH, run the remote commands, and filter anything they passed back to us
ssh user#host <cmdFile | grep "^#SET " | sed 's/#SET //' >vars.$$
# Source the variable settings that were passed back
. vars.$$
rm -f vars.$$
# Now we have the variables set
echo "a = $a"
echo "b = $b"
If you're doing this for lots of variables, you can add a function to cmdFile, to simplify/encapsulate your special syntax for passing data back:
passvar()
{
var=$1
val=$2
val=${val:-${!var}}
echo "#SET ${var}='${val}'"
}
a=`id`
passvar a
b=`pwd`
passvar b
You might need to play with quotes when the values include whitespace.
A script like this could be used to store all the output from SSH into a variable:
#!/bin/bash
VAR=$(ssh user#host << _EOF
id
_EOF)
echo "VAR=$VAR"
it produces the output:
VAR=uid=1000(user) gid=1000(user) groups=1000(user),4(adm),10(wheel)

Bash variables expansion (possible use of eval) in for-do loop

I am studying the book "Beginning Linux Programming 4th ed" and chapter 2 is about shell programming. I was impressed by the example on page 53, and tried to develop a script to display more on that. Here is my code:
enter code here
#!/bin/bash
var1=10
var2=20
var3=30
var4=40
for i in 1 2 3 4 # This works as intended!
do
x=var$i
y=$(($x))
echo $x = $y # But we can avoid declaring extra parameters x and y, see next line
printf " %s \n" "var$i = $(($x))"
done
for j in 1 2 3 4 #This has problems!
do
psword=PS$j
#eval psval='$'PS$i # Produces the same output as the next line
eval psval='$'$psword
echo '$'$psword = $psval
#echo "\$$psword = $psval" #The same as previous line
#echo $(eval '$'PS${i}) #Futile attempts
#echo PS$i = $(($PS${i}))
#echo PS$i = $(($PS{i}))
done
#I can not make it work as I want : the output I expect is
#PS1 = \[\e]0;\u#\h: \w\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$
#PS2 = >
#PS3 =
#PS4 = +
How can I get the intended output? When I run it as it is I only get
PS1 =
PS2 =
PS3 =
PS4 = +
What happened with PS1 and PS2 ?
Why do not I get the same value that I get with
echo $PS1
echo $PS2
echo $PS3
echo $PS4
because that was what I am trying to get.
Shell running a script is always non interactive shell. You may force to run the script in interactive mode using '-i' option:
Try to change:
#!/bin/bash
to:
#!/bin/bash -i
see INVOCATION section in 'man bash' (bash.bashrc is where your PS1 is defined):
When an interactive shell that is not a login shell is started, bash reads and executes commands from
/etc/bash.bashrc and ~/.bashrc, if these files exist. This may be inhibited by using the --norc option. The
--rcfile file option will force bash to read and execute commands from file instead of /etc/bash.bashrc and
~/.bashrc.
When bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in
the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read
and execute. Bash behaves as if the following command were executed:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
but the value of the PATH variable is not used to search for the file name.
you can also read: http://tldp.org/LDP/abs/html/intandnonint.html
simple test:
$ cat > test.sh
echo "PS1: $PS1"
$ ./test.sh
PS1:
$ cat > test.sh
#!/bin/bash -i
echo "PS1: $PS1"
$ ./test.sh
PS1: ${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[01;34m\] \w \$\[\033[00m\]
Use indirect expansion:
for j in 0 1 2 3 4; do
psword="PS$j"
echo "$psword = ${!psword}"
done

Resources