sergs
sergs

Reputation: 137

How does bash treat unmatched wildcard?

I learn bash and I got the next problem using wildcards:

sergs:~ > ls -1
a.sh
Desktop
Downloads
eclipse
sergs:~ > a=*.sh
sergs:~ > echo $a
a.sh
sergs:~ > a=*.sha
sergs:~ > echo $a
*.sha

If there's no file to match a *.sha pattern why does bash return the pattern itself instead of nothing?

Thus behavior breaks such a logic:

for i in /usr/lib/opkg/info/*.postinst; do do_some_logic_with_postinst_file; done

Do I have to check that the /usr/lib/opkg/info/ directory has no any *.postinst files, explicitly, before the loop or there's some other way to do it.

Upvotes: 2

Views: 384

Answers (2)

Inian
Inian

Reputation: 85780

An un-expanded glob string is returned literally in the command-line unless you turn on an expanded shell option nullglob which returns no results if the glob expansion fails.

For your use case though, it is always a good practice to check if glob expansion is successful in a for-loop by testing it to see if the expanded result is a file i.e.

for file in /usr/lib/opkg/info/*.postinst; do 
    [ -f "$file" ] || continue
    do_some_logic_with_postinst_file
done

The condition [ -f "$file" ] will check if your glob expansion, i.e. if there are any .postinst files the [ -f "$file" ] would be true. For an un-expanded glob, the condition

[ -f /usr/lib/opkg/info/*.postinst ] 

would fail because the literal string /usr/lib/opkg/info/*.postinst is not a valid file, and the failure of this test command along with the Boolean OR || causes the continue action to the for loop. Since the for loop does not have any more arguments to process, the loop exits gracefully.

Doing such a way ( with the -f against a glob string) is guaranteed to be POSIX compliant. For a bash specific answer though, you can turn on the option as mentioned before

# '-s' enables the option
shopt -s nullglob

for file in /usr/lib/opkg/info/*.postinst; do 
    do_some_logic_with_postinst_file
done

# '-u' disables the option
shopt -u nullglob

Once you decide to use the nullglob option, there are myriad of ways on how you can use it to run your feature. For e.g. you can put all your glob results in a an array type and check if the length is valid

shopt -s nullglob
results=(/usr/lib/opkg/info/*.postinst)

if (( ${#results[@]} )); then
    printf '%s\n' 'my array is not empty'
    for file in "${results[@]}"; do
        printf '%s\n' "some action on ${file}"
    done
fi

shopt -u nullglob

Be careful of this approach when not using the nullglob option. Without the same, the results=(/usr/lib/opkg/info/*.postinst) part could still be non-empty as the literal string is still stored in the array.

Another small addendum from your OP, is never to use variables to hold your glob string. Because when the variable is expanded unquoted and if the glob expansion is success, the contents of the variable are subject to Word-Splitting done by the shell and the filenames containing spaces could be potentially broken down to multiple entries when you expect it not to.

Always use an array and proper quoted expansion. See more on BashFAQ - BashGuide/Arrays for an excellent read-up on the subject.

Upvotes: 4

Lars Christian Jensen
Lars Christian Jensen

Reputation: 1642

That's how glob patterns work (edit: unless you enable nullglob as pointed out by Inian). Another alternative can be something like

find /usr/lib/opkg/info -name \*.postinst|while read file; do
   # do_some_logic_with_postinst_file
done```

Upvotes: 0

Related Questions