☰
fish shell `commandline` 内建命令完全指南:读取与改写命令行缓冲区
2026/10/1 8:38:31 网站建设 项目流程
  • CLI
  • 开发工具

【免费下载链接】fish-shell

The user-friendly command line shell.

项目地址:https://gitcode.com/GitHub_Trending/fi/fish-shell
点击查看免费下载

commandline是 fish shell 中一个专为交互式场景设计的内建命令,用于读取、修改当前正在编辑的命令行缓冲区(command line buffer),是补全脚本、自定义快捷键函数和交互式提示符的核心工具。读完本文,你将掌握它的全部选项语义、缓冲区作用域(buffer/job/process/token)的划分规则,并能够写出像commandline -xpc这样专业级的补全辅助代码。本文以 doc_src/cmds/commandline.rst 为骨架,并结合作品仓库中 src/builtins/commandline.rs 的 Rust 实现与 tests/checks/commandline.fish 测试用例进行源码级验证。

一、命令概览与基本用法

语法

commandline [OPTIONS] [CMD]

commandline的功能可以用一句话概括:读取或设置当前命令行的内容。它在 C 语言时代就是 fish 的传统内建命令,如今在 Rust 重写版本中由src/builtins/commandline.rs中的commandline()函数实现。

无参数与单参数行为

  • 不带任何参数:commandline直接打印当前命令行的完整内容,等价于commandline --current-buffer(默认作用域)。
  • 带 CMD 参数:清除当前命令行缓冲区,并用CMD的内容整体替换它(等价于默认的--replace模式)。

需要特别注意的是,commandline只有在交互式会话中才有意义。从源码看,当既不存在 transient commandline(补全包装时产生的临时命令行)也没有交互式会话时,会直接报错Can not set commandline in non-interactive mode(见 src/builtins/commandline.rs)。测试用例 tests/checks/commandline.fish 也验证了fish -c 'commandline foo'会输出这一错误。

二、选项总览

commandline的选项数量多、组合规则严格,先给出一张总览表(信息依据 doc_src/cmds/commandline.rst):

分类选项说明
光标与选区-C/--cursor读取或设置光标位置;配合-j/-p/-t时位置相对相应子串
光标与选区-B/--selection-start读取选区起点位置
光标与选区-E/--selection-end读取选区终点位置
光标与选区-L/--line打印/设置光标所在行(从 1 开始)
光标与选区--column打印/设置光标在当前行的码点偏移(从 1 开始)
更新方式-a/--append不清空现有命令行,将字符串追加到末尾
更新方式-i/--insert/--insert-smart在光标位置插入字符串;--insert-smart启用 DWIM 模式
更新方式-r/--replace清空并用指定字符串替换(默认)
作用范围-b/--current-buffer整个命令行(默认,不含自动补全建议)
作用范围-j/--current-job当前 job(一条流水线),停在逻辑运算符或;、&、换行处
作用范围-p/--current-process当前 process(一条命令),停在逻辑运算符、终止符和管道处
作用范围-s/--current-selection当前选区
作用范围-t/--current-token当前 token
作用范围--search-field操作 pager 搜索框而非命令行;搜索框未显示时返回假
作用范围--input=INPUT把INPUT当作命令行内容来操作,便于配合--tokens-expanded等
打印方式-c/--cut-at-cursor只打印到光标位置为止的选区
打印方式-x/--tokens-expanded对选区做参数展开,每个参数单独一行输出
打印方式-o/tokenize/--tokens-raw已弃用,不要使用
输入函数-f/--function把参数当作输入函数放入队列,先于后续按键被读取;不可与其他选项组合
状态查询-S/--search-mode判断是否处于历史搜索模式
状态查询-P/--paging-mode判断是否显示 pager 内容(如 Tab 补全)
状态查询--paging-full-modepager 内容且所有行都显示(没有 " more rows")
状态查询--is-valid判断命令行是否语法完整有效
状态查询--showing-suggestion判断是否正在显示历史自动补全建议
通用-h/--help显示帮助

