欢迎大家来到IT世界,在知识的湖畔探索吧!
★引子
首先,
这篇是为了补前几年的“欠债”。这些年,俺写了好多篇 Linux 相关的技术教程。但还从来没有【系统性】地介绍 Linux 命令行相关的基本概念和基本知识。几年来,已经有不少读者催俺填上这个大坑,但俺比较懒,一直拖到现在,惭愧 :(
其次,
一个多月前(9月份)写了一篇 netcat 的扫盲教程,其中涉及了很多命令行相关的知识。很多菜鸟读者,如果缺乏这些基础知识,恐怕看不懂那篇 netcat 教程。再加上前几天的博文谈到了【系统性学习】相关的方法论,并且还聊了【费曼学习法】的各种好处。
今天这篇,算是俺第 N 次践行“费曼学习法”——无论对俺还是读者,这都是【双赢】滴 :)
★本文目标读者
虽然本文的标题号称是【扫盲】,但俺相信:即使是一些 POSIX 系统的命令行【老手】,对本文中介绍的某些概念,可能也会有【欠缺】。
因此,这篇教程既适合于命令行的新手,也值得某些【老手】看一看。
由于本文介绍的是 POSIX 系统中【通用的】概念与知识。因此,包括 Linux、BSD 家族、macOS 等各种系统的用户,应该都能从中受益。
(注:POSIX 是某种操作系统的标准/规范。各种 Linux 发行版以及所有的 UNIX 变种,包括 macOS,都属于“POSIX 系统”)
如果你是这方面的【菜鸟】,并且想要掌握这个领域。【不要】企图只看一遍就完全理解本文的内容(可能需要看好几遍)。俺的建议是:要一边看,一边拿命令行的环境【实践】一下。
★一切都从【电传打字机】开始说起
(说完了“引子”与“目标读者”,开始切入正题)
可能有些读者会纳闷——“聊命令行的基本概念”,为啥要扯到“电传打字机”?是不是扯得太远了?
俺来解释一下:
IT 行业的很多基本概念都来自于【历史遗迹】。有时候你觉得某些东西很奇怪(并纳闷“为啥会设计成这样”);而当你搞清楚历史的演变过程之后,自然就明白其中的原因。
◇在那遥远的【电报时代】
在计算机诞生之前(二战前),【电报】属于高科技的玩意儿——它能够瞬间把信息传送到另一个城市(甚至传送到大洋彼岸)。
当年的电报线路,是以【字符】为单位发送信息。在线路两端使用【电传打字机】,就可以自动地把对方发过来的字符打印出来。
◇“回车/换行”的来历
稍微懂点 IT 的同学,应该都听说过“回车/换行”,洋文分别称之为“carriage return”&“line feed”。在编程领域,这两个字符简称为 \r & \n。
为啥会有这么两个玩意儿捏?
因为在电传打字机时代,当打印完一行之后,需要用一个控制命令把“打印头”复位(移到打印纸的左边),然后再用另一个控制命令把“打印头”往下移动一行。很自然地,这俩动作就对应了两个控制字符(CR & LF),也就是所谓的“回车 & 换行”。
◇其它控制字符
如果你去留意一下 ASCII 字符表的开头部分,前面那32个字符都是控制字符,很多都源于遥远的【电报时代】。
在本文后续的介绍中,还会再聊到这些“控制字符”。
★终端(terminal/TTY)
◇历史演变
“终端”一词,洋文称之为“terminal”。有时候又被称作 TTY,而 TTY 这个简写就来自刚才介绍的【电传打字机】(teletype printer)。
因为早期的大型机,其“终端”就是【电传打字机】。那时候的终端,也称作【硬件终端】。
为啥会有“终端”这个概念捏?你依然需要了解历史的变迁。
最早期的计算机(大型机)是【单任务】滴——也就是说,每次只能干一件事情。
到了60年代,出现了一个【革命性】的飞跃——发明了【多任务】系统,当时叫做“time-sharing”(分时系统)。有了“分时系统”,就可以让多个人同时使用一台大型机。而为了让多个人同时操作这台大型机,就引入了【终端】的概念。每一台大型机安装多个终端,每个操作员都在各自的终端上进行操作,互不干扰。
◇(跑题)“约翰·麦卡锡”其人
聊到这里,稍微跑题一下:
最早的“分时系统”由 IT 超级大牛“约翰·麦卡锡”(John McCarthy)设计。此人不仅仅是“分时系统它爹”,还是“Lisp 语言它爹”,另外还参与设计了编程语言“ALGOL 60”。而这个“ALGOL 60”编程语言虽然知道的人不多,但该语言深刻影响了后续的 Ada、BCPL、C、Pascal……
为了让你体会这只大牛到底有多牛。俺引用另一个牛人保罗·格雷汉姆(《黑客与画家》作者)的观点——他认为在所有编程语言中, Lisp 与 C 是两座无法超越的高峰。而“约翰·麦卡锡”亲自发明了 Lisp 语言,然后又深刻地影响了 C 语言。
另外,麦卡锡这只大牛还参与创立了“MIT 人工智能实验室”与“斯坦福人工智能实验室”。前者涌现出一大批早期的黑客,其中包括大名鼎鼎的 Richard Stallman(此人开创了:自由软件运动、GNU 社区、GCC、GDB、GNU Emacs ……)。
◇【远程】终端
跑题结束,言归正传。
“终端”的好处不光是“多任务”,而且还可以让用户在【远程】进行操作。这种情况下,“终端”通过 modem(调制解调器)与“主机”相连。这种玩法很类似于——互联网普及初期的拨号上网。
最早的“终端”,本质上就是“电传打字机”——以“打字机”作为输入;以“打印纸”作为输出。这类终端,比较经典的是如下这款:
(Teletype Model 33 ASR)
到了上世纪70年初,终于有了带【屏幕】的远程终端。DEC 公司的 VT05 是第一款基于 CRT 显示器的远程终端。
◇内部结构示意图
下面这张是大型机时代,“终端”与“进程”通讯的示意图。
图中的 UART 是洋文“Universal Asynchronous Receiver and Transmitter”的缩写(相关维基百科链接在“这里”)。LDISC 是洋文“line discipline”的简写(相关维基百科链接在“这里”)。
通俗地说,UART 用来处理物理线路的字符传输(比如:“错误校验”、“流控”、等);LDISC 用来撮合底层的“硬件驱动”与上层的“系统调用”,并完成某些“控制字符”的处理与翻译。
◇如今的含义
如今,“终端”一词的含义已经扩大了——用来指:基于【文本】的输入输出机制。
在本文后续的章节中, terminal 与 TTY 这两个术语基本上是同义词。
★终端的3种【缓冲模式】——字符模式、行模式、屏模式
◇字符模式(character mode)
又要说回到【电传打字机】。
在本文开头,已经聊过这个玩意儿,并且提到——它是基于【字符】传输滴。也就是说,操作员每次在“电传打字机”上按键,对应的字符会立即通过线路发送给对方。这就是最传统的【字符模式】
通俗地说,“字符模式”也就是【无缓冲】的模式。
◇行模式(line mode)
不客气地说,“字符模式”是非常SB滴!因为如果你不小心按错键,这个错误也会立即发送出去。
比如说,你在输入一串很长的命令,结果输到半当中,敲错一个按键,整个命令就废了——要重新再输入一遍。
所以,当早期的程序员对“字符模式”实在忍无可忍之后,终于发明了【行模式】。
【行模式】也叫做“行缓冲”。也就是说,终端会把你当前输入的这行先缓冲在本地。只有当你最终按了【回车键】,才会把这一整行发送出去。如果你不小心敲错了一个字符,可以赶紧用“退格键”删掉重输这个字符。
因此,这种模式称之为【行缓冲】。
顺便说一下:
早期的标准键盘,【没有】方向键(“上下左右”这4个键)。不信的话,可以去看本文前面贴的那张“Teletype Model 33 ASR”的照片。
因为无论是“字符模式”还是“行模式”,都没这个需求。
◇屏模式(screen mode/block mode)
“行模式”进一步的发展就是【屏模式】。这个玩意儿也叫“全屏缓冲”,顾名思义,终端会缓冲当前屏幕的内容。
在这种模式下,用户可以利用方向键,操纵光标(cursor)在屏幕上四处游走。
开发这种类型的软件,比较复杂——程序员至少需要做如下工作:
1. 保存整个屏幕的状态
2. 根据键盘输入,操纵光标(cursor)移动
3. 控制屏幕的哪些区域是光标可达,哪些是不可达;
4. 对于光标可达的部分,控制哪些是“可编辑”,哪些是“只读”;
5. 根据“光标移动”以及某些“特定的按键”(比如“翻页键”),重新绘制屏幕
……
后来,为了简化”屏模式“的编程,专门搞了一个叫做 curses 的编程库。如今的“ncurses 库”就是从 curses 衍生出来滴(前面加了一个 n 表示 new)。
前面说了——早期的键盘【没】方向键。有了这个【屏模式】之后,键盘上才开始增加了“方向键”(所以“方向键”位于键盘的扩展区)
◇小结
上述这三种模式,第1种基本淘汰(仅限于极少数场景);第3种用得也不多。与本文关系比较密切的,其实是【第2种】——行模式。
为了加深你的印象,用 cat 命令来举例(注:这个命令其实与“猫”【无关】,而是 concatenate 的简写)
大部分情况下,都是用它来显示某个文件的内容,比如说:cat 文件名 。但如果你运行 cat【没】加任何参数,那么它就会尝试读取你在终端的输入,然后把读到的文本再原样输出到终端。
在上述动中,你的输入并【没有】直接传递给 cat 进程。要一直等到你按下【回车键】,cat 进程才收到你的输入,并立即打印了输出。
★终端的【回显】
◇“回显”是啥?
在刚才那个 gif 动画中,当俺逐个输入 test 的每个字母,这些字母也会逐个显示在屏幕上。这种做法叫做【回显】。
◇“回显”地打开与关闭(启用/禁用)
虽然“回显”很人性化,但某些特殊的场合是【不想】“回显”滴,比如当你输入密码/口令的时候。
因此,终端提供了某种机制,使得程序能够控制“回显”的启用/禁用。
对于大多数终端,可以用【Ctrl + S】禁用“回显”,然后用【Ctrl + Q】启用“回显”。
如果你在禁用“回显”的情况下输入一些文本,当你重新启用“回显”的瞬间,这些文本会一起出现在屏幕上。
顺便说一下:
由于【Ctrl + S】在 Windows 上是很常见的组合键。某些菜鸟刚开始玩 Linux 命令行的时候,会习惯性地按这个组合键,结果就禁用了回显。这时候,任何键盘输入都没有反应。菜鸟就以为终端死掉了。
◇历史演变
对于 Windows 用户来说,【Ctrl + S】实在太常用了,很容易误按。肯定有大量的用户吐槽过 POSIX 终端的这个快捷键。
那么,为啥要用这两个快捷键来控制“回显”捏?俺又要第 N 次说到【电传打字机】了。
由于这玩意儿的输出是【打印纸】,其速率比较【慢】。一旦“对方发送字符的速率”高于“自己这边的打印速率”,就需要向对方发一个控制信号,让对方暂停发送;等到自己这边打印完了,再发送另一个控制字符,通知对方继续。
(注:上述这种玩法,通信领域行话称之为“流量控制/流控”)
当年用来表示“暂停发送”的控制字符,对应的就是【Ctrl + S】;用来“恢复发送”的控制字符,也正是【Ctrl + Q】。
★(早期的)系统控制台/物理控制台(system console)
(前面说了)在【没】发明“分时系统”之前,当时的计算机只能执行【单任务】。因此,那时候的大型机只有【一个】操作界面,称之为【控制台】。
话说那时的“控制台”,真的是一个台子
后来发明了“分时系统”。如刚才所说——“分时系统”使得大型机可以具备多个终端。在这种情况下,你可以把“控制台”通俗地理解为“本地终端”,而【不】是“控制台”的那些终端,称之为“远程终端”。
在那个年代,计算机属于【非常非常稀缺】的资源。于是拥有大型机的公司,就可以【出租计算资源】,获得一笔相当可观的收入。他们把大型机的某个“远程终端”租给外来人员使用,然后根据“时间/空间”收取费用。由于资源的稀缺性,当年的 CPU 是按【秒】计费,而内存是按【KB】计费。
由于“远程终端”可能会被【外人】使用,因此对“远程终端”的【权限】要进行一些限制。如果要进行一些高级别的操作(比如“关闭整个系统”),就只能限制在【控制台】(本地终端)进行。有些公司为了安全起见,还会把“控制台”单独锁在某个“secured room”里面。
★(如今的)虚拟控制台(virtual console)
到了 PC 时代,传统意义上的【控制台】已经看不到了。但 console 这个术语保留了下来。
◇从“物理 console”到“虚拟 console”
早期大型机的 console 是【独占】硬件滴——“键盘/显示器”固定用于某个 console 滴。
【现代】的 POSIX 系统,衍生出“virtual console”的概念——可以让几个不同的 console【共用】一套硬件(键盘/显示器)。“virtual”一词就是这么来滴。
再重复唠叨一下:不论是早期的“物理控制台”还是后来的“虚拟控制台”,都属于广义上的“终端”。
◇举例:Linux 的 virtual console
假设你的 Linux 系统没安装图形界面(或者默认不启用图形界面),当系统启动完成之后,你会在屏幕上看到一个文本模式的登录提示。这个界面就是 virtual console 的界面。
在默认情况下,Linux 内置了【6个】virtual console 用于命令行操作,然后把第7个 virtual console 预留给图形系统。你可以使用 Alt + Fn 或 Ctrl + Alt + Fn 在这几个 console 之间切换(注:上述所说的 Fn 指的是 F1、F2… 之类的功能键)。
◇虚拟控制台的【内部结构】
★终端模拟器(terminal emulator)
请注意上面那张示意图,图中出现了一个【终端模拟器】,这就是本章节要说的东东。
如果你对比前面的【TTY 示意图1】与【TTY 示意图2】的变化,会发现——“UART & UART 驱动”没了,然后多了这个【终端模拟器】。
多出来的这个玩意儿相当于加了一个【抽象层】,模拟出早期硬件终端的效果,因此就【无需改动】系统内核中的其它部分,比如:LDISC(line discipline)
请注意,这个场景下的“终端模拟器”位于操作系统【内核】。换句话说,它属于【内核态】的模拟器。正是因为它处于这个地位,所以能够在“驱动”&“LDISC”之间进行协调。
★伪终端(PTY/pseudotty/pseudoterminal)
◇从“文本模式”到“图形模式”
前面讲的那些,都是【文本模式】(文本界面)。
话说到了上世纪80年代,随着【图形界面】的兴起,就出现某种需求——想在图形界面下使用“【文本】终端”。于是就出现了“伪终端”的概念。
通俗地说,“伪终端”就是用某个图形界面的软件来模拟传统的“文本终端”的各种行为。前面说了,TTY 这个缩写相当于“终端”的同义词;因此“pseudotty” 就衍生出 PTY 这个缩写。
◇从“【内核态】终端模拟器”到“【用户态】终端模拟器”
在上一个章节中,emulator 运行在系统内核中,因此是“内核态模拟器”;
等到后来搞“伪终端”的时候,就直接把这个玩意儿从【内核态】转到【用户态】——让它直接运行在【桌面环境】。如此一来,用户就可以直接在桌面环境中使用“终端模拟器”。
当“终端模拟器”变为【用户态】,它就【无法】直接与“键盘驱动 or 显卡驱动”打交道。在这种情况下,由“GUI 系统”(比如:X11)负责与这些驱动打交道,然后再把用户的输入输出转交给“终端模拟器”。
xterm。别看它长得丑,它的出现也算是“里程碑”了。
◇内部结构示意图
很多人把“emulator”与“PTY”混为一谈。实际上两者处于【不同】层次。
在操作系统内部(内核),PTY 分为两部分实现,分别叫做“PTY master” & “PTY slave”。master 负责与“terminal emulator”打交道;而用户通过 emulator 里面的 shell 启动的其它进程,则与 slave 打交道。
在这个环节中,“PTY slave”又进一步缩写为“PTS”。如果你用 ps 命令查看系统中的所有进程,经常会看到 PTS 之类的字样,指的就是这个玩意儿。对普通用户而言,看到的是“终端模拟器”的界面,至于 PTY 内部的 master & slave,通常是感觉不到滴。
为了让大伙儿更加直观,再放一张 PTY 的结构示意图。
★shell——命令行解释器
费了好多口水,咱们终于聊到 shell 了。
顺便吐槽一下:
扫盲命令行的教程,很少会像俺这样,从最基本的概念说起。其导致的后果就是——很多人(甚至包括很多 Linux 程序员)都搞不清“shell、terminal、console、TTY、PTY、PTS”这些概念到底有啥区别。
在《如何【系统性学习】——从“媒介形态”聊到“DIKW 模型”》一文中,俺特别强调了【基本概念/基础知识】的重要性。这也就是俺为啥前面要费这么多口水的原因。
◇shell VS terminal
前面所说的“终端”(terminal),本质上是:基于【文本】的输入输出机制。它并【不】理解具体的命令及其语法。
于是就需要引入 shell 这个玩意儿——shell 负责解释你输入的命令,并根据你输入的命令,执行某些动作(包括:启动其它进程)。
◇常见 shell 举例
常见的 shell 包括如下这些(为避免排名纠纷,按字母序列出):
bash
csh
fish
ksh
zsh
在维基百科的“这个页面”,列出了各种各样的 shell 及其功能特性的对照表。
如今影响力最大的 shell 是 bash(没有之一)。其名称源自“Bourne-again shell”,是 GNU 社区对 Bourne shell 的重写,使之符合自由软件(GPL 协议)。
本文后续章节对 shell 的举例,如果没有做特殊说明,均指 bash 这个 shell。
★shell 的基本功能
◇显示【命令行提示符】
当你打开一个 shell,会看闪烁的光标左侧显示一个东东,那个玩意儿就是【命令行提示符】(参见下图)
(截图中的“命令行提示符”包含了:用户名、当前路径、$分隔符)
很多 shell 的“命令行提示符”都会包含【当前路径】。当你用 cd 命令切换目录,提示符也会随之改变。这有助于你搞清楚当前在哪个目录下,可以有效避免误操作。
“命令行提示符”随着当前目录的变化而变化。
大部分 shell 都可以让你自定义这个【命令行提示符】,使之显示更多的信息量。
比如说,可以让它显示:当前的时间、主机名、上一个命令的退出码……
(注:如果你需要开多个【远程】终端,去操作多个【不同】的系统,“主机名”就蛮有用)
◇解析用户输入的【命令行】
假设你想看一下 /home 这个目录下有哪些子目录,可以在 shell 中运行了如下命令:
ls /home[/pre]
当你输入这串命令并敲回车键,shell 会拿到这一行,然后它会分析出,空格前面的 ls 是一个外部命令,空格后面的 /home 是该命令的参数。
然后 shell 会启动这个外部命令对应的进程,并把上述参数作为该进程的启动参数。
◇内部命令 VS 外部命令
(刚才提到了【外部命令】这个词汇,顺便解释一下)
通俗地说,“内部命令”就是内置在 shell 中的命令;而“外部命令”则对应了某个具体的【可执行文件】。
当你在 shell 中执行“外部命令”,shell 会启动对应的可执行文件,从而创建出一个“子进程”;而如果是“内部命令”,就【不】产生子进程。
那么,如何判断某个命令是否为“外部命令”捏?
比较简单的方法是——用如下方式来帮你查找。如果某个命令能找到对应的可执行文件,就是“外部命令”;反之则是“内部命令”。
whereis 命令名称[/pre]
◇翻译【通配符】
玩过命令行的同学,应该都知道:“星号”(*)与“问号”(?)可以作为通配符,用来模糊匹配文件名。
当你在 shell 中执行的命令包含了上述两个通配符,实际上是 shell 先把”通配符“翻译成具体的文件名,然后再传给相应命令。
◇翻译某些【特殊符号】
比如说:在 POSIX 系统中,通常用 ~ 来表示当前用户的【主目录】(home 目录)。
如果你在 shell 中用到了 ~ 这个符号,shell 会先把该符号翻译成“home 目录的【全路径】”,然后再传给相应命令。
◇翻译【别名】
很多 POSIX 的 shell 都支持用 alias 命令设置别名(把一个较长的命令串,用一个较短的别名来表示)。
设置了别名之后,当你在 shell 中使用“别名”,由 shell 帮你翻译成原先的命令串。
举例:
在《扫盲 netcat(网猫)的 N 种用法——从“网络诊断”到“系统入侵”》一文中,俺使用如下命令创建了 nc-tor 这个别名。
alias nc-tor=’nc -X 5 -x 127.0.0.1:9050′[/pre] 设置完之后,当你在 shell 中执行了这个 nc-tor 命令,shell 会把它自动翻译成 nc -X 5 -x 127.0.0.1:9050
◇历史命令
大部分 shell 都会记录历史命令。你可以使用某些设定的快捷键(通常是【向上】的方向键),重新运行之前执行过的命令。
◇自动补全
很多 shell 都具备自动补全的功能。
该功能不仅对“命令”本身的自动补全,还包括对“命令的参数”进行自动补全。
◇操作“环境变量”
关于这部分,在下面的“环境变量”章节单独聊。
◇“管道”与“重定向”
关于这部分,在下面的“管道”章节单独聊。
◇“进程控制”与“作业控制”
关于这部分,在下面的“进程控制”与“作业控制”章节单独聊。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/66757.html