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?

highsciguy
  • 2,574

4 Answers4

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

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.

dubiousjim
  • 2,698
Chris Down
  • 125,559
  • 25
  • 270
  • 266
  • 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. – Stéphane Chazelas Nov 18 '12 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 '12 at 21:34
  • 1
    submitted edit to handle broken symlinks. hope that's ok. – dubiousjim Nov 18 '12 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. – Stéphane Chazelas Nov 19 '12 at 09:51
  • @StephaneChazelas, great, thanks for explaining. – dubiousjim Nov 19 '12 at 16:22
  • @StéphaneChazelas in that case, how would you test to see if there exists an inaccessible file? – ban_javascript Jul 20 '23 at 09:23
  • @ban_javascript, my answer shows a POSIX way to list directory entries. The ones in there for which [ -e file ] (initially, that was [ -a file ] for accessible) returns false would be the entries that exist but which you can't stat(). The error code returned by stat() / lstat() can sometimes tell you that a file truely doesn't exist when you try to find out. More details at How do I tell if a file does not exist in Bash? – Stéphane Chazelas Jul 20 '23 at 15:47
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.

  • 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 '24 at 08: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 '24 at 09: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). – Stéphane Chazelas Jan 10 '24 at 09: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 '24 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).

user
  • 28,901