[bash] Checking from shell script if a directory contains files

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).

This question is related to bash unix shell

The answer is


Three best tricks


shopt -s nullglob dotglob; f=your/dir/*; ((${#f}))

This trick is 100% bash and invokes (spawns) a sub-shell. The idea is from Bruno De Fraine and improved by teambob's comment.

files=$(shopt -s nullglob dotglob; echo your/dir/*)
if (( ${#files} ))
then
  echo "contains files"
else 
  echo "empty (or does not exist or is a file)"
fi

Note: no difference between an empty directory and a non-existing one (and even when the provided path is a file).

There is a similar alternative and more details (and more examples) on the 'official' FAQ for #bash IRC channel:

if (shopt -s nullglob dotglob; f=(*); ((${#f[@]})))
then
  echo "contains files"
else 
  echo "empty (or does not exist, or is a file)"
fi

[ -n "$(ls -A your/dir)" ]

This trick is inspired from nixCraft's article posted in 2007. Add 2>/dev/null to suppress the output error "No such file or directory".
See also Andrew Taylor's answer (2008) and gr8can8dian's answer (2011).

if [ -n "$(ls -A your/dir 2>/dev/null)" ]
then
  echo "contains files (or is a file)"
else
  echo "empty (or does not exist)"
fi

or the one-line bashism version:

[[ $(ls -A your/dir) ]] && echo "contains files" || echo "empty"

Note: ls returns $?=2 when the directory does not exist. But no difference between a file and an empty directory.


[ -n "$(find your/dir -prune -empty)" ]

This last trick is inspired from gravstar's answer where -maxdepth 0 is replaced by -prune and improved by phils's comment.

if [ -n "$(find your/dir -prune -empty 2>/dev/null)" ]
then
  echo "empty (directory or file)"
else
  echo "contains files (or does not exist)"
fi

a variation using -type d:

if [ -n "$(find your/dir -prune -empty -type d 2>/dev/null)" ]
then
  echo "empty directory"
else
  echo "contains files (or does not exist or is not a directory)"
fi

Explanation:

  • find -prune is similar than find -maxdepth 0 using less characters
  • find -empty prints the empty directories and files
  • find -type d prints directories only

Note: You could also replace [ -n "$(find your/dir -prune -empty)" ] by just the shorten version below:

if [ `find your/dir -prune -empty 2>/dev/null` ]
then
  echo "empty (directory or file)"
else
  echo "contains files (or does not exist)"
fi

This last code works most of the cases but be aware that malicious paths could express a command...


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

# 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.

This tells me if the directory is empty or if it's not, the number of files it contains.

directory="/some/dir"
number_of_files=$(ls -A $directory | wc -l)

if [ "$number_of_files" == "0" ]; then
    echo "directory $directory is empty"
else
    echo "directory $directory contains $number_of_files files"
fi

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

dir_is_empty() {
   [ "${1##*/}" = "*" ]
}

