Printing command line arguments in quoted form

Viewed 324

$@ when double quoted("$@") expands to "$1" "$2" "$3" ...

There seems to be no easy way to print quoted form of what is inside "$@"

$ set -- "one two" three  
$ echo "$@"
one two three

What I am looking for is, output of

'one two' three

Using 'set -x', does echo quoted form of arguments inside "$@"

$ set -x; echo "$@"; set +x
+ set -x
+ echo 'one two' three
one two three
+ set +x

User defined variable $DOLLAR_AT contains same things inside it, as what "$@" contains

$ DOLLAR_AT="'one two' three"

Dumping out, what is inside $DOLLAR_AT, using 'echo' works as expected

$ echo $DOLLAR_AT
'one two' three

due to some kind of automatic quoting by the shell of arguments that contain special characters

$ set -x; echo $DOLLAR_AT; set +x
+ echo ''\''one' 'two'\''' three
+ set +x

It is not clear why

$ echo "$@"

does not produce same output as

$ echo "$DOLLAR_AT"

When starting a command from a shell script using received "$@", there is a need to print out what is inside "$@" in a quoted form before invoking the command.

There is a similar need in printing out command line arguments passed from a bash array.

$ CMDARGS=("one two" "three")

Evaluating the array within double quotes does not print array elements in a quoted form

$ echo "${CMDARGS[@]}"
one two three

I also need a compact way to print what is in $CMDARGS[@] in a quoted form similar to what comes out of this iteration

$ for ARG in "${CMDARGS[@]}"; do echo \'$ARG\'; done
'one two'
'three'
1 Answers

1. Using parameter expansion:

You could try inline:

$ set -- 'foo bar' baz
$ echo ${@@Q}
'Foo bar' 'baz'

$ echo ${@@A}
set -- 'Foo bar' 'baz'

$ DOLLAR_AT="'one two' three"
$ echo ${DOLLAR_AT@Q}
''\''one two'\'' three'

$ echo ${DOLLAR_AT@A}
DOLLAR_AT=''\''one two'\'' three'

More info in bash's manpage, under Parameter Expansion subsection.

   ${parameter@operator}
         Parameter transformation.  The expansion is either a transforma‐
         tion  of  the  value of parameter or information about parameter
         itself, depending on the value of operator.  Each operator is  a
         single letter:
 ...
         Q      The  expansion is a string that is the value of parameter
                quoted in a format that can be reused as input.
 ...
         A      The expansion is a string in the form  of  an  assignment
                statement  or  declare  command  that, if evaluated, will
                recreate parameter with its attributes and value.

For re-using this:

$ myString1=${@@Q}
$ set -- something\ else
$ . <(echo "set -- $myString1")
$ echo ${@@A}
set -- 'Foo bar' 'baz'

1.1. Difference between $* and $@.

Simply $* is used to generate strings where $@ could generate both arrays or stringS, depending on context.

From bash's manpage:

   *      Expands to the positional parameters, starting from  one...
          ...   That  is, "$*" is equivalent to "$1c$2c...", where  c is
          the first character of the value of the IFS variable...
   @      Expands to the positional parameters,  starting  from  one...
          ...  That is, "$@" is equivalent to "$1" "$2" ... 

Small demo:

$ paramExpDemo() {
    local IFS=+ _arry
    _arry="${@@Q}"         #  var="..." -> context string
    declare -p _arry

    _arry="${*@Q}"
    declare -p _arry

    _arry=("${@@Q}")       #  var=(...) -> context array
    declare -p _arry

    _arry=("${*@Q}")
    declare -p _arry
}

then

$ paramExpDemo  'foo bar' baz
declare -- _arry="'foo bar'+'baz'"
declare -- _arry="'foo bar'+'baz'"
declare -a _arry=([0]="'foo bar'" [1]="'baz'")
declare -a _arry=([0]="'foo bar'+'baz'")

In context string, $@ and $* will give same result, but in context array, $* will return a string when $@ return an array.

$ set -- 'foo bar' baz
$ Array=("${@@Q}")
$ declare -p Array
declare -a Array=([0]="'foo bar'" [1]="'baz'")
$ echo "${Array[@]@Q}"
''\''foo bar'\''' ''\''baz'\'''

2. Using printf %q

As Léa Gris rightly commented out, you could use:

$ printf '%q\n' "$@"
Foo\ bar
baz

$ printf -v myString2 "%q " "$@"
$ echo "${myString2% }"
Foo\ bar baz 

For re-using this:

$ set -- something\ else
$ . <(echo "set -- $myString2")
$ echo ${@@A}
set -- 'Foo bar' 'baz'

Note: As Charles Duffy commented out,this is not perfect! In fact, there is no way to retrieve which syntax was used in command line.

$ printf "<%q>\n" "$myString1" "${myString2% }"
<\'Foo\ bar\'\ \'baz\'>
<Foo\\\ bar\ baz>

As both string work for sourcing variable, they are not same, so further string manipulation could become complex...

Related