事情是這樣的。我需要寫一個腳本去自動讀取 OneDrive 目錄裡的檔案,做一些資料處理後再寫回去。聽起來很簡單對吧?不就是 cat 讀取、python open() 處理、再 cp 寫回去嗎?
結果一跑就炸了:
1 | $ cat ~/Library/CloudStorage/OneDrive-xxx/some-file.xlsx |
好,cat 不行,那試試 cp⋯⋯全死。連 Python 的 open() 也不行。整個 debug 過程讓我懷疑人生。
為什麼會這樣?
後來查了 Apple 的文件才發現,問題出在 macOS 的 File Provider 架構。
從 macOS 11 Big Sur 開始,Apple 引入了新的 File Provider API 來管理雲端儲存(iCloud Drive、OneDrive、Dropbox 等)。這些檔案不是「真的」存在本機,而是以 on-demand 串流 的方式呈現。當你訪問一個檔案時,File Provider 需要:
- 檢查檔案是否在本地有快取
- 如果沒有,從雲端下載
- 協調多個程序同時訪問的衝突
- 同步回寫變更到雲端
傳統的 Unix 工具(cat、cp、dd)和直接的 open() 呼叫,完全繞過了這套協調機制。它們試圖直接讀取底層檔案,但 File Provider 不讓——於是就報 Resource deadlock avoided。
這不是 bug,是 by design。Apple 就是要你乖乖用他們提供的 API。
正確的解法:NSFileCoordinator
macOS 提供了 NSFileCoordinator 這個 class,專門用來跟 File Provider 協調檔案訪問。它的概念是:
「嘿,File Provider 大哥,我要讀這個檔案了,麻煩你幫我準備好,順便告訴我真的在哪裡。」
要用這個 API,只能透過 Swift 或 Objective-C。我寫了兩個 bash wrapper script,讓命令行也能用:
讀取腳本:onedrive-read.sh
1 |
|
用法:
1 | # 讀取到 stdout |
寫入腳本:onedrive-write.sh
1 |
|
用法:
1 | bash ~/clawd/scripts/onedrive-write.sh /tmp/processed.xlsx ~/Library/CloudStorage/OneDrive-xxx/file.xlsx |
為什麼不用 brctl?
你可能聽說過 brctl download 這個命令(用來下載 iCloud 檔案)。遺憾的是,brctl 只支援 iCloud Drive,不支援 OneDrive。每個雲端服務提供商實作 File Provider 的方式不同,OneDrive 有自己的機制。
其他注意事項
Python 也不行嗎?
對,Python 的 open() 直接呼叫底層 system call,同樣會觸發 deadlock。你必須:
- 用上面的 script 讀取到临时檔案
- Python 處理临时檔案
- 寫回临时檔案
- 用 write script 寫回 OneDrive
效能影響?
有,但不大。NSFileCoordinator 的協調過程會增加一些 overhead,但對於自動化腳本來說,正確性比那幾毫秒重要多了。
iCloud 也適用嗎?
對,iCloud Drive 也是用 File Provider,同樣的問題、同樣的解法。只是 iCloud 多了 brctl 這個後門。
結論
這個坑踩得有點冤。表面上看起來是普通的檔案操作,背後卻是 macOS 整套雲端檔案同步架構在運作。Apple 的意圖是好的——避免衝突、保證一致性——但對於習慣 Unix「一切皆檔案」哲學的開發者來說,這種抽象洩漏(leaky abstraction)真的很痛。
關鍵教訓:
- 在 macOS 上操作 OneDrive/iCloud 檔案,不要用傳統 Unix 工具
- 用 NSFileCoordinator 跟 File Provider 協調
- bash + Swift inline script 是可行的 workaround
如果這篇能幫你少踩一個坑,那我的血淚就沒白流了。😂