Bash substitution with variable defined from a glob pattern

10,287
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.

Share:
10,287

Related videos on Youtube

TheMeaningfulEngineer
Author by

TheMeaningfulEngineer

I like to think of myself as a Hardware/Software guy who will gladly discuss referential transparency during a code review and the next moment take a circular saw to build a casing for a self made power supply. My main interest can be summarized into Linux related software development, low power electronics and general DIY projects.

Updated on September 18, 2022

Comments

  • TheMeaningfulEngineer
    TheMeaningfulEngineer almost 2 years

    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
    
  • Stéphane Chazelas
    Stéphane Chazelas about 9 years
    @Alan, that's the wrong way to address it. You want to use a array here. Either the positional parameters as in my example ($1, $2...) or a bash array like: files=(/home/user/file_*).
  • Stéphane Chazelas
    Stéphane Chazelas about 9 years
    (and all upper-case variables should really be reserved for environment variables, echo should not be used for arbitrary data, variables should not be left unquoted in list contexts).