0

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/*
5
  • 2
    What is your end goal? are you expecting /tmp/hello/* to be expanded when the array is created, or when the command is run? Commented Jun 20 at 14:43
  • Also mind that filename expansion is not the only thing that doesn't happen in double quotes: braces and tildes won't be expanded either, process subsititutions won't work and, besides expansions, many shell facilities would be missing. You generally can "convert" commands like that only in very simple cases and, as steeldriver mentioned, expansions will take place when assigning elements to the array. choroba's answer fits your case perfectly, but you should keep all of this in mind
    – kos
    Commented Jun 20 at 15:07
  • @steeldriver, I want the "/tmp/hello/*" to be expanded only when I call the "${my_array[@]}" in my bash script.
    – stackbiz
    Commented Jun 20 at 16:14
  • 2
    If you want the expansion to happen only when the command is run, without modifying the array, then eval "${my_array[@]}" will join the elements of my_array and run it as a command, fully expanding *.
    – kos
    Commented Jun 20 at 16:42
  • Why do you need the second item? What's your real goal?
    – choroba
    Commented Jun 20 at 21:05

2 Answers 2

2

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 using set -f (or equivalently set -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=* or my_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):

 % typeset -a my_array
 % my_array[1]=ls
 % my_array[2]="my dir/*"
 % typeset -p my_array
typeset -a my_array=( ls 'my dir/*' )

 % ${my_array[@]}
ls: cannot access 'my dir/*': No such file or directory

(same as quoted "${my_array[@]}" in both bash and zsh) but

 % ${~my_array[@]}
'my dir/hello'  'my dir/world'

You can verify that the globbing is happening at expansion time by adding another file:

 % touch 'my dir/a new file'
steeldriver@steeldriver-virtualbox ~/dir
 % ${~my_array[@]}          
'my dir/a new file'  'my dir/hello'  'my dir/world'

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):

#!/bin/zsh

typeset -a my_array

my_array[1]=mv
my_array[2]=-nt
my_array[3]=/tmp/world
my_array[4]=--
my_array[5]=/tmp/hello/*

${~my_array[@]}

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 plain mv.


  1. by which I mean without using eval

  2. although note that ${~my_array[@]} turns on globbing during the expansion of every element of the array

1

Use the asterisk when populating the array, but don't populate it one element at a time:

my_array=(cp /tmp/hello/* /tmp/world)
"${my_array[@]}"

Or use the += operator to exapand the array:

my_array=(cp)
my_array+=(/tmp/hello/*)
my_array+=(/tmp/world)
"${my_array[@]}"
1
  • I tried your solution but found a problem, please check my updated post. I want the "/tmp/hello/*" to be expanded only when I call the "${my_array[@]}" in my bash script.
    – stackbiz
    Commented Jun 20 at 16:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .