How to quote nested sub-shell arguments correctly?
Asked Answered
T

1

8

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|
Tincher answered 7/3, 2017 at 15:30 Comment(1)
Great question, by the way -- clearly asked, shows your intent, shows what you tried, etc. Thank you for the effort. :)Thornhill
T
15

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.

Thornhill answered 7/3, 2017 at 15:32 Comment(1)
Note that the syntax highlighter is unaware of the new context, though. (Or at least, does a poor job of indicating it.)Grozny

© 2022 - 2024 — McMap. All rights reserved.