Quoting is not allowed inside a bash variable

I am storing the arguments in a command in a variable. The last command I want is:

mock -r myconfig --define "debug_package %{nil}" --resultdir results --rebuild mypackage.src.rpm

      

Here's my attempt:

set -x    # for debugging

RESULTDIR=results
MOCK_CONFIG="myconfig"
MOCK_ARGS="-r $MOCK_CONFIG --define \"debug_package %{nil}\" --resultdir $RESULTDIR"
cmd="mock $MOCK_ARGS --rebuild mypackage.src.rpm"
$cmd

      

Results:

+ RESULTDIR=results
+ MOCK_CONFIG=myconfig
+ MOCK_ARGS='-r myconfig --define "debug_package %{nil}" --resultdir results'
+ cmd='mock -r myconfig --define "debug_package %{nil}" --resultdir results --rebuild mypackage.src.rpm'
+ mock -r myconfig --define '"debug_package' '%{nil}"' --resultdir results --rebuild mypackage.src.rpm
ERROR: Bad option for '--define' ("debug_package).  Use --define 'macro expr'

      

As you can see, the parameter arguments were --define

not specified correctly. --define

thinks that I am passing it on only debug_package

, which is incomplete.

I've tried different variants of quotes when defining MOCK_ARGS

, even trying to avoid the space between debug_package

and %{nil}

.

What combination of quotes and / or screens allows me to create this list of arguments and execute a command from this script?

EDIT:

The reason I am storing the resulting command in a variable is because it gets passed into a function that does some registration and then executes the command.

Also, I came across this FAQ which suggests that I should be using arrays instead of a variable. I started experimenting with arrays but still don't have a working solution.

+2


source to share


2 answers


Arrays are the way to go about things like this. This is what I came up with:

log_and_run() {
    echo "$(date): running command: $*"
    "$@"
    echo "$1 completed with status $?"
}

RESULTDIR=results
MOCK_CONFIG="myconfig"
MOCK_ARGS=(-r "$MOCK_CONFIG" --define "debug_package %{nil}" --resultdir "$RESULTDIR")
cmd=(mock "${MOCK_ARGS[@]}" --rebuild mypackage.src.rpm)
log_and_run "${cmd[@]}"
# could also use: log_and_run mock "${MOCK_ARGS[@]}" --rebuild mypackage.src.rpm

      

Note that $ * expands to all parameters, separated by spaces (good for going to log command); while "$ @" expands to all parameters as single words (as used in log_and_run, the former will be treated as a command to execute, the rest as parameters to it); Likewise, "$ {MOCK_ARGS [@]}" expands to all MOCK_ARGS elements as single words, whether or not they contain spaces (hence every MOCK_ARGS element becomes a cmd element, no confusion with quoting parsing).

Also, I have quoted the MOCK_CONFIG and RESULTDIR extensions; this is not necessary here because they do not contain spaces, but it is a good habit to get in if they ever contain spaces (for example, is it passed to your script from the outside? Then you have to assume that it might contain spaces).

BTW, if you need to pass additional parameters to the function (I'll use an example of a login file name), you can use slicing arrays to separate only the later parameters for use as a login / execute command:



log_and_run() {
    echo "$(date): running command ${*:2}" >>"$1" 
    "${@:2}"
    echo "$2 completed with status $?" >>"$1"
}
#...
log_and_run test.log "${cmd[@]}"

      

Addendum: if you want to specify spatial parameters in the log, you can "manually" build the log line and do whatever you want. For example:

log_and_run() {
    local log_cmd=""
    for arg in "$@"; do
        if [[ "$arg" == *" "* || -z "$arg" ]]; then
            log_cmd="$log_cmd \"$arg\""
        else
            log_cmd="$log_cmd $arg"
        fi
    done
    echo "$(date): running command:$log_cmd"
    ...

      

Note that this will handle spaces in parameters and empty parameters, but not (for example) double quotes inside parameters. Doing this "right" can get as difficult as you want ...

Also the way I build the log_cmd line, it ends with a leading space; I processed this above by dropping the space in front of it in the echo command. If you need to trim space, use "${log_cmd# }"

.

+4


source


Shell scripts can be poor. In many cases it cmd="blah $blah";$cmd

differs from blah $blah

. You can try eval $cmd

$ cmd instead.

Try calling this perl script instead of mock:

#!/usr/bin/perl
print join("\n",@ARGV),"\n";

      

You might be surprised to see actually the list of arguments:

-r
myconfig
--define
"debug_package
%{nil}"
--resultdir
results
--rebuild
mypackage.src.rpm

      

Although I think the "set -x" output is trying to show you this with single quotes.

One nice trick I recently learned is that



function f {
  echo "$@"
}

      

Really correctly passes positional arguments to f ( $*

).

It looks like "eval" gives what you probably intend:

set -x    # for debugging
RESULTDIR=results
MOCK_CONFIG="myconfig"
MOCK_ARGS="-r $MOCK_CONFIG "'--define "debug_package %{nil}" --resultdir '"$RESULTDIR"
cmd="mock $MOCK_ARGS --rebuild mypackage.src.rpm"
echo $cmd
eval $cmd

      

Output:

+ echo mock -r myconfig --define '"debug_package' '%{nil}"' --resultdir results --rebuild mypackage.src.rpm
mock -r myconfig --define "debug_package %{nil}" --resultdir results --rebuild mypackage.src.rpm
+ eval mock -r myconfig --define '"debug_package' '%{nil}"' --resultdir results --rebuild mypackage.src.rpm
++ mock -r myconfig --define 'debug_package %{nil}' --resultdir results --rebuild mypackage.src.rpm
-r
myconfig
--define
debug_package %{nil}
--resultdir
results
--rebuild
mypackage.src.rpm

      

+4


source







All Articles