Checking from shell script if a directory contains files

Viewed 235882

From a shell script, how do I check if a directory contains files?

Something similar to this

if [ -e /some/dir/* ]; then echo "huzzah"; fi;

but which works if the directory contains one or several files (the above one only works with exactly 0 or 1 files).

30 Answers

The solutions so far use ls. Here's an all bash solution:

#!/bin/bash
shopt -s nullglob dotglob     # To include hidden files
files=(/some/dir/*)
if [ ${#files[@]} -gt 0 ]; then echo "huzzah"; fi

How about the following:

if find /some/dir/ -maxdepth 0 -empty | read v; then echo "Empty dir"; fi

This way there is no need for generating a complete listing of the contents of the directory. The read is both to discard the output and make the expression evaluate to true only when something is read (i.e. /some/dir/ is found empty by find).

Try:

if [ ! -z `ls /some/dir/*` ]; then echo "huzzah"; fi

Take care with directories with a lot of files! It could take a some time to evaluate the ls command.

IMO the best solution is the one that uses

find /some/dir/ -maxdepth 0 -empty
# Works on hidden files, directories and regular files
### isEmpty()
# This function takes one parameter:
# $1 is the directory to check
# Echoes "huzzah" if the directory has files
function isEmpty(){
  if [ "$(ls -A $1)" ]; then
    echo "huzzah"
  else 
    echo "has no files"
  fi
}
DIR="/some/dir"
if [ "$(ls -A $DIR)" ]; then
     echo 'There is something alive in here'
fi

Could you compare the output of this?

 ls -A /some/dir | wc -l

This may be a really late response but here is a solution that works. This line only recognizes th existance of files! It will not give you a false positive if directories exist.

if find /path/to/check/* -maxdepth 0 -type f | read
  then echo "Files Exist"
fi
# Checks whether a directory contains any nonhidden files.
#
# usage: if isempty "$HOME"; then echo "Welcome home"; fi
#
isempty() {
    for _ief in $1/*; do
        if [ -e "$_ief" ]; then
            return 1
        fi
    done
    return 0
}

Some implementation notes:

  • The for loop avoids a call to an external ls process. It still reads all the directory entries once. This can only be optimized away by writing a C program that uses readdir() explicitly.
  • The test -e inside the loop catches the case of an empty directory, in which case the variable _ief would be assigned the value "somedir/*". Only if that file exists will the function return "nonempty"
  • This function will work in all POSIX implementations. But be aware that the Solaris /bin/sh doesn't fall into that category. Its test implementation doesn't support the -e flag.

ZSH

I know the question was marked for bash; but, just for reference, for zsh users:

Test for non-empty directory

To check if foo is non-empty:

$ for i in foo(NF) ; do ... ; done

where, if foo is non-empty, the code in the for block will be executed.

Test for empty directory

To check if foo is empty:

$ for i in foo(N/^F) ; do ... ; done

where, if foo is empty, the code in the for block will be executed.

Notes

We did not need to quote the directory foo above, but we can do so if we need to:

$ for i in 'some directory!'(NF) ; do ... ; done

We can also test more than one object, even if it is not a directory:

$ mkdir X     # empty directory
$ touch f     # regular file
$ for i in X(N/^F) f(N/^F) ; do echo $i ; done  # echo empty directories
X

Anything that is not a directory will just be ignored.

Extras

Since we are globbing, we can use any glob (or brace expansion):

$ mkdir X X1 X2 Y Y1 Y2 Z
$ touch Xf                    # create regular file
$ touch X1/f                  # directory X1 is not empty
$ touch Y1/.f                 # directory Y1 is not empty
$ ls -F                       # list all objects
X/ X1/ X2/ Xf Y/ Y1/ Y2/ Z/
$ for i in {X,Y}*(N/^F); do printf "$i "; done; echo  # print empty directories
X X2 Y Y2

We can also examine objects that are placed in an array. With the directories as above, for example:

$ ls -F                       # list all objects
X/ X1/ X2/ Xf Y/ Y1/ Y2/ Z/
$ arr=(*)                     # place objects into array "arr"
$ for i in ${^arr}(N/^F); do printf "$i "; done; echo
X X2 Y Y2 Z

Thus, we can test objects that may already be set in an array parameter.

Note that the code in the for block is, obviously, executed on every directory in turn. If this is not desirable then you can simply populate an array parameter and then operate on that parameter:

$ for i in *(NF) ; do full_directories+=($i) ; done
$ do_something $full_directories

Explanation

For zsh users there is the (F) glob qualifier (see man zshexpn), which matches "full" (non-empty) directories:

$ mkdir X Y
$ touch Y/.f        # Y is now not empty
$ touch f           # create a regular file
$ ls -dF *          # list everything in the current directory
f X/ Y/
$ ls -dF *(F)       # will list only "full" directories
Y/

The qualifier (F) lists objects that match: is a directory AND is not empty. So, (^F) matches: not a directory OR is empty. Thus, (^F) alone would also list regular files, for example. Thus, as explained on the zshexp man page, we also need the (/) glob qualifier, which lists only directories:

$ mkdir X Y Z
$ touch X/f Y/.f    # directories X and Y now not empty
$ for i in *(/^F) ; do echo $i ; done
Z

Thus, to check if a given directory is empty, you can therefore run:

$ mkdir X
$ for i in X(/^F) ; do echo $i ; done ; echo "finished"
X
finished

and just to be sure that a non-empty directory would not be captured:

$ mkdir Y
$ touch Y/.f
$ for i in Y(/^F) ; do echo $i ; done ; echo "finished"
zsh: no matches found: Y(/^F)
finished

Oops! Since Y is not empty, zsh finds no matches for (/^F) ("directories that are empty") and thus spits out an error message saying that no matches for the glob were found. We therefore need to suppress these possible error messages with the (N) glob qualifier:

$ mkdir Y
$ touch Y/.f
$ for i in Y(N/^F) ; do echo $i ; done ; echo "finished"
finished

Thus, for empty directories we need the qualifier (N/^F), which you can read as: "don't warn me about failures, directories that are not full".

Similarly, for non-empty directories we need the qualifier (NF), which we can likewise read as: "don't warn me about failures, full directories".

With some workaround I could find a simple way to find out whether there are files in a directory. This can extend with more with grep commands to check specifically .xml or .txt files etc. Ex : ls /some/dir | grep xml | wc -l | grep -w "0"

#!/bin/bash
if ([ $(ls /some/dir | wc -l  | grep -w "0") ])
    then
        echo 'No files'
    else
        echo 'Found files'
fi

Taking a hint (or several) from olibre's answer, I like a Bash function:

function isEmptyDir {
  [ -d $1 -a -n "$( find $1 -prune -empty 2>/dev/null )" ]
}

Because while it creates one subshell, it's as close to an O(1) solution as I can imagine and giving it a name makes it readable. I can then write

if isEmptyDir somedir
then
  echo somedir is an empty directory
else
  echo somedir does not exist, is not a dir, is unreadable, or is  not empty
fi

As for O(1) there are outlier cases: if a large directory has had all or all but the last entry deleted, "find" may have to read the whole thing to determine whether it's empty. I believe that expected performance is O(1) but worst-case is linear in the directory size. I have not measured this.

if [[ -s somedir ]]; then
    echo "Files present"
fi

In my testing with bash 5.0.17, [[ -s somedir ]] will return true if somedir has any children. The same is true of [ -s somedir ]. Note that this will also return true if there are hidden files or subdirectories. It may also be filesystem-dependent.

In another thread How to test if a directory is empty with find i proposed this

[ "$(cd $dir;echo *)" = "*" ] && echo empty || echo non-empty

With the rationale that, $dir do exist because the question is "Checking from shell script if a directory contains files", and that * even on big dir is not that big, on my system /usr/bin/* is just 12Kb.

Update: Thanx @hh skladby, the fixed one.

[ "$(cd $dir;echo .* *)" = ". .. *" ] && echo empty || echo non-empty

Without calling utils like ls, find, etc.:

POSIX safe, i.e. not dependent on your Bash / xyz shell / ls / etc. version:

dir="/some/dir"
[ "$(echo $dir/*)x" != "$dir/*x" ] || [ "$(echo $dir/.[^.]*)x" != "$dir/.[^.]*x" ] || echo "empty dir"

The idea:

  • echo * lists non-dot files
  • echo .[^.]* lists dot files except of "." and ".."
  • if echo finds no matches, it returns the search expression, i.e. here * or .[^.]* - which both are no real strings and have to be concatenated with e.g. a letter to coerce a string
  • || alternates the possibilities in a short circuit: there is at least one non-dot file or dir OR at least one dot file or dir OR the directory is empty - on execution level: "if first possibility fails, try next one, if this fails, try next one"; here technically Bash "tries to execute" echo "empty dir", put your action for empty dirs here (eg. exit).

Checked with symlinks, yet to check with more exotic possible file types.

It really feels like there should be an option to test for an empty directory. I'll leave that editorial comment as a suggestion to the maintainers of the test command, but the counterpart exists for empty files.

In the trivial use case that brought me here, I'm not worried about looping through a huge number of files, nor am I worried about .files. I was hoping to find the aforementioned "missing" operand to test. C'est la guerre.

In the example below directory empty is empty, and full has files.

$ for f in empty/*; do test -e $f; done
$ echo $?
1
$ for f in full/*; do test -e $f; done
$ echo $?
0

Or, shorter and uglier still, but again only for relatively trivial use cases:

$ echo empty/*| grep \*
$ echo $?
1

$ echo full/* | grep \*
$ echo $?
0
if ls /some/dir/* >/dev/null 2>&1 ; then echo "huzzah"; fi;
Related