25

In bash I often use for-loops such as the following

for file in *.type; do 
  sommecommand "$file"; 
done;

to perform an operation for all files matching *.type. If no file with this ending is found in the working directories the asterisk is not expanded and usually I will get an error message saying that somecommand didn't find the file. I can immediately think of several ways to avoid this error. But adding a conditional does not seem to be very elegant. Is there a short and clean way to achieve this?

4 Answers 4

27

Yes, run the following command :

shopt -s nullglob

it will nullify the match and no error will be triggered.

  • if you want this behaviour by default, add the command in your ~/.bashrc
  • if you want to detect a null glob in POSIX shell, try

    for i in *.txt; do
      [ "$i" = '*.txt' ] && [ ! -e '*.txt' ] && continue
    done
    

See http://mywiki.wooledge.org/NullGlob

3
  • 1
    Note that it is actually possible to have a file called *.txt. It would be worth checking if the file exists.
    – Chris Down
    Nov 18, 2012 at 19:17
  • post edited accordingly. Nov 18, 2012 at 19:20
  • @ChrisDown Note that the same comment as on your answer applies here (with potentially more severe consequences because of the break instead of continue). Nov 19, 2012 at 10:59
9

In bash you can use shopt -s nullglob to expand to an empty array if there are no matches.

In POSIX shells without nullglob, you can avoid this problem by checking that the filename being passed actually exists by having [ -e "$file" ] || [ -L "$file" ] || continue as the first part of your for loop.

7
  • 2
    Note that it wouldn't be strictly equivalent since [ -e would return false for inaccessible files or files that are symlinks to inaccessible or unexisting files. Nov 18, 2012 at 19:14
  • @StephaneChazelas, let the points about symlinks be acknowledged. But what do you have in mind by "inaccessible files"? Even if I chmod 0 the_file, [ -e the_file ] still evaluates true, so it must be something else.
    – dubiousjim
    Nov 18, 2012 at 21:34
  • 1
    submitted edit to handle broken symlinks. hope that's ok.
    – dubiousjim
    Nov 18, 2012 at 21:38
  • 4
    @dubiousjim, mkdir -p x/{a,b} && chmod 444 x && echo x/* && [ -e x/a ]. x/a is inaccessible but as x is readable x/* will expand. Nov 19, 2012 at 9:51
  • @StephaneChazelas, great, thanks for explaining.
    – dubiousjim
    Nov 19, 2012 at 16:22
8

The usual technique for shells that don't have a nullglob option is

set -- [*].type *.type
case $1$2 in
  '[*].type*.type') shift 2;;
  *) shift
esac
for file do
  cmd  -- "$file"
done

The extra [*].type is to cover the case where there's one file called *.type in the current directory.

Now, if you want to include dot files, that becomes more complicated.

I beleive that technique was coined by Laura Fairhead on usenet a few years ago.

4
  • Nice solution. I think adding this explanation helps me to understand. Case 1 of 3) No file matches. [*].type *.type results into [*].type*.type literally. shift 2 to discard them. Case 2 of 3) File *.type does not exist. [*].type is not matched, thus results in [*].type literally. shift 1 to discard $1. Case 3 of 3) File *.type exists. [*].type expands to *.type. *.type*.type does not fall into the case with shift 2. shift 1 to discard $1 which is an extra match.
    – midnite
    Jan 10 at 8:48
  • In my opinion, adding a conditional check is more straightforward, more robust, and works in more scenarios. For example, if the pattern is supplied from a variable, it is not straightforward to generate [*].pattern from *.pattern. The OP did not want a conditional check though.
    – midnite
    Jan 10 at 9:06
  • @midnite, if by conditional check, you mean [ -e "$file" ] || [ -L "$file" ], it needs access to the files (being able to lstat() them), while glob expansion only needs read access to the directory, so is not a valid way to check a directory entry exists (though it's often good enough). Jan 10 at 9:51
  • I agree conditional check is doing a different thing than checking if a directory entry exists. I meant in most cases we concern about using (r or w or x) the file. If the directory entry exists but not usable, the script needs to handle this exception in most cases.
    – midnite
    Jan 10 at 10:15
0

find . -name '*.type' -maxdepth 0 -exec somecommand "{}" ";"

This removes the for loop and the shell's globbing from the equation entirely. find will execute the -exec command once per match, and if there are no matches, it will never be executed. The -maxdepth 0 instructs find to not recurse into subdirectories of the named path-argument (., in this case).

The downside is that it involves another application, albeit one that is present on virtually every Linux system out there (and probably most Unixes as well).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .