我正在尝试编写一个 shell 脚本来删除所有空目录以及仅包含.DS_Store
Mac 生成的文件的任何目录。我可以很容易地做到前者
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'\'
CODE
CODE
CODE
$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
仅包含dirB
且dirA/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 {} ';'