Find all empty directories AND directories with a single type of file
I'm trying to write a bash script that delete all empty directories as well as any directory that only container the .DS_Store
file that Mac generates. I can do the former pretty easily with find -depth -type d -empty
but I can't figure how to find directories that only contain .DS_Store
.
Is there an easy way of doing this without writing my own recursive search function?
bash command-line find
add a comment |
I'm trying to write a bash script that delete all empty directories as well as any directory that only container the .DS_Store
file that Mac generates. I can do the former pretty easily with find -depth -type d -empty
but I can't figure how to find directories that only contain .DS_Store
.
Is there an easy way of doing this without writing my own recursive search function?
bash command-line find
add a comment |
I'm trying to write a bash script that delete all empty directories as well as any directory that only container the .DS_Store
file that Mac generates. I can do the former pretty easily with find -depth -type d -empty
but I can't figure how to find directories that only contain .DS_Store
.
Is there an easy way of doing this without writing my own recursive search function?
bash command-line find
I'm trying to write a bash script that delete all empty directories as well as any directory that only container the .DS_Store
file that Mac generates. I can do the former pretty easily with find -depth -type d -empty
but I can't figure how to find directories that only contain .DS_Store
.
Is there an easy way of doing this without writing my own recursive search function?
bash command-line find
bash command-line find
edited Dec 17 at 20:05
nohillside
2,292819
2,292819
asked Dec 17 at 19:54
David
1084
1084
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
POSIX sh + find
Here's a solution that relies only on POSIX find and POSIX sh. List all directories, then filter those that only contain an entry called .DS_Store
.
find -type d -exec sh -c '
cd "$0" &&
for x in * .[!.]* ..?*; do
if [ "$x" = ".DS_Store" ]; then continue; fi;
if [ -e "$x" ] || [ -L "$x" ]; then exit 1; fi;
done' {} ; -print
- I use
find
to enumerate all directories recursively. - On each directory, I call
sh
to run some shell code. - The
for
loop enumerates all the files in the directory. - The body of the loop skips
.DS_Store
. - Each of the three patterns is left unchanged if it doesn't match any file.
[ -e "$x" ] || [ -L "$x" ]
captures any file including broken symbolic links; the only way they don't match is if a pattern was left unchanged. - Therefore the shell snippet runs
exit 1
if there is a file other than.DS_Store
, and returns 0 for success otherwise. - Change
-print
to-exec …
if you want to do something other than printing the names.
Zsh
Here's a solution in zsh. Change echo
to whatever command you want to run.
setopt extended_glob
echo **/*(/DNe''a=($REPLY/^.DS_Store(DNY1)); ((!#a))'')
**/*
enumerates all files recursively.- With the glob qualifier
/
,**/*(/)
enumerates all directories recursively. - The glob qualifier
N
ensures that you get an empty list if there are no matches (by default zsh signals an error). - The glob qualifier
D
causes dot files to be included. - The glob qualifier
e''CODE''
runsCODE
on each matching file name and limits the matches to those for whichCODE
succeeds.CODE
can use the variable$REPLY
to refer to the file name.
^.DS_Store
matches files that are not called.DS_Store
.- Thus the CODE limits the matches to those for which the number of files other than
.DS_Store
is zero. - The glob qualifier
Y1
limits the matches to one (it's only an efficiency improvement).
Python
Here's a solution in Python (it works in both 2 and 3). The structure is rather clearer despite this being compressed into a one-liner.
python -c 'import os; print("n".join([path for path, dirs, files in os.walk(".") if dirs == and files in (, [".DS_Store"])]))'
os.walk
returns a list of directories recursively under its argument. For each directory, it produces a triple containingpath
(the path to the directory),dirs
(the list of subdirectories) andfiles
(the list of files in the directory that aren't themselves directories).
[… for … in os.walk(…) if …]
filters the result ofos.walk
.- The
if
clause keeps an element only if it has no subdirectories and no files other than.DS_Store
. - The script prints the accepted elements, joined with a newline in between and with a final newline.
add a comment |
Easy solution:
first, delete all such files:
find <path> -type f -name "*.DS_Store" -delete
then delete empty directories.
Update based on comment:
In order to delete only directories that have only such files, you will need something like (caution: not tested at all, I will not be surprised if it needs to be a bit more involved):
find <path> -type d | while read dir; do
if ! ls --ignore=*.DS_Store $dir; then
rm -rf $dir
fi
done
Explanation of complicated part:
ls --ignore=*.DS_Store $dir
should print files in $dir that do not end in .DS_Store. If there are none, the expression is False and the if-block is executed.
I though of this but I don't want to delete all .DS_Store files, only the ones in directories that are otherwise empty.
– David
Dec 17 at 20:04
1
Sorry to spoil the fun, but the standardls
on macOS doesn't know about--ignore
...
– nohillside
Dec 17 at 20:27
2
in that case, --ignore can be emulated with suitable 'grep -v'
– WerKater
Dec 17 at 20:33
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
});
}
});
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%2f489556%2ffind-all-empty-directories-and-directories-with-a-single-type-of-file%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
POSIX sh + find
Here's a solution that relies only on POSIX find and POSIX sh. List all directories, then filter those that only contain an entry called .DS_Store
.
find -type d -exec sh -c '
cd "$0" &&
for x in * .[!.]* ..?*; do
if [ "$x" = ".DS_Store" ]; then continue; fi;
if [ -e "$x" ] || [ -L "$x" ]; then exit 1; fi;
done' {} ; -print
- I use
find
to enumerate all directories recursively. - On each directory, I call
sh
to run some shell code. - The
for
loop enumerates all the files in the directory. - The body of the loop skips
.DS_Store
. - Each of the three patterns is left unchanged if it doesn't match any file.
[ -e "$x" ] || [ -L "$x" ]
captures any file including broken symbolic links; the only way they don't match is if a pattern was left unchanged. - Therefore the shell snippet runs
exit 1
if there is a file other than.DS_Store
, and returns 0 for success otherwise. - Change
-print
to-exec …
if you want to do something other than printing the names.
Zsh
Here's a solution in zsh. Change echo
to whatever command you want to run.
setopt extended_glob
echo **/*(/DNe''a=($REPLY/^.DS_Store(DNY1)); ((!#a))'')
**/*
enumerates all files recursively.- With the glob qualifier
/
,**/*(/)
enumerates all directories recursively. - The glob qualifier
N
ensures that you get an empty list if there are no matches (by default zsh signals an error). - The glob qualifier
D
causes dot files to be included. - The glob qualifier
e''CODE''
runsCODE
on each matching file name and limits the matches to those for whichCODE
succeeds.CODE
can use the variable$REPLY
to refer to the file name.
^.DS_Store
matches files that are not called.DS_Store
.- Thus the CODE limits the matches to those for which the number of files other than
.DS_Store
is zero. - The glob qualifier
Y1
limits the matches to one (it's only an efficiency improvement).
Python
Here's a solution in Python (it works in both 2 and 3). The structure is rather clearer despite this being compressed into a one-liner.
python -c 'import os; print("n".join([path for path, dirs, files in os.walk(".") if dirs == and files in (, [".DS_Store"])]))'
os.walk
returns a list of directories recursively under its argument. For each directory, it produces a triple containingpath
(the path to the directory),dirs
(the list of subdirectories) andfiles
(the list of files in the directory that aren't themselves directories).
[… for … in os.walk(…) if …]
filters the result ofos.walk
.- The
if
clause keeps an element only if it has no subdirectories and no files other than.DS_Store
. - The script prints the accepted elements, joined with a newline in between and with a final newline.
add a comment |
POSIX sh + find
Here's a solution that relies only on POSIX find and POSIX sh. List all directories, then filter those that only contain an entry called .DS_Store
.
find -type d -exec sh -c '
cd "$0" &&
for x in * .[!.]* ..?*; do
if [ "$x" = ".DS_Store" ]; then continue; fi;
if [ -e "$x" ] || [ -L "$x" ]; then exit 1; fi;
done' {} ; -print
- I use
find
to enumerate all directories recursively. - On each directory, I call
sh
to run some shell code. - The
for
loop enumerates all the files in the directory. - The body of the loop skips
.DS_Store
. - Each of the three patterns is left unchanged if it doesn't match any file.
[ -e "$x" ] || [ -L "$x" ]
captures any file including broken symbolic links; the only way they don't match is if a pattern was left unchanged. - Therefore the shell snippet runs
exit 1
if there is a file other than.DS_Store
, and returns 0 for success otherwise. - Change
-print
to-exec …
if you want to do something other than printing the names.
Zsh
Here's a solution in zsh. Change echo
to whatever command you want to run.
setopt extended_glob
echo **/*(/DNe''a=($REPLY/^.DS_Store(DNY1)); ((!#a))'')
**/*
enumerates all files recursively.- With the glob qualifier
/
,**/*(/)
enumerates all directories recursively. - The glob qualifier
N
ensures that you get an empty list if there are no matches (by default zsh signals an error). - The glob qualifier
D
causes dot files to be included. - The glob qualifier
e''CODE''
runsCODE
on each matching file name and limits the matches to those for whichCODE
succeeds.CODE
can use the variable$REPLY
to refer to the file name.
^.DS_Store
matches files that are not called.DS_Store
.- Thus the CODE limits the matches to those for which the number of files other than
.DS_Store
is zero. - The glob qualifier
Y1
limits the matches to one (it's only an efficiency improvement).
Python
Here's a solution in Python (it works in both 2 and 3). The structure is rather clearer despite this being compressed into a one-liner.
python -c 'import os; print("n".join([path for path, dirs, files in os.walk(".") if dirs == and files in (, [".DS_Store"])]))'
os.walk
returns a list of directories recursively under its argument. For each directory, it produces a triple containingpath
(the path to the directory),dirs
(the list of subdirectories) andfiles
(the list of files in the directory that aren't themselves directories).
[… for … in os.walk(…) if …]
filters the result ofos.walk
.- The
if
clause keeps an element only if it has no subdirectories and no files other than.DS_Store
. - The script prints the accepted elements, joined with a newline in between and with a final newline.
add a comment |
POSIX sh + find
Here's a solution that relies only on POSIX find and POSIX sh. List all directories, then filter those that only contain an entry called .DS_Store
.
find -type d -exec sh -c '
cd "$0" &&
for x in * .[!.]* ..?*; do
if [ "$x" = ".DS_Store" ]; then continue; fi;
if [ -e "$x" ] || [ -L "$x" ]; then exit 1; fi;
done' {} ; -print
- I use
find
to enumerate all directories recursively. - On each directory, I call
sh
to run some shell code. - The
for
loop enumerates all the files in the directory. - The body of the loop skips
.DS_Store
. - Each of the three patterns is left unchanged if it doesn't match any file.
[ -e "$x" ] || [ -L "$x" ]
captures any file including broken symbolic links; the only way they don't match is if a pattern was left unchanged. - Therefore the shell snippet runs
exit 1
if there is a file other than.DS_Store
, and returns 0 for success otherwise. - Change
-print
to-exec …
if you want to do something other than printing the names.
Zsh
Here's a solution in zsh. Change echo
to whatever command you want to run.
setopt extended_glob
echo **/*(/DNe''a=($REPLY/^.DS_Store(DNY1)); ((!#a))'')
**/*
enumerates all files recursively.- With the glob qualifier
/
,**/*(/)
enumerates all directories recursively. - The glob qualifier
N
ensures that you get an empty list if there are no matches (by default zsh signals an error). - The glob qualifier
D
causes dot files to be included. - The glob qualifier
e''CODE''
runsCODE
on each matching file name and limits the matches to those for whichCODE
succeeds.CODE
can use the variable$REPLY
to refer to the file name.
^.DS_Store
matches files that are not called.DS_Store
.- Thus the CODE limits the matches to those for which the number of files other than
.DS_Store
is zero. - The glob qualifier
Y1
limits the matches to one (it's only an efficiency improvement).
Python
Here's a solution in Python (it works in both 2 and 3). The structure is rather clearer despite this being compressed into a one-liner.
python -c 'import os; print("n".join([path for path, dirs, files in os.walk(".") if dirs == and files in (, [".DS_Store"])]))'
os.walk
returns a list of directories recursively under its argument. For each directory, it produces a triple containingpath
(the path to the directory),dirs
(the list of subdirectories) andfiles
(the list of files in the directory that aren't themselves directories).
[… for … in os.walk(…) if …]
filters the result ofos.walk
.- The
if
clause keeps an element only if it has no subdirectories and no files other than.DS_Store
. - The script prints the accepted elements, joined with a newline in between and with a final newline.
POSIX sh + find
Here's a solution that relies only on POSIX find and POSIX sh. List all directories, then filter those that only contain an entry called .DS_Store
.
find -type d -exec sh -c '
cd "$0" &&
for x in * .[!.]* ..?*; do
if [ "$x" = ".DS_Store" ]; then continue; fi;
if [ -e "$x" ] || [ -L "$x" ]; then exit 1; fi;
done' {} ; -print
- I use
find
to enumerate all directories recursively. - On each directory, I call
sh
to run some shell code. - The
for
loop enumerates all the files in the directory. - The body of the loop skips
.DS_Store
. - Each of the three patterns is left unchanged if it doesn't match any file.
[ -e "$x" ] || [ -L "$x" ]
captures any file including broken symbolic links; the only way they don't match is if a pattern was left unchanged. - Therefore the shell snippet runs
exit 1
if there is a file other than.DS_Store
, and returns 0 for success otherwise. - Change
-print
to-exec …
if you want to do something other than printing the names.
Zsh
Here's a solution in zsh. Change echo
to whatever command you want to run.
setopt extended_glob
echo **/*(/DNe''a=($REPLY/^.DS_Store(DNY1)); ((!#a))'')
**/*
enumerates all files recursively.- With the glob qualifier
/
,**/*(/)
enumerates all directories recursively. - The glob qualifier
N
ensures that you get an empty list if there are no matches (by default zsh signals an error). - The glob qualifier
D
causes dot files to be included. - The glob qualifier
e''CODE''
runsCODE
on each matching file name and limits the matches to those for whichCODE
succeeds.CODE
can use the variable$REPLY
to refer to the file name.
^.DS_Store
matches files that are not called.DS_Store
.- Thus the CODE limits the matches to those for which the number of files other than
.DS_Store
is zero. - The glob qualifier
Y1
limits the matches to one (it's only an efficiency improvement).
Python
Here's a solution in Python (it works in both 2 and 3). The structure is rather clearer despite this being compressed into a one-liner.
python -c 'import os; print("n".join([path for path, dirs, files in os.walk(".") if dirs == and files in (, [".DS_Store"])]))'
os.walk
returns a list of directories recursively under its argument. For each directory, it produces a triple containingpath
(the path to the directory),dirs
(the list of subdirectories) andfiles
(the list of files in the directory that aren't themselves directories).
[… for … in os.walk(…) if …]
filters the result ofos.walk
.- The
if
clause keeps an element only if it has no subdirectories and no files other than.DS_Store
. - The script prints the accepted elements, joined with a newline in between and with a final newline.
answered Dec 17 at 21:14
Gilles
528k12810581583
528k12810581583
add a comment |
add a comment |
Easy solution:
first, delete all such files:
find <path> -type f -name "*.DS_Store" -delete
then delete empty directories.
Update based on comment:
In order to delete only directories that have only such files, you will need something like (caution: not tested at all, I will not be surprised if it needs to be a bit more involved):
find <path> -type d | while read dir; do
if ! ls --ignore=*.DS_Store $dir; then
rm -rf $dir
fi
done
Explanation of complicated part:
ls --ignore=*.DS_Store $dir
should print files in $dir that do not end in .DS_Store. If there are none, the expression is False and the if-block is executed.
I though of this but I don't want to delete all .DS_Store files, only the ones in directories that are otherwise empty.
– David
Dec 17 at 20:04
1
Sorry to spoil the fun, but the standardls
on macOS doesn't know about--ignore
...
– nohillside
Dec 17 at 20:27
2
in that case, --ignore can be emulated with suitable 'grep -v'
– WerKater
Dec 17 at 20:33
add a comment |
Easy solution:
first, delete all such files:
find <path> -type f -name "*.DS_Store" -delete
then delete empty directories.
Update based on comment:
In order to delete only directories that have only such files, you will need something like (caution: not tested at all, I will not be surprised if it needs to be a bit more involved):
find <path> -type d | while read dir; do
if ! ls --ignore=*.DS_Store $dir; then
rm -rf $dir
fi
done
Explanation of complicated part:
ls --ignore=*.DS_Store $dir
should print files in $dir that do not end in .DS_Store. If there are none, the expression is False and the if-block is executed.
I though of this but I don't want to delete all .DS_Store files, only the ones in directories that are otherwise empty.
– David
Dec 17 at 20:04
1
Sorry to spoil the fun, but the standardls
on macOS doesn't know about--ignore
...
– nohillside
Dec 17 at 20:27
2
in that case, --ignore can be emulated with suitable 'grep -v'
– WerKater
Dec 17 at 20:33
add a comment |
Easy solution:
first, delete all such files:
find <path> -type f -name "*.DS_Store" -delete
then delete empty directories.
Update based on comment:
In order to delete only directories that have only such files, you will need something like (caution: not tested at all, I will not be surprised if it needs to be a bit more involved):
find <path> -type d | while read dir; do
if ! ls --ignore=*.DS_Store $dir; then
rm -rf $dir
fi
done
Explanation of complicated part:
ls --ignore=*.DS_Store $dir
should print files in $dir that do not end in .DS_Store. If there are none, the expression is False and the if-block is executed.
Easy solution:
first, delete all such files:
find <path> -type f -name "*.DS_Store" -delete
then delete empty directories.
Update based on comment:
In order to delete only directories that have only such files, you will need something like (caution: not tested at all, I will not be surprised if it needs to be a bit more involved):
find <path> -type d | while read dir; do
if ! ls --ignore=*.DS_Store $dir; then
rm -rf $dir
fi
done
Explanation of complicated part:
ls --ignore=*.DS_Store $dir
should print files in $dir that do not end in .DS_Store. If there are none, the expression is False and the if-block is executed.
edited Dec 17 at 20:25
answered Dec 17 at 20:02
WerKater
963
963
I though of this but I don't want to delete all .DS_Store files, only the ones in directories that are otherwise empty.
– David
Dec 17 at 20:04
1
Sorry to spoil the fun, but the standardls
on macOS doesn't know about--ignore
...
– nohillside
Dec 17 at 20:27
2
in that case, --ignore can be emulated with suitable 'grep -v'
– WerKater
Dec 17 at 20:33
add a comment |
I though of this but I don't want to delete all .DS_Store files, only the ones in directories that are otherwise empty.
– David
Dec 17 at 20:04
1
Sorry to spoil the fun, but the standardls
on macOS doesn't know about--ignore
...
– nohillside
Dec 17 at 20:27
2
in that case, --ignore can be emulated with suitable 'grep -v'
– WerKater
Dec 17 at 20:33
I though of this but I don't want to delete all .DS_Store files, only the ones in directories that are otherwise empty.
– David
Dec 17 at 20:04
I though of this but I don't want to delete all .DS_Store files, only the ones in directories that are otherwise empty.
– David
Dec 17 at 20:04
1
1
Sorry to spoil the fun, but the standard
ls
on macOS doesn't know about --ignore
...– nohillside
Dec 17 at 20:27
Sorry to spoil the fun, but the standard
ls
on macOS doesn't know about --ignore
...– nohillside
Dec 17 at 20:27
2
2
in that case, --ignore can be emulated with suitable 'grep -v'
– WerKater
Dec 17 at 20:33
in that case, --ignore can be emulated with suitable 'grep -v'
– WerKater
Dec 17 at 20:33
add a comment |
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%2f489556%2ffind-all-empty-directories-and-directories-with-a-single-type-of-file%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