1

This has been asked several times, with several accepted answers, but, on my trials none of the answers seem to work... I have two arrays, each of which represent the parameter list for a command. As such, I want to quote the strings properly to use with eval:

bash-4.2> ARRAY0=()
bash-4.2> ARRAY3=("ONE" "TWO WITH SPACE" "THREE")
bash-4.2> echo cmd $opt_arg $(printf "%q " "${ARRAY0[@]}")
cmd ''
bash-4.2> echo cmd $opt_arg $(printf "%q " "${ARRAY3[@]}")
cmd ONE TWO\ WITH\ SPACE THREE
bash# 

Where $opt_arg may or may not be populated. The problem is that in the first case, where the array is empty, it outputs '' as a parameter, even though the array is empty. This kills my command, as it's expecting zero arguments. I've not found a neat solution (I can do an if [[ ${#ARRAY0[@]} ]] around it, but that's rather ugly...). Is there a neat way to do this?

6
  • What is the dash tag doing in a question about arrays? dash doesn't support arrays at all (nor does it support printf '%q'). Commented Oct 24, 2017 at 21:51
  • And btw, [[ ${#array[@]} ]] doesn't work -- it checks for whether your string is empty. 0 is no more an empty string than 1 is. Commented Oct 24, 2017 at 21:52
  • Also, be sure you quote your expansions. In that context, that means echo "cmd $opt_arg $( (( ${#array[@]} )) && printf '%q ' "${array[@]}")" Commented Oct 24, 2017 at 21:56
  • (BTW, is there any reason your opt_arg isn't part of the array, or even a separate array? That way you could have multiple optional arguments if you wanted to without needing unquoted expansion). Commented Oct 24, 2017 at 22:27
  • Yes, the source of the array and the opt_arg come from different places. I was considering adding to a single array, though it's just as ugly as everything else. I'm considering moving the array to a string at this point (and converting to a dash script) Commented Oct 24, 2017 at 22:55

2 Answers 2

1

The idiom I use for this is to always check array length:

(( ${#array[@]} )) && printf '%q ' "${array[@]}"

That said, in present case, you can avoid the zero-argument case simply by having your cmd in the list, ensuring that printf always has at least one non-format-string argument:

printf '%q ' cmd "${array[@]}"
Sign up to request clarification or add additional context in comments.

Comments

1

Why do you need printf? Just "${ARRAY0[@]}" should be fine.

for i in cmd $opt_arg "${ARRAY0[@]}"; do echo "[[[$i]]]"; done
[[[cmd]]]

for i in cmd $opt_arg "${ARRAY3[@]}"; do echo "[[[$i]]]"; done
[[[cmd]]]
[[[ONE]]]
[[[TWO WITH SPACE]]]
[[[THREE]]]

11 Comments

printf %q generates an eval-safe version of its arguments. It's what you use when you want to generate code for use by another shell.
so, let's say you've got code like run-remote-sandbox() { local cmd_q=; (( $# )) && printf -v cmd_q '%q ' "$@"; ssh otherhost bash -s <<<"in-sandbox $cmd_q"; }, where you trust the program in-sandbox to run absolutely any code safely. If someone invoked run-remote-sandbox touch '/tmp/$(rm -rf ~)', printf %q will make sure that the remote copy of bash doesn't try to execute that command substitution before starting the sandbox, as it would have if you'd just echo'd the argument.
The question does not mention neither security nor remote execution with ssh. Give better example with say "ssh localhost"... You can always do the for loop and fill another array, right?
Sure, but why would the OP be using %q if they didn't have code-generation needs? That's what it's for.
...but sure, glad to provide a use case that doesn't involve ssh: run-in-an-hour() { printf -v cmd '%q ' "$@"; at 'now + 1 hour' <<<"$cmd"; }
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.