Stalpotaten
Stalpotaten

Reputation: 501

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.

Upvotes: 4

Views: 1301

Answers (2)

John1024
John1024

Reputation: 113994

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.

Upvotes: 4

Meir Gabay
Meir Gabay

Reputation: 3316

If your end goal is to list them, you can simply go with

ls "${HOME}"/*.txt # or echo "$HOME"/*.txt

# Output
# /Users/meirgabay/file1.txt      /Users/meirgabay/file2.txt      /Users/meirgabay/some_file.txt

If you want to iterate over files paths, here's how

declare -a _TXT_FILES=()
for file_path in "$HOME"/*.txt; do
    echo "$file_path"
    _TXT_FILES+=("$file_path")
done

echo "${_TXT_FILES[@]}"

# Output
# /Users/meirgabay/file1.txt
# /Users/meirgabay/file2.txt
# /Users/meirgabay/some_file.txt
# /Users/meirgabay/file1.txt /Users/meirgabay/file2.txt /Users/meirgabay/some_file.txt

I used an array, declare -a _TXT_FILES=(), to store the results in a variable.

If you want to use ~/ instead of $HOME, that's also possible, just make sure you don't surround it with quotes. The ~ character is expanded by bash, and if you put it between quotes, it is evaluated as a string, instead of getting expanded.

ls ~/*.txt
/Users/meirgabay/file1.txt      /Users/meirgabay/file2.txt      /Users/meirgabay/some_file.txt

# Output
# /Users/meirgabay/file1.txt      /Users/meirgabay/file2.txt      /Users/meirgabay/some_file.txt

echo ~/*.txt
/Users/meirgabay/file1.txt /Users/meirgabay/file2.txt /Users/meirgabay/some_file.txt

# Output
# /Users/meirgabay/file1.txt /Users/meirgabay/file2.txt /Users/meirgabay/some_file.txt

Upvotes: 1

Related Questions