查找所有空目录和具有单个(特定)文件的目录

查找所有空目录和具有单个(特定)文件的目录

我正在尝试编写一个 shell 脚本来删除所有空目录以及仅包含.DS_StoreMac 生成的文件的任何目录。我可以很容易地做到前者

find -depth -type d -empty

但我不知道如何找到包含的目录仅有的 .DS_Store

有没有一种简单的方法可以做到这一点,而无需编写自己的递归搜索函数?

答案1

POSIX sh + 查找

这是一个仅依赖于 POSIX find 和 POSIX sh 的解决方案。列出所有目录,然后过滤那些仅包含名为 的条目的目录.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
  • 我用来find递归地枚举所有目录。
  • 在每个目录上,我调用sh运行一些 shell 代码。
  • for循环枚举目录中的所有文件。
  • 循环体会跳过.DS_Store
  • 如果这三个模式中的每一个都不与任何文件匹配,则将保持不变。[ -e "$x" ] || [ -L "$x" ]捕获任何文件,包括损坏的符号链接;它们不匹配的唯一原因是模式保持不变。
  • exit 1因此,如果存在除 之外的文件,则 shell 代码段运行.DS_Store,否则返回 0 表示成功。
  • 如果您想要执行除打印姓名之外的其他操作,请更改-print为。-exec …

兹什

这是 zsh 中的解决方案。更改echo为您要运行的任何命令。

setopt extended_glob
echo **/*(/DNe\''a=($REPLY/^.DS_Store(DNY1)); ((!#a))'\')
  • **/*递归枚举所有文件。
  • 随着全局限定符 /**/*(/)递归枚举所有目录。
  • N如果没有匹配项,glob 限定符可确保您得到一个空列表(默认情况下 zsh 会发出错误信号)。
  • glob 限定符D会导致包含点文件。
  • glob 限定符对每个匹配的文件名运行 ,并将匹配限制为成功的文件名。可以使用变量来引用文件名。e\''CODE'\'CODECODECODE$REPLY
  • ^.DS_Store匹配未调用的文件.DS_Store
  • 就这样代码将匹配限制为除 之外的文件数.DS_Store为零的匹配。
  • glob 限定符Y1将匹配次数限制为一次(这只是效率的提高)。

Python

这是 Python 中的一个解决方案(它适用于 2 和 3)。尽管被压缩成一行,但结构还是相当清晰。

python -c 'import os; print("\n".join([path for path, dirs, files in os.walk(".") if dirs == [] and files in ([], [".DS_Store"])]))'
  • os.walk在其参数下递归返回目录列表。对于每个目录,它都会生成一个三元组,其中包含path(目录的路径)、dirs(子目录列表)和files(目录中本身不是目录的文件列表)。
  • [… for … in os.walk(…) if …]过滤 的结果os.walk
  • if仅当元素没有子目录且除 之外没有文件时,该子句才会保留该元素.DS_Store
  • 该脚本打印接受的元素,并在中间和最后一个换行符之间加入换行符。

答案2

简单的解决方案:首先,删除所有此类文件:

find <path> -type f -name "*.DS_Store" -delete

然后删除空目录。

根据评论更新: 为了仅删除仅包含此类文件的目录,您将需要类似的东西(警告:根本没有测试,如果需要更多参与,我不会感到惊讶):

find <path> -type d | while read dir; do
    if ! ls --ignore=*.DS_Store $dir; then
        rm -rf $dir
    fi
done

复杂部分说明:

ls --ignore=*.DS_Store $dir

应打印 $dir 中不以 .DS_Store 结尾的文件。如果没有,则表达式为 False,并执行 if 块。

答案3

对于苹果机:

find . -type d | while read dir; do
    
    # Found empty dir if it only has:
    #   ., .. 
    #   ., .., .DS_Store
    
    if [ "$(ls -am "$dir" | sed -E 's/^., ..$|^., .., .DS_Store$//')" == "" ]; then
        echo rm -R "\"$dir\""
    fi
done

答案4

另一个具有更多保护措施的 POSIX 变体:

find . -type d -exec sh -c '
  for dir do
    contents=$(ls -AFq "$dir") &&
      case $contents in
        ("" | .[dD][sS]_[sS][tT][oO][rR][eE]) printf "%s\n" "$dir"
      esac
  done' sh {} +

这里:

  • ls如果无法列出目录内容(或无法找出其中文件的类型),我们就可以解决这个问题。
  • 通过使用ls -F,我们确保不会列出包含.DS_Store不是 a 的目录常规的文件(-F为其他类型的文件添加后缀)。
  • 例如,使用-q,我们可以避免文件名的误报。$'.DS_Store\n\n'
  • 我们不区分大小写地匹配,因为我相信文件名在 Macos 中不区分大小写。

等效zsh的可以是:

empty() {
  set -o localoptions -o extendedglob
  ERRNO=0
  () ((!ERRNO && !$#)) $REPLY/(#i){^.ds_store(NDY1),.ds_store(N^.)}
}
print -rC1 -- **/*(ND/+empty)

现在,如果要删除这些目录,则存在dirA仅包含dirBdirA/dirB为空或仅包含.DS_Store文件的情况。然后,即使它在删除dirA后变空,我们也不会报告。dirA/dirB

为了解决这个问题,您可以在此处使用-depth,但您将无法使用,-exec ... {} +因为空目录一旦找到就被删除,这一点很重要,因此我们将使用-exec ... {} ';'

find . -depth -type d -exec sh -c '
  contents=$(ls -AFq "$1") &&
    case $contents in
      ("" | .[dD][sS]_[sS][tT][oO][rR][eE]) exec rm -rf "$1"
    esac' sh {} ';'

相关内容