My colleague, Ryan, came to me with a bug in his Bash script, and I identified the problem with this test:
$ mkdir ryan
$ mkdir ryan/smells-bad
$ FOO=ryan/sm
The problem is that the glob will only expand if the file "rotten_eggs" exists, because it is included in the glob pattern. You should use an array.
FOO=( ryan/smells-* )
touch "${FOO[@]/%//rotten_eggs}"
The FOO array contains everything matched by the glob. The expansion using % appends /rotten_eggs to each element.
Consider
for dir in $FOO; do
touch "$dir/rotten_eggs"
done
Note that this will touch
multiple files if the glob pattern matches more than one pathname.
I would do it like this:
for FOO in ryan/smells-*; do
touch "$FOO"/rotten_eggs
done
This way $FOO
contains the actual directory name, not the glob pattern. If there's more than one match, though, it will only contain the last one after the loop, so the array solution might be better for that case.
The code as intended with the result of the glob assigned to the variable would be like this:
$ mkdir ryan
$ mkdir ryan/smells-bad
$ FOO=(ryan/smells-*)
$ echo "${FOO[@]}"
ryan/smells-bad
$ echo "$FOO"
ryan/smells-bad
$ touch "$FOO/rotten_eggs"
$ ls -l "$FOO"
total 0
-rw-r--r-- 1 ryan ryan 0 Mar 1 11:17 rotten_eggs
$FOO
is actually an array here, but $FOO also works to get the first element of the array.
but, see how the glob can match more than one file (hence the array is a good idea)
$ mkdir ryan/clean
$ FOO=(ryan/*)
$ echo "$FOO"
ryan/clean
$ echo "${FOO[@]}"
ryan/clean ryan/smells-bad
In these cases the results of the glob is assigned to the variable as desired, rather than the variable being expanded as a glob at point of use.
Of course this means that the variable should really always be used in double quotation marks "..."
otherwise if the filename itself (the glob expansion) also had a *
in it, it would glob again.
e.g.
$ touch ryan/'*ea*'
$ FOO=(ryan/*ea*)
$ echo "${FOO[@]}"
ryan/clean ryan/*ea*
$ echo ${FOO[@]}
ryan/clean ryan/clean ryan/*ea*