在上一节课(Web地址 http://www.cpp7.com/bbs/forum.php?mod=viewthread&tid=25)中,我讲到了UNIX、GNU和Linux这些东东,在有些同学的作业反馈里,也提到它们,并说已经去维基百科它们了 —— 这种积极预习的做法,是值得褒奖的。
那么,我们在正式进入第二课之前,我就来给大家做一个跟UNIX、GNU、Linux等等有关的测验。题目是这样的:请将下图中左边圈子(即Set A)里的各图标,与右边圈子(即Set B)里的与前者相匹配或对应的名称,用箭头连接起来。下图中,已经给出了一个连线示例,请你完成全部的连线。
在不倚助于Google等工具的情况下,你所答出的正确连线之数量,或许可以预示你学习接下来的课程的相关情况:
0条 —— 你完全不适合本课程的学习,劝退。
1 – 14条 —— 你的计算机学科基本素养一般,学习后面的课程,须投入很多额外的努力。
15—28条 —— 你的计算机学科基本素养较好,学习后面的课程,须投入稍多一些的努力。
29—41条 —— 你的计算机学科基本素养非常好,学习后面的课程,将会比较轻松。
如何评判你做出的各条连线是否正确呢?当然是可以利用Google等工具了。在得到这类工具的帮助的情形下,如果你在60分钟内,仍不能确定全部正确连线,那么,结论依然是:你完全不适合本课程的学习,劝退。
经过上面这一回自测之后,我们可以正式进入本课程的第二节(第二课)。
首先,我来解释一下本课的标题。
什么叫“脱屌第一步”呢?
这里的“屌”,指的当然不是男性的外生殖器(否则本课程就成了令人自宫的《葵花宝典》了),也不是“很牛”、“很厉害”的意思,而是指“电脑技术屌丝”,简称“电脑屌丝”。
那么,什么是“电脑屌丝”呢?
在本课程中,关于“电脑屌丝”,藉由以下这样的量表(Rating Scale)来判断或衡量。
这个量表,分为六个轴。
第1轴,关于对何种操作系统(仅以下列各种操作系统作为参照指标)的依赖或熟练程度:Microsoft Windows®的分值最高;GNU Linux(或含前一项)的分值非常接近零;UNIX(或含前一项)的分值为零;诞生于UNIX以前的操作系统(或含前一项)的分值为负数且其绝对值与操作系统兴起年代负相关;若不知操作系统为何物,则完全不适合本课程的学习。(※ Windows®是Microsoft Corporation的商标。)
第2轴,关于平时热衷于如何捣鼓电脑软件:喜好或熟练于(为己或为人)安装或重装 Microsoft Windows操作系统的分值最高; 喜好或熟练于配置 Microsoft Windows上各类GUI软件
注1(或含前一项)的分值次高;喜好或熟练于在Console/Shell下配置UNIX-like
注2上的各类软件(或含前一项)的分值非常接近零;喜好或熟练于自己开发软件(编写小程序或RAD开发的除外)(或含前一项)的分值为零;喜好或熟练于对软件进行逆向工程(或含前项)的分值为负数且其绝对值与工程难度正相关;若不知软件为何物,则完全不适合本课程的学习。
脚注
(1)GUI,Graphical User Interface,用户图形界面,就是由诸如窗口、按钮、菜单、文本框、图标等等视觉与鼠标操作要素构成的界面的。与GUI相对的是TUI,Text User Interface(文本类型用户界面),也叫CLI,Command Line Interface(命令行界面),一般说来,就是用户通过它给出的提示符(Prompt)向系统键入一系列文字命令,且系统通常也通过该界面给出反馈信息。而负责提供提示符与解释用户键入命令的意义的机构,称为Shell(壳)。
(2)在本课程中,UNIX-like指一切类似于UNIX的操作系统,或它们的统称。这个词也可以被写作:*NIX或UN*X。作为统称,它包含但不限于以下操作系统:各类BSD、MINIX、GNU/Linux、GNU Hurd、GNU/OpenSolaris、 System V、RTOS、Syllable、AIX UNIX®、HP-UX UNIX®、IRIX UNIX®、NeXTSTEP、Mac OS X、iOS、SunOS、Solaris UNIX®、SCO UNIX/UnixWare/OpenServer、Xenix、VxWorks等。(※ UNIX®是The Open Group的商标。)
※ GNU的发音为[gnu:],这个词是“GNU is NOT UNIX”的缩写。Linux的发音为[ˈlɪnəks]。
第3轴,关于平时热衷于如何捣鼓电脑硬件:喜好或熟练于(为己或为人)攒机或策划攒机的分值最高;喜好或熟练于利用汇编语言编程(或含前一项)的分值非常接近零;喜好或熟练于捣鼓单片机(或含前一项)的分值为零;喜好或熟练于自己动手捣鼓电子电路(或含前一项)的分值为负数且其绝对值与工程难度正相关;若不知硬件为何物,则完全不适合本课程的学习。
第4轴,关于网络黑客经历:追求或喜好或熟练于使用诸如木马/蠕虫/间谍软件等工具的分值最高;喜好或熟练于使用扫描/嗅探等工具(或含前一项)的分值次高;喜好或熟练于分析网络服务协议/数据包(或含前一项)的分值非常接近零;喜好或熟练于自己开发服务器软件(或含前一项)的分值为零;喜好或熟练于解密工程(或含前一项)的分值为负数且其绝对值与工程难度正相关;若在以往五年之中有出于贪婪私欲利用黑客技术攫获他人任何财富或权利(不论对象是无辜的百姓还是恶贯满盈的官员,只要你有出于满足私欲的主观故意,则是。这里完全排除了所谓“劫富济贫”的客观结果因素)的经历,则完全不适合本课程的学习。
第5轴,关于平时的金钱开销程度:分值与经济上依赖父母或家长的程度正相关。若对父母或家长的经济依赖达到或超过了他们通常情况下可以承受的极限,则完全不适合本课程的学习。
第6轴,关于平时的除电脑以外的兴趣爱好:沉溺于赌博或任何婚姻外的性行为(包括通奸、自慰)(或含后一项)的分值最高;沉溺于观看渲染性行为的影像或文字资料(如俗称的“A片”)(或含后一项)的分值次最高;沉溺于网络闲聊或漫无目的泡网(或含后一项)的分值再次高;嗜好吃喝(或含后一项)的分值再次次高;喜好或熟练于阅读文字书本(不含前一项)的分值非常接近零;喜好或熟练于数学(或含前一项)的分值为零;喜好或熟练于其他理科或工科的理论或实验(或含前一项)的分值为负数且其绝对值与其造诣正相关;若对摄入精神药物或麻醉药物有非医学用途造成的瘾癖,则完全不适合本课程的学习。
上述六个轴上的最高分,可以由你自己来设定(须一致),比如5分或10分;而其余的分值档次,可以由你来适当地来划分。
如果你在六个轴上的评判均为最高分,那么,你就是一枚彻头彻尾的、最最典型的电脑屌丝。如果你在六个轴上的评判均非常接近零分,那么,你的身上就已经几乎完全没有任何电脑屌丝的成分了,学习接下来的课程,对于你来说就是如履平地。如果你的得分介于上述两者之间(但没有出现负分),那么,学习接下来的课程,你将付出一定的辛劳,总体的辛劳程度与总体的分数正相关。如果在某个或某些轴上评判为负分,那么, 学习接下来的课程,你将健步如飞。如果你在六个轴上的评判均没有出现正分,那么,你是名副其实的电脑高手或牛人或大神。还有,只要在量度过程中,出现一次“完全不适合本课程的学习”的评判,那么,不论接下来的评分或评判如何,你就是真的完全不适合本课程的学习,强烈建议你果断放弃本课程。综上所述,对于受测者,只要在其身上不出现超过最高分的评判结果,就都是本课程可以接纳的学员。
我们可以在一个平面上,将上述六个轴的正分值与零分值部分刻画出来,如下图:
当然,类似地,我们也可以在一个平面上,将上述六个轴的负分值与零分值部分刻画出来(将负分变为其绝对值),如下图:
最后,我们还可以综合地,将上述两幅图合到一个3D空间中来(但上述两平面中的一个必须作镜像反转),如下图:
这样,我们可以看出,在零分值所标定的那道红色环线 —— “赤道”的上方,是“电脑屌丝”即属于“矮矬穷”的空间,而 “赤道”的下方,则是属于“黑极富”的空间。
关于“黑极富”:“黑”指黑客(Hacker),精通计算机系统或对计算机系统有深刻理解的人。“极”指极客(Geek),在计算机方面能力超群或富有独特的钻研与创造力的人。至于“富”,这里基本上没有什么引申的含义,指的就是经济上的富裕(Wealthy) —— 我们的课程,从来不会排斥将我们的学员培养成可以拥有丰厚经济收入的人。我们认为,能够成为“黑”且“极”的人,自然完全可以成为资财富有的人。不过,我们的要求是:成为富人之后,必须即刻与持续地孝敬自己的父母。一个不懂得孝敬父母的黑客,终将沦为贪婪可鄙的Cracker。
如果你不幸已经失怙,那么,请用你收获的金钱善待你的配偶。
我们这个课程的目标,就是:将你从“矮矬穷”的电脑屌丝(如果你是的话),拉扯成为一位合格甚至优异的“黑极富”。不过,如果你在前述的第5轴和第6轴上持续高分的话,那么我们将无法保证为你实现上述目标。
回到本节课的标题“脱屌第一步”。这个标题说明了本节课的内容,是将你拖离电脑屌丝的第一步。即便是在目前的这个“脱屌”阶段(入黑的预备阶段),这第一步也是非常小的一步,但是,它的意义重大,它将为电脑屌丝们开启一道全然崭新的门径。并且,我可以预先告诉你一个令你振奋的结论:只要你认真学习并反复操练直至熟练掌握本节课所涉及的知识与技能,那么,你将在你所身处的电脑屌丝圈子里,成为一个实至名归的“小高手”!
此外,本节课还有一个副标题 —— “Vim Fundamentals”,它揭示了本节课教学的终点,即教授学员掌握 Vim的最基要使用方法。
Vim 是什么?
当你阅览到上面这几行中出现的“Vim”这个单词的时候,请注意它的书写:在指代该软件的名称的时候,它的第一个字母必须大写。此外,一个特别容易被忽略或胡乱猜测的问题是:“Vim”一词的发音,显然你无法直观地从文字书写的角度知晓它的发音,所以,必须由我来告诉你:它的发音是[vim],而不是久经讹传的[ˈvi: ˈai ˈem]。(※ Vim所属的许可协议兼容于 GNU General Public License。)
Vim 是一个软件,它的基本用途是以“文本编辑”这个功能呈现的,也就是说,在一个不太辽阔的视角下,它是一款文本编辑器。从文本编辑器这个层面上说,它类似于电脑屌丝们在Microsoft Windows上使用的前者所自带的“记事本(Notepad)” —— 如果你在论坛上发帖的时候,不想自己辛苦键入的文字,被网页自动刷新掉或因为提交失败而彻底丢失的话,通常你就会先在“记事本”里输入和编辑文字甚至将它们保存好,是不是这样?没错,就是那个“记事本”。
或许你很难将“会用一款‘记事本’一样的软件”与“成为屌丝群中的‘小高手’”联系在一起。如果你有这样的疑惑,反而可以揭示出Vim的重要性,具体地说,这是一种作为黑客必须熟练掌握的最基本工具的重要性。
在我们讨论电脑屌丝量表的第4轴的时候,涉及到了这么一种现象:典型的电脑屌丝喜好或只会使用所谓“黑客工具”,而在普遍的实际情况中,这一类所谓“黑客工具”都是GUI的。
在后续的课程中,你将了解到:本课程十分抵制那一类所谓“黑客工具”的软件,至少会与它们保持相当长远的距离。我们的目标是:尽量由我们自己亲自动手编写实现黑客功能/用途的程序或软件 —— 这本身就是黑客的内容。(所以说,那些使用所谓“黑客工具”尤其是GUI的工具的人,根本算不上黑客。)
提到编写软件,有些电脑屌丝们通常会想到诸如Visual Studio或者Eclipse这样的IDE(集成开发环境),通常这类他们所听闻或熟知的IDE也都是GUI的。顺便提一下,Microsoft Windows自带的记事本,显然也是GUI的。
如果总是依赖于GUI,你将永远无法成为真正的黑极富 —— 这有颇具现实意义的理由:当你登录一个UNIX-like系统从事黑客的时候,几乎不可能有GUI环境供你使用,即便有,那也是一个极端糟糕的选择,必须远离直至摒弃!根据我们课程的目标之一,即:你必须在系统提供给你的最小的条件下,展开对关键信息的编撰或更改工作,譬如编写程序、查看或更改系统上的设置。正是基于对这个宗旨的贯彻,我们在学习Vim之前,还必须学习vi的基本使用。通常情况下,只要是一个完整的UNIX-like系统,vi是该系统上的文本编辑器的最低配备,换句话说,如果一个UNIX-like系统自带了文本编辑器,那么vi将是一个“下限”。而我们的黑客教学,必须从这一类“下限”起步。
至此,我们提到了vi。请注意,“vi”这个单词的拼写,即便是指代该软件的名称的时候,也是所有字母都小写。而这个单词的发音,则是[ˈvi: ˈai],千万不要念成[vi:]。(※ vi的许可协议是BSD License。)
现在,我来讲述我的一个小小的亲身经历,它可以体现出掌握vi使用的重要性。
我有一部用来解码卫星电视信号的机顶盒 —— 它从卫星天线(“锅”)那里摄取人造地球卫星广播出来的信号,然后将这些信号解释成为可以观看的电视节目,输送到电视机上。这一类的机顶盒,其实就是一台电脑,只不过没有显示器与键盘鼠标与它相连接(电视机还不能完全算作它的显示器)。这台电脑使用的操作系统是Linux的(请注意,我是在说“是Linux的”而非“是Linux”)。有一次,为了能更长久地收看卫星电视节目,我须要更新机顶盒里的某项设置。进行这种操作,屌丝一些的做法是通过一台与机顶盒同在一个网络中的电脑(通常是Microsoft Windows的)上的一个GUI软件,连接到这部机顶盒,然后在一个图形界面中,为机顶盒修改那些设置。但这样做,违背了黑客的一个原则,即采用最直接的(通常是“幕后”的)途径来实现某项工作。上述那个Microsoft Windows上的GUI软件,之所以能够连接并读写机顶盒,是因为该软件的运行是建立在telnet这样的远程登录服务的基础之上的。也就是说,该GUI软件只是把telnet这个仅需的功能给包装起来而已,且包装得花里胡哨。作为黑客,一旦了解到这个事实(这也是必须的),就应该直接使用telnet登录那部机顶盒,修改相关设置。而这些设置是以文本文件的形式存放在机顶盒上的。这就意味着,我只需利用telnet登录机顶盒一次,就可以把查看、编辑(修改)、保存设置及至重启机顶盒的工作全部完成(而不需要任何额外的工具,进行诸如导出、导入、甚至“远程操控”这样的工作)。于是,我就telnet上了那台机顶盒,telnet在我所使用的电脑上为我呈现了机顶盒的Shell。我透过Shell,输入命令“vi”,果然地,vi这个文本编辑器被打开了,确切地说,是机顶盒里的vi程序被我打开了。利用机顶盒里的vi程序,查看、编辑、保存机顶盒里的文件,是非常自然的事情,就像在自己的(本地的)电脑上使用本地的记事本操作本地的文件一样。
显然那部机顶盒里没有Vim —— Vim比vi更高级一些,也更好用:Vim是Vi IMproved的意思,而vi则是“Visual”的缩写 —— 见识过Microsoft Visual Studio的屌丝们,不要被“Visual”这个名头给迷惑了,千万不要以为vi具有“亲切友好”的可视化界面,vi压根儿就没有这种特质:在vi里面,你甚至不能使用键盘上的方向键来操纵光标的移动!这就好像你在玩电子游戏的时候,手柄上的方向按键是无法操纵主角人物移动的一样。
上面说了这么多,算是初步阐释了一下关于Vim和vi的使用背景以及它们在黑客生涯中的重要性。再强调一下吧:
你若不会使用Vim或vi,那么在黑客的世界里,就好像现实社会中那种大字不识一个的纯文盲一样!
下面就要开始扫盲了!
在扫盲的起头,有些事情还须要说明一下。
Vim是有GUI版本的,比如gvim,即图形化的vim。这类GUI版本的Vim甚至可以在Microsoft Windows上安装并使用。但是,本课程极力抵制这类GUI版本的软件,原因是:如果电脑屌丝抱着gvim学习Vim或vi的话,他们将很难抵御GUI软件上的图形化的按钮与菜单以及允许鼠标点选的诱惑,这样一来,对于Vim和vi的学习终将沦为虎头蛇尾一般的无用功 —— 这是屌丝们极易滑去的境地!
请记住:使用游泳圈,是不可能学会游泳的!
所以,为了强迫电脑屌丝们固守学习纯正黑客技术的阵地,本课程要求学员使用相当纯正的UNIX(而非GNU Linux)环境。在一台完全空白的PC(即x86或x86-64的俗称“台式机”或“笔记本”的电脑)上从无到有地架设这样的UNIX环境,
是必须的!为了满足这样的要求,你应当添置一台新的PC或腾出一台空白的PC(而不是指望使用所谓“双(多)系统”机器),这对于穷屌丝来说,在经济上恐怕是个挑战,但这几乎是本课程对他们唯一的在经济上的可观的要求。说白了,学习纯正的黑客技术,须要砸下本钱!
当你准备好了这样的PC之后,我们开始安装操作系统 —— FreeBSD 9.0 —— 本课程认为:各类BSD都是UNIX,对此有不同意见的,请搁置相关的争论。
首先,我们下载FreeBSD 9.0 RELEASE i386
注3的DVD光盘映像,地址是:
http://ftp.freebsd.org/pub/FreeB ... LEASE-i386-dvd1.iso
或
ftp://ftp.freebsd.org/pub/FreeBS ... LEASE-i386-dvd1.iso
脚注
(3)对于FreeBSD的版本来说,这里的“RELEASE”有“正式发行”的含义,具体地说,它是一个“正式发行”的二进制版本,这里的“二进制”意味着它(的源代码)已经由编译可以直接运行在某个特定硬件架构(有些机构则喜欢称“体系结构”)的计算机上,后缀的“i386”即指明了这种架构。除了“i386”架构之外,FreeBSD 9.0 还提供了可以运行在Alpha、AMD64、IA64、PC98、PowerPC、Sparc64六种硬件架构上的二进制发行版。—— 这里有必要提醒一下:有些不负责任的教师声称“amd64是针对AMD公司的CPU产品的”或“如果你的CPU是AMD的,就选amd64”,这属于信口胡说,是极端错误的。事实上,“amd64”是Intel x86架构(在FreeBSD的架构列表中由“i386”指代)的一种延伸。由于它最早由AMD公司公布,故得其名,而在Intel方面,则称之作“Intel 64”(不同于Intel Architecture 64即IA-64)。如果为排除“amd64”或“Intel 64”中出现的机构名称,则一般称之为“x86_64”或简称“x64”。从指令集(体系结构的重要范畴之一)看,以下CPU产品均符合上述列表中的“amd64”架构:
Athlon 64,Athlon 64 FX,Athlon 64 X2,Turion 64,Turion 64 X2,Opteron,Sempron,Phenom —— AMD公司的,采用AMD64指令集;
Pentium 4,Pentium D,Pentium Extreme Edition,Celeron D,Xeon,Core 2、Core 2 Quad,Core i3,Core i5,Core i7 —— Intel公司的,采用Intel 64指令集。
再重申一遍:在做类似的针对于硬件架构的选择时,请谨记这两道关系式:
i386 = x86 = x86_32 = IA32
amd64 = x86_64 = x64 = Intel 64 ≠ IA64
与“RELEASE”这样的二进制发行版相对的,就是“SNAPSHOT”,即向用户提供某种“预览”,以令后者可以领略系统(的各分支)发开的动向。
还有两个用来修饰FreeBSD版本的词,是“CURRENT”和“STABLE”。前者指代FreeBSD 开发的 “最前沿”,对用户的技术要求比较高。而对于后者,不要被它的字面上的意思所迷惑,实际上,这个词专门指代FreeBSD 开发的“分支”,缀以这个名字的版本,是专门提供给那些希望参与FreeBSD开发的用户使用的,普通用户千万不要望文生义地选择了它。
将下载好的ISO文件刻录成DVD光盘 —— 屌丝们应该擅长操持此事 —— 将光盘推入“空白电脑”的DVD驱动器,随即(重)启动。
不要按任何键,直至出现有Daemon(FreeBSD的吉祥物)圆头的“Welcome to FreeBSD”界面(单色、字符的)。
按下[ENTER]键。
直到进入蓝底色的“FreeBSD Installer”文字界面。
在“Welcome”对话框中,选择
。
在“Keymap Selection”对话框中,选择。
在“Set Hostname”界面中,输入一个Hostname(主机名),之后,按下[ENTER]键。
在“Distribution Select”界面中,用方向键与空白键选中全部四个项目(doc、games、ports、src),之后, 按下[ENTER]键。
在“Partitioning”对话框中,选择“Manual”,进入分区程序。
现在,我们要学习有关FreeBSD硬盘分区的一些基础知识。我们以FreeBSD所支持的较新的一种标准 —— GPT(Globally Unique Identifier Partition Table,GUID分区表)来讲解。
当我们进入“Partition(分区) Editor”之后,会看到唯一的一项(如果你只有一个物理硬盘的话):
ada0 或 da0,后面跟着显示的是硬盘的总容量,单位一般是GB。
这里若是ada,就是指SATA或IDE硬盘,而若是da,则指SCSI(发音为[ˈskʌzi])或USB硬盘。
这里的0指第一个。通常,在计算机科学领域,计数用的“自然数”从0开始。那么,若有ada1,则是指你这台电脑上的第二块SATA或IDE硬盘。
我们再来讨论硬盘用量的单位GB这个词。
这个词由大些字母“G”和大写字母“B”组成。
字母“G”表示一个单纯的
数(正如“三个代表”里的“三”),在
计算机学科的度量中,用大写字母表示单纯的数,我们经常遇到的,还有:
K、M、T。
它们分别表示2的10次方(1,024),2的20次方(1,048,576),2的40次方(1,099,511,627,776)。而G则表示2的30次方(1,073,741,824)。
而大写字母“B”,则是“Byte(s)”的缩写。“Byte”的汉语译词一般为“字节”。一个字节即由2的3次方即8个(几乎一定是连续的)Bit(s)组成。“Bit”的汉语译词一般为“位元”或“位”或“比特”。位元是计算机系统可以存放或传输信息的最小单位:在一个位元中,要么存放一个0,要么存放一个1。“Bit”的缩写形式,一般是小写字母“b”。
但在业界,衡量硬盘之类的存储设备,通常所用到的K、M、G、T则分别指代10的3次方(1,000)、10的6次方(1,000,000)、10的9次方(1,000,000,000)、10的12次方(1,000,000,000,000)。为了与以2作为底数的计数方法下的K、M、G、T区别看来,后者也经常被写作Ki、Mi、Gi、Ti —— 这里的小写字母i代表了“Binary”。
回到“Partition Editor”,这里的硬盘容量若是显示“GB”,其实指的是“GiB”。所以,如果你购置了一块标称容量为1TB的硬盘装在电脑里的话,那么此时可能会显示为900多GB。
接着,在“Partition Editor”界面中,用左右方向键选择来创建一个分区。我们选择“GPT”,后程序告诉我们已经成功为这一块物理硬盘建立了GPT。其实,这一步并不是创建分区,而是创建了一个Partition Scheme,只是因为当前连一个分区也没有,所以选择之后首先要创建一个 Scheme。
这时,在物理硬盘容量的右边,又跟着显示了“GPT”,说明前述操作生效。如果要“反悔”上一步操作,那么就用左右方向键选择。
现在,我们再选择,就可以正式地创建分区了。
在“Add Partition”界面中,我们看到有四项指标,分别是:
Type、Size、Mountpoint、Label。
关于Type,在 GPT的背景下,我们只有三种types可以选择:
freebsd-boot、freebsd-ufs、freebsd-swap。
“boot”是“Bootstraps”(拔靴带)的缩略,因为“Bootstraps”一词有“依靠自己力量”的引申义,所以“Boot”就是指电脑启动的过程 注4 。具体地说,以PC上经典的MBR体系为例,通常的Boot的过程是:
PC通电开机,PC上固有的BIOS(基本输入输出系统)对PC的硬件进行POST(Power On Self Test),之后,BIOS在第一块物理硬盘上寻找它上面的最外磁道(Track)上的第一个扇区(Sector),这个扇区被称为MBR(Master Boot Record,主引导记录)。对于硬盘来说,一个扇区的大小为512 Bytes。所以,MBR的大小恒定为512 Bytes。在这512B的空间中,开头的446B被规定为Bootstrap(有“启动代码”与“启动程序”两种翻译,但有的地方把“Boot Loader”也翻译为“启动程序”,汉语学界的翻译显得十分混乱,所以,最好的办法,还是遵守英语原文用法),随后的64B被规定为Partition Table(分区表),最后的2B为结束标志(被规定为“55AA”)。
脚注
(4)“Boot”除了被翻译为“启动”之外,还普遍地被翻译为“引导”。两词经常混用。
MBR(包括Bootstrap和Partition Table)经由BIOS被加载到内存的0x7c00位置(由于MBR有512B,所以,MBR在内存中的结尾,应该是在0x7c00 + 512B = 0x7dff位置),当确认MBR上最后2B的内容(结束标志,位置显然就应该在0x7dfe - 0x7dff)为55AA之后,位居MBR开头的Bootstrap被运行,运行的目的是,解读Partition Table,由此找到操作系统的内核的所在,或能够加载操作系统内核的程序(Boot Loader,除了被翻译为“启动程序”之外,还有一个不错的翻译叫做“启动管理器”)的所在。
在我小的时候,Boot一般用的是Floppy/Diskette(软盘)而非Hard Disk(硬盘)。将软盘插入软盘驱动器之后,一阵杠唧杠唧,操作系统(当时是DOS)就被加载并启动了。当时,非常憧憬自己可以制作一张那样Boot-able的软盘,即便没有操作系统,但也足以在开机之后玩出一些花样。
现在,当你们完成了这节课的目标 —— Vim Fundamentals之后,在UNIX系统上做出这样一张Boot-able磁盘,就是轻而易举的事情了。—— 最多只有512B的工作量!512B,就是半个KB,在屌丝们的“A片”当中,AV的主角们恐怕连一下动作也做不到。这算又是一个振奋人心的好消息吧,希望大家能够坚持学习!
关于Boot,最后再补充一下,前面提到的Bootstrap,也叫MBC(Master Boot Code)。当我们使用Microsoft Windows操作系统时,Windows系统将MBC写在MBR之内,当MBC被运行后,它将会加载NTLDR程序。而使用GNU/Linux,情况就灵活得许多,可为GNU/Linux服务的Boot Loader程序(比如著名的LILO和GRUB),即可以放在MBR之内,也可以放在MBR之外,即在root(根,请特别注意不要跟boot一词混淆)分区。如果采用后一种方法,就必须经由MBR上的MBC(可以跟GNU/Linux无关)将控制权交给Boot Loader。在x86架构下,
此时Boot Loader只能藉由BIOS提供的“中断调用int 13H ah=8H”来获取Drive Parameters,而BIOS可以提供的关于磁盘Cylinder(柱面)的参数只有10bits,也就是说,只能表示从0到1023(2的10次方-1)这麽1024个数,所以,如果操作系统的内核位于硬盘的第1024个Cylinder之后,操作系统将无法被加载,Boot将归于失败。因此,我们一般在硬盘的最开头,布置一个/boot分区,将操作系统的内核放置其中,就可以确保BIOS可以寻找到它。
而对于FreeBSD,可以配合使用的Boot Loader除了LILO和GRUB之外,还有boot0。后者有两个特点:(1)它是FreeBSD的御定Boot Loader。(2)只有它的规格可以写入MBR,而其他的Boot Loaders都无法被MBR那512B的容量装下。
由于本课程的现阶段,不主张学员们使用双(多)操作系统,所以,FreeBSD提供的一个标准的MBR就足够用了。不过,上述的解说,已经足够为学员藉着LILO或GRUB规置双(多)操作系统打下坚实而清晰的基础了!
下面,我们讲“freebsd-ufs”。这里的“ufs”是指UNIX File System。这是我们的操作系统的“正文”所在。后面的教学内容会详细解说这一部分。
最后是“freebsd-swap”。这里的“swap”是“交换”的意思,代表了交换分区或交换空间(Swap Space)。交换空间非常类似于Microsoft Windows系统上的“虚拟内存”,也就是让操作系统将硬盘空间作为内存(RAM)来使用。一般规划这个空间的经验法则是:Swap的容量必须设置为RAM的容量的至少2倍,且不能少于64MB。
看完了Type这个部分,我们可以看Mountpoint这个部分了。(Size和Label,屌丝们不可能不懂吧。)
Mountpoint,即“挂载点”,这是UNIX-like系统知识中的一个难点和重点。它是许多UNIX-like初学者畏难情绪的渊薮。对于UNIX-like,有太多初学者浅尝辄止于此。它之所以对Microsoft Windows用户(屌丝们)带来这麽大的困扰,是因为它是UNIX-like分区与FHS(Filesystem Hierarchy Standard,文件系统层次结构标准)的结合点,而这两下都是Microsoft Windows用户所极端不适应的。
前面已经介绍了关于分区的基本知识,下面,我们要比较详细地学习一下FHS的基本知识。
屌丝们一定已经非常熟悉Microsoft Windows的文件层次结构了,诸如:
C:\Windows\
C:\Windows\System32\
C:\User\
yourname\Documents\
C:\User\
yourname\Application Data\
C:\Program Files\
C:\Program Files\Common Files\
而UNIX-like系统上的FHS,则要复杂(也更科学)得多。
以FreeBSD为例,分述如下:
Root Filesystem(根文件系统)
概论:Root Filesystem 位于整个层次结构的最顶端。它必须含有可以Boot操作系统的程序(专指Boot Loader)以及相关文件,以及挂载其他文件系统的能力。它的规模必须是精简的(为安全考虑)。应用软件不应该在它上面创建任何内容。它应该包括以下目录:
※ UNIX-like系统中没有Microsoft Windows或DOS下的那种诸如C:、D:、E:之类的盘符观念。
/ 文件系统的根目录,也就是“目录树”的最顶端。
/bin/ 存放着在单个用户和多用户环境下的基本工具,比如系统的基本命令,如ls、cp、cd、mkdir、rm等等。这些命令都具有“应急管理”的用途。
/sbin/ 与/bin/不同的是,这里存放着系统管理的基本工具,如fdisk、mkfs等。
/boot/ 存放着操作系统的内核镜像文件以及Boot Loader。但鉴于x86架构下有“第1024 Cylinder 限制”的问题,所以,在这样的限制下,往往为/boot/目录单独设置分区,有如前述的freebsd-boot类型分区。
/dev/ 设备节点目录。一切设备,如磁盘分区、终端等等,都必须以文件形式在这里存在。
/etc/ 存放着系统启动的配置和脚本。
/etc/defaults/ 存放着系统默认的启动配置和脚本。
/etc/periodic/ 存放着每天、每星期和每月周期性地运行的脚本。
/mnt/ 一般是空目录。系统管理员习惯利用它来挂载临时的文件系统,比如USB硬盘等。
/lib/ 存放着共享链接库和内核模块。
/media/ 类似于/mnt/。
/root/ root用户的Home(家)目录。
请注意它和root文件系统的“root”(根、目录结构的顶层、根目录)完全不是一回事,千万不要混淆!它类似于Microsoft Windows下的C:\User\Administrator\ 。
/srv/ 存放着系统服务专属的数据文件。
/home/ 各用户的Home(家)目录的所在。它类似于Microsoft Windows下的C:\User\ 。
/tmp/ 存放着临时文件,这些文件的生命期不会超过下一次Booting。
/usr/ 存放着大多数用户的应用软件。下面有专门的讲解。
/var/ 多用途日志、 临时或短期存放的, 以及打印假脱机系统文件。
/var/log/ 存放着各种系统记录文件。
/var/lock/ 存放着各种锁定文件。所谓锁定文件,是一种应用程序用以标明自身存在或正在运行的标记,通常都是空文件。FreeBSD本身不带这个目录。
/var/run/ 存放着关于各运行着的软件的状态文件。作为服务而长期运行的程序在启动之后,会将自己的PID(进程ID)写成一个文件,存放于此处。
/proc/ 存放着虚拟文件系统。在这里,系统内核与进程状态,被归档为文本文件。
/opt/ 存放着对于操作系统来说是“额外的”软件及其所需的文件。FreeBSD本身不带这个目录。
/usr/ 目录
/usr/bin/ 存放着各种实用命令和应用软件,与/bin/不同的是,聚集在这里的东东,并非用于“应急管理”。
/usr/sbin/ 存放着系统后台程序、可供用户执行的系统工具,与/usr/bin/之于/bin/类似,聚集在这里的东东,并非用于“应急管理”。
/usr/include/ 主要存放着C和C++程序设计所用的头文件。
/usr/lib/ 类似于/lib/,但只包含各类应用程序的共享链接库,而不包含内核模块。
/usr/libdata/ 存放着各种实用工具的数据文件。它类似于Microsoft Windows下的\Application Data\。
/usr/libexec/ 存放着系统实用程序、后台程序。
/usr/share/ 存放着与硬件架构、操作系统版本无关的数据文件。这与/usr/bin/形成强烈的对比,后者被谓之为“bin”,就有“在特定架构下的二进制可执行代码”的意味。
/usr/local/ 存放着本地执行文件、 库文件等等, 同时也是 FreeBSD ports 安装的默认安装目录。 它的内部布局,大体类似于 /usr/ 中的。唯一被规定的限制在于:/usr/local/share/ 与 /usr/share/ 必须完全等效(通常用符号链接实现,类似于Microsoft Windows的“快捷方式”)。
/usr/src/ 存放着FreeBSD的源代码文件、本地程序的源码文件。
/usr/obj/ 存放着通过联编 /usr/src 而得到的目标文件。
/usr/ports/ 存放着 FreeBSD 的 Ports Collection 。
/usr/X11R6/ 存放着 XFree86软件及其所需的文件,如X11R6的可执行文件、库文件、配置文件等。在大多数UNIX-like上,X Window系统就直接部署在此处,所以许多X的应用软件也默认地如此认为,所以不要擅自变更它的位置。
下面,我们介绍“挂载点”。
在上面,我们反复提到了一个名词 —— File System(文件系统)。不同于Microsoft Windows或DOS观念下的File System其实是“分区类型”
注5的意思,在UNIX-like体系中,File System则是Directory Tree(目录树)的意思。
脚注
(5)Microsoft Windows或DOS支持的分区类型有FAT、FAT32、NTFS等。在UNIX-like阵营,则有Ext2、Ext3、Ext4、XFS、JFS、ReiserFS等等。
提到“目录树”,屌丝们也不会陌生,因为Microsoft Windows上就有这种现象 —— 目录层次如“树干(根)–树枝”状分布。在UNIX-like世界里,也是类似的,只不过,它采用了单目录树的设计。那么,我们所设置的分区,都将以 Mount(挂载)的方法,加入到这单独一棵树上。而各分区与这棵大树的众结点,就是所谓的挂载点。
也就是说,只有分区被挂载到目录树上(的挂载点),它们才可以被访问/存取。而挂载这种方法,甚至可以延伸地作用到不在本地电脑的、网络上的某处文件体系身上。用户若不特别加以关注,是无法判断他/她面前的这个/usr/目录是位于本地电脑上,还是位于远程的某个主机上。对于没有参与或观察挂载操作的用户来说,他/她无法觉察到挂载的具体细节,他/她所面对的,惟有一棵完整而自洽的目录树。就从这一点上来说,UNIX-like的挂载机制,就比Microsoft Windows的类似实现要高明得多 —— 在Microsoft Windows上,必须将远程文件体系(一个卷即逻辑磁盘/分区或一个文件夹)“映射”为“我的电脑”下的一个卷,并须为之配备一个卷符(盘符),且有格格不入的图标,显得十分拖沓与累赘。
正是UNIX-like利用了挂载机制,才使得LiveCD(可以只在光盘/U盘上运行而完全无需硬盘的操作系统)成为可能 —— 即文件系统的根目录并不被本地的物理硬盘所挂载。
综上所述,挂载点成为各分区
以分支目录形式接入目录树的入口。另一方面,若分区没有被挂载,那么,对应的分支目录里面就是空的。此外,要特别注意的是:当UNIX-like系统启动时,首先被挂载的是由 Boot Loader所提供其具体位置的 Root Filesystem,而后系统依据/etc/fstab文件的具体内容,逐项挂载其他的Filesystems。
现在,我们用图表来展现相关的内容。在下面的例子中,我们有两块物理硬盘,第一块是SATA的,被标为ada0,第二块是SCSI的,被标为da0。
我们看到,在上述分区与挂载方案中,除了/(根目录)与Swap Space之外,我们
还为/boot、/home、/tmp、/var、/usr这五个分支目录单独地设置了分区,并将它们挂载到Root Filesystem上 —— 将哪些分支目录设置为独立的分区,并没有任何硬性的强制规定,施行何种规置方案,取决于用户的个人习惯与使用环境的技术要求。
一般说来,为/boot、/home、/tmp、/var、/usr这五个分支目录设置独立的分区,缘由是这样的:
/boot 前面已经说过了,为这个目录设置独立的分区,主要是为了规避x86架构下有“第1024 Cylinder 限制”。
/home 这里存放着属于用户自己的文件。对于一个具有实用的面向多用户的系统(比如为多用户服务的在线服务器)来说,这个目录是最主要的工作空间,也是最耗费硬盘资源的区域,所以,非常有必要为此目录设置独立的分区,甚至为其配备独立的物理硬盘或磁盘阵列。然而,对于那些具有另外特殊用途的系统(比如Router(路由器))来说,这个目录却是几乎是无用的。
/tmp 由于这里存放着临时文件,且其中的数据访问权限是完全开放的,所以就有必要为其设置独立的分区,从而避免某些心怀恶意的使用者往其中充塞大量垃圾数据以至于侵扰系统上其他部分的合法资源。
/var 正如这个目录的名字(Variable)所揭示的,这里存放的数据富于变化性,这些数据包括了诸如Log(日志)之类的文件。与为/tmp设置独立分区的理由类似,一旦自动轮替机制(最新的信息会覆盖掉最旧的信息)失效,独立分区的方案可以避免大量的数据挤占系统上其他部分的合法资源。
/usr 前面我们看到,这个目录的结构与根目录的结构非常类似,可谓是一个“小根目录”,所以它的结构与内容也颇为庞杂,且耗费的硬盘资源也是相当可观的,并且它里面的内容可以是Read-Only(只读不写)的,所以,我们有非常充分的理由,为它设置独立的分区(甚至可以用只读模式来挂载它)。
下面,给出一个分配硬盘资源并设置分区与挂载点的例子,供学员们参考。
还是假设有两块物理硬盘,第一块是SATA的,容量是400GB,第二块是SCSI的,容量是800GB,再假设电脑内存容量是8GB。
/dev/ada0p1 freebsd-boot /boot 512MB(FreeBSD推荐的为64KB的boot设置)
/dev/ada0p2 freebsd-ufs / 40GB
/dev/ada0p3 freebsd-ufs /usr 359.5GB
/dev/da0p1 freebsd-ufs /tmp 16GB
/dev/da0p2 freebsd-ufs /var 200GB
/dev/da0p3 freebsd-ufs /home 560GB(如果系统上用户不多,可以适当缩减此项配额,转而扩大/var所在的分区规模)
/dev/da0p4 freebsd-swap swap 24GB
※ 这里出现的路径“/dev/”表示:分区都是以“/dev/”目录下的文件形式存在于系统之中的 —— 这一点,在“Partition Editor”阶段并不会显明,但以后我们会体会到这一点。
学员可以根据自己的电脑的实际情况以及自己的具体需求,设置分区与挂载点。
在设置过程中,若出现错误,想“反悔”时,可以用回滚。
若想调整、补充已经设置好的内容,可以用。
对于Boot分区(freebsd-boot)与Swap Space(freebsd-swap),挂载点都不必填写。
顺便说一句,其实,UNIX-like系统的分区与挂载点设置,一点都不难,跟屌丝们在Microsoft Windows里配置他们电脑的道理是一样的,比如说,如果屌丝们的电脑上挂有两块硬盘,一块较小一块很大,较小的那块硬盘设两个分区:C盘装系统,D盘安放应用程序(软件);很大的那块硬盘也设两个分区:E盘存放自己的普通文档,而F盘则非常非常大,且目录层次也异常庞杂,用来存放长年日以继夜下载得来并严选珍存的“A片”…… 屌丝们把捣鼓自己Windows下的硬盘资源的那种废寝忘食的劲头,分一些些在学习UNIX-like上,那也足够让自己脱屌了!
当一切确认无误之后,选择,设置将会生效,且硬盘将进入准备格式化阶段。在接下来的“Confirmation”对话框中,程序给出“data will be PERMANENTLY ERASED”(硬盘数据将被永久抹除)的警告,选择进行确认。
只要你对于上述每一步操作都认真仔细地完成(并检查确认)了,那么,接下来,你就可以松一口气了,FreeBSD系统进入自动安装阶段。
这一阶段的安装结束之后,进入设置系统基本信息阶段,此时,画面回到单色的TUI界面,要求你设置root密码 —— root是整个系统的最高级管理员。
接着又进入“Network Configuration(网络配置)”界面,此时,会显示出系统所寻找到的网络适配器(一般就是俗称“网卡”),如果仅有一个适配器,就直接按进入下一步,如果适配器不止一个,则要逐个配置。
接下来出现的对话框,是询问用户是否要为选定的适配器设置IPv4和DHCP,可以直接按让系统自行继续。之后则是IPv6,这个可以暂时忽略选择。
之后,会出现IPv4 DNS的设置,这跟Microsoft Windows中关于网络适配器的TCP/IP设置很类似,一般就使用你的路由器的IP地址,比如就是系统自动获取到的192.168.1.1等。
接下来,是“Select local or UTC clock”的对话框,对于中国学员,可以选继续。之后,选择国家或地区(比如Asia->China或Hong Kong或Taiwan)。
而后,进入“System Configuration”界面,这里需要你选择需要开启的系统服务,一般地,用空格键全部选中,然后按。接着,进入“Dumpdev Configuration”对话框,这里提示该项功能会透过/var目录耗用大量磁盘空间,如果满足这个条件,就可以按继续。
接下来,进入“Add User Accounts”界面,强烈建议你选为系统增加新的用户 —— 在UNIX-like的使用过程中,要尽量避免使用root账户来进行日常的一般性操作,应该代之以普通用户的身份行事。
此时,应该根据提示,逐项输入信息。
最后,进入“Final Configuration”界面,可以直接选定Exit结束整个安装过程,但之后又会有提示“Manual Configuration”即系统可以提供一个Shell界面让用户可以手动配置系统,我们选。
此时,我们就正式进入了最具UNIX-like本色的操作环境 —— Shell!
下面,我们要开始学习关于Shell以及CLI的一些基本知识,并藉着这样的途径,开始进一步(但仍然是初阶地)了解UNIX-like系统。
我们使用电脑,无非是向电脑(系统)表达我们的意图 —— 这种意图的表达,是以Command(命令)方式存在的,而命令则是以Text(文本,即由键盘输入的信息,与利用鼠标进行点击、拖拽等的图形界面相对)形式存在的,然后,我们期待电脑就我们的命令做出相应的反馈。
我们希望向电脑表达的意图,通常是这样的:
I want you(指电脑) to get/do/make/take something in some way.
在所有的这样的意图表述中,“I want you”这个部分是雷同的,所以,就可以省略它。那么,上面的语句就可以表达为:
To get/do/make/take something in some way.
在自然语言的语法看来,“To get/do/make/take”是谓语动词,所以,我们将它统合地概念化为:
V
而“something”是V的Object(对象,宾语),所以,我们将它概念化为:
O
因此, “To get/do/make/take something”就可以概念化为:
V(O)
而最后的“in some way”则是表达了进行V(O)所须要遵从的法则、方法、依据(Argument),所以,我们将它概念化为:
A
那么,“To get/do/make/take something in some way.”就可以概念化为:
V(O,A)
或
V(A,O)
我们将O与A放在V所作用的同一对括号中,暗示着:在V看来,O和A的概念可以彼此交换(混同),也就是说,对于某些场景,在V看来,A也可以被当作O,反之亦然。
举个例子:我们希望系统将当前目录的上一级目录中的文件foo.bar复制到当前目录来,自然语言的表达是:
To copy the file which name is “foo.bar “ and which is in the parent directory from there to the current directory.
按照V(O,A)的概念范式,这里的V是“copy”,O是“the file which name is “foo.bar “ and which is in the parent directory”,A则是“from there to the current directory”。
在UNIX-like命令体系中:
V是“cp”。
O则用“../foo.bar”表达 —— 其中的“../”表示“the parent directory(当前目录的上一级目录)”。
A则用“./”表达 —— 其中的“./”表示“the current directory(当前目录)”。
于是,整个意图用命令表达就是:
cp ../foo.bar ./
我们注意到,原本意图中的“from there to...”这个部分,藉由“将A放置在O后面”这个“语序”被表达了。也就是说,在命令中,概念O在表达时,向V指明了它的行动(具体指copy动作)的源头(Source),而概念A在表达时,向V指明了它的行动的目的地(Destination)。
在里的“源头”不仅是行动对象的所处的位置,更是行动对象本身,类似地,这里的“目的地”也不仅可以是行动的目的位置,也可以是行动的结果本身。所以,我们若将上述命令中的“./”用“./foo.bar”代替,效果是一样的 —— 请记住:效果一样的两种实现,我们(命令的设计者)自然会“更加喜欢”最简单的表达方式。
既然可以在概念A中有“./foo.bar”这样的表达,我们完全可以有这样的表达:
这里,概念A(行动的结果)变成了“./foo.baz”,实际发生的事情就成了:
系统将当前目录的上一级目录中的文件foo.bar复制到当前目录来,并且新的(目的)文件被命名为“foo.baz”。
请注意,如果在这样的cp命令执行之前,当前的目录中已经存有foo.bar或foo.baz文件,那么“cp ../foo.bar ./”或“cp ../foo.bar ./foo.baz”这样的命令,会
豪无警示地覆盖掉目标 —— UNIX-like系统的设计者奉行“多一事不如少一事”的实践哲学,当命令本身合法且被正常完成之后,在命令被执行时与结束时,通常不会有任何附加的信息反馈。显然,这样的哲学,会令那些缺乏经验的用户(尤其是从Microsoft Windows或DOS那里转学而来的屌丝们)吃尽苦头,然而,这些命令的设计者已经提供了相当丰富的Optional(备选项)令用户可以规避这样的风险。
比如,在概念A中加入“-i”表达 —— 其中的“-(Hyphen,连字号)”表示这是一个 备选项,而“i”表示“Interactive(交互模式)”,即当命令发现有可能会覆盖已经存在的目标时,会提醒用户,并且期望用户以“yes”或“no”的键入来确定如何处理当前风险。
这样,类似的命令就成了:
cp -i ../foo.bar ./
请注意,在当前这个FreeBSD系统所提供的cp命令的规范中,作为概念A的“-i”表达只能紧跟在V(即“cp”)之后(当然得与V间隔至少一个空格),所以,如果命令表达为:
cp ../foo.bar ./ -i
则会导致cp命令报告“-i is not a directory”,也就是说,出现在概念A的表达序列的最右端的内容,将被解读为一个目标(或位置),而不是一个选项。
※ 当前这个FreeBSD系统所提供的其他基本命令,情况亦类似。
当然,在某些GNU/Linux系统所提供的cp命令,却允许备选项出现在概念A的表达序列中的任何位置,因为那种cp命令自认为可以将一切“-”开头的表达无歧义地解读为选项,而不是源头或目标(或位置)。
在cp命令的备选项中,还有一个“f”,即“Force(强迫执行)”的意思。
如果当前目录中已经存有foo.bar文件,那么,我们执行命令:
cp -i -f ../fobar ./
“-f ”则会让位于它前面的“-i”失效,也就是说,即便有了“-i”作为警醒用途的选项,“-f”也将“覆盖”掉它。既然“-f”可以覆盖掉前面的“-i”,同样,如果把“-i”放在“-f”之后,那么,“-i”就可以“覆盖掉”前面的“-f”,令“-f”无效:
cp -f -i ../fobar ./
我们发现,在选项序列中,后面的总是会覆盖掉前面的,所以,如果我们这麽做:
cp -f -i -f ../fobar ./
和
cp -i -f -i ../fobar ./
它们终将等效于:
cp -f ../fobar ./
和
cp -i ../fobar ./
此外,选项序列中的各项,可以共用一个连字符,也就是说,我们可以写出这样的“变态的”命令:
cp -fffffiiiiiiffifiifffiiiii ../fobar ./
它最终的效果是:仍然会对可能发生覆盖的情况向用户提供警告。
当然,有些V
本身并不需要任何O或A,比如:
clear
这个命令类似于Microsoft DOS里的“CLS”命令,即清屏。
为了符合V(O,A)的范式(也就是,非要写出个O或A不可的话),我们用概念void来弥补这个空阙,即可以概念化为:
V(
void,
void)
上面这种情况,是V本身的O与A均规定为void,这必须与那种V本身可以让用户省略表达O或A的情况区别来开,比如命令:
ls
这个命令类似于Microsoft DOS里的“DIR”命令,即列出当前目录的内容(从目录这个角度所看到的其中的内容,都是以文件形式存在的,所以,这个命令也就是列出当前目录里的文件)。
执行这个命令之后,系统反馈出的,仅仅是当前目录里的文件的文件名(包括扩展名)—— 并不包括那些以“.(小数点)”开头的文件(即表示当前目录的“.”、表示上一级目录的“..”以及Hidden Files(隐藏文件),统称为dot files)。如果要列出那些dot files,必须显式地加上概念A的表达“-a”:
ls -a
此时,系统的反馈将包括那些dot files。由于对于任何一个目录(甚至是根目录)来说,一定会含有“.”和“..”这两个文件,所以这两项可以在实际应用中经常需要忽略,那么,这时概念A的表达应该为“-A”:
ls -A
UNIX-like系统的实践哲学的确是“多一事不如少一事”,这个命令只反馈出了文件名,关于文件的更加详细的信息(比如大小、更改日期、属性等等),却完全没有给出。(然而,在后面的学习中,我们终将领略到:“多一事不如少一事”实实在在地是一项卓尔不群、富于远见的主张!)
为了让系统反馈出关于文件的更加详细的信息,我们须显式地加上概念A的表达“-l” —— 小写字母“l”是“Long (format)”的意思。我们执行命令:
ls -l
我们将会得到系统的反馈,在反馈中,每一行是形如这样的:
drwxr-xr-x 7 root wheel 1024 Jan 3 2012 boot
我们先看“drwxr-xr-x”这个部分。
这个部分由10个字符组成。居首位的字符是“d”,表示该行所列出的文件是一个Directory(目录)(※ 在UNIX-like的世界里,除了普通文件是文件之外,目录、设备、Socket等等均是文件。),如果是小写字母“l”则表示这是一个Symbolic Link(符号链接,类似与Microsoft Windows下的“快捷方式”)。如果是“c”、“b”、“p”、“s”则分别代表字符特殊设备、模块特殊设备、FIFO、Socket(套接字), 如果该位是“-”(连字符在这里表示“无”)则表示该文件是一个普通文件。
接下来的9个字符,应该分为三组,每一组由3位字符组成:第一组为“rwx”、第二组为 “r-x”、第三组为” r-x”。这三组分别表示了该文件对于
围绕着它的三种角色的权限设置。这三种角色依次为:
Owner —— 该文件的所有者、主人。
Group —— 该文件所属于的群组。
Others —— 其他人。
在每一种角色的权限设置中,都有3位:
第1位:要么是r,表示可以Read(读取),要么是-,表示不可以读取。
第2位:要么是w,表示可以Write(写),要么是-,表示不可以写。(在计算机学科领域,“写”通常就意味着“更改”)。
第3位:要么是x,表示可以Execute(执行),要么是-,表示不可以执行 —— 在UNIX-like,文件是否可以视为“可被执行的”,并不像Microsoft Windows或DOS那样倚靠文件的扩展名(如.exe、.com和.bat)来辨别,而是倚靠这一位上的标志。此外,目录的属性为“x”,并不是说目录如同普通文件那样被运行,而是代表了相关角色可以进到这个目录里头。(※ 如你所猜到的那样,进入一个目录的命令,从外观上看,跟Microsoft DOS的命令是一样的,就是“cd 目标”,意为“Change Directory” ——
不过Microsoft DOS不区分命令或文件名的大小写,而UNIX-like则严格地区分。)
那么,如果某文件的权限属性是这样的:
rwxr-xr—
则表示,该文件:可以被它的主人(通常是它的创建者)读取、改写、执行;可以被它所属于的群组读取、执行,但不能被改写;可以被其他人读取,但不能被改写、执行。—— 在 https://www.cpfn.org/bbs/viewtopic.php?f=74&t=647这个帖子里,这样的权限属性,正是关于《基里巴斯国宪法》的最精简描述。
在FreeBSD系统中,还有一些字母可以出现在权限信息的序列里。
小写字母s —— 如果它出现在Owner组的x位置上(代替了x),则代表,在执行该文件的时候,执行的角色乃是它的Owner;如果它出现在Group组的x位置上(代替了x),则代表,在执行该文件的时候, 执行的角色乃是它所属于的Group。
小写字母t —— 它表示“Sticky(粘滞)
注6”,当它出现在目录文件的Other组的x位置上(代替了x),则代表,该目录下的文件,只能由其Owner来删除或重命名了。
脚注
(6)在早期, 为可执行文件的Sticky位置值,将使得它们在运行结束之后,其Image(镜像,即被CPU执行的代码)仍可以“滞留”在内存中,以避免后来的重复加载,以期提高系统的运行效能。但随着虚拟内存技术的普及,这种“滞留”机制已经无用,所以Sticky位转而被用在了文件系统的安全保障策略上,但“Sticky”这个名称却被Sticky了下来。
随着系统被不断使用,文件的权限属性并非一成不变,只要用户在系统中的地位适当(比如是超级管理员),它们就可以随时被用户所更改。命令是:
chmod
我们举个例子:
chmod go-w,a+x foo.bar
从V(O,A)的范式解读它:V是“chmod”,O是“foo.bar”这个文件,A则是“go-w,a+x” —— A由逗号隔开的两个部分组成:
go-w —— 这表示关于该文件的Group与Other角色将被削减掉对它的w权限。
a+x —— 这表示关于该文件的Anybody(一切角色)都将被增加对它的x权限。
再来一个例子:
chmod go= foo.bar
“go=”表示:关于该文件的Group与Other角色的权限将被置空(=后面是空的),这意味着,除了该文件的Owner之外,任何无法访问/存取它。
接下来的“7 root wheel”,分别表示了:该文件的Number of Links(链接数量,关于链接,今后我们将进一步学习)是7(个)、该文件的Owner(是root,即本系统的超级管理员)、该所属于的Group(是wheel,可以理解为一个管理员组,类似于Microsoft Windows里的Administrator组)。
最后的“1024 Jan 3 2012 boot”,则分别表示了:该文件的大小(作为目录文件,这里反馈的数字并非目录里包含的全部内容的总体积)、该文件(最后一次)被更改的日期、该文件的名称。
我们回到ls命令来 ——
ls foo
在这个命令的表达中,概念O(也可以混同为概念A)是“foo”。
当“foo”是一个非目录的文件时,ls命令将反馈出foo这个文件。
当“foo”是一个目录时,ls命令将反馈出foo这个目录下的文件,而非目录文件本身。—— 这跟Microsoft DOS里的DIR命令有非常大的差异,在Microsoft DOS中,命令“DIR 目录名”将反馈出目录本身的信息,而不会反馈出目录里面的内容。
所以,如果我们使用这样的命令:
ls foo*
倘若我们用Microsoft DOS的“DIR foo*”去理解它的话,就会期望该命令仅仅反馈出当前一级的目录里面一切以“foo”打头的文件或目录。而实际上这个命令反馈出来的是:当前一级的目录里面一切以“foo”打头的非目录文件,以及当前一级的目录里面一切以“foo”打头的目录里面的文件(不包括下一级子目录里面的内容)。
通常,我们在Microsoft DOS中,利用“DIR foo*”这样的命令来判断当前一级的目录里面是否含有以“foo”打头的文件或目录,如果有反馈,则说明那些文件就在当前一级的目录里面。而这样的思路会给UNIX-like的用户带来困扰 —— ls命令反馈出的文件,不一定就是在当前一级的目录里面!
上面的例子中,已经出现了Wildcard(通配符,有的地方也翻译作“百搭字符” —— Wildcard的原意就是“百搭牌张”,通常这种牌张可以充当任何花色和点数的牌张),就是“*(星号)”。
在Microsoft DOS中, 通配符一般只有两个:
* —— 代表零个或任意个合法(规定范围内)的字符。
? ——代表单独一个合法(规定范围内)的字符。
在UNIX-like里面,上述两个通配符的用法跟它们在Microsoft DOS中的用法,基本一致。但在UNIX-like中,还有更多的匹配(若有目标符合通配符所代表的意思,即叫“匹配”)方法:
[
char] ——代表任何一个出现在方括号内字符,比如a[bc]可以匹配ab和ac。此外,还可以用[a-z]来匹配从小写字母a、小写字母z之间以及它们两者之间的任何小写字母,但要匹配全部英文字母的大小写,则应该用[a-zA-Z]而不是[a-Z]。
[!
char] ——代表任何一个
没有出现在方括号内字符,比如a[!bc]可以匹配ax和ay,但不能匹配ab和ac。
{
a,
b,
c,
...} ——代表创建一个a、b、c、……组成的序列,这样,“Foo_{x,y,z}”就可以匹配“Foo_x”、“Foo_y”以及“Foo_z”。
跟Microsoft DOS不同,在UNIX-like中的“ls foo*”这样的命令,负责解释通配符的不是ls命令,而是Shell,具体地说,是Shell将“*”先行解释,即在当前目录中找到所有可以匹配“*”的文件名诸如“foobar”、“foobaz”、“fooXyZ”……,也就是说,命令中出现的通配符,先经由Shell展开为具体的内容,而这些内容作为概念O或概念A的序列,被哺馈给V。 —— UNIX-like这样的设计理念,相对于Microsoft DOS中命令自己负责解释通配符的做法,高明了不知道多少倍!由于在UNIX-like的体系中,Shell程序与命令程序分开,这样一些对于命令程序的设计来说是雷同而重复的工作(比如实现解释通配符)就可以完全抽离、集约到Shell程序的设计中,那么,命令程序的设计工作将被大大的简化,类似于数学解题中的“合并同类项”。
※ 确切地说,Shell仅仅在它认为通配符所在的序列是文件名的时候,才会做这样的解释、展开工作。
说到这里,我们才算是可以真正开始了解Shell这个至关重要的角色,及其特性。上面说到,解释通配符的工作是由Shell完成的,这意味着,不同的Shell在处理这类问题的细节上可能会有不同。这样就又揭示了:有多种Shell可供UNIX-like用户选择。
在我们刚刚安装好的这个FreeBSD中,系统默认的Shell程序是csh的改进版本 —— csh全称是C Shell,它是由美国计算机科学家William Nelson Joy开发的。“William”这个名字在英语国家中一般会被昵称作“Bill”(
比如有William Jefferson Clinton昵称为Bill Clinton,William Henry Gates 三世昵称为Bill Gates),所以,对于这位科学家,我们一般称呼他作Bill Joy,他是一位世界顶级大黑客。
上图:Bill Joy。
如何察看当前所使用的Shell是哪个版本的呢?可以使用命令:
echo $SHELL
它将反馈:
/bin/csh
实际上,目前FreeBSD上使用的C Shell是前者的一个改进版本,即Tenex C shell,简称tcsh。我们使用命令:
diff /bin/csh /bin/tcsh
没有任何反馈结果,这说明两者完全一致。(这再次验证了:UNIX-like系统真是懒到多说一句就会累死的地步!)
刚才,我们还学习了一个新的命令 —— “echo $SHELL”。
“echo”的意思,跟Microsoft DOS里的“ECHO”命令的,是一样的,就是“回声”。这个名字原本属于希腊神话中一位善良美丽的女神。这位女神在遭遇了某种残酷的折磨之后,对任何人的问话都不予回答,只是戚戚然地重复别人的话语,所以她的名字被引申为“回声”的意思(更准确地说,是重复别人所说的话)。
当我们使用命令:
echo 123
程序就回馈给我们:
123
甚至我们使用命令:
echo 1+2
“她”也回馈我们:
1+2
—— “她”无法解释任何在我们看来有意义的表达式。
所以,我们若使用命令:
echo SHELL
就会原原本本地得到:
SHELL
这个字符串。
但我们若在“SHELL”前面顶上了“$”(Dollar,美元符),结果就全然不同了。这当然不是因为Echo女神“见钱眼开”,而是因为:Shell程序将“$SHELL”这个字符串展开为“/bin/csh”这个字符串,然后将展开后的结果哺馈给echo命令程序。请记住,Echo女神永远只会单纯地重复任何说给她听的东西,永远!(哭!)
这里,就可以延伸出一个技巧,我们可以藉着echo程序,窥看到Shell是将那些特殊字符(通配符、转义序列等等)如何地展开的。比如:
echo Foo_{A,b,C,d}
反馈的结果将是:
Foo_A Foo_b Foo_C Foo_d
我们再一次认识到了Shell程序解释命令中所出现的特殊字符的能力。那么,这次它解释“$”的具体过程是怎样的呢?
当Shell发现输入的命令中含有“$”这个特殊字符,它会将紧跟在这个符号后面一个单词视为a Name of Variable(变量的名称) ,这里所谓的“一个单词”,就是由合法(规定范围内)的字符组成的且以空格(或其他特殊字符)与其他“单词”相隔的字符串。正如在编程的环境中,我们可以用一个Identifier(标识符)来作为变量的名称(并利用这个标识符来指代变量的实际的值)一样,在这里,我们也可以用“一个单词”来作为变量的名称,但要让Shell根据这个名称引出它所指代的实际的值的时候,必须在这个名称前顶上“$”。
请注意,“$”本身也是一个变量的名字,这个变量的实际的值是当前Process(进程)的ID号(简称PID)。如果当前的Shell的PID是1218,那么,使用命令:
echo $$
就会反馈:
1218
如果直接输入命令:
$$
则会反馈:
1218: Command not found.
这说明,命令“$$”被Shell展开为“1218”,然后系统认为你输入了“1218”这个命令,结果显然就是报错。
在UNIX-like系统中,有一组通常在用户干预之前,就由系统自行设置好的变量,它们被统称为Environment Variable(环境变量) —— 这些变量反应了或决定了我们在系统中所处的使用环境。
我们使用命令:
env
就可以让系统反馈这些环境变量。反馈内容的格式是,每一行中:
变量的名称=变量的实际的值
我们可以透过这些环境变量的名称,了解各个变量的含义,比如说:
HOME=/root
这一行揭示了,当前用户的Home(家)目录是/root。当你用使用这样的命令:
cd ~
特殊字符“~(Tilde,波浪号)”就会被Shell解释为“$HOME”,进而被展开为“/root”,再将“/root”哺馈给cd命令。
我们使用命令:
pwd
即可以让系统反馈当前的工作目录名(the name of Working Directory)。也就是说,在“cd ~”命令之后,“pwd”将反馈给我们“/root”这样的结果。
还有一个环境变量,名字叫做“OSTYPE”,该变量被系统自行设定的值是“FreeBSD”。所以,我们可以使用这样的命令:
echo I love $OSTYPE!
反馈的结果,自然就是:
I love FreeBSD!
※ 命令中的“!(感叹号)”并没有与 字符串“OSTYPE”黏合而产生一个叫做“OSTYPE!”的变量名,因为Shell知道“!”不可能作为变量名的一部分。
在众多环境变量当中,还有一个环境变量,对Shell的工作方式具有直接的、举足轻重的影响作用,那就是名为“PATH”的变量。这个变量的名字的含义是“Search Path(搜索路径)”。这个变量的实际的值,是由一个以“:(Colon,冒号)”黏结的若干个目录名所组成的序列。UNIX-like中的PATH变量的含义,与Microsoft Windows或DOS中的PATH环境变量的,基本上是一致的(但后者的PATH变量值是由“;(Semicolon,分号)”黏结起若干个路径)。
我们已经知道,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将单独的“:”与之前的字符串相黏合,从而规避将其解读为一个命令。
你是不是觉得挺复杂的,感觉有点头昏了。我们暂且休息一下,不过在休息之前,我们再学习几个有用的命令:
mkdir
—— 创建目录。
rm
—— 删除概念O所对应的内容(可以是文件也可以是目录(文件的位置)),如果要删除一个目录(及其中包含的一切内容),则需要一种概念A的表达:“-r”或“-R”,这个字母这表示“Recursive(递归的)”。在许多命令中,都可以利用递归选项遍历子目录中的一切内容,比如在chmod命令中,就可以加上“-R”选项,实现子目录中的一切内容的属性更改。
mv
—— 这个命令的字面意思是“Move(移动)”。除了提供了类似于Microsoft Windows或DOS下的“移动目标”的功能之外,UNIX-like系统的“移动”命令,还包括了Rename(重命名)功能。其实,重命名是移动的一种,仔细想想看,你应该可以明白过来的。
※单纯的cp、rm、mv命令(这三个命令属于日常操作中最经常用到的、最基本的命令)会不给予用户任何警告地覆盖掉原有的内容或删除掉一切它认为符合要求的内容,所以,请在必要时,一定加上选项“-i”!切记!
shutdown
—— 选项“-r”表示“Reset(自动地重新启动电脑)”,“-h”则表示“Halt(关机)”(某些情况下,在系统Halted之后,你可以关闭电源,也可以按任意键重新启动电脑),这些选项后面须跟上一个子选项,即时间参数,如果是:
shutdown -r now
则是要求系统立即重新启动。
好吧,系统安装完毕后给我们提供的Shell,被我们捣鼓了这麽久(或者说,我们被它折磨了这麽久),是可以重新启动电脑的时候了,我们借此也可以稍事休息一下,待会儿再回来!
重新启动电脑之后,再次出现FreeBSD Daemon圆头的单色启动界面,此时按,直接进入系统。其间,要输入登录用的用户名(在“Login as:”提示后输入)与密码。之后,进入Shell环境。
之前,我们已经了解到:命令中的通配符将被Shell先行解释,也就是说,Shell将诸如“*”这样的通配符,在指定的环境(比如当前一级目录)中寻找到可以匹配的项目,然后将所有匹配的项目展开,哺馈给命令程序。
所以,当我们使用这样的命令:
ls foo*
Shell将首先找到当前一级目录下所有可以与“foo*”匹配的项目(包括文件与目录),然后将它们以展开的形式作为概念O(或概念A)哺馈给命令程序ls。假设当前一级目录中,仅有这样的普通文件:
foo0、foo1、foo2、foo3、……、foo9998、foo9999、bar0、bar1、bar2、bar3、……、bar9998、bar9999
共计20,000个,那么,实际被ls命令程序所执行的命令行是这样的:
ls foo0 foo1 foo2 foo3 …… foo9998 foo9999
这样就引入一个问题,Shell容许有一个命令行
这么长吗?这个长达一万项的概念O的表达,恐怕已经超过了Shell可以接纳长度的极限(或者也已经超过了命令程序可以接纳参数的极限)!然而,UNIX-like对于此类问题的处理机制,是极端高明的。我们试想,如果先让ls命令程序列出当前一级目录下的所有文件,但是,这个输出并不反馈到屏幕上,而是作为另一个命令程序的概念O,也就是说,ls命令程序的输出结果被哺馈给了另一个命令程序,然后,再让这后一个命令程序“分拣”出可以匹配的项目,那不是很棒吗,而且很合理吗?于是,我们就可以使用这样的命令:
ls | grep "^foo.*"
在这个命令中,出现了不少令我们感到陌生的新东东。
首先,我们看一个新东东—— “|(Pipe,管道符,在我的电脑上,这个字符由[Shift]+[\]键入)”。管道,是UNIX-like系统最具威力的一个机制。为了能明白和领略这个机制的强大,我们要从I/O(输入/输出)的概念讲起。
在诸位屌丝接受计算机启蒙教育的时候,一定都了解到了,诸如键盘、鼠标、轨迹球、压感笔、扫描仪、摄像头这样的设备,都叫做“输入设备”;而诸如屏幕、打印机这样的设备,都叫做“输出设备”。UNIX-like除了有“多一事不如少一事”这个颇具远见的哲学之外,还有另一个绝对高屋建瓴、所向披靡的设计哲学 —— “一切皆是文件”,亦即,一切的设备,包括磁盘驱动器、磁盘分区、终端机(Terminal)、串行端口、并行端口、USB端口、打印机等等等等,以及负责收发网络数据封包的Socket,无一例外地均被视作为File(文件)。(※ 对于设备文件,都存放在/dev/目录中,这些文件也叫做“Device Nodes(设备节点)”。)
举个具体的例子,当我们利用Shell与系统进行交互式操作的时候,其实,在我们与Shell之间,还存在一个终端(即我们面前的这个屏幕,或仿真终端,即我们在GUI环境中所适用的呈现出终端样式的窗口程序)。显然,不论是真实的终端,还是仿真的终端,在UNIX-like看来,皆是文件。
我们使用命令:
w
可以观察到当前整个系统上,有哪几位用户登录,他们分别从那个终端接入的。比如,反馈是这样的:
USER TTY FROM LOGIN@ IDLE WHAT
root v0 - 3:31PM 2 -csh (csh)
USER一栏给出了用户名;TTY一栏给出了该用户所接驳的终端名称;FROM一栏给出了该用户的登录地点(一般远程登录者以网络地址如IP地址形式给出);LOGIN@一栏给出了该用户的(最近一次)登录时刻;IDLE一栏给出了该用户的“发呆时间”(即从该用户最后一次敲键盘至当前时刻所间隔的分钟数);WHAT一栏给出了该用户当前的“正在干什么”(即当前的进程名及其参数)。
我们可以看到,上面的TTY一栏,表明了我这个root用户接驳的终端名是v0。既然有终端v0,也就应该有终端v1、v2、v3、…… 的确如此,我们在/dev/目录中,可以看到那些以“tty”开头的文件,那些就是以文件形式存在的终端,这些文件也叫做“终端节点”。我们使用命令:
ls -l /dev/tty*
就可以看到全部tty开头的终端节点的详情,这些节点文件的属性都是c开头的,表示它们都是所谓的“字符(Character)型设备”。
一般说来,当今的UNIX-like系统,为用户面前的实体电脑自动配置了8个TTY终端。这8个TTY终端,可以由用户按下[Ctrl]+[Alt]+[F1]到[F8]切换。每个终端,可以登入不同的用户,也可以登入相同的用户。通俗地说,屌丝们可以认为:一台UNIX-like实体机器,至少可以当8台机器(由你自己扮演的8人次)同时使用。
TTY是什么?UNIX-like世界中总能经常见到这个词。TTY是TeleType的缩写。
注7
脚注
(7)早期的计算机系统终端,都采用上图这种Teletype设备。Teletype跟我们(比如在我上 中学时候的)经常接触的那种英文打字机不同的是:打字机的打字头的位置是固定不动的,每敲一次按键之后,负责夹纸、走纸的滚筒就往左边移动一个字符的长 度;而Teletype的打字头(Carriage,即上图中有长得像扇环形百叶窗的金属东西并绑着色带的那个部分)是可以在字符水平方向上移动的,而负 责夹纸、走纸的滚筒在这个方向上却是固定的。
在Teletype上:
如果要“回车换行” —— (A)负责实现CR的指令让Carriage回到该行的初始位置(最左端),是为“回车”(\r);然后,(B)负责实现LF的指令,让滚筒将纸张往上走一行,是为“馈行”,也叫“换行”(\n)。
如果要“换行回车” —— 将上面的(A)、(B)步骤次序对调。
既然终端是一个文件,那么,如果我们把某些信息写到这样的文件中去的话,屏幕岂不是会显示出信息来?的确如此。我们使用命令:
ls > /dev/ttyv3
就会把ls命令的反馈结果输出到v3这个终端屏幕上去。我们按下[Ctrl]+[Alt]+[F4](v3终端是第四个终端),看看,是不是这样?
上面命令中出现的“>(大于号)”,叫做“I/O重定向符”,意思是,将左边命令产生的结果,输出到右边的
文件中去 ——左边命令产生的结果原本是要输出到终端屏幕上的,但现在没有输出到屏幕,转而输出到了某个指定的文件,故此得名“重定向”。※作为重定向符的时候,“>”代表“
覆盖性写入”;“>>”代表“追加性写入”;“<”代表“读取”。
现在我们回到之前的例子,了解一下比重定向符更难理解(只是更难一点点儿)的管道符。
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/ttyv
x文件,因此,该流以文件句柄的形式存在),当然你也可以使用前述的“重定向”方法将改流转向输出其他文件(犹如黄河改道,从江苏省流向黄海)。
(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/ttyv
x文件)上,而是
经由管道哺馈给(流向)了管道符后面的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的(更简单、更强大)。
—— 只要八个汉字:
转义、数量、位置、逻辑
(0)正则表达式的目的,在于用它自己寻找到目标文本中符合某种特征的字符串,这种“寻找”的过程,也叫“扫描”,一旦寻找到,则就叫“匹配”。这跟之前介绍的“通配符”很相似,但正则表达式机制远比后者更复杂、更强大。正则表达式犹如一个模板一样,去圈套一切符合它样式的内容,所以,它也叫Pattern(模式、模板)。
(1)转义 —— 用元字符指代一类具有特性的字符的机制。一般通过中括号、小数点、反斜杠这些特殊符号(就是所谓的Meta Character,元字符)的序列来实现。
中括号 —— 类似于之前介绍的通配符中的。比如:模板“[a-z]”可以匹配26个小写英文字母中的任何单一字符;模板“[^A-D]”可以匹配大写英文字母A-D之外的任何单一字符。
小数点 —— 除了“换行”(\n)之外的任何单一字符。比如:模板“^.{5}$”可以匹配任何正好有5个字符的行。
反斜杠序 —— 这是典型的转义序列:序列“\w”等效于“[a-zA-Z0-9_]”;序列“\W”则等效于“[^a-zA-Z0-9_]”。
(2)数量 —— 用来指定被匹配的字符(串)的数量。一般通过花括号、星号、加号和问号这些元字符来实现:
花括号—— 描述字符出现多少次将会被匹配。{m,n}表示m次或n次或两者之间的任意次数;{m,}表示大于等于m的次数;{,n}表示小于等于n的次数;{m}表示有且仅有m次。比如:模板“s{2,3}”可以匹配字符串“ss”、“sss”。
星号 —— 意同{0,},即0次或任意正次数。比如:模板“As*”可以匹配字符串“A”、“As”、“Ass”。
加号 —— 意同{1,},即1次或其他任意正次数。比如:模板“As+” 可以匹配字符串“As”、“Ass”但不能匹配“A”。
问号 —— 要么有1次,要么有0次。比如:模板“as?\>” 可以匹配文本“a man as an ass”中的字符串“a”、“as”,但不能匹配“ass”或“an”。
※ 所有表示数量的元字符,作用且仅作用在紧挨在它左边的第一个(或第一个分组)字符的身上,比如:“foo.*”里的“*”仅仅作用在“.”身上,而不是“foo.”身上。
(3)位置 —— 用来匹配到指定位置的字符串。一般通过以下两对元字符来实现:
补脱符(^,Caret,[Shift]+[6]) —— 表示BOL(Begin of Line),比如:模板“^Hello”只能匹配位于行首的字符串“Hello”,在非行首出现的字符串“Hello”将
不能被匹配。美元符 —— 与补脱符相反,表示EOL(End of Line)。
\<(反斜杠加一个小于号)——描述了这么一种位置:若有一个字符串处于它左边的空白或标点之右,该字符串将被“\<字符串”这样的模板匹配。比如:模板“\不能被匹配。而“\>”的意义正相反,比如:模板“Windows\>” 将匹配“Windows 98”中的字符串“Windows”,但“Windows98”中的字符串“Windows”却
不能被匹配。
其实,关于“位置”的模板表达,有一种非常易于识记的方法:将一行的行首、行尾这些“看不见字符”的东东,以及用于分割单词的某种空间,想象为可以某种“摸得着”的特殊字符,那么,这些特殊字符就是“^”、“$”、“\<”、“\>”,把那些原本难以表达的概念,转化为实体字符,然后将这种字符,安插在它们的对应位置,就没错了!
(4)逻辑。主要内容只有两个:
小括号 —— 用来分组,即将被它括起来的字符串视为一个整体。
管道符 —— 两项被管道符(|)黏结的字符串组成的模板,表示要么用左边一项字符串来匹配,要么用右边一项字符串来匹配。
比如:可以用模板“^[0-9]{6}(1970|1972)[0-9]{8}$”匹配文本中所有其出生年份信息是1970年或1972年的身份证号码行,当然可以可以模板“^[0-9]{6}197[02][0-9]{8}$”。
在grep命令行中,如果模板中的某些字符(比如小于号、大于号等)会引起Shell出现我们所不期望的解读(发生了歧义),我们就应该用双引号将Pattern括起来。然而,在csh中,用双引号括起某些特殊符号(比如美元符)反而又会引起Shell的解读问题,倒是用单引号却不会有问题。所以,当我们怀疑Shell误解了我们向grep程序提供的Pattern的时候,我们应当用其他的方案反复测试与验证,以确认是否发生了“歧义”。
用起csh来,是不是让人觉得有点闹心?或许你的感觉正是如此。那么,我向你强烈推荐另一个Shell —— Z Shell,简称zsh,它是由大黑客Paul Falstad在学生时代(于普林斯顿大学)创建的。※ zsh的许可协议属于MIT-like(类麻省理工学院的)。
我们面前的这个FreeBSD上,有zsh吗?我们使用命令:
whereis zsh
得到的反馈是:
zsh: /usr/ports/shells/zsh
※ 一个具有专门搜索文件的命令是“locate”,该程序将搜索数据库中符合关键词的文件,不过这个数据库是需要适时更新的,在FreeBSD上,这个更新的命令是“/usr/libexec/locate.updatedb”。
再对比一下命令:
whereis tcsh
其反馈结果则是:
tcsh: /bin/tcsh /usr/share/man/man1/tcsh.1.gz
这至少可以说明,tcsh已经是一个被安装在系统上的可执行程序,但zsh不是,关于zsh的位置只有“/usr/ports/shells/zsh”这么一项信息。
之前介绍过,“/usr/ports/”目录是Ports Collection的位置。Ports Collection是什么?Ports又是什么?
我们必须了解,在为UNIX-like系统上安装软件的某种“经典”过程:
(1)下载软件的源代码,通常是一个扩展名为.tar.gz的文件(原始文件被tar程序打包再由gzip程序压缩得到),解开它的命令通常是:
tar zxvf foo.tar.gz
(※ 这里的参数z代表关于gZip的压缩或解压缩,x代表解包,v代表滔滔不绝地说话意即信息馈出,f代表后面隔着空格紧跟的是解压缩的对象或压缩的目的文件。各参数前的连字符可以省略,且可以黏合在一块儿。)
通常会得到一个被释放出来的目录。进入目录,可以看到一堆文件。
接下来是经典三部曲:
./configure
—— 根据当前的架构特征,配置和生成Makefile文件。
make
—— 根据当前目录中的Makefile,开始将源代码编译成可执行程序。
make install
—— 将程序安装到系统文件层次结构中的特定位置。
※ 卫生习惯好的用户,还会:
make clean
—— 清除上述过程中的中间件,为今后可能再次进行的编译工作提供一个干净的环境。
看到这里,你可能会想,如果这几样步骤(包括下载源代码、解压缩、配置编译、编译、安装等等)能够一次性自动运行,那该多好呀!
FreeBSD的Ports机制,就是为了满足这样的要求而生的。一个特定软件的port就是可以自动实现上述步骤的文件的集合。而Ports Collection就是这样的集合的集合。我们进入“/usr/ports/”目录,可以看到这种集合内容的排布,这些内容分门别类、层次分明、井然有序,比如“/usr/ports/shells/zsh”就告诉我们,zsh软件的port正位于“shells”这个分类当中。
现在,我们进入“/usr/ports/shells/zsh”目录,使用命令:
make install clean
(※ Port的Makefile文件提供了足够的自动化机能,使得这行命令可以将make、make install、 make clean这些工作“一站式”完成。)
接着,一个蓝底安装界面出现,我们尽可以选上全部项目,然后按。
经过“漫长”的下载、配置、编译、安装(其间还会有分支安装的交互式界面,所以最好别走开)之后,整个安装程序以这么一行结束
===> Cleaning for zsh-4.3.14
这标志着zsh程序已经完全安装完毕。
此时,我们需要使用命令:
rehash
来刷新Shell缓存(这并非是启用新的Shell。在任何安装过程结束后,都必须立即执行这个命令),令我们可以执行新近被安装好的程序。
此后,我们再使用命令:
whereis zsh
就会发现zsh已经安装在特定的目录中了(/usr/local/bin/zsh),而且还多了一项:
/usr/local/man/man1/zsh.1.gz
这说明,我们可以使用命令:
man 1 zsh
来查看zsh的man文档(Manual,手册,使用说明书)。这里的“1”代表,我们将这里的“zsh”视为“命令程序”。“1”是“程序命令”在man文档里的章编号。这种章编号可以反映出一个专有术语的属性:
1 命令程序。
2 系统调用。
3 C库函数。
4 设备的驱动程序。
5 文件格式。
6 游戏或娱乐项目。
7 杂项资讯。
8 系统维护。
9 内核开发情况。
比如,我们可以分别用:
man 1 printf
和
man 3 printf
来查看命令程序printf与C库函数printf(两者同名却是不同的东东)的手册文档。
查看man文档的方法与使用less程序查看一般文档的方法是类似的。
现在,我们要将当前用户的Shell(即tsch)更新为zsh。我们使用Change Shell命令:
chsh
此时,一个编辑器打开了,我们需要修改其中内容的第10行。
其实,chsh命令只是一个将某个编辑器打开某个文档的操作“打包”起来。而这时chsh用到的编辑器正是vi!
哇,终于讲到本节课的正题了!好,我们快快使用vi来更新文件内容吧!
鼠标指针当然是没有的,所以,我们只能用方向键[↓]将光标移动到第10行,再用方向键[→]将光标移动到“/bin/csh”中的字符“b”位置(因为第一个字符“/”我们并不需要修改),然后用[Delete]键将“bin/csh”这些字符逐个消灭掉(只能用[Delete]键,若使用[Backspace]键则无效,在有些虚拟终端中,[Backspace]键会被解释为“^?”,这会让vi报错。) ……当我们消灭掉最后一个字符“h”时,光标向后退了一个字符,真是觉得有点怪怪的 ……让用惯了GUI软件的屌丝们更加感到意外的事情发生了 —— 当我们准备输入zsh程序的具体位置(应该是准备输入“usr/local/bin/zsh”)的时候,按下路径的第一个字符“u”时,居然在光标的位置上出现了一个莫名其妙的字符“h”!……好吧,你可以就此打住了,因为你完全不了解vi应该如何使用!
vi的使用,是Microsoft Windows或DOS用户转学UNIX-like系统的重大难点之一,更是让用惯了GUI软件的屌丝们极感手足无措的一件事情。没有一本UNIX-like系统基础教材会回避教授关于vi或Vim的使用方法,因为它们是UNIX-like系统必备的标准编辑器。但是,正如在讲授正则表达式这个难点时表现得非常无力一样,许多教材对于vi或Vim的讲授,也是格外地给学生添堵,这恐怕也是因为那些教材的编写者自己根本就不熟悉vi或Vim,更不要说以深刻的见解为学生解惑了。
对vi或Vim的熟悉程度,基本上与用户使用UNIX-like的深刻程度成正比。现在我教授大家如何使用vi,其中的思路,是在我多年来所累计的技术经验的一种反映,比如说:许多教材(尤其是国内的)都宣称vi或Vim有三种模式(命令模式、末行模式、插入模式),但我的技术经验给予我的强大观念是:vi或Vim只有两种模式,所谓“末行模式”其实也是命令模式。下面的解说,将完全按照这样的思路进行。
当面前的这个vi程序使我们感到混乱的时候,请不要试图继续录入或编辑操作,而是应该立即按下键盘最左上角的[Esc]键(该键的意思是“Escape”,即逃生、逃逸 —— 一旦你在vi中迷失方向,就应该使用这样的逃生方法,它是最最重要的安全通道),多按它几下也无妨。在任何状况下,按下[Esc]都将把vi拉回到“命令模式”
注8。
脚注
(8)基本上只有这么一种例外:在vi中按下[Esc]键会执行“:”开头的命令,所以在已经输入“:q”这类命令(但还没有按[Enter]键)的情况下,按下[Esc]键会让vi退出。
将vi拉回到命令模式之后,输入命令:
:q! √
※ 上面的“√”表示须按下[Enter]键,这种提示对于学习vi或Vim非常重要,所以就不再忽略。
结果是我们退出了vi程序。“q”的意思是“Quit”。在vi的命令中,感叹号“!”的意思是忽略一切诸如“内容已经改动但还未保存”、“文件将被覆盖”之类的警告,直到你的意图被执着地完成。使用命令“:q!”,使得在上一次文件被更改之后的任何对文件Buffer(文件内容在内存中的一个副本,即你在vi程序所直观的文本内容)的更改都不会写入到文件中,且vi程序直接被退出 —— 利用这个命令,你尽可以找来各种又大又复杂的文件来演练vi的使用。
我们退出vi之后,重新使用命令:
chsh
进入vi。面对这个文件,我们最好能让vi显示行号。我们现在已经命令模式下了,那么,输入命令:
:set number√
此时,我们可以比较直观地看到:“Shell: /bin/csh”在第10行。那么,我们要将光标移动到第10行,我们使用命令(如果你码不准是否处于命令模式,请多按[Esc]键,此时你会听到“Beep-Beep”或“Ding-Ding”声响。):
10G
这次没有“√”,表示你不需要按下[Enter]。凡是不以“:”打头的命令,都不需要再命令结尾键入[Enter],除此之外,这时你甚至都看不到你到底输入了什么!确实,除了“:”打头的命令之外,vi对于你输入的其他一切命令,都不会显示 —— 这的确是屌丝们难以驾驭vi的原因之一。
“10G”这个命令代表了“Goto the line #10”的意思,你或许会想,为什么不是“G10”呢?再仔细想想,如果按照这样的思路:当你输入了“G10”之后,只有你按下[Enter]键vi才知道你的意图是“G10”,如果没有按下[Enter]键而是继续输入一个“0”,那么再按下[Enter]键vi才知道你的意图是“G100”,也就是说,再你按下[Enter]键之前,vi无法确定你的意图是什么。但vi的设计哲学是:最大限度地节省你的键入量,包括削减一切可以削减的[Enter]键入,这样,就必须要求有一个键入的字符触发了vi
立即完整地领会你的意图 —— 命令中字符“G”的键入就是如此,它一定是命令的结尾(在字符G之后不应该有任何参数附赘)。所以,“Goto(转到……行)”的命令格式必然是:
nG
n不能等于0,因为没有第0行(vi从1开始为行计数),事实上,“0”是一个命令,这意味着当你键入“0”的时候,命令的输入就终结了,所以,也不可能有“0G”这样的命令。命令“0”是将光标拉回到当前文本行首个字符的位置。(※ 命令“^”则是将光标拉回到当前文本行首个非空格字符的位置;命令“$”是将光标拉回到当前文本行的最末一个字符的位置。)一个无参数的命令“G”,会将光标拉到文件的末行。
现在,光标被拉到了第10行的起头,现在我们要按键将光标逼近“bin/csh”位置,此时,你可以用方向键,但我强烈建议你使用命令:命令“l”将光标右移一个字符,“h”则是左移一个字符,“k”则是上移一行(是文本的行,即逻辑行,而非屏幕直观的一行),“j” 则是下移一行 —— 键盘上的连续排列小写字母hjkl分别对应了方向
←↓↑→(容易记错为“
←↑↓→”,因为我们可能会习惯于有next意味的↓和→紧挨在一起);命令“L”与“H”分别会将光标拉到当前页面的顶部或底部。
当我们将光标移动到“bin/csh”的第一个字符时,我们的确可以使用[Delete]键来删除当前字符,但我建议你不要使用这类非字符按键,而是使用字符按键所输入的命令,删除光标当前字符的命令是“x”。如果你持续使用命令“x”的话,你会发现,它最终将消灭掉当前行上的全部字符,但一个空行将被保留,不会被消灭。如果你希望彻底消灭这一行,你应该使用命令“dd”。
不过,我仍然不建议你在这里使用命令“x”,因为一旦你不留神地多按下几次,那些你不想更改的文本内容也可能被破坏掉,而且重复地输入同一个命令,实在不符合UNIX的Style。我们必须认定一种更为高效的思路:将“bin/csh”中的字符一次Cut掉(没错,是剪切而不是删除,因为剪切可以保存被剪切的内容以待将来粘贴,而如果你以后不想粘贴的话,就当是删除它们了,这很符合UNIX把雷同的事情合并在一种方法里的设计哲学)。Cut的命令是:
c目的地
这里的“目的地”是这么一个意思:想Cut掉光从标当前位置到某个位置的全部字符,那么“某个位置”就是这个命令中的“目的地”。那么,这个“目的地”该如何表达呢?难道用行列坐标吗?不是。要表达这个“目的地”,应该用“从当前光标如何到达目的地”的“命令”来表达,比如说:
我希望Cut掉“bin/csh”的全部字符,也就是说,从字符u(当前光标所在字符)到当前的行尾,全部要Cut掉,那么,“光标移动到当前的行尾”的命令是“$”,这样,这个Cut命令就应该是:
c$
当命令被输入后,“bin/csh”并没有如我们预期的那样消失(跟GUI软件的那种Cut不一样),而是行尾的字符“h”变成了“$”—— 这说明,Cut命令生效了:
bin/cs$
最关键的一点是:Cut命令生效后,vi从“命令模式”转变到了“插入模式”。所谓“插入模式”,就是:
光标所处在的位置,是等待你输入(插入、填入)字符的位置,当你键入一个字符后,光标从这个位置上跳到下一个字符位置,即继续等待你在该位置上填入字符,……
这跟GUI软件里的编辑文本模式,是一致的,只不过在GUI软件里,光标通常是一个闪烁的细竖线,而在vi这样的TUI软件里,光标则是一个不会闪的实心方块儿。但不论是细竖线还是方块儿,
它们总是出现在你已经输入的字符的右边。
如果想从让vi从命令模式直接进入插入模式,可以键入命令“i”(表示Insert,插入),这样,光标所在位置的字符以及后面的字符,将会被“插入”的(键入的新的)字符往后“驱逐”;也可以键入命令“a”(表示Append,追加),这样,光标会先 “跳到”后面一个字符上,然后再让你“插入”字符 —— 这等同于:在光标原来(跳动之前)所在的字符之后“追加”新的内容,故有此名。※ 在插入模式下,有时候(比如使用某些虚拟终端)不能使用方向键来操纵光标的移动。
有Cut就应该有Copy(复制)和Paste(粘贴),复制的命令是“y”(Yank,拉扯);粘贴的命令是“p”(粘贴到当前光标所在位置之后)或“P” (粘贴到当前光标所在位置之前)。如同命令“dd”是删除当前整行,命令“yy”是复制当前整行。
现在,在插入模式下,你可以直接逐字键入“usr/local/bin/zsh”了。由于这个字符串比原来“bin/cs$”的要长,所以我们可以发现原本的字符串被逐字覆盖的过程,但如果新插入的字符串的长度不足以覆盖原来的内容,那么,就会有“……$”样的字符串总是残留在那里,为了让这些残留的“影子”消失,我们可以按下[Esc]键,那么Buffer将被更新,同时,vi又回到了命令模式。
之前,我们已经学习了RegExp,如果能把RegExp的技巧运用在vi中,那就更加符合vi的设计哲学了(不做呆板而重复的工作、找到直接切入问题核心的途径)!
我们可以这样:
使用命令:
/Shell.*csh√
※ 命令“/”是运用RegExp,从当前光标位置向下寻找到第一个匹配的字符串。(若是向上(往回),则用命令“?”。)后续键入“n”或“N”则可以寻找“nEXT(下一个)”或“上一个”。
于将光标拉到“Shell”所在行的首段。
然后,再用命令:
:s/bin.*$/usr\/local\/bin\/zsh√
这个命令的格式是:
:区域s/模板/被匹配的字符串将被更新为什么/参数√
这里的“区域”是指查找的范围,用“m,n”表示从第m行到第n行,用“%”整个Buffer;这里的“参数”有:“g”表示全行每处匹配处均进行替换(否则仅在当前行第一处匹配处进行替换);“c”表示替换时需要Confirm(确认)。
若要在命令里的正斜杠之间表达正斜杠,必须使用转义序列“\/”。
至此,文件的Buffer被更新好了,我们需要将Buffer写入文件,我们可以使用命令:
:w√
或
:w! √
以及
:wq√
或
:wq! √
来实现写入以及写入并退出vi(也退出了chsh程序)。
※ 必须确认上述操作完全无误,否则破坏了用户配置文件,很可能会导致无法登录Shell,那可就麻烦了!
※ 现在,我们可以领略到,vi有这么一个优点:用户的手指不需要离开键盘(甚至仅仅是主键盘,而不要方向键),就可以完成一切操作。
现在我们通过chsh命令(藉着vi)改变了当前用户的Shell设置,这当然需要系统再一次进行某种初始化后才能生效。于是,我们登出当前用户,使用命令:
exit
再登录。
现在,我们使用命令:
echo $SHELL
反馈的结果是新的Shell(即zsh)的位置,这说明我们Change Shell的操作成功了。
不过 …… 你会觉得,这个zsh跟原来的tcsh根本没有什么区别嘛……
的确是的,目前的Shell的Prompt(提示符,就是提示用户可以输入命令的特殊符号,由Shell提供)仍然是“主机名”加一个“#”(如果是root账户登录,就是“#”,此外就是“$”)。
现在我们掌握了vi的基本用法,就可以Customize(由客户定制)自己的zsh了。
我们进入自己的家目录,使用命令:
cd ~
然后创建一个名为“.zshrc”的文件:
vi .zshrc
撰写文件内容如下:
case $TERM in
xterm*) TERM=xterm-256color ;;
screen*) TERM=screen-256color ;;
esac
alias grep='grep -E --color=auto' \
fgrep='fgrep --color=auto' \
egrep='egrep --color=auto' \
ll='ls -lh' \
la='ls -A' \
l='ls -CF' \
tree='tree -C' \
v='vi'
HISTFILE=~/.zsh_history
HISTSIZE=1000
SAVEHIST=1000
bindkey -e
bindkey "^[OH" beginning-of-line
bindkey "^[OF" end-of-line
bindkey "^[[3~" delete-char
autoload compinit && compinit
zstyle ':completion:*' menu select
setopt autocd
setopt autopushd
setopt cdablevars
setopt correct
setopt globdots
setopt extendedglob
setopt interactivecomments
setopt completeinword
setopt nobeep
setopt multios
PS1=$'%{\e[1;32m%}%n@%M%{\e[0m%}%{\e[1;33m%}:%{\e[0m%}%{\e[1;32m%}%~%{\e[0m%}%{\e[1;33m%}\n$%{\e[0m%} '
alias cp="cp -i"
alias mv="mv -i"
alias rm="rm -i"
※ 这段.zshrc文件内容,参考并引用了 CPFN.org(中国程序员自由网络)的站长wallee的作品,特此致谢:)
这是一段关于当前用户的zsh的配置(准确地说,是当前用户登录后,用来初始化zsh的Script(脚本)),如果你看不懂其中的某些命令或变量名,请你用man来学习!
保存文件,登出,再登录。怎么样?TUI界面是不是漂亮了许多?!
让我们休息一下,回顾一下这段历程:我们从ls命令中出现的通配符讲到了管道,“顺带着”学习了“一切皆是文件”、设备节点、终端、TTY、换行与回车、I/O重定向、STDIO(重点)、grep、RegExp(重点)、FreeBSD Ports、.tar.gz、make install、man、chsh、vi(重点)、zsh。
现在,该是我们回到这个支点的时候了:
ls | grep "^foo.*"
当ls将当前一级目录下的所有文件名以“逐行”形式的stdout,透过管道哺馈到grep程序的stdin之后,grep程序就对它所获取的stdin流进行处理:凡是匹配于模板的行,就被反馈到终端屏幕上。请特别注意:
stdio流是的存在方式是“流”——“流动”的“流”,这就意味着:这里的数据流以类似“逐行逐行”的“川行”方式,源源不断地被哺馈给grep程序,当grep获得到流中的“一行”,它就做出处理,之后继续处理“川流不息”而来的数据中的“下一行”。—— 整个过程是动态的(有当前一步不能预料将来一步的意味,这也叫Asynchronous(异步的))。
下面这个例子,将让屌丝们体会一下 UNIX Styled 命令行、正则表达式、Shell、管道流等等的强大威力:
假设当前一级目录下、以及其子目录下、以及其子目录的子目录下、以及其子目录的子目录的子目录下、……(有多少级目录都没关系)存放着许多文件(全都是文本类型的文件),这些文件中有大量的手机号码,且有大量重复号码,每个号码都是独立占一行,但号码顶头或尾后可能有空格。现在,我们只要一行命令,就将这些号码中的以“136”开头的中国移动的号码的数量统计出来(重复存在的号码只计一次),显示在终端屏幕上,并保存到一个文件中。
只要一行命令哦:
grep "^\s*136[0-9]{8}\s*[DISCUZ_CODE_106]quot; * -rho | sed 's/[^0-9]//g' | sort -nr | uniq -c | wc -l | sed 'i\Qty of 136-numbers:' | tee stat.txt
我们逐项来解释一下:
(1)grep命令程序中的 RegExp 模板我们应该已经很容易理解它了,这里还有两个参数:“r”表示递归查找,“h”表示to Hide filename(不显示被匹配的文本所在的文件的名字),“o”表示Only(仅仅)显示被匹配的字符串而不是显示所在行的全部内容。
(2)sed命令程序是一个Strean Editor(流编辑器),它好像一个过滤/整流器,可以利用其中的编辑命令“s”对流中的内容进行更新,这里,命令“s”将一切非数字的字符全部替换为空 —— 这是为了除去之前grep匹配所获取到的空白字符(\s*)。(※ 标记“g”的意思是:全局替换而非只替换第一次所匹配到的。)
(3)sort命令程序,用来对grep的stdout(匹配结果)排序,参数“n”表示Number(以数值大小排序),“r”表示Reverse(逆向排序,这只是为了讲解这个选项,此例完全可以不用它)。
(4)因为sort将号码排序,所以相同的号码一定是聚集着连续排列在一起的,此时,uniq命令程序将这样的结果进行“消除重复行(使文本行内容Unique)”,参数“c”表示在每行结果前面打印出该行出现的次数 —— 这在本例中也是完全可以不用的,但对于那些接着需要用到“sort -n”的场合就十分必要了。
(5)wc命令程序是一个Word Count(计数)程序,参数“l”令它反馈出stdin中的行数。
(6)sed命令程序中的“i”命令,使它在流中Insert(插入)文本。
(7)tee命令程序,则如同一个Tee(T形三通水管)一样,使得本来单单反馈到终端屏幕的数据流,能被复制到制定的文件中去。
—— 是不是超强大?如果按照屌丝们使用GUI软件的方法 …… 在Microsoft Windows环境下,要多少个软件,拖泥带水地组合在一起,鼠标要拉扯点击多少次,才能实现这个需求呢?!
现在,我们安装Vim —— 它将是本节课的终点。
使用命令:
cd /usr/ports/editors/vim/
make install clean
如同给zsh配备初始化脚本(一个dot file)一样,我们也需在Vim安装完成之后,为其配备初始化脚本,这样的脚本,在系统中已经有现成的了,我们直接使用命令copy到当前用户的家目录即可:
cp /usr/local/share/vim/vim73/vimrc_example.vim ~/.vimrc
此后,使用命令:
vim foo.txt
就可以打开Vim,编辑你所需要的文件了。你将会发现,Vim比vi要好用得多。
现在,我们专门介绍一下Vim较vi的一些新加特性:
(一)窗口(Window)
在命令模式下,按[Ctrl]+[w]之后,Vim界面的末行,将显示一个“^W”,表示你已经按下了这个组合键,此时,你可以再按以下的键:
【n】 —— 在Vim界面上开启一个新窗口,用来编辑新文件(New file)。
【s】 —— 在Vim界面上开启一个新窗口,用来编辑相同的文件(Same file)。
【w】 —— 编辑的焦点(Focus),在不同窗口之间顺次切换。
(二)拆分(Split)
使用命令:
:split 文件名
或
:vsplit 文件名
可以水平或垂直拆分出新的窗口,以编辑指定的文件,否不指定文件名,则是开启空Buffer。
在Vim的多窗口模式下,编辑工作一如单窗口模式,没有什么区别。除了可以用命令“:q”关闭当前焦点所在的窗口之外,还可以使用命令:
:qall
—— 关闭全部窗口。
:only
—— 关闭其他窗口。
最后,我们利用Vim编写一个汇编程序,使得我们可以有属于自己的Boot-able软盘:
; Verse 1
org 0x7c00
; Verse 2
mov ax, cs
mov ds, ax
mov es, ax
call PrintStr
jmp fin
; Verse 3
fin:
hlt
jmp fin
; Verse 4
PrintStr:
mov bp, MyBoot
mov cx, 80
mov ax, 0x1301
mov bh, 0x00
mov bl, 0x09
mov dl, 35
mov dh, 12
int 0x10
jmp fin
ret
; Verse 5
MyBoot:
db "Just A Tiny Tiny Boot!"
times 510-($-$) db 0
dw 0xaa55
※ 类似上述代码示例,在网上的许多地方、在许多书中都有,令人无法稽考最初的原创者是谁,无论如何,向相关原创者致谢!
为了使得程序线索清晰,我将代码分为五段(Verses),大家可以练习用Vim将这段代码又快又准地录入到一个文本文档,至于代码各部分的含义,大家如果不理解,可以先猜一猜。尽量猜,能猜到哪里算哪里。※ 可以参考之前介绍过的关于BIOS读取Bootstrap以及BIOS中断调用的知识。
我们需要使用Netwide 汇编器(NASM),将上述代码翻译为CPU可以执行的(二进制)机器代码。如何安装nasm,就不用我多说了吧:)
使用nasm翻译汇编程序的命令格式如下:
nasm 源程序名 -o 二进文件名
我们应该将二进文件的扩展名设为“.img”(映像),然后我们将这个映像文件复制到一台有Virtual Machine(虚拟机软件,如Virtual Box)的电脑上,对于如何使用虚拟机软件,屌丝们应该十分熟稔吧!但对于如何将文件从这台FreeBSD电脑上复制出去,恐怕有点无措,其实很简单:
找来一个你在Microsoft Windows下使用的USB硬盘或闪存设备,插上机器。
FreeBSD系统随即会有提示,有新设备加入了。
此时你会发现在/dev/目录下多了一个诸如da0这样的文件(设备节点)。
现在,我们需要将这个设备挂载到我们的文件系统上,运用之前已经学到的知识,你应该不难理解以下操作:
mount -t msdosfs /dev/da0 /media
(※ 参数t后面跟着的,是所要挂载的文件系统的类型。)
当然,你可以在/media/目录下新建一个目录,然后将da0设备挂载到那里。
将之前生成好的二进制映像文件复制到/media/目录。
此后,你可以随时卸载设备,可以使用命令:
umount /media
这样,你就把文件从FreeBSD电脑上导出去了。
回到你再熟悉不过的Microsoft Windows电脑上,使用虚拟机软件,新建一个虚拟电脑,并为其配置一个软盘控制器,再将刚才的二进制映像文件作为软盘“插入”到这个“软盘驱动器”上,此外,别忘了设置从这个软盘Boot,最后,开启这台虚拟电脑。
怎么样,是不是很有趣?
这节课,就到此为止了。对于电脑屌丝们来说,这节课已经为你们完成了脱屌的第一步!
我们下节课再见!
祝你们平安!
Larry
Ageratum
Westernwall
2012年11月15日凌晨