<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>雜項資訊 on Tarragon</title><link>https://tarrragon.github.io/blog/other/</link><description>Recent content in 雜項資訊 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sun, 28 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/other/index.xml" rel="self" type="application/rss+xml"/><item><title>macOS 每個 App 到底吃多少空間：聚合佔用的 app-report 腳本</title><link>https://tarrragon.github.io/blog/other/macos-%E6%AF%8F%E5%80%8B-app-%E5%88%B0%E5%BA%95%E5%90%83%E5%A4%9A%E5%B0%91%E7%A9%BA%E9%96%93%E8%81%9A%E5%90%88%E4%BD%94%E7%94%A8%E7%9A%84-app-report-%E8%85%B3%E6%9C%AC/</link><pubDate>Sat, 27 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/other/macos-%E6%AF%8F%E5%80%8B-app-%E5%88%B0%E5%BA%95%E5%90%83%E5%A4%9A%E5%B0%91%E7%A9%BA%E9%96%93%E8%81%9A%E5%90%88%E4%BD%94%E7%94%A8%E7%9A%84-app-report-%E8%85%B3%E6%9C%AC/</guid><description>&lt;p>&lt;code>du ~/Library/*&lt;/code> 只能列出 Caches、Containers 這些目錄各佔多少，答不出「Steam 這個 App 一共吃了多少」。原因是一個 App 的資料散落在 &lt;code>~/Library&lt;/code> 好幾個不同位置，按目錄統計就拆不回它名下。這篇記錄一個把這些散落佔用聚合回各 App 的 &lt;code>app-report&lt;/code> 腳本——搭配磁碟層的 &lt;a href="../macos_disk_space_diagnosis/">disk-report&lt;/a>，後者找出哪棵子樹最大，這篇把子樹拆到 App。&lt;/p>
&lt;h2 id="一個-app-的真實佔用不等於它的-app-大小">一個 App 的真實佔用不等於它的 .app 大小&lt;/h2>
&lt;p>判斷一個 App 吃多少空間，要算的是它的總足跡（footprint），而不是 &lt;code>/Applications&lt;/code> 裡那顆 &lt;code>.app&lt;/code> 的大小。&lt;code>.app&lt;/code> 只是程式本體，App 跑起來產生的資料（下載內容、快取、登入狀態、設定、日誌）絕大多數寫在 &lt;code>~/Library&lt;/code> 底下的好幾個不同位置，跟 &lt;code>.app&lt;/code> 完全分家。&lt;/p>
&lt;p>這台機器上最極端的例子是 Steam：它的 &lt;code>.app&lt;/code> 只有 10.8M，但遊戲資料佔了 8.1G，兩者差了近 800 倍。只看 &lt;code>/Applications&lt;/code> 的大小排序，Steam 會排在很後面，完全看不出它是全機第一大戶。同樣地，Amazon Kindle 的 &lt;code>.app&lt;/code> 才 138M，書庫卻在沙箱容器裡佔了 3.2G。這就是為什麼「按目錄統計」和「按 App 統計」會給出完全不同的排行；要回答「哪個 App 該清」，必須把佔用聚合回 App。&lt;/p>
&lt;h2 id="佔用散落在-library-的哪些地方">佔用散落在 ~/Library 的哪些地方&lt;/h2>
&lt;p>聚合的第一步是知道一個 App 會把資料寫到哪些固定位置。下表只列與空間相關的主要位置（非 &lt;code>~/Library&lt;/code> 全量），macOS 對它們有約定，每個位置承擔不同責任，也決定了它能不能安全清掉。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>位置&lt;/th>
 &lt;th>放什麼&lt;/th>
 &lt;th>清掉的後果&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>/Applications/*.app&lt;/code>&lt;/td>
 &lt;td>程式本體&lt;/td>
 &lt;td>等於移除 App&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>~/Library/Caches/&lt;/code>&lt;/td>
 &lt;td>快取&lt;/td>
 &lt;td>下次自動重建，安全&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>~/Library/HTTPStorages/&lt;/code>&lt;/td>
 &lt;td>網路快取（cookie / 暫存）&lt;/td>
 &lt;td>多半要重新登入，大致安全&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>~/Library/Application Support/&lt;/code>&lt;/td>
 &lt;td>設定與使用者資料&lt;/td>
 &lt;td>掉資料&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>~/Library/Containers/&lt;/code>&lt;/td>
 &lt;td>沙箱 App 的完整家目錄&lt;/td>
 &lt;td>掉資料&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>~/Library/Group Containers/&lt;/code>&lt;/td>
 &lt;td>同廠商 App 共享的資料&lt;/td>
 &lt;td>掉資料、可能影響多個 App&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>~/Library/Saved Application State/&lt;/code>&lt;/td>
 &lt;td>視窗位置與復原狀態&lt;/td>
 &lt;td>下次開窗位置重置，無傷&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>~/Library/Logs/&lt;/code>&lt;/td>
 &lt;td>日誌&lt;/td>
 &lt;td>安全&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這張表的關鍵分界是「快取」與「資料」。&lt;code>Caches&lt;/code> 和 &lt;code>HTTPStorages&lt;/code> 是純衍生物，清掉只是讓 App 下次重新下載或重建，最多重新登入一次，所以是回收空間時的首選。&lt;code>Application Support&lt;/code>、&lt;code>Containers&lt;/code>、&lt;code>Group Containers&lt;/code> 則是使用者資料，Steam 的遊戲、Kindle 的書庫、聊天記錄都在這裡，刪了就真的沒了。&lt;code>Group Containers&lt;/code> 還要多一層留意：它是同一個開發商旗下多個 App 共享的目錄，動它可能同時影響好幾個 App。&lt;/p>
&lt;p>腳本對每個 App 把上面這些位置全部找出來、用 &lt;code>du&lt;/code> 量實際佔用、加總成一個數字，再附上逐項明細，讓人一眼看出「這 4G 裡有多少是可清的快取、多少是動不得的資料」。&lt;/p>
&lt;h2 id="命名不一致是聚合的主要難點">命名不一致是聚合的主要難點&lt;/h2>
&lt;p>把資料夾正確歸給某個 App 的難點在於：macOS 對這些目錄沒有統一的命名規則。有些 App 用它的 bundle id（例如 &lt;code>com.valvesoftware.steam&lt;/code>）當目錄名，有些直接用 App 的顯示名稱（例如 &lt;code>Steam&lt;/code>），同一個 App 的不同位置甚至各用一種。&lt;/p>
&lt;p>腳本的做法是對每個 App 先讀出它的 bundle id，然後 &lt;code>Caches&lt;/code>、&lt;code>Application Support&lt;/code>、&lt;code>Logs&lt;/code> 這幾個位置兩種命名都比對一次，bundle id 專屬的位置（&lt;code>Containers&lt;/code>、&lt;code>HTTPStorages&lt;/code>、&lt;code>Saved Application State&lt;/code>）則用 bundle id 找。&lt;code>Group Containers&lt;/code> 又是另一種格式，名稱前面多一段開發商的 team id（10 碼英數，像 &lt;code>ABCDE12345.group.com.foo&lt;/code>），因此改用 bundle id 做子字串比對。這套規則涵蓋了絕大多數 App，但用罕見自訂命名的資料仍可能漏抓，這是聚合式估算的固有邊界，腳本在輸出裡據實標明「可能漏抓」而不假裝是精確值。&lt;/p>
&lt;h2 id="homebrew-要分開算">Homebrew 要分開算&lt;/h2>
&lt;p>透過 Homebrew 裝的工具不在 &lt;code>/Applications&lt;/code>，需要獨立統計。佔用分兩類（概念詳見 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/homebrew/" data-link-title="Homebrew" data-link-desc="macOS 上社群維護的套件管理器、用一行指令安裝 CLI 工具與背景服務">Homebrew 知識卡&lt;/a>）：命令列工具與函式庫（&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/homebrew/" data-link-title="Homebrew" data-link-desc="macOS 上社群維護的套件管理器、用一行指令安裝 CLI 工具與背景服務">formula&lt;/a>）在 &lt;code>Cellar/&lt;/code>，GUI App 的下載 artifact 與 metadata（&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/homebrew/" data-link-title="Homebrew" data-link-desc="macOS 上社群維護的套件管理器、用一行指令安裝 CLI 工具與背景服務">cask&lt;/a>）在 &lt;code>Caskroom/&lt;/code>。cask 安裝的 &lt;code>.app&lt;/code> 本體實際放在 &lt;code>/Applications&lt;/code>，已被前面的 App 聚合排行計入；&lt;code>Caskroom/&lt;/code> 存的是安裝來源與版本資訊，體積通常遠小於 App 本體，兩邊不重複計。&lt;/p></description><content:encoded><![CDATA[<p><code>du ~/Library/*</code> 只能列出 Caches、Containers 這些目錄各佔多少，答不出「Steam 這個 App 一共吃了多少」。原因是一個 App 的資料散落在 <code>~/Library</code> 好幾個不同位置，按目錄統計就拆不回它名下。這篇記錄一個把這些散落佔用聚合回各 App 的 <code>app-report</code> 腳本——搭配磁碟層的 <a href="../macos_disk_space_diagnosis/">disk-report</a>，後者找出哪棵子樹最大，這篇把子樹拆到 App。</p>
<h2 id="一個-app-的真實佔用不等於它的-app-大小">一個 App 的真實佔用不等於它的 .app 大小</h2>
<p>判斷一個 App 吃多少空間，要算的是它的總足跡（footprint），而不是 <code>/Applications</code> 裡那顆 <code>.app</code> 的大小。<code>.app</code> 只是程式本體，App 跑起來產生的資料（下載內容、快取、登入狀態、設定、日誌）絕大多數寫在 <code>~/Library</code> 底下的好幾個不同位置，跟 <code>.app</code> 完全分家。</p>
<p>這台機器上最極端的例子是 Steam：它的 <code>.app</code> 只有 10.8M，但遊戲資料佔了 8.1G，兩者差了近 800 倍。只看 <code>/Applications</code> 的大小排序，Steam 會排在很後面，完全看不出它是全機第一大戶。同樣地，Amazon Kindle 的 <code>.app</code> 才 138M，書庫卻在沙箱容器裡佔了 3.2G。這就是為什麼「按目錄統計」和「按 App 統計」會給出完全不同的排行；要回答「哪個 App 該清」，必須把佔用聚合回 App。</p>
<h2 id="佔用散落在-library-的哪些地方">佔用散落在 ~/Library 的哪些地方</h2>
<p>聚合的第一步是知道一個 App 會把資料寫到哪些固定位置。下表只列與空間相關的主要位置（非 <code>~/Library</code> 全量），macOS 對它們有約定，每個位置承擔不同責任，也決定了它能不能安全清掉。</p>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>放什麼</th>
          <th>清掉的後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>/Applications/*.app</code></td>
          <td>程式本體</td>
          <td>等於移除 App</td>
      </tr>
      <tr>
          <td><code>~/Library/Caches/</code></td>
          <td>快取</td>
          <td>下次自動重建，安全</td>
      </tr>
      <tr>
          <td><code>~/Library/HTTPStorages/</code></td>
          <td>網路快取（cookie / 暫存）</td>
          <td>多半要重新登入，大致安全</td>
      </tr>
      <tr>
          <td><code>~/Library/Application Support/</code></td>
          <td>設定與使用者資料</td>
          <td>掉資料</td>
      </tr>
      <tr>
          <td><code>~/Library/Containers/</code></td>
          <td>沙箱 App 的完整家目錄</td>
          <td>掉資料</td>
      </tr>
      <tr>
          <td><code>~/Library/Group Containers/</code></td>
          <td>同廠商 App 共享的資料</td>
          <td>掉資料、可能影響多個 App</td>
      </tr>
      <tr>
          <td><code>~/Library/Saved Application State/</code></td>
          <td>視窗位置與復原狀態</td>
          <td>下次開窗位置重置，無傷</td>
      </tr>
      <tr>
          <td><code>~/Library/Logs/</code></td>
          <td>日誌</td>
          <td>安全</td>
      </tr>
  </tbody>
</table>
<p>這張表的關鍵分界是「快取」與「資料」。<code>Caches</code> 和 <code>HTTPStorages</code> 是純衍生物，清掉只是讓 App 下次重新下載或重建，最多重新登入一次，所以是回收空間時的首選。<code>Application Support</code>、<code>Containers</code>、<code>Group Containers</code> 則是使用者資料，Steam 的遊戲、Kindle 的書庫、聊天記錄都在這裡，刪了就真的沒了。<code>Group Containers</code> 還要多一層留意：它是同一個開發商旗下多個 App 共享的目錄，動它可能同時影響好幾個 App。</p>
<p>腳本對每個 App 把上面這些位置全部找出來、用 <code>du</code> 量實際佔用、加總成一個數字，再附上逐項明細，讓人一眼看出「這 4G 裡有多少是可清的快取、多少是動不得的資料」。</p>
<h2 id="命名不一致是聚合的主要難點">命名不一致是聚合的主要難點</h2>
<p>把資料夾正確歸給某個 App 的難點在於：macOS 對這些目錄沒有統一的命名規則。有些 App 用它的 bundle id（例如 <code>com.valvesoftware.steam</code>）當目錄名，有些直接用 App 的顯示名稱（例如 <code>Steam</code>），同一個 App 的不同位置甚至各用一種。</p>
<p>腳本的做法是對每個 App 先讀出它的 bundle id，然後 <code>Caches</code>、<code>Application Support</code>、<code>Logs</code> 這幾個位置兩種命名都比對一次，bundle id 專屬的位置（<code>Containers</code>、<code>HTTPStorages</code>、<code>Saved Application State</code>）則用 bundle id 找。<code>Group Containers</code> 又是另一種格式，名稱前面多一段開發商的 team id（10 碼英數，像 <code>ABCDE12345.group.com.foo</code>），因此改用 bundle id 做子字串比對。這套規則涵蓋了絕大多數 App，但用罕見自訂命名的資料仍可能漏抓，這是聚合式估算的固有邊界，腳本在輸出裡據實標明「可能漏抓」而不假裝是精確值。</p>
<h2 id="homebrew-要分開算">Homebrew 要分開算</h2>
<p>透過 Homebrew 裝的工具不在 <code>/Applications</code>，需要獨立統計。佔用分兩類（概念詳見 <a href="/blog/llm/knowledge-cards/homebrew/" data-link-title="Homebrew" data-link-desc="macOS 上社群維護的套件管理器、用一行指令安裝 CLI 工具與背景服務">Homebrew 知識卡</a>）：命令列工具與函式庫（<a href="/blog/llm/knowledge-cards/homebrew/" data-link-title="Homebrew" data-link-desc="macOS 上社群維護的套件管理器、用一行指令安裝 CLI 工具與背景服務">formula</a>）在 <code>Cellar/</code>，GUI App 的下載 artifact 與 metadata（<a href="/blog/llm/knowledge-cards/homebrew/" data-link-title="Homebrew" data-link-desc="macOS 上社群維護的套件管理器、用一行指令安裝 CLI 工具與背景服務">cask</a>）在 <code>Caskroom/</code>。cask 安裝的 <code>.app</code> 本體實際放在 <code>/Applications</code>，已被前面的 App 聚合排行計入；<code>Caskroom/</code> 存的是安裝來源與版本資訊，體積通常遠小於 App 本體，兩邊不重複計。</p>
<p>這台機器的 formula 前幾名是開發語言執行環境：<code>dotnet@9</code> 634M、兩個版本的 <code>openjdk</code> 合計 600M、<code>mysql</code> 292M、<code>go</code> 258M。formula 會多版本並存（例如 <code>python@3.13</code> 和 <code>python@3.14</code> 各算各的），所以腳本把整個 formula 目錄一起計。除了已安裝的部分，腳本還列出 <code>brew --cache</code> 的下載快取，以及 <code>brew cleanup -n</code> 預估可回收的舊版本（<code>-n</code> 是 dry-run，只報告不刪），跟整支腳本的唯讀原則一致。</p>
<h2 id="聚合一律用-du-取實際佔用">聚合一律用 du 取實際佔用</h2>
<p>App 各位置的聚合一律用 <code>du -skx</code> 取實際佔用，而不是 <code>ls</code> / <code>find -size</code> 的邏輯大小。sparse 檔（稀疏檔）只有寫入過的區塊才真正佔磁碟，宣告的邏輯大小可能是實際佔用的數十倍；容器與資料目錄裡正好常有 VM 映像、容器磁碟這類 sparse 檔，拿邏輯大小加總會把整份聚合排行灌水。完整的 sparse 踩坑案例見 <a href="../macos_disk_space_diagnosis/">disk-report 那篇</a>。</p>
<p><code>-x</code> 讓 <code>du</code> 不跨越檔案系統邊界，避免把掛載進來的卷重複計入；<code>-k</code> 統一用 KB 當單位，方便把各位置的數字加總後再換算成人類可讀的 G / M。</p>
<h2 id="實測結果">實測結果</h2>
<p>下面是這台機器的實測排行（名次因個人使用習慣而異）；要看的是聚合排行和「按目錄統計」給的印象差多少：</p>
<table>
  <thead>
      <tr>
          <th>App</th>
          <th>總佔用</th>
          <th>主要落點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Steam</td>
          <td>8.1G</td>
          <td>data 8.1G（<code>.app</code> 只有 10.8M）</td>
      </tr>
      <tr>
          <td>Xcode</td>
          <td>4.8G</td>
          <td>bundle 4.8G</td>
      </tr>
      <tr>
          <td>Readmoo 看書</td>
          <td>4.6G</td>
          <td>data 3.8G + bundle 816M</td>
      </tr>
      <tr>
          <td>Dia</td>
          <td>4.1G</td>
          <td>cache 1.6G + bundle 1.3G + data 1.1G</td>
      </tr>
      <tr>
          <td>Amazon Kindle</td>
          <td>3.3G</td>
          <td>container 3.2G（<code>.app</code> 才 138M）</td>
      </tr>
  </tbody>
</table>
<p>全機掃到 65 個 App、聚合總計 48.2G。這份排行的價值在於它直接指向「該從哪裡下手」，而判讀邏輯可以套到任何人的排行上：本體小、資料大的 App（這台是 Steam、Kindle）要回收就得處理書庫與遊戲本身；純快取大的（這台是 Dia 的 1.6G）清掉零風險；本體就大的開發工具（Xcode、Android Studio）除非不再開發否則動不得。同一個總數字底下，可清的比例天差地別，這正是逐項明細要回答的問題。</p>
<h2 id="聚合的邊界總計不等於整機">聚合的邊界：總計不等於整機</h2>
<p>這個 48.2G 是「能歸屬到已安裝 App 的部分」之和，不是 <code>~/Library</code> 的全量。<a href="../macos_disk_space_diagnosis/">disk-report 那篇</a>量到的 <code>~/Library</code> 約 70G，差額落在幾類刻意不歸進單一 App 的位置。</p>
<p>最大的一塊是 <code>~/Library/Developer</code>（這台約 5.5G，幾乎全是 Xcode 的 DerivedData、CoreSimulator 與 iOS DeviceSupport）。它們是 Xcode 與模擬器產生的共用產物，硬塞給 Xcode 會誇大這顆 App、塞給別人又不對，app-report 比照 Homebrew 把它單獨列成一段（<code>app-report --dev</code>）。也因為這樣，上面排行裡的 Xcode 只算到 <code>.app</code> 本體，它的建置產物要看 Developer 那段——這也是為什麼 disk-report 會把「Xcode DeviceSupport」列為大戶，而逐 App 排行卻看不到：那筆資料正住在這個不歸單一 App 的位置。</p>
<p>其餘排除的還有 iCloud 與雲端硬碟的本地鏡像（<code>Mobile Documents</code>、<code>CloudStorage</code>）、已移除 App 留下的孤兒資料夾、以及 <code>Preferences</code>。排行掃的是 <code>/Applications</code>、<code>~/Applications</code>、<code>/Applications/Utilities</code> 與 Setapp、Mac App Store 裝的 App；直接從 DMG 跑、沒搬進 Applications 的 App 不會出現在排行，但它的 <code>~/Library</code> 資料若命名對得上仍可能部分計入。</p>
<p>還有一個方向相反的誤差要記得：這是估算不是精算。同一份資料若以 APFS clone 出現在多個被聚合的位置，逐位置分開跑 <code>du</code> 會各自計入（<code>du</code> 只在單次執行內對硬連結以 inode 去重，對 APFS clone 不去重），聚合值可能偏高。要看整個 <code>~/Library</code> 到底多大、由誰佔，回到 disk-report 的逐層 <code>du</code>。</p>
<h2 id="固化成-app-report-腳本">固化成 app-report 腳本</h2>
<p>把這套聚合邏輯寫成腳本，往後想知道「誰在吃空間」就一行重跑，不必每次重想要比對哪些目錄、要怎麼處理命名差異。腳本和 <code>disk-report</code> 收在同一個公開 repo <a href="https://github.com/tarrragon/scripts">tarrragon/scripts</a> 裡，維持「跟專案無關的系統工具放個人 bin」的一致做法。</p>
<p>兩支腳本在同一個 repo；若已為 <code>disk-report</code> clone 過 <code>~/Projects/scripts</code>，跳過 clone、只做 symlink。首次安裝則把 repo clone 下來，再把腳本本體 symlink 到個人的 <code>~/.local/bin</code>，這樣本機呼叫的永遠是 repo 的最新版：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">git clone https://github.com/tarrragon/scripts.git ~/Projects/scripts
</span></span><span class="line"><span class="ln">2</span><span class="cl">ln -s ~/Projects/scripts/app-report/app-report ~/.local/bin/app-report</span></span></code></pre></div><p>PATH 設定同 disk-report（見 <a href="../macos_new_machine_setup/">macOS 新機基礎建設</a>）。裝好後直接呼叫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">app-report           <span class="c1"># 完整報告：App 聚合排行 + Developer + Homebrew</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">app-report --apps    <span class="c1"># 只看 App 聚合排行（預設前 30）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">app-report --apps <span class="m">50</span> <span class="c1"># 排行顯示前 50</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">app-report --dev     <span class="c1"># 只看 ~/Library/Developer 開發工具共用資料</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">app-report --brew    <span class="c1"># 只看 Homebrew</span></span></span></code></pre></div><p>要清哪個 App，看完明細再動手：移掉 <code>.app</code> 並清對應的 <code>~/Library</code> 資料夾（報告每個 App 下方列的路徑就是清除對象；先從 <code>Caches</code> / <code>HTTPStorages</code> 開始，確認再考慮資料目錄），Homebrew 用 <code>brew cleanup -s</code>。</p>
<h2 id="兩支腳本的分工">兩支腳本的分工</h2>
<p><code>disk-report</code> 與 <code>app-report</code> 是磁碟清理的兩個接力棒。前者在卷與目錄層找出最大的子樹，通常落在 <code>~/Library</code>；後者接手把那棵子樹拆到 App，看出具體是誰佔的、各自有多少是可清的快取。先 disk 找方向、再 app 定位到人，兩支都唯讀，回收的最後一步都留在人這一端。</p>
]]></content:encoded></item><item><title>macOS 新機基礎建設：套件管理與個人 bin 的設定順序</title><link>https://tarrragon.github.io/blog/other/macos-%E6%96%B0%E6%A9%9F%E5%9F%BA%E7%A4%8E%E5%BB%BA%E8%A8%AD%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E8%88%87%E5%80%8B%E4%BA%BA-bin-%E7%9A%84%E8%A8%AD%E5%AE%9A%E9%A0%86%E5%BA%8F/</link><pubDate>Sat, 27 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/other/macos-%E6%96%B0%E6%A9%9F%E5%9F%BA%E7%A4%8E%E5%BB%BA%E8%A8%AD%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E8%88%87%E5%80%8B%E4%BA%BA-bin-%E7%9A%84%E8%A8%AD%E5%AE%9A%E9%A0%86%E5%BA%8F/</guid><description>&lt;p>重灌或換機後要補的設定很多，但有個底層順序不能跳：套件管理工具要先到位，後面的補強才裝得起來。這篇聚焦最底層的三項基礎建設（Homebrew、bash、個人 bin），按依賴順序排列。重點不只是「裝什麼」，而是「為什麼這個順序」；之後遇到的新需求會接在後面繼續增補。&lt;/p>
&lt;h2 id="先裝-homebrew它是後面一切的基礎">先裝 Homebrew，它是後面一切的基礎&lt;/h2>
&lt;p>macOS 本身沒有內建的第三方套件管理工具，而後面幾乎每一項補強（命令列工具、開發語言、甚至部分 GUI App）都靠它安裝。沒有 Homebrew，這份清單的其他項目全部無從裝起，所以它排第一。&lt;/p>
&lt;p>安裝過程會要求輸入登入密碼（sudo），並自動下載 Xcode Command Line Tools，畫面可能停住數分鐘屬正常。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">/bin/bash -c &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>裝完還要把 Homebrew 的執行檔目錄加進 PATH，shell 才找得到 &lt;code>brew&lt;/code> 與之後用它裝的工具。Homebrew 的安裝前綴依晶片而異：Apple Silicon 機器裝在 &lt;code>/opt/homebrew&lt;/code>、Intel 機器裝在 &lt;code>/usr/local&lt;/code>。安裝腳本結尾會印出對應這台機器的設定指令，照它印的路徑寫進 &lt;code>~/.zprofile&lt;/code> 讓每次開 shell 都生效。以下以 Apple Silicon 為例，Intel 機器把前綴換成 &lt;code>/usr/local&lt;/code> 即可：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;eval &amp;#34;$(/opt/homebrew/bin/brew shellenv)&amp;#34;&amp;#39;&lt;/span> &amp;gt;&amp;gt; ~/.zprofile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">eval&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>/opt/homebrew/bin/brew shellenv&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這一步做完，驗證安裝成功：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">brew --version &lt;span class="c1"># 應印出 Homebrew 4.x.x&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>版本號印出來，&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/homebrew/" data-link-title="Homebrew" data-link-desc="macOS 上社群維護的套件管理器、用一行指令安裝 CLI 工具與背景服務">Homebrew&lt;/a> 就能當後面所有項目的安裝來源。&lt;/p>
&lt;h2 id="把-bash-更新到-5x">把 bash 更新到 5.x&lt;/h2>
&lt;p>bash 是裝完 Homebrew 後最值得優先換掉的內建工具。macOS 的 &lt;code>/bin/bash&lt;/code> 長年凍結在 3.2 系列（2006 年釋出，目前是 patchlevel 57），Apple 不再更新它，原因是 bash 4 改用 GPLv3 授權、Apple 不願隨系統散布。對寫腳本的人來說，這代表 associative array、&lt;code>${var,,}&lt;/code> 大小寫轉換、&lt;code>mapfile&lt;/code> 等近二十年的語法都用不了。&lt;/p>
&lt;p>正確做法是用 Homebrew 另外裝一份新版並排存在，而不是覆寫系統版。&lt;code>/bin/bash&lt;/code> 在唯讀的系統卷上、受 SIP（System Integrity Protection）保護，覆寫會被擋下：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">brew install bash&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這會把 bash 5.x 裝到 &lt;code>/opt/homebrew/bin/bash&lt;/code>，完全不碰 &lt;code>/bin/bash&lt;/code>。因為前一步已經把 &lt;code>/opt/homebrew/bin&lt;/code> 排在 PATH 前面，用 &lt;code>#!/usr/bin/env bash&lt;/code> 起手的腳本就會自動改用新版。裝完驗證一下版本確實切過去：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">env bash --version &lt;span class="c1"># 應顯示 5.x&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">/bin/bash --version &lt;span class="c1"># 系統版仍是 3.2.57，沒被動&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>要留意的是互動 shell 在現代 macOS 預設是 zsh，這一步不影響它。更新 bash 的目的是給 &lt;code>#!/usr/bin/env bash&lt;/code> 腳本一個現代執行環境，不是換登入 shell。真要把新版 bash 當登入 shell，才需要額外把它加進 &lt;code>/etc/shells&lt;/code> 再 &lt;code>chsh&lt;/code>。&lt;/p>
&lt;h2 id="把-localbin-加進-path放個人腳本">把 ~/.local/bin 加進 PATH，放個人腳本&lt;/h2>
&lt;p>跟專案無關的小工具（例如 &lt;a href="../macos_disk_space_diagnosis/">disk-report&lt;/a> 與 &lt;a href="../macos_app_footprint_report/">app-report&lt;/a> 這類系統診斷腳本）需要一個能在任何地方直接呼叫、又不污染專案 repo 的家。慣例是個人的 &lt;code>~/.local/bin&lt;/code>，把它建好並掛上 PATH。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">mkdir -p ~/.local/bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;export PATH=&amp;#34;$HOME/.local/bin:$PATH&amp;#34;&amp;#39;&lt;/span> &amp;gt;&amp;gt; ~/.zprofile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/.local/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>目錄建好、PATH 掛上後，確認它確實生效：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> tr &lt;span class="s1">&amp;#39;:&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;\n&amp;#39;&lt;/span> &lt;span class="p">|&lt;/span> grep &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/.local/bin&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>之後把腳本 symlink 進這個目錄就能直接當指令用。&lt;/p>
&lt;h2 id="後續項目">後續項目&lt;/h2>
&lt;p>基礎建設到位後，第一個掛上去的實用腳本就是系統診斷：&lt;a href="../macos_disk_space_diagnosis/">磁碟空間診斷的 disk-report&lt;/a> 與 &lt;a href="../macos_app_footprint_report/">按 App 聚合佔用的 app-report&lt;/a>，兩支都 symlink 進 &lt;code>~/.local/bin&lt;/code> 直接當指令用。&lt;/p>
&lt;p>這份清單會隨著之後遇到的需求往下增補，新項目接在這裡。原則維持不變：基礎建設排前面，依賴它的補強排後面，每一項都寫清楚「為什麼要做」而不只是貼指令。&lt;/p></description><content:encoded><![CDATA[<p>重灌或換機後要補的設定很多，但有個底層順序不能跳：套件管理工具要先到位，後面的補強才裝得起來。這篇聚焦最底層的三項基礎建設（Homebrew、bash、個人 bin），按依賴順序排列。重點不只是「裝什麼」，而是「為什麼這個順序」；之後遇到的新需求會接在後面繼續增補。</p>
<h2 id="先裝-homebrew它是後面一切的基礎">先裝 Homebrew，它是後面一切的基礎</h2>
<p>macOS 本身沒有內建的第三方套件管理工具，而後面幾乎每一項補強（命令列工具、開發語言、甚至部分 GUI App）都靠它安裝。沒有 Homebrew，這份清單的其他項目全部無從裝起，所以它排第一。</p>
<p>安裝過程會要求輸入登入密碼（sudo），並自動下載 Xcode Command Line Tools，畫面可能停住數分鐘屬正常。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">/bin/bash -c <span class="s2">&#34;</span><span class="k">$(</span>curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh<span class="k">)</span><span class="s2">&#34;</span></span></span></code></pre></div><p>裝完還要把 Homebrew 的執行檔目錄加進 PATH，shell 才找得到 <code>brew</code> 與之後用它裝的工具。Homebrew 的安裝前綴依晶片而異：Apple Silicon 機器裝在 <code>/opt/homebrew</code>、Intel 機器裝在 <code>/usr/local</code>。安裝腳本結尾會印出對應這台機器的設定指令，照它印的路徑寫進 <code>~/.zprofile</code> 讓每次開 shell 都生效。以下以 Apple Silicon 為例，Intel 機器把前綴換成 <code>/usr/local</code> 即可：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;eval &#34;$(/opt/homebrew/bin/brew shellenv)&#34;&#39;</span> &gt;&gt; ~/.zprofile
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>/opt/homebrew/bin/brew shellenv<span class="k">)</span><span class="s2">&#34;</span></span></span></code></pre></div><p>這一步做完，驗證安裝成功：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">brew --version   <span class="c1"># 應印出 Homebrew 4.x.x</span></span></span></code></pre></div><p>版本號印出來，<a href="/blog/llm/knowledge-cards/homebrew/" data-link-title="Homebrew" data-link-desc="macOS 上社群維護的套件管理器、用一行指令安裝 CLI 工具與背景服務">Homebrew</a> 就能當後面所有項目的安裝來源。</p>
<h2 id="把-bash-更新到-5x">把 bash 更新到 5.x</h2>
<p>bash 是裝完 Homebrew 後最值得優先換掉的內建工具。macOS 的 <code>/bin/bash</code> 長年凍結在 3.2 系列（2006 年釋出，目前是 patchlevel 57），Apple 不再更新它，原因是 bash 4 改用 GPLv3 授權、Apple 不願隨系統散布。對寫腳本的人來說，這代表 associative array、<code>${var,,}</code> 大小寫轉換、<code>mapfile</code> 等近二十年的語法都用不了。</p>
<p>正確做法是用 Homebrew 另外裝一份新版並排存在，而不是覆寫系統版。<code>/bin/bash</code> 在唯讀的系統卷上、受 SIP（System Integrity Protection）保護，覆寫會被擋下：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">brew install bash</span></span></code></pre></div><p>這會把 bash 5.x 裝到 <code>/opt/homebrew/bin/bash</code>，完全不碰 <code>/bin/bash</code>。因為前一步已經把 <code>/opt/homebrew/bin</code> 排在 PATH 前面，用 <code>#!/usr/bin/env bash</code> 起手的腳本就會自動改用新版。裝完驗證一下版本確實切過去：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">env bash --version   <span class="c1"># 應顯示 5.x</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">/bin/bash --version  <span class="c1"># 系統版仍是 3.2.57，沒被動</span></span></span></code></pre></div><p>要留意的是互動 shell 在現代 macOS 預設是 zsh，這一步不影響它。更新 bash 的目的是給 <code>#!/usr/bin/env bash</code> 腳本一個現代執行環境，不是換登入 shell。真要把新版 bash 當登入 shell，才需要額外把它加進 <code>/etc/shells</code> 再 <code>chsh</code>。</p>
<h2 id="把-localbin-加進-path放個人腳本">把 ~/.local/bin 加進 PATH，放個人腳本</h2>
<p>跟專案無關的小工具（例如 <a href="../macos_disk_space_diagnosis/">disk-report</a> 與 <a href="../macos_app_footprint_report/">app-report</a> 這類系統診斷腳本）需要一個能在任何地方直接呼叫、又不污染專案 repo 的家。慣例是個人的 <code>~/.local/bin</code>，把它建好並掛上 PATH。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir -p ~/.local/bin
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;export PATH=&#34;$HOME/.local/bin:$PATH&#34;&#39;</span> &gt;&gt; ~/.zprofile
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span></span></span></code></pre></div><p>目錄建好、PATH 掛上後，確認它確實生效：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$PATH</span><span class="s2">&#34;</span> <span class="p">|</span> tr <span class="s1">&#39;:&#39;</span> <span class="s1">&#39;\n&#39;</span> <span class="p">|</span> grep <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/bin&#34;</span></span></span></code></pre></div><p>之後把腳本 symlink 進這個目錄就能直接當指令用。</p>
<h2 id="後續項目">後續項目</h2>
<p>基礎建設到位後，第一個掛上去的實用腳本就是系統診斷：<a href="../macos_disk_space_diagnosis/">磁碟空間診斷的 disk-report</a> 與 <a href="../macos_app_footprint_report/">按 App 聚合佔用的 app-report</a>，兩支都 symlink 進 <code>~/.local/bin</code> 直接當指令用。</p>
<p>這份清單會隨著之後遇到的需求往下增補，新項目接在這裡。原則維持不變：基礎建設排前面，依賴它的補強排後面，每一項都寫清楚「為什麼要做」而不只是貼指令。</p>
]]></content:encoded></item><item><title>macOS 磁碟空間被吃光的診斷流程</title><link>https://tarrragon.github.io/blog/other/macos-%E7%A3%81%E7%A2%9F%E7%A9%BA%E9%96%93%E8%A2%AB%E5%90%83%E5%85%89%E7%9A%84%E8%A8%BA%E6%96%B7%E6%B5%81%E7%A8%8B/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/other/macos-%E7%A3%81%E7%A2%9F%E7%A9%BA%E9%96%93%E8%A2%AB%E5%90%83%E5%85%89%E7%9A%84%E8%A8%BA%E6%96%B7%E6%B5%81%E7%A8%8B/</guid><description>&lt;p>一台原本還有約 30G 餘裕的 Mac，使用幾小時後空間全部歸零，清過系統各種 cache 也沒有改善。這次排查的重點是順序與判讀依據：用什麼順序找、用哪個數字判斷，最後刪了什麼反而次要。順序對了，就能避開兩個讓人空轉的陷阱。&lt;/p>
&lt;p>最後把整套診斷固化成一個唯讀的 &lt;code>disk-report&lt;/code> 腳本，往後同類情況可以一行指令重跑。&lt;/p>
&lt;h2 id="先確認問題是真的滿還是浮動的假象">先確認問題是「真的滿」還是「浮動的假象」&lt;/h2>
&lt;p>排查磁碟的第一步是分辨空間到底去哪：是被真實檔案佔走，還是被系統的快照與 purgeable（系統可隨時回收的緩衝空間）暫時佔住。這兩者的處理方式完全不同，先分清楚才不會白清。&lt;/p>
&lt;p>在 APFS（Apple File System，macOS 的預設檔案系統）上，根目錄 &lt;code>/&lt;/code> 是唯讀的系統封印卷，真正存放使用者資料的是 &lt;code>/System/Volumes/Data&lt;/code>，而它們和其他卷（Preboot、Recovery、VM、模擬器 runtime）共用同一個 container（容器，APFS 管理空間的最上層單位）的空間池。判斷「還剩多少」要看整個 container 的可用空間，而不是單一卷的數字。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">df -h /System/Volumes/Data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">diskutil info /System/Volumes/Data &lt;span class="p">|&lt;/span> grep -iE &lt;span class="s2">&amp;#34;Container Free Space|Container Total Space&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這次的結果是資料卷 100% 滿、整個 container 只剩約 591MB。確認確實滿載、不是顯示誤差，後面才值得花力氣找佔用大戶。&lt;/p>
&lt;h2 id="空間掉了又回來的根因本地快照與-purgeable">「空間掉了又回來」的根因：本地快照與 purgeable&lt;/h2>
&lt;p>空間在幾小時內反覆消長、清 cache 卻無效，最常見的原因是 Time Machine 的本地快照（local snapshots）加上 macOS 的 purgeable 空間，而不是某個看得見的檔案。這是排查時要先排除的一條線。&lt;/p>
&lt;p>本地快照的運作方式是：Time Machine 啟用時，系統約每小時自動建立一張快照「凍結」當下狀態，好讓本地也能做時光機回溯。這些被凍結的資料，正是先前以為已刪除、卻怎麼清都不會釋放的空間。快照保留約 24 小時（Apple 的 thinning 策略，觀察值），或在磁碟空間壓力過大時提前清除；後者正是「過一陣子空間又回來」的來源。若從未設定 Time Machine，這條線可跳過——沒啟用就不會有 local snapshot。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">tmutil listlocalsnapshots /System/Volumes/Data&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這次查的時候快照數是 0，但這不代表它不是元兇——恰恰相反，是磁碟已經滿到讓系統把快照全數清光了。判讀訊號是：若這個指令平常列出多筆快照、且磁碟空間在數字上頻繁浮動，浮動量就來自這裡，跟手動清的 cache 無關。根治方向是把總用量降下來、讓磁碟保有餘裕，系統就不會一直貼著上限狂建狂清快照。&lt;/p>
&lt;p>purgeable 是同一條線的另一半，但它沒有好用的精確讀數。&lt;code>diskutil apfs list&lt;/code> 能看 container 層的概況，而 purgeable 主要由快照與系統快取構成、本來就會自己浮動。處理方式跟快照一樣：把總用量降下來、讓系統在空間有壓力時自行釋放，而不是找指令直接清它。「沒有直接讀數」本身就是判讀邊界——看到可用空間和「實際檔案總和」對不上時，差額多半就在這塊浮動緩衝，不必懷疑是哪個檔案在搞鬼。&lt;/p>
&lt;h2 id="用實際佔用值找大戶避開-sparse-假大小">用實際佔用值找大戶，避開 sparse 假大小&lt;/h2>
&lt;p>找佔用大戶要用 &lt;code>du&lt;/code>（實際佔用的磁碟區塊）排序，不能依賴 &lt;code>ls -l&lt;/code> 顯示、或 &lt;code>find -size&lt;/code> 篩選所用的邏輯大小。對一般檔案兩者相同，但對 sparse 檔（稀疏檔）差距可以是好幾十倍，誤判會追錯目標。&lt;/p>
&lt;p>這次就踩到這個陷阱。&lt;code>find&lt;/code> 列出近期修改的大檔時，OrbStack（一套容器與 VM 執行環境）的虛擬磁碟映像顯示為 228G，看起來像頭號兇手；但用 &lt;code>du&lt;/code> 一量，實際佔用只有 1.9G。同樣地，macOS Podcasts 在 tmp 塞的一堆 &lt;code>.tmp.resize.img&lt;/code> 顯示有數十個檔，實際只佔 3.5M。這些都是 sparse 檔：宣告了很大的邏輯大小，但只有寫入過的區塊才真正佔磁碟。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 實際佔用（正確）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">du -sh ~/some/large.img
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 顯示大小（對 sparse 檔會嚴重高估，誤判用）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">ls -lh ~/some/large.img&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>定位順序是由外往內逐層收斂：先看家目錄前 20 大，鎖定最大的子樹（這次是 &lt;code>~/Library&lt;/code> 70G 左右），再往下展開 &lt;code>~/Library/Application Support&lt;/code>、&lt;code>~/Library/Containers&lt;/code>，直到找到具體的檔案或目錄。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">du -shx ~/* ~/.&lt;span class="o">[&lt;/span>!.&lt;span class="o">]&lt;/span>* 2&amp;gt;/dev/null &lt;span class="p">|&lt;/span> sort -rh &lt;span class="p">|&lt;/span> head -20
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">du -shx ~/Library/* 2&amp;gt;/dev/null &lt;span class="p">|&lt;/span> sort -rh &lt;span class="p">|&lt;/span> head -12&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>-x&lt;/code> 讓 &lt;code>du&lt;/code> 不跨越檔案系統邊界，避免把掛載進來的唯讀卷（例如 iOS 模擬器 runtime）重複計入；&lt;code>~/.[!.]*&lt;/code> 這個寫法只展開以單一點開頭的隱藏檔，排除掉 &lt;code>.&lt;/code> 和 &lt;code>..&lt;/code> 兩個會被一般 &lt;code>.*&lt;/code> 誤抓進來、算出整個家目錄大小的假項目。&lt;/p>
&lt;h2 id="這次找到的佔用大戶與處理">這次找到的佔用大戶與處理&lt;/h2>
&lt;p>定位出來的大戶集中在開發工具鏈與閒置的本地資料，多數可逆、刪了之後需要時會自動重建或可重新下載。下面的項目與數字都是這台機器的實測，換一台機器組成會完全不同；值得帶走的是每一項背後的判讀問題，不是這份清單本身。具體刪除指令因工具而異（Android Studio GUI、&lt;code>rm -rf&lt;/code>、&lt;code>ollama rm&lt;/code>），本文只做診斷與定位，刪除操作留給各工具自身的文件。以下逐項說明判讀依據。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>項目&lt;/th>
 &lt;th>實際佔用&lt;/th>
 &lt;th>處理判斷&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>舊版 Android NDK&lt;/td>
 &lt;td>約 3G&lt;/td>
 &lt;td>裝了多版、保留專案實際引用的版本，刪最舊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>用不到的 AVD + system-image&lt;/td>
 &lt;td>約 3G&lt;/td>
 &lt;td>一個 API 版本一組、停用的版本連 AVD 帶映像一起刪&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Claude 桌面 Cowork 沙箱 VM&lt;/td>
 &lt;td>約 11G&lt;/td>
 &lt;td>只在使用桌面 App 的本地 agent 功能時才佈建，不用則可刪&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ollama 本地模型&lt;/td>
 &lt;td>約 9G&lt;/td>
 &lt;td>改用雲端後閒置的大模型可刪，小的 embedding 模型常是依賴&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Xcode iOS DeviceSupport&lt;/td>
 &lt;td>約 4.5G&lt;/td>
 &lt;td>實體裝置接線除錯的符號快取，重連會自動重建&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Android NDK 的判讀要回到「誰在用它」：這次專案是 Flutter，NDK 版本由 &lt;code>flutter.ndkVersion&lt;/code> 決定，而不是專案自己 pin。查當前 Flutter 要求的版本後發現，本機裝的兩版都是舊 Flutter 留下的殘留，於是保留較新的一版、刪掉最舊的。判斷可不可刪的關鍵是先確認「現在到底用哪版」，而不是看修改日期就動手。&lt;/p></description><content:encoded><![CDATA[<p>一台原本還有約 30G 餘裕的 Mac，使用幾小時後空間全部歸零，清過系統各種 cache 也沒有改善。這次排查的重點是順序與判讀依據：用什麼順序找、用哪個數字判斷，最後刪了什麼反而次要。順序對了，就能避開兩個讓人空轉的陷阱。</p>
<p>最後把整套診斷固化成一個唯讀的 <code>disk-report</code> 腳本，往後同類情況可以一行指令重跑。</p>
<h2 id="先確認問題是真的滿還是浮動的假象">先確認問題是「真的滿」還是「浮動的假象」</h2>
<p>排查磁碟的第一步是分辨空間到底去哪：是被真實檔案佔走，還是被系統的快照與 purgeable（系統可隨時回收的緩衝空間）暫時佔住。這兩者的處理方式完全不同，先分清楚才不會白清。</p>
<p>在 APFS（Apple File System，macOS 的預設檔案系統）上，根目錄 <code>/</code> 是唯讀的系統封印卷，真正存放使用者資料的是 <code>/System/Volumes/Data</code>，而它們和其他卷（Preboot、Recovery、VM、模擬器 runtime）共用同一個 container（容器，APFS 管理空間的最上層單位）的空間池。判斷「還剩多少」要看整個 container 的可用空間，而不是單一卷的數字。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">df -h /System/Volumes/Data
</span></span><span class="line"><span class="ln">2</span><span class="cl">diskutil info /System/Volumes/Data <span class="p">|</span> grep -iE <span class="s2">&#34;Container Free Space|Container Total Space&#34;</span></span></span></code></pre></div><p>這次的結果是資料卷 100% 滿、整個 container 只剩約 591MB。確認確實滿載、不是顯示誤差，後面才值得花力氣找佔用大戶。</p>
<h2 id="空間掉了又回來的根因本地快照與-purgeable">「空間掉了又回來」的根因：本地快照與 purgeable</h2>
<p>空間在幾小時內反覆消長、清 cache 卻無效，最常見的原因是 Time Machine 的本地快照（local snapshots）加上 macOS 的 purgeable 空間，而不是某個看得見的檔案。這是排查時要先排除的一條線。</p>
<p>本地快照的運作方式是：Time Machine 啟用時，系統約每小時自動建立一張快照「凍結」當下狀態，好讓本地也能做時光機回溯。這些被凍結的資料，正是先前以為已刪除、卻怎麼清都不會釋放的空間。快照保留約 24 小時（Apple 的 thinning 策略，觀察值），或在磁碟空間壓力過大時提前清除；後者正是「過一陣子空間又回來」的來源。若從未設定 Time Machine，這條線可跳過——沒啟用就不會有 local snapshot。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">tmutil listlocalsnapshots /System/Volumes/Data</span></span></code></pre></div><p>這次查的時候快照數是 0，但這不代表它不是元兇——恰恰相反，是磁碟已經滿到讓系統把快照全數清光了。判讀訊號是：若這個指令平常列出多筆快照、且磁碟空間在數字上頻繁浮動，浮動量就來自這裡，跟手動清的 cache 無關。根治方向是把總用量降下來、讓磁碟保有餘裕，系統就不會一直貼著上限狂建狂清快照。</p>
<p>purgeable 是同一條線的另一半，但它沒有好用的精確讀數。<code>diskutil apfs list</code> 能看 container 層的概況，而 purgeable 主要由快照與系統快取構成、本來就會自己浮動。處理方式跟快照一樣：把總用量降下來、讓系統在空間有壓力時自行釋放，而不是找指令直接清它。「沒有直接讀數」本身就是判讀邊界——看到可用空間和「實際檔案總和」對不上時，差額多半就在這塊浮動緩衝，不必懷疑是哪個檔案在搞鬼。</p>
<h2 id="用實際佔用值找大戶避開-sparse-假大小">用實際佔用值找大戶，避開 sparse 假大小</h2>
<p>找佔用大戶要用 <code>du</code>（實際佔用的磁碟區塊）排序，不能依賴 <code>ls -l</code> 顯示、或 <code>find -size</code> 篩選所用的邏輯大小。對一般檔案兩者相同，但對 sparse 檔（稀疏檔）差距可以是好幾十倍，誤判會追錯目標。</p>
<p>這次就踩到這個陷阱。<code>find</code> 列出近期修改的大檔時，OrbStack（一套容器與 VM 執行環境）的虛擬磁碟映像顯示為 228G，看起來像頭號兇手；但用 <code>du</code> 一量，實際佔用只有 1.9G。同樣地，macOS Podcasts 在 tmp 塞的一堆 <code>.tmp.resize.img</code> 顯示有數十個檔，實際只佔 3.5M。這些都是 sparse 檔：宣告了很大的邏輯大小，但只有寫入過的區塊才真正佔磁碟。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 實際佔用（正確）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">du -sh ~/some/large.img
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 顯示大小（對 sparse 檔會嚴重高估，誤判用）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">ls -lh ~/some/large.img</span></span></code></pre></div><p>定位順序是由外往內逐層收斂：先看家目錄前 20 大，鎖定最大的子樹（這次是 <code>~/Library</code> 70G 左右），再往下展開 <code>~/Library/Application Support</code>、<code>~/Library/Containers</code>，直到找到具體的檔案或目錄。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">du -shx ~/* ~/.<span class="o">[</span>!.<span class="o">]</span>* 2&gt;/dev/null <span class="p">|</span> sort -rh <span class="p">|</span> head -20
</span></span><span class="line"><span class="ln">2</span><span class="cl">du -shx ~/Library/* 2&gt;/dev/null <span class="p">|</span> sort -rh <span class="p">|</span> head -12</span></span></code></pre></div><p><code>-x</code> 讓 <code>du</code> 不跨越檔案系統邊界，避免把掛載進來的唯讀卷（例如 iOS 模擬器 runtime）重複計入；<code>~/.[!.]*</code> 這個寫法只展開以單一點開頭的隱藏檔，排除掉 <code>.</code> 和 <code>..</code> 兩個會被一般 <code>.*</code> 誤抓進來、算出整個家目錄大小的假項目。</p>
<h2 id="這次找到的佔用大戶與處理">這次找到的佔用大戶與處理</h2>
<p>定位出來的大戶集中在開發工具鏈與閒置的本地資料，多數可逆、刪了之後需要時會自動重建或可重新下載。下面的項目與數字都是這台機器的實測，換一台機器組成會完全不同；值得帶走的是每一項背後的判讀問題，不是這份清單本身。具體刪除指令因工具而異（Android Studio GUI、<code>rm -rf</code>、<code>ollama rm</code>），本文只做診斷與定位，刪除操作留給各工具自身的文件。以下逐項說明判讀依據。</p>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>實際佔用</th>
          <th>處理判斷</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>舊版 Android NDK</td>
          <td>約 3G</td>
          <td>裝了多版、保留專案實際引用的版本，刪最舊</td>
      </tr>
      <tr>
          <td>用不到的 AVD + system-image</td>
          <td>約 3G</td>
          <td>一個 API 版本一組、停用的版本連 AVD 帶映像一起刪</td>
      </tr>
      <tr>
          <td>Claude 桌面 Cowork 沙箱 VM</td>
          <td>約 11G</td>
          <td>只在使用桌面 App 的本地 agent 功能時才佈建，不用則可刪</td>
      </tr>
      <tr>
          <td>ollama 本地模型</td>
          <td>約 9G</td>
          <td>改用雲端後閒置的大模型可刪，小的 embedding 模型常是依賴</td>
      </tr>
      <tr>
          <td>Xcode iOS DeviceSupport</td>
          <td>約 4.5G</td>
          <td>實體裝置接線除錯的符號快取，重連會自動重建</td>
      </tr>
  </tbody>
</table>
<p>Android NDK 的判讀要回到「誰在用它」：這次專案是 Flutter，NDK 版本由 <code>flutter.ndkVersion</code> 決定，而不是專案自己 pin。查當前 Flutter 要求的版本後發現，本機裝的兩版都是舊 Flutter 留下的殘留，於是保留較新的一版、刪掉最舊的。判斷可不可刪的關鍵是先確認「現在到底用哪版」，而不是看修改日期就動手。</p>
<p>Claude 桌面的 <code>vm_bundles</code> 是最大單一項目（11G）。它是桌面 App 的 Cowork 功能在本地沙箱 VM 裡執行程式用的根檔案系統映像。關鍵判讀是：它不是每次開 App 就重建——映像的修改日期停在數月前，是一次性佈建、之後沿用。只有實際使用 Cowork 沙箱時才會佈建和更新。所以對只用終端機 CLI、桌面 App 僅拿來聊天的人，這 11G 是純佔用，可以安全刪除；唯一後果是哪天實際開了 Cowork session，它會重新佈建。</p>
<p>剩下三項的判讀各有自己的關鍵問題。閒置的 AVD 與 system-image 是「一個 API 版本一組」的綁定，停用某個 Android 版本時要連 AVD 帶它依賴的系統映像一起刪，只刪一邊會留下半套。ollama 本地模型的判斷是「改用雲端後還會不會在本地跑」，閒置的大模型可刪，但小的 embedding 模型常被其他工具當依賴、刪了會牽連（ollama 模型的累積速度與專屬清理 idiom，見 <a href="/blog/llm/01-local-llm-services/hands-on/resource-management/" data-link-title="Hands-on：LLM 運行中 &#43; 結束的資源管理" data-link-desc="RAM / 磁碟 / port 三個 dimension 的觀察跟釋放、Ollama keep_alive 跟 ComfyUI 兩種 lifecycle 對比、實測釋放數字">本地 LLM 的資源管理</a>）。Xcode 的 iOS DeviceSupport 則是實體裝置接線除錯時產生的符號快取，可以放心刪——下次接上同一台裝置除錯時 Xcode 會自動重建。</p>
<p>這幾項合計回收約 17G，可用空間從約 591MB 拉回到 18G，磁碟脫離滿載。</p>
<h2 id="把診斷固化成-disk-report-腳本">把診斷固化成 disk-report 腳本</h2>
<p>一次性查完之後，把這套順序寫成腳本的價值是：下次同類情況不必重新回想指令與判讀順序，一行就能重跑，而且固定先看快照、再用實際佔用值，不會又掉進 sparse 假大小的陷阱。</p>
<p>腳本收在公開 repo <a href="https://github.com/tarrragon/scripts">tarrragon/scripts</a>，而不是放進某個專案的 <code>bin/</code>。它跟任何專案無關，連到個人 bin 才能在任何地方直接呼叫，也不會污染專案 repo。安裝方式是 clone 下來、把腳本本體 symlink 到 <code>~/.local/bin</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">git clone https://github.com/tarrragon/scripts.git ~/Projects/scripts
</span></span><span class="line"><span class="ln">2</span><span class="cl">ln -s ~/Projects/scripts/disk-report/disk-report ~/.local/bin/disk-report</span></span></code></pre></div><p>這一步預設 <code>~/.local/bin</code> 已在 PATH 上。若還沒設定，做法見 <a href="../macos_new_machine_setup/">macOS 新機基礎建設</a> 的對應項目。腳本刻意設計成唯讀：只報告、不刪除，刪什麼由人看完報告再決定。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">disk-report              <span class="c1"># 完整診斷：總覽 + 快照狀態 + 各層大戶 + 開發環境可清項</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">disk-report --growing    <span class="c1"># 只看過去 180 分鐘內長大的大檔（抓動態暴增最快）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">disk-report --growing <span class="m">60</span> <span class="c1"># 改成過去 60 分鐘</span></span></span></code></pre></div><p><code>--growing</code> 模式對應的是本文開頭那個「幾小時內暴增」的情境：當空間正在快速消失、想抓現行犯時，直接列出近期被寫入的大檔，比逐層 <code>du</code> 更快定位。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">find <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">&#34;</span> -type f -size +50M -mmin -180 2&gt;/dev/null <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  -exec du -h <span class="o">{}</span> <span class="se">\;</span> 2&gt;/dev/null <span class="p">|</span> sort -rh <span class="p">|</span> head -25</span></span></code></pre></div><p>50M 的下限是為了過濾日常小檔雜訊、鎖定單一大檔暴增；若懷疑是大量小檔累積吃空間（如快取碎片），這個門檻抓不到，要回逐層 <code>du</code> 看目錄總量。排序依據同樣是 <code>du</code> 的實際佔用值，而不是 <code>find -size</code> 的邏輯大小門檻，理由和前面一致：避免 sparse 檔的邏輯大小把排序帶歪。</p>
<h2 id="排查順序總結">排查順序總結</h2>
<p>這次的方法可以收斂成一條固定順序，往後遇到任何「磁碟莫名變滿」都先照這條走：</p>
<ol>
<li>先看 container 可用空間，確認是真滿還是顯示誤差。</li>
<li>再查本地快照與 purgeable，排除「掉了又回來」的浮動來源。</li>
<li>用 <code>du -shx</code> 由外往內逐層找大戶，全程以實際佔用值判斷，不信 <code>ls</code> / <code>find</code> 的顯示大小。</li>
<li>對每個大戶問「現在誰在用它」再決定刪不刪，可逆的優先清。</li>
<li>把整套順序固化成唯讀腳本，下次一行重跑。</li>
</ol>
<p>第 3 步若收斂到 <code>~/Library</code> 這種多個 App 共用的大目錄，按目錄統計只能看出 Caches、Containers 各多大，看不出是哪幾個 App 佔的。把這棵子樹再按 App 拆開的做法，見 <a href="../macos_app_footprint_report/">macOS App 聚合佔用報告</a>。</p>
]]></content:encoded></item><item><title>如果mac多桌面錯置如何重置</title><link>https://tarrragon.github.io/blog/other/%E5%A6%82%E6%9E%9Cmac%E5%A4%9A%E6%A1%8C%E9%9D%A2%E9%8C%AF%E7%BD%AE%E5%A6%82%E4%BD%95%E9%87%8D%E7%BD%AE/</link><pubDate>Thu, 18 Sep 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/other/%E5%A6%82%E6%9E%9Cmac%E5%A4%9A%E6%A1%8C%E9%9D%A2%E9%8C%AF%E7%BD%AE%E5%A6%82%E4%BD%95%E9%87%8D%E7%BD%AE/</guid><description>&lt;h2 id="what-happen-">What happen ?&lt;/h2>
&lt;p>Macos 不同螢幕的多桌面跑到錯誤的螢幕。&lt;/p>
&lt;h2 id="why-">Why ?&lt;/h2>
&lt;p>因為我會用並行裝置連接平板當成額外的螢幕，但是如果中斷再連接有時候會造成MACOS辨識多桌面出錯，導致無法正常切換桌面。&lt;/p>
&lt;h2 id="how-to-solve-this-">How to solve this ?&lt;/h2>
&lt;h3 id="方法一重置-mission-control-偏好設定">方法一：重置 Mission Control 偏好設定&lt;/h3>
&lt;p>這會清除所有顯示器的 Spaces 配置（等於重置多桌面設定）。&lt;/p>
&lt;p>打開 終端機（Terminal）&lt;/p>
&lt;p>輸入以下指令並按 Enter：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">defaults delete com.apple.spaces &lt;span class="c1">#清除設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">killall Dock &lt;span class="c1">#重啟 Dock&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Dock 重啟後，Mission Control 的桌面配置會被重置，所有螢幕會只剩下「一個桌面」。
注意：這會刪除所有已建立的桌面（Spaces），但不會影響 App 或檔案。&lt;/p>
&lt;h3 id="方法二暫時停用顯示器有單獨的空間">方法二：暫時停用「顯示器有單獨的空間」&lt;/h3>
&lt;p>macOS 會為每個螢幕建立自己的桌面空間，這個設定有時會造成錯亂。&lt;/p>
&lt;p>前往 系統設定 &amp;gt; 桌面與 Dock&lt;/p>
&lt;p>找到 「顯示器有單獨的空間」（英文為 Displays have separate Spaces）&lt;/p>
&lt;p>取消勾選&lt;/p>
&lt;p>登出並重新登入你的帳號&lt;/p>
&lt;p>重新勾選回來（如果你平常需要它）&lt;/p></description><content:encoded><![CDATA[<h2 id="what-happen-">What happen ?</h2>
<p>Macos 不同螢幕的多桌面跑到錯誤的螢幕。</p>
<h2 id="why-">Why ?</h2>
<p>因為我會用並行裝置連接平板當成額外的螢幕，但是如果中斷再連接有時候會造成MACOS辨識多桌面出錯，導致無法正常切換桌面。</p>
<h2 id="how-to-solve-this-">How to solve this ?</h2>
<h3 id="方法一重置-mission-control-偏好設定">方法一：重置 Mission Control 偏好設定</h3>
<p>這會清除所有顯示器的 Spaces 配置（等於重置多桌面設定）。</p>
<p>打開 終端機（Terminal）</p>
<p>輸入以下指令並按 Enter：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">defaults delete com.apple.spaces <span class="c1">#清除設定</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">killall Dock                     <span class="c1">#重啟 Dock</span></span></span></code></pre></div><p>Dock 重啟後，Mission Control 的桌面配置會被重置，所有螢幕會只剩下「一個桌面」。
注意：這會刪除所有已建立的桌面（Spaces），但不會影響 App 或檔案。</p>
<h3 id="方法二暫時停用顯示器有單獨的空間">方法二：暫時停用「顯示器有單獨的空間」</h3>
<p>macOS 會為每個螢幕建立自己的桌面空間，這個設定有時會造成錯亂。</p>
<p>前往 系統設定 &gt; 桌面與 Dock</p>
<p>找到 「顯示器有單獨的空間」（英文為 Displays have separate Spaces）</p>
<p>取消勾選</p>
<p>登出並重新登入你的帳號</p>
<p>重新勾選回來（如果你平常需要它）</p>
]]></content:encoded></item><item><title>Android 無線調試連接指南</title><link>https://tarrragon.github.io/blog/other/android-%E7%84%A1%E7%B7%9A%E8%AA%BF%E8%A9%A6%E9%80%A3%E6%8E%A5%E6%8C%87%E5%8D%97/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/other/android-%E7%84%A1%E7%B7%9A%E8%AA%BF%E8%A9%A6%E9%80%A3%E6%8E%A5%E6%8C%87%E5%8D%97/</guid><description>&lt;h2 id="前置條件">前置條件&lt;/h2>
&lt;ul>
&lt;li>Android 裝置系統版本 &lt;strong>Android 11 以上&lt;/strong>&lt;/li>
&lt;li>電腦與 Android 裝置連接在&lt;strong>同一個區域網路&lt;/strong>&lt;/li>
&lt;li>已安裝 ADB 工具（通常隨 Android Studio 安裝）&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="步驟一啟用無線偵錯">步驟一：啟用無線偵錯&lt;/h2>
&lt;ol>
&lt;li>進入 Android 裝置的 &lt;strong>設定 → 開發人員選項&lt;/strong>&lt;/li>
&lt;li>開啟 &lt;strong>無線偵錯（Wireless debugging）&lt;/strong>&lt;/li>
&lt;li>在彈出的對話框中選擇「允許」&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="步驟二配對裝置首次連接需要">步驟二：配對裝置（首次連接需要）&lt;/h2>
&lt;h3 id="在-android-裝置上">在 Android 裝置上&lt;/h3>
&lt;ol>
&lt;li>點擊 &lt;strong>無線偵錯&lt;/strong> 進入詳細頁面&lt;/li>
&lt;li>點擊 &lt;strong>使用配對碼配對裝置&lt;/strong>&lt;/li>
&lt;li>記下畫面上顯示的：
&lt;ul>
&lt;li>&lt;strong>IP 位址與配對端口&lt;/strong>（例如：&lt;code>192.168.1.100:45213&lt;/code>）&lt;/li>
&lt;li>&lt;strong>配對碼&lt;/strong>（6 位數字）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="在電腦終端機上">在電腦終端機上&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">adb pair &amp;lt;IP&amp;gt;:&amp;lt;配對端口&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">adb pair 192.168.1.100:45213&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>輸入配對碼後，看到以下訊息表示配對成功：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Successfully paired to 192.168.1.100:45213&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>：配對碼有時效性，產生後請盡快輸入&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="步驟三連接裝置">步驟三：連接裝置&lt;/h2>
&lt;h3 id="在-android-裝置上-1">在 Android 裝置上&lt;/h3>
&lt;p>回到&lt;strong>無線偵錯主頁面&lt;/strong>，查看顯示的 &lt;strong>IP 位址與連接端口&lt;/strong>（與配對端口不同）&lt;/p>
&lt;p>例如：&lt;code>192.168.1.100:38745&lt;/code>&lt;/p>
&lt;h3 id="在電腦終端機上-1">在電腦終端機上&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">adb connect &amp;lt;IP&amp;gt;:&amp;lt;連接端口&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">adb connect 192.168.1.100:38745&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>看到以下訊息表示連接成功：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">connected to 192.168.1.100:38745&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="步驟四驗證連接">步驟四：驗證連接&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">adb devices&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或使用 Flutter：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">flutter devices&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應該能看到無線連接的裝置列表。&lt;/p>
&lt;hr>
&lt;h2 id="常用指令速查">常用指令速查&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>用途&lt;/th>
 &lt;th>指令&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>配對裝置&lt;/td>
 &lt;td>&lt;code>adb pair &amp;lt;IP&amp;gt;:&amp;lt;配對端口&amp;gt;&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>連接裝置&lt;/td>
 &lt;td>&lt;code>adb connect &amp;lt;IP&amp;gt;:&amp;lt;連接端口&amp;gt;&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>查看已連接裝置&lt;/td>
 &lt;td>&lt;code>adb devices&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>中斷連接&lt;/td>
 &lt;td>&lt;code>adb disconnect &amp;lt;IP&amp;gt;:&amp;lt;連接端口&amp;gt;&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>重啟 ADB 服務&lt;/td>
 &lt;td>&lt;code>adb kill-server &amp;amp;&amp;amp; adb start-server&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="疑難排解">疑難排解&lt;/h2>
&lt;h3 id="配對失敗">配對失敗&lt;/h3>
&lt;ul>
&lt;li>確認電腦與裝置在同一區網&lt;/li>
&lt;li>檢查路由器是否開啟 AP 隔離功能&lt;/li>
&lt;li>暫時關閉電腦防火牆測試&lt;/li>
&lt;/ul>
&lt;h3 id="連接後無法使用">連接後無法使用&lt;/h3>
&lt;ul>
&lt;li>確認使用的是「連接端口」而非「配對端口」&lt;/li>
&lt;li>嘗試重啟 ADB 服務：&lt;code>adb kill-server &amp;amp;&amp;amp; adb start-server&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="裝置離線或消失">裝置離線或消失&lt;/h3>
&lt;ul>
&lt;li>無線偵錯可能因裝置休眠而中斷&lt;/li>
&lt;li>重新執行 &lt;code>adb connect&lt;/code> 即可恢復&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="流程圖">流程圖&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">┌─────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">│ 啟用無線偵錯 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">└────────┬────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">┌─────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">│ 取得配對資訊 │ ← 配對端口 + 配對碼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">└────────┬────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">┌─────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ adb pair │ ← 首次連接需要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">└────────┬────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">┌─────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">│ 取得連接端口 │ ← 無線偵錯主頁面
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">└────────┬────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">┌─────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">│ adb connect │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">└────────┬────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">┌─────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">│ 連接完成 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">└─────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr></description><content:encoded><![CDATA[<h2 id="前置條件">前置條件</h2>
<ul>
<li>Android 裝置系統版本 <strong>Android 11 以上</strong></li>
<li>電腦與 Android 裝置連接在<strong>同一個區域網路</strong></li>
<li>已安裝 ADB 工具（通常隨 Android Studio 安裝）</li>
</ul>
<hr>
<h2 id="步驟一啟用無線偵錯">步驟一：啟用無線偵錯</h2>
<ol>
<li>進入 Android 裝置的 <strong>設定 → 開發人員選項</strong></li>
<li>開啟 <strong>無線偵錯（Wireless debugging）</strong></li>
<li>在彈出的對話框中選擇「允許」</li>
</ol>
<hr>
<h2 id="步驟二配對裝置首次連接需要">步驟二：配對裝置（首次連接需要）</h2>
<h3 id="在-android-裝置上">在 Android 裝置上</h3>
<ol>
<li>點擊 <strong>無線偵錯</strong> 進入詳細頁面</li>
<li>點擊 <strong>使用配對碼配對裝置</strong></li>
<li>記下畫面上顯示的：
<ul>
<li><strong>IP 位址與配對端口</strong>（例如：<code>192.168.1.100:45213</code>）</li>
<li><strong>配對碼</strong>（6 位數字）</li>
</ul>
</li>
</ol>
<h3 id="在電腦終端機上">在電腦終端機上</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb pair &lt;IP&gt;:&lt;配對端口&gt;</span></span></code></pre></div><p>範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb pair 192.168.1.100:45213</span></span></code></pre></div><p>輸入配對碼後，看到以下訊息表示配對成功：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Successfully paired to 192.168.1.100:45213</span></span></code></pre></div><blockquote>
<p><strong>注意</strong>：配對碼有時效性，產生後請盡快輸入</p></blockquote>
<hr>
<h2 id="步驟三連接裝置">步驟三：連接裝置</h2>
<h3 id="在-android-裝置上-1">在 Android 裝置上</h3>
<p>回到<strong>無線偵錯主頁面</strong>，查看顯示的 <strong>IP 位址與連接端口</strong>（與配對端口不同）</p>
<p>例如：<code>192.168.1.100:38745</code></p>
<h3 id="在電腦終端機上-1">在電腦終端機上</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb connect &lt;IP&gt;:&lt;連接端口&gt;</span></span></code></pre></div><p>範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb connect 192.168.1.100:38745</span></span></code></pre></div><p>看到以下訊息表示連接成功：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">connected to 192.168.1.100:38745</span></span></code></pre></div><hr>
<h2 id="步驟四驗證連接">步驟四：驗證連接</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb devices</span></span></code></pre></div><p>或使用 Flutter：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">flutter devices</span></span></code></pre></div><p>應該能看到無線連接的裝置列表。</p>
<hr>
<h2 id="常用指令速查">常用指令速查</h2>
<table>
  <thead>
      <tr>
          <th>用途</th>
          <th>指令</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>配對裝置</td>
          <td><code>adb pair &lt;IP&gt;:&lt;配對端口&gt;</code></td>
      </tr>
      <tr>
          <td>連接裝置</td>
          <td><code>adb connect &lt;IP&gt;:&lt;連接端口&gt;</code></td>
      </tr>
      <tr>
          <td>查看已連接裝置</td>
          <td><code>adb devices</code></td>
      </tr>
      <tr>
          <td>中斷連接</td>
          <td><code>adb disconnect &lt;IP&gt;:&lt;連接端口&gt;</code></td>
      </tr>
      <tr>
          <td>重啟 ADB 服務</td>
          <td><code>adb kill-server &amp;&amp; adb start-server</code></td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="疑難排解">疑難排解</h2>
<h3 id="配對失敗">配對失敗</h3>
<ul>
<li>確認電腦與裝置在同一區網</li>
<li>檢查路由器是否開啟 AP 隔離功能</li>
<li>暫時關閉電腦防火牆測試</li>
</ul>
<h3 id="連接後無法使用">連接後無法使用</h3>
<ul>
<li>確認使用的是「連接端口」而非「配對端口」</li>
<li>嘗試重啟 ADB 服務：<code>adb kill-server &amp;&amp; adb start-server</code></li>
</ul>
<h3 id="裝置離線或消失">裝置離線或消失</h3>
<ul>
<li>無線偵錯可能因裝置休眠而中斷</li>
<li>重新執行 <code>adb connect</code> 即可恢復</li>
</ul>
<hr>
<h2 id="流程圖">流程圖</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">┌─────────────────┐
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">│  啟用無線偵錯   │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">└────────┬────────┘
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">         ▼
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">┌─────────────────┐
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│  取得配對資訊   │ ← 配對端口 + 配對碼
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">└────────┬────────┘
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">         ▼
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">┌─────────────────┐
</span></span><span class="line"><span class="ln">10</span><span class="cl">│   adb pair      │ ← 首次連接需要
</span></span><span class="line"><span class="ln">11</span><span class="cl">└────────┬────────┘
</span></span><span class="line"><span class="ln">12</span><span class="cl">         ▼
</span></span><span class="line"><span class="ln">13</span><span class="cl">┌─────────────────┐
</span></span><span class="line"><span class="ln">14</span><span class="cl">│  取得連接端口   │ ← 無線偵錯主頁面
</span></span><span class="line"><span class="ln">15</span><span class="cl">└────────┬────────┘
</span></span><span class="line"><span class="ln">16</span><span class="cl">         ▼
</span></span><span class="line"><span class="ln">17</span><span class="cl">┌─────────────────┐
</span></span><span class="line"><span class="ln">18</span><span class="cl">│  adb connect    │
</span></span><span class="line"><span class="ln">19</span><span class="cl">└────────┬────────┘
</span></span><span class="line"><span class="ln">20</span><span class="cl">         ▼
</span></span><span class="line"><span class="ln">21</span><span class="cl">┌─────────────────┐
</span></span><span class="line"><span class="ln">22</span><span class="cl">│    連接完成     │
</span></span><span class="line"><span class="ln">23</span><span class="cl">└─────────────────┘</span></span></code></pre></div><hr>
]]></content:encoded></item></channel></rss>