How do I check if a directory is empty or not in ksh on AIX?
If files exist I need to remove files only.
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)" ]
.*
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
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.
In any shell [ "$(find path_to_dir -mindepth 1)" ] || echo EMPTY
find path_to_dir ! -name dir
or find path_to_dir ! -path 'path_to_dir'
?
find path_to_dir/. ! -name .
. -path
is a recent POSIX addition, doesn't seem to have made its way to AIX yet
Feb 11, 2015 at 0:11
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
empty
if there is a broken symbolic called *
(in addition to dot files).
Feb 10, 2015 at 23:31
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.
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`" ] ; }
yash
.
May 26, 2016 at 15:40
$1
that start with -
or newline.
May 26, 2016 at 15:41
`
:). •• 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.
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
$(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
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)
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; maybeksh
does too?