3

$@ 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'
user3477071
  • 194
  • 1
  • 13
  • 1
    The quotes are generally eaten by the (interactive) shell that calls your script. The effect of the quotes (grouping multiword values) has already served its purpose by the time your code is run. the value `one two` has already been assigned to `$1`. – Raxi Dec 30 '21 at 15:35
  • 1
    Generally, if you want to pass on values in escaped form, for example when passing on / calling another script , then you can use `printf %q`. It auto-escapes values so they pass into the next program as-is. It doesn't use quotes but instead it just backslash-escapes the space(s), which has the same endresult. – Raxi Dec 30 '21 at 15:37
  • The need is to print arguments in "$@" or "${ARRAY[@]}$" in the the same way it gets passed to invoked script before invoking it. – user3477071 Dec 30 '21 at 15:50
  • @user3477071 But *why*? Consider `printf '%s\n' "$@"`. It will print `one two` on one line, `three` on the next line. Just because the quotes are missing doesn't mean you aren't preserving the distinction between the two arguments. – chepner Dec 30 '21 at 16:01
  • I need everything in quoted form in the same line to see what arguments get passed to a command before the command gets invoked in a script. There is a frequent need for this in debugging commands invoked from shell scripts. – user3477071 Dec 30 '21 at 16:16
  • @user3477071 Then try `printf "'%s' " "$@"; echo` – M. Nejat Aydin Dec 30 '21 at 16:18
  • @Nejat printf gives each argument in separate line. Hauri's answer, gives exactly what I need in the same line. – user3477071 Dec 30 '21 at 16:20
  • i realise you've got your answer, but just to add: his newline was explicit, without the `\n` you would get it on one line; and `printf` can use the same effect as `${1@Q}` when using `%q` instead of `%s`, – Raxi Dec 30 '21 at 16:34
  • 1
    Your `for` loop already shows you each argument at a time; the quotes don't give you any additional information. – chepner Dec 30 '21 at 17:06

1 Answers1

3

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...

F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
  • Also `printf %q\\n "'one two' three"` – Léa Gris Dec 30 '21 at 16:39
  • @LéaGris Right, answer edited! – F. Hauri - Give Up GitHub Dec 30 '21 at 16:59
  • 3
    @user3477071, you said you were looking for the _exact way_ the value is quoted on input. That does not give you that -- it'll print `'one two'` on output even if the input was `one\ two` or `one" "two` or `'one 'two` or any other way of writing the exact same value. (That said, the reason it doesn't give you that is that it's _impossible to get_; values are passed as an array of C strings after all the parsing is already done, so the details of what input was transformed into that C string are gone) – Charles Duffy Dec 30 '21 at 17:06
  • @CharlesDuffy : Thanks for your caveat on input, where both approaches will fail. "$@" received by my script, will already be single quoted or double quoted, if any argument, has any special characters. Wanted some way to see how positional parameters are broken down when "$@" gets passed to invoked command. I am hoping, setting CMD=" ${@@Q}" followed by echo "$CMD" and eval "$CMD" should do the job. – user3477071 Dec 31 '21 at 01:42
  • To be clear, `printf %q` and `${var@Q}` will always give you something _that parses to an identical value_, even if it's not written out the same way. _All_ the examples I gave are identical -- if you start a C program with `someprog one\ two` or `someprog one" "two` or so forth, they all get `char[][]{"someprog", "one two", NULL}` as their argv. But the point is that you can't get from that `char[][]{"someprog", "one two", NULL}` back to `someprog 'one two'`, as opposed to `someprog one\ two` or whichever other specific invocation the user used; that detail is known only to the calling shell – Charles Duffy Dec 31 '21 at 02:53
  • ...and not to whichever program the shell invoked. – Charles Duffy Dec 31 '21 at 02:55
  • @CharlesDuffy: Get your point that different inputs to the shell can result in the same "$@" and once inside a script or program, one cannot map back to the 'exact' way command line arguments were presented to the shell which resulted in "$@". Not sure, if there are situations where this reverse engineering would be necessary. I am happy with $1=one two which results from shell presented values of one\ two or one" "two or 'one 'two. All I am trying to do is to print what is in "$@" in a quoted form for debugging purposes before invoking 'someprog'. – user3477071 Dec 31 '21 at 19:29
  • Gotcha. From your objections to chepner's comments on the question, it looked like you were insisting on getting the original formatting, which is why I was digging in on the point. – Charles Duffy Dec 31 '21 at 22:18
  • @CharlesDuffy I didn't object anything from chepner's comment! (Except about [use of EPOCHSECOND along **with** EPOCHREALTIME](https://stackoverflow.com/questions/55436913/printing-current-time-in-milliseconds-or-nanoseconds-with-printf-builtin/58557346#comment103519943_55437805), but this is an old thread). Maybe answered too quickly (before finding duplicate). Anyway, your comment is referenced in my answer! – F. Hauri - Give Up GitHub Jan 01 '22 at 08:53