上述所有选项的解析都发生在commandline()函数开头的 getopt 循环中(src/builtins/commandline.rs),并且源码中还有一批文档未收录但已实现的--forward-jump、--backward-jump、--forward-jump-till、--backward-jump-till跳转选项,它们会调用reader_jump()驱动光标跳转。

三、三种更新模式:追加、插入与替换

commandline修改缓冲区的方式由三个互斥选项控制,源码中以AppendMode枚举表示(src/builtins/commandline.rs):

选项AppendMode行为
-r/--replaceReplace移除当前命令行,用指定字符串替换(默认模式)
-i/--insertInsert保留当前命令行,把字符串插入到光标位置
--insert-smartInsertSmart同 insert,但启用 DWIM(Do-What-I-Mean)智能处理
-a/--appendAppend保留当前命令行,把字符串追加到选区末尾

从源码replace_part()(src/builtins/commandline.rs)可以看到四种模式的差异:

  • Replace:直接丢弃选区内文本,拼入新字符串,光标移到新内容末尾;
  • Append:先保留选区内原文,再在后面追加新字符串;
  • Insert/InsertSmart:在光标处把字符串"楔入"选区中间,光标前移一个插入串长度。

DWIM 模式:--insert-smart 的特殊行为

--insert-smart会调用strip_dollar_prefixes()(src/builtins/commandline.rs):它会解析插入后的完整文本,当某一行行首的进程带有$前缀时,自动剥掉这个前缀。例如插入'$ echo 123',最终进入缓冲区的是echo 123而不是$ echo 123。

这个选项有两处限制,源码中有显式校验:

  • 不能与--current-token组合(因为当前 token 可能只是命令中间的一部分,语义不明,见 src/builtins/commandline.rs);
  • 不能与--search-field组合。

对应测试 tests/checks/commandline.fish 中commandline --insert-smart '$ echo 123' --current-token会报错options cannot be used together。

四、作用范围:buffer、job、process 与 token

理解commandline作用范围的划分是正确使用它的前提。源码中用TextScope枚举区分四种范围(src/builtins/commandline.rs),实际范围计算交给parse_util模块的get_job_extent/get_process_extent/get_token_extent:

  • -b/--current-buffer:整个命令行缓冲区,不包括显示的自动补全建议(默认)。源码中直接映射为0..current_buffer.len()。
  • -j/--current-job:光标所在的job,即一条流水线。划分边界是逻辑运算符和终止符(;、&、换行符)。
  • -p/--current-process:光标所在的process,即一条命令。划分边界是逻辑运算符、终止符和管道符(|)。
  • -t/--current-token:光标所在的token。
  • -s/--current-selection:当前选区的文本内容(若有选区,直接输出rstate.text[selection])。

以下面的命令行(光标在 "flounder" 的 "o" 上)为例:

>_ echo $flounder >&2 | less; and echo $catfish

按照文档的定义和get_process_extent、get_job_extent的实现逻辑:

  • 第一个process是echo $flounder >&2,第二个是less,第三个是and echo $catfish;
  • 第一个job是echo $flounder >&2 | less,第二个是and echo $catfish;
  • 当前的token是$flounder。

实际运行验证(输出与文档示例一致):

>_ commandline -t $flounder >_ commandline -p echo $flounder >&2 >_ commandline -j echo $flounder >&2 | less >_ commandline -b # 或直接 commandline echo $flounder >&2 | less; and echo $catfish

五、打印与分词选项:--cut-at-cursor 与 --tokens-expanded

-c / --cut-at-cursor:只输出光标之前的部分

-c让打印在光标位置截断。配合--tokens-expanded时,输出到最后一个已完成的 token(排除光标所在的 token)。这通常是补全场景下最想要的结果。