if dir_is_empty /some/dir/* ; then
   echo "huzzah"
fi

Assume you don't have a file named * into /any/dir/you/check, it should work on bash dash posh busybox sh and zsh but (for zsh) require unsetopt nomatch.

Performances should be comparable to any ls which use *(glob), I guess will be slow on directories with many nodes (my /usr/bin with 3000+ files went not that slow), will use at least memory enough to allocate all dirs/filenames (and more) as they are all passed (resolved) to the function as arguments, some shell probably have limits on number of arguments and/or length of arguments.

A portable fast O(1) zero resources way to check if a directory is empty would be nice to have.

update

The version above doesn't account for hidden files/dirs, in case some more test is required, like the is_empty from Rich’s sh (POSIX shell) tricks:

is_empty () (
cd "$1"
set -- .[!.]* ; test -f "$1" && return 1
set -- ..?* ; test -f "$1" && return 1
set -- * ; test -f "$1" && return 1
return 0 )

But, instead, I'm thinking about something like this:

dir_is_empty() {
    [ "$(find "$1" -name "?*" | dd bs=$((${#1}+3)) count=1 2>/dev/null)" = "$1" ]
}

Some concern about trailing slashes differences from the argument and the find output when the dir is empty, and trailing newlines (but this should be easy to handle), sadly on my busybox sh show what is probably a bug on the find -> dd pipe with the output truncated randomically (if I used cat the output is always the same, seems to be dd with the argument count).


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".


I am surprised the wooledge guide on empty directories hasn't been mentioned. This guide, and all of wooledge really, is a must read for shell type questions.

Of note from that page:

Never try to parse ls output. Even ls -A solutions can break (e.g. on HP-UX, if you are root, ls -A does the exact opposite of what it does if you're not root -- and no, I can't make up something that incredibly stupid).

In fact, one may wish to avoid the direct question altogether. Usually people want to know whether a directory is empty because they want to do something involving the files therein, etc. Look to the larger question. For example, one of these find-based examples may be an appropriate solution:

   # Bourne
   find "$somedir" -type f -exec echo Found unexpected file {} \;
   find "$somedir" -maxdepth 0 -empty -exec echo {} is empty. \;  # GNU/BSD
   find "$somedir" -type d -empty -exec cp /my/configfile {} \;   # GNU/BSD

Most commonly, all that's really needed is something like this:

   # Bourne
   for f in ./*.mpg; do
        test -f "$f" || continue
        mympgviewer "$f"
    done

In other words, the person asking the question may have thought an explicit empty-directory test was needed to avoid an error message like mympgviewer: ./*.mpg: No such file or directory when in fact no such test is required.


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.


Small variation of Bruno's answer:

files=$(ls -1 /some/dir| wc -l)
if [ $files -gt 0 ] 
then
    echo "Contains files"
else
    echo "Empty"
fi

It works for me


Mixing prune things and last answers, I got to

find "$some_dir" -prune -empty -type d | read && echo empty || echo "not empty"

that works for paths with spaces too


So far I haven't seen an answer that uses grep which I think would give a simpler answer (with not too many weird symbols!). Here is how I would check if any files exist in the directory using bourne shell:

this returns the number of files in a directory:

ls -l <directory> | egrep -c "^-"

you can fill in the directory path in where directory is written. The first half of the pipe ensures that the first character of output is "-" for each file. egrep then counts the number of line that start with that symbol using regular expressions. now all you have to do is store the number you obtain and compare it using backquotes like:

 #!/bin/sh 
 fileNum=`ls -l <directory> | egrep -c "^-"`  
 if [ $fileNum == x ] 
 then  
 #do what you want to do
 fi

x is a variable of your choice.


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

Simple answer with bash:

if [[ $(ls /some/dir/) ]]; then echo "huzzah"; fi;

I would go for find:

if [ -z "$(find $dir -maxdepth 1 -type f)" ]; then
    echo "$dir has NO files"
else
    echo "$dir has files"

This checks the output of looking for just files in the directory, without going through the subdirectories. Then it checks the output using the -z option taken from man test:

   -z STRING
          the length of STRING is zero

See some outcomes:

$ mkdir aaa
$ dir="aaa"

Empty dir:

$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty

Just dirs in it:

$ mkdir aaa/bbb
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty

A file in the directory:

$ touch aaa/myfile
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
$ rm aaa/myfile 

A file in a subdirectory:

$ touch aaa/bbb/another_file
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty

if ls /some/dir/* >/dev/null 2>&1 ; then echo "huzzah"; fi;

Try with command find. Specify the directory hardcoded or as argument. Then initiate find to search all files inside the directory. Check if return of find is null. Echo the data of find

#!/bin/bash

_DIR="/home/user/test/"
#_DIR=$1
_FIND=$(find $_DIR -type f )
if [ -n "$_FIND" ]
then
   echo -e "$_DIR contains files or subdirs with files \n\n "
   echo "$_FIND"
else
echo "empty (or does not exist)"
fi

to test a specific target directory

if [ -d $target_dir ]; then
    ls_contents=$(ls -1 $target_dir | xargs); 
    if [ ! -z "$ls_contents" -a "$ls_contents" != "" ]; then
        echo "is not empty";
    else
        echo "is empty";
    fi;
else
    echo "directory does not exist";
fi;

I dislike the ls - A solutions posted. Most likely you wish to test if the directory is empty because you don't wish to delete it. The following does that. If however you just wish to log an empty file, surely deleting and recreating it is quicker then listing possibly infinite files?

This should work...

if !  rmdir ${target}
then
    echo "not empty"
else
    echo "empty"
    mkdir ${target}
fi

Works well for me this (when dir exist):

some_dir="/some/dir with whitespace & other characters/"
if find "`echo "$some_dir"`" -maxdepth 0 -empty | read v; then echo "Empty dir"; fi

With full check:

if [ -d "$some_dir" ]; then
  if find "`echo "$some_dir"`" -maxdepth 0 -empty | read v; then echo "Empty dir"; else "Dir is NOT empty" fi
fi

Questions with bash tag:

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal? Copy Files from Windows to the Ubuntu Subsystem Docker: How to use bash with an Alpine based docker image? How to print / echo environment variables? Passing bash variable to jq gzip: stdin: not in gzip format tar: Child returned status 1 tar: Error is not recoverable: exiting now How to check if an environment variable exists and get its value? docker entrypoint running bash script gets "permission denied" Copy Paste in Bash on Ubuntu on Windows How to set env variable in Jupyter notebook How do I disable Git Credential Manager for Windows? How to set aliases in the Git Bash for Windows? MINGW64 "make build" error: "bash: make: command not found" Disable beep of Linux Bash on Windows 10 What does `set -x` do? psql: command not found Mac How do I use a regex in a shell script? nodemon not working: -bash: nodemon: command not found How to open google chrome from terminal? Get Environment Variable from Docker Container How to find files modified in last x minutes (find -mmin does not work as expected) How to pass arguments to Shell Script through docker run How to run C program on Mac OS X using Terminal? Curl command without using cache Running a script inside a docker container using shell script Creating an array from a text file in Bash Bash checking if string does not contain other string How to check if a Docker image with a specific tag exist locally? How do I edit $PATH (.bash_profile) on OSX? Raise error in a Bash script how to wait for first command to finish? I just assigned a variable, but echo $variable shows something else What do $? $0 $1 $2 mean in shell script? How to sort a file in-place How to read a .properties file which contains keys that have a period character using Shell script How can I remount my Android/system as read-write in a bash script using adb? How to get ip address of a server on Centos 7 in bash Changing an AIX password via script? How to remove last n characters from a string in Bash? Difference between ${} and $() in Bash List file using ls command in Linux with full path

Questions with unix tag:

Docker CE on RHEL - Requires: container-selinux >= 2.9 What does `set -x` do? How to find files modified in last x minutes (find -mmin does not work as expected) sudo: npm: command not found How to sort a file in-place How to read a .properties file which contains keys that have a period character using Shell script gpg decryption fails with no secret key error Loop through a comma-separated shell variable Best way to find os name and version in Unix/Linux platform Resource u'tokenizers/punkt/english.pickle' not found Unix command to check the filesize How can I get the IP address from NIC in Python? OSError - Errno 13 Permission denied nginx error connect to php5-fpm.sock failed (13: Permission denied) How to get key names from JSON using jq How/When does Execute Shell mark a build as failure in Jenkins? mkdir's "-p" option How to cat <<EOF >> a file containing code? ssh-copy-id no identities found error Setting PATH environment variable in OSX permanently cut or awk command to print first field of first row How do I pause my shell script for a second before continuing? how to set mongod --dbpath SCP Permission denied (publickey). on EC2 only when using -r flag on directories Command to change the default home directory of a user How to split CSV files as per number of rows specified? Assigning the output of a command to a variable Trim leading and trailing spaces from a string in awk Boolean operators ( &&, -a, ||, -o ) in Bash Send password when using scp to copy files from one server to another centos: Another MySQL daemon already running with the same unix socket /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version CXXABI_1.3.8' not found What does 'stale file handle' in Linux mean? How to run mysql command on bash? How can I use a Python script in the command line without cd-ing to its directory? Is it the PYTHONPATH? Why does ENOENT mean "No such file or directory"? How do I escape spaces in path for scp copy in Linux? Display exact matches only with grep Configure cron job to run every 15 minutes on Jenkins grep without showing path/file:line Bash command line and input limit Get specific line from text file using just shell script How to run SQL in shell script How to use variables in a command in sed? CURL to pass SSL certifcate and password Running Groovy script from the command line Only mkdir if it does not exist Difference between using "chmod a+x" and "chmod 755" Run ssh and immediately execute command How to check the first character in a string in Bash or UNIX shell?

Questions with shell tag:

Comparing a variable with a string python not working when redirecting from bash script Get first line of a shell command's output How to run shell script file using nodejs? Run bash command on jenkins pipeline Way to create multiline comments in Bash? How to do multiline shell script in Ansible How to check if a file exists in a shell script How to check if an environment variable exists and get its value? Curl to return http status code along with the response docker entrypoint running bash script gets "permission denied" Interactive shell using Docker Compose How do I use a regex in a shell script? How to pass arguments to Shell Script through docker run Can pm2 run an 'npm start' script Running a script inside a docker container using shell script shell script to remove a file if it already exist Run a command shell in jenkins Raise error in a Bash script I just assigned a variable, but echo $variable shows something else What do $? $0 $1 $2 mean in shell script? How to sort a file in-place How to get a shell environment variable in a makefile? How to read a .properties file which contains keys that have a period character using Shell script Run multiple python scripts concurrently how to check which version of nltk, scikit learn installed? Changing an AIX password via script? Loop through a comma-separated shell variable How to use execvp() List file using ls command in Linux with full path How to use su command over adb shell? Display curl output in readable JSON format in Unix shell script How to check the exit status using an if statement How to solve ADB device unauthorized in Android ADB host device? Redirect echo output in shell script to logfile adb shell su works but adb root does not Run Python script at startup in Ubuntu Copy multiple files from one directory to another from Linux shell Relative paths based on file location instead of current working directory How to Batch Rename Files in a macOS Terminal? Speed up rsync with Simultaneous/Concurrent File Transfers? Multi-line string with extra space (preserved indentation) How to set child process' environment variable in Makefile Get MAC address using shell script How to force 'cp' to overwrite directory instead of creating another one inside? Multiple conditions in if statement shell script How do I change file permissions in Ubuntu How to get key names from JSON using jq Bash: Echoing a echo command with a variable in bash How/When does Execute Shell mark a build as failure in Jenkins? Shell Script: How to write a string to file and to stdout on console?