Nested quotes in Bash

Viewed 825

Please let me explain my scenario with some sample script which is much simpler than my actual application but shows the same behavior:

For the demo I use two bash shell scripts. The first one is called argtest.sh simply outputs the command line parameters:

#/bin/bash

echo "Argument 1: $1"
echo "Argument 2: $2"

Test:

root@test:~# ./argtest.sh 1 2
Argument 1: 1
Argument 2: 2

and

root@test:~# ./argtest.sh "1 2" 3
Argument 1: 1 2
Argument 2: 3

This works as expected

I've created another command like this:

./argtest.sh $(for i in ` seq 1 2 ` ; do echo Number $i; done) 

This is the result

Argument 1: Number
Argument 2: 1

I've tried many different combinations of quotation marks (") and escaped quotation marks (\") around Number, but no combination produced the desired output of:

Argument 1: Number 1
Argument 2: Number 2

How can I get to that output?

4 Answers

You can't. The command substitution is subject to word-splitting. Its output is two lines

Number 1
Number 2

but newlines are treated as arbitrary whitespace during word splitting. As a result, the command substitution is split into 4 words (Number, 1, Number, and 2), each of which is a separate argument to argtest.sh.

Additional quotes would treated literally during word-splitting; a string like "a b" c is split into 3 words ("a, b", and c), not 2 (a b, c).

To do what you want, you need to use a while loop to read one line of output at a time from your loop. For example:

for i in $(seq 1 2); do
  echo "Number $i"
done | while read -r line; do
  ./argtest.sh "$line"
done

As chepner mentioned, command substitution is subjected to word splitting.

Below are few examples to better understand this

Here is the behaviour when I replace echo with printf and escape the double quotes:-

for i in ` seq 1 2 ` ; do printf "\"Number %d\" " "$i"; done; printf "\n"
"Number 1" "Number 2"

./arg.sh  $(for i in ` seq 1 2 ` ; do printf "\"Number %d\" " "$i"; done; printf "\n")
Argument 1: "Number                                                                   
Argument 2: 1"     

Now if I try to enclose the whole command substitution in double quotes, it will consider them as one single argument:-

./arg.sh "$(for i in ` seq 1 2 ` ; do printf "\"Number %d\" " "$i"; done; printf "\n")"
Argument 1: "Number 1" "Number 2"                                                                                                                         
Argument 2:                                                                                                                                                                      

because arguments are subject to word splitting, you may change $IFS and put the the new separator into your echo

IFS=$'_\n'
./argtest.sh $(for i in {1..2} ; do echo "Number ${i}_"; done )

don't forget to restore the old $IFS

oIFS=$IFS
...
IFS=$oIFS

btw {1..2} is equivalent to seq 1 2 but you don't need an external command

You can use xargs

for i in ` seq 1 2 ` ; do echo Number $i; done | xargs -d $'\n' ./argtest.sh
Related