包包baobaolin.com
date
entry
053
topic
tooling
rev
—

macOS 版 Claude Code 裡的 find 是 bfs:-newermt "-5 minutes" 回 Invalid timestamp,接 wc -l 數出來是 0

10 月 18 日凌晨 1 點 10 分左右(bfs 錯誤訊息裡印的範例時間是 01:10:56),我想數 Go 建置快取裡五分鐘內改過的檔案,下了 find … -newermt "-5 minutes" | wc -l,得到 0。後來檢查快取,裡面有修改時間是 01:06:07 的檔案。Claude Code 裡的 find 其實是內建的 bfs,它不接受這種相對時間;錯誤訊息寫在 stderr,wc 只收到空的 stdout,數出 0。

在 macOS 上找最近改過的檔案,我習慣寫 find 目錄 -newermt "-5 minutes"。macOS 的 /usr/bin/find 用自己的日期解析,接受「負五分鐘」「昨天」這種寫法,之前一直用得好好的。

但 10 月 18 日凌晨在 Claude Code 的 Bash 工具裡下同一個指令,接 | wc -l 得到的是 0,上面還有幾行 bfs: error。042 寫過這個環境的 grep 是內建的 ugrep,也提到 find 同理是內建的 bfs,但那篇的實測集中在 grep,沒有測 find。這篇補 find:它長什麼樣、和 macOS find 不一樣的四個地方、哪些情況會用回系統的 find,最後是我現在怎麼寫。以下都是 Claude Code 2.1.302、macOS 原生版、zsh 工作階段裡的結果。

find 是一個函式

type find 說它是函式,which find(zsh 會印出函式本體)看得到內容,節錄:

$ type find
find is a shell function from /Users/…/.claude/shell-snapshots/snapshot-zsh-….sh
$ which find
find () {
	…
	if [[ -n ${ZSH_VERSION:-} ]]
	then
		ARGV0=bfs "$_cc_bin" -S dfs -regextype findutils-default ${1+"$@"}
	…
}

$_cc_bin 是 Claude Code 自己的執行檔,以 bfs 的名字啟動,前面固定加 -S dfs -regextype findutils-default。用同樣的方式問版本,ARGV0=bfs claude --version 回 bfs 4.1.1。Claude Code 的 changelog 2.1.117 和工具文件(Tools reference 的 Glob 一節)都寫了這件事:macOS 和 Linux 的原生版本裡,find 和 grep 跑的是內建的 bfs 和 ugrep。

差異一:-newermt 的時間格式

我在暫存目錄裡對同一個目錄,用六種時間寫法各跑一次,記結束碼:

寫法                 bfs   /usr/bin/find
"-5 minutes"          1       0
"5 minutes ago"       1       0
"yesterday"           1       0
"2026-10-17"          0       0
"2026-10-17 10:00"    0       0
"@1792200000"         0       1

bfs 不認的時候,錯誤訊息很清楚,還列出它接受的格式(節錄,範例時間會隨執行時間變):

bfs: error: Invalid timestamp.

Supported timestamp formats are ISO 8601-like, e.g.

  - 2026-10-18
  - 2026-10-18T01:10:56
  - 2026-10-18T01:10:56+08:00
  - 2026-10-17T17:10:56Z

問題在這些訊息寫在 stderr。管線只把 stdout 交給 wc,stdout 什麼都沒有,wc 誠實地數出 0;錯誤訊息還是會印在畫面上,但如果你只讀最後那個數字,整條指令看起來就像「沒有符合的檔案」。要是在 find 那一端加了 2>/dev/null(find … 2>/dev/null | wc -l),連錯誤訊息都看不到。bfs 4.1.1 的手冊寫 -newerXt 的參考值是「ISO 8601-style timestamp」,這次測的三種相對時間都不接受;反過來,@ 加 Unix 秒數只有 bfs 接受,macOS 的 find 不接受。這六個寫法裡,兩邊都接受的只有那兩個絕對日期和時間。

差異二:-regex 是 GNU 的寫法

函式固定加了 -regextype findutils-default。macOS 的 find 預設是 POSIX 基本正規表示式,+ 和 \| 是普通字元。在同一棵測試目錄樹上:

