Portable check empty directory
With Bash and Dash, you can check for an empty directory using just the shell
(ignore dotfiles to keep things simple):
set *
if [ -e "$1" ]
then
echo 'not empty'
else
echo 'empty'
fi
However I recently learned that Zsh fails spectacularly in this case:
% set *
zsh: no matches found: *
% echo "$? $#"
1 0
So not only does the set
command fail, but it doesn't even set $@
. I suppose
I could test if $#
is 0
, but it appears that Zsh even stops execution:
% { set *; echo 2; }
zsh: no matches found: *
Compare with Bash and Dash:
$ { set *; echo 2; }
2
Can this be done in a way that works in bash, dash and zsh?
bash zsh wildcards portability dash
New contributor
add a comment |
With Bash and Dash, you can check for an empty directory using just the shell
(ignore dotfiles to keep things simple):
set *
if [ -e "$1" ]
then
echo 'not empty'
else
echo 'empty'
fi
However I recently learned that Zsh fails spectacularly in this case:
% set *
zsh: no matches found: *
% echo "$? $#"
1 0
So not only does the set
command fail, but it doesn't even set $@
. I suppose
I could test if $#
is 0
, but it appears that Zsh even stops execution:
% { set *; echo 2; }
zsh: no matches found: *
Compare with Bash and Dash:
$ { set *; echo 2; }
2
Can this be done in a way that works in bash, dash and zsh?
bash zsh wildcards portability dash
New contributor
add a comment |
With Bash and Dash, you can check for an empty directory using just the shell
(ignore dotfiles to keep things simple):
set *
if [ -e "$1" ]
then
echo 'not empty'
else
echo 'empty'
fi
However I recently learned that Zsh fails spectacularly in this case:
% set *
zsh: no matches found: *
% echo "$? $#"
1 0
So not only does the set
command fail, but it doesn't even set $@
. I suppose
I could test if $#
is 0
, but it appears that Zsh even stops execution:
% { set *; echo 2; }
zsh: no matches found: *
Compare with Bash and Dash:
$ { set *; echo 2; }
2
Can this be done in a way that works in bash, dash and zsh?
bash zsh wildcards portability dash
New contributor
With Bash and Dash, you can check for an empty directory using just the shell
(ignore dotfiles to keep things simple):
set *
if [ -e "$1" ]
then
echo 'not empty'
else
echo 'empty'
fi
However I recently learned that Zsh fails spectacularly in this case:
% set *
zsh: no matches found: *
% echo "$? $#"
1 0
So not only does the set
command fail, but it doesn't even set $@
. I suppose
I could test if $#
is 0
, but it appears that Zsh even stops execution:
% { set *; echo 2; }
zsh: no matches found: *
Compare with Bash and Dash:
$ { set *; echo 2; }
2
Can this be done in a way that works in bash, dash and zsh?
bash zsh wildcards portability dash
bash zsh wildcards portability dash
New contributor
New contributor
edited Jan 7 at 1:28
terdon♦
129k32253428
129k32253428
New contributor
asked Jan 7 at 1:06
ThreeThree
362
362
New contributor
New contributor
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
While zsh's default behaviour is to give an error, this is controlled by the nomatch
option. You can unset the option to leave the *
in place the way that bash and dash do:
setopt -o nonomatch
While that command won't work in either of the others, you can just ignore that:
setopt -o nonomatch 2>/dev/null || true ; set *
This runs setopt
on zsh, and suppresses the error output (2>/dev/null
) and return code (|| true
) of the failed command on the others.
As written it's problematic if there is a file, for example, -e
: then you will run set -e
and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- *
will be safer and prevent the option changes.
add a comment |
Most portable way would be via set
and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:
rm -rf empty_dir/
mkdir empty_dir/
pwd
cd empty_dir/
pwd
dir_empty(){
# https://stackoverflow.com/a/9911082/3701431
if [ -n "$ZSH_VERSION" ]; then
# https://unix.stackexchange.com/a/310553/85039
setopt +o nomatch
fi
set -- * .*
echo "$@"
for i; do
[ "$i" = "." ] || [ "$i" = ".." ] && continue
[ -e "$i" ] && echo "Not empty" && return 1
done
echo "Empty" && return 0
}
dir_empty
touch '*'
dir_empty
The big problem with zsh
is that while ksh
and bash
behave in more or less consistent manner - that is when we do set * .*
you will have 3 positional parameters * . ..
in really empty directory - in zsh
you will get * .*
as positional parameters. Luckily at least for i ; do ... done
to iterate over positional parameters works consistently. The rest is just iteration and check for existence of the filename, with .
and ..
skipped.
Try it online in ksh!
Try it online in zsh!
@Three I've adapted the answer to check forzsh
. Unfortunatelly for uszsh
decided to go the weird way instead of similar behavior tobash
or other shells, since according to POSIX: "If the pattern does not match any pathnames, the returned number of matched paths is set to 0, and the contents of pglob->gl_pathv are implementation-defined." source
– Sergiy Kolodyazhnyy
Jan 7 at 2:25
turningnomatch
everywhere risks breaking code that assumes the default (and very sensible) setting thatsh
andbash
get wrong, so the change really should be localized only to this function
– thrig
Jan 7 at 3:02
@thrig Well, considering that so far others haven't found a way to make glob work withoutnomatch
, that's the best we got. We can also toggle it back before function exits, of course. Or we could just abandon shell ways and just use something else, likefind
for instance.
– Sergiy Kolodyazhnyy
Jan 7 at 3:16
@Three I've revised the answer again. Probably this is the best I can do, aszsh
seems to favor features instead of consistency. Hope this helps somewhat.
– Sergiy Kolodyazhnyy
Jan 7 at 3:18
@SergiyKolodyazhnyy yes thanks - I would just avoid Zsh totally but its used on Manjaro - maybe I will avoid that too :)
– Three
Jan 7 at 3:20
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "106"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Three is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f492912%2fportable-check-empty-directory%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
While zsh's default behaviour is to give an error, this is controlled by the nomatch
option. You can unset the option to leave the *
in place the way that bash and dash do:
setopt -o nonomatch
While that command won't work in either of the others, you can just ignore that:
setopt -o nonomatch 2>/dev/null || true ; set *
This runs setopt
on zsh, and suppresses the error output (2>/dev/null
) and return code (|| true
) of the failed command on the others.
As written it's problematic if there is a file, for example, -e
: then you will run set -e
and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- *
will be safer and prevent the option changes.
add a comment |
While zsh's default behaviour is to give an error, this is controlled by the nomatch
option. You can unset the option to leave the *
in place the way that bash and dash do:
setopt -o nonomatch
While that command won't work in either of the others, you can just ignore that:
setopt -o nonomatch 2>/dev/null || true ; set *
This runs setopt
on zsh, and suppresses the error output (2>/dev/null
) and return code (|| true
) of the failed command on the others.
As written it's problematic if there is a file, for example, -e
: then you will run set -e
and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- *
will be safer and prevent the option changes.
add a comment |
While zsh's default behaviour is to give an error, this is controlled by the nomatch
option. You can unset the option to leave the *
in place the way that bash and dash do:
setopt -o nonomatch
While that command won't work in either of the others, you can just ignore that:
setopt -o nonomatch 2>/dev/null || true ; set *
This runs setopt
on zsh, and suppresses the error output (2>/dev/null
) and return code (|| true
) of the failed command on the others.
As written it's problematic if there is a file, for example, -e
: then you will run set -e
and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- *
will be safer and prevent the option changes.
While zsh's default behaviour is to give an error, this is controlled by the nomatch
option. You can unset the option to leave the *
in place the way that bash and dash do:
setopt -o nonomatch
While that command won't work in either of the others, you can just ignore that:
setopt -o nonomatch 2>/dev/null || true ; set *
This runs setopt
on zsh, and suppresses the error output (2>/dev/null
) and return code (|| true
) of the failed command on the others.
As written it's problematic if there is a file, for example, -e
: then you will run set -e
and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- *
will be safer and prevent the option changes.
edited Jan 7 at 2:02
answered Jan 7 at 1:55
Michael HomerMichael Homer
46.5k8122161
46.5k8122161
add a comment |
add a comment |
Most portable way would be via set
and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:
rm -rf empty_dir/
mkdir empty_dir/
pwd
cd empty_dir/
pwd
dir_empty(){
# https://stackoverflow.com/a/9911082/3701431
if [ -n "$ZSH_VERSION" ]; then
# https://unix.stackexchange.com/a/310553/85039
setopt +o nomatch
fi
set -- * .*
echo "$@"
for i; do
[ "$i" = "." ] || [ "$i" = ".." ] && continue
[ -e "$i" ] && echo "Not empty" && return 1
done
echo "Empty" && return 0
}
dir_empty
touch '*'
dir_empty
The big problem with zsh
is that while ksh
and bash
behave in more or less consistent manner - that is when we do set * .*
you will have 3 positional parameters * . ..
in really empty directory - in zsh
you will get * .*
as positional parameters. Luckily at least for i ; do ... done
to iterate over positional parameters works consistently. The rest is just iteration and check for existence of the filename, with .
and ..
skipped.
Try it online in ksh!
Try it online in zsh!
@Three I've adapted the answer to check forzsh
. Unfortunatelly for uszsh
decided to go the weird way instead of similar behavior tobash
or other shells, since according to POSIX: "If the pattern does not match any pathnames, the returned number of matched paths is set to 0, and the contents of pglob->gl_pathv are implementation-defined." source
– Sergiy Kolodyazhnyy
Jan 7 at 2:25
turningnomatch
everywhere risks breaking code that assumes the default (and very sensible) setting thatsh
andbash
get wrong, so the change really should be localized only to this function
– thrig
Jan 7 at 3:02
@thrig Well, considering that so far others haven't found a way to make glob work withoutnomatch
, that's the best we got. We can also toggle it back before function exits, of course. Or we could just abandon shell ways and just use something else, likefind
for instance.
– Sergiy Kolodyazhnyy
Jan 7 at 3:16
@Three I've revised the answer again. Probably this is the best I can do, aszsh
seems to favor features instead of consistency. Hope this helps somewhat.
– Sergiy Kolodyazhnyy
Jan 7 at 3:18
@SergiyKolodyazhnyy yes thanks - I would just avoid Zsh totally but its used on Manjaro - maybe I will avoid that too :)
– Three
Jan 7 at 3:20
add a comment |
Most portable way would be via set
and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:
rm -rf empty_dir/
mkdir empty_dir/
pwd
cd empty_dir/
pwd
dir_empty(){
# https://stackoverflow.com/a/9911082/3701431
if [ -n "$ZSH_VERSION" ]; then
# https://unix.stackexchange.com/a/310553/85039
setopt +o nomatch
fi
set -- * .*
echo "$@"
for i; do
[ "$i" = "." ] || [ "$i" = ".." ] && continue
[ -e "$i" ] && echo "Not empty" && return 1
done
echo "Empty" && return 0
}
dir_empty
touch '*'
dir_empty
The big problem with zsh
is that while ksh
and bash
behave in more or less consistent manner - that is when we do set * .*
you will have 3 positional parameters * . ..
in really empty directory - in zsh
you will get * .*
as positional parameters. Luckily at least for i ; do ... done
to iterate over positional parameters works consistently. The rest is just iteration and check for existence of the filename, with .
and ..
skipped.
Try it online in ksh!
Try it online in zsh!
@Three I've adapted the answer to check forzsh
. Unfortunatelly for uszsh
decided to go the weird way instead of similar behavior tobash
or other shells, since according to POSIX: "If the pattern does not match any pathnames, the returned number of matched paths is set to 0, and the contents of pglob->gl_pathv are implementation-defined." source
– Sergiy Kolodyazhnyy
Jan 7 at 2:25
turningnomatch
everywhere risks breaking code that assumes the default (and very sensible) setting thatsh
andbash
get wrong, so the change really should be localized only to this function
– thrig
Jan 7 at 3:02
@thrig Well, considering that so far others haven't found a way to make glob work withoutnomatch
, that's the best we got. We can also toggle it back before function exits, of course. Or we could just abandon shell ways and just use something else, likefind
for instance.
– Sergiy Kolodyazhnyy
Jan 7 at 3:16
@Three I've revised the answer again. Probably this is the best I can do, aszsh
seems to favor features instead of consistency. Hope this helps somewhat.
– Sergiy Kolodyazhnyy
Jan 7 at 3:18
@SergiyKolodyazhnyy yes thanks - I would just avoid Zsh totally but its used on Manjaro - maybe I will avoid that too :)
– Three
Jan 7 at 3:20
add a comment |
Most portable way would be via set
and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:
rm -rf empty_dir/
mkdir empty_dir/
pwd
cd empty_dir/
pwd
dir_empty(){
# https://stackoverflow.com/a/9911082/3701431
if [ -n "$ZSH_VERSION" ]; then
# https://unix.stackexchange.com/a/310553/85039
setopt +o nomatch
fi
set -- * .*
echo "$@"
for i; do
[ "$i" = "." ] || [ "$i" = ".." ] && continue
[ -e "$i" ] && echo "Not empty" && return 1
done
echo "Empty" && return 0
}
dir_empty
touch '*'
dir_empty
The big problem with zsh
is that while ksh
and bash
behave in more or less consistent manner - that is when we do set * .*
you will have 3 positional parameters * . ..
in really empty directory - in zsh
you will get * .*
as positional parameters. Luckily at least for i ; do ... done
to iterate over positional parameters works consistently. The rest is just iteration and check for existence of the filename, with .
and ..
skipped.
Try it online in ksh!
Try it online in zsh!
Most portable way would be via set
and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:
rm -rf empty_dir/
mkdir empty_dir/
pwd
cd empty_dir/
pwd
dir_empty(){
# https://stackoverflow.com/a/9911082/3701431
if [ -n "$ZSH_VERSION" ]; then
# https://unix.stackexchange.com/a/310553/85039
setopt +o nomatch
fi
set -- * .*
echo "$@"
for i; do
[ "$i" = "." ] || [ "$i" = ".." ] && continue
[ -e "$i" ] && echo "Not empty" && return 1
done
echo "Empty" && return 0
}
dir_empty
touch '*'
dir_empty
The big problem with zsh
is that while ksh
and bash
behave in more or less consistent manner - that is when we do set * .*
you will have 3 positional parameters * . ..
in really empty directory - in zsh
you will get * .*
as positional parameters. Luckily at least for i ; do ... done
to iterate over positional parameters works consistently. The rest is just iteration and check for existence of the filename, with .
and ..
skipped.
Try it online in ksh!
Try it online in zsh!
edited Jan 7 at 3:13
answered Jan 7 at 2:02
Sergiy KolodyazhnyySergiy Kolodyazhnyy
8,53112254
8,53112254
@Three I've adapted the answer to check forzsh
. Unfortunatelly for uszsh
decided to go the weird way instead of similar behavior tobash
or other shells, since according to POSIX: "If the pattern does not match any pathnames, the returned number of matched paths is set to 0, and the contents of pglob->gl_pathv are implementation-defined." source
– Sergiy Kolodyazhnyy
Jan 7 at 2:25
turningnomatch
everywhere risks breaking code that assumes the default (and very sensible) setting thatsh
andbash
get wrong, so the change really should be localized only to this function
– thrig
Jan 7 at 3:02
@thrig Well, considering that so far others haven't found a way to make glob work withoutnomatch
, that's the best we got. We can also toggle it back before function exits, of course. Or we could just abandon shell ways and just use something else, likefind
for instance.
– Sergiy Kolodyazhnyy
Jan 7 at 3:16
@Three I've revised the answer again. Probably this is the best I can do, aszsh
seems to favor features instead of consistency. Hope this helps somewhat.
– Sergiy Kolodyazhnyy
Jan 7 at 3:18
@SergiyKolodyazhnyy yes thanks - I would just avoid Zsh totally but its used on Manjaro - maybe I will avoid that too :)
– Three
Jan 7 at 3:20
add a comment |
@Three I've adapted the answer to check forzsh
. Unfortunatelly for uszsh
decided to go the weird way instead of similar behavior tobash
or other shells, since according to POSIX: "If the pattern does not match any pathnames, the returned number of matched paths is set to 0, and the contents of pglob->gl_pathv are implementation-defined." source
– Sergiy Kolodyazhnyy
Jan 7 at 2:25
turningnomatch
everywhere risks breaking code that assumes the default (and very sensible) setting thatsh
andbash
get wrong, so the change really should be localized only to this function
– thrig
Jan 7 at 3:02
@thrig Well, considering that so far others haven't found a way to make glob work withoutnomatch
, that's the best we got. We can also toggle it back before function exits, of course. Or we could just abandon shell ways and just use something else, likefind
for instance.
– Sergiy Kolodyazhnyy
Jan 7 at 3:16
@Three I've revised the answer again. Probably this is the best I can do, aszsh
seems to favor features instead of consistency. Hope this helps somewhat.
– Sergiy Kolodyazhnyy
Jan 7 at 3:18
@SergiyKolodyazhnyy yes thanks - I would just avoid Zsh totally but its used on Manjaro - maybe I will avoid that too :)
– Three
Jan 7 at 3:20
@Three I've adapted the answer to check for
zsh
. Unfortunatelly for us zsh
decided to go the weird way instead of similar behavior to bash
or other shells, since according to POSIX: "If the pattern does not match any pathnames, the returned number of matched paths is set to 0, and the contents of pglob->gl_pathv are implementation-defined." source– Sergiy Kolodyazhnyy
Jan 7 at 2:25
@Three I've adapted the answer to check for
zsh
. Unfortunatelly for us zsh
decided to go the weird way instead of similar behavior to bash
or other shells, since according to POSIX: "If the pattern does not match any pathnames, the returned number of matched paths is set to 0, and the contents of pglob->gl_pathv are implementation-defined." source– Sergiy Kolodyazhnyy
Jan 7 at 2:25
turning
nomatch
everywhere risks breaking code that assumes the default (and very sensible) setting that sh
and bash
get wrong, so the change really should be localized only to this function– thrig
Jan 7 at 3:02
turning
nomatch
everywhere risks breaking code that assumes the default (and very sensible) setting that sh
and bash
get wrong, so the change really should be localized only to this function– thrig
Jan 7 at 3:02
@thrig Well, considering that so far others haven't found a way to make glob work without
nomatch
, that's the best we got. We can also toggle it back before function exits, of course. Or we could just abandon shell ways and just use something else, like find
for instance.– Sergiy Kolodyazhnyy
Jan 7 at 3:16
@thrig Well, considering that so far others haven't found a way to make glob work without
nomatch
, that's the best we got. We can also toggle it back before function exits, of course. Or we could just abandon shell ways and just use something else, like find
for instance.– Sergiy Kolodyazhnyy
Jan 7 at 3:16
@Three I've revised the answer again. Probably this is the best I can do, as
zsh
seems to favor features instead of consistency. Hope this helps somewhat.– Sergiy Kolodyazhnyy
Jan 7 at 3:18
@Three I've revised the answer again. Probably this is the best I can do, as
zsh
seems to favor features instead of consistency. Hope this helps somewhat.– Sergiy Kolodyazhnyy
Jan 7 at 3:18
@SergiyKolodyazhnyy yes thanks - I would just avoid Zsh totally but its used on Manjaro - maybe I will avoid that too :)
– Three
Jan 7 at 3:20
@SergiyKolodyazhnyy yes thanks - I would just avoid Zsh totally but its used on Manjaro - maybe I will avoid that too :)
– Three
Jan 7 at 3:20
add a comment |
Three is a new contributor. Be nice, and check out our Code of Conduct.
Three is a new contributor. Be nice, and check out our Code of Conduct.
Three is a new contributor. Be nice, and check out our Code of Conduct.
Three is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Unix & Linux Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f492912%2fportable-check-empty-directory%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown