4

How do I check if a directory is empty or not in ksh on AIX?

If files exist I need to remove files only.

1
  • If you are about to remove the directory if it is empty, the easiest check is to run rmdir anyway. If you want to know if there are (no) files to process, use a glob and be prepared to deal with a non-existent file name such as *. Bash has a way (shopt -s nullglob) to have a glob expand to empty/nothing when no files match; maybe ksh does too? Feb 10, 2015 at 14:46

6 Answers 6

3

Here's a POSIX-compliant way of testing whether dir is empty using only built-in shell constructs. The set command sets the positional arguments to dot files (including the always-existing . and ..) followed by non-dot files. If the directory is empty then the .* glob only matches . and .., and the * glob matches nothing so remains unexpanded.

set dir/.* dir/*
[ "$1" = "dir/." ] && [ "$2" = "dir/.." ] && ! [ -e "$3" ] && ! [ -L "$3" ]

This works on ATT ksh but fails with some shells that omit the . and .. entries. To be more portable, you need to also allow for .* to match nothing.

set dir/.* dir/*
case $# in
  0) true;; # can't happen in a standard shell, this is for shells with nullglob
  2) { [ "$1" = "dir/.*" ] && [ "$2" = "dir/*" ] && ! [ -e "$1" ] && ! [ -L "$1" ] && ! [ -e "$2" ] && ! [ -L "$2" ]; } ||
     { [ "$1" = "dir/." ] && [ "$2" = "dir/.." ]; };;
  3) [ "$1" = "dir/." ] && [ "$2" = "dir/.." ] && ! [ -e "$3" ] && ! [ -L "$3" ];;
  *) false;;
esac

Recent enough versions of ksh allow a simpler test, but I think AIX's is too old (even its ksh93). Here's how you can test whether dir is empty in a recent enough ksh93:

FIGNORE='.?(.)'; set -- ~(N)dir/*; [[ $# -eq 0 ]]

or alternatively

FIGNORE=; set -- dir/*; [[ $# -eq 2 ]]

An alternative approach is to call find. This is simpler in that find doesn't treat dot files specially and doesn't have anything like the shell's difficulty to distinguish between a pattern that matches a single file and a pattern that is left unexpanded because it matches nothing.

[ -z "$(find dir/. -name . -o -print | head -n 1)" ]
2
  • @mikeserv The pattern .* must match . and .. as per POSIX (annoying though it may be when writing scripts). That's assuming the entries exist, but they exist in all unices that I've ever seen (and if they do it's not up to an individual shell to decide to hide them, but up to readdir to return them or not). I'm surprised that posh and mksh don't comply. ATT ksh (the only one on AIX) does. Feb 11, 2015 at 12:58
  • Yes, I'm aware their behavior is contrary to the standard - I suspect it is actually just a lazy mode of attempting to conform, though - don't glob leading dots, basically. Your case fixes that - but only added to the complexity. How come? Do you think I've got the wrong idea by comparing one portion of a glob match against another across / barriers? It works in all of my tests for all of the shells - but if I'm missing something I'd like to know. I actually just did some research on the history of that lazy glob thing yesterday, though.
    – mikeserv
    Feb 11, 2015 at 15:53
0

In any shell [ "$(find path_to_dir -mindepth 1)" ] || echo EMPTY

3
0

I know of no direct way, and it also depends on whether you want also dot-files to be considered. This (or a variant thereof) may do what you want...

set directory/*
[[ -f $1 ]] || print empty
3
  • This wrongly prints empty if there is a broken symbolic called * (in addition to dot files). Feb 10, 2015 at 23:31
  • @Gilles, it also prints empty if the first file expanded from that * is not regular (directory, fifo, socket...) Feb 10, 2015 at 23:33
  • Yes. As said, depending on the requirements that needs adjustments. For the directory case you can extend it to [[ -f $1 ]] || [[ -d $1 ]] || print empty and similar for other special file cases. Handling dot files is a bit bulky.
    – Janis
    Feb 11, 2015 at 0:44
0

You can let the globs resolve themselves. As far as I'm aware, this strategy should be very shell portable for every case except one in which you have read permissions for dir/ but only search permissions for dir/../. This is because read and search permissions are required to resolve a glob, but only search permissions to show up a match.

set di[r]/* di[r]/.[!.]* di[r]/..?*
case $* in 
(*r/*) echo dir not empty;;
(*]/*) echo dir is empty ;;
esac

This works because if a pathname resolves for any of the three globs - which do not match dir/. or dir/.. - then the di[r] glob must also resolve, but the results cannot expand a /. The above should work for any of posh, ksh, bash, dash, mksh, yash, mksh, or busybox ash. It will also work for zsh if emulate sh is set. The following also works for the majority of those - all excepting posh and mksh as far as I'm aware - and should work for all POSIX shells:

For the current directory you can do:

set "/./${PWD##*/}/"
set "$1" .?"$1"* .?"$1".[!.]* .?"$1"..?*
case $* in (*.././*) set "$1";;esac
printf "%s%${2+.}3sempty\n" "${1#/} " "is not "

...which doesn't have the read/search permission problem because all globs resolve for the current directory - though they recurse. Here it is again, fleshed out a little:

ksh -x <<\SCRIPT  
dirempty(){
    cd -P -- "${1-.}" && set "/./${PWD##*/}/" || return 2
    set .?"$1"* .?"$1".[!.]* .?"$1"..?*
    case $* in (*.././*) set --;;esac
    printf "%s%${1+.}2s empty\n" "$PWD " "is not"
    cd -- "$OLDPWD" && return "$((!$#))"
}

###TEST LOOP###

    mkdir empty
    for new in "" ... notdot
    do  for c in touch mkdir
        do  "$c" "empty/$new" 2>&3
            dirempty empty ||
            rm -rf "empty/$new"
        done 
    done 3>/dev/null

###END
SCRIPT

Here's some of the set -x output:

+ touch empty/
+ dirempty empty
+ cd -P -- empty
+ set /./empty/
+ set '.?/./empty/*' '.?/./empty/.[!.]*' '.?/./empty/..?*'
+ printf '%s%.2s empty\n' '/home/mikeserv/empty/ ' 'is not'
/home/mikeserv/empty/ is empty
+ cd -- /home/mikeserv
+ return 0

It is the following line which demonstrates how it works best:

... '.?/./empty/*' '.?/./empty/.[!.]*' '.?/./empty/..?*' ...

...because, for each of *, .[!.]*, and ..?*, the whole pathname is invalid, then so also is each part so no globs resolve - to include the leading .?. Compare that to this other section from the same output...

+ touch empty/...
...
+ set /./empty/
+ set '.?/./empty/*' '.?/./empty/.[!.]*' .././empty/...
+ set --
+ printf '%s%2s empty\n' '/home/mikeserv/empty/ ' 'is not'
/home/mikeserv/empty/ is not empty
+ cd -- /home/mikeserv
+ return 1

See...?

'.?/./empty/*' '.?/./empty/.[!.]*' .././empty/...

...because ..?* resolves, then so also does .?/./ so all that is needed is to look for one occurrence of .././ in the shell array, and is handled like:

case $* in (*.././*)

And that's how you can simply check that a glob resolves (and so also reliably check for any directory contents) - just compare the unknown with the known.

0

Here is a function which works on many Unixes,

on Bourne compatible shells (even prehistoric models),

and doesn't drill too deep (the filesystem, the CPU and the brain):

isempty() { [ -z "`find \"$1/.\" ! -name . -print -prune | head -1`" ] ; }
3
  • Note that those backslashes are needed for the Bourne shell or AT&T versions of ksh (where users are expected to assist the parser which otherwise would think the second quote closes the first one) but otherwise break yash. May 26, 2016 at 15:40
  • Note that it fails for values of $1 that start with - or newline. May 26, 2016 at 15:41
  • Thank you for the improvment and the missing ` :). •• return •• The leading return isn't a serious problem since to pass it to anything you will have to "backslash" or "quote" it. BTW I don't know that many Unix admins who are able to play with such names and survive.
    – dan
    May 26, 2016 at 15:52
0

I might have overseen something, but this should work as well and is IMHO more readable than set or find.

-A just exclude . and .. but include other dotfiles.

| head -n 5 is to shorten the ls in case of many files. You can omit it if you expect to have no or few files in the folders you are checking. Edit : I updated to 5 so we handled a sneeky file name beginning with newline(s).

if [ -z "$(ls -A /path/to/folder | head -n 5)" ] 
then 
    print "EMPTY"
else 
    print "NOT EMPTY"
fi

Checked on AIX 7.1

12
  • Or if ls -A /path/to/folder | grep -q '^'. May 26, 2016 at 15:19
  • $(ls -A /path/to/folder | head -n 1) could be empty if the first file starts with newline characters. May 26, 2016 at 15:20
  • @StéphaneChazelas or even a name with only spaces.
    – Mat M
    May 27, 2016 at 8:50
  • No, space is OK, it's just trailing newline characters that command substitution removes. May 27, 2016 at 9:00
  • @StéphaneChazelas Just a warning :grep -q '^' returns 0 on empty folder in Linux, and does not avoid the full ls listing if many files (which was the goal of the head -n 1 at first)
    – Mat M
    May 27, 2016 at 9:09

You must log in to answer this question.

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