关于 fedit
fedit 是面向系统管理员、DevOps 工程师及自动化脚本的 Go 命令行工具和 MCP 服务,用于精确编辑文件。它支持按行和锚点定位、操作验证,以及处理大型文件的流式操作。
主要功能
- 01按锚点插入、删除、替换、移动和复制
- 02支持验证的按行编辑
- 03多文件操作与跨文件传输
- 04大型文件的流式查找和替换
- 05语言结构映射与分隔字段提取
适用场景
- 01在部署脚本中更新配置文件
- 02跨多个文件编辑匹配的区段
- 03搜索或替换超大型日志中的内容
MCP 相关
MCP 服务提供文件查看、搜索、映射和编辑工具,也支持相应的多文件及跨文件操作。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
fedit — 终端快速文件编辑器
一款零依赖的 CLI 工具,可通过命令行对文件进行精准编辑。 无需交互式编辑器。无需折腾 sed/awk。只需简单、可预测的操作, 并内置验证功能。
专为系统管理员、DevOps 工程师以及所有需要编写配置更改脚本的人打造。

go install github.com/amalexico/fedit@latest
为什么选择 fedit?
- 单个二进制文件,零依赖 — 纯 Go 编写,随处可运行
- 21 种语言映射器 — 编辑前查看任意文件的结构
- -v 标志 — 每次修改后都进行验证,再继续操作
- 按行处理 — 不会遇到正则表达式意外,也不用猜“匹配到了哪一处?”
- 流式引擎 — 逐行处理数 GB 的文件,并保证原子完整性
- 字段提取 — 无需 awk 即可提取 CSV/TSV 的第 N 列
- 一次处理多个文件 — 在 -file 中使用逗号分隔列表或 glob,还可通过 -dest 跨文件复制、移动和替换(自 v1.9.0 起)
- 安全 — 除非明确指定,否则不会原地修改;绝不触碰未指定的文件
安装
通过 Go 安装(推荐):
go install github.com/amalexico/fedit@latest
或者从 GitHub Releases 下载二进制文件,并将其放入 PATH。
验证:
fedit -file /etc/hostname -op show
快速开始
# See what is in a file
fedit -file config.yaml -op show
# See just lines 10-25
fedit -file config.yaml -op show -line 10 -end 25
# Find every line containing "timeout"
fedit -file config.yaml -op find -match "timeout"
# See the structure of a Go file
fedit -file main.go -op map -lang go
# Replace line 42 with new content
fedit -file config.yaml -op replace -line 42 -end 42 -text "timeout: 60s" -v
# Insert a line after every occurrence of "server {"
fedit -file nginx.conf -op insertafter -match "server {" -text " include security.conf;" -v
# Move a function block before another (content-matched, atomic)
fedit -file main.go -op move -match "func OldHelper(" -end 45 -beforematch "func NewHelper(" -v
# Copy a config block and paste it 3 times at a new location
fedit -file values.yaml -op copy -line 50 -end 65 -after 200 -times 3 -v
# Extract column 2 from a TSV file (v1.4+)
fedit -file data.tsv -op fields -col 2
# Write content with literal backslashes — no \n escape expansion (v1.6.0)
fedit -file config.txt -op writeraw -text "path=C:\\Users\\admin"
# Hex-encode tricky text to sidestep shell quoting (v1.6.0)
# fwencode produces the hex; fedit decodes it before writing
fedit -file config.txt -op write -texthex 706174683d2f746d70
# Overwrite a file cleanly then insert new content (v1.6.0)
fedit -file config.txt -op insert -line 0 -cleanfirst -text "# regenerated"
# Get bare line numbers for scripting (v1.6.0)
fedit -file main.go -op find -match "TODO" -x 2>$null
# Regex replace on a multi-GB log without loading it into memory (v1.4+)
fedit -file huge.log -op replaceall -match 'ERROR' -text 'WARN' -stream
所有操作
show — 显示文件内容
# Entire file
fedit -file app.conf -op show
# Lines 50-75 only
fedit -file app.conf -op show -line 50 -end 75
# Last 10 lines
fedit -file app.conf -op show -line -10:
# Lines 50 to 5th from end
fedit -file app.conf -op show -line 50 -end -5
# Lines 100-103 (relative range)
fedit -file app.conf -op show -line 100:+3
# Show from one anchor to another (no line numbers needed)
fedit -file config.go -op show -match "func Start" -endmatch "func End"
find — 搜索包含指定子字符串的行
# Find all lines containing "ERROR"
fedit -file /var/log/app.log -op find -match "ERROR"
# Output includes context lines and occurrence numbers
# Use -nth to target a specific match in other operations
专业提示: 先运行 find 获取行号,再使用 replace 或 delete 精确指定行进行操作。
insert — 在指定行号后插入内容
# Insert a comment after line 1
fedit -file script.sh -op insert -line 1 -text "# Added by deploy script" -v
# Insert multiple lines from a file
fedit -file config.yaml -op insert -line 10 -textfile extra-config.yaml -v
insertafter — 在匹配行后插入内容(推荐)
# Add a firewall rule after the matching comment
fedit -file iptables.rules -op insertafter -match "# Custom rules" -text "-A INPUT -p tcp --dport 8080 -j ACCEPT" -v
# Target the 2nd occurrence
fedit -file nginx.conf -op insertafter -match "server {" -nth 2 -text " listen 8443 ssl;" -v
# Target the last occurrence
fedit -file docker-compose.yml -op insertafter -match "volumes:" -nth -1 -text " - /data:/data" -v
insertbefore — 在匹配行前插入内容(推荐)
# Add a header before the first route definition
fedit -file routes.rb -op insertbefore -match "get '/'" -text " # === Public Routes ===" -v
# Insert a dependency before the closing bracket
fedit -file package.json -op insertbefore -match "}" -nth -1 -textfile new-deps.txt -v
replace — 用新内容替换指定行范围
# Replace a single line
fedit -file config.ini -op replace -line 15 -end 15 -text "max_connections = 200" -v
# Replace lines 30-35 with content from a patch file
fedit -file server.conf -op replace -line 30 -end 35 -textfile patched-block.txt -v
# Replace a section by content anchors (no line numbers needed)
fedit -file CHANGELOG.md -op replace -match "## v1.6" -endmatch "## v1.5" -textfile new-section.txt -v
replaceall — 全局查找并替换
# Change all occurrences of old domain to new
fedit -file nginx.conf -op replaceall -match "old.example.com" -text "new.example.com" -v
# Update a version string everywhere
fedit -file Makefile -op replaceall -match "1.1.0" -text "1.2.0" -v
fields -- 从分隔符文件中提取一列(v1.4.0)
# Extract column 2 from a tab-separated file (default delimiter: tab)
fedit -file data.tsv -op fields -col 2
# Extract the third field from a CSV
fedit -file report.csv -op fields -col 3 -delim ","
# Extract usernames from /etc/passwd (colon-delimited)
fedit -file /etc/passwd -op fields -col 1 -delim ":"
输出会写入 stdout,便于通过管道传递。短于 -col 指定列数的行会被静默跳过。
始终采用流式处理——文件大小不受内存限制。
-stream -- 大文件流式处理模式(v1.4.0)
为 replaceall 或 find 添加 -stream,即可逐行处理文件,而无需将其载入内存。每行 10 MB 的缓冲区可处理 JSON 块和压缩文件。
原子完整性:先写入临时文件,再进行重命名——中断时不会影响原文件。
# Replace a pattern in a multi-GB log file
fedit -file server.log -op replaceall -match "10.0.0.1" -text "10.0.0.2" -stream
# Regex replace in a huge file with capture groups
fedit -file big.csv -op replaceall -match-regex 'id_(\d+)' -text 'ID_$1' -stream
# Streaming find -- grep-style output to stdout
fedit -file huge.log -op find -match "FATAL" -stream
支持 -stream 的操作:replaceall(字面匹配和正则表达式)、find。
不支持:move、copy、map(这些操作需要将完整文件结构载入内存)。
writeraw — 不展开转义字符,直接写入
# Write a Windows path without double-escaping backslashes
fedit -file config.ini -op writeraw -text "basedir=C:\\Program Files\\App"
# Write content from a file as-is
fedit -file output.txt -op writeraw -textfile template.txt
与 write 不同,writeraw 会将 \n 视为两个字符(反斜杠 + n),而不是换行符。
writelines — 从 stdin 交互式写入行
fedit -file notes.txt -op writelines
# Type lines at the > prompt, Ctrl+Z (Windows) or Ctrl+D (Unix) to finish
-texthex — 十六进制编码输入(v1.6.0)
先将文本编码为十六进制(例如使用 fwencode),然后将十六进制字符串作为 -text 传入。
彻底避免所有 shell 引号问题。
# Decode hex string and write — no quoting gymnastics needed
fedit -file deploy.sh -op write -texthex 23212f62696e2f62617368
-matchhex / -endmatchhex — 十六进制编码锚点(v1.8.1)
与 -texthex 相同的思路,但用于搜索锚点。PowerShell 会在 fedit 看到 -match 中的双引号前将其破坏,因此 case "replace": 会悄无声息地变成 case replace:,导致没有任何匹配。改用十六进制传入锚点;fedit 会将其解码为 -match
(或 -endmatch),因此所有接受锚点的操作都能照常使用。
# Find a line containing double quotes (hex of: case "replace":)
fedit -file main.go -op find -matchhex 6361736520227265706c616365223a
无效的十六进制字符串会以状态码 1 退出,并显示 matchhex: invalid hex string: ...(或 endmatchhex: ...)。
多个文件——逗号分隔列表或 glob(show/find/map 自 v1.8.1 起支持,修改操作和 MCP 自 v1.9.0 起支持,-sourcefile 和 -sourceblock 搭配列表自 v1.11.0 起支持)
-file 接受逗号分隔列表、glob,或两者兼用。若存在名称完全匹配的实际文件,则优先使用该文件,不进行 glob 展开。
只读操作(show、find、map)会为每个文件打印一个 ==> path <== 标题:
# Find TODO in every Go file in the current folder
fedit -file '*.go' -op find -match 'TODO'
# Map two files at once
fedit -file main.go,mcp.go -op map -lang go
- find 会跳过没有命中项的文件(类似 grep),且仅在所有文件都没有匹配项时以状态码 1 退出。
- map 会跳过无法识别扩展名的文件,并输出一行 SKIP。
- 摘要行会报告有结果的文件数量。
修改操作(replaceall、insertafter、insertbefore、delete、replace、write)会分别对每个文件单独执行:
# Replace in every matching file
fedit -file '*.conf' -op replaceall -match 'old.example.com' -text 'new.example.com'
# Insert after the first match in each file
fedit -file a.yaml,b.yaml -op insertafter -match 'servers:' -text ' - new'
# Delete from a start line through an end line in each file (-endmatch is inclusive)
fedit -file '*.md' -op delete -match '' -endmatch ''
- 某个文件出现问题(无匹配项、无法读取、只读、写入失败)时,会报告为 SKIP,且不会中止批处理。被跳过的文件绝不会被修改。
- 报告会逐个列出每个文件的 OK 或 SKIP,然后显示
N/M file(s) succeeded。只要至少有一个文件成功,退出代码就是 0;如果没有文件成功,则为 1。 - 只接受 -match(delete 和 replace 还接受 -endmatch)、-nth 和 -text/-textfile/-texthex,以及 insertafter 和 insertbefore 中的 -sourcefile/-sourceblock/-lang(自 v1.11.0 起)。replaceall 支持 -match-regex;write 无需 -match,会向每个文件写入相同内容。-block、-line、-cleanfirst、-stream 和 -files 均不受支持,move 和 copy 也不受支持;这些参数会在读取任何文件之前被拒绝。
- MCP:fedit_replaceall、fedit_insertafter、fedit_insertbefore、fedit_delete 和 fedit_replace 的 file 参数接受逗号分隔列表或 glob,规则相同,并会逐个文件生成报告;block、line、stream 和 files 不受支持。fedit_write 始终将 file 视为单一路径。fedit_show、fedit_find 和 fedit_map 也接受列表。
跨文件复制、移动和替换——-dest(v1.9.0,MCP 从 v1.10.0 起支持)
-dest 会将 -file 中的代码块复制或移动到一个或多个其他文件中。它接受以逗号分隔的现有文件列表(不支持 glob)。源内容按常规方式选取:使用 -line/-end、-match/-endmatch,或使用带 -lang 的 -block。目标锚点会在每个目标文件中单独解析,使用 -after、-before、-aftermatch、-beforematch、-afterblock 或 -beforeblock 之一(块锚点需要 -lang)。
# Copy a function into two other files, right after a named block in each
fedit -file a.go -op copy -block Helper -lang go -dest b.go,c.go -afterblock Init
# Move a config section into two files, before a marker line in each
fedit -file base.conf -op move -match '[cache]' -endmatch '[/cache]' -dest x.conf,y.conf -beforematch '[logging]'
# Paste 2 copies in each destination
fedit -file src.txt -op copy -line 5:+3 -dest a.txt,b.txt -aftermatch ANCHOR -times 2
- 每个目标都会单独处理。某个目标出现问题(文件不存在、未找到锚点、只读、写入失败)时,会报告为 SKIP,且不会修改该文件。批处理不会中止。
- move 会先写入所有目标。只有至少一个目标写入成功时,才会移除源文本。如果所有目标都失败,源文件会保持不变,因此文本绝不会丢失。
- 报告会列出每个目标的 OK 或 SKIP,随后显示
N/M file(s) succeeded。至少有一个目标成功时,退出码为 0;全部失败时,退出码为 1。 - 如果目标文件就是源文件,则会被标记为 SKIP;同一个文件在列表中出现多次时,只会使用一次。
- copy 和 move 使用带锚点标志的 -dest,replace 使用带 -destblock 的 -dest(见下文)。与任何其他 op 以及 -match-regex、-cleanfirst、-stream 或 -files 一起使用时,命令会被拒绝,且不会读取任何文件。它不能与多文件 -file 列表组合使用:-file 必须是单个源文件。
- 跨文件传输的是文本,而不是依赖项。调用 fmt.Println 的 Go 函数会原样写入没有 fmt 导入的文件中,而该文件在添加导入之前无法编译。从 v1.11.0 起,如果 Go 目标文件缺少传输行所用的标准库导入,使用 -dest 的 copy、move 和 replace 会添加提示(仅供参考:结果和退出码不变)。
- MCP:fedit_copy、fedit_move 和 fedit_replace 接受 dest 参数(文件逗号分隔列表),并使用相同的锚点;replace 还接受 destblock。规则相同,并会按目标分别报告;file 必须是单个路径。fedit_insertafter 和 fedit_insertbefore 接受 sourcefile 和 sourceblock(需带 lang);其中 file 也可以是逗号分隔列表或 glob。
替换其他文件中的代码块——replace -dest 和 -destblock
replace -dest 会用 -file 中的源代码块或范围覆盖每个目标文件中的指定代码块。-destblock 用于指定要覆盖的代码块,且为必填项。源内容的选取方式与 copy 和 move 相同,-lang 用于解析代码块名称。
# Overwrite Helper in two files with the Helper from a.go
fedit -file a.go -op replace -block Helper -lang go -dest b.go,c.go -destblock Helper -v
- 如果目标文件中没有该代码块,则会标记为 SKIP,绝不会改为插入。每个目标都会像 copy 和 move 一样单独处理。
- 仅在使用 replace -dest 时才接受 -destblock。
从其他文件插入代码块——-sourcefile 和 -sourceblock
insertafter 和 insertbefore 可以从另一个文件中指定的代码块获取内容。-file 仍然是要编辑的文件,锚点按常规方式选取。
# Insert Helper from a.go into b.go, after the line matching '// helpers'
fedit -file b.go -op insertafter -match '// helpers' -sourcefile a.go -sourceblock Helper -lang go -v
- -sourcefile 和 -sourceblock 必须同时使用,并且需要 -lang。它们不能与 -text、-textfile 或 -texthex 组合使用。
- -file 可以是逗号分隔列表或 glob(从 v1.11.0 起支持)。代码块会从 -sourcefile 读取一次,然后插入到每个文件各自的 -match 位置,并按文件分别报告。未找到匹配项的文件会被标记为 SKIP,且不会修改。这不是 -dest 扇出:每个目标都在自身的 -match 位置进行编辑。
- 从 v1.11.0 起,如果源文件和目标文件都是 Go 文件,且插入的代码块使用了目标文件未导入的标准库包,则会打印提示(CLI 中输出到 stderr,MCP 中显示在 OK 消息中;仅供参考:仍会执行插入,退出码不变)。使用文件列表时,提示会添加到对应文件的报告行中。
- MCP 工具 fedit_insertafter 和 fedit_insertbefore 接受 sourcefile 和 sourceblock 参数(需带 lang)。
guided——自然顺序入口点(CLI,自 v1.11.0 起)
fedit guided 按自然顺序接收源、动词和目标,将它们转换为显式的 -op 命令,并在运行前将其打印到 stderr。它只是 CLI 快捷方式:没有对应的 MCP 工具,引擎和显式标志均保持不变。
fedit guided SRCFILE SRCBLOCK copy|move DESTFILE [before TARGET | after TARGET | at-end] [silent|report-only]
fedit guided SRCFILE SRCBLOCK replace DESTFILE [TARGET]
fedit guided SRCFILE SRCBLOCK insertbefore|insertafter DESTFILE TARGET
fedit guided "new text" insertbefore|insertafter FILE TARGET
fedit guided
fedit guided a.go Helper copy b.go before Init
fedit guided a.go Helper move "b.go,c.go" after Init
fedit guided a.go "line 12-18" copy b.go at-end
fedit guided a.go Helper replace b.go
- 源可以是一个命名代码块(语言根据文件扩展名确定),也可以是
"line N-M"。 - 多个目标:将它们作为一个逗号分隔列表加引号(
"b.go,c.go"),否则 PowerShell 会将它们拆开。 - 每个目标都会单独处理。缺失的文件会被创建;锚点缺失(或未指定位置)时,会追加到末尾,并附带说明。移动操作会先执行复制,最后再移动,因此只有写入成功后才会删除源。
- 代码块名称会按子字符串匹配,与其他地方的规则相同。如果名称匹配多个代码块,则优先选择唯一的精确匹配(并附带说明);否则,在终端中会显示带编号的选项,没有终端时则会显示候选列表并以退出代码 2 结束。随后会通过行号定位所选代码块。
replace会在每个目标中覆盖 TARGET(默认与源名称相同),并跳过不包含该代码块的目标。silent使用-quiet运行计划:不回显、不显示说明;如果所有文件均成功,则不显示引擎报告(跳过和错误仍会显示)。report-only运行计划时不显示验证输出和回显行,但会保留说明和引擎自身的摘要。这两个修饰符都放在最后,并且适用于所有动词。- 单独运行
fedit guided(不带其他参数)时,会依次询问:操作、源文件和代码块、目标、位置,然后是修饰符。操作和位置可以按名称或编号回答;选错时会重新询问(最多三次)。提示会发送到 stderr,答案从 stdin 逐行读取,因此支持管道输入。如果输入在最后一个答案之前结束,则不会进行任何更改。
-cleanfirst——写入前截断(v1.6.0)
# Clear the file then insert fresh content at line 0
fedit -file output.txt -op insert -line 0 -cleanfirst -text "# regenerated"
-x——机器可读输出(v1.6.0)
# Get bare line numbers from find (stdout only, no context noise)
fedit -file main.go -op find -match "TODO" -x 2>$null
# Extract CSV column with no stats footer
fedit -file data.csv -op fields -col 2 -delim "," -x
v1.5.0:HCL/Terraform 代码块映射器(-lang hcl)
按名称移动、复制和重构 Terraform 代码块,无需行号。
接受 -lang hcl、-lang tf 或 -lang terraform(三者等效)。
支持的代码块类型:resource、data、module、provider、variable、
output、locals、terraform、moved、import、check。
# Move a resource block before another
fedit -file main.tf -op move -block 'resource "aws_instance" "web"' \
-beforeblock 'resource "aws_s3_bucket" "data"' -lang hcl -v
# Copy a variable definition (scaffold new variable from existing)
fedit -file variables.tf -op copy -block 'variable "instance_type"' \
-after 20 -lang hcl -v
# Reorder provider blocks
fedit -file providers.tf -op move -block 'provider "google"' \
-beforeblock 'provider "aws"' -lang hcl -v
嵌套代码块(例如 resource 中的 ingress {})会被正确忽略——
只匹配顶层代码块。
v1.5.0:Nix 代码块映射器(-lang nix)
在 Nix 表达式文件中移动和复制顶层属性绑定。
支持属性集(name = { })、列表(name = [ ])和
点分属性(programs.git = { })。
# Reorder home-manager program configs
fedit -file home.nix -op move -block "programs.git" \
-beforeblock "programs.ssh" -lang nix -v
# Copy a service config as a scaffold
fedit -file configuration.nix -op copy -block "services.nginx" \
-after 50 -lang nix -v
move——将一段行移动到新位置
# Move lines 100-120 to after line 200 (explicit range)
fedit -file server.go -op move -line 100 -end 120 -after 200 -v
# Move a function block to before another function (content-matched)
fedit -file routes.go -op move -match "func OldHelper(" -end 45 -beforematch "func NewHelper(" -v
# Swap two nginx server blocks
fedit -file nginx.conf -op move -match "server {" -endmatch "# end server 1" -aftermatch "# end server 2" -v
# Cut once, scaffold 3 copies at destination
fedit -file main.go -op move -line 5 -end 12 -after 100 -times 3 -v
规则:
- 目标位置不得与源范围重叠——fedit 会报告包含行号的精确错误。
-times N:剪切一次,粘贴 N 次。净变化量 = 代码块大小 × (N−1)。默认 times=1,净变化量为零。- 若要将代码块移动到其他文件,请使用
-dest(参见上文“跨文件复制、移动和替换”)。
copy——将一段行复制到新位置
# Copy a config block to after a section header
fedit -file values.yaml -op copy -line 50 -end 65 -aftermatch "# staging" -v
# Duplicate a test fixture 10 times for parameterised tests
fedit -file fixtures_test.go -op copy -match "func TestCase(" -end 30 -after 200 -times 10 -v
# Reorder Python classes (copy source before target; overlap is allowed)
fedit -file processor.py -op copy -match "class ModuleProcessor_15" -endmatch "class ModuleProcessor_16" -beforematch "class ModuleProcessor_13" -v
规则:
- 快照语义:在任何写入之前只读取一次源范围。即使目标与源重叠,所有 N 个副本也都是原始内容的相同克隆。
- 若要将代码块复制到其他文件,请使用
-dest(参见上文“跨文件复制、移动和替换”)。 - 净变化量 = 代码块大小 × times。
delete——删除行
# Delete a single line
fedit -file hosts -op delete -line 12 -end 12 -v
# Delete a block (lines 40-55)
fedit -file config.yaml -op delete -line 40 -end 55 -v
write——创建或覆盖文件
# Create a new file
fedit -file /tmp/note.txt -op write -text "Deployment started" -v
# Write multi-line content from another file
fedit -file /etc/motd -op write -textfile new-motd.txt -v
map——文件结构概览
# Map a Go file — see all functions, types, imports
fedit -file main.go -op map -lang go
# Map a Dockerfile — see stages and instructions
fedit -file Dockerfile -op map -lang dockerfile
# Map a Makefile — see variables, targets, duplicates
fedit -file Makefile -op map -lang makefile
支持的映射语言(21 种)
| 语言 | 检测到的关键结构 |
|---|---|
| go | package、imports、types、interfaces、functions |
| python | imports、classes、functions、decorators |
| javascript | imports、exports、classes、functions、arrows |
| typescript | (同 javascript) |
| css | imports、custom properties、selectors、@media、@keyframes;重复选择器按容器块计数,因此不同 @media 或 @keyframes 块中的同一选择器不算重复(自 v1.11.0 起) |
| rust | use、mod、struct、enum、trait、impl、fn、macro_rules! |
| java | package、imports、classes、interfaces、enums、methods |
| csharp | using、namespace、classes、interfaces、records、properties |
| html | doctype、head/body、headings、scripts、links、forms、ids |
| sql | CREATE、ALTER、DROP、INSERT、SELECT、indexes、triggers |
| yaml | 文档分隔符、顶层键 |
| toml | tables、array tables、顶层键 |
| markdown | 标题(跳过围栏代码,因此不会将 shell 注释列为标题)、代码块、链接;重复标题(文本、级别和父级相同,且不区分大小写)会被标记(自 v1.11.0 起) |
| ruby | requires、modules、classes、methods、attributes |
| php | namespace、use、classes、interfaces、traits、functions |
| dockerfile | FROM 阶段、所有指令;重复的阶段名称(不区分大小写),以及同一阶段中重复的 CMD、ENTRYPOINT 或 HEALTHCHECK 会被标记(自 v1.11.0 起) |
| makefile | includes、variables、.PHONY、targets |
| json | 根类型、顶层键、嵌套深度、同一对象中的重复键、格式错误文件提示 |
| csv | 行、列、标题列表、不规则行、重复标题列、空字段 |
| hcl | 按类型划分的顶层块(terraform、provider、variable、locals、data、resource、module、output、moved、import、check)、重复块;terraform 和 tf 是别名(自 v1.11.0 起) |
| nix | 顶层属性块和简单属性、重复属性路径;仅限未缩进的行(自 v1.11.0 起) |
大多数映射器都会检测重复项(函数、表、键、标题列、块、属性、选择器、标题、Dockerfile 阶段),并通过警告标记出来。
标志参考
| 标志 | 描述 |
|---|---|
| -file PATH | 目标文件(必填);用于 show/find/map 和 replaceall/insertafter/insertbefore/delete/replace/write 时,可使用逗号分隔的列表或 glob |
| -dest PATH,PATH | 跨文件复制、移动和替换:逗号分隔的目标文件列表;每个目标都需要一个锚点标志(-after、-before、-aftermatch、-beforematch、-afterblock、-beforeblock),或在 replace 中使用 -destblock(v1.9.0 起;MCP 自 v1.10.0 起支持) |
| -op OP | 要执行的操作(必填) |
| -line N | 起始行号(从 1 开始);N:+M=范围,-N=从末尾开始,-N:=最后 N 行,:=EOF |
| -end N | 结束行(用于范围);-N 表示从文件末尾开始计数 |
| -text "s" | 内联文本内容 |
| -textfile F | 从文件读取内容 |
| -match "s" | 要搜索的子字符串 |
| -nth N | 指定第几次出现(默认 1,-1 = 最后一次) |
| -lang LANG | map 和 -block 操作使用的语言 |
| -v | 验证:显示编辑后受影响的行 |
| -endmatch "s" | 内容锚点,用作范围的结束位置(show/replace/delete/move/copy) |
| -quiet | 成功时抑制输出,包括多文件和 -dest 执行时的逐文件报告(仍会显示跳过项和错误);退出代码表示结果(自 v1.11.0 起) |
| -texthex HEX | 以十六进制编码的 UTF-8 内容,绕过 shell 引号处理(v1.6.0) |
| -matchhex HEX, -endmatchhex HEX | 以十六进制编码的 -match 和 -endmatch 锚点,适用于包含双引号的锚点(v1.8.1) |
| -match-regex P | replaceall:带捕获组($1 $2)的正则表达式 |
| -files GLOB | 对匹配 glob 的所有文件执行 replaceall(其他操作在 -file 中使用逗号分隔的列表或 glob) |
| -stream | replaceall 和 find 的大文件模式(v1.4.0) |
| -cleanfirst | 插入或写入前先截断文件(v1.6.0) |
| -raw | show:仅输出内容,不含行号或页脚,以便通过管道传给 fwencode(v1.7.0) |
| -x | find:仅输出行号;fields:不显示统计页脚(v1.6.0) |
| -col N, -delim CHAR | fields:列号(从 1 开始)和字段分隔符(默认为制表符)(v1.4.0) |
| -extract SPEC, -get REGEX, -wdelim CHAR | find:子行提取(单词和字符切片)、正则预筛选和单词分隔符(v1.7.0;通过 MCP 使用时,无效的 get 正则表达式会报错,自 v1.11.0 起) |
| -block NAME | 定位命名函数、类或资源,而非使用行号(需要 -lang);块结束位置的判定会跳过字符串、多行字符串和注释,见下文“如何确定块的结束位置”(自 v1.11.0 起) |
| -after N, -before N | move/copy 目标位置:在第 N 行之后或之前(0 = 文件开头) |
| -aftermatch TEXT, -beforematch TEXT | move/copy 目标位置:在首个包含 TEXT 的行之后或之前 |
| -afterblock NAME, -beforeblock NAME | move/copy 目标位置:在命名块之后或之前(需要 -lang);用于 insertafter 和 insertbefore 时会被拒绝,并报错提示使用 -block(自 v1.11.0 起) |
| -times N | move/copy:粘贴 N 次(move 只剪切一次) |
| -destblock NAME | 仅用于 replace -dest:指定要在每个目标文件中覆盖的块(必填);缺少此项的目标会被跳过(v1.9.0 起;MCP 自 v1.10.0 起支持) |
| -sourcefile F, -sourceblock NAME | insertafter/insertbefore:从另一个文件的命名块获取内容(需要 -lang;不能与 -text、-textfile 或 -texthex 同用)。-file 可为列表,且可以出现 Go 导入提示(两者均自 v1.11.0 起);这些标志本身自 v1.9.0 起提供,MCP 自 v1.10.0 起支持 |
如何查找代码块结束位置(自 v1.11.0 起)
使用 -block NAME(以及 -sourceblock、-destblock、-beforeblock、-afterblock)时,代码块范围由对应语言的扫描器确定。Python 和 Ruby 代码块不使用花括号计数,因此不受影响。Go、JS 和 TS、Rust、Java、C#、PHP、HCL 和 Nix 会统计花括号(Nix 还会统计方括号),但以下内容不会计入:
- 字符串,包括多行字符串:反引号字符串(Go 原始字符串、JS 模板字面量)、C# 逐字字符串(
@"...")和原始字符串(""")、Java 文本块、Rust 原始字符串(r"..."、r#"..."#)、HCL heredoc(<<EOT、<<-EOT)、PHP heredoc 和 nowdoc(<<<EOT、<<<'EOT',允许结束标记缩进)以及 Nix 缩进字符串(两个单引号)。在 Java 文本块中,反斜杠会转义下一个字符,因此转义后的\"""不会结束代码块。 - Rust 字符字面量、生命周期和循环标签。
- 注释:
//和/* */(Rust 中可嵌套),以及 PHP、HCL 和 Nix 中的#(在 PHP 中,#[表示属性的开始,不是注释)。Nix 没有//注释,因为//是它的更新运算符。
已知限制:包含未转义 /* 的 JS 正则表达式,以及在任何代码块之外开始的块注释。对于这类代码块,请先用 show 检查范围,再回退使用 -line。
真实示例
在部署脚本中修改 Nginx 配置
#!/bin/bash
# Update upstream server and reload
fedit -file /etc/nginx/conf.d/app.conf \
-op replaceall \
-match "server 10.0.1.50:8080" \
-text "server 10.0.1.51:8080" -v
nginx -t && systemctl reload nginx
向 crontab 添加 cron 作业
fedit -file /etc/crontab \
-op insertafter \
-match "# Custom jobs" \
-text "0 2 * * * root /opt/backup.sh" -v
批量更新多个文件中的版本
for f in Makefile config.yaml package.json; do
fedit -file "$f" -op replaceall -match "1.1.0" -text "1.2.0" -v
done
编辑前检查 Dockerfile
fedit -file Dockerfile -op map -lang dockerfile
# See the structure, then surgically edit:
fedit -file Dockerfile -op insertafter -match "FROM alpine" -text "RUN apk add --no-cache curl" -v
从配置中移除代码块
# First, find the lines
fedit -file app.conf -op find -match "deprecated-feature"
# Output says lines 44-49, so:
fedit -file app.conf -op delete -line 44 -end 49 -v
PowerShell 工作流(Windows 系统管理员)
# Set an alias
$f = "C:\tools\fedit.exe"
# Find all TODO comments in Go code
& $f -file main.go -op find -match "TODO"
# Replace a config value
& $f -file config.toml -op replaceall -match 'debug = true' -text 'debug = false' -v
# Map a file to understand its structure
& $f -file main.go -op map -lang go
提示
- 修改时始终使用 -v — 没有额外成本,还能避免盲目编辑
- 替换前先查找 — 先获取准确的行号
- 优先使用 insertafter/insertbefore,而不是 insert — 匹配比硬编码行号更可靠
- 使用 -nth -1 定位匹配项的最后一次出现
- 多行插入时使用 -textfile — 避免令人头疼的 shell 引号问题
- 编辑不熟悉的文件前先使用 map — 先查看文件结构
大模型基准测试
当前大模型使用 fedit 与重写整个文件,哪种方式表现更好?
我们在 7 个真实编辑任务中测试了 Claude(Sonnet 4.6)、ChatGPT(GPT-4o) 和 Gemini(2.5 Pro),涉及行数为 565–1206 行的文件。每个模型针对每项任务测试两次:一次要求输出整个文件(“raw”),一次要求只输出 fedit 命令(“fedit”)。
结果
图例:PASS+ = 最优的单行解决方案 | PASS = 正确 | PASS* = 内容正确,但格式有瑕疵 | PARTIAL = 意图正确,但存在偏一错误或方案脆弱 | FAIL = 输出错误
| 测试 | 文件 | 任务 | Claude raw | Claude fedit | GPT raw | GPT fedit | Gemini raw | Gemini fedit |
|---|---|---|---|---|---|---|---|---|
| T1 | processor_600.go(575 行) | 在指定的结构体方法后插入方法 | PASS | PARTIAL | FAIL | PARTIAL | PASS | FAIL |
| T2 | config_800.yaml(1059 行) | 替换 24 行的部署代码块 | PASS | PASS | FAIL | FAIL | PASS | FAIL |
| T3 | styles_500.css(565 行) | 查找并删除 CSS 规则代码块 | PASS | PASS | PASS* | FAIL | PASS* | FAIL |
| T4 | system_1000.go(980 行) | 全局重命名(36 处) | PASS | PASS+ | FAIL | PASS+ | PASS* | PASS+ |
| T5 | analytics_700.py(682 行) | 三步链式操作(插入 + 删除 + 替换) | PASS | PARTIAL | FAIL | FAIL | PASS | FAIL |
| T6 | dashboard_900.html(891 行) | 在第 3 个匹配按钮前插入(-nth) | PASS | PARTIAL | FAIL | PASS+ | PASS | FAIL |
| T7 | engine_1200.go(1196 行) | 映射 + 在指定方法后插入 | PASS | PASS | FAIL | FAIL | PASS | FAIL |
核心数据
| 模型 | 原始模式 | Fedit 模式 |
|---|---|---|
| Claude | 7/7 PASS | 4 PASS,3 PARTIAL |
| ChatGPT | 1/7 PASS* | 2 PASS+,1 PARTIAL,4 FAIL |
| Gemini | 7/7 PASS* | 1 PASS+,6 FAIL |
主要发现
1. ChatGPT 无法可靠地输出大型文件。
7 项原始测试中有 6 项输出被截断。最明显的失败出现在 T6:它在原本有效的 HTML 中插入了字面占位行 [... TRUNCATED FOR BREVITY ...]——这是一种独特且危险的失败模式:输出看起来结构完整,却包含占位字符串。T7 的输出则从 1206 行截断到 166 行。
2. 大模型会幻觉行号,而且链条越长,情况越严重。 Gemini 的行号错误在整个测试套件中不断增大:T2(单次替换)偏差 36–56 行,T3 偏差 45 行,到了 T5(三步链)则达到 73 行。ChatGPT 在 T5 中也出现了类似的偏差。只有 Claude 能始终如一地产生可运行的带行号命令。
3. 基于内容匹配的操作不会受到这类幻觉的影响。
Gemini 在所有需要行号的 fedit 测试中都失败了(T1、T2、T3、T5、T6、T7),但通过了使用 replaceall 进行内容匹配的 T4。同一个模型、相同的任务复杂度,可靠性却大不相同。瓶颈在于计数,而非理解。
4. 对函数声明使用 insertafter 会匹配函数的起始行。
模型反复尝试使用 insertafter -match "func MyFunc()",想在函数结束后插入内容。这符合 fedit 的预期行为(它会在匹配行之后插入内容),但新代码会被插入函数体内。解决方法:在下一个结构元素之前插入。已在 T1 和 T7 中确认。
5. 连 Claude 也会弄错行号方向。
T6:Claude 使用 insert -line 30,想在第 30 行的第 3 个按钮之前添加横幅。但 insert -line N 会在第 N 行之后添加内容。正确命令是 insertbefore -match "View Details" -nth 3。即便是最强的模型,在选择行号进行单步操作时,大约每 6 次也会有 1 次弄错方向。
6. -match 只支持单行匹配——这反而是优点。
ChatGPT(T3 fedit)试图将一个多行 CSS 代码块作为 -match 参数传入,并使用字面 \n 转义符。fedit 不会在 -match 中解释转义符,且只匹配单行。这让操作保持安全(没有匹配项 → 不进行编辑),避免使用容易发生不匹配的脆弱多行模式。
7. Markdown 渲染是隐藏的干扰因素。
ChatGPT 和 Gemini 的输出在聊天界面渲染时,会将 __name__ 变成 name、将 __init__ 变成 **init**,并去掉 CSS/Python 缩进。直接下载的底层文件则正确无误。这会影响复制粘贴工作流,但不会影响 API 集成。为获得最佳效果,请使用模型的“复制代码”按钮或下载链接,切勿从渲染后的输出中选择并复制。
8. 三种不同的模型对 T4 得出了相同的单行命令。
Claude、ChatGPT 和 Gemini 都独立生成了
fedit -op replaceall -match "FetchUser" -text "GetAccount"。
当正确的工具一目了然时,模型就会选用它。fedit 的设计让基于内容的编辑显而易见地适合使用该工具。
面向 LLM 驱动工作流的建议
根据这些结果,从 LLM 生成 fedit 命令时,优先使用按内容匹配的操作,而非按行号操作:
- 使用
insertbefore -match "next anchor",而不是insert -line N - 使用
replaceall -match "old" -text "new",而不是replace -line N -end M - 在执行任何按行号操作之前,使用
find -match和show确认行号 - 仅在 MCP 连接的 LLM 刚刚运行
fedit_find或fedit_show,并已在上下文中核实行号时,才使用按行号操作 - 使用
-block/-lang定位命名函数和结构体,无需行号
为获得最佳效果,请为 LLM 提供 fedit 的 MCP 连接(参见
MCP 服务模式),使其在修改文件前能够运行 fedit_find 和 fedit_show。这样可以彻底消除行号幻觉;在 Claude 可以使用 recon 时,它始终选择了这种方式。
方法
- 每次测试都在一个新的聊天中进行,并上传原始文件
- 所有模型使用相同的提示词;只有“raw”和“fedit”的表述不同
- 原样保存输出,然后使用
Compare-Object与基准结果进行差异比较 - 通过在本地运行 fedit 命令并重新读取输出,验证基准结果
- 所有 7 个 raw 输出均通过 Compare-Object 空结果检查(与基准结果逐字节一致):Claude T2、T3、T4、T5、T6、T7 + Gemini T5、T6、T7
PASS*表示聊天界面渲染时去除了缩进,导致字节差异,但内容结构正确(通过对保存的文件重新运行 fedit 操作进行了验证)
测试语料
7 个合成文件(565–1206 行),涵盖 Go、YAML、CSS、Python 和 HTML。 完整结果、基准文件、测试提示词和 7 个文件组成的语料: amalexhandler.com/fedit#benchmark。
MCP 服务模式
fedit 内置了 Model Context Protocol (MCP) 服务,因此 AI 编码助手可以使用 fedit 精确编辑文件。
fedit mcp
这会在 stdin/stdout 上启动 JSON-RPC 2.0 服务。该服务提供全部 14 种编辑操作作为 MCP 工具:
| 工具 | 描述 |
|---|---|
fedit_show | 显示文件内容(全部内容或指定行范围);file 支持逗号分隔列表或 glob |
fedit_insert | 在指定行号后插入内容 |
fedit_delete | 删除一行或多行;file 支持逗号分隔列表或 glob,且可与 match 配合使用 |
fedit_replace | 替换行范围或命名块;file 支持逗号分隔列表或 glob,且可与 match 配合使用;dest + destblock 可覆盖其他文件中的命名块 |
fedit_replaceall | 全局查找并替换;match_regex 用于正则表达式,textfile 用于替换文本(两者均自 v1.11.0 起支持);file 支持逗号分隔列表或 glob |
fedit_write | 创建或覆盖文件 |
fedit_writeraw | 创建或覆盖文件,不展开转义序列(反斜杠按字面处理) |
fedit_map | 结构概览(支持 21 种语言;未指定时根据扩展名检测 lang);file 支持逗号分隔列表或 glob |
fedit_find | 查找与子字符串匹配的行;file 支持逗号分隔列表或 glob |
fedit_insertafter | 在匹配行或命名块后插入;sourcefile + sourceblock + lang 可从其他文件插入代码块(自 v1.11.0 起提供 Go 导入提示);file 支持逗号分隔列表或 glob |
fedit_insertbefore | 在匹配行或命名块前插入;sourcefile + sourceblock + lang 可从其他文件插入代码块(自 v1.11.0 起提供 Go 导入提示);file 支持逗号分隔列表或 glob |
fedit_move | 将行范围移至新位置;拒绝目标范围重叠;dest 可将代码块移至其他文件 |
fedit_copy | 复制行范围;采用快照语义,允许重叠,支持 -times N;dest 可将代码块复制到其他文件 |
fedit_fields | 从 CSV/TSV/分隔符分隔文件中提取第 N 列(-col N -delim CHAR);始终采用流式处理 |
使用 Claude Desktop 进行配置
添加到你的 claude_desktop_config.json:
{
"mcpServers": {
"fedit": {
"command": "fedit",
"args": ["mcp"]
}
}
}
完整的设置指南(Windows 路径、故障排除、Cursor/Cline/Windsurf 代码片段):CLAUDE_DESKTOP.md
使用 Cursor 进行配置
添加到项目根目录下的 .cursor/mcp.json:
{
"mcpServers": {
"fedit": {
"command": "fedit",
"args": ["mcp"]
}
}
}
为什么使用 MCP?
不使用 fedit 时,大模型会重写整个文件,浪费 token 并引入偏差。将 fedit 作为 MCP 工具后,模型会调用 fedit_map 查看结构,调用 fedit_find 定位目标,再调用 fedit_replace 或 fedit_insertafter 进行精准编辑。每次修改都会返回统计信息(行数增减、耗时),以便模型验证操作结果。
常见问题
如果团队有完善的 PR 审核流程,还需要 fedit 吗?
对于每次都会审查差异的人工作业,可能不需要。fedit 在两种特定情况下尤其有用:
-
重写长配置文件。 当大模型为了修改一个键而重新生成 600 行的
values.yaml时,差异在技术上可以审查,但实际上没人会滚动到第 412 行去确认其他内容没有变化。使用 fedit,修改 N 行时,差异恰好只有 N 行。 -
中间没有人工介入的 Agent 循环。 如果你以半自主方式运行 Agent(问题 → 分支 → 创建 PR,最后由你审核),为 Agent 提供精准工具(
fedit_replace、fedit_insertafter),而不是让它“重写整个文件”,就能显著缩小审核范围。
如果你的工作流已经能发现这些问题,那么命令行工具就有些大材小用了。MCP 服务仍可作为 Agent 的基础工具。
fedit 是否支持所有操作的流式处理?
目前,replaceall 和 find 支持 -stream 模式。move 和 copy 需要将整个文件载入内存,因为它们需要读取源和目标范围。map 需要进行结构分析。fields 始终采用流式处理。
fedit 能处理的最大文件有多大?
在默认(内存)模式下,大小受可用 RAM 限制,通常处理数百 MB 的文件没有问题。使用 -stream 时则没有大小限制——fedit 每次处理一行,并使用 10 MB 的逐行缓冲区。
fedit 支持 Terraform / HCL 吗?
支持,从 v1.5.0 开始。使用 -lang hcl(或 -lang tf/terraform)搭配 -block、-beforeblock、-afterblock,即可按名称移动和复制 Terraform 块。支持全部 11 种顶层块类型。从 v1.11.0 开始,map 操作支持 HCL/Terraform 和 Nix:对 .tf、.hcl 或 .nix 文件执行 fedit -op map(或搭配 -lang hcl、terraform、tf 或 nix),即可列出顶层块(HCL)或属性(Nix)并标记重复项;fedit_map 也可通过 MCP 执行相同操作。
fedit 会保留注释和格式吗?
会,这是其设计原则。fedit 不会解析并重新格式化,而是以行为粒度直接处理原始文本字节。
如果你执行 replace -line 47 -end 49,第 1–46 行和第 50 行至文件末尾都将逐字节保持不变:注释、尾随空格、混合缩进、BOM 标记都会原样保留。这也是 fedit 按行定位而非基于 AST 的主要原因:AST 往返会丢失太多内容(尾随逗号、注释位置、键顺序、空行间距)。
这对 nginx 配置(内联注释会说明指令存在的原因)、Ansible playbook(YAML 锚点和合并键),以及任何人工选择的格式具有语义重要性的文件来说尤其关键。
MCP 服务是专为 Claude 还是 Cursor 设计的?
两者都不是——它面向开放的 MCP 规范。它通过标准输入/输出使用纯 JSON-RPC 2.0,并遵循公开的 Model Context Protocol 架构,不包含任何厂商专属内容。
已在 Claude Desktop 中测试;应该也能与任何符合规范的 MCP 客户端配合使用(Cursor、Continue、Cline、自定义集成)。工具定义和 JSON Schema 位于 mcp.go,你可以在接入前查看其接口范围。
如果目标字符串在文件中出现多次,该怎么办?
使用 -nth N 标志:
-nth 1(默认)——第一个匹配项-nth 2——第二个匹配项-nth -1——最后一个匹配项- 任意正整数都可指定对应位置的匹配项
示例:定位第三个 http.Redirect 调用:
fedit -file router.go -op insertbefore -match "http.Redirect" -nth 3 -textfile patch.txt -v
如果不确定有多少个匹配项,请先运行 find——它会列出每个匹配项及其上下文,方便你在修改前确认目标:
fedit -file router.go -op find -match "http.Redirect"
链式执行命令会导致行号偏移吗?
不会。每次调用 fedit 时都会重新读取文件,因此行号始终反映磁盘上的当前状态。你可以使用 ;(PowerShell)或 &&(bash)安全地链式执行操作:
fedit -op replaceall -file f.conf -match "old.com" -text "new.com" -v ; fedit -op replace -file f.conf -line 42 -end 42 -text "fixed" -v
这样做的代价是每次操作都会读取一次磁盘,对于编辑工作流来说,这点开销可以忽略不计。
如果希望从设计上避免行号偏移,优先使用基于内容的定位方式(insertafter / insertbefore / replaceall 配合 -match),而不是行号——无论之前执行了什么操作,这些方式都会在每次调用时重新确定目标。
许可证
MIT——参见 LICENSE
由 Amalex 构建——Amalex Handler 的开发者,这是一款通用文件传输工具。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。