
CLI开发工具【免费下载链接】fish-shellThe user-friendly command line shell.项目地址https://gitcode.com/GitHub_Trending/fi/fish-shell点击查看免费下载read是 fish-shell 内置命令用于从标准输入读取一行或多行文本并按指定的规则切分后存入 shell 变量它还支持交互式读取自定义提示符、密码掩码、shell 代码输入模式以及“标准输入直通标准输出”的命令替换模式。读完本文你将完整掌握read的全部选项、作用域规则、分词机制、读取上限保护并能结合源码理解其底层实现在脚本中写出可靠、安全的输入读取代码。本文以官方文档 doc_src/cmds/read.rst 为骨架结合 src/builtins/read.rs 实现与 tests/checks/read.fish 测试用例进行深度展开。命令概览与基本行为read [OPTIONS] [VARIABLE ...]read从标准输入读取数据并存入 shell 变量。在另一种模式下它也可以把输入直接打印到自己的标准输出常用于命令替换command substitution。默认情况下read读取一行然后按空格或制表符把它切分成多个 token依次赋给命令行中列出的各个变量。也可以改用 NUL 字符或指定最大字符数来终止输入还可以自定义其它分隔符。与 bash 等 shell 不同fish 的read没有默认变量例如 bash 的REPLY。如果读取的结果无处存放它会被直接打印到标准输出。退出状态当read遇到文件末尾EOF而不是预期的终止符时退出状态为1其他情况下为0参数错误非法选项、变量数不符等时状态为2见 src/builtins/shared/misc.rs 中STATUS_INVALID_ARGS读取超过字节上限时状态为122STATUS_READ_TOO_MUCH。从源码看这些状态码分别在 src/builtins/shared/misc.rs 中定义为STATUS_CMD_ERROR 1、STATUS_INVALID_ARGS 2、STATUS_READ_TOO_MUCH 122。变量设置与作用域规则如果read设置了某个变量而你没有显式指定作用域它遵循与 set 相同的规则如果变量已存在就复用该变量在最低作用域中如果不存在则创建一个不导出的函数级变量。关于这一点tests/checks/read.fish 中的function-scoped-read测试给出了直观验证在函数内read --function skamtebord得到 local scope 变量而嵌套begin ... end块内不指定作用域的read同样落入 local scope。变量作用域与属性选项与 set 一致以下选项控制变量作用域或属性语义与 set 对应选项完全一致选项说明-U,--universal设置通用universal变量。变量会立即对机器上该用户的所有 fish 实例可见并在 shell 重启后持久保存。-f,--function设置函数作用域变量。函数结束时该变量被清除。-l,--local在当前块内设置局部变量。块结束时被清除在块外使用时与--function等价。-g,--global设置全局变量。同一 shell 内所有函数都能访问可修改或清除。-u,--unexport阻止变量被导出到子进程默认行为。-x,--export将变量导出到子进程。在源码 src/builtins/read.rs 的parse_cmd_opts中这些选项直接写入Options.set_mode.mode分别置位function/global/local/universal/export标志与set共用同一套ParserEnvSetMode机制。同时validate_read_args会做两项约束校验作用域互斥local、function、global、universal四者中只能选一个否则报错read: scope can be only one of: universal function global local导出互斥-x与-u不能同时出现否则报错read: cannot both export and unexport。-Uuniversal与-f/-l/-g之间的互斥同样在作用域校验中生效。测试用例echo foo | read -lf var与echo foo | read -lxu var分别验证了这两类错误。交互模式选项当标准输入是终端tty时read会进入交互模式借用 fish 的行编辑器读取输入。以下选项控制交互行为选项说明-cCMD,--commandCMD设置交互模式命令缓冲区中的初始字符串为CMD。-s,--silent屏蔽写入终端的字符用星号代替显示适合读取密码等敏感信息。-pPROMPT_CMD,--promptPROMPT_CMD以 shell 命令PROMPT_CMD的输出作为交互模式的提示符。默认提示符命令为set_color green; echo -n read; set_color --reset; echo -n 即绿色的read后跟。-PPROMPT_STR,--prompt-strPROMPT_STR以字面字符串PROMPT_STR作为提示符。-RRIGHT_PROMPT_CMD,--right-promptRIGHT_PROMPT_CMD以 shell 命令RIGHT_PROMPT_CMD的输出作为右侧提示符默认没有右侧提示符。-S,--shell启用语法高亮、Tab 补全以及适合输入 shell 脚本代码的命令终止行为。注意fish 3.0 之前--shell的短选项是-s后为了兼容 bash 中-s表示--silent而改为-S。关于默认提示符的源码佐证默认提示符字符串定义在 src/builtins/shared/misc.rs 的DEFAULT_READ_PROMPT常量中与文档描述逐字一致。另外validate_read_args中会检查-p与-P不能同时使用报错Options -p and -P cannot be used together且-P会被内部实现为echo 转义后的字面串的提示命令。交互读取的底层实现在 src/builtins/read.rs 的read_interactive中read会构造一个ReaderConfig开启highlight_ok、complete_ok、syntax_check_ok关闭自动补全与缩写设置in_silent_mode处理密码掩码然后调用reader_pushreader_readline复用 fish 的reader模块。交互读取只使用内存态历史记录MemoryHistoryId::BuiltinRead不会写入历史文件。注意交互模式只在标准输入是 tty 且未使用-zNUL 终止时生效。若 stdin 不是 tty例如管道输入read退化为非交互读取——tests/checks/read.fish 中专门有一个回归测试#4206echo abc\ndef | $fish -i -c read a; read b; ...验证非交互读取正常工作。读取与存储选项以下选项控制“读多少、怎么存”选项说明-dDELIMITER,--delimiterDELIMITER按DELIMITER切分。DELIMITER是一整个字符串可多字符而非字符集合。-nNCHARS,--ncharsNCHARS读取NCHARS个字符后返回或读到行尾即返回取先到者。-t,--tokenize按 shell 的 token 化规则切分输入遵循引号与转义。-t / --tokenize会做引号移除例如a\ b存为a b但不会展开变量与命令替换。--tokenize-raw与--tokenize相同的切分规则但不做引号移除保留原始 token 文本。-a,--list把切分结果作为列表存入唯一一个变量旧名--array保留用于向后兼容。-z,--null用 NUL 字符而不是换行标记行尾。此选项同时会禁用交互模式。-L,--line把输入逐行读入各个变量直到每个变量都被填满为止。不能与--null或--delimiter等切分选项组合。关于 --nchars 的边界-n 0表示不设上限会读完整行测试echo test | read -n 0 foo得到test负数报错read: -1: invalid integer超出usize范围的数字报错read: Argument 2147483648 is out of range源码通过fish_wcstoi解析并校验。关于 --tokenize 与 --tokenize-raw两者在源码中分别对应TokenOutputMode::Unescaped与TokenOutputMode::Raw。它们互相排斥同时指定会报错--tokenize and --tokenize-raw are mutually exclusive且与--delimiter、--line互斥分别报错--delimiter --tokenize: options cannot be used together、--line --tokenize-raw: options cannot be used together。token 化基于 fish 的Tokenizer实现因此不遵循IFS与 fish 分词器一致。分词规则默认 IFS 与各种切分模式在不带--line选项时read从标准输入读取一行切成 token然后按顺序把每个 token 赋给VARIABLES中列出的变量。如果 token 数量多于变量数量剩余的完整部分全部赋给最后一个变量如果变量多于 token剩余变量被设置为空源码clear_remaining_vars会把这些变量set_empty。IFS 与切分如果没有显式给出切分选项如--delimiter、--line、--tokenizeread使用变量IFS作为要切分的字符集合。文档明确指出依赖IFS的行为已被弃用未来版本将移除。IFS的默认值包含空格、制表符和换行符。特殊情形如果IFS被设置为空字符串则输入中的每个字符都被视为一个独立 token。源码实现src/builtins/read.rs中IFS为空串时走每字符一个 token分支非列表模式下前几个变量各得一个字符最后一个变量获得剩余全部字符列表模式下每个字符成为列表的一个元素。测试用例印证了这一点set -l IFS echo hello | read -l one two # $one h$two ello echo hello | read -la ary # $ary (h e l l o)另外注意多字符IFS的行为与-d不同IFS是字符集合而-d是整个字符串。测试中set -l IFS ...时echo a...b...c | read -l a b c得到$aa、$bb、$c..c剩余部分带前缀..而用-d ...时$cc。--line逐行读取带--line时read从标准输入为每个给定的变量读取一行行不做 token 化直到所有变量都被填满。例如begin echo 1 echo 2 end | read -l --line foo bar # $foo 1$bar 2从源码看--line被实现为“以换行符为分隔符、重复 N 次read”opts.delimiter \n每次循环填一个变量直到vars_left() 0退出。互斥组合速查--delimiter与--line不可同时使用-z与--line不可同时使用--tokenize/--tokenize-raw与--delimiter、--line不可同时使用-a/--list与--array是同一选项只能配合一个变量名使用read -a v1 v2报错read: expected 1 arguments; got 2。无变量特殊模式stdin → stdout 直通如果不提供任何变量名read进入特殊模式把标准输入重定向到标准输出非常适合命令替换场景。例如下面的命令可以从控制台读取密码而不把它硬编码在命令行里从而避免密码出现在 fish 的历史记录中mysql -uuser -p(read)在此模式下read不做任何切分文本不加任何处理、原样输出到标准输出。测试用例也验证了“无变量的 read 不是错误”echo banana (echo sausage | read)输出banana sausage。读取上限保护与 fish_read_limit为了防止 shell 消耗过多系统资源read只会消费有限数量的字节。原文档记载的默认上限为 100 MiB104857600 字节如果在这个上限到达前还没遇到终止符则变量被置为空退出状态设为122。注意以当前仓库源码为准在 src/env/mod.rs 中当前默认值DEFAULT_READ_BYTE_LIMIT定义为1024 * 1024 * 10241 GiB按字节计非宽字符数其注释明确写着“Limitreadto 1 GiB by default”。由此可以推断官方文档中“100 MiB”的表述相对当前源码已过时实际默认上限请以源码与发行版行为为准。该上限可通过环境变量fish_read_limit调整定义见 doc_src/language.rst设置为0表示取消限制设置为其它正整数则覆盖默认上限未设置或设为空串时恢复默认值。源码 src/env_dispatch.rs 的handle_read_limit_change负责监听$fish_read_limit的变化变量非空时用fish_wcstoul解析并写入原子变量READ_BYTE_LIMIT解析失败会输出警告并忽略变量缺失/为空时恢复为DEFAULT_READ_BYTE_LIMIT。CHANGELOG 中还记载了“fish_read_limit被 unset 时 read 限制恢复到默认值”issue #9129。在读取逻辑中src/builtins/read.rs两种读取路径都会检查该上限read_in_chunksfd 可 seek 或 stdin 被直接重定向时按 128 字节块读取中累计字节数超过READ_BYTE_LIMIT即返回STATUS_READ_TOO_MUCHread_one_char_at_a_time非 seek 管道逐字符读取同样在累计字节超过上限时截断并返回 122。tests/checks/read.fish 用set fish_read_limit 8192做了一组完整测试读超过上限的数据时状态为 122 且变量为空、有--nchars时同样受限、恰好读到上限时成功、以及通过head -c控制输入量来验证上限边界。完整示例read有多种独立用途以下示例全部来自官方文档。读取一行存入变量echo hello | read foo # $foo hellowhile 循环逐行处理命令输出while 是逐行处理命令输出的优雅方式printf %s\n line1 line2 line3 line4 | while read -l foo echo This is another line: $foo end-d 分隔符是一整个字符串echo abc | read -d -l a b c echo $a # a echo $b # b echo $c # c--tokenize 遵循引号与转义echo a\ b | read -t first second echo $first # 输出 a b$second 为空 echo afoo barb (command echo wurst)* {a,b} | read -lt -l a b c echo $a # 输出 afoo barb去掉引号 echo $b # 输出 (command echo wurst)* {a,b}去掉引号 echo $c # 空第二个例子说明--tokenize会移除引号并把afoo barb合并为afoo barb但保留(command echo wurst)*与{a,b}的字面文本不展开命令替换与花括号只把整个剩余部分交给最后一个变量。列表模式与 null 模式echo one two three | read -la ary # $ary (one two three) echo -ne foo\0bar\0baz | while read -lza foo echo got: $foo end # 第一轮 $foo (foo bar)第二轮 (baz)其中-znull 终止适合处理包含换行的数据流-a则把 token 收进单一列表变量。实战交互式查询用户输入官方文档 doc_src/language.rst 的 “Querying for user input” 章节给出了一个完整的交互式确认案例是read交互选项的集大成示范。下面这个函数会 glob 所有参数目录下的文件若文件数超过 5 个且 stdin/stdout 都是终端则反复询问用户直到输入 y/Y 或 n/Nfunction show_files # 在所有参数上 glob非目录会被忽略 set -l files $argv/* # 如果文件超过 5 个 if test (count $files) -gt 5 # 并且 stdin读取输入和 stdout写提示符都是终端 and isatty stdin and isatty stdout # 反复询问直到得到有效回答 while read --nchars 1 -l response --prompt-strAre you sure? (y/n) or return 1 # 若 read 被 ctrl-c/ctrl-d 中止则返回 switch $response case y Y echo Okay # 跳出 while继续执行函数 break case n N # 不打印直接返回 echo Not showing return 1 case * # 回到 while 循环重新询问 echo Not valid input continue end end end # 最后打印文件 printf %s\n $files end这段代码的关键点--nchars 1让用户只按一个键即可作答无需回车--prompt-str直接指定字面提示符避免引号与转义问题while read ... or return 1利用read在 EOF/中断时的非零退出状态优雅退出isatty stdin/isatty stdout保证在管道环境下如show_files / | cat自动跳过询问直接输出行为与交互终端不同。源码与测试读一遍实现心里更有底实现入口src/builtins/read.rs 中的read()函数。整体流程为parse_cmd_opts解析选项 →validate_read_args校验参数与互斥组合 → 根据 stdin 是否 tty / 是否 seekable / 是否指定nchars选择read_interactive、read_in_chunks或read_one_char_at_a_time三种读取路径 → 按 token 模式或分隔符模式把缓冲区切分并set_var_and_fire到各变量。三种读取路径read_interactivestdin 是 tty 且未用-z时调用复用 reader 模块的行编辑器支持提示符、右侧提示符、--shell高亮补全与--silent掩码read_in_chunksfd 可 seek或 stdin 被直接重定向且未指定nchars时按 128 字节块读取READ_CHUNK_SIZE命中分隔符后通过lseek把多余字节“还”回去供下一次read继续消费read_one_char_at_a_time管道等不可 seek 的 fd 逐字节读取并做 UTF-8 解码用于-n字符数或非 seek 场景。默认提示符src/builtins/shared/misc.rs 的DEFAULT_READ_PROMPT。字节上限src/env/mod.rs 的DEFAULT_READ_BYTE_LIMIT与原子变量READ_BYTE_LIMITsrc/env_dispatch.rs 的handle_read_limit_change监听$fish_read_limit。状态码src/builtins/shared/misc.rs 中STATUS_CMD_ERROR 1、STATUS_INVALID_ARGS 2、STATUS_READ_TOO_MUCH 122。测试覆盖tests/checks/read.fish 覆盖了切分默认/IFS 空串/-d多字符/-ttokenize/-znull、-n边界、--line、--list、--tokenize-raw、读上限 122、只读变量保护、非法组合报错、非交互读取回归#4206以及函数/局部作用域等场景是排查read行为问题的第一手资料。小结fish 的read内置命令远不止“读一行赋值”这么简单通过-U/-f/-l/-g/-x/-u可以精确控制变量的作用域与导出属性通过-p/-P/-R/-c/-s/-S可以打造完整的交互式输入体验通过-d/-t/--tokenize-raw/-a/-z/-L可以按字符串、shell token、NUL、逐行或列表等多种粒度切分输入而无变量名模式则让read成为命令替换中安全读取密码等敏感数据的利器。配合fish_read_limit与 122 状态码的资源保护机制read在生产脚本中既灵活又安全。要深入掌握某个细节建议直接阅读 src/builtins/read.rs 实现并以 tests/checks/read.fish 为行为基准进行验证。赞分享CLI开发工具【免费下载链接】fish-shellThe user-friendly command line shell.项目地址https://gitcode.com/GitHub_Trending/fi/fish-shell点击查看免费下载相关推荐Gleam标准输入输出控制台交互技术Gleam标准输入输出控制台交互技术 概述 Gleam是一种用于构建类型安全、可扩展系统的友好型编程语言其标准输入输出Standard I/O系统提供了编程语言编译器fish shell commandline 内建命令完全指南读取与改写命令行缓冲区fish shell commandline 内建命令完全指南读取与改写命令行缓冲区 commandline 是 fish shell 中一个专为交互式场景设CLI开发工具Bash 用户输入read 命令实战指南让 Bash 脚本与用户交互Bash 用户输入read 命令实战指南让 Bash 脚本与用户交互 本篇文章以开源电子书《Introduction to Bash Scripting》文档教程上一篇Nx 受影响项目Affected Projects实战指南用 nx show projects --affected 精准定位变更影响范围下一篇ToolJet Table 组件服务端过滤Server Side Filter完整实现指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考