WHen I write a bash program I typically construct calls like follows:
declare -a mycmd=( command.ext "arg1 with space" arg2 thing etc )
"${mycmd[@]}" || echo "Failed: foo"
Where die foo
is a bash function that prints Error foo
and exits.
But if I want to be clear about the error reason, I want to print the failed command:
"${mycmd[@]}" || echo "Failed: foo: ${mycmd[*]}"
So the user can run the dead command and find out why. However, quoting is lost on this pass - the Failed message arguments that have spaces or escaped characters are not printed in a way that can be cut-n-pasted and run.
Does anyone have a suggestion for a compact way to fix this problem?
I think the problem is the way bash deals with argument parsing for commands, and the way (builtin) echo handles arguments. Another way of stating the problem is:
How can I print the quotes around arguments with spaces in the following bash example (which must be run as a script, not in immediate mode):
#!/bin/bash
mkdir qatest; cd qatest
declare -a myargs=(1 2 "3 4")
touch "${myargs[@]}"
ls
echo "${myargs[@]}"
actual result:
1 2 3 4
1 2 3 4
desired result
1 2 3 4
1 2 "3 4"
OR
1 2 3 4
"1" "2" "3 4"
In few additional bash code characters.
updated script:
#!/bin/bash
mkdir qatest; cd qatest
declare -a myargs=(1 2 "3 4")
touch "${myargs[@]}"
ls
echo "${myargs[@]}"
echo $(printf "'%s' " "${myargs[@]}")
output:
1 2 3 4
1 2 3 4
'1' '2' '3 4'
Your problem is with echo
. It is getting the correct number of parameters, with some parameters containing spaces, but it's output loses the distinction of spaces between parameters and spaces within parameters.
Instead, you can use printf(1)
to output the parameters and always include quotes, making use of printf's feature that applies the format string successively to parameters when there are more parameters than format specifiers in the format string:
echo "Failed: foo:" $(printf "'%s' " "${mycmd[@]}")
That will put single quotes around each argument, even if it is not needed:
Failed: foo: 'command.ext' 'arg1 with space' 'arg2' 'thing' 'etc'
I've used single quotes to ensure that other shell metacharacters are not mishandled. This will work for all characters except single quote itself - i.e. if you have a parameter containing a single quote, the output from the above command will not cut and paste correctly. This is likely the closest you will get without getting messy.
Edit: Almost 5 years later and since I answered this question, bash 4.4 has been released. This has the "${var@Q}"
expansion which quotes the variable such that it can be parsed back by bash.
This simplifies this answer to:
echo "Failed: foo: " "${mycmd[@]@Q}"
This will correctly handle single quotes in an argument, which my earlier version did not.
bash's printf command has a %q format that adds appropriate quotes to the strings as they're printed:
echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"
Mind you, its idea of the "best" way to quote something isn't always the same as mine, e.g. it tends to prefer escaping funny characters instead of wrapping the string in quotes. For example:
crlf=$'\r\n'
declare -a mycmd=( command.ext "arg1 with space 'n apostrophe" "arg2 with control${crlf} characters" )
echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"
Prints:
Failed: foo: command.ext arg1\ with\ space\ \'n\ apostrophe $'arg2 with control\r\n characters'
How about declare -p quotedarray
?
-- edit --
Acctually, declare -p quotedarray
will satisfy you purpose well. If you insist on the output format of the result, then I have a small trick would do the work, but just for the indexed array not associative one.
declare -a quotedarray=(1 2 "3 4")
temparray=( "${quotedarray[@]/#/\"}" ) #the outside double quotes are critical
echo ${temparray[@]/%/\"}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With