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|
ceving
  • 21,900
  • 13
  • 104
  • 178
  • 1
    Great question, by the way -- clearly asked, shows your intent, shows what you tried, etc. Thank you for the effort. :) – Charles Duffy Mar 07 '17 at 15:48

1 Answers1

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.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 3
    Note that the syntax highlighter is unaware of the new context, though. (Or at least, does a poor job of indicating it.) – chepner Mar 07 '17 at 15:33