-regex '.*/[0-9]+'          bfs: ./12 ./a/1 ./c/3 ./a/b/2   /usr/bin/find: (沒有)
-regex '.*/\(a\|c\)'        bfs: ./a ./c ./a/b/c            /usr/bin/find: (沒有)
find -E . -regex '.*/(a|c)' /usr/bin/find: ./a ./a/b/c ./c

同一個 pattern,在 Claude Code 裡找到 4 個,換到終端機裡找到 0 個,而且兩邊都不報錯。findutils-default 這個值,bfs 4.1.1 的手冊頁(docs/bfs.1)列出的 -regextype 清單裡沒有,但 -regextype help 的輸出有,說明是「Like GNU find」。從結果看也一致:+ 和 \| 都是運算子。

差異三:-printf 和輸出順序

-printf 是 GNU find 的動作,macOS 的 find 沒有。find . -name z -printf '%s %p\n' 在 Claude Code 裡印出 0 ./z,到終端機是 find: -printf: unknown primary or operator。在 Claude Code 裡寫好、測過的指令,貼進部署腳本可能就壞了。

輸出順序也不一樣。同一棵測試樹:

bfs:           . ./1a ./z ./a ./c ./12 ./a/1 ./a/b ./c/3 ./a/b/c ./a/b/2 ./a/b/c/d ./a/b/c/d/x
/usr/bin/find: . ./1a ./z ./a ./a/1 ./a/b ./a/b/c ./a/b/c/d ./a/b/c/d/x ./a/b/2 ./c ./c/3 ./12

在這棵樹上,bfs 先把一層列完再往下;macOS 的 find 遇到目錄就先走完它。奇怪的是函式明明加了 -S dfs(手冊寫的是深度優先),我另外用 -S bfs、-S dfs -j1 跑,三種輸出完全一樣。為什麼 -S dfs 沒有改變輸出順序,我沒有查到原因。實際的影響是:依賴輸出順序的寫法在兩邊可能拿到不同的項目。例如 find . -type f | head -3,這棵樹上 bfs 給 ./1a ./z ./12,macOS find 給 ./1a ./z ./a/1。兩邊的順序都沒有排序,要穩定就自己 sort。

什麼時候會用回系統的 find

和 042 裡的 grep 一樣,find 是 shell 函式,只有在這個帶著函式定義的 shell 裡呼叫時才生效。經過 timeout、xargs 這類「再去執行一個程式」的包裝,找的是 PATH 上的執行檔;這次的 PATH 下是 /usr/bin/find:

$ timeout 5 find . -maxdepth 0 -newermt "-5 minutes"; echo rc=$?
.
rc=0
$ echo . | xargs -I{} find {} -maxdepth 0 -newermt "-5 minutes"; echo rc=$?
.
rc=0

(會印出 .,是因為當時這個目錄五分鐘內才改過。)同一個指令,前面多一個 timeout 5,結果就從錯誤變成成功。所以在 Claude Code 裡看到的行為,不一定是你腳本跑起來的行為。

我現在怎麼寫

要在這台 Mac 的終端機、部署腳本和 Claude Code 裡都一樣的指令,我直接寫 /usr/bin/find。command find 也能略過函式,但它找的是 PATH 上的第一個 find,換一台裝了 GNU find 的機器就可能不同。時間條件改用兩邊都接受的絕對時間,先算好再傳進去:find 目錄 -newermt "$(date -v-5M '+%Y-%m-%d %H:%M:%S')"(這是 macOS 的 date 寫法)。只需要比對檔名時,我用 -name 代替 -regex(-name 比的是檔名的 glob,-regex 比的是整條路徑);真的要用 -regex,先在兩邊各跑一次對結果。

回到開頭那個 0:如果我沒有注意到上面那幾行錯誤,可能就把「快取裡五分鐘內沒有新檔案」當成結論了。對 find 接管線的指令,我現在會先單獨跑一次看結束碼,或者開 pipefail,讓管線前段的失敗反映在整條指令的結束碼上。

如果只記得一件事

Claude Code 2.1.302 macOS 原生版的 Bash 工具裡,find 是內建的 bfs:-newermt 不接受這次測的相對時間,-regex 是 GNU 風格,-printf 可用,輸出順序也不同;錯誤寫在 stderr,只看 wc -l 的數字會以為沒有符合的檔案。要和這台機器的終端機、腳本一致,寫 /usr/bin/find。

修訂紀錄

  1. 首次發布