从零开始,编写一个 HomeBrew 缓存清理脚本
2021-04-03 16:20:38 Author: sspai.com(查看原文) 阅读量:158 收藏

从零开始,编写一个 HomeBrew 缓存清理脚本

Matrix 首页推荐

Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。

文章代表作者个人观点,少数派仅对标题和排版略作修改。


如果你正在使用 HomeBrew,并且没有使用过垃圾清理软件对其缓存进行过清理,不如通过 open ~/Library/Caches/Homebrew 命令打开 HomeBrew 缓存目录来看看。HomeBrew 虽然提供了 brew cleanup 工具来回收空间,但它只能清理历史版本备份,而不能清理下载缓存。经年累月的各种软件包可能堆积在这个目录中,一点一点蚕食宝贵的硬盘空间。

在过去,我一直使用 Setapp 中提供的 CleanMyMac X 来清理这些缓存。CleanMyMac X 作为 macOS 垃圾清理软件中的老牌劲旅,可以较好地识别并清理 HomeBrew 留下的垃圾。在退订 Setapp 后,我选择了 Lemon(腾讯柠檬清理)作为 CleanMyMac X 的替代品。Lemon 对国内(尤其是腾讯自家)app 的缓存识别以及清新的 UI 都让我十分满意,但它可以清理的缓存并不及 CleanMyMac X 来得丰富,而其中的一条「漏网之鱼」便是 HomeBrew。

Lemon 无法检测并清理 HomeBrew 缓存
Lemon 无法检测并清理 HomeBrew 缓存

即使是 CleanMyMac X,在清理 HomeBrew 缓存时也稍显不足。

垃圾清理软件的工作原理一般是扫描可能的缓存路径,并且对其中可以清理的目录进行移除。然而,HomeBrew 的缓存目录中除了占据较大空间的软件包之外,还有指令表等其他索引。虽然这些索引可以随时重建,但如果能够精准定位并移除要清理的文件,就能将重建缓存和目录结构的时间节约下来。接下来,我们将编写一个简单的 shell 脚本来完成这个任务。

设计逻辑

我们首先来观察 HomeBrew 缓存目录的结构。HomeBrew 缓存目录下存放了数个索引文件,以及指向所有 HomeBrew Formulae 软件包的符号链接(软链接);Cask 目录则存放了指向所有 HomeBrew Cask 软件包的软链接。在 downloads 目录中,我们可以看到被指向的这些软件包。

HomeBrew 缓存目录结构
HomeBrew 缓存目录结构

软链接,或者叫符号链接,是文件系统中指向其他位置的一种特殊文件。不同于 macOS 中的「替身」和 Windows 中的「快捷方式」,对软链接的访问将直接指向原始文件或目录,因此可以通过软链接访问原始目录内的文件。值得一提的是「访达」会将软链接显示为「替身」,不过二者实际上有很大的不同。

我们发现这个目录下存放了指向两种类型、全部软件包的软链接。除了软链接和软件包之外,其他文件均不属于需要清理的垃圾。因此,我们只需找出所有的软链接,将它们所指的软件包和本身均删除,就能达到精确清理的目的。这样的清理不会影响未完成的下载,因为它们不会被创建软链接。我们将在后面介绍如何清理这类文件。

编写代码

通常,我们建议在 shell 脚本的第一行标明脚本所使用的 shell。macOS Big Sur 的默认 shell 为 zsh,而在这篇文章中我选择了使用更广泛的 bash。二者之间有些许语法差异,但本示例中的脚本不会涉及这些差异。

#!/bin/bash

# 在 shell 脚本中用作单行注释的提示符,系统通过读取位于脚本开头的注释就可以识别脚本所使用的 shell。我们在脚本中可以用 # 来添加一些注释,有时也会被用来隐藏一些无用的代码。

接下来,我们需要找出所有的软链接。我们可以使用系统内置的 find 工具,向它传入 -type l 即可搜索指定目录下的软链接。HomeBrew 缓存目录中的软链接可以用以下命令导出:

find ~/Library/Caches/Homebrew -type l

