bos
bos

Reputation: 6555

Script is not glob-expanding, but works fine when running the culprit as a minimalistic example

I've been trying for hours on this problem, and cannot set it straight.

This minimal script works as it should:

#!/bin/bash
wipe_thumbs=1

if (( wipe_thumbs )); then
 src_dir=$1
 thumbs="$src_dir"/*/t1*.jpg
 echo $thumbs
fi

Invoke with ./script workdir and a lot of filenames starting with t1* in all the sub-dirs of workdir are shown.

When putting the above if-case in the bigger script, the globbing is not executed:

SRC: -- workdir/ --
THUMBS: -- workdir//*/t1*.jpg --
ls: cannot access workdir//*/t1*.jpg: No such file or directory

The only difference with the big script and the minimal script is that the big script has a path-validator and getopts-extractor. This code is immediately above the if-case:

#!/bin/bash
OPTIONS=":ts:d:"
src_dir=""
dest_dir=""
wipe_thumbs=0

while getopts $OPTIONS opt ; do
  case "$opt" in
    t) wipe_thumbs=1
       ;;
  esac
done
shift $((OPTIND - 1))

src_dir="$1"
dest_dir="${2:-${src_dir%/*}.WORK}"

# Validate source
echo -n "Validating source..."
if [[ -z "$src_dir" ]]; then
  echo "Can't do anything without a source-dir."
  exit
else
  if [[ ! -d "$src_dir" ]]; then
    echo "\"$src_dir\" is really not a directory."
    exit
  fi
fi
echo "done"

# Validate dest
echo -n "Validating destination..."
if [[ ! -d "$dest_dir" ]]; then
  mkdir "$dest_dir"
  (( $? > 0 )) && exit
else
  if [[ ! -w "$dest_dir" ]]; then
    echo "Can't write into the specified destination-dir."
    exit
  fi
fi
echo "done"


# Move out the files into extension-named directories
echo -n "Moving files..."

if (( wipe_thumbs )); then
  thumbs="$src_dir"/*/t1*.jpg               # not expanded
  echo DEBUG THUMBS: -- "$thumbs" --
  n_thumbs=$(ls "$thumbs" | wc -l)
  rm "$thumbs"
fi

...rest of script, never reached due to error...

Can anyone shed some lights on this? Why is the glob not expanded in the big script, but working fine in the minimalistic test script?

EDIT: Added the complete if-case.

Upvotes: 1

Views: 55

Answers (1)

Gordon Davisson
Gordon Davisson

Reputation: 125798

The problem is that wildcards aren't expanded in assignment statements (e.g. thumbs="$src_dir"/*/t1*.jpg), but are expanded when variables are used without double-quotes. Here's an interactive example:

$ src_dir=workdir
$ thumbs="$src_dir"/*/t1*.jpg
$ echo $thumbs    # No double-quotes, wildcards will be expanded
workdir/sub1/t1-1.jpg workdir/sub1/t1-2.jpg workdir/sub2/t1-1.jpg workdir/sub2/t1-2.jpg
$ echo "$thumbs"    # Double-quotes, wildcards printed literally
workdir/*/t1*.jpg
$ ls $thumbs    # No double-quotes, wildcards will be expanded
workdir/sub1/t1-1.jpg   workdir/sub2/t1-1.jpg
workdir/sub1/t1-2.jpg   workdir/sub2/t1-2.jpg
$ ls "$thumbs"    # Double-quotes, wildcards treated as literal parts of filename
ls: workdir/*/t1*.jpg: No such file or directory

...so the quick-n-easy fix is to remove the double-quotes from the ls and rm commands. But this isn't safe, as it'll also cause parsing problems if $src_dir contains any whitespace or wildcard characters (this may not be an issue for you, but I'm used to OS X where spaces in filenames are everywhere, and I've learned to be careful about these things). The best way to do this is to store the list of thumb files as an array:

$ src="work dir"
$ thumbs=("$src_dir"/*/t1*.jpg)    # No double-quotes protect $src_dir, but not the wildcard portions
$ echo "${thumbs[@]}"    # The "${array[@]}" idiom expands each array element as a separate word
work dir/sub1/t1-1.jpg work dir/sub1/t1-2.jpg work dir/sub2/t1-1.jpg work dir/sub2/t1-2.jpg
$ ls "${thumbs[@]}"
work dir/sub1/t1-1.jpg  work dir/sub2/t1-1.jpg
work dir/sub1/t1-2.jpg  work dir/sub2/t1-2.jpg

You might also want to set nullglob in case there aren't any matches (so it'll expand to a zero-length array).

In your script, this'd come out something like this:

if (( wipe_thumbs )); then
  shopt -s nullglob
  thumbs=("$src_dir"/*/t1*.jpg)              # expanded as array elements
  shopt -u nullglob    # back to "normal" to avoid unexpected behavior later
  printf 'DEBUG THUMBS: --'
  printf ' "%s"' "${thumbs[@]}"
  printf ' --\n'
  # n_thumbs=$(ls "${thumbs[@]}" | wc -l)    # wrong way to do this...
  n_thumbs=${#thumbs[@]}    # better...
  if (( n_thumbs == 0 )); then
    echo "No thumb files found" >&2
    exit
  fi
  rm "${thumbs[@]}"
fi

Upvotes: 4

Related Questions