↓ 跳过正文

fd:用符合直觉的默认行为完成日常文件搜索

·4413 字·9 分钟·
目录
modern-cli - 这篇文章属于一个选集。
§ 4: 本文

阅读信息

  • 内容类型:教程
  • 难度:入门
  • 适合谁:希望在 Linux、WSL2 与远程服务器上提升命令行效率的开发者。
  • 读完后:用 fd 按名称、扩展名、类型、路径和隐藏状态查找文件,并安全地把结果交给其他命令。
  • 依据版本:fd 10.5.0
  • 验证日期:2026-09-11

find 的语法是在向内核文件系统表达逻辑谓词:

find . -type f -iname '*trace*'

在日常开发场景中,读者的真实需求通常只是“在当前工程里找到某个文件”:

fd trace
fd trace src
fd -e cpp

fd 把日常交互改写为贴近直觉的默认值:默认并行遍历目录树、将搜索词作为正则表达式匹配文件名、跳过隐藏项并自动遵守 .gitignore。这些默认行为显著减少了日常敲击命令的摩擦,但它并不试图覆盖 find 的全部能力。权限掩码、时间戳精确区间、inode 检索以及 POSIX 兼容脚本,依然属于 find 的工作领地。

本文核验版本为 fd 10.5.0。在 Ubuntu 24.04 与 Debian 系列发行版中,安装名和命令调用存在历史包名差异。

fd 文件搜索与动作流程:指定名称和目录、按类型与扩展名收窄、检查隐藏项和 ignore 边界、选择 -x 逐项或 -X 批量执行,并用 NUL 记录安全接入 fzf
图:先限定范围并打印检查候选,再选择逐项或批量动作;特殊文件名在整条流水线中使用 NUL 协议。

1. 安装与命令名解析
#

在 Debian 与 Ubuntu 官方仓库中,由于同名软件的历史冲突,软件包被命名为 fd-find:

sudo apt install fd-find

安装后,系统提供的可执行文件通常命名为 fdfind:

fdfind --version

若希望使用标准的 fd 命令名,不要直接在 /usr/bin 中创建系统级软链接以防覆盖系统未来包。若 ~/.local/bin 已包含在当前用户的 PATH 环境变量中,推荐创建用户级软链接:

mkdir -p ~/.local/bin
ln -s "$(command -v fdfind)" ~/.local/bin/fd

在终端中核验命令解析与版本:

command -v fdfind
command -v fd
fd --version

如果使用官方独立二进制包、Cargo 编译安装(cargo install fd-find)或 mise/asdf 等版本管理器,通常会直接安装上游命令名 fd。本文后续统一使用 fd 进行说明。

2. 参数结构与核心搜索维度
#

参数模型:[pattern] 与 [path]…
#

理解 fd 的关键在于掌握其参数位置模型:

fd [OPTIONS] [pattern] [path]...

第一个非选项参数始终被解析为 pattern(匹配模式),后续参数才被解析为搜索根路径 path。如果没有指定搜索路径,默认在当前工作目录递归搜索。

常见错误是在指定扩展名搜索特定目录时省略 pattern:

fd -e json research/modern-cli/fixture

这会触发错误提示:

[fd error]: The search pattern 'research/modern-cli/fixture' contains a path-separation character and will not lead to any search results.

原因在于 fd 将 research/modern-cli/fixture 识别为了模式,而默认情况下模式只与文件名(basename)比对。当模式包含路径分隔符 / 时必须明确告知 fd 意图:使用 . 表示匹配任意文件名的模式,或者通过 --search-path 显式声明根路径:

# 推荐:使用 '.' 作为任意匹配模式,随后指定目录
fd -e json . research/modern-cli/fixture

# 或者使用长选项显式指定路径
fd -e json --search-path research/modern-cli/fixture

在测试夹具中执行输出:

research/modern-cli/fixture/data/results.json

模式匹配:正则、固定字符串与 Glob
#

fd 默认使用正则表达式匹配文件名。若查找包含特殊符号的纯文本文件名,使用 -F(--fixed-strings):

fd -F 'config[old]'

若习惯通配符形式,可以使用 -g(--glob):

fd -g '*.toml'

如果需要模式匹配包含目录结构的完整路径,加上 -p(--full-path):

fd -p 'research/.*/results\.json'

扩展名与条目类型
#

指定文件扩展名无需手动书写正则前缀:

fd -e cpp
fd -e yaml -e yml
fd trace -e cpp

注意:-e 过滤只检查名称末尾是否符合扩展名,如果某个目录恰好以 .json 结尾,也会被匹配出来。若仅需要匹配普通文件,应配合 -t f 显式限定类型:

fd -t f -e json . research/modern-cli/fixture

常用类型选项包括:

  • -t f(--type file):普通文件;
  • -t d(--type directory):目录;
  • -t l(--type symlink):符号链接;
  • -t x(--type executable):可执行文件;
  • -t e(--type empty):空文件或空目录。

大小写敏感度
#

