Bash substitution with variable defined from a glob pattern

bashquotingshellshell-scriptwildcards

The below example explains the issue. Why is the FILENAME printed correctly when echoed and perceived as a pattern when using substitution?

#!/bin/bash

FILEPATH_WITH_GLOB="/home/user/file_*"
FILENAME=$(basename "$FILEPATH_WITH_GLOB")
echo $FILENAME                #file_1234
echo ${FILENAME:1:5}          #ile_*   <---why is this not ile_1

Best Answer

FILEPATH_WITH_GLOB="/home/user/file_*"

Now, FILEPATH_WITH_GLOB contains /home/user/file_*

FILENAME=$(basename "$FILEPATH_WITH_GLOB")

FILENAME contains file_*.

echo $FILENAME                #file_1234

$FILENAME being unquoted in list context, that expansion undergoes the split+glob operator, so that's expanded to the list of matching file: filename generation is performed upon parameter expansion.

echo ${FILENAME:1:5}          #ile_*   <---why is this not ile_1

It's still an unquoted parameter expansion in list context, so still undergoes split+glob. However here, the ile_* pattern doesn't match any file, so it expands to itself instead.

What you probably want here is:

shopt -s nullglob # have globs expand to nothing when they don't match
set -- /home/user/file_* # expand that pattern into the list of matching 
                         # files in $1, $2...
for file do  # loop over them
  filename=$(basename -- "$file")
  printf '%s\n' "$filename" "${filename:1:5}"
done

Or you can store them in an array:

shopt -s nullglob
files=(/home/user/file_*)

If you only care about the first match, or you know there's only one match, you can then refer to that file as $files. bash has that usually annoying behaviour that $files expands to ${files[0]} instead of all the elements of the array (a behaviour inherited from ksh, fixed in zsh), but here, that would be a wanted behaviour for once.