文档给出了一个经典组合技巧:想同时拿到"光标前的已完成 token"和"正在输入的 token",可以用两条命令:

commandline --cut-at-cursor --tokens-expanded; commandline --cut-at-cursor --current-token

短写形式:

commandline -cx; commandline -ct

-x / --tokens-expanded:展开后逐行输出参数

-x会对选区执行参数展开(大括号展开、变量展开、通配符展开等),每个展开结果单独一行输出;命令替换不会被展开,而是原样透传。这在补全脚本里极其常用。

底层实现位于write_part()(src/builtins/commandline.rs),关键点是expand_string()以CmdsubstMode::Skip模式执行展开(即跳过命令替换),并设置了COMMANDLINE_TOKENS_MAX_EXPANSION = 512的展开数量上限;如果展开结果超出限制或出现WildcardNoMatch,会回退为输出未展开的原始 token。分词采用Tokenizer的accept_unfinished模式,并会跳过重定向目标 token(如>out中的out)——测试commandline --input "echo {arg1,arg2} <in >out" --tokens-expanded的输出正是echo、arg1、arg2三行(见 tests/checks/commandline.fish)。

已弃用选项

-o、tokenize、--tokens-raw已弃用,文档明确警告"do not use"。它们对应TokenOutputMode::Unescaped与TokenOutputMode::Raw,前者做反转义、后者完全不做展开直接输出原始 token。源码中这三种 token 选项互相排斥(--tokens options are mutually exclusive),测试 tests/checks/commandline.fish 也覆盖了该报错路径。

六、光标与选区信息:--cursor、--selection-start/end、--line、--column

-C / --cursor

  • 不给参数:打印当前光标位置(相对选区的偏移)。
  • 给参数:把光标移动到指定位置。
  • 若同时指定-j、-p或-t,位置是相对对应子串的,而不是相对整个缓冲区。

源码实现里,光标位置会经过range.start.saturating_add_signed(...)换算,并钳制在缓冲区长度以内(src/builtins/commandline.rs)。注意-C与-c(cut-at-cursor)不可组合,测试 tests/checks/commandline.fish 验证了该组合会报invalid option combination。

-B / --selection-start 与 -E / --selection-end

分别打印当前选区的起始和结束位置(无选区时返回错误)。两者不能带位置参数,测试用例commandline --selection-start foo会报too many arguments。

-L / --line 与 --column

  • --line:不给参数时打印光标所在行号(最上面一行是 1);给参数时把光标设置到指定行。
  • --column:不给参数时打印从行首到光标的Unicode 码点偏移(从 1 开始);给参数时把光标设置到指定列。

源码中行号与列号都从 1 开始计数(line/column index starts at 1),列号超过行长度会报column N exceeds line length,行号超过最大行数会报there is no line N——这些边界都在测试中逐一覆盖(tests/checks/commandline.fish)。

七、状态查询选项:判断 shell 当前处于什么状态

这些选项都"只读不改",返回值用于条件判断(0 表示真,非 0 表示假/错误):

选项查询内容源码实现
-S/--search-mode是否正在进行历史搜索rstate.search_mode
-P/--paging-mode是否正在显示 pager(如 Tab 补全列表)rstate.pager_mode
--paging-full-modepager 是否完整显示(无 " more rows")rstate.pager_mode && rstate.pager_fully_disclosed
--is-valid命令行是否语法完整detect_parse_errors()
--showing-suggestion是否正在显示自动历史补全建议reader_showing_suggestion(parser)

--is-valid 的三态返回值

--is-valid是比较特别的选项,它的返回值有三种:

  • 返回 0(真):命令行语法有效且完整,此时按回车(execute绑定函数)会直接执行;
  • 返回 2:命令行不完整(例如echo foo |后面还缺内容);
  • 返回 1:命令行存在语法错误(例如echo $$)。