fd 默认采用 smart case 策略:

  • 搜索模式全为小写时,执行大小写不敏感搜索;
  • 搜索模式中只要包含任意大写字符,自动切换为大小写敏感搜索。

需要强制控制时:

fd -i readme    # 强制忽略大小写(匹配 README.md, readme.txt)
fd -s README    # 强制区分大小写(仅匹配大写 README)

隐藏文件与 Ignore 规则的分层
#

fd 默认跳过两类条目:

  1. 隐藏文件与隐藏目录(以 . 开头);
  2. 被 .gitignore、.ignore、.fdignore 或全局 gitignore 规则匹配的路径。

放宽过滤条件需要区分两者的边界:

  • -H(--hidden):包含隐藏项,但依然严格遵守 ignore 规则;
  • -I(--no-ignore):不遵守 ignore 规则,但依然跳过隐藏项;
  • -u(--unrestricted):等价于 -H -I,同时搜索隐藏项和被 ignore 的文件。

在测试夹具中验证分层过滤:

# 只开启隐藏项,能搜到 .env.example,但排除被 gitignore 的 generated.txt
fd -H env research/modern-cli/fixture

# 忽略 ignore 规则,能搜到 build/generated.log 与 ignored/generated.txt
fd -I generated research/modern-cli/fixture

# 两者均放开
fd -u generated research/modern-cli/fixture

如需在放宽过滤后针对特定目录排除噪声,可以使用 -E(--exclude,支持多次指定):

fd -H -E .git -E node_modules -E target config

3. -x 与 -X:逐项并行与批量执行
#

fd 内置了类似 find -exec 的执行机制,分为 -x(--exec)与 -X(--exec-batch)两种截然不同的执行模型。

-x(逐项执行)
#

-x 为检索出的每个目标分别调用一次外部命令。默认情况下,fd 会利用多核 CPU 并行派发进程:

fd -e sh . research/modern-cli/fixture -x wc -l

执行模型等价于:

wc -l research/modern-cli/fixture/script.sh
wc -l research/modern-cli/fixture/expected/check-quoted.sh
wc -l research/modern-cli/fixture/src/check.sh

因为并行执行且顺序无序,绝对不能依赖前一个文件的执行输出作为后一个文件的前置条件。若需要顺序逐项执行,必须显式限制线程数:--threads=1。

-X(批量执行)
#

-X 把所有检索出的候选文件汇总,作为参数一次性传递给外部命令(类似于 xargs):

fd -e sh . research/modern-cli/fixture -X wc -l

执行模型等价于:

wc -l file1.sh file2.sh file3.sh

终端实际输出:

  5 research/modern-cli/fixture/src/check.sh
  8 research/modern-cli/fixture/script.sh
  5 research/modern-cli/fixture/expected/check-quoted.sh
 18 total

wc -l 只启动了一次,并输出了合计行数 18 total。如果候选数量超过系统单条命令长度上限,fd 会自动切分为多批执行,也可以通过 --batch-size <NUM> 手动指定批次大小。

占位符与参数终止陷阱
#

