I need to pass an array of filenames to a command, preserving proper quoting. So far, so good. Unfortunately the command is actually a sub-command that is, in turn, invoked by another command. Concretely, the command is:
git filter-branch --index-filter \
'git rm -rf --cached ‹file1› ‹file2›…' \
HEAD
For simplicity, I’m going to replace this in the following by a simpler command that exhibits the same problem:
printf '%s\n' 'cmd file1 file2…'
Now I’ve got an array files=('a b' c)
. My desired result is that the above command prints in a single line, and individually quotes every token after cmd
as necessary (e.g. when there’s a space).
It works if I manually expand and quote the file names:
$ printf '%s\n' 'cmd '\''a b'\'' c'
→ cmd 'a b' c
(Alternatively I could mix single and double quotes to achieve the same result.)
But it no longer works if I am trying to pass an array:
$ (set -x; printf '%s\n' "cmd '${files[@]}'") + printf '%s\n' 'cmd '\''a b' 'c'\''' → cmd 'a b c'
$ (set -x; printf '%s\n' 'cmd '\'"${files[@]}"\') + printf '%s\n' 'cmd '\''a b' 'c'\''' → cmd 'a b c'
$ (set -x; printf '%s\n' 'cmd '"${files[@]}") + printf '%s\n' 'cmd a b' c → cmd a b c
I’m not surprised (3) doesn’t work (and it’s only included for completeness). Based on the output of set -x
, the shell correctly quotes the individual array elements in (1) and (2) and it even puts escaped quotes around the whole thing. But then it breaks apart the individually quoted items. Is there a way to prevent this?
Incidentally, Shellcheck (SC2145) suggests replacing the [@]
part by [*]
in the above. This obviously breaks for filenames with spaces.
gitCmd=( git filter-branch --index-filter 'git rm -rf --cached [MAGIC]' HEAD )
and doing an array expansion"${gitCmd[@]}"
doesn't work? It preserves the quoted expressions as defined. You can see the part within the quotes is not broken and preserved – Inian May 09 '19 at 16:30