Variable indirection

Hello.

This an issue from previous thread :grep - Extracting multiple key words from stdout

In a script, I want to print what command is to be being executed.
Generally I use :

CMD="some simple command"
echo "COMMAND : $CMD"
$CMD

Now echo "COMMAND : $CMD" show this :

COMMAND : CNT=$(  grep -c -e  "Non-option program arguments: 'nxclient'" -e   "Checking whether to refresh metadata for zypper_local"  -e  "Selecting 'nxclient-3.5.0-7.x86_64' from repository 'zypper_local' for installation."  /tmp/nxclient_install_log.txt )

Which is correct; and should give CNT=3 which is correct.
But when I try to execute $CMD I get this error :

/root/bin/freenx_install_server_function: line 102: CNT=$(: command not found

I have try

CMD="some simple command"
echo "COMMAND : $CMD"
${$CMD}

Which give also an error

How to execute $CMD with success.

Hi.

Here are a few methods:

#!/usr/bin/env bash

# @(#) s1	Demonstrate execution of created shell command.

pe() { for _i;do printf "%s" "$_i";done; printf "\n"; }
pl() { pe;pe "-----" ;pe "$*"; }
db() { ( printf " db, ";for _i;do printf "%s" "$_i";done;printf "\n" ) >&2 ; }
db() { : ; }
C=$HOME/bin/context && [ -f $C ] && $C

pl " Create, display, attempt to execute shell command:"
cmd='count=$( wc -l < s1 )'
pe " Command is \"$cmd\""
$cmd

pl " Write command to file, use as input to shell:"
echo "$cmd" > f1
echo "echo \$count" >> f1
cat f1
bash f1
pe " Current value of count is \"$count\""

pl " Write command to file with here document, use as input to shell:"
cat <<EOF > f1
$cmd
echo "count is \$count"
EOF
cat f1
bash f1
pe " Current value of count is \"$count\""

pl " Create, display, attempt to execute shell command, using eval:"
unset count
echo " count before eval is \"$count\""
cmd='count=$( wc -l < s1 )'
pe " Command is \"$cmd\""
pe " Current value of count is \"$count\""
eval $cmd
echo " count after  eval is \"$count\""

pl " Create, display, attempt to execute shell command, using source:"
unset count
echo " count before source is \"$count\""
cmd='count=$( wc -l < s1 )'
pe " Command is \"$cmd\""
echo "$cmd" > f1
pe " Current value of count is \"$count\""
source f1
echo " count after  source is \"$count\""

exit 0

producing:

% ./s1

Environment: LC_ALL = C, LANG = C
(Versions displayed with local utility "version")
OS, ker|rel, machine: Linux, 2.6.26-2-amd64, x86_64
Distribution        : Debian GNU/Linux 5.0.8 (lenny) 
bash GNU bash 3.2.39

-----
 Create, display, attempt to execute shell command:
 Command is "count=$( wc -l < s1 )"
./s1: line 14: count=$(: command not found

-----
 Write command to file, use as input to shell:
count=$( wc -l < s1 )
echo $count
51
 Current value of count is ""

-----
 Write command to file with here document, use as input to shell:
count=$( wc -l < s1 )
echo "count is $count"
count is 51
 Current value of count is ""

-----
 Create, display, attempt to execute shell command, using eval:
 count before eval is ""
 Command is "count=$( wc -l < s1 )"
 Current value of count is ""
 count after  eval is "51"

-----
 Create, display, attempt to execute shell command, using source:
 count before source is ""
 Command is "count=$( wc -l < s1 )"
 Current value of count is ""
 count after  source is "51"

See man bash for details.

Best wishes ... cheers, drl

The double quotes around "some simple command" are causing the command substitution contained therein to be executed. Use single quotes to get the actual literal command displayed. Then, use eval to force the shell to evaluate the line where you want to run the command twice. The first pass replaces "eval $CMD" with "CNT=$(who|grep -c username)" then it gets executed.

eval can be dangerous if a user can affect what is eval'd, but since you are controlling the command to be eval'd you should be ok here.

$ cat x
#!/bin/ksh

CMD='CNT=$(who|grep -c username)'

print "COMMAND: $CMD"

eval $CMD

print $CNT

exit 0
$ x
COMMAND: CNT=$(who|grep -c username)
1
$

I note that one of drl's examples includes this method already.

You might want to read this thread for a more detailed explanation about eval .

I hope this helps.

bakunin

Seems to be not possible to use the same variable to print to screen and to execute when mixing single quote and double quote :

PACKAGE_1="nxclient"
REPOS_1="zypper_local"
LOG_FILE_1="/tmp/nxclient_install_log.txt"
CMD='CNT=$(  grep -c -e  "Non-option program arguments: '"'$PACKAGE_1'"'"  -e  "Checking whether to refresh metadata for '$REPO_1'" -e  "Selecting  '"'$PACKAGE_1-3.5.0-7.x86_64'"' from repository  '"'$REPO_1'"' for installation."    $LOG_FILE_1 ) '

echo $CMD give :

CNT=$(  grep -c -e  "Non-option program arguments: 'nxclient'"  -e  "Checking whether to refresh metadata for zypper_local" -e  "Selecting  'nxclient-3.5.0-7.x86_64' from repository  'zypper_local' for installation."    $LOG_FILE_1 )

I must separate the initial variable into 2 temporaries variables :

CMD_1='CNT=$(  grep -c -e  "Non-option program arguments: '"'$PACKAGE_1'"'"  -e  "Checking whether to refresh metadata for '$REPO_1'" -e  "Selecting  '"'$PACKAGE_1-3.5.0-7.x86_64'"' from repository  '"'$REPO_1'"' for installation."'
CMD_2="  $LOG_FILE_1 ) "
CMD=$CMD_1$CMD_2

Now echo $CMD give :

CNT=$(  grep -c -e  "Non-option program arguments: 'nxclient'"  -e  "Checking whether to refresh metadata for zypper_local" -e  "Selecting  'nxclient-3.5.0-7.x86_64' from repository  'zypper_local' for installation."  /tmp/nxclient_install_log.txt )

But to execute the command using eval I must use another temporary variable CMD_3:

CMD_3='CNT=$(  grep -c -e  "Non-option program arguments: '"'"'$PACKAGE_1'"'"'"  -e  "Checking whether to refresh metadata for $LOCK_INSTAL$REPO_1" -e  "Selecting '"'"'$PACKAGE_1-3.5.0-7.x86_64'"'"' from repository '"'"'$REPO_1'"'"' for installation."  $LOG_FILE_1 ) '
eval $CMD_3

The full script i_s now :

PACKAGE_1="nxclient"
REPOS_1="zypper_local"
LOG_FILE_1="/tmp/nxclient_install_log.txt"
CMD_1='CNT=$(  grep -c -e  "Non-option program arguments:  '"'$PACKAGE_1'"'"  -e  "Checking whether to refresh metadata for  '$REPO_1'" -e  "Selecting  '"'$PACKAGE_1-3.5.0-7.x86_64'"' from  repository  '"'$REPO_1'"' for installation."'
CMD_2="  $LOG_FILE_1 ) "
CMD=$CMD_1$CMD_2
echo $CMD
CMD_3='CNT=$(  grep -c -e  "Non-option program arguments:  '"'"'$PACKAGE_1'"'"'"  -e  "Checking whether to refresh metadata for  $LOCK_INSTAL$REPO_1" -e  "Selecting '"'"'$PACKAGE_1-3.5.0-7.x86_64'"'"'  from repository '"'"'$REPO_1'"'"' for installation."  $LOG_FILE_1 ) '
eval $CMD_3

The sample log file is here SUSE Paste

Any idea would be appreciated.

It is indeed possible, but you have to be aware about the different number of times the line gets evaluated with and without eval.

If you use eval var='$(...)' to execute it you should probably print it with the same number of evals. But to be honest i have a feeling that your whole script is not planned very well because the structures you use are looking wrong anyway. I may be wrong, but if you post your whole script and tell us what you want to achieve we could find a better way to do it than with such crooked measures.

I hope this helps.

bakunin

If you try double quotes instead of single quotes, I think it becomes a bit more straight forward, you should then escape the double quotes with a backslash, plus the $-signs that you wish to protect from evaluation during assignment:

CMD="CNT=\$( grep -c -e \"Non-option program arguments: '$PACKAGE_1'\" -e \"Checking whether to refresh metadata for $REPO_1\" -e \"Selecting '$PACKAGE_1-3.5.0-7.x86_64' from repository '$REPO_1' for installation.\" \"$LOG_FILE_1\" )"

If you use a here-document you do not need the double quotes around the string and therefore you do not need to escape the double quotes inside:

read CMD << EOF
CNT=\$( grep -c -e "Non-option program arguments: '$PACKAGE_1'" -e "Checking whether to refresh metadata for $REPO_1" -e "Selecting '$PACKAGE_1-3.5.0-7.x86_64' from repository '$REPO_1' for installation." "$LOG_FILE_1" )
EOF

And you need to use double quotes around variable references:

printf "%s\n" "$CMD"
eval "$CMD"

A third option, since this is just a deferred command in a variable and there is no reason for evaluating one $sign earlier than the next you could use:

read CMD << "EOF"
CNT=$( grep -c -e "Non-option program arguments: '$PACKAGE_1'" -e "Checking whether to refresh metadata for $REPO_1" -e "Selecting '$PACKAGE_1-3.5.0-7.x86_64' from repository '$REPO_1' for installation." "$LOG_FILE_1" )
EOF

Then you do not need to escape any character, but then the variable names will show up when you print the content of $CMD, which you may or may not prefer...

My script is not doing more than the example I gave.

#!/bin/bash
#
My_CMD1="some texte and parameter $PARAM1 $PARAM2  without quote"
# Want to print to screen
# for debugging
echo "The command is : $MY_CMD1"
# now execute it
$MY_CMD1
# OK no problem with this case
#
#Trying this one
#
PARAM1="No"
PARAM2="Not installed"
PARAM3="FreeNX"
PARAM4="My_freenx_Directory"
PARAM5="/tmp/install_test/freenx.log"
#
zypper info -r $PARAM4 > $PARAM5
#
# We try to verify if the log file contains the information
# that prove that the software is not installed
# 4 parameters are nedeed
#
# For good understanding I replace all quote (simple or double) with this : %
# and each time the single quote ( ' )  is necessary I put it in the right place
# So the next line is not the real one
#
MY_CMD2=%CNT=$( grep -c  -e  %some_text1 $PARAM1%  -e  %some_text2 '$PARAM2'%  -e  %some_text3-1 $PARAM3-1 some_text3-2 '$PARAM4'%  -e %some_text4% $PARAM4%    $PARAM_5 ) %
#
# If the software is not installed, CNT = 4
#
# now try to print the command to screen ( very important for debugging )
echo "The command is : $MY_CMD2"
# now execute it
$MY_CMD2
# Problem with this case
#

I get this working with 2 conditions :
1�)I must separate the variable content (because of ( " ) and ( ' ) ) for printing or for execute
2�)I use eval for execute --> eval $MY_CMD2

#!/bin/bash
#
My_CMD1="some texte and parameter $PARAM1 $PARAM2  without quote"
# Want to print to screen
# for debugging
echo "The command is : $MY_CMD1"
# now execute it
$MY_CMD1
# OK no problem with this case
#
#Trying this one
#
PARAM1="No"
PARAM2="Not installed"
PARAM3="FreeNX"
PARAM4="My_freenx_Directory"
PARAM5="/tmp/install_test/freenx.log"
#
zypper info -r $PARAM4 > $PARAM5
#
# We try to verify if the log file contains the information
# that prove that the software is not installed
# 4 parameters are nedeed
#
# For good understanding I replace all quote (simple or double) with this : %
# and each time the single quote ( ' )  is necessary I put it in the right place
# So the next line is not the real one
#
# MY_CMD3 is use for echoing to the screen and have to put some more single and double quote.
#
MY_CMD3=%CNT=$( grep -c  -e  %some_text1 $PARAM1%  -e  %some_text2  '$PARAM2'%  -e  %some_text3-1 $PARAM3-1 some_text3-2 '$PARAM4'%  -e  %some_text4% $PARAM4%    $PARAM_5 ) %
#
# MY_CMD4 use some different single and double quote ,where single quote are mandatory.
#
$MY_CMD4=%CNT=$( grep -c  -e  %some_text1 $PARAM1%  -e  %some_text2  %%'$PARAM2'%%  -e  %some_text3-1 $PARAM3-1 some_text3-2  %%'$PARAM4'%%  -e  %some_text4% $PARAM4%    $PARAM_5 ) %
 #
# now try to print the command to screen ( very important for debugging )
echo "The command is : $MY_CMD3"
# now execute it
eval $MYCMD4
# No Problem with this case
#

CMD_3 -->

CMD='CNT=$(  grep -c -e  "Non-option program arguments: '"'$PACKAGE_1'"'"  -e  "Checking whether to refresh metadata for '$REPO_1'" -e  "Selecting '"'FreeNX-0.7.3-22.1.x86_64'"' from repository '"'$REPO_1'"' for installation."    '$LOG_FILE_1' ) '

CMD_4 -->

CMD='CNT=$(  grep -c -e  "Non-option program arguments: '"'"'$PACKAGE_1'"'"'"  -e  "Checking whether to refresh metadata for $REPO_1" -e  "Selecting '"'"'FreeNX-0.7.3-22.1.x86_64'"'"' from repository '"'"'$REPO_1'"'"' for installation."  $LOG_FILE_1 ) '
eval CMD$ 

Works great if quoted correctly as you show me.

The thread is closed.

Thank you every body for your help and for your patience.