源码通过detect_parse_errors(buffer, None, accept_incomplete=true)区分:解析成功返回 0,incomplete标记为真的返回 2,其余解析错误返回 1(src/builtins/commandline.rs)。空命令行也视为错误(返回 1)。测试 tests/checks/commandline.fish 完整验证了这三种情况。

--showing-suggestion 的典型用途

--showing-suggestion用于判断当前是否有一条自动补全建议正在显示、等待被forward-*系列绑定消费。文档给出的典型场景是:判断光标在行尾时右移是"没有效果"还是会"接受补全"(forward-char-passive会自动处理这个逻辑)。

八、--search-field 与 --input:操作"非真实"命令行

--search-field

commandline默认操作真实命令行缓冲区;--search-field让操作对象变为pager 的搜索输入框。如果搜索框当前没有显示,命令返回假。源码中会从rstate.search_field取出搜索框文本与光标位置(src/builtins/commandline.rs)。该选项不能与--current-buffer、--tokens-expanded、--insert-smart组合。

--input=INPUT

--input让命令操作给定的字符串,而不是真实命令行。这在非交互式环境里测试、或在脚本中复用--tokens-expanded等解析能力时非常有用——测试文件 tests/checks/commandline.fish 里大量用例都依赖--input才能脱离交互式终端运行。源码注释还提到它是一个"历史遗留、未收录进文档"的选项,实现中会直接把current_buffer替换为INPUT字符串。

九、--function:把输入函数放入队列

-f/--function是绑定脚本中常用的"程序化按键"手段:它把每个参数解释为输入函数(如execute、repaint、forward-char等,完整列表见 bind 命令文档),放入输入队列,让它们先于后续真实按键被读取。

-f不能与任何其他选项组合(-h除外)。源码实现里,每个参数都会经input_function_get_code()转换为ReadlineCmd后通过reader_execute_readline_cmd()入队(src/builtins/commandline.rs);未知函数名会报Unknown input function 'foo',测试 tests/checks/commandline.fish 覆盖了该错误路径。另外,如果当前正处于重绘(repaint)流程中,源码会跳过RepaintMode/ForceRepaint/Repaint类命令以避免无限循环。

仓库内置函数__fish_toggle_comment_commandline(share/functions/__fish_toggle_comment_commandline.fish)就演示了-f的经典组合用法:它用commandline -r $cmdlines把加上#的注释版本写回缓冲区,然后用commandline -f execute让 fish 立即"按键执行":

function __fish_toggle_comment_commandline --description 'Comment/uncomment the current command' set -l cmdlines (commandline -b) if test -z "$cmdlines" set cmdlines (history search -p "#" --max=1) end set -l cmdlines (printf '%s\n' '#'$cmdlines | string replace -r '^##' '') commandline -r $cmdlines string match -q '#*' $cmdlines[1] and commandline -f execute end

十、补全脚本的标准范式:xpc 与 ct 组合

文档明确指出,补全脚本最常用的写法是:

set -l tokens (commandline -xpc)

这行命令的含义可以逐字母拆解:

  • -x:对选区做参数展开,每个参数输出一行;
  • -p:只取当前process(正在被补全的那条命令,不含|之后的其他命令);
  • -c:截断到光标位置。

组合效果是:得到当前进程已被完成的参数列表(逐个展开),但不包含正在输入的那个 token。

如果还想拿到正在输入的 token 本身,追加:

set -l current (commandline -ct)

文档特别提醒:这样拆分之后,不要再用$tokens/$current去手动做前缀匹配,因为 fish 自身有 infix matching(中缀匹配)机制——最好的做法是让补全直接输出所有可能性,把与当前 token 的匹配交给 fish 内置逻辑处理。

这个范式在仓库补全系统中被广泛使用,例如 share/functions/__fish_complete_command.fish 中的set -l ctoken "$(commandline -ct)",以及 share/functions/__fish_list_current_token.fish 中set -l val "$(commandline -t | string replace -r '^~' "$HOME")"(绑定在 Alt-L 上,列出光标下目录的内容)。

