Bash – Can a shell script prints its argument, quoted as you would write them on the shell prompt

argumentsbashquotingshell-script

In a shell script, my understanding is that "$@" expands to the script arguments, quoting them as needed. For instance this forwards the script arguments to gcc:

gcc -fPIC "$@"

When using the bash pass-to-stdin syntax <<< though, "@$" doesn't work as I would expect it to.

#!/bin/bash
cat <<< "$@"

Calling the script as ./test.sh foo "bar baz" gives

foo bar baz

I would expect

foo "bar baz"

Is there a way to write a shell script that prints it's arguments as you would write them on the shell prompt? For instance: a hint as to what command to use next, including the script arguments in the hint.

Best Answer

Well, "$@" expands to the list of positional parameters, one argument per positional parameter.

When you do:

set '' 'foo bar' $'blah\nblah'
cmd "$@"

cmd is being invoked with those 3 arguments: the empty string, foo bar and blah<newline>blah. The shell will call the execve() system call with something like:

execve("/path/to/cmd", ["cmd", "", "foo bar", "blah\nblah"], [envvars...]);

If you want to reconstruct a shell command line (that is code in the shell language) that would reproduce that same invocation, you could do something like:

awk -v q="'" '
  function shellquote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  BEGIN {
    for (i = 1; i < ARGC; i++) {
      printf "%s", sep shellquote(ARGV[i])
      sep = " "
    }
    printf "\n"
  }' cmd "$@"

Or with zsh, asking for different types of quotes:

$ set '' 'foo bar' $'blah\nblah'
$ print -r -- cmd "${(q)@}"
cmd '' foo\ bar blah$'\n'blah
$ print -r -- cmd "${(qq)@}"
cmd '' 'foo bar' 'blah
blah'
$ print -r -- cmd "${(qqq)@}"
cmd "" "foo bar" "blah
blah"
$ print -r -- cmd "${(qqqq)@}"
cmd $'' $'foo bar' $'blah\nblah'

Or with zsh, bash or ksh93 (here for bash, YMMV with other shells):

$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'

You could also use the shell's xtrace option that causes the shell to print what it's going to execute:

$ (PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'

Above, we ran the : no-op command with cmd and the positional parameters as argument. My shell printed them in a nice quoted fashion suitable for reinput to the shell. Not all shells do that.

Related Question