在 bash 中,我们可以用 $() 来将指令的输出转换为变量。获取到所有软链接后,我们需要对其进行遍历并分别进行处理。bash 提供了 for 语句来进行遍历,在本例中用法如下:

for link in $(find ~/Library/Caches/Homebrew -type l)
do
    # 清理语句
done

在循环体中,符号链接的路径将被保存到局部变量 $link。我们可以用 realpath 工具来直接获取其指向的文件,而不需要自行编写相关的代码。这个工具可以用以下命令安装:

brew install realpath

不少垃圾清理软件都提供了直接删除和移动到废纸篓的选项。在 macOS 操作系统中提供了 rm 这一很多人都有所耳闻的命令,可以用来直接删除。而如果需要将文件移到废纸篓,我们可以用以下命令安装 trash-cli 工具:

brew install trash-cli

在本例中,所要删除的均是单个文件,而且不存在权限问题,因此调用方式非常简单。

rm <file>       # 直接删除
trash <file>    # 移到废纸篓

结合上述知识点,我们就能实现循环体了。我个人比较推荐将软链接文件直接删除,将软件包移到废纸篓,大家可以根据自己的需要进行选择。对于移到废纸篓的软件包,我们如果需要再次使用可以将其放回 ~/Library/Caches/Homebrew/downloads 目录,HomeBrew 会自动识别并重新建立软链接。完整的脚本文件如下:

#!/bin/bash

for link in $(find ~/Library/Caches/Homebrew -type l)
do
    trash $(realpath $link)
    rm $link
done

其中尤其需要注意的是删除的顺序,必须先处理原始文件、再处理软链接,软链接被删除后将无法定位到原始文件。

保存脚本

我们可以将脚本保存为形如 brew-clean.sh 的脚本文件,这样就可以用 bash brew-clean.sh 来执行它。为了调用更方便,我们可以将其赋予可执行权限,这样就能直接地调用它。我们不妨将其命名为 brew-clean,我们的目标则是通过 brew-clean 这样的命令直接调用这个脚本。

首先,我们可以使用 chmod 命令为 brew-clean 赋予可执行权限。参数 +x 就表示「增加可执行权限」。

chmod +x brew-clean

现在,我们已经可以用 ./brew-clean 的方式来调用它了。为了在任意目录下都可以调用,我们可以将其保存到 /usr/local/bin。这是专门存放用户自行安装的程序的目录。

mv brew-clean /usr/local/bin

以上命令将 brew-clean 文件移动到了 /usr/local/bin。如果你希望复制而非移动,可以将 mv(即 move)替换为 cp(即 copy)。

让我们来试试运行效果:

简单 HomeBrew 缓存清理 demo
简单 HomeBrew 缓存清理 demo

Awesome!

功能拓展

在前两节,我们用短短七行代码就实现了 HomeBrew 缓存清理功能。我将在这一板块抛砖引玉,提出一些可以扩展此脚本的想法和实现方式,大家也可以在评论区里提出自己的 idea。

在脚本中加入 brew cleanup

我们在上文中已经提到,HomeBrew 内置的 brew cleanup 工具可以用来移除历史版本备份,从而释放磁盘空间。如果确有需要,可以在 for 循环前或循环后加入一行 brew cleanup 来调用它。

之所以不推荐在脚本中加入,是因为 brew cleanup 执行十分缓慢(在我的 Mac 上可能长达数分钟),而这个脚本在大多数情况下只需要数秒就能完成下载缓存的清理,直接在脚本中调用 brew cleanup 会使原本轻快的脚本变得笨重。此外,在默认设置下 HomeBrew 每三十天将自动进行一次 cleanup(一般在完成 upgrade 或 install 之后),手动调用比较费时,意义不大。

在运行结束后展示清理结果

brew cleanup 完成后,它会输出清理的文件总数。我们也可以在脚本中实现这一功能,大致的实现如下:

count=0
for link in $(find ~/Library/Caches/Homebrew -type l)
do
    let count++
    # 清理语句
done

