了解一下关于原始模式, 管道, 标准输出等的概念吧
同系列传送门: rust-tui
注意:
这些概念已经尽量简化过了, 因为只是写一个 tui 程序的话不必太过深入, 但可能还是会忍不住扯开去, 见谅
你可以观看来自 gnu 官网的原文, 里面有对此的解释
在 POSIX 系统中, 有着两种输入模式: canonical 或者 noncanonical
终端 IO 存在着这两种主要的模式:
canonical: 规范模式, 也叫作 cooked-mode, 是最常见的模式
在该模式下, 会以行为单位, 读取输入(read函数), 在终端输入的字符被保存在缓冲区中
当输入行结束符(换行/EOF)时, 缓冲区的内容才会被发送到对应的处理程序
特殊字符不会被无视, 换行符也会像普通字符一样被缓冲区接收, 但 EOF 不会
nocanonical: 非规范模式, 也叫作 raw-mode, 也是非常常见的模式
类似 ERASE 与 KILL 这些特殊的编辑字符会被禁用, 每次输入都会读取, 而不再是基于 line 为单位
比如 vim, 或者我现在正在使用的 helix-editor, 就处于该模式下, 方便程序捕获并独占你的每份输入, 方便处理
ratatui 中位于 into_raw_mode
的文档, 对于原始模式下的输入的描述如下:
原始模式(raw-mode), 意味着你所输入的标准输入不会被打印/不会显示在屏幕上(它必须由程序手动编写)
此外, 输入不是 canonical 或 buffered 的, 也就是说, 您可以一次读取一个字节
简单来说, 原始模式让我们可以逐一捕获用户的输入, 便于对其进行处理
并且禁用且无视了特殊的字符, 进一步增强了我们程序的权能与对终端的掌控权限
(想想 vim 的操作, 再想想你在 bash 输入命令, 对比一下两者的区别)
在 Linux 系统中, 万物皆文件, 不管是进程也好, 还是你的设备也好, 都是文件, 且有一个对应的文件描述符(file descriptor)
这个文件描述符, 可以看作是系统为了追踪打开的文件而分配的唯一标号, 通过它对文件进行读写
Linux 在启动时, 会创建一个 init 进程, 此时自动创建 3 个特殊的文件描述符, 对应 3 个设备IO文件
3 个特殊的文件描述符:
文件 | 含义 | 描述符 |
---|---|---|
/dev/stdin | 标准输入 | 0 |
/dev/stdout | 标准输出 | 1 |
/dev/stderr | 标准错误 | 2 |
而之后产生的进程, 则都是 init 进程的子进程(一颗多叉树), 子进程会继承父进程的文件描述符
因此, 你打开的 shell(bash, zsh, fish, nushell等), 因为是子进程, 自然会有这三个文件描述符
简单理解:
之后的子进程中所打开的文件, 文件描述符则从 3 开始往上递增
重定向(redirect), 光听名字就晓得什么意思了, 与网页的重定向一个道理
我们的 tui 程序, 就可以使用 管道(pipeline, 重定向的一种) 来跟更多的其他程序进行交互, 之后会讲
举个例子, 当你执行程序时, 从 stdin(键盘) 获取输入, 成功则将结果输出到 stdout(屏幕), 失败则将报错输出到 stderr(屏幕) 上
这是你没有指定任何重定向时的默认行为, 你会发现 stdout/stderr 都会输出到屏幕上, 极其容易造成混乱
让我们用 rust 来进行一点演示, 首先创建项目并跳转
cargo new --bin demo
cd demo
然后进行编辑:
fn main() {
println!("This is a str, which will be outputted to stdout");
eprintln!("This is a str, which will be outputted to stderr");
}
当你运行 cargo run --quiet
, 屏幕上将显示下列内容:
This is a str, which will be outputted to stdout
This is a str, which will be outputted to stderr
println!
对应的应该是我们正常的输出, 而 eprintln!
应该对应一些报错信息, 可是它们同时出现了
这太混乱了, 当向 stdout 与 stderr 大量输出时更是如此, 我们要的是将 "正确的输出" 与 "报错的信息" 分开!
请对比以下三条命令(在bash中):
This is a str, which will be outputted to stdout
This is a str, which will be outputted to stderr
This is a str, which will be outputted to stderr
This is a str, which will be outputted to stderr
This is a str, which will be outputted to stdout
简单地了解下就差不多了, 其实还有很多内容, 不过懒得展开了, 知道两者区别就够了
本节最后使用 nushell(类似bash/zsh的现代shell) 再展示一下:
┌───────────┬──────────────────────────────────────────────────┐ │ stdout │ This is a str, which will be outputted to stdout │ │ │ │ │ stderr │ This is a str, which will be outputted to stderr │ │ │ │ │ exit_code │ 0 │ └───────────┴──────────────────────────────────────────────────┘
nushell 可以直接帮我们结构化这段信息, 因此我放上来方便小伙伴们理解
再来一个例子, 当使用 cat 命令时传入一个不存在的文件:
┌───────────┬───────────────────────────────────────────────┐ │ stdout │ │ │ │ │ │ stderr │ cat: non-exist.txt: No such file or directory │ │ │ │ │ exit_code │ 1 │ └───────────┴───────────────────────────────────────────────┘
而如果使用 bash, 自然就无法这么简单地得到如此结构化的数据了:
cat: non-exist.txt: No such file or directory
此处以 bash 中的管道(pipeline)为例
管道命令的操作符时 "|", 形式为 command_1 | command_2
, 表示将前者的输出作为后者的输入, 即 stdout -> stdin
使用管道时, 两个命令会依次被调用,
值得注意的有两点:
来点例子, 假设存在这么一个文件 README.md
:
57
cat: REDM.m: No such file or directory
0
再来点例子:
Documents Downloads Music Pictures Videos Trash
管道与重定向的区别:
管道在 tui 程序中的常见用途:
假设我们的 tui 程序, 想在执行的时候将结果交给外部的命令, 我们就可以通过管道来支持这一行为
比如, 我们做了一个超级小的终端文件浏览器demo, 类似 ranger/nnn/xplr/yazi
, 在选择文件按下回车后, 能获得其 path
假设这个程序叫作 file_chooser
, 我们想将获取的路径传递给其他程序, 类似这样:
file_chooser | vim
获取的文件路径, 通过管道传递给了 vim, 然后可以在 vim 中编辑这个文件
但请注意, 我们的 file_chooser 是 tui 程序, 也是一堆字符所组成的, 如果你让这些字符(ui), 渲染输出到了 stdout 中......
那通过管道传递的, 可就不止是你真正想传递给外部的文件路径了! 还有你的一大堆 ui 字符!
真是可怕啊......不过解决办法非常简单, 你只要在 stderr 中渲染你的 ui 界面就阔以了
所以我才在 p2 中提过一嘴, 为什么那个模板是使用了 stderr, 而不是使用 stdout
上一篇: p2~> 基础架构