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 50 mins ago
terdon♦
128k31252427
128k31252427
New contributor
asked 1 hour ago
Three
162
162
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.
If a file is named*
, then$1
equals*
and[ -e "$1" ]
passes, meaning the example in the question still works
– Three
18 mins ago
@Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
– Michael Homer
16 mins ago
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:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
The way it works is by using set
to set items in current working directory as positional parameters ( $1
, $2
, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal *
as $1
, .
as $2
and $3
, so we also account for hidden files here.
Of course, it's a possibility that *
could be a real filename, which is why we also perform [ -e "$1" ]
check. If you want to use the function silently, we could rewrite it to return
a value:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
return 1
else
return 0
fi
}
You can see it in action via this repl:
main.sh
rm -rf empty_dir
mkdir empty_dir
pwd
cd empty_dir
pwd
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
dir_empty
touch '*'
dir_empty
rm '*'
touch '.dot_file'
dir_empty
Output:
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
/home/runner
/home/runner/empty_dir
Directory is empty
Directory not empty
Directory not empty
1
This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
– Three
11 mins ago
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.
If a file is named*
, then$1
equals*
and[ -e "$1" ]
passes, meaning the example in the question still works
– Three
18 mins ago
@Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
– Michael Homer
16 mins ago
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.
If a file is named*
, then$1
equals*
and[ -e "$1" ]
passes, meaning the example in the question still works
– Three
18 mins ago
@Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
– Michael Homer
16 mins ago
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 16 mins ago
answered 23 mins ago
Michael Homer
46.3k8121161
46.3k8121161
If a file is named*
, then$1
equals*
and[ -e "$1" ]
passes, meaning the example in the question still works
– Three
18 mins ago
@Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
– Michael Homer
16 mins ago
add a comment |
If a file is named*
, then$1
equals*
and[ -e "$1" ]
passes, meaning the example in the question still works
– Three
18 mins ago
@Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
– Michael Homer
16 mins ago
If a file is named
*
, then $1
equals *
and [ -e "$1" ]
passes, meaning the example in the question still works– Three
18 mins ago
If a file is named
*
, then $1
equals *
and [ -e "$1" ]
passes, meaning the example in the question still works– Three
18 mins ago
@Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
– Michael Homer
16 mins ago
@Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
– Michael Homer
16 mins ago
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:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
The way it works is by using set
to set items in current working directory as positional parameters ( $1
, $2
, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal *
as $1
, .
as $2
and $3
, so we also account for hidden files here.
Of course, it's a possibility that *
could be a real filename, which is why we also perform [ -e "$1" ]
check. If you want to use the function silently, we could rewrite it to return
a value:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
return 1
else
return 0
fi
}
You can see it in action via this repl:
main.sh
rm -rf empty_dir
mkdir empty_dir
pwd
cd empty_dir
pwd
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
dir_empty
touch '*'
dir_empty
rm '*'
touch '.dot_file'
dir_empty
Output:
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
/home/runner
/home/runner/empty_dir
Directory is empty
Directory not empty
Directory not empty
1
This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
– Three
11 mins ago
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:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
The way it works is by using set
to set items in current working directory as positional parameters ( $1
, $2
, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal *
as $1
, .
as $2
and $3
, so we also account for hidden files here.
Of course, it's a possibility that *
could be a real filename, which is why we also perform [ -e "$1" ]
check. If you want to use the function silently, we could rewrite it to return
a value:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
return 1
else
return 0
fi
}
You can see it in action via this repl:
main.sh
rm -rf empty_dir
mkdir empty_dir
pwd
cd empty_dir
pwd
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
dir_empty
touch '*'
dir_empty
rm '*'
touch '.dot_file'
dir_empty
Output:
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
/home/runner
/home/runner/empty_dir
Directory is empty
Directory not empty
Directory not empty
1
This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
– Three
11 mins ago
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:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
The way it works is by using set
to set items in current working directory as positional parameters ( $1
, $2
, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal *
as $1
, .
as $2
and $3
, so we also account for hidden files here.
Of course, it's a possibility that *
could be a real filename, which is why we also perform [ -e "$1" ]
check. If you want to use the function silently, we could rewrite it to return
a value:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
return 1
else
return 0
fi
}
You can see it in action via this repl:
main.sh
rm -rf empty_dir
mkdir empty_dir
pwd
cd empty_dir
pwd
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
dir_empty
touch '*'
dir_empty
rm '*'
touch '.dot_file'
dir_empty
Output:
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
/home/runner
/home/runner/empty_dir
Directory is empty
Directory not empty
Directory not empty
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:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
The way it works is by using set
to set items in current working directory as positional parameters ( $1
, $2
, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal *
as $1
, .
as $2
and $3
, so we also account for hidden files here.
Of course, it's a possibility that *
could be a real filename, which is why we also perform [ -e "$1" ]
check. If you want to use the function silently, we could rewrite it to return
a value:
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
return 1
else
return 0
fi
}
You can see it in action via this repl:
main.sh
rm -rf empty_dir
mkdir empty_dir
pwd
cd empty_dir
pwd
dir_empty(){
# fully empty directory will only have
# 3 parameters * . ..
set -- * .*
if [ $# -ne 3 ] || [ -e $1 ]; then
echo "Directory not empty"
else
echo "Directory is empty"
fi
}
dir_empty
touch '*'
dir_empty
rm '*'
touch '.dot_file'
dir_empty
Output:
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
/home/runner
/home/runner/empty_dir
Directory is empty
Directory not empty
Directory not empty
answered 16 mins ago
Sergiy Kolodyazhnyy
8,38212152
8,38212152
1
This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
– Three
11 mins ago
add a comment |
1
This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
– Three
11 mins ago
1
1
This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
– Three
11 mins ago
This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
– Three
11 mins ago
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.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- 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