echo "Pruned $count symbolic links and $count files from $(realpath ~/Library/Caches/Homebrew)"

在上面这段代码中,我使用了变量 $count 进行计数。shell 默认将变量视为字符串,因此进行数学运算需要使用其他工具或特殊语法,这里使用的是兼容性比较好且较为直观的 let

对 Cask 和 Formulae 进行针对性处理

HomeBrew Cask 软件包一般是 DMG 或是 PKG 格式的安装包。不同于经过特殊封装的 Formulae,Cask 软件包大都是由软件开发商官方发布的,并且可以在任何兼容的机器上很方便地进行安装。我们当然可以单独处理 HomeBrew Cask 软件包,例如将其保留或者移至回收站,而对 Formulae 采取直接删除处理。要达到以上效果,我们可以将以下代码加入到脚本开头:

for link in $(find ~/Library/Caches/Homebrew/Cask -type l)
do
    trash $(realpath $link) # 删除此行以保留 Cask 软件包
    rm $link
done

以上这段代码从利用了 HomeBrew Cask 软链接存放在单独目录的特性,先对其进行处理。这样在执行原有逻辑时,指向 Cask 软件包的链接已经不复存在,所以可以用于单独处理 Formulae 软件包。

清理未完成的下载

在 HomeBrew 中,未完成的下载统一使用了 .incomplete 作为扩展名。它们不会被建立符号连接,因此不能用上文的方式进行处理。对于这类缓存,我们可以直接在下载目录中进行定位并删除。所需命令大致如下:

rm ~/Library/Caches/Homebrew/downloads/*.incomplete

其中,* 是通配符,*.incomplete 即表示该目录下所有名称以 .incomplete 结尾的文件。rmtrash 均支持使用 * 通配符批量删除文件。

总结与延伸

接下来我会将以上提到的所有功能进行整合,晒出一份完整的脚本。大家可以自取所需,以此为基础定制自己的清理脚本:

#!/bin/bash

# 文件计数
file_count=0
link_count=0
incomplete_count=0

# 清理 HomeBrew Cask 下载缓存
for cask_link in $(find ~/Library/Caches/Homebrew/Cask -type l)
do
    # 将 Cask 软件包移至废纸篓
    # let file_count++
    # trash $(realpath $cask_link)
    let link_count++
    rm $link
done

# 清理 HomeBrew 下载缓存
for link in $(find ~/Library/Caches/Homebrew -type l)
do
    let file_count++
    trash $(realpath $link)
    let link_count++
    rm $link
done

# 获取 *.incomplete 文件数量
let incomplete_count=$(ls -l ~/Library/Caches/Homebrew/downloads/*.incomplete | wc -l)
# 清理未完成的下载
rm ~/Library/Caches/Homebrew/downloads/*.incomplete

# 复数输出函数
plural() {
    if [ $1 -gt 1 ]
    then
        echo "$1 $2s"
    else
        echo "$1 $2"
    fi
}

# 输出消息提示
echo "Pruned $(plural $link_count "symbolic link"), $(plural $file_count "file") and $(plural $incomplete_count "incomplete download") from $(realpath ~/Library/Caches/Homebrew)"

# 调用 `brew cleanup`
# echo 'Running `brew cleanup`'
# brew cleanup

在以上的示例中,我使用了一些之前未提到的技巧,例如管道、if 条件分支、自定义函数,以及 lswc 工具。感兴趣的读者可以自行阅读相关的开放教程,例如 鄙社 Linux 101 教程 中的 Shell 脚本 部分。此外,由人民邮电出版社引进的《UNIX/Linux/OS X中的Shell编程(第4版)》是 Shell 编程领域不可多得的佳作,适合各知识水平的读者参考和学习。

拓展阅读:

> 下载少数派 客户端 、关注 少数派公众号 ,了解更妙的数字生活 🍃

> 想申请成为少数派作者?冲!

© 本文著作权归作者所有,并授权少数派独家使用,未经少数派许可,不得转载使用。

stevapple


文章来源: https://sspai.com/post/65842
如有侵权请联系:admin#unsafe.sh