CLI
(Command Line Interface)是同操作系统交互最原始也最直接的方式,在这种界面下,用户通过键盘等文本输入方式,将各种命令交给终端(Terminal),命令的解释与执行借助shell完成,命令的集合还能构成功能强大的脚本。这一话题实际深挖可以发现很多有趣的细节。本文以Linux系统及其诸多发行版为研究对象。
标题:Linux关于CLI的术语与工具
shell
简介
shell英文直译为“壳”,“外壳”,广义上的shell指:将操作系统提供的服务,供用户或者其它程序调用的程序,它既可以使用CLI也可以使用GUI。
在一般的语境下,shell指使用CLI的命令行解释器程序,它可以解释用户输入或脚本中的指令,并调用用户想使用的程序或系统调用
用户一般通过终端使用shell,具体内含见后文
shell script
shell脚本是一种被shell解释并运行的程序,一般以.sh
作为扩展名,它可以看成是shell命令的集合,被shell按照流程顺序逐行解释运行
不同的shell可能支持不同的shell脚本语法,最常见的是bash支持的语法
常见shell
sh
全称
Bourne shell
,是UNIX最初使用的shell,也是第一个流行的shell,和用户的交互有一定欠缺- Linux中的sh往往是假的,以ubuntu为例,sh通过符号链接指向了dash
bash
:全称Bourne Again shell
,是Linux系统默认的shell,是sh的扩展,与sh向下兼容,提供了命令补全、命令历史等功能,用户界面有了很大改进rbash
- 全称
restricted bash
,是受限制的bash,虽然实质上软链接指向了bash - 但这一模式可以被shell命令启动的第三方软件破解,需要限制用户可以执行的指令来弥补
- 全称
dash
- 全称
Debian Almquist shell
,是bash的简化版本,支持POSIX标准,执行更快,很多情况下堪用 - 也有翻车的时候,例如
echo
命令的-e
参数就不被支持
- 全称
ash
:bash的轻量版本,占用资源少,兼容bash语法csh
:语法和C语言有一定类似,和BSD出自同一个作者tcsh
:是csh的扩展,加入了命令补全和更强大的语法功能zsh
:- 全称
Z shell
,是bash的扩展,加入了很多强大的功能 - 配置一般使用
oh my zsh
项目,再搭配Powerlevel10k
等主题
- 全称
login与interactive
同一种shell,根据运行的情景和可以使用的功能,又可以按照(non)login与(non)interactive进行细分,它们对理解部分manual中的概念很重要
login shell
在用户登入系统所使用的第一个shell,它需要用户验证,初始化时不光会执行
rc
类别的文件,还会执行用户特定的login
、profile
类别的文件等non-login shell
已经登录的用户后续创建的shell,它不再需要输入用户名密码等,一般只会在初始化时执行
rc
类别的文件
区分这两种shell的类型,部分shell可以执行shell命令echo $0
,shell的0号参数一般是shell的名字附带是否为login shell的信息。如果前面带有-
符号,则为login shell
interactive shell
我们平时使用的shell一般都是这种类型,它可以接收用户输入并返回输出
non-interactive shell
该种shell不会和用户交互,但是可能在执行过程中生成新的interactive shell
- 组合起来的常见情景:
interactive login shell
:ssh指令直接登录远程服务器,直接使用下文的虚拟终端等non-interactive login shell
:部分GUI下用于用户登录及初始化的shell,或ssh登录远程服务器并由脚本等非终端提供标准输入等interactive non-login shell
:已登录的用户通过终端等方式开启新的shell进程时non-interactive non-login shell
:基于前者,运行shell脚本的时候往往交互就冇了
控制
查看当前的shell
查询当前shell的
0
号参数echo $0
查询
/etc/passwd
文件,可以找到login shellgrep [UserName] /etc/passwd
查询系统的SHELL环境变量,它表示系统的默认shell
echo $SHELL
同样的道理,也可以使用
env
命令的默认输出查看环境变量(它原本的作用是为程序提供定制的环境)env | grep SHELL
使用ps查看前台活跃的进程,其中就包括了shell
ps
查看当前shell的
$
参数,它代表了当前shell的进程号,然后利用ps找到进程详细信息echo $$ ps aux | grep [PID]
随便输入一条不存在的shell命令,部分shell的报错开头会带上shell的类型
shell切换
查看设备上可以使用的shell
cat /etc/shells
在当前终端换用另一种shell,则将它的名字作为命令即可,例如可以直接切换当前的shell为dash:
dash
切换之后,退出就能回到先前的shell
设置默认shell
如果偏爱某种类型的shell,不如将它设置为用户的login shell,这样每次启动新shell都会是它而无需命令切换
使用
chsh
命令,它可以切换login shell,-s
选项指定了切换后的默认login shell ,若为空则使用系统默认chsh -s [ShellDir]
使用
usermod
命令,它可以修改用户的有关信息,-s
选项含义同上usermod -s [ShellDir] [UserName]
terminal
- 终端(terminal)提供程序与用户交流的界面
- 在用户视角下终端有不同的形式,最常用的是虚拟终端
- 类UNIX系统下的程序有
stdin
、stdout
、stderr
三种IO流,它们默认都连接到抽象的终端上
电传打字机
- 英文名:Teleprinter, Teletypewriter, Teletype(TTY的来源)
- 历史:在计算机诞生之前,电传打字机就已经用于通信。计算机诞生后的一段时间里,它也承担了终端的职能,处理用户与计算机的交互,它会在纸带上逐行打印显示用户的输入或计算机的输出
- 架构:终端–UART串行通信部件–UART驱动–TTY driver–程序
TTY driver
- 起始的所在
- 位于操作系统内核
- 具有
line discipline
模块 - 程序通过TTY driver与终端连接,TTY driver将输入交给用户进程进行处理或调用,并将执行结果输出
- line discipline
- 处理输入的特殊字符
- 对输入进行行缓冲
- 将输入回显给终端及其他功能
虚拟终端/终端模拟器
英文名:Terminal Emulator
历史:电传打字机取消后,内核的TTY driver并没有消失,由终端模拟器来通过键盘驱动监听输入,通过显示器驱动给出输出,并模拟从前电传打字机通过UART组件与TTY driver的通信,完成用户与系统的交互
架构:键盘/显示器–键盘/显示器驱动–Terminal Emulator–TTY driver–用户进程
Terminal Emulator
最早属于内核组件,是多数图形界面的基础;后来出现了用户态的终端模拟器,它需要借助下文的伪终端使用。单独提及终端模拟器一般指内核组件
因为取代了电传打字机,虚拟终端需要监听与处理同键盘/显示器这样的外设驱动的通信
挂载于文件系统的
/dev/tty[N]
下,[N]
为虚拟终端编号,可以使用Alt+Ctrl+F[N]
进行切换tty
指令可以查看终端模拟器对应的设备文件被X系统(多数linux发行版的GUI系统基础)所使用,服务于GUI
验证:
ps aux | grep Xorg #查看X系统进程号 ll /proc/[PID]/fd | grep tty #查看对应进程号[PID]所打开的文件中是否有tty
在ubuntu系统下,tty1被用户登录界面使用,tty2被登录后GUI使用,其他tty则空闲
在多个虚拟终端登录的状态下可以通过读写对应的设备文件来相互通信,如:
echo "helloworld from tty3" > /dev/tty4
伪终端
英文名:Pseudo Terminal
历史:内核的虚拟终端不够灵活和安全,因此在用户态出现了
gnome-terminal
(基于xterm)等虚拟终端,它们通过内核为虚拟终端提供的pty
(pseudoterminal interfaces)(伪终端)接口,来像内核的虚拟终端一样提供终端会话,并相较前者具有更好的可扩展性与安全性下文的终端默认指用户态虚拟终端
pty接口:
- 是成对的虚拟字符设备文件,提供双向通信的通道,一端称为
pty master
,和终端相连,一端称为pty slave
,和执行的程序相连 - 通过tty指令可以找到终端对应的pty接口文件,位于
/dev/pts
中 - 终端启动时,将打开
/dev/ptmx
文件并进行一系列操作来获取pty接口 - 从终端输入并发送到pty master的内容将送达pty slave用于程序执行,程序执行后发送到pty slave的内容也将送达pty master用于终端显示
- 该接口一样能提供line discipline的功能
- 是成对的虚拟字符设备文件,提供双向通信的通道,一端称为
打开gnome-terminal并执行ls经历了什么:
- 使用
Ctrl+Alt+T
或其他方式启动gnome-terminal - gnome-terminal获取pty接口并使用master端,fork启动的shell子进程使用slave端,shell的标准流都和slave端对接
- 用户输入ls之后,监听键盘驱动的gnome-terminal将其发送到pty接口的master端
- 接口提供了line discipline功能,不仅将ls命令写回gnome-terminal的显存让用户看到输入的命令;等待识别输入的特殊字符;还对输入的指令进行了缓冲,并在接收到回车之后,将master端的ls命令传到slave端
- slave端的shell进程获得输入的命令,解析得到要执行ls命令
- shell通过fork得到ls子进程,子进程继承了对接到slave端的标准流,执行命令并将执行结果发送到pty接口的slave端
- pty接口slave端进一步将执行结果传到master端,并显示在gnome-terminal上
- 使用
远程连接中的终端
这里以ssh远程连接服务器并执行命令的过程为例,总体上看,除了需要本地的虚拟终端,还需要服务器的虚拟终端,二者使用ssh的client/server程序通过TCP协议进行通信
以连接后执行ls命令为例,过程大致如下:
- 上文打开用户态虚拟终端并执行ssh命令,直到shell通过fork得到ssh-client子进程的全过程
- 本地执行ssh命令的过程中,接口并不会启动line discipline的设置,从而让特殊字符等可以正确被服务器处理
- ssh-client执行命令,按照用户名、地址密码等信息向服务器发送连接请求
- 收到并验证请求的服务器上的ssh-server申请pty接口开启新的用户态虚拟终端会话,过程与上文类似
- 本地输入的ls命令与回车符,被gnome-terminal接收后,被原样发送到pty-master、pty-slave、ssh-client、ssh-server、pty-master(server设备)
- 由于ssh-server使用的pty接口仍然打开了line discipline设置,因而一方面会进行行缓冲,直到收到回车符后才会将命令送到server上的pty-slave端;另一方面会进行回显,传到server的master端的ls命令输入内容会全部通过ssh-server经由网络传回本地的ssh-client,并如同上文最终送回gnome-terminal让用户看到输入的命令
- server端对于ls命令加回车符,按照上一节的过程执行后,将传回到ssh-server的执行结果再次经由网络传回本地的ssh-client,并最终送到gnome-terminal让用户看到server执行的结果
终端配置
不同的虚拟终端,在开启后都可以使用stty
指令对当前终端会话进行配置:
- 虚拟终端行数(影响vi等进程的显示界面)等常规参数
- 特殊字符与按键的绑定
- line discipline的具体功能是否开启
- 查询当前终端会话的设置
具体配置可以参考手册,下面给出几个配置的例子:
查询当前使用的虚拟终端的具体参数
stty -a
配置当前使用的虚拟终端,如加上line discipline回显的功能
stty echo
去掉当前虚拟终端的回显功能
stty -echo
操控其他的虚拟终端时使用
-F
参数,内核态虚拟终端参数为/dev/tty[N],用户态虚拟终端参数为/dev/pts/[N],如关闭gnome-terminal零号的回显功能stty -F /dev/pts/0 -echo
合理的配置,不仅能让自己使用虚拟终端更加方便,通过观察开启的功能等途径还能理解很多内在实现的细节
参考教程
Linux shells: interactive/non-interactive, login and non-login