5

I have the following example.

#!/bin/bash
ARGUMENTS="-executors 1 -description \"The Host\" "

# call1
# error: parameter Host" is not allowed
java -jar swarm-client.jar $ARGUMENTS

# call2    
# works fine with eval
eval java -jar swarm-client.jar $ARGUMENTS

In $ARGUMENTS, I have a quoted argument. I do not understand why grouping of argument by escaped quotes is not working in call1. I do not understand why is eval necessary to resolve the quoting problem.

I think I do not understand the process and the order of command evaluation in shell. Can you explain it to me?

2 Answers 2

7

You don't pass quoted arguments to a command, you pass arguments.

When you enter:

cmd arg1 arg2

The shell parses that line in its own syntax where space is a word delimiter and calls cmd1 with cmd, arg1 and arg2 as arguments.

Note: cmd does not receive any space character in its arguments, the spaces are just operators in the shell language syntax.

Like when in C, you write func("foo", "bar"), at run time, func receives two pointer arguments, it does not see any of the ( or , or " or space character.

Also part of the shell syntax is quoting. " is used to be able to have words that contain characters that are otherwise part of the shell syntax.

When you do:

cmd "arg 1" arg2

cmd receives cmd, arg 1 and arg2 as arguments. It does not see any " character. Those " are used to prevent the space from being treated as a word separator in the shell syntax.

Now, when you do:

cmd $VAR

it's not the same as doing:

cmd the content of the variable

If it were, you'd have trouble with:

VAR='foo; reboot'
echo $VAR

for instance.

In Bourne-like shell, the content of $VAR is not passed verbatim as a single argument to cmd either (unfortunately; it's been fixed in some other shells like rc, es, fish and to a lesser extent zsh). Instead, it's subject to splitting and globbing (split+glob) and the resulting words passed to cmd.

The splitting is done based on the characters in the special $IFS variable, by default space, tab and newline.

For your $ARGUMENTS which contains -executors 1 -description "The Host", that's splitting into -executors, 1, -description, "The and Host". Since none of those words contain wildcard character, the glob part doesn't apply, so it's those words that are passed to cmd.

Here, you could use the split+glob operator, and use as separator for the splitting part a character that does not appear in those words:

ARGUMENTS='-executors|1|-description|The Host'
IFS='|'
cmd $ARGUMENTS

Or better, for shells that support them (like bash), use arrays, where you can have a variable that contains all those arguments.

eval is to evaluate shell code. So the other option is to have ARGUMENTS contain shell code (text in the shell syntax as opposed to a list of arguments), and have that passed to eval for interpretation. But remember to quote the variable to avoid the split+glob operator:

eval "cmd $ARGUMENTS"
7

When a quote is inside another string, bash treats it as just another character. Use arrays instead:

args=(-executors 1 -description "The Host")
java -jar swarm-client.jar "${args[@]}"

For a good discussion of this issue, see "I'm trying to put a command in a variable, but the complex cases always fail!".

Also, use lower-case or mixed-case names for your variables. The system uses all capitals for its variable names and you don't want to accidentally overwrite one of them.

How it works

When the array args is defined, separate quoted strings become separate items in the array. We can see this by using declare -p arrayname to examine what is in the array:

$ args=(-executors 1 -description "The Host")
$ declare -p args
declare -a args=([0]="-executors" [1]="1" [2]="-description" [3]="The Host")

As we can see, the string The Host is element 3 in the array.

When bash expands the special form "${args[@]}", each element of the array becomes a separate word. In this way, the string The Host remains one word even though it contains spaces.

1
  • Thnx for the solution with the array - it seems clearer than using eval. Still I do not understand how the evaluation of items in array happens, when the "${args[@]}" is expanded. What is evaluated different, when I use array expansion instead of strings. Any tips on the literature to learn that?
    – Skip
    Jan 12, 2018 at 13:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .