rm
破壞性刪除裝置上的一個檔案,或一棵目錄樹。
- 指令
- adb shell rm
- 指令
- 檔案與空間
- 風險
- 破壞性
語法
rm [-f] [-r] <path>
rmdir <path>旗標與參數
| token | 意義 |
|---|---|
| -f | 不要詢問。這關乎的是卡住而不是隨便:舊的 shell 服務會給指令一個終端機,所以遇到防寫的檔案會跳出一個沒有人能回答的確認。 |
| -r | 遞迴進入目錄。危險的是這一個。 |
| rmdir | 只在目錄是空的時候刪掉它。對一個掃描結果幾分鐘前說是空的資料夾,它比 `rm -rf` 安全。 |
| -v | 把每一次移除即時印出來,一行一個被引號包起來的路徑。成功本來是完全沒有聲音的,所以這是唯一不用再掃一次裝置,就能看出哪些路徑走了、哪些被跳過的方法。 |
| -i | 每次移除前都詢問——`-f` 的完全相反,而且在一條另一端沒有東西可以回答的連線上根本不能用。列出來是為了讓你在一段抄來的指令卡住時認得出它。 |
範例
rm -f '/sdcard/Download/old installer.apk'一個檔案,用單引號包起來以保留名稱裡的空白。
rmdir '/sdcard/Android/data/com.gone.app'一個空的殘留目錄,而「是空的」這件事由裝置確認。
rm -rf '/sdcard/Android/data/com.gone.app'同一個目錄與它裡面的一切。先確認那個套件真的已經不在了。
rm -v -f '/sdcard/Download/old installer.apk'同一次刪除,但它會印出實際移除的路徑,所以結果是一張收據而不是一個推論。
輸出怎麼讀
成功時什麼都沒有。被拒絕時會印出來(`Permission denied`、`rmdir` 的 `Directory not empty`、沒加 `-f` 時的 `No such file or directory`),而那段文字就是全部的訊息。
輸出欄位
| 欄位 | 意義 |
|---|---|
| (no output) | 那個路徑不見了。 |
| rmdir: … Directory not empty | 從你上次看之後有東西出現了。這是刻意不吞掉的。 |
| Permission denied | shell 使用者不能往那裡寫。 |
| rm '<path>' | 被移除檔案的 `-v` 收據。被移除的目錄會以同樣的格式回報成 `rmdir '<path>'`。 |
| rm ro <path> | 這是一個提問,不是結果。指令正在等;到現在還什麼都沒刪掉,而且在它拿到答案之前也不會刪。 |
需要什麼權限
不用 root、沒有開關、沒有東西要啟用——而這正是這一頁在講的事,因為 shell 對共用儲存空間的觸及範圍很寬。`adbd` 降到 `shell` uid 的時候,附加群組清單裡帶著 `sdcard_rw`、`ext_data_rw` 與 `ext_obb_rw`,這就是為什麼一條線可以刪掉共用儲存空間底下的任何東西,包含 `Android/data` 與 `Android/obb` 那兩個目錄——也就是同一支手機上的普通 app 從 Android 11 起就被擋在外面的那幾個資料夾。`/data/data` 底下的 app 私有儲存空間沒有 root 依然碰不到,系統分割區是唯讀掛載的,所以一個沒有特權的 `rm` 的殺傷範圍,幾乎剛好就是使用者自己的照片、下載與 app 快取:那些在別的地方沒有副本的資料。`adb root` 會把範圍擴大到全部,而它只存在於 `userdebug` 與 `eng` 版本上;在這裡完全沒有理由去碰它。有一個存取細節會改變你該打哪個指令。舊的 shell 服務會給 `rm` 一個終端機,而 toybox 的 `rm` 在移除一個 shell 寫不了的檔案之前會先問——它把問題印在 stderr 上,然後等。在一條沒有人可以回答的連線上,這個指令永遠不會回來。`-f` 就是防止這件事的東西,這也是它出現在這一頁上的身分:治療卡住的方法,不是可以隨便亂來的許可。
失敗訊息對照
| 訊息 | 意義 |
|---|---|
| rm: /sdcard/Download/old installer.apk: Permission denied | toybox 指名的是它當下在處理的那個節點。對你在指令列上給的路徑來說,那個名字就是整個路徑,所以這一行可以照抄;但對遞迴走訪「裡面」碰到的檔案來說,它只有最後一段,於是你得自己回推它是從哪個目錄來的。一次指令一個確切路徑,就是讓這件事不會模糊的方法。 |
| rmdir: /sdcard/Android/data/com.gone.app: Directory not empty | 在掃描與刪除之間,有東西進到那個資料夾裡了——一張截圖、一行被寫到磁碟上的紀錄、一個續傳的下載。這正是 `rmdir` 在做它存在的工作:它是唯一有前置條件的刪除指令,而那個前置條件是在刪除那一瞬間由裝置判定的,不是由幾分鐘前列過那個資料夾的什麼東西判定的。 |
| rm: rm /. if you mean it | 字面上的參數 `/` 會被用那句怪怪的話拒絕,然後指令就直接處理下一個參數。它不是權限檢查,而那個說法也不是這一頁的錯字——原始碼裡就是那段文字。什麼都沒被移除。 |
| rm: bad path /sdcard/Download/.. | 最後一段是 `..` 的參數會被直接拒絕,因為像 `dir/.*` 這樣的樣式展開之後會包含它,刪除就會落到父目錄上。什麼都沒被移除。請把它當成一個警訊:你送出去的那個路徑是別的東西組出來的,不是你。 |
| rm: Needs 1 argument (see "rm --help") | 根本沒有任何路徑活著抵達這個指令——通常是一個沒加引號的變數展開成了空的。這值得停下來想一下,不要直接重試:沒有這個檢查,同樣的錯誤就會變成一個沒有任何錨點的刪除指令。 |
| rm ro /sdcard/protected.bin | 它不是錯誤,也不是輸出的結尾:它是一個寫到 stderr 上的問題,而 `rm` 現在正在等一個 yes 或 no。沒有 `-f` 時,任何 shell 寫不了的檔案都會觸發它,而在一條鍵盤前沒有人的連線上,指令會卡住而不是失敗——這是最容易被誤認成「裝置很慢」的失敗模式。 |
會改變什麼,以及怎麼回頭
Android 沒有垃圾桶也沒有復原。刪掉的照片就是不見了,而對錯的目錄下 `-r` 會把它底下的一切帶走。一次指令只給一個確切路徑——不要用萬用字元,也不要對著你沒有剛剛列過的目錄下手;你認為是空的東西請優先用 `rmdir`,因為它在不是空的時候會「失敗」。這個指令沒有回頭路,唯一的保護是你打進去的那個路徑。
版本與廠商注意事項
- 每個路徑都要用單引號包起來。單引號裡面,空白、`&`、`$`、反引號與分號全都只是位元組,而手機上真實存在的檔案就叫做 `Rock & Roll.mp3` 這種名字。單引號唯一帶不了的字元是單引號本身。
- 一次指令一個路徑。整批刪除只會對一大堆檔案回報一個結束碼,所以部分失敗是看不見的。
- 刪掉共用儲存空間底下的各 app 目錄不等於解除安裝,也不會釋放 app 自己的私有資料;消失的是放在共用儲存空間上的那一份。
- shell 拿到的那幾個附加群組——`sdcard_rw`、`ext_data_rw`、`ext_obb_rw`——就是讓一條線可以刪掉同一支手機上的 app 根本碰不到的 `Android/data` 資料夾的原因。那是刻意給的能力而不是疏忽,也正是這一頁堅持「一次一個確切、而且剛剛才列過的路徑」的理由。
- `-v` 不用付出任何代價,卻能把一次無聲的成功變成一張收據。照著 Android Storage Analyzer 產出的清單一路做下去時,它就是「知道哪些路徑走了」與「重掃一次裝置才知道」的差別。
這個我們有 UI
Android Storage Analyzer 會透過 USB 執行這個指令並解析回應——只要 Chromium 系瀏覽器,不必安裝 SDK,也不會上傳任何東西。
相關指令
和 rm 會在同一次作業裡出現的指令。
| 指令 | 做什麼 |
|---|---|
| find | 走過一棵目錄樹,印出符合條件的東西——大檔案、空資料夾、殘留物。 |
| du | 回報一棵目錄樹占掉多少磁碟空間。 |
| screenrecord | 把螢幕錄成裝置上的一個 MP4 檔。 |
| pm install | 安裝一個已經在裝置上的 APK,也就是 `adb install` 底層在做的事。 |
其他檔案與空間指令
| 指令 | 做什麼 |
|---|---|
| df | 回報每個已掛載檔案系統的剩餘與已用空間。 |
這些行為是在撰寫 Web ADB 工具背後的解析程式時,對照 AOSP、toybox 與核心原始碼查證的。真的會因 Android 版本或廠牌而異的地方,這一頁會直接說明,而不是給你一個在半數手機上都是錯的數字。