Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to quote nested sub-shell arguments correctly?

Tags:

bash

quoting

How to pass a string with spaces to a command which returns itself a string with spaces?

I tried the following four versions.

arg='one arg'

arg() { echo "arg: $1"; }

printf '1 |%s|\n' $(arg "$arg")
printf '2 |%s|\n' "$(arg $arg)"
printf '3 |%s|\n' "$(arg \"$arg\")"
printf '4 |%s|\n' "$(arg '$arg')"

They all fail:

1 |arg:|
1 |one|
1 |arg|
2 |arg: one|
3 |arg: "one|
4 |arg: $arg|

How to get this result?

? |arg: one arg|
like image 436
ceving Avatar asked Mar 07 '17 15:03

ceving


1 Answers

The Syntax

Using $() creates a new quoting context. Thus, double quotes inside a command substitution are completely independent of those outside it, and inside of closing the outer double quotes start a new and independent pair.

arg='one arg'

arg() { echo "arg: $1"; }

printf '? |%s|\n' "$(arg "$arg")"

...properly emits:

? |arg: one arg|

The Rationale (And History)

With the above syntax, adding additional nesting layers is easy:

printf '%s |%s|\n' "$(arg "$(arg "$arg")")"

With the pre-POSIX backtick syntax instead of $(), your attempt #3 would have been correct:

printf '3 |%s|\n' "`arg \"$arg\"`"

However, needing to backslash-escape both quotes and nested backticks gets unworkable quickly as your nesting depth increases. Adding just one more nested arg makes it:

printf '3a |%s|\n' "`arg \"\`arg \\\"$arg\\\"\`\"`"

Adding two additional layers (thus, three arg function invocations total) is even worse, getting you into:

printf '3b |%s|\n' "`arg \"\`arg \\\"\\\`arg \\\\\\\"$arg\\\\\\\"\\\`\\\"\`\"`"

Whereas with the modern syntax, it's just:

printf '3b |%s|\n' "$(arg "$(arg "$(arg "$arg")")")"

Much, much easier.

like image 199
Charles Duffy Avatar answered Nov 16 '22 15:11

Charles Duffy