How to preserve double quotes in $@ in a shell script?

Viewed 11378

Let's say I have a really simple shell script 'foo':

  #!/bin/sh
  echo $@

If I invoke it like so:

  foo 1 2 3

It happily prints:

  1 2 3

However, let's say one of my arguments is double-quote enclosed and contains whitespace:

  foo 1 "this arg has whitespace" 3

foo happily prints:

  1 this arg has whitespace 3

The double-quotes have been stripped! I know shell thinks its doing me a favor, but... I would like to get at the original version of the arguments, unmolested by shell's interpretation. Is there any way to do so?

6 Answers

The most reliable method that I've found to do this is to leverage the logic that's built into the shell for "xtrace". This is a subsystem that's turned on via set -x which will print every command that's run by the shell. If we turn on xtrace then run a command that does nothing (true) with the same arguments, the shell (most shells...) will quote the arguments for us.

show_quoted() {
  (
    # use > as the xtrace prefix (this is the default)
    PS4='+'
    exec 2>&1  # send the xtrace (on stderr, 2) to stdout (1) for processing
    # turn on xtrace -- all commands are printed, with $PS4 as a prefix
    set -x
    # `true` is the command that does nothing, successfully
    true "$@"
  ) |
    sed '
      # remove the xtrace prefix -- any number of + characters
      s/^+*//
      # hide the "true" command
      s/^true //
    '
}

Same thing, but with a normal amount of comments:

show_quoted() {
  (
    PS4='+'  # reset to default
    exec 2>&1  # send xtrace to stdout
    set -x
    true "$@"
  ) | sed 's/^+*true //'  # remove the xtrace prefix
}

Here's my test results: (these vary a bit but are all valid)

$ sh show_quoted.sh 1 '2 3' 'he said: "hi"' "she said: 'bye'"
1 '2 3' 'he said: "hi"' 'she said: '\''bye'\'''

$ bash show_quoted.sh 1 '2 3' 'he said: "hi"' "she said: 'bye'"
1 '2 3' 'he said: "hi"' 'she said: '\''bye'\'''

$ zsh show_quoted.sh 1 '2 3' 'he said: "hi"' "she said: 'bye'"
1 '2 3' 'he said: "hi"' 'she said: '\''bye'\'

$ busybox sh show_quoted.sh 1 '2 3' 'he said: "hi"' "she said: 'bye'"
1 '2 3' 'he said: "hi"' 'she said: '"'"'bye'"'"

These shells (below) weren't up to the task however. To be fair, the toybox shell is "80% done".

$ dash show_quoted.sh 1 '2 3' 'he said: "hi"' "she said: 'bye'"
1 2 3 he said: "hi" she said: 'bye'

$ ./toybox sh show_quoted.sh 1 '2 3' 'he said: "hi"' "she said: 'bye'"
"$@"
Related