《命令调用的基本语法和终端相关的概念》原文链接,阅读体验更佳!
Linux对于计算机各组件和设备的识别,和我们最常用的Windows操作系统完全不一样。在Linux系统中,每个设备都会被当成一个文件来对待,举例来说,SATA接口的硬盘的文件名即为/dev/sda、/dev/sdb、/dev/sdc和/dev/sdd等。
更多Linux内核支持的硬件设备与文件名,可以参考如下网页:https://www.kernel.org/doc/Documentation
这种一切皆文件的设计方式,使得我们可以以统一的方式操作文件和设备。这也使得Linux下的命令行非常的强大。
Linux中与命令行终端相关的术语非常多,比如控制台(Console)、终端(Terminal)、Shell、Bash、命令行(Cli)、tty等等,这些术语的使用往往非常混乱,甚至在日常的使用中往往指代同一个东西——一个黑乎乎的命令行界面。
造成这种混乱有历史遗留的原因。虽然名词之争没有太大的意义,但是从刚开始进行学习的时候就理清这些混乱,有利于理清自己的思路和进行更加深入的学习。所以,这里我对这些术语进行一个简单的介绍。
总的来说,计算机分为硬件和软件两个部分,所以终端相关的概念有的是软件上的,有的是硬件上的,那么接下来我们对终端相关概念的介绍也是分为硬件和软件两个部分。
了解过冯·诺依曼计算机体系结构的同学应该知道所有的计算机硬件都可以归类到控制器、运算器、存储器、输入设备和输出设备其中的一种,而其中的输入设备和输出设备就是我们和计算机进行交互的桥梁。
在计算机刚刚诞生的时候,人们都是通过手拨开关的方式直接向计算机发送指令,后来通过纸带向计算机传送指令,随着计算机的不断发展,计算机的体系形成,产生了键盘、鼠标、显示器和硬盘等等这样的设备,我们向计算机中输入数据的方式变得更加灵活。
随着计算机的不断发展,操作系统往往需要同时支持多个用户和计算机进行交互,而用户用来和计算机进行交互的设备被称为终端。
终端必须具备I/O功能从而与计算机进行交互。从硬件上来说,终端就是指一组具有输入输出功能的设备,用户使用这些设备对计算机进行操作。
现在,对于一台计算机来说最常见的终端设备就是一台显示器外加键鼠套。
控制台其实是一套比较特殊的终端,在早期的计算机上,往往是具有一个带着大量开关和指示灯的面板,可以对计算机进行一些底层的操作和设置,这个面板就被叫做Console,其概念来源于风管琴的控制台。
当然,在现在的一些大型计算机系统中,通常会有一台单独的小型计算机来对整个大型计算机进行一些底层运行模式等电路状态的设置,我们认为这也是一种Console;现在的个人计算机往往通过特殊的软件来对自身进行一些设置,比如BIOS等,所以个人计算机往往没有专门的控制台设备;同时,现在非常流行的云主机往往会提供一个网络程序来作为控制台程序,让我们可以远程对主机进行设置。
tty是比较早期的终端,在计算机刚刚出现的时候,是没有显示器的,和计算机进行交互也没有形成现在使用键盘鼠标和显示器的格局。
在1869年,证券报价机(stock ticker)被发明了。这是一台由打字机,一对长电缆和一个自动收录机打印机组成的电动机械机器,其目的是长距离实时传播股票的价格。这个概念逐渐演变成更快的基于ASCII的电传机(Teletypewriter)。Teletypewriter曾经在世界各地的大型网络中连接,并被称为Telex,其主要用于传输商业电报,但此时尚未连接到任何计算机。
与此同时,计算机(虽然还是又笨重又昂贵)也开始支持多任务处理了,即能够实时和多个用户进行交互。当命令行最终取代了古老的批处理模型后,Teletypewriter成为唯一能够被使用的“实时”输入/输出设备,因为它们在市场上很容易买到。而在Linux操作系统中,/dev/tty文件就代表了这种设备。
随着计算机技术的发展,Teletypewriter被键盘和显示器取代,但是用tty文件来代表用户与操作系统交互的终端设备的传统却是保留了下来。
硬件是计算机的身体,那么软件就是计算机的灵魂,早期的计算机是没有任何软件的裸机,人们都是通过手拨开关的方式直接向计算机发送指令,但是随着计算机电路的不断发展,这样的操作方式显然就无法满足需求了,所以操作系统出现了。
有了操作系统之后,人们就不用直接操作硬件了,而是通过操作系统与计算机进行交互。
操作系统类似于电脑硬件的一个“大管家”,就好像管弦乐队的总指挥一样,需要指挥各个乐手的演奏。而操作系统这个“大管家”软件则需要管理我们电脑的内存,把内存合理地分配给各个软件;还要管理各种I/O设备,它起到连接电脑的硬件(内存、显卡、光驱、硬盘…)和各种软件的“桥梁的作用”,这使得程序员在开发程序的时候不用直接和各种硬件打交道,而只需要和操作系统这个大管家打交道。
Shell本身就是‘壳子’的意思,了解操作系统的同学都知道,操作系统一般分为两个部分,即内核和壳。
总的来说,内核提供诸如内存管理,文件系统,I/O设备管理等底层服务,同时内核通过一些特定的API向壳子暴露这些功能。Shell通过内核暴露的这些功能和内核进行交互,而用户通过Shell间接和操作系统进行交互从而操纵电脑进行各种各样的作业。用户和计算机硬件之间的交互可以用下面的图形简单表示:
命令行是Shell的一种实现方式,Shell为用户屏蔽了操作系统内核的复杂性,是提供给计算机使用者的操作视图,这种视图可以有多种实现方式,可以是图形用户界面,也可以是命令行界面。图形用户界面和命令行用户界面各有优劣,图形用户界面操作更加简单,更容易上手,但是要消耗更多的计算机硬件资源,同时操作模式比较固化,功能相对较弱。命令行界面性能更好,功能更强劲,但是通常更难学习。
命令行界面是从一种对话形式演变而来的,这种对话形式曾经是由人类通过电传打字机(TTY)进行的,在这种对话中,人类操作人员远程交换信息,通常是一次一行文本。
在Linux的学习中,命令行形式的Shell是我们重点需要进行学习的,也是走向Linux高手的必由之路。
Bash是一种主流的命令行Shell程序,提到命令行,我们对其的固有印象可能认为它就是一个黑乎乎的框子,所有的命令行好像都是一个样子的,现实并不是这样的,命令行Shell程序有非常多种,以下列出几种主流的命令行Shell程序:
在大多数时候,我们使用的都是图形界面的Shell,因为更加直观,更好操作。Linux系统中也有不少的图形界面Shell,如:GNOME、Unity、KDE、XFCE等等,不同的图形界面环境总是容易辨认,例如菜单项不一样、图标不一样、配色不一样等等。
不同的命令行Shell之间的区别并不向图形界面的Shell那么明显,因为它们一般都是黑底白字。但是根据命令行Shell程序的不同,它们的语法和所能提供的功能也会有所不同。
在介绍这两个概念之前,我们先来了解另外的一个概念——pty
,什么是pty呢?即pseudo-tty(伪终端),也就是假的终端,一般是指使用软件模拟出来的命令行终端。
上文中介绍过的tty
是代表了真正的设备,而pty并不是代表设备的,而pty所代表的是一种使用软件模拟出来的终端。所以它其实只是代表了一种通信管道。
Linux man中对ptms的介绍,通过这个介绍,我们可以知道,ptms实际上就是一种pty的实现方式。
When a process opens /dev/ptms, it gets a file descriptor for a pseudoterminal master(PTM), and a pseudoterminal slave(PTS) device is created in the /dev/pts directory. Each file descriptor obtained by opening /dev/ptmx is an independent PTM with its own associated PTS, whose path can be found by passing the discriptor ptsname.
通过上面的描述,我们可以将 /dev/ptmx 理解为一个linux操作系统对外提供的服务,通过调用这个服务可以创建一个连接,而每一个连接到这个服务端的软件终端都会有一个对应的 /dev/pts/N 文件与之对应。我们可以认为每一个 /dev/pts/N 文件就代表了一个伪终端,客户端(比如Windows平台下的xshell远程连接工具)将数据发送到 /dev/pts/ptmx 文件中和Linux系统进行通信,而Linux系统通过将响应数据写入对应的 /dev/pts/N 文件中来对客户端进行响应。
用软件模拟出来的终端有可能是类似于Windows平台下的XShell这种远程连接工具,以下是我使用xshell软件连接到远程Linux主机是Linux主机中 /dev/pts 目录下的文件列表和我的xshell打开的会话列表
还有一种可能就是桌面Shell的Linux系统中,一般都集成了软件窗口版的命令行Shell,这种软件窗口的命令行shell也是使用了ptms实现的伪终端,如下是我在Ubuntu系统下打开的终端的显示情况:
可以看到,情况跟上面使用xshell进行远程连接是一样的。
计算机的硬件和软件其实是密不可分的,比如我们在提到一个终端的时候可能会说这是一个图形终端还是一个命令行终端,意思就是这个终端设备和计算机进行交互的方式是使用命令行还是使用图形界面。随着计算机技术的不断发展,计算机硬件和软件变得越来越密不可分,所以我们现在再来讨论这些术语的时候,它们所指代的概念也越来越趋于统一了。
比如我们在MacOS上和Linux的一些图形Shell中可能会看到打开终端的选项,其实这里的终端指的是虚拟终端。再比如我们在Windows系统中可能会看到打开命令行的选项,这里的命令行其实也是一种软件模拟出来命令行终端。
随机计算机技术的不断发展,除了非常大型的计算机,一般的计算机已经没有单独的控制台了,一般的显示屏键盘鼠标就可以充当计算机的控制台,在计算机启动的时候通过事先写入主板ROM中的BIOS程序对计算机进行一些底层的设置。
在Linux系统中,计算机显示器通常被称为控制台终端(Console)。它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等
我们称这些文件为虚拟控制台文件&#xff0c;例如/dev/tty1代表第一个虚拟控制台。这些文件赋予Linux多窗口切换的能力。我们通常在Linux下看到的控制台一般是/dev/ttyN&#xff0c;其中一般 1<&#61;N<&#61;63&#xff0c;而并不是 /dev/console。
而比较特殊的是/dev/tty0&#xff0c;他代表当前虚拟控制台&#xff0c;是当前所使用虚拟控制台的一个别名。因此不管当前正在使用哪个虚拟控制台&#xff08;注意&#xff1a;这里是虚拟控制台&#xff0c;不包括伪终端&#xff09;&#xff0c;系统信息都会发送到/dev/tty0上。只有系统或超级用户root可以向/dev/tty0进行写操作。tty0是系统自动打开的&#xff0c;但不用于用户登录。只有在单用户模式下&#xff0c;才允许用户使用控制台进行登录。
同时。/dev目录下还有一个tty文件&#xff0c;这个文件就代表了用户当前所使用的虚拟控制台&#xff0c;下图可以表明他们之间的关系。
例如当使用ALT&#43;F2进行切换时&#xff0c;系统的虚拟控制台为/dev/tty2 &#xff0c;当前控制台&#xff08;/dev/tty&#xff09;则指向/dev/tty2
你可以登录到不同的虚拟控制台上去&#xff0c;因而可以让系统同时有几个不同的会话存在。
可以注意到我们上面对tty的介绍是虚拟控制台&#xff0c;但是tty文件还是真正的代表了终端设备的&#xff0c;而虚拟化的的核心其实就是重用&#xff0c;我们通过虚拟终端对物理终端也就是Console终端进行了重用。
还是那句话&#xff0c;名词之争没有太大的意义&#xff0c;计算机发展到现在&#xff0c;我在理解终端相关的概念的时候&#xff0c;只需要做到不影响沟通交流就可以&#xff0c;用不着死扣概念和字眼。
学好命令行是学好Linux的必经之路&#xff0c;那命令行到底是什么呢&#xff1f;
其实我们这里所说的命令行是Bash这样的命令行Shell&#xff0c;它其实是一个命令解释器&#xff0c;是操作系统在Shell中实现的命令行接口&#xff0c;用于交互式访问操作系统功能或服务。命令行界面通常在终端设备中实现&#xff0c;终端设备也能够使用面向屏幕的基于文本的用户界面&#xff0c;这些用户界面使用光标寻址将符号放置在显示屏幕上。
最本质上的&#xff0c;命令行Shell(以下简称cli)其实是一门脚本语言&#xff0c;它的上下文环境就是操作系统(当然可能需要配置Path环境变量)&#xff0c;安装在操作系统中的可执行程序就是这门语言中一个个的函数。我们正是通过cli来调用这一个个的函数来达到和操作系统交互的目的的。
命令行界面是从一种对话形式演变而来的&#xff0c;这种对话形式曾经是由人类通过电传打字机(TTY)进行的&#xff0c;在这种对话中&#xff0c;人类操作人员远程交换信息&#xff0c;通常是一次一行文本。命令行元素之间的分隔符是空白字符&#xff0c;行结束分隔符是换行分隔符。这是cli中广泛使用的(但不是通用的)约定。
当然&#xff0c;作为一门语言&#xff0c;cli也为我们提供了一些关键字和操作符&#xff0c;比如Bash中的管道符|
&#xff0c;重定向符>
、2>
等等&#xff0c;而命令在cli中则是作为一个标识符存在的&#xff0c;cli搜索命令的上下文就是操作系统的Path环境变量和当前目录。
关于关键字、操作符、标识符等编程语言的基本元素可以参考文章《高级语言中的单词——5种类型的token》
当我们在cli中输入一行文本之后&#xff0c;cli就负责解释我们的文本&#xff0c;它会分辨出我们调用的命令是什么&#xff0c;命令的调用是从什么地方开始&#xff0c;到什么地方结束&#xff0c;有哪些字符是需要传递给命令的信息等等。
如下&#xff1a;
ls -l > ls_result
在上面的命令中&#xff0c;因为我们使用了重定向符号>
&#xff0c;所以Bash就会知道ls是我们想要调用的命令&#xff0c;而-l是需要传递给ls命令的信息&#xff0c;最后对ls命令调用的结果需要重定向到当前目录的ls_result文件中。
而cli提供给我们的关键字和操作符通常是比较有限的&#xff0c;其主要功能还是依赖于各个命令&#xff0c;所以命令的调用方式就显得非常重要了&#xff0c;所以接下来我们简单介绍一下命令调用的基本语法。
上文中介绍过&#xff0c;Bash是最主流的命令行Shell之一&#xff0c;它也是大多数Linux系统的默认Shell&#xff0c;所以这里我们就以Bash为例介绍一下命令调用的基本语法。注意这里我们说的是命令的基本语法&#xff0c;而不是Bash的基本语法。对于Bash的语法&#xff0c;我们会在后面介绍脚本的时候进行介绍。
如果说Bash是一个命令解释器&#xff0c;是一个语言的话&#xff0c;那么每一个命令其实就相当于这门语言中的一个函数。我们调用函数的时候需要给函数传递参数。而cli这种语言比较特殊。因为它的上下文环境是操作系统&#xff0c;那么它的参数往往只有两组&#xff0c;那就是source…和target&#xff0c;有的命令可以接收多个源。
而剩余的信息&#xff0c;则类似于其他编程语言中的具名参数&#xff0c;而在cli中我们通常称之为命令的选项。很多命令的选项不需要指定值&#xff0c;这种类型的选项类似于一个功能开关&#xff0c;我们只要指定了这个选项&#xff0c;那么就相当于打开了开关&#xff0c;还有一些命令选项是需要接收值的。
了解C语言的同学应该知道&#xff0c;C语言的入口函数main的签名是int main(int argc, char* argv[])
&#xff0c;它可以接收两个参数&#xff0c;第一个参数代表的是参数的个数&#xff0c;第二个才是字符串类型的参数列表&#xff0c;而这个列表往往就是程序接收cli信息的地方。
也就是说&#xff0c;整个命令行的选项只是传递给程序的一个字符串&#xff0c;程序可以以程序员希望的任何方式处理它&#xff0c;只要解释器能够告诉命令名在哪里结束&#xff0c;它的参数和选项在哪里开始。
这也就造成了一个问题&#xff0c;我们在cli中调用命令的语法并不是强制的&#xff0c;选项的格式在不同的cli之间可能存在很大的差别。在大多数情况下&#xff0c;语法是根据约定而不是操作系统的需求。
虽然对参数和选项的解释方式是由命令的开发者自己决定的&#xff0c;但是大多数的操作系统中都对命令的语法有着不成文的约定。
Linux下的约定一般是这样的command [OPTIONS]... [SOURCE]... [TARGET]
&#xff0c;通过上文介绍我们知道其实只有SOURCE和TARTE这两组代表的是真正的参数&#xff0c;它们一般是文件系统中的一个路径&#xff0c;这个就是直接传递就可以了&#xff0c;操作系统会以当前的文件系统作为上下文来对TARGET和SOURCE进行搜索。
下面我们重点要介绍的是Linux系统下对命令选项语法的约定。
-
开头的短选项短选项使用一个-
然后一个字母(例如-c
)来标识选项的使用。如果-
后面跟着两个或者更多的字母&#xff0c;这可意味着指定了多个选项或者也可能意味着一个或多个选项以及第一个需要接收值的选项以及该选项的选项值。
比如&#xff1a;
command -p
command -p -a -c -T result
而上面代码中的第二行我们一次指定了多个选项&#xff0c;并且为选项-T指定了选项值result&#xff0c;这个时候我们可以把第二行命令简写成如下的方式&#xff1a;
command -pac -T result
# 或者是
command -pacT result
# 甚至是
command -pacTresult
当我们为一个可以接收选项值的短选项指定值的时候&#xff0c;可以有上面的三种写法&#xff0c;第一种写法command -pac -T result
中&#xff0c;因为-T选项是需要值的&#xff0c;所以我们把它单拎出来了&#xff0c;并且在-T和它的选项值之间加了一个空格来进行分割&#xff1b;写法二command -pacT result
则把所有的短选项进行了连写&#xff0c;但是把需要接收值的选项放到了选项列表的末尾&#xff0c;并在它和它的选项值之间加了空格进行分隔。第三种写法中则直接连写了&#xff0c;这个时候&#xff0c;根据约定&#xff0c;T选项是需要接收值的&#xff0c;那么选项列表中位于T之后的所有的字符将都认为是T的选项值而不是其他的选项了。
--
开头的长选项使用--
(两个连字符)然后一个单词(例如--create
)来标识选项的使用。长选项具有更高的可读性。
需要注意的是&#xff0c;因为长选项是有多个字符的&#xff0c;所以它不再支持连写&#xff0c;并且如果我们需要为长选项指定值&#xff0c;则必须在长选项和它的值之间添加分隔符进行分隔&#xff0c;一般是使用&#61;
或者是一个空格&#xff0c;如下&#xff1a;
command -target&#61;result # 使用 &#61; 分隔选项和选项值
command -target result # 使用 空格 分隔选项和选项值
--
分隔的剩余参数如果是--
后面不跟任何的单词而是直接跟着空白字符的话&#xff0c;则表示剩余的所有字符不应该被视为选项&#xff0c;例如&#xff1a;
command -- -c --create
在这个示例中&#xff0c;-c
和--create
都出现在--
后面&#xff0c;这个时候-c
和--create
就不应该被认为是选项&#xff0c;而是普通的参数。
这是非常有用的&#xff0c;比如文件名本身就是以-
开头的时候&#xff0c;或者是进一步的参数其实是一个内部的命令的时候&#xff0c;我们可以利用这一点很方便地分别对主命令和内部命令指定选项和参数。
参考自维基百科&#xff1a;https://en.wikipedia.org/wiki/Command-line_interface#Option_conventions_in_Unix-like_systems
这篇文章我们简单介绍了终端和命令行相关的概念&#xff0c;同时介绍了Linux下命令调用语法的基本约定&#xff0c;接下来&#xff0c;我们可以开始步入Bash这个Shell的更深度的学习了。
感谢你耐心读完。本人深知技术水平和表达能力有限&#xff0c;如果文中有什么地方不合理或者你有其他不同的思考和看法&#xff0c;欢迎随时和我进行讨论(laomst&#64;163.com)。