Mac App 解除安裝不掉:七個成因,每個一種症狀
macOS 上解除安裝失敗不是一種故障,是好幾種。Finder 可能根本不讓你搬走那個套件,也可能搬走成功了,下次登入 App 又回來,還可能套件消失了,當初惹到你的那個輔助程式照樣在跑。機制不一樣,修法也不一樣,所以要從這台 Mac 到底做了什麼開始查。一般流程見徹底解除安裝,下面這些是在那條路已經走不通之後。
從症狀開始
- 「因為項目正在使用中,所以無法移到垃圾桶。」 有東西在跑,而且常常不是你剛關掉的那個 App。
- 明明刪掉了,它又回來。 是某個 launchd 工作、某份描述檔,或某個套件管理器把它裝回去的。
- Finder 要管理者密碼。 某個
.pkg把套件的所有者留成了 root,這很正常。 - 按了什麼都沒發生,沒有對話框也沒有錯誤。 「App 管理」權限被拒。
- 以 root 執行
rm卻回 Operation not permitted。 系統完整性保護。 - 變成灰色,或者在公司的 Mac 上一再出現。 它屬於某份設定描述檔或 MDM。
1. App 或它的某個輔助程式還在跑
套件裡有檔案開著,Finder 就不肯搬它,而關掉你看得見的那個行程,並不等於停掉這個 App 拉起來的全部東西。「活動監視器」列的是每一個行程,不只是有視窗的那些。搜廠商名稱,然後把整組結果讀完,一款叫 Foo 的 App 常常還帶著 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 再啟動一次,那是下一節的事。
「快速查看」的預覽或 Spotlight 建索引也可能握著套件裡的檔案,重新開機兩個都會清掉。訊息又出現的話,Apple 的移除 App 說明建議進安全模式。
2. 某個 launchd agent 或 daemon 把它拉回來
這就是「我刪掉了它又回來」那一種,也是最常被誤判的一種,大家去「活動監視器」看一眼,什麼都沒有,就斷定 App 沒在跑。按需啟動的意思就是,那個「什麼都沒有」不構成證據。
launchd 監管背景工作。一筆工作就是一份 property list,裡面有 Label、一個程式,以及執行它的條件。RunAtLoad 在工作載入時啟動它,KeepAlive 在它結束時把它拉回來。兩個都沒有的工作照樣會回來,MachServices、Sockets、WatchPaths、QueueDirectories 和 StartInterval 都會讓 launchd 在有東西來要的那一刻啟動這個行程,所以有些輔助程式閒置十秒就結束,靠自己的 Mach 服務再起來,你每次去看,它都剛好不在。
定義放在三個地方。~/Library/LaunchAgents 只屬於你這個使用者,在你的登入工作階段裡;/Library/LaunchAgents 在每個使用者登入時執行;/Library/LaunchDaemons 則在任何人登入之前,以 root 身分在整個系統範圍執行,這就是為什麼手動解除安裝殺得掉 agent,卻常常殺不掉 daemon。/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 指的就是你要移除的那個 App,因為廠商不見得會用裡面的標籤替檔案命名。
順序很重要
先把工作停掉並停用,再刪 plist,最後才移除 App。
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 手上就留著一個磁碟上已經沒有定義的服務,它一路跑到下次重新開機,還可能在停用狀態資料庫裡留下一筆過期紀錄。先移除 App 更糟,那筆工作會對著一個不存在的可執行檔一再重生,反覆失敗成一個迴圈,「東西明明沒了,登入項目裡卻還列著」就是這麼來的。
macOS 13 以後,App 改用 SMAppService 註冊,那些登記就放在 App 套件裡,所以你可能根本找不到 plist。它們歸背景工作管理管,開關在系統設定 › 一般 › 登入項目與延伸功能(見關閉開機啟動項目)。
3. 它是受 SIP 保護的系統 App
系統完整性保護是核心層級的政策,不是一個權限位元。Apple 的說法是它用核心權限把關鍵系統檔案鎖成不可寫,而且適用於「系統上執行的每一個行程,不論該行程是否在沙盒中執行,或是否具有管理者權限」(Apple 平台安全性)。從 Big Sur 起,系統內容還放在一個獨立、以密碼學封印的磁碟區上。csrutil status 印出保護是不是開著,而 Apple 也講明「你無法移除 Mac 必需的 App」,這一組包括「郵件」、「音樂」、「書籍」、「備忘錄」、「Podcast」、「地圖」、「新聞」和「股市」。
為了刪掉一個套件而關掉 SIP,這筆交易不划算。你得開機進復原模式,改掉一條全機範圍的安全性政策,Intel Mac 上,Apple 提到關掉它會讓那顆實體儲存裝置上的每一個分割區都失去保護,Apple 晶片的 Mac 則會離開「完全安全性」。好處還留不住,下一次 macOS 更新會整個換掉系統磁碟區,而且一點可用空間都回收不到。
更好的做法是把圖示從 Dock 拖出去,到系統設定 › 一般 › 登入項目與延伸功能把它移掉,它要是一直搶著開你的檔案,就用顯示簡介 › 打開檔案的應用程式 › 全部更改換掉。
4. 它是 MDM 或設定描述檔裝上去的
受管理的 Mac 上,管理伺服器會按自己的排程把 App 推回來,描述檔也可以標成不可移除。症狀是移除幾小時後自己又復原、某個控制項變成灰色,或者你翻遍 launchd 什麼都找不到,因為重新安裝是從遠端驅動的。去看系統設定 › 一般 › 裝置管理,如果根本沒有這一節,那這台 Mac 沒有受管理,成因不在這裡。
profiles status -type enrollment
sudo profiles list
第一條回報的是自動裝置註冊的狀態,以及有沒有經過使用者核准;第二條列出已安裝的描述檔,需要 root。然後去問 IT。Apple 的建議是,移不掉的描述檔就去問當初提供它的人,還提醒移除一份描述檔,會把它設定過的東西全部刪掉,所以一份帶著你郵件帳號的描述檔,會把帳號一起帶走。
5. 所有權,以及那個會靜默失敗的權限
Finder 要管理者密碼。 很平常。.pkg 安裝程式是以 root 執行的,留下的套件所有者就是 root,所以搬動它需要驗證身分。用 ls -ld /Applications/Foo.app 確認一下。順帶把安裝收據弄清楚,pkgutil --files <id> 列出一個套件放過哪些路徑,sudo pkgutil --forget <id> 把那張收據從 /private/var/db/receipts 拿掉,但一個檔案都不會刪。
什麼都沒發生。 沒有提示、沒有錯誤,App 還在 /Applications 裡,而你用的那個工具報了一個含糊的失敗,或者乾脆改去做別的。macOS 14 以後,這通常是「App 管理」,Apple 對它的描述是「允許 App 更新或刪除 Mac 上的其他 App」。判斷方式很簡單,這條路徑在 POSIX 層看得出可寫,寫入卻還是失敗:
test -w /Applications/Foo.app && echo "posix says yes"
這一行印出來了,移除還是沒發生,那問題就不在檔案權限上。去系統設定 › 隱私權與安全性 › App 管理,把負責移除的那款 App 打開,然後重新啟動它,因為很多 App 只在啟動時讀一次這個權限。
底層:三種長得一樣的拒絕
POSIX 權限是第一道門,套件的所有者是 root,你不是,sudo 就解得開,因為這道檢查只問你是哪個使用者。TCC,也就是系統設定背後那層隱私權機制,在 POSIX 通過之後才做決定。「App 管理」是 TCC 針對單一操作設的一道門,那個操作就是修改或刪除另一個 App 的套件。你是管理者不算數,sudo 也不算數,因為它認的是發出請求的那個程式,不是使用者,所以它的拒絕才會變成一個不跳提示的籠統錯誤。
SIP 在這兩者之下,直接拒絕 root。POSIX 的拒絕設的是 EACCES,印出 Permission denied;SIP 的拒絕設的是 EPERM,印出 Operation not permitted。前者的意思是換個有權限的身分再試一次,後者的意思是沒有哪個身分可以再試一次。
6. 系統延伸功能或網路過濾器還開著
資安軟體、VPN 用戶端和虛擬化產品都會安裝系統延伸功能。App 套件只是註冊那個延伸功能的,它自己不是延伸功能。延伸功能還開著就把容器刪掉,那筆登記就沒有所有者了,Mac 會繼續把流量交給你以為已經移除的軟體去過濾,就是這麼來的。
systemextensionsctl list
輸出裡有 team identifier、bundle identifier,以及像 [activated enabled] 這樣的狀態。停用這件事屬於承載它的那個 App,而且常常需要重新開機,所以先跑原廠的解除安裝程式。怎麼解除安裝 Mac 上的防毒軟體講的就是這一類軟體的拆除順序。
7. 它來自 App Store,或者 Homebrew
Mac App Store 的 App。 刪掉套件不等於取消那筆購買。最常讓人栽跟頭的是一個設定,Apple 的描述是「自動下載你在其他 Mac 電腦和裝置上從 App Store 購買的 App」。同一個帳號下的第二台 Mac 還裝著它,這個設定就能把它帶回來。到 App Store › 設定把它關掉。
Homebrew 的 cask。 當初是用 brew install --cask foo 裝的,把 App 丟進垃圾桶之後,Homebrew 仍然認為它裝著。brew list --cask 照樣列得出那個名稱,而下一次 brew upgrade 就可能把你剛刪掉的 App 裝回來,這也是一種貨真價實的「它又回來了」,只是跟 launchd 無關。改用 brew uninstall --cask foo。Homebrew 的手冊把 --zap 描述成移除「與一個 cask 相關的所有檔案」,也警告它「可能移除多個應用程式之間共用的檔案」,所以那個參數要想清楚再用。
刪成功之後,還留下什麼
「登入項目與延伸功能」裡還列著它。 要嘛是一筆「登入時打開」的項目,指向一條已經不存在的路徑,要嘛是背景工作管理裡的一筆服務紀錄,程式早就不在了。到系統設定 › 一般 › 登入項目與延伸功能用減號把它移掉,macOS 通常在重新開機之後也會自己清掉。
開機時冒出一個選單列圖示。 還有東西在載入一個執行檔,通常是一個你沒移掉的 launch agent,或者安裝程式複製到 ~/Library/Application Support/<vendor> 裡的輔助程式。
特權輔助程式還活著。 需要 root 的軟體會裝到 /Library/PrivilegedHelperTools/<label>,配一份對應的 /Library/LaunchDaemons/<label>.plist。兩個的所有者都是 root,也都在套件外面,所以把 App 丟進垃圾桶,這兩個都碰不到。
ls -la ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons \
/Library/PrivilegedHelperTools 2>/dev/null
資料那一側見解除安裝之後的殘留檔案。
用 Mole 做這套診斷
這篇文章的手動版本要開五個畫面,活動監視器、三個 launchd 資料夾、登入項目與延伸功能、隱私權與安全性,還有 Finder。Mole 把同一款 App 名下的東西收進同一個分頁,而掃描永遠免費,所以買不買,它都能當一個唯讀的診斷工具用。
打開 Mole 的 Software 分頁,裡面有三段,已安裝的 App 清單、可用的更新,以及開機啟動項目。最後那一段,就是這篇文章要你手動拼出來的那份 launchd 和登入項目清單,所以「它會回來」的成因,在你刪之前就看得見,不是刪完才看見。
選中一款 App,Mole 會解析它的 bundle identifier,再找出這個身分名下的東西,Application Support、Caches、Preferences、Containers、HTTPStorages,還有 launch agent 和 daemon,條件是那些 plist 真的引用到這個 App,不是只跟它的名字撞了一個詞。每個候選都帶著路徑、大小,還有把它掛上來的證據,信心不足的一律預設不勾。
按下確認,跑的就是這篇文章建議的那個順序,而且是程式強制執行,不靠人記得。Mole 會關掉這個 App 和它套件裡巢狀的輔助程式,把它的登入項目輔助程式踢出去,在刪 plist 之前先用 launchctl 卸下已核准的啟動項目,並在真正刪除的那一刻把每條路徑再驗一次。刪除走垃圾桶,弄錯了是從垃圾桶拖回來。
接著 Mole 報的是略過和失敗的項目連同路徑,不是一個成功總數,所以某一列因為「App 管理」被拒而失敗,或者路徑屬於 root、被保護機制擋下,你當場就知道自己撞上的是上面哪一個成因。工具要是把一次被拒絕的寫入印成一個勾,就會有人為了一個選單列圖示耗掉一整晚。Mole 做的每件事都在本機執行,檔案操作都寫進 ~/Library/Logs/mole/operations.log。終端機裡,Mole CLI 免費且開源,mo uninstall 吃 --dry-run,讓你先把路徑清單讀過。
有三個成因 Mole 贏不了,也不去硬碰。它不會關掉 SIP,也不會從封印的磁碟區裡移除 Apple 內建的 App。受管理的 Mac 上,設定描述檔會把軟體裝回來,這一點它蓋不過。遇到資安代理程式、VPN 用戶端和虛擬化產品,它取代不了那個知道拆除順序的原廠解除安裝程式。「App 管理」被拒的時候,Mole 回報這次失敗,不會繞過去提權。
常見問題
為什麼 App 刪掉之後又回來?
有四個成因,大致按常見程度排。某個 launchd agent 或 daemon 手上還留著一份指向它的工作定義,觸發條件在活動監視器裡看不到,時間到了就把它拉起來。或者你是把 App 丟進垃圾桶,沒跑 brew uninstall --cask,Homebrew 的 cask 紀錄就活了下來。受管理的 Mac 上,是 MDM 把它推回來。再來就是 App Store 那個「自動下載你在其他裝置上購買的 App」的設定,把它從第二台 Mac 拉了過來。
關掉 SIP 就可以刪掉 Apple 內建的 App 嗎?
技術上可以,但這筆交易不划算,你要從復原模式做一次全機範圍的安全性降級,而換來的那個套件,下一次 macOS 更新換掉封印的系統磁碟區時就會回來。把它從 Dock 和登入項目裡移掉就好。
Finder 刪 App 時要密碼,是出了什麼問題嗎?
沒有。是 .pkg 安裝程式以 root 身分把套件放進去的,所以搬動它要驗證身分。真正該擔心的是反過來那種,沒有提示、沒有錯誤,什麼都沒發生,那通常是「系統設定 › 隱私權與安全性」底下的「App 管理」被拒,sudo 修不好它,因為那道門認的是提出請求的程式,不是使用者。
延伸閱讀
- 如何徹底解除安裝 Mac 上的 App,講一般流程,以及它會逐層審過的資源庫。
- 怎麼解除安裝 Mac 上的防毒軟體,講帶系統延伸功能的軟體,那一類的重點整個就在拆除順序。
- 怎麼關閉 Mac 的開機啟動項目,講沒有東西再跟你作對之後,launchd 和登入項目那一側該怎麼收。