在 -x 与 -X 中可以使用结构化占位符:

  • {}:完整相对路径(例如 src/utils/calc.py);
  • {/}:基本文件名(basename,例如 calc.py);
  • {//}:所在父目录(例如 src/utils);
  • {.}:去除扩展名的路径(例如 src/utils/calc);
  • {/.}:去除扩展名的基本文件名(例如 calc)。

若未书写任何占位符,fd 会在命令末尾自动追加 {}。

重要参数陷阱: 命令行中紧跟在 -x 或 -X 之后的所有参数都会被解释为外部命令的组成部分,而不再是 fd 的参数。

例如若将搜索根目录放在 -X 之后:

# 错误写法:fixture 目录会被当作 wc 的待读取文件
fd -e sh -X wc -l research/modern-cli/fixture

这会导致 fd 在当前目录搜索所有 .sh 文件,同时把 research/modern-cli/fixture 传递给 wc 处理报错。正确的做法是将目录放在 -X 前面,或者使用 \; 明确结束命令模板:

# 正确写法 1:将搜索根放在 -X 之前
fd -e sh . research/modern-cli/fixture -X wc -l

# 正确写法 2:用分号转义结束外部命令模板
fd -e sh -X wc -l research/modern-cli/fixture \;

在执行具有破坏性修改的操作(如删除、覆盖)前,务必先用无副作用命令预览候选清单:

fd -e tmp . -x printf '待清理文件: %s\n' {}

确认匹配边界安全后,再切换为真正的动作命令。

4. 与 rg、fzf 组合时的文件名边界
#

在 Unix 系统中,换行符、空格与破折号是合法的文件名字符。构建自动化流水线时必须防范文件名注入与路径解析截断。

与 ripgrep 组合
#

fd 专注于找文件,rg 专注于搜文本。二者通过 -X 联动兼具速度与精确度:

fd -e py . research/modern-cli/fixture -X rg -n 'def '

相比 rg --glob,这种方式可以借助 fd 的类型筛选、空文件排除和动态条件灵活圈定目标集。

与 fzf 交互联动
#

通过管道将结果交给 fzf 进行交互式选择:

fd -t f | fzf

当文件树可能包含空格或换行等特殊字符时,标准换行分隔会导致下游工具切词错误。稳健的处理方案是全程采用 NUL(\0)字节流协议:

fd -t f -0 | fzf --read0 --print0

注意:fd 在使用 -0、-x 或 -X 时,默认会在相对路径前保留 ./ 前缀,以防以连字符 - 开头的文件名被下游工具误认为是命令参数。如果传递给 fzf 希望展示更干净的相对路径,可以使用 --strip-cwd-prefix。

5. 工具分工:fd、find 与 locate 的场景边界
#

fd 并不能也不应该完全淘汰 find 或 locate,三者解决不同的工程问题:

对比维度fdfindlocate
核心机制多线程实时遍历,默认 ignore单线程实时遍历文件系统谓词查询预建数据库(updatedb)
性能特征本地 SSD 项目树极快中等,受单线程与 POSIX 约束全盘秒级返回,非实时
默认行为忽略隐藏文件与 .gitignore遍历一切路径,不跳过任何文件依赖数据库最近更新状态
适用场景开发者在代码库与日常目录交互运维审计、复杂逻辑判断、POSIX 脚本在全系统跨多盘查找未知文件

必须使用 find 的场景
#

当涉及复杂的文件系统元数据、POSIX 脚本规范或系统审计时,应当坚决使用 find:

# 按最后修改时间(30天前)、文件大小(>100M)与属主精确筛选
find /var/log -type f -mtime +30 -size +100M -user root

# 按八进制权限位匹配并执行安全删除
find /tmp -type f -perm 0777 -exec rm -f {} +

使用 locate(如 plocate / mlocate)的场景
#

需要在整台主机的数十万目录中寻找某个系统库或头文件,且可以接受索引更新存在延迟时,locate 无需遍历磁盘,速度远高于任何实时搜索工具:

locate libssl.so

6. 运行资源、故障排查与防护
#

fd 是无守护进程(daemonless)的纯命令行工具,进程退出后立即释放全部内存与句柄。但在特定环境下需要注意系统资源开销:

  1. 并行 I/O 压力:fd 默认根据 CPU 核心数启动线程并发扫描。在本地 NVMe SSD 上性能优异;但在机械硬盘(HDD)、NFS 网络存储或 WSL2 挂载的宿主机盘符(如 /mnt/c)上,大量并发跨目录寻道会导致 I/O 抖动。此时应使用 -j 1(--threads=1)限制并发线程。
  2. 避免越过文件系统挂载点:在根目录 / 下搜索时,应配合 --one-file-system 防止扫描意外深入到挂载的远程网络卷或只读快照中。
  3. 控制遍历深度:在大型依赖树(如 monorepo 或深度嵌套目录)中,使用 -d 3(--max-depth 3)设置搜索深度上限。

搜不到目标时的排查清单
#

当 fd 未返回预期文件时,按以下顺序排查:

  1. 参数解析顺序:检查是否因遗漏 pattern 而把目录当成了搜索词(是否应写 fd . <path>);
  2. 大小写敏感:检查搜索词是否包含大写字母触发了 smart case;
  3. 隐藏文件:目标路径或其父目录是否以 . 开头(尝试添加 -H);
  4. 忽略规则拦截:目标是否被当前或上级 .gitignore、.ignore、.fdignore 排除(尝试添加 -I);
  5. 类型或扩展名限制:是否叠加了 -t f 但目标是软链接或目录,或 -e 拼写错误;
  6. 模式语法:当前模式是正则还是纯文本(包含 [、+ 等符号时尝试添加 -F)。

绝不要在系统 Shell 配置中设置 alias fd='fd -u'。无条件关闭 ignore 与隐藏过滤会把 node_modules、构建缓存和临时索引重新注入搜索结果,破坏 fd 设计的初衷。

7. 最小可复现练习
#

在测试夹具目录 research/modern-cli/fixture 中执行以下命令,验证对各类条件与执行模型的理解:

# 1. 查找指定目录下的所有 json 文件(注意使用 . 占据 pattern 位置)
fd -e json . research/modern-cli/fixture

# 2. 搜索隐藏文件并限定为普通文件(查看 .env.example)
fd -H -t f env research/modern-cli/fixture

# 3. 忽略 ignore 规则搜索构建产物
fd -I generated research/modern-cli/fixture

# 4. 仅检索目录条目
fd -t d . research/modern-cli/fixture

# 5. 检索所有 shell 脚本并批量统计行数(验证 -X 的参数传递)
fd -e sh . research/modern-cli/fixture -X wc -l

完成练习后,应能清楚解释:命令中各个参数的位置含义、默认过滤规则何时生效,以及外部命令 -x 与 -X 的启动次数与风险控制。

参考资料
#


本文采用 CC BY-NC-SA 4.0 许可。

modern-cli - 这篇文章属于一个选集。
§ 4: 本文

相关文章