1

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?

2

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'\' runs CODE on each matching file name and limits the matches to those for which CODE 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 containing path (the path to the directory), dirs (the list of subdirectories) and files (the list of files in the directory that aren't themselves directories).
  • [… for … in os.walk(…) if …] filters the result of os.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.
0

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 '18 at 20:04
  • 1
    Sorry to spoil the fun, but the standard ls on macOS doesn't know about --ignore... – nohillside Dec 17 '18 at 20:27
  • 2
    in that case, --ignore can be emulated with suitable 'grep -v' – WerKater Dec 17 '18 at 20:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.