Search code examples
zshglobhidden-files

zsh glob to find directories that are empty or that only contain a .DS_Store


Is there any zsh glob that will find directories that are either empty or that only contain a .DS_Store file?

I know that the (/^F) glob flag combo will find empty directories, but I also want to include directories that contain only a .DS_Store file but that contain no other nodes (files, directories, etc.).

For my purposes, if I cannot filter out specifically .DS_Store, filtering out either all hidden files (.*(.)) or all hidden nodes (.*) will suffice.


Solution

  • Not simple, but we could do:

    setopt extendedglob  # for '~' and '#q' in the `[[ … ]]' form
    setopt globstarshort # shortcut for '**/*' to '**'
    
    print -rC1 -- **(DN/^F,/e['[[ -z $REPLY/*~*/.DS_Store(#qoNDNY1) ]]'])
    

    lists empty directories or directories in which any file does not exist other than .DS_Store (in other words: only .DS_Store exits).

    • **(…): GLOB_STAR_SHORT shortcut for **/*.

    • *(…): Glob qualifiers specify which filenames that otherwise match the given pattern will be inserted in the argument list.

    • D: set GLOB_DOTS, include dotfiles,dirs.

    • N: set NULL_GLOB, don't signal error if pattern does not match. (Not required for particularly testing/experimenting.)

    • /^F: zsh idiom for empty directories.

    • ,: OR

    • /e[…]: directories AND estring qualifier that is directories which satisfy the zsh code string that is .

    • [[ -z … ]]: will describe no file exists other than .DS_Store using glob patterns.

    • $REPLY: during execution e[…] , the filename currently being tested in this case directoryname will be passed by zsh.

    • *~*/.DS_Store: X~Y means match anything that matches the pattern X but does not match Y. So, it will expand anything other than .DS_Store.

    • #q: globbing performed in the [[ -z … ]] form.

    • oN: no sort.

    • D: GLOB_DOTS again.

    • N: set NULL_GLOB, let zsh not signal error while processing e[…].

    • Y1: short-circuit, only the first 1 match will be considered is enough.

    setopt extendedglob globstarshort
    rm -rf tester
    mkdir tester
    cd tester
    mkdir empty dsstore-only more-dotfiles more
    touch dsstore-only/.DS_Store more-dotfiles/{.DS_Store,.DS_Store.old} more/{.DS_Store,more}
    mkdir child
    cp -a ^child child
    tree -a
    #>> .
    #>> ├── child
    #>> │   ├── dsstore-only
    #>> │   │   └── .DS_Store
    #>> │   ├── empty
    #>> │   ├── more
    #>> │   │   ├── .DS_Store
    #>> │   │   └── more
    #>> │   └── more-dotfiles
    #>> │       ├── .DS_Store
    #>> │       └── .DS_Store.old
    #>> ├── dsstore-only
    #>> │   └── .DS_Store
    #>> ├── empty
    #>> ├── more
    #>> │   ├── .DS_Store
    #>> │   └── more
    #>> └── more-dotfiles
    #>>     ├── .DS_Store
    #>>     └── .DS_Store.old
    #>>
    #>>10 directories, 10 files
    
    print -rC1 -- **(DN/^F,/e['[[ -z $REPLY/*~*/.DS_Store(#qoNDNY1) ]]'])
    #>> child/dsstore-only
    #>> child/empty
    #>> dsstore-only
    #>> empty
    

    Note: some references:

    Glob Qualifires:
    ...
    More than one of these lists can be combined, separated by commas. The whole list matches if at least one of the sublists matches (they are `or'ed, the qualifiers in the sublists are `and'ed). Some qualifiers,

    --- Glob Qualifires, zshexpn(1)

    Conditional Expressions:
    ...
    Filename generation is not performed on any form of argument to conditions. However, it can be forced in any case where normal shell expansion is valid and when the option EXTENDED_GLOB is in effect by using an explicit glob qualifier of the form (#q) at the end of the string.

    --- Conditional Expressions, zshmisc(1)