十一、更复杂的实战函数拆解

fish_commandline_prepend(share/functions/fish_commandline_prepend.fish)是一个把给定字符串加在命令行开头的完整实战样例,它同时用到了读取、替换、插入和光标定位四种能力:

function fish_commandline_prepend --description "Prepend the given string to the command-line, or remove the prefix if already there" if not commandline | string length -q commandline -r $history[1] # 空命令行时先取上一条历史 end set -l process (commandline -p | string collect) # 读取当前进程文本 set -l to_prepend "$argv[1] " ... set -l cursor_location (commandline -pC) # 读取相对进程的光标位置 ... commandline -pC 0 # 光标移到进程开头 commandline -pi -- "$to_prepend" # 在进程开头插入前缀 commandline -pC (math "max 0,($cursor_location + $length_diff)") # 恢复光标 end

这里可以看到几个要点:commandline -pC是"读取/设置相对当前 process 的光标位置",commandline -pi是"相对当前 process 插入"——也就是-C文档中提到的"配合-j、-p、-t时位置相对子串"的实际应用。

替换历史条目

文档给出的第一个示例是:

commandline -j $history[3]

它把光标所在的job(当前流水线)替换为历史记录的第 3 条。这是-j选区和-r(默认替换)模式结合的典型场景。

十二、与complete -C STRING的协同

文档指出:如果在调用complete -C STRING补全给定字符串的过程中调用commandline,commandline会把STRING视为当前命令行内容。

从源码实现看,这依赖 fish 的transient commandline机制:当parser.libdata().transient_commandline存在时(即正在求值complete --arguments之类的包装补全),commandline读取/写入的都是这个临时缓冲区而非真实命令行(src/builtins/commandline.rs)。唯一例外是:在求值期间设置光标位置(--cursor带参数)尚不被支持,会报setting cursor while evaluating 'complete --arguments' is not yet supported。

十三、注意事项与常见错误

综合文档、源码与测试用例,使用commandline时有以下几点容易踩坑:

  1. 只能在交互式环境使用:fish -c 'commandline foo'会报Can not set commandline in non-interactive mode。要在脚本/测试中使用,请搭配--input。
  2. 选项组合限制严格:-f不能与其他选项组合;--tokens-*三类互相排斥;-c与-C不能组合;--is-valid系列状态查询与读取/写入类选项不能混用。源码中都有显式校验并返回STATUS_INVALID_ARGS。
  3. --cut-at-cursor和 token 选项不能用于"设置"场景:当同时给出位置参数(要写入内容)时报--cut-at-cursor and token options can not be used when setting the commandline。
  4. 行号/列号从 1 开始:传入 0 会报错,超出范围(如列号超过行长度)也会报错。
  5. 空命令行在--is-valid下是错误:返回 1,而不是不完整(2)。
  6. -o、tokenize、--tokens-raw已弃用:新代码一律使用-x/--tokens-expanded。

小结

commandline是 fish 交互层能力的枢纽:它同时扮演"读取器"(buffer/job/process/token 分片读取、展开分词、状态查询)和"写入器"(替换/插入/追加/光标定位),并深度参与了complete -C的 transient 补全流程。掌握它的选项矩阵与作用范围语义,是编写高质量 fish 补全脚本和自定义绑定函数的基础。若需继续深入,可研读其实现 src/builtins/commandline.rs、官方文档 doc_src/cmds/commandline.rst,以及覆盖了各选项错误路径与正常行为的测试套件 tests/checks/commandline.fish,同时可参考 bind 命令文档 了解可入队的输入函数全集。

  • CLI
  • 开发工具

【免费下载链接】fish-shell

The user-friendly command line shell.

项目地址:https://gitcode.com/GitHub_Trending/fi/fish-shell
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询