In a CI/CD job, I have a shell variable X
defined. It contains one or more words, each of which might have glob operators:
X="foo bar* 'a b c'"
Suppose bar*
matches 3 files, bard
, bare
, and barf
. And the file a b c
exists and has 2 spaces in it.
I want to create a Bash array Y
with these contents:
Y=(--arg foo --arg bard --arg bare --arg barf --arg 'a b c')
In other words: do glob/quote expansion, then map each word w
to --arg $w
.
A clean solution would allow spaces in the expanded names (to be honest, I'm never going to have that situation in this CI/CD code - but I or someone else might easily copy/paste this technique somewhere else where it does matter).
It would also be awesome if I could do glob/quote expansion without invoking all other possible shell expansions (e.g. process substitution & subshells) - I'm having trouble thinking of how to do that, though.
The only solution I've come up with so far is to use unprotected expansion:
Y=()
for a in $X; do
Y+=(--arg "$a")
done
Is that the best I can do? Is it safe? It works well on the foo
and bar*
cases, but not well on the a b c
case.
To expand globs while honoring quotes (for grouping but not glob-suppression), but not expand variables or handle process substitutions or other dangerous syntax...
X="foo bar* 'a b c'"
IFS=
args=( )
while read -r -d '' word; do
for item in $word; do
args+=( --arg "$item" )
done
done < <(xargs printf '%s\0' <<<"$X")
See this running in an online sandbox (where bard
, bare
and barf
exist) at https://replit.com/@CharlesDuffy2/UnlawfulDecentApplicationserver#main.sh
The use of xargs printf '%s\0'
has xargs
do the work of word-splitting in an almost POSIX-compliant way (if you want something completely POSIX-compliant, you need to use the Python shlex
module instead -- other Q&A on the site demonstrates how), and the unquoted expansion with an empty IFS performs globbing only.