I want to stored all command args
in an array
but it only works when all args have no any (*
).
Here is a test without using array:
mkdir -p /tmp/hello
echo "hello" > /tmp/hello/hello.txt
echo "world" > /tmp/hello/world.txt
mkdir -p /tmp/world
# This cp command is success
cp /tmp/hello/* /tmp/world
Now convert the cp command into my_array:
mkdir -p /tmp/hello
echo "hello" > /tmp/hello/hello.txt
echo "world" > /tmp/hello/world.txt
mkdir -p /tmp/world
declare -a my_array=()
my_array[0]="cp"
my_array[1]="/tmp/hello/*"
my_array[2]="/tmp/world"
# Run the command and it will fail
"${my_array[@]}"
Here is the error:
cp: cannot stat '/tmp/hello/*': No such file or directory
Is it possible for using (*
) in the my_array
?
What's the correct syntax for implementing the 'cp /tmp/hello/* /tmp/world
' with my_array
?
Update:
There is a problem
from @choroba's answoer
, The $count
and $sedond_item
will be wrong:
mkdir -p /tmp/hello
echo "hello" > /tmp/hello/hello.txt
echo "world" > /tmp/hello/world.txt
mkdir -p /tmp/world
my_array=(cp)
my_array+=(/tmp/hello/*)
my_array+=(/tmp/world)
count=${#my_array[@]}
printf "%s\n" "count is: $count"
sedond_item="${my_array[1]}"
printf "%s\n" "second item is: $sedond_item"
Here is the output of @choroba's answoer:
count is: 4
second item is: /tmp/hello/hello.txt
But the $count
and $sedond_item
are correct in my original array:
mkdir -p /tmp/hello
echo "hello" > /tmp/hello/hello.txt
echo "world" > /tmp/hello/world.txt
mkdir -p /tmp/world
declare -a my_array=()
my_array[0]="cp"
my_array[1]="/tmp/hello/*"
my_array[2]="/tmp/world"
count=${#my_array[@]}
printf "%s\n" "count is: $count"
sedond_item="${my_array[1]}"
printf "%s\n" "second item is: $sedond_item"
Here is the output of original array:
count is: 3
second item is: /tmp/hello/*
I don't think you can - safely1 - do what you want to in bash.
In bash (as well as POSIX sh and ksh), double quoting of parameter expansions (including array expansions like
"{my_array[@]}"
in shells that support them) prevents both word splitting and filename generation (aka globbing). On the other hand, unquoted expansions are subject to both word splitting and globbing (the so-called "split+glob" operation). In bash, you can turn off globbing usingset -f
(or equivalentlyset -o noglob
) but AFAIK you can't turn off word splitting while leaving globbing on.Note that word splitting occurs before globbing, so it's not the expansion of
/path/without/whitespace/*
to/path/without/whitespace/name with whitespace
that's the issue - it's the expansion of/path with whitespace/*
.In zsh however, unquoted variable expansions are not subject to split+glob by default, and you can enable globbing separately using the
~
parameter expansion flag2.Note that in all cases, the RHS of an assignment like
var=*
ormy_array[1]=/tmp/hello/*
is not expanded even when unquoted.So in zsh for example (noting that zsh arrays are indexed from 1 not 0 as in bash):
(same as quoted
"${my_array[@]}"
in both bash and zsh) butYou can verify that the globbing is happening at expansion time by adding another file:
For your specific case I'd suggest adding a few safety measures - in particular using GNU mv's
-t
to set the target directory explicitly and-n
to prevent accidental overwriting, and an additional argument--
to mark the end of options (which becomes important if the glob may expand to filenames starting with hyphens):Although TBH if you are going to switch to zsh, you might consider using the contributed
zmv
module - which takes (quoted) glob expressions and expands them internally - in place of plainmv
.by which I mean without using
eval
although note that
${~my_array[@]}
turns on globbing during the expansion of every element of the arrayUse the asterisk when populating the array, but don't populate it one element at a time:
Or use the
+=
operator to exapand the array: