Arguments in variable vs direct string

Hello Community!
Let's say that we have some script which counts its arguments number:
arguments_count.sh:

#!/bin/sh
echo "Number of arguments="$#

and some test script:
test.sh:

#!/bin/sh
my_args="1 2 3 '4 5' 6"
echo "Count of arguments when using my_args:"
./arguments_count.sh $my_args
echo "Count of arguments when using direct string:"
./arguments_count.sh 1 2 3 '4 5' 6

Output after execute test.sh is:

Count of arguments when using my_args:
Number of arguments=6
Count of arguments when using direct string:
Number of arguments=5

What should I do to get output for "my_args" the same as for direct string?

Did you try double quoting $my_args ?

Yes and the result is "1 argument".

Yes I see; just found out myself. hmmm - I don't seem to achieve the desired result playing around... sorry.

I wonder if not because comparing a variable with many values...What about comparing the same ?:

./arguments_count.sh $(echo 1 2 3 '4 5' 6) 

The problem is in the field splitting, after expansion of variable my_args . It is split into 6 fields:
1 2 3 '4 5' and 6
You see you called it my_args, but you turned it into a string instead of discrete arguments. By unquoting the string expansion you are not getting the arguments back, but 6 fields that stem from that string split on white space...

An alternative would be to use positional parameters:

set -- 1 2 3 '4 5' 6
echo "Count of arguments when using positional parameters"
./arguments_count.sh "$@"

--
If you are using bash, ksh93 or zsh you could try arrays:

my_args=(1 2 3 '4 5' 6)
echo "Count of arguments when using my_args array:"
./arguments_count.sh "${my_args[@]}"
Count of arguments when using positional parameters
Number of arguments=5
Count of arguments when using my_args array:
Number of arguments=5
echo "$4"; echo "${my_args[3]}"
4 5
4 5

Scrutinizer is - as always - correct, but i suppose the problem comes from not understanding the quotation process:

The shell maintains a SINGLE flag for being inside a single- or double-quoted string. That means, that quotes can - unlike brackets - in no way be nested!

Consider:

(abc(def)ghi)
"abc"def"ghi"

These are completely different: there is not a string "def", which is part of a larger string "abc"def"ghi" (like the brackets-expression might be) but a string "abc", another string "ghi" and some unquoted "def" in between these two. So far so obvious and generally known.

But when it comes to single-quoted strings inside double-quoted strings some people think these are somehow nested:

"abc'def'ghi"

But this is not the case at all! In fact a single-quote character loses it special power to start or end a quotation inside a double.quoted string (and vice versa). That is, in the above example, the character between "c" and "d" is just an ordinary character, like "x" or any other.

If you want to create "nested quotes", which "peel off" one layer after the other you have to use escapes:

$ my_args="a b c \'d e\' f g"
$ arguments_count.sh $my_args

I hope this helps.

bakunin

I hope this helps.

bakunin

Hello!
Thank You all for the answers.
The best solution for now is:
test.sh:

#!/bin/sh
my_args="1 2 3 '4 5' 6"
echo "Count of arguments when using my_args:"
echo "./arguments_count.sh $my_args" > ./my_args.sh    #put whole command into a file 
sh ./my_args.sh                        #run the file
echo "Count of arguments when using direct string:"
echo "./arguments_count.sh 1 2 3 '4 5' 6" > ./my_string.sh    #put whole command into a file
sh ./my_string.sh                        #run the file

That is double parsing/interpretation. It is equivalent to

eval /.arguments_count.sh "$my_args"

Which has the same security concerns unless you are in control of the input..

----

Hi Bakunin, this would offer no different result, since the since the unquoted variable expansion of my_args would still be subjected to field splitting into 7 arguments:

$ printf "%s\n" $my_args
a
b
c
\'d
e\'
f
g

within the same parsing run, there is no way to recover the intended quoted fields from the string contained in the variable, if it is subjected to field splitting with the standard IFS.

Thank You. Eval also solves the problem.

Hi Break_da_funk. Yes that could solve your problem, but if at all possible, such an approach is best avoided, given the security concerns that come with the use of eval or double shell interpretation ..

I will remeber about this :slight_smile:

If you can possibly avoid it, just don't do that - these are all ugly hacks to accommodate something structured inside-out and backwards. But if you absolutely have to, xargs can handle them.

People loathe xargs for processing quotes instead of handling them raw, but that's actually useful here. It will handle quotes but will ignore shell syntax -- which makes it much safer than eval.

UGLYMESS="a b 'c d' e"
OLDIFS="$IFS"

# Split unquoted strings on newlines and ONLY newlines.
IFS="
"

# xargs printf "%s\n" will print every separate argument or quoted section on its own newline.
# You could set $1=a, $2=b, $3="c d", $4=e
set -- $(echo "$UGLYMESS" | xargs printf "%s\n" )
# ...or put them into an array like this:
ARR=( $(echo "$UGLYMESS" | xargs printf "%s\n" ) )

# Now that we've stored them already split, we can restore IFS.
IFS="$OLDIFS"
1 Like