Mac 应用卸不掉:七种成因,每种一个症状
macOS 上的卸载失败不是同一个问题,是好几个。访达可能拒绝移动应用包,也可能移动成功了,下次登录它又回来了,还可能包没了,而那个烦你的助手进程照跑不误。每一种都是不同机制、不同解法,所以先看这台 Mac 实际做了什么。常规顺序见彻底卸载 Mac 应用,下面这些都假设那一套已经失败了。
从症状开始
- 「无法将项目移到废纸篓,因为它已打开。」 有东西还在跑,往往不是你刚退出的那个应用。
- 删掉了,它又回来了。 某个 launchd 任务、某份描述文件,或者某个包管理器把它装回来了。
- 访达要求输入管理员密码。 某个
.pkg让应用包归 root 所有,这很正常。 - 完全没反应,没有对话框也没有报错。 App 管理权限被拒了。
- 以 root 跑
rm提示「操作不被允许」。 系统完整性保护。 - 选项变灰,或者在公司的 Mac 上又冒出来。 一份配置描述文件或 MDM 拥有它。
1. 应用本身或它的某个助手还在跑
应用包里只要还有文件开着,访达就不肯移动它,而退出你看得见的那个进程,并不会停掉这个应用启动的全部东西。活动监视器列的是每一个进程,不只是有窗口的那些。搜厂商名,然后把整组结果都读一遍:一个叫 Foo 的应用常常还带着 Foo Helper、一个 FooUpdater,以及一个显示名跟它毫不相干的登录项包。苹果菜单 > 强制退出那个窗口替代不了这一步,因为它只列有界面的应用。
pgrep -fl -i foo
lsof +D /Applications/Foo.app 2>/dev/null | awk '{print $1, $2}' | sort -u
pgrep -fl 匹配的是完整命令行,所以哪怕助手进程的进程名里没有厂商名,只要可执行文件路径里有,它也能抓到。lsof +D 报出所有正持有这个应用包内文件的进程。
强制退出父进程并不能可靠地停掉助手。助手是自己的进程、自己的 PID,杀掉父进程只是让它成了孤儿进程,除非父进程在退出时主动把它拆掉。而如果助手由 launchd 管理,杀掉其中任何一个都只是在告诉 launchd 再启动一次,这就是下一节的内容。
一次快速查看的预览或者聚焦的索引过程,也可能持有包内的文件,重启一次两者都会释放。提示反复出现的话,Apple 的删除应用指南建议进安全模式。
2. 某个 launchd 代理或守护进程把它拉了回来
这就是「我删了它又回来了」那一种,也是最常被误诊的:人们打开活动监视器,什么都没看到,于是断定这个应用没在跑,可按需启动的东西时有时无,看不见不算证据。
launchd 负责看管后台任务。一个任务就是一份属性列表,带 Label、一个程序,以及运行它的条件。RunAtLoad 在任务加载时启动它,KeepAlive 在它退出时重启它。两者都没有的任务照样会回来:MachServices、Sockets、WatchPaths、QueueDirectories 和 StartInterval 都会让 launchd 在有人来要的那一刻把进程拉起来,所以有的助手空闲十秒就退出,靠 Mach 服务再被唤起,你每次去看它都不在。
定义住在三个地方。~/Library/LaunchAgents 只属于你这个用户,跑在你的登录会话里;/Library/LaunchAgents 在每个用户登录时运行;/Library/LaunchDaemons 在任何人登录之前就以 root 身份全系统运行,这也是为什么手动卸载之后守护进程往往活下来、而代理常常没有。/System/Library/Launch* 是 Apple 的,受保护。
launchctl list | grep -i foo
launchctl print-disabled gui/$(id -u) | grep -i foo
grep -l -i foo ~/Library/LaunchAgents/*.plist /Library/LaunchAgents/*.plist \
/Library/LaunchDaemons/*.plist 2>/dev/null
在 launchctl list 里,带 PID 的标签是此刻正在跑,带短横线的是已加载、正等触发条件;print-disabled 读的是持久化的禁用状态数据库,那和「plist 存不存在」是两码事。用 plutil -p <path> 读一读候选项,确认 Program 或 ProgramArguments 指向的确实是你要删的那个应用,厂商不总是按里面的标签给文件命名。
顺序很重要
先停掉并禁用任务,再删 plist,最后删应用。
launchctl bootout gui/$(id -u)/com.vendor.foo.helper
sudo launchctl bootout system/com.vendor.foo.daemon
launchctl disable gui/$(id -u)/com.vendor.foo.helper
任务还加载着就删 plist,launchd 手里就留下一个服务,而它的定义在磁盘上已经没有了,这个服务会一直跑到下次重启,还可能在禁用状态数据库里留下一条陈旧记录。先删应用更糟:任务会对着一个不存在的可执行文件反复重生并失败,「它明明没了,登录项里还显示着」就是这么来的。
macOS 13 及以后,应用通过 SMAppService 注册,这些注册记录住在应用包内部,所以可能根本找不到 plist。它们归后台任务管理,在系统设置 > 通用 > 登录项与扩展里控制(见关掉开机启动项)。
3. 它是受系统完整性保护的系统应用
系统完整性保护是一条内核级策略,不是一个权限位。Apple 的描述是它用内核权限限制关键系统文件的可写性,作用于「系统上运行的每一个进程,不论该进程是否运行在沙盒中或是否具有管理员权限」(Apple 平台安全)。从 Big Sur 起,系统内容还放在一个独立的、经过加密封存的卷上。csrutil status 会打印保护是否开启,而 Apple 明说「你无法删除 Mac 所需的应用」,这一组包括邮件、音乐、图书、备忘录、播客、地图、新闻和股市。
为了删掉一个应用包去关系统完整性保护,是笔亏本买卖。那意味着进恢复模式,改一条影响整台机器的安全策略;Apple 指出在 Intel 机型上,关掉它会让这块物理存储上的每一个分区都失去保护,而 Apple 芯片机型会离开完全安全性等级。而且收益也留不住:下次 macOS 更新时系统卷会被整卷替换,也一点可用空间都收不回来。
更好的做法是:把图标从程序坞里拖走,在系统设置 > 通用 > 登录项与扩展里移除它,如果它老是抢着打开你的文件,就用显示简介 > 打开方式 > 全部更改换掉默认处理程序。
4. 它是由 MDM 或配置描述文件装上的
在受管理的 Mac 上,一个应用可能按管理服务器的排期被推回来,而一份描述文件可以被标记为不可移除。症状是删完几小时后又被装了回来、某个控件是灰的,或者在 launchd 里怎么找都找不到,因为把它装回来的动作是从远端发起的。去看系统设置 > 通用 > 设备管理,如果根本没有这一节,这台 Mac 就不受管理,也就不是这个原因。
profiles status -type enrollment
sudo profiles list
第一条报的是自动设备注册的状态,以及有没有经过用户批准,第二条列出已安装的描述文件,需要 root。然后去找 IT。Apple 给的建议是,遇到自己移除不掉的描述文件就去问提供它的人,同时提醒移除一份描述文件会一并删掉这份文件配置过的所有东西,所以带着你邮件账户的那份,会把账户也带走。
5. 归属,以及那个悄无声息失败的权限
访达要求输入管理员密码。 很正常。.pkg 安装程序以 root 身份运行,留下的应用包归 root 所有,所以移动它需要认证,用 ls -ld /Applications/Foo.app 就能确认。顺带说说安装记录:pkgutil --files <id> 列出某个包放下的路径,sudo pkgutil --forget <id> 把这条记录从 /private/var/db/receipts 里去掉,一个文件都不会删。
完全没有反应。 没有提示,没有报错,应用还在 /Applications 里,你用的那个工具报了个含糊的失败,或者干脆转头去做别的了。在 macOS 14 及以后,这通常是 App 管理,Apple 对它的描述是「允许 App 更新或删除 Mac 上的其他 App」。判据是这条路径按 POSIX 是可写的,写却仍然失败:
test -w /Applications/Foo.app && echo "posix says yes"
如果这句打印出来了、删除还是没发生,那问题就不在权限位上。去系统设置 > 隐私与安全性 > App 管理,把执行删除的那个应用打开,然后重启它,因为很多应用只在启动时读一次这个权限。
三种长得一样的拒绝,底下不是一回事
POSIX 权限是第一道闸:应用包归 root 所有,你不是 root,sudo 就能解决,因为这道检查只看你是哪个用户。TCC,也就是系统设置背后那层隐私机制,是在 POSIX 通过之后才判断的。App 管理是针对一种操作的 TCC 闸门,也就是修改或删除另一个应用的包。管理员身份不能满足它,sudo 也不行,因为它认的是发起请求的那个程序而不是用户,这也是为什么它的拒绝会表现成一个没有提示的笼统错误。
系统完整性保护在这两者之下,对 root 也直接拒绝。POSIX 拒绝设置 EACCES,打印 Permission denied;系统完整性保护拒绝设置 EPERM,打印 Operation not permitted。前者的意思是换个更高的身份再试,后者的意思是没有更高的身份可以再试了。
6. 某个系统扩展或网络过滤器还活着
安全软件、VPN 客户端和虚拟化产品都会装系统扩展。扩展是应用包注册上去的,应用包本身不是扩展。在扩展还活着的时候删掉承载它的容器,注册记录就没了归属,这正是为什么一台 Mac 会继续用你以为已经删掉的软件过滤流量。
systemextensionsctl list
输出会显示团队标识符、bundle 标识符和形如 [activated enabled] 的状态。停用属于承载它的那个应用,而且往往需要重启,所以先跑厂商自己的卸载器。怎么卸载 Mac 上的杀毒软件讲了这一类的拆除顺序。
7. 它来自 App Store,或者来自 Homebrew
Mac App Store 应用。 删掉应用包并不会取消购买。会绊住人的是一个设置,Apple 把它写成「自动下载你在其他 Mac 电脑和设备上从 App Store 购买的 App」,同一账户下另一台 Mac 上还留着它,它就能被带回来。去 App Store > 设置里关掉。
Homebrew cask。 如果当初是用 brew install --cask foo 装的,把应用扔进废纸篓之后,Homebrew 仍然认为它装着。brew list --cask 里还会显示这个名字,下一次 brew upgrade 就可能把你刚删掉的应用装回来,这也是一种实打实的「它又回来了」,跟 launchd 没关系。改用 brew uninstall --cask foo。Homebrew 的手册把 --zap 描述为移除「与某个 cask 相关的所有文件」,同时警告它「可能会移除多个应用之间共享的文件」,所以这个参数要在想清楚之后再用。
删除成功之后,还会留下什么
登录项与扩展里还列着它。 要么是一条「登录时打开」记录,它指的那条路径已经不在了,要么是一条后台任务管理记录,它对应的那个服务程序已经没了。在系统设置 > 通用 > 登录项与扩展里用减号移除,macOS 也常常在重启后自己清掉。
开机时冒出一个菜单栏图标。 还有东西在加载某个二进制文件,通常是一个你没删掉的启动代理,或者安装程序复制到 ~/Library/Application Support/<vendor> 里的一个助手。
一个特权助手活了下来。 需要 root 的软件会装到 /Library/PrivilegedHelperTools/<label>,配一份对应的 /Library/LaunchDaemons/<label>.plist。两者都归 root 所有,也都在应用包之外,所以把应用扔进废纸篓,哪个都碰不到。
ls -la ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons \
/Library/PrivilegedHelperTools 2>/dev/null
数据那一侧见卸载之后的残留文件。
在 Mole 里做这套诊断
这篇文章的手动版本要开五个界面:活动监视器、三个 launchd 目录、登录项与扩展、隐私与安全性,再加访达。Mole 把属于同一个应用的部件放在同一页,而且扫描一直免费,所以买不买它都能当成一个只读的诊断工具用。
打开 Mole 的「软件」页,它分三段:已安装应用清单、可用更新和启动项。最后那一段,就是前面让你手动去拼的那份 launchd 与登录项清单,所以「它又回来了」的成因在删之前就看得见,不是删完才发现。
选中一个应用,Mole 会解析出它的 bundle 标识符,再去找这个身份拥有什么:Application Support、Caches、Preferences、Containers、HTTPStorages,还有启动代理和守护进程,前提是 plist 里确实引用了这个应用,而不是名字里凑巧有同一个词。每个候选项都带着路径、大小,以及把它关联进来的那份证据,低置信度的一律不勾选。
点确认之后,跑的就是这篇文章推荐的顺序,而且是强制执行不是靠记性。Mole 会退出这个应用以及嵌在它包里的助手,停掉它注册的登录项助手,在删除 plist 之前先用 launchctl 卸载每一个已批准的启动项,真正删除时再把每条路径校验一遍。删除进废纸篓,出了错是拖回来。
然后 Mole 报的是带路径的跳过项和失败项,不是一个成功总数,所以某一行因为 App 管理被拒而没删掉,或者因为路径归 root 所有被守卫挡下,你一眼就知道自己撞上的是上面哪一种成因。工具要是把写入被拒也盖上对勾,就会有人对着一个菜单栏图标琢磨一整晚。Mole 做的一切都在本地跑,文件操作会追加到 ~/Library/Logs/mole/operations.log。终端里,Mole CLI 免费开源,mo uninstall 支持 --dry-run,可以先把路径清单读一遍。
有三种成因 Mole 打不赢,它也不去硬碰:它不会关掉系统完整性保护,也不会从封存的系统卷上删掉 Apple 自带应用;在受管理的 Mac 上,配置描述文件要把软件装回来,它盖不过去;而对安全代理、VPN 客户端和虚拟化产品,它替代不了那个知道拆除顺序的厂商卸载器。App 管理被拒时,Mole 报告这次失败,不会绕过去提权。
常见问题
为什么应用删掉之后又回来了
四种成因,大致按出现频率排。某个 launchd 代理或守护进程手里还留着它的任务定义,触发条件一到就把它重启,而那些条件在活动监视器里根本看不见。某条 Homebrew cask 记录活了下来,因为你把应用扔进了废纸篓,而不是跑 brew uninstall --cask。在受管理的 Mac 上,是 MDM 把它推了回来。或者 App Store 里那个「自动下载在其他设备上购买的 App」的设置,从另一台 Mac 上把它拉了过来。
关掉系统完整性保护就能删 Apple 自带应用吗
技术上可以,但这是笔亏本买卖:为了一个应用包,从恢复模式里把整台机器的安全等级降一级,而下次 macOS 更新替换掉封存的系统卷时,它还会回来。改成把它从程序坞和登录项里移除。
访达要我输密码才能删应用,是出问题了吗
不是。.pkg 安装程序以 root 身份把应用包放在那里,所以移动它需要认证。真正值得担心的是反过来那种:没有提示、没有报错、什么都没发生,那通常是系统设置的隐私与安全性里 App 管理被拒了,而 sudo 解决不了,因为这道闸认的是发起请求的程序,不是用户。
延伸阅读
- 怎么彻底卸载 Mac 应用,讲常规顺序和它会复核的各层资源库目录。
- 怎么卸载 Mac 上的杀毒软件,讲带系统扩展的软件,那类东西拆除顺序就是全部工作。
- 怎么关掉开机启动项,讲没有东西跟你作对之后,launchd 和登录项这一侧该怎么弄。