How to Preserve Quotes in Printing a Bash Script'S Arguments

How can I preserve quotes in printing a bash script's arguments

The quotes are removed before the arguments are passed to your script, so it's too late to preserve them. What you can do is preserve their effect when passing the arguments to the inner command, and reconstruct an equivalent quoted/escaped version of the arguments for printing.

For passing the arguments to the inner command "$@" -- with the double-quotes, $@ preserves the original word breaks, meaning that the inner command receives exactly the same argument list that your script did.

For printing, you can use the %q format in bash's printf command to reconstruct the quoting. Note that this won't always reconstruct the original quoting, but will construct an equivalent quoted/escaped string. For example, if you passed the argument 'uptime ; uname -a' it might print uptime\ \;\ uname\ -a or "uptime ; uname -a" or any other equivalent (see @William Pursell's answer for similar examples).

Here's an example of using these:

printf "Running command:"
printf " %q" innercmd "$@" # note the space before %q -- this inserts spaces between arguments
printf "\n"
innercmd "$@"

If you have bash version 4.4 or later, you can use the @Q modifier on parameter expansions to add quoting. This tends to prefer using single-quotes (as opposed to printf %q's preference for escapes). You can combine this with $* to get a reasonable result:

echo "Running command: innercmd ${*@Q}"
innercmd "$@"

Note that $* mashes all arguments together into a single string with whitespace between them, which is normally not useful, but in this case each argument is individually quoted so the result is actually what you (probably) want. (Well, unless you changed IFS, in which case the "whitespace" between arguments will be the first character of $IFS, which may not be what you want.)

Bash stripping quotes - how to preserve quotes

I typically use single quotes to avoid shell interpolation when you want to quote strings with quotes in them.

./test.sh -t some.comman -extraswitch '"some addtional info"'

Printing command line arguments in quoted form

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

Can I get the original quote characters surrounding arguments in my Bash script?

No, there is no way to obtain the command line from before the shell performed whitespace tokenization, wildcard expansion, and quote removal on it.

If you want to pass in literal quotes, try

./script.sh '"-a"' '"this is a sentence"' '"-b"' '"1"'

Notice also how your original command line could have been written

'./script.sh' '-a' 'this is a sentence' '-b' '1'

How to quotes in bash function parameters?

The reason this happens is because bash interprets the arguments, as you thought. The quotes simply aren't there any more when it calls the function, so this isn't possible. It worked in DOS because programs could interpret the command line themselves, not that it helps you!

Preserving escapes in bash arguments $@

You need to use an array.

cmd=( "$@" )
printf '%q\n' "${cmd[@]}"
nohup "${cmd[@]}"

Scalar variables (strings) are NUL-delimited, so they can't contain an argument list (which is, by its nature, NUL-separated).



Related Topics



Leave a reply



Submit