bashglobexpansion

Bash glob expansion like echo $pathInVar*


I have a variable containing a path and want to expand a glob pattern based on that path. I want to understand why my attempts don't work and what is the preferred way of doing this in bash.

EX: I want to list all text files in my home directory or only those that starts with "test". My failing attempts:

foo="~/"
echo $foo*.txt
echo ${foo}test*.txt

These results in the string outputs ~/* and ~/test*txt respectively. I have tried different versions with quotes etc. but I guess this enough to show my issue and level of understanding — I am a bash beginner. Is the issue related to tilde expansion vs. using $HOME?

Ultimately, I want to loop over these files but I ask this question to understand bash, not just get the result.

P.S. I am certain there are answers to this already out there but I've not managed to find any that have helped me understand this case. I tried to understand the general expansion order in bash but still don't understand how to apply it here.


Solution

  • In a glob, * matches any character in a file name but it does not match /. Thus, to get files in your home directory, try:

    foo=$HOME
    echo $foo/*.txt
    echo ${foo}/test*.txt
    

    In the odd case that foo ($HOME) includes shell-active characters, it is better practice to use:

    echo "$foo"/*.txt
    echo "${foo}"/test*.txt
    

    To loop over such files, use:

    for fname in "$foo"/*.txt
    do
        # do something
    done
    

    This loop structure is safe for all filenames, even ones with spaces or other shell-active characters. This, of course, assumes that the code in your loop has $fname inside double-quotes as appropriate.

    If no files match the glob, the for loop will still run over a single item matching the unexpanded glob:

    for not_exist in "$foo"/not_exists*.txt; do
        if [[ "$not_exist" == "$foo/not_exists*.txt" ]]; then
            # nothing matched the glob
            break
        fi
    done
    

    Alternatively, if bash's nullglob option is set, the loop will only be executed if there are matching files. (Note: This is for bash only: nullglob is not POSIX compatible.)

    Update for revised question

    foo="~/"
    

    ~/ never expands when inside quotes:

    $ foo="~/"
    $ echo $foo
    ~/
    

    If you really want to use ~/, don't use quotes. Unless you want to use one of the fancy features of ~, it is usually simpler and more reliable to use $HOME instead.