I am writing a script to call a command (weka, if you are curious) a couple of times with some different parameters. One of the parameters, '-search "< stuff >" ' needs to be quoted. In some cases I need to use multiple of these parameters eg.

weka a -search "a params" -search "other a params"

weka b -search "just these params"`

I've been trying to use an associative array that looks something like:

search=( ["a"]='-search "a params" -search "other a params"'
["b"]='-search "just these params"'

Then make the call like:

function call_thing_with_the_right_parameters_so_it_works_out_alright {
    weka $1 ${search["$1"]}
} # <-- closing brace for the function

Alas, no matter what I try, the quoting gets all buggered up:

bash -x ./this_is_the_name_of_my_script_sorry_its_so_long
+ weka a -search '"a' 'params"' # <-- (not what I want)

Any ideas?

  • Shouldn't you use either a or b after the function name to call it? Like this: call_thing_with_the_right_parameters_so_it_works_out_alright a – user79743 Apr 28 '16 at 2:13
  • Is that the whole line? In weka a -search '"a' 'params"'. I can not reproduce your problem. – user79743 Apr 28 '16 at 2:16
  • Of course, you should quote better in this line weka $1 ${search["$1"]} you should use this instead: weka "$1" "${search["$1"]}" – user79743 Apr 28 '16 at 2:24

The line in your code weka $1 ${search["$1"]} is being subject to shell spliting.
If you have not changed the variable $IFS that splitting will happen on spacetabnew line.

The line gets expanded to:

weka $1 ${search["$1"]}
weka a -search "a params" -search "other a params"

But getting split as described above, this is what it means:

<weka> <a> <-search> <"a> <params"> <-search> <"other> <a> <params">

You can see exactly the same prepending printf to the line:

$ printf '<%s> ' weka $1 ${search["$1"]}; echo
<weka> <a> <-search> <"a> <params"> <-search> <"other> <a> <params">

This will get better if the variables are correctly quoted:

$ printf '<%s> ' weka "$1" "${search["$1"]}"; echo
<weka> <a> <-search "a params" -search "other a params">

But that is not what you want. You need to split it, but not on simple spaces.

What to do?

There are two solutions:

Split manually

Use some character like # to manually mark the split position:

search=( ["a"]='-search#a params#-search#other a params' ["b"]='-search#just these params' )

And then tell bash which is the character you used to split with IFS, so it could split the string in a new array b:

b=( ${search["a"]} )
printf '<%s> ' "${b[@]}"; echo

Which produces:

<-search> <a params> <-search> <other a params> 

The exact splitting you want.
The only problem is that IFS got changed, but we can solve that in a function making IFS local:

    local IFS='#'
    b=( ${search["$1"]} )
    weka "$1" "${b[@]}"

Use Eval

The other solution is to use eval to re-parse the command line so the shell could split it as is the common way of shells to split lines.
The value of the variable search will be as you defined it:

search=( ["a"]='-search "a params" -search "other a params"'  
         ["b"]='-search "just these params"' )

But we will expand the execute line with eval:

eval weka "$1" "${search["$1"]}"

If you want to see how is the line expanded, use this:

$ eval printf "'<%s> '" weka "$1" "${search["$1"]}"; echo
<weka> <a> <-search> <a params> <-search> <other a params>

The whole script will be:

declare -A search
search+=( ["a"]='-search "a params" -search "other a params"')
search+=( ["b"]='-search "just these params"' )

call_thing() {
    eval weka "$1" "${search["$1"]}"
} # <-- closing brace for the function

call_thing "a"

Note: That will work correctly assuming that the values of search are set inside the script (no external attacker could set them) and those values are quoted as a common shell "command line" is quoted.

Warning: The use of eval may allow data as an string to be converted into code like a command. In this specific script, this line:

call_thing "a; touch new_file"

Will execute the command touch new_file. But any other command could also be executed. Be careful, very careful with what you feed to eval.

Having said the above, remember that there are many dangerous commands that could be executed in shell, like rm -r /. The command eval is not more powerful than any of those. Just be careful.

  • 1
    That's exactly why I added a comment stating: values of search are set inside the script (no external attacker could set them). If the script writer choose to create an string that when evaluated creates a file, well ... it is what the script writer is asking the shell to do. – user79743 Apr 28 '16 at 7:17
  • Yeah, I could use eval in this case, but don't want to make a habit out of it I guess. I think the manual splitting works fine in my case as well. Thanks! – Mike Apr 28 '16 at 16:54
  • @BinaryZebra Very good. – John1024 Apr 28 '16 at 17:26

The shell does not handle quoted quotes the way that you want. (Once the quotes are quoted, they are treated like regular characters.)

You can trick bash into doing what you want. To start define an associative array:

declare -A s
s=( ["a"]='-search;a params;-search;other a params;' ["b"]='-search;just these params' )

As you can see, the string has the arguments for each command separated by a semicolon. To extract, for example, the arguments to be used for a, we tell the shell to use ; as the field separator:

IFS=\; v=(${s[a]})

You can verify that array v now has the fields that you want as follows:

$ declare -p v
declare -a v='([0]="-search" [1]="a params" [2]="-search" [3]="other a params")'

You can then run weka as:

weka a "${v[@]}"

So, putting it all together, your function definition might look like:

declare -A s
s=( ["a"]='-search;a params;-search;other a params;' ["b"]='-search;just these params' )
callthing() (
        IFS=\; v=(${s[$1]})
        weka "$1" "${v[@]}"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.