Linux Terminal Mode | canonical / nocanonical / cbreak / raw
注:本文为 “Linux 终端模式” 相关文章合辑。
略作重排,如有内容异常,请看原文。
终端输入输出的三种模式
guidao
1 前言
在进行项目开发时,需要实时读取终端输入(无需按下 Enter 键即可读取)。然而,Go 语言并未提供便捷的接口。为此,查阅了相关资料,对终端的 I/O 进行了深入研究,并将关键内容整理如下。
2 终端 I/O 的三种模式
2.1 Canonical 模式
该模式也称为 cooked 模式 。在这种模式下,终端每次返回一行数据,所有特殊字符均会被解释(例如:^C)。
2.2 Nocanonical 模式
此模式亦称 raw 模式 。在这种模式下,终端每次返回一个字符,而非先收集一行数据再返回。特殊字符不会被特殊处理,需自行处理。例如,终端编辑器 vi 即处于这种模式下,可完全控制输入输出字符。
2.3 Cbreak 模式
该模式与 raw 模式相似,但会处理特殊字符(某些场景下需使用此模式)。
3 终端控制结构
终端设备的所有可控制属性均通过以下结构进行管理。Go 语言中,该结构定义于 syscall 包中。
struct termios {tcflag_t c_iflag; /* 输入标志 */tcflag_t c_oflag; /* 输出标志 */tcflag_t c_cflag; /* 控制标志 */tcflag_t c_lflag; /* 本地标志 */cc_t c_cc[NCCS]; /* 控制字符 */
};
其中,c_iflag 控制输入属性(例如:将 CR 映射为 NL),c_oflag 控制输出属性(例如:将 tab 扩展为空格),c_cflag 控制行属性,c_lflag 设置用户与设备接口属性(例如:本地回显、允许信号产生)。该结构通过两个函数进行获取与设置,函数声明如下:
#include <termios.h>
int tcgetattr(int filedes, struct termios *termptr);
int tcsetattr(int filedes, int opt, const struct termios *termptr);
% 返回值:若成功则返回 0,若失败则返回 -1
filedes 是文件描述符,通常通过打开设备文件 /dev/tty 获得。tcsetattr 函数中的 opt 参数可取以下值:
TCSANOW:使设置立即生效。TCSADRAIN:在输出缓存区输出到屏幕后生效。TCSAFLUSH:在输出缓存区输出到屏幕后生效,并且丢弃所有输入缓存中未处理的数据。
4 纯 Go 实现一个 getchar(可立即获取用户输入而无需按下 Enter 键)
package mainimport ("fmt""os""syscall""unsafe"
)% 通过系统调用设置属性
func ioctl(fd, request, argp uintptr) error {if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, fd, request, argp, 0, 0, 0); e != 0 {return e}return nil
}% 获取设备属性
func Tcgetattr(fd uintptr, argp *syscall.Termios) error {return ioctl(fd, syscall.TIOCGETA, uintptr(unsafe.Pointer(argp)))
}% 设置终端
func Tcsetattr(fd, opt uintptr, argp *syscall.Termios) error {return ioctl(fd, opt, uintptr(unsafe.Pointer(argp)))
}func main() {var term syscall.Termiosvar origin syscall.Termiosfd, err := os.Open("/dev/tty")must(err)err = Tcgetattr(fd.Fd(), &term)origin = termmust(err)% 设置为nobufferterm.Lflag &^= syscall.ICANONterm.Cc[syscall.VMIN] = 1term.Cc[syscall.VTIME] = 0Tcsetattr(fd.Fd(), syscall.TIOCSETA, &term)c, err := ReadChar(fd.Fd())must(err)fmt.Println(" read:", string(c))% 恢复原来的设置Tcsetattr(fd.Fd(), syscall.TIOCSETA, &origin)
}func ReadChar(fd uintptr) ([]byte, error) {b := make([]byte, 4)n, e := syscall.Read(int(fd), b)if e != nil {return nil, e}return b[:n], nil
}
func must(err error) {if err != nil {panic(err)}
}
5 参考资料
- http://www.lafn.org/~dave/linux/terminalIO.html
终端的 Raw Mode
Erzbir
2025-02-10
Raw Mode
在启动 Terminal 时,默认是以 canonical mode 或者叫 cooked mode 启动的。这种模式下,用户按键产生的字符会由终端驱动存入内部缓冲区,期间可以自动处理 Backspace 、Ctrl-C 等特殊字符,需要等待用户按下 Enter 后才将字符从缓冲区中送到程序的标准输入(STDIN_FILENO 为 0)。
例如下面这个程序:
#include <unistd.h>int main() {char c;while (read(STDIN_FILENO, &c, 1) == 1) {write(STDOUT_FILENO, &c, 1);}return 0;
}
终端默认是 canonical mode ,所以会在按下 Enter 后,从标准输入中每次读取一个字节到 c ,直到 0 个字节可读。
这也是常用的一种模式,输入命令及参数回车,然后 shell 就会解释我们的命令。但是你可能见过一些命令行程序比如 top ,按下 q 后并没有 Enter 就直接退出了,这就需要 raw mode 让我们自己来处理这些输入。
下面这个例子仍然是 canonical mode 的,也是通过 q 来退出,但是需要 Enter :
#include <unistd.h>int main() {char c;while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {write(STDOUT_FILENO, &c, 1);}return 0;
}
但是这样的问题就在于和预期稍微有些差别,我们不仅要 Enter ,而且那之后的字符可能是 wqeqweq 这样,而这个程序是读到一个 q char 就会退出。
而在 raw mode 下,没有回显,不会处理特殊字符,也没有缓冲区。
关闭回显
要切换到 raw mode 首先是关闭回显,需要用到 termios.h 提供的 tcgetattr() 函数以及 tcsetattr() 函数。
这两个函数的定义是这样的:
% The tcgetattr() function copies the parameters associated with the terminal referenced by fildes in the termios structure referenced by termios_p.
% This function is allowed from a background process; however, the terminal attributes may be subsequently changed by a foreground process.int tcgetattr(int, struct termios *);% The tcsetattr() function sets the parameters associated with the terminal from the termios structure referenced by termios_p.
% The optional_actions field is created by or'ing the following values, as specified in the include file ⟨termios.h⟩.int tcsetattr(int, int, const struct termios *);
通过下面这三个语句就可以获取到当前的状态:
struct termios raw;
tcgetattr(STDIN_FILENO, &raw);
printf("%lu", raw.c_lflag);
我们需要的就是这个 c_lflag ,将这个 flag 进行位运算后再调用 tcsetattr() 设置即可:
void enable_raw_mode() {struct termios raw;tcgetattr(STDIN_FILENO, &raw);raw.c_lflag &= ~(ECHO);tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
这样就关闭了回显,运行之后输入字符就不会再有回显。如果你发现还是有回显,确保在终端运行,而不是用 IDE 的 run 来运行或是其他的一些 wrap。
切换到 Raw Mode
我们已经关闭了回显,现在还需要禁用缓冲区和禁用特殊字符处理等等操作,下面一步一步来。
禁用 SIGINT 和 SIGSTP
ISIG 控制是否发送 SIGINT 和 SIGSTP 。
void enable_raw_mode() {struct termios raw;tcgetattr(STDIN_FILENO, &raw);raw.c_lflag &= ~(ECHO | ICANON | ISIG);tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
现在,不需要回车且 Ctrl-C 和 Ctrl-Z 的输入已经不起作用。
禁用输出流控^Software flow control^
通过 flag: c_iflag 的 IXON 控制。
void enable_raw_mode() {struct termios raw;tcgetattr(STDIN_FILENO, &raw);raw.c_lflag &= ~(ECHO | ICANON | ISIG);raw.c_iflag &= ~(IXON);tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
禁用 Ctrl-V
通过 c_lflag 的 IEXTEN 控制。
void enable_raw_mode() {struct termios raw;tcgetattr(STDIN_FILENO, &raw);raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);raw.c_iflag &= ~(IXON);tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
关闭换行映射
c_lflag ,ICRNL 。
\r 的 char 值是 13 ,\n 的 char 值是 10 。
为了消除不同操作系统之间换行符的差异,默认开启映射,可能会将 \r 自动转换成 \n ,也就是说 Ctrl-J 和 Ctrl-M 会是一样的值,而 Ctrl-M 原本是 13 。
void enable_raw_mode() {struct termios raw;tcgetattr(STDIN_FILENO, &raw);raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);raw.c_iflag &= ~(IXON | ICRNL);tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
关闭输出处理
c_oflag ,OPOST 。
开启时终端会将换行符 \n 转换成回车符后跟一个换行符:\r\n 。
void enable_raw_mode() {struct termios raw;tcgetattr(STDIN_FILENO, &raw);raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);raw.c_iflag &= ~(IXON | ICRNL);raw.c_oflag &= ~(OPOST);tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
实际上还需要更多的操作,比如通过结构体中的 c_cc 来设置 read() 返回所需要的最小字节数以及读超时时间等:
void enable_raw_mode() {struct termios raw;tcgetattr(STDIN_FILENO, &raw);% 修改 raw.c_lflag 来禁用终端的一些本地模式% 禁用 ECHO: 关闭回显% 禁用 ICANON: 关闭规范模式 (行缓冲), 使得输入按字符处理% 禁用 IEXTEN: 禁用扩展输入处理% 禁用 ISIG: 禁用信号生成 (例如 Ctrl+C 不再生成 SIGINT 信号)raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);% 修改 raw.c_iflag 来禁用某些输入处理% 禁用 BRKINT, ICRNL, INPCK, ISTRIP, IXON 等标志raw.c_iflag &= ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP);% 修改 raw.c_oflag 来禁用输出处理 (比如自动转换换行符)raw.c_oflag &= ~(OPOST);% 修改 raw.c_cflag" 设置字符大小为 8 位 (通常为 CS8)raw.c_cflag |= (CS8);% 设置控制字符% VMIN = 0: read() 至少读取 0 个字节% VTIME = 1000: read() 超时时间为 1000msraw.c_cc[VMIN] = 0;raw.c_cc[VTIME] = 1000;tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}int main() {char c;enable_raw_mode();while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {if (c == '\r' | c == '\n') {write(STDOUT_FILENO, "\r\n", 2);}write(STDOUT_FILENO, &c, 1);}return 0;
}
这里就设置了 1000 秒后如果没有读取到 read() 就会直接返回,如果 c_cc[VMIN] 为 1 则是会阻塞到读取到一个字节为止。
BRKINT:当检测到 break 条件时触发中断行为或产生错误,帮助捕捉和处理输入中断。INPCK:启用输入奇偶校验,用于检测数据传输中的错误。ISTRIP:对输入数据进行剥离,将每个字节的最高位清零,确保只保留 7 位数据。
直接切换到 Raw Mode
实际上 termios.h 中有提供一个直接切换的 API :cfmakeraw() 。
这个函数只需要传入一个 struct termios ,会修改结构体中的值就像我们上面做的那样。
void enable_raw_mode() {struct termios raw;cfmakeraw(&raw);tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
使用其他语言
使用其他语言就要用到 FFI 了,比如下面使用 Rust ,好在 Rust 提供了一个 libc 库可以很容易做到:
fn enable_raw_mode() {unsafe {let mut termios = {std::mem::zeroed()};libc::tcgetattr(libc::STDIN_FILENO, &mut termios);libc::cfmakeraw(&mut termios);libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &termios);}
}
这只是一个最简单的例子。
下面是纯 FFI 的例子:
#[repr(C)]
#[derive(Copy, Clone)]
struct Termios {c_iflag: u32,c_oflag: u32,c_cflag: u32,c_lflag: u32,cc_c: [u8; 20],c_ispeed: u32,c_ospeed: u32,
}extern "C" {fn tcgetattr(fd: i32, termios: &mut Termios) -> i32;fn tcsetattr(fd: i32, command: i32, termios: &Termios) -> i32;fn cfmakeraw(termios: &mut Termios);
}const STDIN_FILENO: i32 = 0;
const TCSAFLUSH: i32 = 2;fn enable_raw_mode() {unsafe {let mut termios = std::mem::zeroed();cfmakeraw(&mut termios);tcsetattr(STDIN_FILENO, TCSAFLUSH, &mut termios);}
}
via:
-
终端输入输出的三种模式
https://guidao.github.io/termio.html -
终端的 Raw Mode
https://erzbir.com/archives/terminal-raw-mode
相关文章:
Linux Terminal Mode | canonical / nocanonical / cbreak / raw
注:本文为 “Linux 终端模式” 相关文章合辑。 略作重排,如有内容异常,请看原文。 终端输入输出的三种模式 guidao 1 前言 在进行项目开发时,需要实时读取终端输入(无需按下 Enter 键即可读取)。然而&a…...
预测分析(二):基于机器学习的数值预测
文章目录 基于机器学习的数值预测机器学习简介监督学习的任务创建第一个机器学习模型机器学习的目标——泛化过拟合现象评价函数与最优化 建模前的数据处理进一步特征变换 多元线性回归模型LASSO回归kNN算法原理算法步骤k值的选择 基于机器学习的数值预测 机器学习是人工智能的…...
JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题
目录 JavaScript双问号操作符(??)详解,解决使用||时因类型转换带来的问题 一、双问号操作符??的基础用法 1、传统方式的痛点 2、双问号操作符??的精确判断 3、双问号操作符??与逻辑或操作符||的对比 二、复杂场景下的空值处理 …...
蓝桥杯 web 展开你的扇子(css3)
普通答案: #box:hover #item1{transform: rotate(-60deg); } #box:hover #item2{transform: rotate(-50deg); } #box:hover #item3{transform: rotate(-40deg); } #box:hover #item4{transform: rotate(-30deg); } #box:hover #item5{transform: rotate(-20deg); }…...
聚焦楼宇自控:优化建筑性能,引领智能化管控与舒适环境
在当今建筑行业蓬勃发展的浪潮中,人们对建筑的要求早已超越了传统的遮风避雨功能,而是更加注重建筑性能的优化、智能化的管控以及舒适环境的营造。楼宇自控系统作为现代建筑技术的核心力量,正凭借其卓越的功能和先进的技术,在这几…...
前端视频流技术深度解析
一、视频流技术体系架构 1.1 现代视频流技术栈 1.1.1 核心协议对比 协议传输方式延迟适用场景浏览器支持HLSHTTP分片6-30s点播、直播回看全平台DASHHTTP动态适配3-15s多码率自适应Chrome/FirefoxWebRTCP2P/UDP<500ms实时通信、直播现代浏览器RTMPTCP长连接1-3s传统直播推…...
k8s核心资源对象一(入门到精通)
本文将深入探讨Kubernetes中的核心资源对象,包括Pod、Deployment、Service、Ingress、ConfigMap和Secret,详细解析其概念、功能以及实际应用场景,帮助读者全面掌握这些关键组件的使用方法。 一、pod 1 pod概念 k8s最小调度单元,…...
Ubuntu16.04配置远程连接
配置静态IP Ubuntu16.04 修改超管账户默认密码 # 修改root账户默认密码 sudo passwd Ubuntu16.04安装SSH # 安装ssh服务: sudo apt-get install ssh# 启动SSH服务: sudo /etc/init.d/ssh start # 开机自启 sudo systemctl enable ssh# 如无法连接&…...
基于springboot微信小程序课堂签到及提问系统(源码+lw+部署文档+讲解),源码可白嫖!
摘要 随着信息时代的来临,过去的课堂签到及提问管理方式的缺点逐渐暴露,本次对过去的课堂签到及提问管理方式的缺点进行分析,采取计算机方式构建基于微信小程序的课堂签到及提问系统。本文通过阅读相关文献,研究国内外相关技术&a…...
互联网三高-高性能之JVM调优
1 运行时数据区 JVM运行时数据区是Java虚拟机管理的内存核心模块,主要分为线程共享和线程私有两部分。 (1)线程私有 ① 程序计数器:存储当前线程执行字节码指令的地址,用于分支、循环、异常处理等流程控制 ② 虚拟机…...
数据操作语言
一、DML的核心操作类型 1.添加数据(INSERT) (1)手动插入:逐行插入数据,适用于少量数据。 INSERT INTO 表名 (字段1, 字段2) VALUES (值1, 值2);(2)批量导入:通过外部文件导入数据,适用于大数据场景...
智谛达科技:以创新为翼,翱翔AI人形机器人蓝海
在科技创新的浩瀚星空中,智谛达科技集团犹如一颗璀璨的明星,以其独特的创新光芒,照亮了AI人形机器人的广阔蓝海。这家在AI领域深耕多年的企业,始终秉持着创新为翼的发展理念,不断突破技术瓶颈,拓展应用场景,以卓越的实力和前瞻性的思维,引领着人形机器人行业的未来发展。 智谛达…...
封装可拖动弹窗(vue jquery引入到html的版本)
vue cli上简单的功能,在js上太难弄了,这个弹窗功能时常用到,保存起来备用吧 备注:deepseek这个人工智障写一堆有问题的我,还老服务器繁忙 效果图: html代码: <div class"modal-mask&qu…...
【LeetCode77】组合
题目描述 给定区间 [1, n] 和一个整数 k,需要返回所有可能的 k 个数的组合。 思路 算法选择:回溯算法 回溯算法是一种试探性搜索方法,非常适合用来解决组合问题。基本思想是: 从数字 1 开始,逐步构建组合。当当前组…...
【技术报告】GPT-4o 原生图像生成的应用与分析
【技术报告】GPT-4o 原生图像生成的应用与分析 1. GPT-4o 原生图像生成简介1.1 文本渲染能力1.2 多轮对话迭代1.3 指令遵循能力1.4 上下文学习能力1.5 跨模态知识调用1.6 逼真画质与多元风格1.7 局限性与安全性 2. GPT-4o 技术报告2.1 引言2.2 安全挑战、评估与缓解措施2.2.1 安…...
初阶数据结构(3)顺序表
Hello~,欢迎大家来到我的博客进行学习! 目录 1.线性表2.顺序表2.1 概念与结构2.2 分类2.2.1 静态顺序表2.2.2 动态顺序表 2.3 动态顺序表的实现初始化尾插头插尾删头删查找指定位置之前插入数据删除指定位置的数据销毁 1.线性表 首先我们需要知道的是,…...
Visual Studio 中使用 Clang 作为 C/C++ 编译器时,设置优化选项方法
在 Visual Studio 中使用 Clang 作为 C/C 编译器时,可以通过以下方法设置优化选项: 方法 1:通过项目属性设置(推荐) 右键项目 → 属性 配置属性 → C/C → 优化 优化:选择优化级别 /O0 - 禁用优化&#x…...
设计模式简述(七)原型模式
原型模式 描述基本使用 使用场景 描述 基于已有对象,利用JDK的Cloneable接口,生成一个新的对象。 常用于需要同时创建多个对象的场景 默认的clone是浅拷贝,如果要实现深拷贝需自行处理 可以在clone方法中手动拷贝数组成员或者其他引用类型成…...
Linux中查看占用端口号的进程信息的方法
在 Linux 中查看占用 ** 端口(eg:1717)**的进程号(PID),可以通过以下命令实现: 方法 1:使用 netstat 命令 sudo netstat -tulnp | grep :1717参数解释: -t:查看 TCP 端口…...
谷歌发布网络安全AI新模型Sec-Gemini v1
谷歌近日宣布推出实验性AI模型Sec-Gemini v1,旨在通过人工智能技术革新网络安全防御体系。该模型由Sec-Gemini团队成员Elie Burzstein和Marianna Tishchenko共同研发,旨在帮助网络安全人员应对日益复杂的网络威胁。 攻防不对称的破局之道 Sec-Gemini团队…...
【学Rust写CAD】35 alpha_mul_256(alpha256.rs补充方法)
源码 // Calculates (value * alpha256) / 255 in range [0,256], // for [0,255] value and [0,256] alpha256. pub fn alpha_mul_256(self,value: u32) -> Alpha256 {let prod value * self.0;Alpha256((prod (prod >> 8)) >> 8) }代码分析 这个函数 alph…...
嵌入式工程师多线程编程(三)裸机编程、RTOS、Linux及多线程编程的全面对比
以下是裸机编程、RTOS、Linux及多线程编程的全面对比解析,结合技术特性和应用场景进行深度分析: 一、架构与调度机制对比 维度裸机编程RTOSLinux任务调度无调度器(轮询/前后台系统)抢占式优先级调度(硬实时࿰…...
Meta LLaMA 4:对抗 GPT-4o 与 Claude 的开源王牌
2025 年 4 月,Meta 正式发布了 LLaMA 4 系列的首批两款模型。 这两款模型模型分别是:LLaMA 4 Scout 与 LLaMA 4 Maverick,均采用了 专家混合架构(Mixture-of-Experts, MoE)。 据 Meta 表示,这是首次有 …...
企业级 ClickHouse Docker 离线部署实践指南20250407
企业级 ClickHouse Docker 离线部署实践指南 引言 在数据分析与日志处理日益重要的今天,ClickHouse 凭借其高性能、列式存储架构,成为企业在大数据分析中的首选引擎之一。本文基于一位金融行业从业者在离线网络环境中部署 ClickHouse 的真实实践过程&a…...
STM32看门狗应用实战:独立看门狗与窗口看门狗深度解析(下) | 零基础入门STM32第九十五步
主题内容教学目的/扩展视频看门狗什么是看门狗,原理分析,启动喂狗方法,读标志位。熟悉在程序里用看门狗。 师从洋桃电子,杜洋老师 📑文章目录 一、看门狗应用架构分析1.1 系统监控流程图1.2 双看门狗应用场景对比 二、…...
DeepSeek-MLA
MLA 结构 需要缓存 KV 向量共用的压缩隐特征K 向量多头共享的带位置编码的向量 为什么带有位置信息的 Q 向量来自于隐特征向量,而带有位置的 K 向量来自于 H 向量且共享呢? 最好的方法肯定是从H向量直接计算并且不共享,但是会大大增加显存使…...
pyTorch-迁移学习-学习率衰减-四种天气图片多分类问题
目录 1.导包 2.加载数据、拼接训练、测试数据的文件夹路径 3.数据预处理 3.1 transforms.Compose数据转化 3.2分类存储的图片数据创建dataloader torchvision.datasets.ImageFolder torch.utils.data.DataLoader 4.加载预训练好的模型(迁移学习) 4.1固定、修改预训练…...
基于大模型的GCSE预测与治疗优化系统技术方案
目录 技术方案文档:基于大模型的GCSE预测与治疗优化系统1. 数据预处理模块功能:整合多模态数据(EEG、MRI、临床指标等),标准化并生成训练集。伪代码流程图2. 大模型架构(Transformer-GNN混合模型)功能:联合建模时序信号(EEG)与空间结构(脑网络)。伪代码流程图3. 术…...
vscode Colipot 编程助手
1、登录到colipot,以github账号,关联登录 点击【continue】按钮,继续。 点击【打开Visual Studio Code】,回到vscode中。 2、问一下11? 可以看出,很聪明,一下子就算出来了。 3、帮我们写一个文件…...
1、window 下SDL 下载使用, 测试环境搭建
1. SDL3下载 官网: https://www.libsdl.org/ 点击SDL Releases 或者 SDL GItHub 进入github下载: 因为自己在windows下使用的mingw,所以下载mingw版的,也可以 下载源码自己编译。 2. 项目搭建 这里使用的时mingw vsocde cmake, 可以使…...
