36

I have a requirement, if I execute a script ./123 with an arguments of empty path, say /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(this directory is empty). It should display "directory is empty"

My code is:

#!/bin/bash
dir="$1"

if [ $# -ne 1 ]
then
    echo "please pass arguments" 
exit
fi

if [ -e $dir ]
then
printf "minimum file size: %s\n\t%s\n" \
 $(du $dir -hab | sort -n -r | tail -1)

printf "maximum file size: %s\n\t%s\n" \
 $(du $dir -ab | sort -n | tail -1)

printf "average file size: %s"
du $dir -sk | awk '{s+=$1}END{print s/NR}'
else
   echo " directory doesn't exists"
fi

if [ -d "ls -A $dir" ]
 then
    echo " directory is  empty"
fi

I have an error displays like, if I execute the script name ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions (this directory is empty).

minimum file size: 4096
    /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions
maximum file size: 4096
    /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions
average file size: 4

instead of showing output only "directory is empty" its shows the above output

The below output has to be display if I exceute the script with correct arguments( I mean with correct directory path). say ./123 /usr/share

minimum file size: 196
        /usr/share
    maximum file size: 14096
        /usr/share
    average file size: 4000

my expected output is: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.
αғsнιη
  • 41,407

14 Answers14

37
if    ls -A1q ./somedir/ | grep -q .
then  ! echo somedir is not empty
else  echo somedir is empty
fi

The above is a POSIX-compatible test - and should be very fast. ls will list all files/dirs in a directory excepting . and .. (from -A) each one per line (from -1) and will -quote all non-printable characters (to include \newlines) in the output with a ? question-mark. In this way if grep receives even a single character in input it will return true - else false.

To do it in a POSIX-shell alone:

cd  ./somedir/ || exit
set ./* ./.[!.]* ./..?*
if   [ -n "$4" ] ||
     for e do 
         [ -L "$e" ] ||
         [ -e "$e" ] && break
     done
then ! echo somedir is not empty
else   echo somedir is empty
fi
cd "$OLDPWD"

A POSIX-shell (which has not earlier disabled -filename generation) will set the "$@" positional-parameter array to either the literal strings followed by the set command above, or else to the fields generated by glob operators at the end of each. Whether it does so is dependent upon whether the globs actually match anything. In some shells you can instruct a non-resolving glob to expand to null - or nothing at all. This can sometimes be beneficial, but it is not portable and often comes with additional problems - such as having to set special shell-options and afterwards unset them.

The only portable means of handling null-valued arguments involve either empty or unset variables or ~ tilde-expansions. And the latter, by the way, is far safer than the former.

Above the shell only tests any of the files for -existence if neither of the three globs specified resolves to more than a single a match. So the for loop is only ever run at all for three or fewer iterations, and only in the case of an empty directory, or in the case that one or more of the patterns resolves only to a single file. The for also breaks if any of the globs represent an actual file - and as I have arranged the globs in the order of most likely to least likely, it should pretty much quit on the first iteration every time.

Either way you do it should involve only a single system stat() call - the shell and ls should both only need to stat() the directory queried and list out the files its dentries report that it contains. This is contrasted by the behavior of find which would instead stat() every file you might list with it.

cjfp
  • 3
mikeserv
  • 58,310
  • 1
    Can confirm. I use mikeserv's initial example in scripts I've written. – Otheus May 08 '15 at 21:05
  • 1
    That would report as empty directories that don't exist or for which you don't have read access. For the second one, if you have read access but not search access, YMMV. – Stéphane Chazelas May 20 '15 at 12:23
  • @StéphaneChazelas - perhaps, but such reports will be accompanied by error messages to notify the user of such. – mikeserv May 20 '15 at 13:28
  • 1
    Only in the ls case. globs and [ are silent when they don't have access. For instance, [ -e /var/spool/cron/crontabs/stephane ] will silently report false, while there is a file by that name. – Stéphane Chazelas May 20 '15 at 13:47
  • Also note that those solutions are not equivalent if somedir is a symlink to a directory (or not a directory). – Stéphane Chazelas May 20 '15 at 13:50
  • @StéphaneChazelas - good points on both counts. I think the edit handles the latter equivalency problem, and I suppose a [ -x ./somedir/ ] && set ... || set -- would handle the other. Or... – mikeserv May 20 '15 at 19:24
  • @StéphaneChazelas could the ´-H´ ´ls´ switch bring them to an approaching equivalence? – mikeserv Jan 17 '19 at 22:44
  • the way you format your if statements is... sensible. It's like there's finally a reason for bash to have a then statement. – SpinUp __ A Davis Jun 14 '22 at 15:35
  • I use this function now as this is the best answer imo. function is_dir_empty() { [ -z "$1" ] && echo "usage: is_dir_empty <PATH>" && return 0 [ ! -d "$1" ] && echo "'$1' is not a directory, abort" && return 1 [ ! "$(ls -A1q .)" ] && echo true || echo false } – progonkpa Oct 14 '22 at 09:27
  • 1
    @progonkpa if you added a !bang before echo false your return value would reflect your exit status. – mikeserv Sep 24 '23 at 02:33
  • @mikeserv Really? I didn't know that, thanks, awesome ! – progonkpa Sep 25 '23 at 19:17
13

With GNU or modern BSDs find, you can do:

if find -- "$dir" -prune -type d -empty | grep -q '^'; then
  printf '%s\n' "$dir is an empty directory"
else
  printf >&2 '%s\n' "$dir is not empty, or is not a directory" \
                    "or is not readable or searchable in which case" \
                    "you should have seen an error message from find above."
fi

(assumes $dir doesn't look like a find predicate such as !, (, -name...).

POSIXly:

if [ -d "$dir" ] && files=$(ls -qAH -- "$dir") && [ -z "$files" ]; then
  printf '%s\n' "$dir is an empty directory"
else
  printf >&2 '%s\n' "$dir is not empty, or is not a directory" \
                    "or is not readable or searchable in which case" \
                    "you should have seen an error message from ls above."
fi

That one checks that $dir is a directory after symlink resolution.

9

[-z $dir ] complains that there's no command called [-z on most systems. You need spaces around the brackets.

[ -z $dir ] happens to be true if dir is empty, and is false for most other values of dir, but it is unreliable, for example it is true if the value of dir is = -z or -o -o -n -n. Always use double quotes around command substitutions (this goes for the rest of your script as well).

[ -z "$dir" ] tests whether the value of the variable dir is empty. The value of the variable is a string, which happens to be the path to the directory. That doesn't tell you anything about the directory itself.

There's no operator to test whether a directory is empty, like there is for a regular file ([ -s "$dir" ] is true for a directory even if it's empty). A simple way of testing whether a directory is empty is to list its content; if you get empty text, the directory is empty.

if [ -z "$(ls -A -- "$dir")" ]; then
  ...
fi

On older systems that don't have ls -A, you can use ls -a, but then . and .. are listed.


if [ -z "$(LC_ALL=C ls -a -- "$dir")" = "$(printf '.\n..')" ]; then
...
fi
ErikE
  • 129
  • could you please explain this part "$(ls -A -- "$dir")". what does $ in and outside of brackets do? – buddha sreekanth May 09 '15 at 06:51
  • @Giles "$(ls -A -- "$dir")" is not working its throwing error. – buddha sreekanth May 11 '15 at 09:27
  • @buddhasreekanth What's the error? Copy-paste. You can't expect people to help you if you just say “not working” without explaining exactly what you observe. – Gilles 'SO- stop being evil' May 11 '15 at 09:34
  • @Giles this is the error. When I executed my script ./filestats its throwing error.. $ /filestats /home/sreekanth/testdir/aki/schatz minimum file size: 4096 /home/sreekanth/testdir/aki/schatz maximum file size: 4096 /home/sreekanth/testdir/aki/schatz average file size: 4 directory is empty. Instead of showing "directory is empty". Its displaying with minimum size, max size and avg size. – buddha sreekanth May 11 '15 at 09:48
  • @buddhasreekanth I don't see an error here that could result from any of the code snippets I wrote. The code you wrote in the question displays that whether $dir is a directory or not, so this output seems to be what you requested. – Gilles 'SO- stop being evil' May 11 '15 at 10:11
  • "$(ls -A -- "$dir")" is also empty if $dir contains only files whose names are made exclusively of newline characters or if ls fails to list its contents (like when it doesn't have read access to it). – Stéphane Chazelas Nov 04 '22 at 06:06
  • @StéphaneChazelas Indeed. If you're in a potentially hostile environment where file names can contain newlines, there are probably potential race conditions that make a check for emptiness useless. Regarding errors, you should of course check the return status, which yes, is annoying. – Gilles 'SO- stop being evil' Nov 04 '22 at 09:00
5

You're looking at the attributes of the directory itself.

$ mkdir /tmp/foo
$ ls -ld /tmp/foo
drwxr-xr-x 2 jackman jackman 4096 May  8 11:32 /tmp/foo
# ...........................^^^^

You want to count how many files are in there:

$ dir=/tmp/foo
$ shopt -s nullglob
$ files=( "$dir"/* "$dir"/.* )
$ echo ${#files[@]}
2
$ printf "%s\n" "${files[@]}"
/tmp/foo/.
/tmp/foo/..

So, the test for "directory is empty" is:

function is_empty {
    local dir="$1"
    shopt -s nullglob
    local files=( "$dir"/* "$dir"/.* )
    [[ ${#files[@]} -eq 2 ]]
}

Like this:

$ if is_empty /tmp/foo; then echo "it's empty"; else echo "not empty"; fi
it's empty
$ touch /tmp/foo/afile
$ if is_empty /tmp/foo; then echo "it's empty"; else echo "not empty"; fi
not empty
glenn jackman
  • 85,964
2

My tldr answer is:

function emptydir {
 [ "$1/"* "" = "" ]  2> /dev/null &&
 [ "$1/"..?* "" = "" ]  2> /dev/null &&
 [ "$1/".[^.]* "" = "" ]  2> /dev/null ||
 [ "$1/"* = "$1/*" ]  2> /dev/null && [ ! -e "$1/*" ] &&
 [ "$1/".[^.]* = "$1/.[^.]*" ]  2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
 [ "$1/"..?* = "$1/..?*" ]  2> /dev/null && [ ! -e "$1/..?*" ]
}

It's POSIX compliant and, not that it matters much, it's typically faster than the solution that lists the directory and pipes the output to grep.

Usage:

if emptydir adir
then
  echo "nothing found" 
else 
  echo "not empty" 
fi

I like the answer https://unix.stackexchange.com/a/202276/160204, which I rewrite as :

function emptydir {
  ! { ls -1qA "./$1/" | grep -q . ; }
}

It lists the directory and pipe the result to grep. Instead, I propose a simple function that is based on glob expansion and comparison.

function emptydir {   
 [ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}

This function is not standard POSIX and calls a subshell with $(). I explain this simple function first so that we can better understand the final solution (see the tldr answer above) later.

Explanation:

The left hand side (LHS) is empty when no expansion occurs, which is the case when the directory is empty. The nullglob option is required because otherwise when there is no match, the glob itself is the result of the expansion. (Having the RHS matches the globs of the LHS when the directory is empty does not work because of false positives that occur when a LHS glob matches a single file named as the glob itself: the * in the glob matches the substring * in the file name.) The brace expression {,.[^.],..?} covers hidden files, but not .. or ..

Because shopt -s nullglob is executed inside $() (a subshell), it does not change the nullglob option of the current shell, which is normally a good thing. On the other hand, it's a good idea to set this option in scripts, because it's error prone to have a glob returns something when there is no match. So, one could set the nullglob option at the start of the script and it will not be needed in the function. Let's keep this in mind: we want a solution that works with the nullglob option.

Caveats:

If we don't have read access to the directory, the function reports the same as if there was an empty directory. This applies also to a function that lists the directory and grep the output.

The shopt -s nullglob command is not standard POSIX.

It uses the subshell created by $(). It's not a big deal, but it's nice if we can avoid it.

Pro:

Not that it really matters, but this function is four times faster than the previous one, measured with the amount of CPU time spent in the kernel within the process.

Other solutions:

We can remove the non POSIX shopt -s nullglob command on the LHS and put the string "$1/* $1/.[^.]* $1/..?*" in the RHS and eliminate separately the false positives that occur when we only have files named '*', .[^.]* or ..?* in the directory:

function emptydir {
 [ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
 [ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}

Without the shopt -s nullglob command, it now makes sense to remove the subshell, but we have to be careful because we want to avoid word splitting and yet allow glob expansion on the LHS. In particular quoting to avoid word splitting does not work, because it also prevents glob expansion. Our solution is to consider the globs separately:

function emptydir {
 [ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
 [ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
 [ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}

We still have word splitting for the individual glob, but it's ok now, because it will only result in an error when the directory is not empty. We added 2> /dev/null, to discard the error message when there are many files matching the given glob on the LHS.

We recall that we want a solution that works with the nullglob option as well. The above solution fails with the nullglob option, because when the directory is empty, the LHS's is also empty. Fortunately, it never says that the directory is empty when it is not. It only fails to say that it is empty when it is. So, we can manage the nullglob option separately. We cannot simply add the cases [ "$1/"* = "" ] etc. because these will expand as [ = "" ], etc. which are syntactically incorrect. So, we use [ "$1/"* "" = "" ] etc. instead. We again have to consider the three cases *, ..?* and .[^.]* to match the hidden files, but not . and ... These will not interfere if we don't have the nullglob option, because they also never say that it is empty when it is not. So, the final proposed solution is:

function emptydir {
 [ "$1/"* "" = "" ]  2> /dev/null &&
 [ "$1/"..?* "" = "" ]  2> /dev/null &&
 [ "$1/".[^.]* "" = "" ]  2> /dev/null ||
 [ "$1/"* = "$1/*" ]  2> /dev/null && [ ! -e "$1/*" ] &&
 [ "$1/".[^.]* = "$1/.[^.]*" ]  2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
 [ "$1/"..?* = "$1/..?*" ]  2> /dev/null && [ ! -e "$1/..?*" ]
}

Security concern:

Create two files rm and x in an empty directory and execute * on the prompt. The glob * will expand to rm x and this will be executed to remove x. This is not a security concern, because in our function, the globs are located where the expansions are not seen as commands, but as arguments, just like in for f in *.

  • $(set -f; echo "$1"/*) seems a rather complicated way of writing "$1/*". And this won't match hidden directories or files. – muru Dec 11 '19 at 01:36
  • What I meant is that there is no filename expansion in "$1/*" (note the quotes include *), so the set -f and subshell are unnecessary for that in particular. – muru Dec 11 '19 at 01:55
  • It answers a different question: whether the directory contains non hidden files or directories. I am sorry about that. I will be happy to delete it. – Dominic108 Dec 11 '19 at 02:01
  • 1
    There are ways to match hidden files too (see the complicated glob in mikeserv's answer: ./* ./.[!.]* ./..?*). Maybe you can incorporate that to make the function complete. – muru Dec 11 '19 at 02:03
  • Ah ! I will look at this and learn and maybe we will have a complete answer based on globbing expansion ! – Dominic108 Dec 11 '19 at 02:06
2

Here's another simple way of doing it. Assume that D is the full path name of the directory you want to test for emptiness.

Then

if [[ $( du -s  D |awk ' {print $1}') = 0 ]]
then
      echo D is empty
fi

This works because

du -s D

has as output

size_of_D   D

awk removes the D and if the size is 0 the directory is empty.

  • This test fails for a directory containing an empty directory -- this may still meet the definition of an 'empty directory' for your application, but its something to bear in mind. – SpinUp __ A Davis Jun 14 '22 at 15:20
1

Consider running rmdir. If it succeeds, the directory was empty, and you can recreate it. If it fails, there was contents in this directory.

Alex Cohn
  • 111
1

I also am in the situation to find empty folders and I researched a different approach that is working for me.

Example Setup: I created 4 folders with 2 folders that contain files.

enter image description here

I start to search for directories like this

    find ./folders -type d

Result

./folders
./folders/folder_1
./folders/folder_2
./folders/folder_3
./folders/folder_4

Now if I loop over the result in a bash script I can do the following with each iteration:

ls ./folders | wc -l

Result 4 - do nothing

ls ./folders/folder_1 | wc -l

Result 0 - I can delete the folder, folder is empty

ls ./folders/folder_2 | wc -l

Result 2 - do nothing

ls ./folders/folder_3 | wc -l

Result 0 - I can delete the folder, folder is empty

ls ./folders/folder_4 | wc -l

Result 1 - do nothing

#!/bin/bash

startfolder=$1

removeEmptyFolder () { echo "rm -rf $folder" }

for folder in $(find $startfolder -type d) do count=$(ls $folder|wc -l) if [ $count == 0 ];then echo "The directory $folder is empty, will be deleted" removeEmptyFolder $folder else echo "The directory $folder is not empty, nothing to do" fi done

Caution: In my script, a problem occurs with folder names that have blanks. I am using zsh as shell. I currently cannot explain that behavior.

superurmel
  • 49
  • 7
1

Keep it small and simple:

echo -n "Enter Directory: "
read dir
if [ "$(ls $dir)" == "" ]
then
echo "Directory Empty"
else
echo "Directory Not Empty"
fi

Make sure you enter the full path of the folder you want to check, unless that folder is in your current working directory (pwd), in which case you can enter just the name of the folder.

Just tested it on my end before posting, works fine.

Let me know if it gives you any issues.

Lee
  • 105
  • 10
0

I see a lot of answers here, so I must be missing something obviously.

Here is my attempt:

_path=/tmp/
if [ -d "${_path}" ] && \
   [ "$(echo "${_path}/"*)" = "${_path}/*" ]; then
  echo "Empty";
fi

This would work because (on POSIX shells afaik) when the glob fails to expand, we get the exact string returned. Thus if we compare whatever was sent and it contains the asterisk, it means nothing was unglobbed.

The extra slash is only there to avoid having to worry about whether the passed path ends with a slash or not.

The directory check is however fully optional, if we know we are dealing with paths. For example the output of find -type d.

/workdir # _path=/; if [ "$(echo "${_path}/"*)" = "${_path}/*" ]; then echo "Empty"; fi
+ _path=/
+ echo //bin //dev //etc //home //init //lib //media //mnt //opt //proc //root //run //sbin //srv //sys //test //tmp //usr //var //workdir
+ '[' '//bin //dev //etc //home //init //lib //media //mnt //opt //proc //root //run //sbin //srv //sys //test //tmp //usr //var //workdir' '=' '//*' ]
/workdir # _path=/tmp; if [ "$(echo "${_path}/"*)" = "${_path}/*" ]; then echo "Empty"; fi
+ _path=/tmp
+ echo '/tmp/*'
+ '[' '/tmp/*' '=' '/tmp/*' ]
+ echo Empty
Empty
/workdir # _path=/tmp/; if [ "$(echo "${_path}/"*)" = "${_path}/*" ]; then echo Empty; fi
+ _path=/tmp/
+ echo '/tmp//*'
+ '[' '/tmp//*' '=' '/tmp//*' ]
+ echo Empty
Empty
/workdir # _path=/bin/busybox; if [ "$(echo "${_path}/"*)" = "${_path}/*" ]; then echo "Empty"; fi
+ _path=/bin/busybox
+ echo '/bin/busybox/*'
+ '[' '/bin/busybox/*' '=' '/bin/busybox/*' ]
+ echo Empty
Empty
-1

The following simple script illustrates a fairly simple way of checking if a directory is empty. Works on Unix and Linux; usage is

script-name   directory-pathname

Simply modify replace the echo commands!

cd $1
ls > /tmp/test
if [ -s /tmp/test ]
then
echo not empty
else
echo empty
fi

Relies upon /tmp/test file being empty if ls does not find anything in the directory.

AdminBee
  • 22,803
-1

Read man stat, and be aware that a "directory" contains hard links to the files and directories it contains. An "empty" directory contains 2 links, "." and ".."., so, stat -c "%h" "$somedir" will be 2 if "$somedir" is empty, and greater than 2 if there is something in "$somedir".

waltinator
  • 4,865
  • The number of hard links in an empty directory is dependent on the filesystem. – Kusalananda Jan 07 '22 at 20:54
  • @they Please provide examples of Linux/Unix filesystems that have other than 2 links in an empty directory, otherwise I'll suspect you're misunderstanding/making it up. – waltinator Jan 07 '22 at 22:57
  • btrfs. I'm basing that on this answer. – Kusalananda Jan 08 '22 at 07:00
  • I have a non-empty directory, and it only contains two hard links. Apparently, the real files are not considered 'hard links'. – Alex Cohn Mar 16 '22 at 16:48
  • @Kusalananda indeed, on btrfs stat -c "%h" returns 1 for either empty or a directory containing files. That said, "%s" seems to return either 0 for empty or some number for non-empty directory. – ILIV Dec 06 '22 at 19:16
-1
function is_dir_empty() {
  [ -z "$1" ] && echo "usage: is_dir_empty <PATH>" && return 0
  [ ! -d "$1" ] && echo "'$1' is not a directory, abort" && return 1
  [ ! "$(ls -A1q "$1")" ] && echo true || echo false
}

progonkpa
  • 153
-3
#/bin/sh

somedir=somedir
list="`echo "$somedir"/*`"

test "$list" = "$somedir/*" && echo "$somedir is empty"

# dot prefixed entries would be skipped though, you have to explicitly check them
  • 3
    Hi and welcome to the site. Please don't post answers that are just code and no explanation. Comment the code or otherwise explain what it is you are doing. By the way, there's no reason to use echo, all you need is list="$somedir/*". Also, this is tricky and prone to errors. You haven't mentioned that the OP should give the target dir's path including the trailing slash, for example. – terdon Oct 09 '15 at 22:51