unix-like系统的初步知识
Shell承担着解释用户在CLI方式下所输入的命令的重要任务。当用户输入一个概念V的表达(即命令名)之后,Shell如何去找到系统中关于概念V的实现(即用来实现命令的程序)的呢?
Shell的做法,通常分为两个步骤:
(1)判断用户的输入,是否为Shell的内置命令(即这样的命令是Shell的一部分,这类似于Microsoft DOS中的COMMAND.COM所提供的“内部命令”)。若判断为是,则执行;若判断为否,则:
(2)在PATH环境变量所提供的路径(目录名)中,逐个路径地去寻找符合输入的命令。若找到,则执行;若找不到,则反馈“Command not found.”信息,即报错。
当然,如果用户显式地、完整地向Shell输入了概念V的实现(即可执行程序)所处的位置(即完整的路径/目录名称),那么Shell将直接执行那个可执行程序。
跟Microsoft DOS不同的是,UNIX-like系统不会认为用户的当前工作目录是PATH变量“理所应当的”一部分,也就是说,它不会在当前工作目录(与PATH所提供的目录不同)中寻找概念V的实现。
如果要执行当前工作目录中的可执行文件,应该使用这样的命令形式:
代码:
./foo
即:在可执行文件名的前面,顶上“./”(一个小数点加一个正斜杠)—— 这有强调当前工作目录的意味,这是出于对系统安全考虑的强制规定。
通过学习上面的知识,我们可以理解这麽一个技巧:
我们可以特地地将一些专门的路径(某些经常要用到的程序所处的位置),放置到PATH环境变量中,这样我们就不必每次输入完整的路径或切换工作目录了。
更新一个环境变量的实际的值,在csh/tcsh这样的Shell中,应当使用命令:
代码:
setenv VARNAME value
而在sh/bash这样的Shell中,应当使用命令:
代码:
export VARNAME=”value”
请注意上述二者在命令名、等号、双引号等方面的区别。
举个例子。如果我们希望我们的家目录成为PATH环境变量的一部分,那么,在csh/tcsh下,就可以使用这样的命令:
代码:
setenv PATH $PATH\:$HOME
请注意,在这个命令中,PATH环境变量中用来黏合路径用的“:”被写成了“\:”,这是因为,如果csh看到了一个单独的“:”紧跟在字符串后面,会将它解读为一个Built-In Command(内置命令)从而导致反馈“Bad: modifier in $ ($)”即第二个“$”解读错误,所以,为了让csh能在概念A的表达中正确地解读到一个“:”,我们就必须在“:”前面顶上一个“\” —— 这称为“转义序列”机制,学过C语言的学员,对此一定不会感到陌生。又如,我们希望系统反馈这麽一行字符:
代码:
$SHELL
那么我们可以使用命令:
代码:
echo \$SHELL
除了利用转义序列规避csh对概念A的表示解读失败,我们还可以使用命令:
代码:
setenv PATH ${PATH}:$HOME
实现相同的效果 —— 可见,一对花括号可以拦阻csh将单独的“:”与之前的字符串相黏合,从而规避将其解读为一个命令。
我们使用命令:
代码:
w
可以观察到当前整个系统上,有哪几位用户登录,他们分别从那个终端接入的。比如,反馈是这样的:
代码:
USER TTY FROM LOGIN@ IDLE WHAT
root v0 - 3:31PM 2 -csh (csh)
USER一栏给出了用户名;TTY一栏给出了该用户所接驳的终端名称;FROM一栏给出了该用户的登录地点(一般远程登录者以网络地址如IP地址形式给出);LOGIN@一栏给出了该用户的(最近一次)登录时刻;IDLE一栏给出了该用户的“发呆时间”(即从该用户最后一次敲键盘至当前时刻所间隔的分钟数);WHAT一栏给出了该用户当前的“正在干什么”(即当前的进程名及其参数)。
Shell有一种必需的本领,即向它所启动的程序提供Stdio(标准I/O)环境。标准I/O环境,这个抽象的概念是什么意思呢?
我们知道,一个概念V的实现(命令程序)是由Shell找到并启动的。当Shell启动一个程序之后,就会向这个程序提供三个File Handle(文件句柄),这三个文件句柄即构成了属于每个程序自己的标准I/O环境,它们分别是:
(1)stdin —— 标准输入,也叫“FD0(FD是File Descriptor的缩写,意同File Handle)”,这是一种关于字符的Input Stream(输入流),通常,这个流的源头,就是我们手头的键盘。
(2)stdout —— 标准输出,“FD1”,这是一种关于字符的Output Stream,通常,这个流的出口,就是我们面前的终端屏幕(即/dev/ttyvx文件,因此,该流以文件句柄的形式存在),当然你也可以使用前述的“重定向”方法将改流转向输出其他文件(犹如黄河改道,从江苏省流向黄海)。
(3)stderr —— 标准错误信息输出,“FD2”,从流与流的输出形式上说,它与stdout一致,只是程序主体会选择性令错误信息经由这个途径流出。为什么要这么做?这其实是一个很巧妙、很合理的设计,目的在于:使程序的正常输出(它的“本分工作”的结果)与它在遇到错误时所报出的错误信息可以分开,泾渭分明,这样,那些期望攫获该程序正常结果的另一个程序,就可以仅仅收到期望的信息,而不受报错信息的干扰。而且,我们可以通过“重定向”机制,将stdout与stderr流分别导向到不同的文件中去,具体方法是:
代码:
(foo > bar) >& qux
将foo程序的stdout即FD1流写入文件bar,且将foo程序的stderr即FD2流写入文件qux。※由于FreeBSD的默认Shell即tcsh并没有提供单纯而直接的方法,将stdout流与stderr流分别重定向,所以,tsch的用户必须使用上述技巧实现stdout与stderr的分别重定向。
而对于GNU/Linux普遍采用的bash,分流的方法则十分清晰明了:
代码:
foo > bar
或
代码:
foo 1> bar
是将foo程序的stdout即FD1流写入文件bar。
而
代码:
foo 2> qux
是将foo程序的stderr即FD2流写入文件qux。
※请注意,数字“1”或“2”都必须紧贴“>”,否则数字将被视作概念O的表达。
所以,我们要引入一个新的范式:
STDIN-PROG-STDOUT(-STDERR)
简写作:
I-P-O(-E)
这个范式跟之前(关于用户与系统之间关系)的V(O,A)范式不同,它是用来概念化为PROGrams(程序)与Shell为其所提供的标准I/O环境及其要素的。在每个要素之间,川行的是数据流,数据流透过Stdio句柄被接纳或释出。
现在,我们考虑一个问题:既然程序可以由Shell为其创建的stdin(源头通常是键盘)来获取输入信息(流),那么,是否可以选择其他的信息输入源头呢?比如bar程序不再从用户的键盘获取输入信息,而是以foo的输出作为自己的输入源头,即:foo程序的stdout流与bar程序的stdin流“对接”(信息如水流自上而下地行进)?这样的“对接”完全是可以实现的,那就是利用Shell提供的“管道”机制!命令的形式如下:
代码:
foo | bar
我们回到一开始的例子:
代码:
ls | grep "^foo.*"
ls命令程序的输出信息是当前一级目录里的所有文件,这样的信息应该是:
代码:
bar1
bar2
bar3
……
bar9998
bar9999
foo1
foo2
foo3
……
foo9998
foo9999
一共20,000行。
※寻常状况下,我们所看到的ls命令(无参数)的反馈,各个文件名都是按照表格式样排列呈现的,但当这些信息成为stdio流的时候,其实是按照“行”呈现的,例如我们使用命令:
代码:ls | less
就可以看到文件名以“行”排列呈现。less命令是一个用来查看文本类型文件的内容的程序,使用时按[PageUp]与[PageDown]键可以实现翻页,按[↑]与[↓]键可以实现滚动,按[q]键盘退出程序。
这20,000行的信息并没有被反馈到终端屏幕(即/dev/ttyvx文件)上,而是经由管道哺馈给(流向)了管道符后面的grep命令程序,即这10,000行信息成为了grep命令的stdin的内容。
grep命令程序,全称是“Global Regular Expression Print”,这个全称揭示了这个工具的特性:“Global”指它的工作对象是“全局”也就是全部的行,“Regular Expression”就是“正则表达式”,“Print”就是“打印”,统合起来说,就是:在stdin的全部行中,找到跟给定正则表达式匹配的内容,并将该内容所在的行打印出来(该内容本身通常会被高亮凸显)。
说到“正则表达式”(简称RegExp),它又是一个极具UNIX STYLE的超级武器。RegExp的内容极为广泛与庞杂,若要精通它,恐怕要跟精通C语言的难度相当。许多讲述RegExp的教材的作者自己就对它一知半解、不得真要,所以写出来的教材也让学生看得云里雾里、稀里糊涂,更是完全无法发挥这套武器的强大威力。本人根据自己多年在UNIX?-like系统上的琢磨和捣鼓,或许可以用最简洁、最切中要害的方法,向你们解明RegExp的基本精义。
※在当前的FreeBSD系统中,虽然/usr/bin/grep与/usr/bin/egrep两个程序完全一致,但要用到egrep(扩充语法的grep),还是必须显式地使用egrep命令,或用“grep -E”。下面的内容,是针对egrep的(更简单、更强大)。
—— 只要八个汉字:
转义、数量、位置、逻辑