第11章 认识与学习bash

在Linux的环境下,如果你不懂bash是什么,那么其他的东西就不用学了。因为前面几章我们使用终端机执行命令的方式,就是通过 bash 的环境来处理的。所以说它很重要。bash的东西非常多,包括变量的设置与使用、bash操作环境的构建、数据流重定向的功能,还有那好用的管道命令。好好清一清脑门,准备用功去,这个章节几乎是所有命令行(command line)与将来主机维护与管理的重要基础,一定要好好仔细阅读。

11.1 认识bash 这个shell

我们在第1章Linux是什么当中提到了:管理整个计算机硬件的其实是操作系统的内核(kernel),这个内核是需要被保护的,所以我们一般用户就只能通过shell来跟内核通信,以让内核达到我们所想要达到的工作。那么系统有多少shell可用呢?为什么我们要使用bash?下面分别来谈一谈。

11.1.1 硬件、内核与shell

什么是Shell?这应该是个蛮有趣的话题,相信只要摸过计算机,对于操作系统(不论是Linux、UNIX或者是Windows)有点概念的朋友们大多听过这个名词,因为只要有“操作系统”那么就离不开Shell。不过在讨论Shell之前,我们先来了解一下计算机的运作状况。举个例子来说:当你要计算机传输出来“音乐”的时候,你的计算机需要什么东西呢?

1.硬件:当然就是需要你的硬件有“声卡芯片”这个配备,否则怎么会有声音;

2.内核管理:操作系统的内核可以支持这个芯片组,当然还需要提供芯片的驱动程序;

3.应用程序:需要用户(就是你)输入发生声音的命令。

这就是基本的一个输出声音所需要的步骤。也就是说,你必须要“输入”一个命令之后,“硬件”才会通过你执行的命令来工作。那么硬件如何知道你执行的命令呢?那就是 kernel(内核)的控制工作了。也就是说,我们必须要通过“Shell”将我们输入的命令与内核通信,好让内核可以控制硬件来正确无误地工作。基本上,我们可以通过图11-1来说明一下。

figure_0309_0244

图11-1 硬件、内核与用户的相关性图示

我们在第0章的操作系统小节曾经提到过,操作系统其实是一组软件,由于这组软件在控制整个硬件与管理系统的活动监测,如果这组软件能被用户随意操作,若用户应用不当,将会使得整个系统崩溃。因为操作系统管理的就是整个硬件功能,所以当然不能够随便被一些没有管理能力的终端用户随意使用。

但是我们总是需要让用户操作系统的,所以就有了在操作系统上面发展的应用程序。用户可以通过应用程序来指挥内核,让内核达成我们所需要的硬件任务。如果考虑如第0章所提供的操作系统图(见图 0-18),我们可以发现应用程序其实是在最外层,就如同鸡蛋的外壳一样,因此这个也就被称呼为shell。

其实shell的功能只是提供用户操作系统的一个接口,因此这个shell需要可以调用其他软件才好。我们在第5章到第10章提到过很多命令,包括man,chmod,chown,vi,fdisk,mkfs等命令,这些命令都是独立的应用程序,但是我们可以通过shell(就是命令行模式)来操作这些应用程序,让这些应用程序调用内核来运行所需的工作。这样对于shell是否有了一定的概念了?

也就是说,只要能够操作应用程序的接口都能够称为shell。狭义的shell指的是命令行方面的软件,包括本章要介绍的 bash 等。广义的shell 则包括图形界面的软件,因为图形界面其实也能够操作各种应用程序来调用内核工作。不过在本章中,我们主要还是在使用 bash 。

11.1.2 为何要学命令行界面的shell

命令行界面的 shell 是很不好学的,但是学了之后好处很多。所以在这里鸟哥要先对你进行一些心理辅导,先来了解一下为什么学习shell是有好处的,这样你才会有信心继续玩下去。

命令行界面的shell:大家都一样。

鸟哥经常听到这个问题:“我干嘛要学习 shell 呢?不是已经有很多的工具可以提供我设置我的主机了?我为何要花这么多时间去学命令呢?不是用 X Window 按一按几个按钮就可以搞定了吗?”。还是得一再地强调,X Window 还有 Web 界面的设置工具例如 Webmin [2]是真的好用的软件,它真的可以帮助我们很简易地设置好我们的主机,甚至是一些很高级的设置都可以帮我们搞定。

但是鸟哥在前面的章节里面也已经提到过相当多次了,X Window 与Web 界面的工具,它的界面虽然亲善,功能虽然强大,但毕竟它是将所有利用到的软件都集成在一起的一组应用程序而已,并非是一个完整的套件,所以某些时候当你升级或者是使用其他套件管理模块(例如tarball而非rpm文件等)时,就会造成设置的困扰了。甚至不同的distribution所设计的X Window界面也都不相同,这样也造成学习方面的困扰。

命令行界面的shell就不同了。几乎各家distributions使用的bash都是一样的。如此一来,你就能够轻轻松松转换不同的distributions,就像武侠小说里面提到的“一法通、万法通。”

远程管理:命令行界面就是比较快。

此外,Linux的管理经常需要通过远程联机,而联机时命令行界面的传输速度一定比较快,而且,较不容易出现断线或者是信息外流的问题,因此,shell真的是得学习的一项工具。而且,它可以让你更深入 Linux,更了解它,而不是只会按一按鼠标而已。所谓“天助自助者”,多摸一点文本模式的东西,会让你与Linux更亲近。

Linux的任督二脉:shell是也。

有些朋友也很可爱,常会说:“我学这么多干什么?又不常用,也用不到。”有没有听过“书到用时方恨少”?当你的主机一切安然无恙的时候,你当然会觉得好像学这么多的东西一点帮助也没有。万一某一天真的不幸中标了,你该如何是好?是直接重新安装?还是先追踪入侵来源后进行漏洞的修补?或者是干脆就关站好了?这当然涉及很多的考虑,但就从鸟哥的观点来看,多学一点总是好的,尤其我们可以有备而无患,甚至学得不精也没有关系,毕竟没有人要你一定要背这么多的内容,了解概念就很了不起了。

此外,如果你真的有心想要将你的主机管理的好,那么良好的shell程序编写是一定需要的。就鸟哥自己来说,鸟哥管理的主机虽然还不算多,只有区区不到十部,但是如果每部主机都要花上几十分钟来查阅它的登录文件信息以及相关的信息,那么鸟哥可能会疯掉。基本上也太没有效率了。这个时候如果能够通过shell提供的数据流重定向以及管道命令。那么鸟哥分析日志信息只要花费不到十分钟就可以看完所有的主机之重要信息了,相当好用。

由于学习shell的好处真的是很多,所以如果你是个系统管理员,或者有心想要管理系统的话,那么 shell 与 shell scripts 这个东西真的有必要看一看,因为它就像“打通任督二脉,任何武功都能随你应用”一样。

11.1.3 系统的合法shell 与/etc/shells 功能

知道什么是Shell之后,那么我们来了解一下Linux使用的是哪一个shell呢?什么?哪一个?难道说shell不就是“一个shell吗?”。那可不!由于早年的UNIX年代,发展者众多,所以由于shell依据发展者的不同就有许多的版本,例如常听到的 Bourne SHell(sh)、Sun 里头默认的 C SHell、商业上常用的K SHell、,还有 TCSH 等,每一种 Shell 都各有其特点。至于 Linux 使用的这一种版本就称为“Bourne Again SHell(简称bash)”,这个 Shell 是Bourne Shell 的增强版本,也是基于 GNU 的架构下发展出来的。

在介绍shell的优点之前,先来说一说shell的简单历史吧 [3]。第一个流行的shell是由Steven Bourne 发展出来的,为了纪念他所以就称为 Bourne shell,或直接简称为 sh。而后来另一个广为流传的 shell 是由柏克莱大学的 Bill Joy 设计依附于 BSD 版的 UNIX系统中的 shell,这个 shell 的语法有点类似 C 语言,所以才得名为 C shell,简称为 csh。由于在学术界 Sun 主机势力相当庞大,而 Sun主要是 BSD 的分支之一,所以 C shell 也是另一个很重要而且流传很广的 shell 之一。

由于 Linux 是由 C 程序语言编写的,很多程序员使用 C 来开发软件,因此 C shell相对就很热门了。另外,还记得我们在第1 章Linux 是什么中提到的吧?Sun 公司的创始人就是 Bill Joy,而 BSD 最早就是 Bill Joy 发展出来的。

那么目前我们的 Linux(以 CentOS 5.x 为例)有多少我们可以使用的 shell 呢?你可以检查一下/etc/shells这个文件,至少就有下面这几个可以用的shell:

/bin/sh(已经被/bin/bash所替代)

/bin/bash(就是Linux默认的shell)

/bin/ksh(Kornshell 由 AT&T Bell lab.发展出来的,兼容于 bash)

/bin/tcsh(整合 C Shell,提供更多的功能)

/bin/csh(已经被/bin/tcsh所替代)

/bin/zsh(基于ksh发展出来的,功能更强大的shell)

虽然各家shell的功能都差不多,但是在某些语法的执行方面则有所不同,因此建议你还是得要选择某一种shell来熟悉一下较佳。Linux默认就是使用bash,所以最初你只要学会bash就非常不错了。另外,为什么我们系统上合法的shell要写入/etc/shells这个文件?这是因为系统某些服务在运行过程中,会去检查用户能够使用的shells,而这些shell的查询就是借助/etc/shells这个文件。

举例来说,某些FTP网站会去检查用户的可用shell,而如果你不想要让这些用户使用FTP以外的主机资源时,可能会给予该用户一些奇怪的shell,让用户无法以其他服务登录主机。这个时候,你就得将那些怪怪的 shell 写到/etc/shells 当中了。举例来说,我们的 CentOS 5.x 的/etc/shells 里头就有个/sbin/nologin文件的存在,这个就是我们所说奇怪的shell。

那么再想一想,我这个用户什么时候可以取得 shell 来工作呢?还有我这个用户默认会取得哪一个shell?还记得我们在第5章的在终端界面登录linux小节当中提到的登录操作吧?当我登录的时候系统就会给我一个shell让我来工作了。而这个登录取得的shell就记录在/etc/passwd这个文件内。这个文件的内容是什么?

[root@www ~]# cat /etc/passwd

root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

…..(下面省略)…..

如上所示,在每一行的最后一个数据,就是你登录后可以取得的默认的shell。那你也会看到,root是/bin/bash,不过系统账号bin与daemon等就使用那个奇怪的/sbin/nologin,关于用户这部分的内容,我们留在第14章的账号管理时提供更多的说明。

11.1.4 bash shell 的功能

既然/bin/bash是Linux默认的shell,那么总是得了解一下这个shell吧!bash是GNU计划中重要的工具软件之一,目前也是 Linux distributions 的标准 shell。bash 主要兼容于 sh,并且依据一些用户需求而加强的 shell 版本。不论你使用的是那个 distribution,都需要学习 bash。那么这个 shell有什么好处,为什么Linux要使用它作为默认的shell呢?bash主要的优点有下面几个:

命令记忆能力(history)

在 bash 的功能里头,鸟哥个人认为相当棒的一个就是“它能记忆使用过的命令”。这功能真的相当好。因为我只要在命令行中按上下键就可以找到前/后一个输入的命令。而在很多distribution里头,默认的命令记忆功能可以到达1000个。也就是说,你曾经执行过的命令几乎都被记录下来了。

这么多的命令记录在哪里呢?在你的主文件夹内的.bash_history 中。不过需要留意的是,~/.bash_history记录的是前一次登录以前所执行过的命令,而至于这一次登录所执行的命令都被暂存在临时内存中,当你成功注销系统后,该命令记忆才会记录到.bash_history当中。

这有什么功能呢?最大的好处就是可以查询曾经做过的操作。如此可以知道你的执行步骤,那么就可以追踪你曾执行过的命令,以作为排错的工具。但如此一来也有个烦恼,就是如果被黑客入侵了,那么它只要翻你曾经执行过的命令,刚好你的命令又跟系统有关(例如直接输入MySQL的密码在命令行上面),那你的主机可就危险了。到底记录命令的数目越多还是越少越好?所以,最好是将记录的命令数目减小一点。

命令与文件补全功能([Tab]按键的好处)

还记得我们在第5章内的重要的几个热键小节当中提到的[Tab]这个按键吗?这个按键的功能就是在bash里面才有的。经常在bash环境中使用[Tab]是个很好的习惯,因为至少可以让你少打很多字,并且确定输入的数据是正确的。使用[Tab]按键的时机依据[Tab]接在命令后或参数后而有所不同。我们再复习一次:

[Tab]接在一串命令的第一个字的后面,则为命令补全;

[Tab]接在一串命令的第二个字以后时,则为文件补齐。

所以说,如果我想要知道我的环境中所有可以执行的命令有几个,就直接在bash的提示符后面连续按两次[Tab]按键就能够显示所有的可执行命令了。那如果想要知道系统当中所有以 c为开头的命令呢?就按下“c[Tab][Tab]”就好。

真的是很方便的功能,在bash shell 下面多按几次[Tab]是一个不错的习惯。

命令别名设置功能(alias)

假如我需要知道这个目录下面的所有文件(包含隐藏文件)及所有的文件属性,那么我就必须要执行“ls-al”这样的命令串,有没有更快的替代方式?就使用命令别名呀。例如鸟哥最喜欢直接以lm这个自定义的命令来替换上面的命令,也就是说,lm会等于ls-al这样的一个功能,那么要如何做呢?就使用alias即可。你在命令行输入alias就可以知道目前的命令别名有哪些了。也可以直接执行命令来设置别名:

alias lm='ls -al'

作业控制、前台、后台控制(job control,foreground,background)

这部分我们在第17章Linux过程控制中再提。使用前后、后台的控制可以让作业进行得更为顺利。至于作业控制(jobs)的用途则更广,可以让我们随时将工作丢到后台中执行。而不怕不小心使用了[Ctrl]+C来中断该进程。此外也可以在单一登录的环境中达到多任务的目的呢!

程序脚本(shell script)

在 DOS 年代还记得将一堆命令写在一起的所谓的“批处理文件”吧?在 Linux 下面的 shell script则发挥更为强大的功能,可以将你平时管理系统常需要执行的连续命令写成一个文件,该文件并且可以通过交互的方式来进行主机的检测工作。也可以通过 shell 提供的环境变量及相关命令来进行设计。整个设计下来几乎就是一个小型的程序语言了。该 script 的功能真的是超乎我的想象之外。以前在DOS下面需要程序语言才能写的东西,在Linux下面使用简单的 shell scripts 就可以帮你完成。这部分我们在第 13 章再来谈。

通配符(Wildcard)

除了完整的字符串之外,bash还支持许多的通配符来帮助用户查询与命令执行。举例来说,想要知道/usr/bin 下面有多少以 X 为开头的文件吗?使用“ls-l /usr/bin/X*”就能够知道了。此外还有其他可供利用的通配符,这些都能够加快用户的操作。

11.1.5 bash shell 的内置命令:type

我们在第 5 章提到关于 Linux 的在线帮助文件部分,也就是 man page 的内容,那么 bash 有没有什么说明文件?当然有!在 shell 的环境下直接输入 man bash,你可以看到非常多并且很详尽的数据。

不过在这个 bash 的 man page 当中,不知道你是否有察觉到怎么这个说明文件里面有其他的文件说明啊?举例来说,那个 cd 命令的说明就在这个 man page 内,然后我直接输入 man cd 时,怎么出现的界面中,最上方竟然出现一堆命令的介绍?为了方便shell的操作,其实bash已经“内置”了很多命令,例如上面提到的cd,还有例如umask等的命令,都是内置在bash当中的。

那我怎么知道这个命令是来自于外部命令(指的是其他非bash所提供的命令)或是内置在bash当中的呢?利用type这个命令来查看即可。举例来说:

[root@www ~]# type [-tpa] name

参数:

type:不加任何参数时,type 会显示出 name 是外部命令还是 bash 内置命令

-t :当加入 -t 参数时,type 会将 name 以下面这些字眼显示出它的意义:

file :表示为外部命令;

alias :表示该命令为命令别名所设置的名称;

builtin :表示该命令为 bash 内置的命令功能。

-p :如果后面接的 name 为外部命令时,才会显示完整文件名;

-a :会由 PATH 变量定义的路径中,将所有含 name 的命令都列出来,包含 alias

范例一:查询一下 ls 这个命令是否为 bash 内置

[root@www ~]# type ls

ls is aliased to `ls —color=tty' <==未加任何参数,列出 ls 的最主要使用情况

[root@www ~]# type -t ls

alias      <==仅列出 ls 执行时的依据

[root@www ~]# type -a ls

ls is aliased to `ls —color=tty' <==最先使用 aliase

ls is /bin/ls    <==还有找到外部命令在 /bin/ls

范例二:那么 cd 呢?

[root@www ~]# type cd

cd is a shell builtin   <==看到了吗?cd 是 shell 内置命令

通过type这个命令我们可以知道每个命令是否为bash的内置命令。此外由于利用type找到后面的名称时,如果后面接的名称并不能以执行文件的状态被找到,那么该名称是不会被显示出来的。

也就是说type主要在找出“执行文件”而不是一般文件名。所以,这个type也可以用来作为类似which命令的用途了。

11.1.6 命令的执行

我们在第 5 章的开始执行命令小节已经提到过在 shell 环境下的命令执行方法,如果你忘记了请回到第5章再去复习一下。这里不重复说明了。鸟哥这里仅就反斜杠(\)来说明一下命令执行的方式。

范例:如果命令串太长的话,如何使用两行来输出?

[vbird@www ~]# cp /var/spool/mail/root /etc/crontab \

> /etc/fstab /root

上面这个命令的用途是将三个文件复制到/root这个目录下而已。不过因为命令太长,于是鸟哥就利用“[Enter]”来将[Enter]这个按键“转义”开来,让[Enter]按键不再具有“开始执行”的功能。好让命令可以继续在下一行输入。需要特别留意,[Enter]按键是紧接着反斜杠(\)的,两者中间没有其他字符。因为\仅转义“紧接着的下一个字符”而已。所以万一我写成 “[Enter]”,即[Enter]与反斜杠中间有一个空格时,则\转义的是空格键而不是[Enter]按键。这个地方请再仔细看一遍,很重要。

如果顺利转义[Enter]后,下一行最前面就会出现>的符号,你可以继续输入命令。也就是说那个>是系统自动出现的,你不需要输入。

总之当我们顺利在终端机(tty)上面登录后,Linux就会依据/etc/passwd文件的设置给我们一个shell(默认是 bash),然后我们就可以依据上面的命令执行方式来操作 shell,之后我们就可以通过man这个在线查询来查询命令的使用方式与参数说明,很不错吧?那么我们就赶紧来操作bash这个shell。

11.2 shell的变量功能

变量是 bash 环境中非常重要的一个玩意儿,我们知道 Linux 是多用户、多任务的环境,每个人登录系统都能取得一个bash,每个人都能够使用bash执行mail这个命令来收取“自己”的邮件,问题是 bash 是如何得知你的邮件信箱是哪个文件?这就需要“变量”的帮助了。所以你说变量重不重要呢?下面我们将介绍重要的环境变量、变量的使用与设置等数据。

11.2.1 什么是变量?

那么什么是“变量”呢?简单地说就是让某一个特定字符串代表不固定的内容就是了。举个大家在高中时都会学到的数学例子,那就是 “y=ax+b”这东西,在等号左边的(y)就是变量,在等号右边的(ax+b)就是变量内容。要注意的是,左边是未知数,右边是已知数。讲得更简单一点,我们可以用一个简单的“字眼”来替代另一个比较复杂或者是容易变动的数据。这最大的好处就是“方便”。

变量的可变性与方便性

举例来说,我们每个账号的邮件信箱默认是以MAIL这个变量来进行访问的,当dmtsai这个用户登录时,他便会取得MAIL这个变量,而这个变量的内容其实就是/var/spool/mail/dmtsai,那如果vbird登录呢?他取得的MAIL这个变量的内容其实就是/var/spool/mail/vbird。而我们使用信件读取命令mail来读取自己的邮件信箱时,这个程序可以直接读取MAIL这个变量的内容,就能够自动分辨出属于自己的信箱信件了。这样一来程序员就真的很方便了。

figure_0314_0245

图11-2 程序、变量与不同用户的关系

如图11-2所示,由于系统已经帮我们规划好MAIL这个变量,所以用户只要知道mail这个命令如何使用即可,mail会主动使用MAIL这个变量,就能够如上图所示取得自己的邮件信箱了。(注意大小写,小写的mail是命令,大写的MAIL则是变量名称。)

那么使用变量真的比较好吗?这是当然的。想象一个例子,如果mail这个命令将root收信的邮件信箱(mailbox)中文件名为/var/spool/mail/root 的文件内容直接写入程序代码中。那么当dmtsai要使用mail时,将会取得/var/spool/mail/root这个文件的内容。所以你就需要帮dmtsai也设计一个mail的程序,将/var/spool/mail/dmtsai写死到mail的程序代码当中。那系统要有多少个 mail 命令啊?反过来说使用变量就变得很简单了。因为你不需要改动程序代码,只要将MAIL这个变量带入不同的内容即可让所有用户通过mail取得自己的信件。当然简单多了。

影响bash环境操作的变量

某些特定变量会影响到bash的环境。举例来说,我们前面已经提到过很多次的那个PATH变量,你能不能在任何目录下执行某个命令与PATH这个变量有很大的关系。例如你执行ls这个命令时,系统就是通过 PATH 这个变量里面的内容所记录的路径顺序来查找命令的呢。如果在找完 PATH 变量内的路径还找不到 ls 这个命令时,就会在屏幕上显示“command not found”的错误信息了。

如果说得明白一点,那么由于在Linux下面,所有的执行都是需要一个执行码,而就如同上面提到的,你真正以shell来跟Linux通信,是在正确的登录Linux之后。这个时候你就有一个bash的执行程序,也才可以真正经由bash来跟系统通信。而在进入shell之前,也正如同上面提到的,由于系统需要一些变量来提供它数据的访问(或者是一些环境的设置参数值,例如是否要显示彩色等的),所以就有一些所谓的“环境变量”需要来读入系统中了。这些环境变量例如PATH、HOME、MAIL、SHELL等,为了区别与自定义变量的不同,环境变量通常以大写字符来表示。

脚本程序设计(shell script)的好帮手

这些还都只是系统默认的变量的目的,如果是个人的设置方面的应用呢?例如你要写一个大型的 script 时,有些数据因为可能由于用户习惯的不同而有区别,比如说路径好了,由于该路径在 script 被使用在相当多的地方,如果下次换了一台主机,都要修改 script 里面的所有路径,那么我一定会疯掉。这个时候如果使用变量,而将该变量的定义写在最前面,后面相关的路径名称都以变量来替代。那么你只要修改一行就等于修改整篇 script 了,方便得很。所以良好的程序员都会善用变量的定义,如图11-3所示。

figure_0315_0246

图11-3 变量应用于shell script 的示意图

最后我们就简单地对“什么是变量”作个简单定义好了:变量就是以一组文字或符号等,来替代一些设置或者是一串保留的数据,例如:我设置了“myname”就是“VBird”,所以当你读取myname这个变量的时候,系统自然就会知道那就是 VBird 了。那么如何显示变量呢?这就需要使用到echo这个命令。

11.2.2 变量的显示与设置:echo, unset

说得口沫横飞的,也不知道“变量”与“变量代表的内容”有什么关系?那我们就将“变量”的“内容”显示出来。你可以利用echo这个命令来显示变量,但是变量在被显示时,前面必须要加上字符“$”才行,举例来说要知道PATH的内容,该如何是好?

变量的显示:echo

[root@www ~]# echo $variable

[root@www ~]# echo $PATH

/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

[root@www ~]# echo ${PATH}

变量的显示就如同上面的范例,利用ehco就能够读出,只是需要在变量名称前面加上$,或者是以${变量}的方式来显示都可以。当然那个 echo 的功能可是很多的,我们这里单纯是拿echo 来读出变量的内容而已,更多的 echo 使用,请自行 man echo 吧。

figure_0316_0247

请在屏幕上面显示出你的环境变量HOME与MAIL:

答:echo $HOME 或者是 echo ${HOME}

echo $MAIL 或者是 echo ${MAIL}

现在我们知道了变量与变量内容的之间的相关性了,那么我要如何“设置”或者是“修改”某个变量的内容啊?很简单啦。用“等号(=)”连接变量与它的内容就好了。举例来说:我要将myname这个变量名称的内容设置为VBird,那么:

[root@www ~]# echo $myname

<==这里并没有任何数据,因为这个变量尚未被设置,是空的。

[root@www ~]# myname=VBird

[root@www ~]# echo $myname

VBird <==出现了。因为这个变量已经被设置了。

如此一来,这个变量名称myname的内容就带有VBird这个数据了,而由上面的例子当中,我们也可以知道:在bash当中,当一个变量名称尚未被设置时,默认的内容是“空”的。另外变量在设置时,还是需要符合某些规定的,否则会设置失败。这些规则如下所示。

变量的设置规则

1.变量与变量内容以一个等号“=”来连接,如下所示:

“myname=VBird”

2.等号两边不能直接接空格符,如下所示为错误的:

“myname=VBird”或“myname=VBird Tsai”

3.变量名称只能是英文字母与数字,但是开头字符不能是数字,如下为错误的:

“2myname=VBird”

4.变量内容若有空格符可使用双引号“"”或单引号“'”将变量内容结合起来,但是

双引号内的特殊字符如$等,可以保有原本的特性,如下所示:

若“var="lang is $LANG"”,则“echo $var”可得“lang is en_US”

单引号内的特殊字符则仅为一般字符(纯文本),如下所示:

若“ar='lang is $LANG'”,则“echo $var”可得“lang is $LANG”

5.可用转义字符“\”将特殊符号(如[Enter]、$、\、空格符、!等)变成一般字符。

6.在一串命令中,还需要通过其他的命令提供的信息,可以使用反单引号“命令”或“$(命令)”。特别注意,那个是键盘上方的数字键 1 左边那个按键,而不是单引号。例如想要取得内核版本的设置:

“version=$(uname-r)”再“echo $version”可得“2.6.18-128.el5”

7.若该变量为了增加变量内容时,则可用"$变量名称"或${变量}累加内容,如下所示:

“PATH="$PATH":/home/bin”

8.若该变量需要在其他子进程执行,则需要以export来使变量变成环境变量:

“export PATH”

9.通常大写字符为系统默认变量,自行设置变量可以使用小写字符,方便判断(纯粹依照用户兴趣与嗜好)。

10.取消变量的方法为使用“unset变量名称”,例如取消myname的设置:

“unset myname”

下面让鸟哥举几个例子来让你看看,就知道怎么设置好你的变量。

范例一:设置一个变量 name ,且内容为 VBird

[root@www ~]# 12name=VBird

-bash: 12name=VBird: command not found <==屏幕会显示错误。因为不能以数字开头。

[root@www ~]# name = VBird   <==还是错误,因为有空白。

[root@www ~]# name=VBird   <==OK 啦!

范例二:承上题,若变量内容为 VBird's name 呢,就是变量内容含有特殊符号时。

[root@www ~]# name=VBird's name

单引号与双引号必须要成对,在上面的设置中仅有一个单引号,因此当你按下 enter 后,

你还可以继续输入变量内容。这与我们所需要的功能不同,失败。

记得,失败后要复原请按下 [ctrl]-c 结束。

[root@www ~]# name="VBird's name" <==OK 的啦!

命令是由左边向右找→,先遇到的引号先有用,因此如上所示,单引号会失效。

[root@www ~]# name='VBird's name' <==失败。

因为前两个单引号已成对,后面就多了一个不成对的单引号了,因此也就失败了。

[root@www ~]# name=VBird\'s\ name <==OK !

利用反斜杠 (\) 转义特殊字符,例如单引号与空格键,这也是 OK 的!

范例三:我要在 PATH 这个变量当中“累加” /home/dmtsai/bin 这个目录

[root@www ~]# PATH=$PATH:/home/dmtsai/bin

[root@www ~]# PATH="$PATH":/home/dmtsai/bin

[root@www ~]# PATH=${PATH}:/home/dmtsai/bin

上面这三种格式在 PATH 里头的设置都是 OK 的。但是下面的例子就不见得了。

范例四:承范例三,我要将 name 的内容多出 "yes" 呢?

[root@www ~]# name=$nameyes

知道了吧?如果没有双引号,那么变量成了什么?name 的内容是 $nameyes 这个变量。

我们可没有设置过 nameyes 这个变量。所以应该是下面这样才对。

[root@www ~]# name="$name"yes

[root@www ~]# name=${name}yes <==此例较贴切。

范例五:如何让我刚才设置的 name=VBird 可以用在下个 shell 的程序?

[root@www ~]# name=VBird

[root@www ~]# bash  <==进入到所谓的子进程

[root@www ~]# echo $name <==子进程:再次echo 一下;

<==嘿嘿!并没有刚才设置的内容。

[root@www ~]# exit  <==子进程:离开这个子进程

[root@www ~]# export name

[root@www ~]# bash  <==进入到所谓的子进程

[root@www ~]# echo $name <==子进程:在此执行。

VBird <==看吧!出现设置值了。

[root@www ~]# exit  <==子进程:离开这个子进程

什么是“子进程”呢?就是说在我目前这个shell的情况下,去打开另一个新的shell,新的那个shell就是子进程。在一般的状态下,父进程的自定义变量是无法在子进程内使用的。但是通过 export 将变量变成环境变量后,就能够在子进程下面应用了。至于程序的相关概念,我们会在第17章程序管理当中提到。

范例六:如何进入到你目前内核的模块目录?

[root@www ~]# cd /lib/modules/uname -r/kernel

[root@www ~]# cd /lib/modules/$(uname -r)/kernel

每个Linux都能够拥有多个内核版本,且几乎distribution的所有内核版本都不相同。以CentOS 5.3 (未更新前)为例,它的默认内核版本是 2.6.18-128.el5 ,所以内核模块目录在/lib/modules/2.6.18-128.el5/kernel/内。也由于每个distributions的这个值都不相同,但是我们却可以利用 uname- r 这个命令先取得版本信息,所以就可以通过上面命令当中的内含命令uname -r先取得版本输出到 cd 那个命令当中,就能够顺利进入目前内核的驱动程序所放置的目录,非常方便。

其实上面的命令可以说是做了两次操作:

1.先进行反单引号内的操作“uname-r”并得到内核版本为2.6.18-128.el5;

2.将上述的结果代入原命令,故得命令为“cd/lib/modules/2.6.18-128.el5/kernel/”。

范例七:取消刚才设置的 name 这个变量内容

[root@www ~]# unset name

根据上面的案例你可以试试看就可以了解变量的设置。这个是很重要的。请勤加练习。其中较为重要的一些特殊符号的使用,例如单引号、双引号、转义字符、“$”、反单引号等,下面的例题想一想。

figure_0318_0248

在变量的设置当中,单引号与双引号的用途有何不同?

答:单引号与双引号的最大不同在于双引号仍然可以保有变量的内容,但单引号内仅能是一般字符,而不会有特殊符号。我们以下面的例子做说明:假设你定义了一个变量 name=VBird,现在想以name 这个变量的内容定义出 myname 显示 VBird its me 这个内容,要如何设置呢?

[root@www ~]# name=VBird

[root@www ~]# echo $name

VBird

[root@www ~]# myname="$name its me"

[root@www ~]# echo $myname

VBird its me

[root@www ~]# myname='$name its me'

[root@www ~]# echo $myname

$name its me

发现了吗?没错!使用了单引号的时候,那么$name将失去原有的变量内容,仅为一般字符的显示类型而已。这里必须要特别小心注意。

figure_0318_0249

在命令执行的过程中,反单引号(`)这个符号代表的意义为何?

答:在一串命令中,在`之内的命令将会被先执行,而其执行出来的结果将作为外部的输入信息。例如 uname -r 会显示出目前的内核版本,而我们的内核版本在/lib/modules 里面,因此,你可以先执行 uname -r 找出内核版本,然后再以“cd 目录”回到该目录下,当然也可以执行如同上面范例六的执行内容。

另外再举个例子,我们也知道,locate命令可以列出所有的相关文件名,但是如果我想要知道各个文件的权限呢?举例来说,我想要知道每个crontab相关文件名的权限:

[root@www ~]# ls -l locate crontab

如此一来,先以locate将文件名数据都列出来,再以ls命令来处理即可。

figure_0318_0250

若你有一个常去的工作目录名称为“/cluster/server/work/taiwan_2005/003/”,如何进行该目录的简化?

答:在一般的情况下,如果你想要进入上述的目录得要执行“cd/cluster/server/work/taiwan_2005/003/”,以鸟哥自己的案例来说,鸟哥跑数值模式经常会设置很长的目录名称(避免忘记),但如此一来变换目录就很麻烦,此时鸟哥习惯利用下面的方式来降低命令执行错误的问题:

[root@www ~]# work="/cluster/server/work/taiwan_2005/003/"

[root@www ~]# cd $work

将来我想要使用其他目录作为我的模式工作目录时,只要更改 work 这个变量即可。而这个变量又可以在 bash 的配置文件中直接指定,那我每次登录只要执行“cd $work”就能够去到数值模式仿真的工作目录了。是否很方便呢?

老实说,使用“version=$(uname -r) ”来替代“version=uname -r ”比较好,因为反单引号大家老是容易打错或看错,所以现在鸟哥都习惯使用 $( 命令 )来介绍这个功能。

11.2.3 环境变量的功能

环境变量可以帮我们达到很多功能,包括主文件夹的变换、提示符的显示、执行文件查找的路径等,那么既然环境变量有那么多的功能,问一下目前我的shell环境中,有多少默认的环境变量啊?我们可以利用两个命令来查阅,分别是env与export。

用env查看环境变量与常见环境变量说明

范例一:列出目前的 shell 环境下的所有环境变量与其内容。

[root@www ~]# env

HOSTNAME=www.vbird.tsai <== 这台主机的主机名

TERM=xterm    <== 这个终端机使用的环境是什么类型

SHELL=/bin/bash   <== 目前这个环境下使用的 Shell 是哪一个程序

HISTSIZE=1000   <== 记录命令的条数,在 CentOS中默认可记录 1000 笔

USER=root    <== 用户的名称

LS_COLORS=no=00:fi=00:di=00;34:ln=00;36:pi=40;33:so=00;35:bd=40;33;01:cd=40;33;01:

or=01;05;37;41:mi=01;05;37;41:ex=00;32:.cmd=00;32:.exe=00;32:.com=00;32:.btm=0

0;32:.bat=00;32:.sh=00;32:.csh=00;32:.tar=00;31:.tgz=00;31:.arj=00;31:*.taz=

00;31:.lzh=00;31:.zip=00;31:.z=00;31:.Z=00;31:.gz=00;31:.bz2=00;31:*.bz=00;3

1:.tz=00;31:.rpm=00;31:.cpio=00;31:.jpg=00;35:.gif=00;35:.bmp=00;35:*.xbm=00

;35:.xpm=00;35:.png=00;35:*.tif=00;35: <== 一些颜色显示

MAIL=/var/spool/mail/root <== 这个用户所取用的 mailbox 位置

PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin:

/root/bin    <== 不再多讲。是执行文件命令查找路径

INPUTRC=/etc/inputrc  <== 与键盘按键功能有关。可以设置特殊按键。

PWD=/root    <== 目前用户所在的工作目录 (利用 pwd 取出)

LANG=en_US    <== 这个与语系有关,下面会再介绍

HOME=/root    <== 这个用户的主文件夹

_=/bin/env    <== 上一次使用的命令的最后一个参数(或命令本身)

env是environment(环境)的简写,上面的例子当中,是列出了所有的环境变量。当然如果使用 export 也会是一样的内容,只不过 export 还有其他额外的功能,我们等一下再提这个export命令。那么上面这些变量有些什么功能呢?下面我们就一个一个来分析。

HOME

代表用户的主文件夹。还记得我们可以使用cd~去到自己的主文件夹吗?或者利用cd就可以直接回到用户主文件夹了。那就是使用这个变量,有很多程序都可能会用到这个变量的值。

SHELL

它告知我们目前这个环境使用的shell是哪个程序?Linux默认使用/bin/bash的。

HISTSIZE

这个与“历史命令”有关,即是我们曾经执行过的命令可以被系统记录下来,而记录的“条数”则是由这个值来设置的。

MAIL

当我们使用mail这个命令在收信时系统会去读取的邮件信箱文件(mailbox)。

PATH

就是执行文件查找的路径,目录与目录中间以冒号(:)分隔,由于文件的查找是依序由PATH的变量内的目录来查询,所以目录的顺序也是重要的。

LANG

这个重要。就是语系数据,很多信息都会用到它。举例来说,当我们在启动某些Perl的程序语言文件时,它会主动去分析语系数据文件,如果发现有它无法解析的编码语系,可能会产生错误。一般来说,我们中文编码通常是 zh_CN.gb2312 或者是 zh_CN.UTF-8,这两个编码偏偏不容易被解译出来,所以有的时候,可能需要修改一下语系数据。这部分我们会在下个小节做介绍的。

RANDOM

这是“随机数”的变量。目前大多数的distributions都会有随机数生成器,那就是/dev/random这个文件。我们可以通过这个随机数文件相关的变量($RANDOM)来随机取得随机数值。在BASH 的环境下,这个 RANDOM 变量的内容介于 0~32767 之间,所以你只要 echo$RANDOM时,系统就会主动随机取出一个介于0~32767的数值。万一我想要使用0~9之间的数值呢?利用declare声明数值类型,然后这样做就可以了:

[root@www ~]# declare -i number=$RANDOM*10/32768 ; echo $number

8 <== 此时会随机取出 0~9 之间的数值。

大致上是有这些环境变量,里面有些比较重要的参数,在下面我们都会另外进行一些说明的。

用set查看所有变量(含环境变量与自定义变量)

bash可不只有环境变量,还有一些与bash操作接口有关的变量,以及用户自己定义的变量存在的。那么这些变量如何查看呢?这个时候就得要使用set这个命令了。set除了环境变量之外,还会将bash内的其他变量全部显示出来。下面鸟哥仅列出几个重要的内容:

[root@www ~]# set

BASH=/bin/bash  <== bash 的主程序放置路径

BASH_VERSINFO=([0]="3" [1]="2" [2]="25" [3]="1" [4]="release"

[5]="i686-redhat-linux-gnu")  <== bash 的版本。

BASH_VERSION='3.2.25(1)-release' <== 也是 bash 的版本。

COLORS=/etc/DIR_COLORS.xterm  <== 使用的颜色记录文件

COLUMNS=115      <== 在目前的终端机环境下,使用的字段有几个字符长度

HISTFILE=/root/.bash_history  <== 历史命令记录的放置文件,隐藏文件

HISTFILESIZE=1000  <== 保存的(与上个变量有关)的文件命令的最大记录条数。

HISTSIZE=1000   <== 目前环境下可记录的历史命令最大条数。

HOSTTYPE=i686   <== 主机安装的软件主要类型。我们用的是 i686 兼容机器软件

IFS=$' \t\n'   <== 默认的分隔符

LINES=35    <== 目前的终端机下的最大行数

MACHTYPE=i686-redhat-linux-gnu <== 安装的机器类型

MAILCHECK=60   <== 与邮件有关。每 60 秒去扫描一次信箱有无新信。

OLDPWD=/home   <== 上个工作目录。我们可以用 cd - 来使用这个变量。

OSTYPE=linux-gnu  <== 操作系统的类型。

PPID=20025   <== 父进程的 PID (会在后续章节才介绍)

PS1='[\u@\h \W]\$ ' <== PS1 就厉害了。这个是命令提示符,也就是我们常见的

[root@www ~]# 或 [dmtsai ~]$ 的设置值。可以改动的。

PS2='> '    <== 如果你使用转义符号 (\) 第二行以后的提示符也可以被列出来。

name=VBird   <== 刚才设置的自定义变量

$     <== 目前这个 shell 所使用的 PID

?     <== 刚才执行完命令的回传码。

一般来说,不论是否为环境变量,只要跟我们目前这个shell的操作接口有关的变量,通常都会被设置为大写字符,也就是说,基本上,在Linux默认的情况中,使用{大写的字母}来设置的变量一般为系统内定需要的变量。那么上面那些变量当中,有哪些是比较重要的?大概有这几个吧!

PS1(提示符的设置)

这是PS1(数字的1,不是英文字母),这个东西就是我们的“命令提示符”。当我们每次按下[Enter]按键去执行某个命令后,最后要再次出现提示符时,就会主动去读取这个变量值了。上面 PS1 内显示的是一些特殊符号,这些特殊符号可以显示不同的信息,每个 distributions的 bash 默认的 PS1 变量内容可能有些区别,你可以用 man bash [4]查询一下 PS1 的相关说明,以理解下面的一些符号意义。

\d:可显示出“星期月日”的日期格式,如“Mon Feb 2”。

\H:完整的主机名。举例来说,鸟哥的练习机为“www.vbird.tsai”。

\h:仅取主机名在第一个小数点之前的名字,如鸟哥主机则为“www”,后面的省略。

\t:显示时间,为24小时格式的“HH:MM:SS”。

\T:显示时间,为12小时格式的“HH:MM:SS”。

\A:显示时间,为24小时格式的“HH:MM”。

\@:显示时间,为12小时格式的“am/pm”样式。

\u:目前用户的账号名称,如“root”。

\v:BASH的版本信息,如鸟哥的测试主版本为3.2.25(1),仅取“3.2”显示。

\w:完整的工作目录名称,由根目录写起的目录名称。但主文件夹会以~替代。

\W:利用basename函数取得工作目录名称,所以仅会列出最后一个目录名。

#:执行的第几个命令。

\$:提示符,如果是root时,提示符为#,否则就是$。

好了让我们来看看 CentOS 默认的 PS1 内容:[\u@\h \W]\$,现在你知道那些反斜杠后的数据意义了吧?要注意,那个反斜杠后的数据为 PS1 的特殊功能,与 bash 的变量设置没关系。不要搞混了。那你现在知道为何你的命令提示符是“[root@www~]#”了吧?好了,那么假设我想要有类似下面的提示符:

[root@www /home/dmtsai 16:50 #12]#

那个#代表第12次执行的命令。那么应该如何设置PS1呢?可以这样:

[root@www ~ ]# cd /home

[root@www home]# PS1='[\u@\h \w \A ##]\$ '

[root@www /home 17:02 #85]#

看到了吗?提示符变了。变得很有趣吧!其中,那个 #85 比较有趣,

如果你再随便输入几次 ls 后,该数字就会增加。为什么?上面有说明。

$(关于本shell的PID)

“$”本身也是个变量。这个代表的是目前这个Shell的线程代号,即是所谓的PID(Process ID)。更多的进程观念,我们会在第四部分的时候提及。想要知道我们的shell的PID,用“echo $$”即可,出现的数字就是你的PID号码。

?(关于上个执行命令的回传码)

问号也是一个特殊的变量?没错!在bash里面这个变量很重要。这个变量是上一个执行的命令所回传的值,上面这句话的重点是“上一个命令”与“回传值”两个地方。当我们执行某些命令时,这些命令都会回传一个执行后的代码。一般来说,如果成功执行该命令,则会回传一个0值,如果执行过程发生错误,就会回传“错误代码”才对。一般就是以非0的数值来替代。我们以下面的例子来说明:

[root@www ~]# echo $SHELL

/bin/bash        <==可顺利显示,没有错误。

[root@www ~]# echo $?

0          <==因为没问题,所以回传码为 0

[root@www ~]# 12name=VBird

-bash: 12name=VBird: command not found <==发生错误了,bash回报有问题

[root@www ~]# echo $?

127         <==因为有问题,回传错误代码(非0)

错误代码回传码依据软件而有不同,我们可以利用这个代码来找出错误的原因。

[root@www ~]# echo $?

0

怎么又变成正确了?这是因为“?”只与“上一个执行命令”有关,

所以,我们上一个命令是执行“echo $? ”,当然没有错误,所以是 0 没错。

OSTYPE,HOSTTYPE,MACHTYPE(主机硬件与内核的等级)

我们在第0章计算机概论内的CPU等级说明中谈过CPU,目前个人计算机的CPU主要分为32、64位,其中32位又可分为i386、i586、i686,而64位则称为x86_64。由于不同等级的CPU命令集不太相同,因此你的软件可能会针对某些CPU进行优化,以求取较佳的软件性能。所以软件就有i386、i686及x86_64之分。以目前的主流硬件来说,几乎都是x86_64的天下。但是毕竟旧机器还是非常多,以鸟哥的环境来说,我用P-III等级的计算机,所以上面就发现我的等级是i686。

要留意的是,较高级的硬件通常会向下兼容旧有的软件,但较高级的软件可能无法在旧机器上面安装。我们在第3章就曾说明过,这里再强调一次,你可以在x86_64的硬件上安装i386的Linux操作系统,但是你无法在i686的硬件上安装x86_64的Linux操作系统。这点得要牢记。

export:自定义变量转成环境变量

谈了env与set现在知道有所谓的环境变量与自定义变量,那么这两者之间有什么差异呢?其实这两者的差异在于该变量是否会被子进程所继续引用。那么什么是父进程?子进程?这就得要了解一下命令的执行行为了。

当你登录Linux并取得一个bash之后,你的bash就是一个独立的进程,被称为PID的就是。接下来你在这个bash下面所执行的任何命令都是由这个bash所衍生出来的,那些被执行的命令就被称为子进程了。我们可以用下面的图示来简单说明一下父进程与子进程的概念。

figure_0322_0251

图11-4 进程相关性示意图

如图11-4所示,我们在原本的bash下面执行另一个bash,结果操作的环境接口会跑到第二个bash去(就是子进程),那原本的bash就会处于暂停的情况(就是sleep)。整个命令运行的环境是实线的部分。若要回到原本的bash去,就只有将第二个bash结束掉(执行exit或logout)才行。更多的程序概念我们会在第四部分谈及,这里只要有这个概念即可。

这个程序概念与变量有什么关系啊?关系可大了!因为子进程仅会继承父进程的环境变量,子进程不会继承父进程的自定义变量,所以你原本bash中的自定义变量在进入了子进程后就会消失不见,一直到你离开子进程并回到原本的父进程后,这个变量才会又出现。

换个角度来说,如果我能将自定义变量变成环境变量的话,那不就可以让该变量值继续存在于子进程了?没错!此时那个export命令就很有用了。如你想要让该变量内容继续在子进程中使用,那么就请执行:

[root@www ~]# export 变量名称

在引用自己的变量设置给后来调用的文件或其他程序时,像鸟哥经常在自己的主控文件后面调用其他附属文件(类似函数的功能),但是主控文件与附属文件内都有相同的变量名称,若一再重复设置时,要修改也很麻烦,此时只要在原本的第一个文件内设置好export变量,后面所调用的文件就能够使用这个变量设置了,而不需要重复设置,这非常适用于 shell script当中。如果仅执行export而没有接变量时,那么此时将会把所有的“环境变量”显示出来。例如:

[root@www ~]# export

declare -x HISTSIZE="1000"

declare -x HOME="/root"

declare -x HOSTNAME="www.vbird.tsai"

declare -x INPUTRC="/etc/inputrc"

declare -x LANG="en_US"

declare -x LOGNAME="root"

后面的鸟哥就都直接省略了。不然….浪费版面~ ^_^

那如何将环境变量转成自定义变量呢?可以使用本章后续介绍的declare。

11.2.4 影响显示结果的语系变量(locale)

还记得我们在第5章里面提到的语系问题吗?就是当我们使用man command的方式去查询某个数据的说明文件时,该说明文件的内容可能会因为我们使用的语系不同而产生乱码。另外利用ls查询文件的时间时,也可能会有乱码出现在时间的部分。那就是语系的问题了。

目前大多数的 Linux distributions 已经都是支持日渐流行的各国及地区代码了,也都支持大部分的语系。这有赖于i18n [5]支持的帮助。那么我们的Linux到底支持了多少的语系呢?这可以由locale这个命令来查询到。

[root@www ~]# locale -a

….(前面省略)….

zh_CN

zh_CN.big5

zh_CN.euctw

zh_CN.utf8

zu_ZA

zu_ZA.iso88591

zu_ZA.utf8

繁体中文语系至少支持了两种以上的编码,一种是目前还是很常见的big5,另一种则是越来越热门的utf-8编码。那么我们如何修订这些编码呢?其实可以通过下面这些变量:

[root@www ~]# locale  <==后面不加任何参数即可

LANG=en_US    <==主语言的环境

LC_CTYPE="en_US"   <==字符(文字)辨识的编码

LC_NUMERIC="en_US"  <==数字系统的显示信息

LC_TIME="en_US"   <==时间系统的显示数据

LC_COLLATE="en_US"  <==字符串的比较与排序等

LC_MONETARY="en_US"  <==币值格式的显示等

LC_MESSAGES="en_US"  <==信息显示的内容,如菜单、错误信息等

LC_ALL=     <==整体语系的环境

….(后面省略)….

基本上,你可以逐一设置每个与语系有关的变量数据,但事实上如果其他的语系变量都未设置,且你有设置LANG或者是LC_ALL时,则其他的语系变量就会被这两个变量所替代。这也是为什么我们在Linux当中,通常说明仅设置LANG这个变量而已,因为它是最主要的设置变量。好了那么你应该要觉得奇怪的是,为什么在 Linux 主机的终端机接口( tty1~tty6 )的环境下,如果设置“LANG=zh_TW.big5”这个设置值生效后,使用 man 或者其他信息输出时,都会有一堆乱码,尤其是使用ls-l这个参数时。

因为在Linux主机的终端机接口环境下是无法显示像中文这么复杂的编码文字,所以就会产生乱码了。也就是如此我们才会必须要在 tty1~tty6 的环境下,装一些中文化接口的软件,才能够看到中文。不过如果你是在Windows主机中以远程连接服务器的软件连接到主机的话,那么其实命令行界面确实是可以看到中文的。所以此时你要在LANG设置中文编码。

无论如何,如果发生一些乱码的问题,那么设置系统里面保有的语系编码,例如:en_US或 en_US.utf8 等的设置,那么系统默认支持多少种语系呢?当我们使用 locale 时,系统是列出目前 Linux 主机内保有的语系文件,这些语系文件都放置在/usr/lib/locale/ 这个目录中。

你当然可以让每个用户自己去调整自己喜好的语系,但是整体系统默认的语系定义在哪里呢?其实就是在/etc/sysconfig/i18n。这个文件在 CentOS 5.x 的内容有点像这样:

[root@www ~]# cat /etc/sysconfig/i18n

LANG="zh_CN.UTF-8"

因为鸟哥在第4章的安装时选择的是中文语系安装界面,所以这个文件默认就会使用中文编码。你也可以自行将它改成你想要的语系编码即可。

假设您有一个纯文本文件原本是在 Windows 下面创建的,那么这个文件默认应该是gb2312 的编码格式。在您将这个文件上传到 Linux 主机后,在 X Window 下面打开时,怎么中文全部变成乱码了?别担心。您只要将打开该文件的软件编码由 utf8 改成 gb2312就能够看到正确的中文了

11.2.5 变量的有效范围

变量也有使用的“范围”?我们在上面的export命令说明中,就提到了这个概念了。如果在跑程序的时候,有父进程与子进程的不同程序关系时,则“变量”可否被引用与 export 有关。被 export后的变量,我们可以称它为“环境变量”。环境变量可以被子进程所引用,但是其他的自定义变量内容就不会存在于子进程中。

在某些不同的书籍中会谈到“全局变量”(global variable)与“局部变量”(local variable)。基本上您可以这样区别:

环境变量=全局变量

自定义变量=局部变量

为什么环境变量的数据可以被子进程所引用呢?这是因为内存配置的关系。理论上是这样的:

当启动一个shell,操作系统会分配一记忆块给shell使用,此内存内的变量可让子进程取用;

若在父进程利用export功能,可以让自定义变量的内容写到上述的记忆块当中(环境变量);

当加载另一个shell时(即启动子进程,而离开原本的父进程了),子shell可以将父shell的环境变量所在的记忆块导入自己的环境变量块当中。

通过这样的关系,我们就可以让某些变量在相关的进程之间存在,以帮助自己更方便地操作环境。不过要提醒的是,这个“环境变量”与“bash的操作环境”意思不太一样,举例来说,PS1并不是环境变量,但是这个PS1会影响到bash的接口(提示符)。

11.2.6 变量键盘读取、数组与声明:read,array,declare

我们上面提到的变量设置功能都是由命令行直接设置的,那么可不可以让用户能够经由键盘输入?什么意思呢?是否记得某些程序执行的过程当中,会等待用户输入“yes/no”之类的信息?在bash里面也有相对应的功能。此外我们还可以声明这个变量的属性,例如数组或者是数字等。下面就来介绍。

read

要读取来自键盘输入的变量,就是用 read 这个命令。这个命令最常被用在 shell script 的编写当中,想要跟用户对谈?用这个命令就对了。关于 script 的写法,我们会在第 13 章介绍,下面先来瞧一瞧read的相关语法。

[root@www ~]# read [-pt] variable

参数:

-p :后面可以接提示符。

-t :后面可以接等待的“秒数。”这个比较有趣,不会一直等待用户。

范例一:让用户由键盘输入内容,将该内容变成名为 atest 的变量

[root@www ~]# read atest

This is a test  <==此时光标会等待你输入。请输入左侧文字看看

[root@www ~]# echo $atest

This is a test  <==你刚才输入的数据已经变成一个变量内容。

范例二:提示用户 30 秒内输入自己的大名,将该输入字符串作为名为 named 的变量内容

[root@www ~]# read -p "Please keyin your name: " -t 30 named

Please keyin your name: VBird Tsai <==注意看,会有提示符。

[root@www ~]# echo $named

VBird Tsai  <==输入的数据又变成一个变量的内容了。

read 之后不加任何参数,直接加上变量名称,那么下面就会主动出现一个空白行等待你的输入(如范例一)。如果加上-t后面接秒数,例如上面的范例二,那么30秒之内没有任何操作时,该命令就会自动略过了,如果是加上-p,在输入的光标前就会有比较多可以用的提示符给我们参考。在命令的执行里面,这样比较美观。

declare / typeset

declare或typeset是一样的功能,就是声明变量的类型。如果使用declare后面并没有接任何参数,那么bash就会主动将所有的变量名称与内容全部调出来,就好像使用set一样。我们来看看declare的语法:

[root@www ~]# declare [-aixr] variable

参数:

-a :将后面名为 variable 的变量定义成为数组(array)类型

-i :将后面名为 variable 的变量定义成为整数数字(integer)类型

-x :用法与 export 一样,就是将后面的 variable 变成环境变量

-r :将变量设置成为 readonly 类型,该变量不可被更改内容,也不能重设

范例一:让变量 sum 进行 100+300+50 累加结果

[root@www ~]# sum=100+300+50

[root@www ~]# echo $sum

100+300+50 <==怎么没有帮我计算加总?因为这是文字类型的变量属性。

[root@www ~]# declare -i sum=100+300+50

[root@www ~]# echo $sum

450  <==明白了吗?

由于在默认的情况下面,bash对于变量有几个基本的定义:

变量类型默认为“字符串”,所以若不指定变量类型,则1+2为一个“字符串”而不是“计算式”,所以上述第一个执行的结果才会出现那个情况的;

bash环境中的数值运算,默认最多仅能到达整数类型,所以1/3结果是0。

现在你知道为什么需要进行变量声明了吧?如果需要非字符串类型的变量,那就得要进行变量的声明才行。下面继续来熟悉其他的声明功能。

范例二:将 sum 变成环境变量

[root@www ~]# declare -x sum

[root@www ~]# export | grep sum

declare -ix sum="450" <==果然出现了。包括i 与 x 的声明。

范例三:让 sum 变成只读属性,不可改动。

[root@www ~]# declare -r sum

[root@www ~]# sum=tesgting

-bash: sum: readonly variable <==不能改这个变量了。

范例四:让 sum 变成非环境变量的自定义变量。

[root@www ~]# declare +x sum <== 将 - 变成 + 可以进行“取消”操作

[root@www ~]# declare -p sum <== -p 可以单独列出变量的类型

declare -ir sum="450" <== 看吧!只剩下 i, r 的类型,不具有 x 了。

declare 也是个很有用的功能,尤其是当我们需要使用到下面的数组功能时,它也可以帮我们声明数组的属性。不过数组也是在 shell script 中比较常用的。比较有趣的是,如果你不小心将变量设置为“只读”,通常得要注销再登录才能复原该变量的类型了。

数组(array)变量类型

某些时候,我们必须使用数组来声明一些变量,这有什么好处啊?在一般人的使用上,果然是看不出来有什么好处的。不过如果你曾经写过程序的话,那才会比较了解数组的意义,数组对写数值程序的程序员来说,可是不能错过学习的重点之一。那么要如何设置数组的变量与内容呢?在bash里头,数组的设置方式是:

var[index] = content

意思是说,我有一个数组名为 var,而这个数组的内容为 var[1] = “small min”,var[2] = “big min”,var[3]=“nice min”,等等,那个 index 就是一些数字,重点是用中刮号([ ])来设置的。目前我们bash提供的是一维数组。老实说,如果你不必写一些复杂的程序,那么这个数组的地方可以先略过,等到有需要再来学习即可,因为要制作出数组,通常与循环或者其他判断式交互使用才有比较高的存在意义。

范例:设置上面提到的 var[1] ~ var[3] 的变量。

[root@www ~]# var[1]="small min"

[root@www ~]# var[2]="big min"

[root@www ~]# var[3]="nice min"

[root@www ~]# echo "${var[1]}, ${var[2]}, ${var[3]}"

small min, big min, nice min

数组的变量类型比较有趣的地方在于“读取”,一般来说,建议直接以${数组}的方式来读取,会比较正确无误。

11.2.7 与文件系统及程序的限制关系:ulimit

想象一个状况:我的Linux主机里面同时登录了十个人,这十个人不知怎么搞的,同时打开了100个文件,每个文件的大小约 10MB,请问一下,我的 Linux 主机的内存要有多大才够?10×100×10 = 10000MB = 10GB⋯⋯天哪,这样,系统不挂掉才怪。为了要预防这个情况的发生,所以我们的bash是可以限制用户的某些系统资源的,包括可以打开的文件数量、可以使用的 CPU 时间、可以使用的内存总量等。如何设置?用ulimit吧。

[root@www ~]# ulimit [-SHacdfltu] [配额]

参数:

-H :hard limit ,严格的设置,必定不能超过这个设置的数值。

-S :soft limit ,警告的设置,可以超过这个设置值,但是若超过则有警告信息。

在设置上,通常 soft 会比 hard 小,举例来说,soft 可设置为 80 而 hard

设置为 100,那么你可以使用到 90 (因为没有超过 100),但介于 80~100 之间时,

系统会有警告信息通知你。

-a :后面不接任何参数,可列出所有的限制额度。

-c :当某些进程发生错误时,系统可能会将该进程在内存中的信息写成文件(排错用),

这种文件就被称为内核文件(core file)。此为限制每个内核文件的最大容量。

-f :此 shell 可以创建的最大文件容量(一般可能设置为 2GB)单位为 KB。

-d :进程可使用的最大断裂内存(segment)容量。

-l :可用于锁定 (lock) 的内存量。

-t :可使用的最大 CPU 时间 (单位为秒)。

-u :单一用户可以使用的最大进程(process)数量。

范例一:列出你目前身份(假设为root)的所有限制数据数值

[root@www ~]# ulimit -a

core file size (blocks, -c) 0  <==只要是 0 就代表没限制

data seg size  (kbytes, -d) unlimited

scheduling priority  (-e) 0

file size  (blocks, -f) unlimited <==可创建的单一文件的大小

pending signals  (-i) 11774

max locked memory (kbytes, -l) 32

max memory size (kbytes, -m) unlimited

open files   (-n) 1024  <==同时可打开的文件数量

pipe size  (512 bytes, -p) 8

POSIX message queues (bytes, -q) 819200

real-time priority  (-r) 0

stack size  (kbytes, -s) 10240

cpu time  (seconds, -t) unlimited

max user processes  (-u) 11774

virtual memory (kbytes, -v) unlimited

file locks   (-x) unlimited

范例二:限制用户仅能创建立 10MB以下的容量的文件

[root@www ~]# ulimit -f 10240

[root@www ~]# ulimit -a

file size  (blocks, -f) 10240 <==最大量为10240KB,相当10MB

[root@www ~]# dd if=/dev/zero of=123 bs=1M count=20

File size limit exceeded <==尝试创建 20MB 的文件,结果失败了。

还记得我们在第 8 章 Linux 磁盘文件系统里面提到过,单一文件系统能够支持的单一文件大小与 block 的大小有关。例如 block size 为 1024 byte 时,单一文件可达 16GB 的容量。但是,我们可以用 ulimit 来限制用户可以创建的文件大小。利用 ulimit -f 就可以来设置了。例如上面的范例二,要注意单位。单位是 KB。若改天你一直无法创建一个大容量的文件,记得查看 ulimit 的信息。

想要复原 ulimit 的设置最简单的方法就是注销再登录,否则就是得要重新以 ulimit设置才行。不过要注意的是一般身份用户如果以 ulimit 设置了 -f 的文件大小,那么他只能继续减小文件容量,不能增加文件容量。另外若想要管控用户的 ulimit 限值,可以参考第14 章的 pam 的介绍。

11.2.8 变量内容的删除、替代与替换

变量除了可以直接设置来修改原本的内容之外,有没有办法通过简单的操作来将变量的内容进行微调呢?举例来说,进行变量内容的删除与替换等是可以的。我们可以通过几个简单的小步骤来进行变量内容的微调。下面就来试试看。

变量内容的删除与替换

变量的内容可以很简单地通过几个命令来进行删除。我们使用PATH 这个变量的内容来做测试好了。请你依序进行下面的几个例子来操作,比较容易感受得到鸟哥在这里想要表达的意义:

范例一:先让小写的 path设置得与 PATH 内容相同

[root@www ~]# path=${PATH}

[root@www ~]# echo $path

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:

/usr/sbin:/usr/bin:/root/bin <==这两行其实是同一行。

范例二:假设我不喜欢 kerberos,所以要将前两个目录删除掉,如何显示?

[root@www ~]# echo ${path#/*kerberos/bin:}

/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

上面这个范例很有趣的。它的重点可以说明如下:

${variable#/*kerberos/bin:}

上面的特殊字体部分是关键字。用在这种删除模式是必须存在的

${variable#/*kerberos/bin:}

这就是原本的变量名称,以上面范例二来说,这里就填写 path 这个“变量名称”。

${variable#/*kerberos/bin:}

这是重点,代表从变量内容的最前面开始向右删除,且仅删除最短的那个

${variable#/*kerberos/bin:}

代表要被删除的部分,由于 # 代表由前面开始删除,所以这里便由开始的 / 写起。

需要注意的是,我们还可以通过通配符 * 来替代 0 到无穷多个任意字符

以上面范例二的结果来看,path 这个变量被删除的内容如下所示:

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:

/usr/sbin:/usr/bin:/root/bin <==这两行其实是同一行。

很有趣吧?这样了解了#的功能了吗?接下来让我们来看看下面的范例三。

范例三:我想要删除前面所有的目录,仅保留最后一个目录

[root@www ~]# echo ${path#/*:}

/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:

/root/bin <==这两行其实是同一行。

由于一个 # 仅删除掉最短的那个,因此它删除的情况可以用下面的删除线来看:

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:

/usr/sbin:/usr/bin:/root/bin <==这两行其实是同一行。

[root@www ~]# echo ${path##/*:}

/root/bin

多加了一个 # 变成 ## 之后,它变成删除掉最长的那个数据,即是:

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:

/usr/sbin:/usr/bin:/root/bin <==这两行其实是同一行。

非常有趣!不是吗?因为在PATH这个变量的内容中,每个目录都是以冒号“:”隔开的,所以要从头删除掉目录就是介于斜线(/)到冒号(:)之间的数据。但是PATH中不只一个冒号(:),所以#与##就分别代表:

:符合替换文字的“最短的”那一个;

:符合替换文字的“最长的”那一个。

上面谈到的是从前面开始删除变量内容,那么如果想要从后面向前删除变量内容呢?这个时候就得使用百分比(%)符号了。来看看范例四怎么做的吧。

范例四:我想要删除最后面那个目录,即从“:”到 bin 为止的字符串

[root@www ~]# echo ${path%:*bin}

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:

/usr/sbin:/usr/bin <==注意,最后面一个目录不见了。

这个 % 符号代表由最后面开始向前删除,所以上面得到的结果其实是来自如下:

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:

/usr/sbin:/usr/bin:/root/bin <==这两行其实是同一行。

范例五:那如果我只想要保留第一个目录呢?

[root@www ~]# echo ${path%%:*bin}

/usr/kerberos/sbin

同样,%% 代表的则是最长的符合字符串,所以结果其实是来自如下:

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:

/usr/sbin:/usr/bin:/root/bin <==这两行其实是同一行啦。

由于我是想要由变量内容的后面向前面删除,而我这个变量内容最后面的结尾是“/root/bin”,所以你可以看到上面我删除的数据最终一定是“bin”,即是“:bin”那个代表通配符。至于%与%%的意义其实与#及##类似。这样是否可以理解?

figure_0329_0252

假设你是root,那你的MAIL变量应该是/var/spool/mail/root。假设你只想要保留最后面那个文件名(root),前面的目录名称都不要了,如何利用$MAIL变量来完成?

答:题意其实是这样: /var/spool/mail/root,即删除掉两条斜线间的所有数据(最长符合的)。这个时候你这样做即可:

[root@www ~]# echo ${MAIL##/*/}

相反,如果你只想要去掉文件名,保留目录的名称,即是“/var/spool/mail/root”(最短符合的)。但假设你并不知道结尾的字母,此时你可以利用通配符来处理,如下所示:

[root@www ~]# echo ${MAIL%/*}

了解了删除功能后,接下来谈谈替换吧!继续看看范例六。

范例六:将 path 的变量的 sbin 替代成大写 SBIN:

[root@www ~]# echo ${path/sbin/SBIN}

/usr/kerberos/SBIN:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:

/usr/sbin:/usr/bin:/root/bin

这个部分就容易理解得多了。关键字在于那两个斜线,两个斜线中间的是旧字符串

后面的是新字符串,所以结果就会出现如上述的特殊字体部分。

[root@www ~]# echo ${path//sbin/SBIN}

/usr/kerberos/SBIN:/usr/kerberos/bin:/usr/local/SBIN:/usr/local/bin:/SBIN:/bin:

/usr/SBIN:/usr/bin:/root/bin

如果是两条斜线,那么就变成所有符合的内容都会被替代。

我们将这部分作个总结说明一下,如表11-1所示。

表11-1

figure_0330_0253

变量的测试与内容替换

在某些时刻我们经常需要“判断”某个变量是某存在,若变量存在则使用既有的设置,若变量不存在则给予一个常用的设置。我们举下面的例子来说明好了,看看你能不能比较容易理解。

范例一:测试一下是否存在 username 这个变量,若不存在则给予 username 内容为 root

[root@www ~]# echo $username

<==由于出现空白,所以 username 可能不存在,也可能是空字符串

[root@www ~]# username=${username-root}

[root@www ~]# echo $username

root  <==因为 username 没有设置,所以主动给予名为 root 的内容。

[root@www ~]# username="vbird tsai" <==主动设置 username 的内容

[root@www ~]# username=${username-root}

[root@www ~]# echo $username

vbird tsai <==因为 username 已经设置了,所以使用旧有的设置而不以 root 替替代

在上面的范例中,重点在于减号“-”后面接的关键字。基本上你可以这样理解:

new_var=${old_var-content}

新的变量,主要用来替换旧变量。新旧变量名称其实经常是一样的

new_var=${old_var-content}

这是本范例中的关键字部分。必须要存在的。

new_var=${old_var-content}

旧的变量,被测试的选项。

new_var=${old_var-content}

变量的“内容”,在本范例中,这个部分是在给予未设置变量的内容

不过这还是有点问题。因为username可能已经被设置为空字符串了。要是这样的话,那你还可以使用下面的范例来将username的内容设为root。

范例二:若 username 未设置或为空字符串,则将username内容设置为root

[root@www ~]# username=""

[root@www ~]# username=${username-root}

[root@www ~]# echo $username

<==因为 username 被设置为空字符串了,所以当然还是保留为空字符串。

[root@www ~]# username=${username:-root}

[root@www ~]# echo $username

root <==加上“: ”后若变量内容为空或者是未设置,都能够以后面的内容替换。

在大括号内有没有冒号“:”的差别是很大的。加上冒号后,被测试的变量未被设置或者是已被设置为空字符串时,都能够用后面的内容(本例中是使用root为内容)来替换与设置。这样可以了解了吗?除了这样的测试之外,还有其他的测试方法。鸟哥将它整理如表11-2所示。

下面的例子当中,那个var与str为变量,我们想要针对str是否有设置来决定var的值。一般来说,str:代表str没设置或为空的字符串时,至于str则仅表示没有该变量。

表11-2

figure_0331_0254

根据上面这张表,我们来进行几个范例的练习吧!首先让我们来测试一下,如果旧变量(str)不存在时,我们要给予新变量一个内容,若旧变量存在,则新变量内容以旧变量来替换,结果如下:

测试:先假设 str 不存在 (用 unset) ,然后测试一下等号 (-) 的用法

[root@www ~]# unset str; var=${str-newvar}

[root@www ~]# echo var="$var", str="$str"

var=newvar, str=  <==因为 str 不存在,所以 var 为 newvar

测试:若 str 已存在,测试一下 var 会变怎样

[root@www ~]# str="oldvar"; var=${str-newvar}

[root@www ~]# echo var="$var", str="$str"

var=oldvar, str=oldvar <==因为 str 存在,所以 var 等于 str 的内容

关于减号(-)其实上面我们谈过了。这里的测试只是要让你更加了解,这个减号的测试并不会影响到旧变量的内容。如果你想要将旧变量内容也一起替换掉的话,那么就使用等号 (=)。

测试:先假设 str 不存在 (用 unset) ,然后测试一下等号 (=) 的用法

[root@www ~]# unset str; var=${str=newvar}

[root@www ~]# echo var="$var", str="$str"

var=newvar, str=newvar <==因为 str 不存在,所以 var/str 均为 newvar

测试:如果 str 已存在了,测试一下 var 会变怎样

[root@www ~]# str="oldvar"; var=${str=newvar}

[root@www ~]# echo var="$var", str="$str"

var=oldvar, str=oldvar <==因为 str 存在,所以 var 等于 str 的内容

那如果我只是想知道,如果旧变量不存在时,整个测试就告知我“有错误”,此时就能够使用问号“? ”。下面这个测试练习一下先。

测试:若 str 不存在时,则 var 的测试结果直接显示“无此变量”

[root@www ~]# unset str; var=${str?无此变量}

-bash: str: 无此变量 <==因为 str 不存在,所以输出错误信息

测试:若 str 存在时,则 var 的内容会与 str 相同。

[root@www ~]# str="oldvar"; var=${str?novar}

[root@www ~]# echo var="$var", str="$str"

var=oldvar, str=oldvar <==因为 str 存在,所以 var 等于 str 的内容

基本上这种变量的测试也能够通过 shell script 内的 if…then…来处理,不过既然 bash 有提供这么简单的方法来测试变量,那我们也可以多学一些。不过这种变量测试通常是在程序设计当中比较容易出现,如果这里看不懂就先略过,将来有用到判断变量值时再回来查看吧!

11.3 命令别名与历史命令

我们知道在早期的DOS年代,清除屏幕上的信息可以使用cls来清除,但是在Linux里面,我们则是使用 clear 来清除界面的。那么可否让 cls 等于 clear 呢?可以啊!用什么方法?link file 还是什么的?别急!下面我们介绍不用 link file 的命令别名来完成。那么什么又是历史命令?曾经做过的举动我们可以将它记录下来,那就是历史命令,下面分别来谈一谈这两个命令。

11.3.1 命令别名设置:alias,unalias

命令别名是一个很有趣的东西,特别是你的惯用命令特别长的时候。还有增设默认的选项在一些惯用的命令上面,可以预防一些不小心误删文件的情况发生的时候。举个例子来说,如果你要查询隐藏文件,并且需要长的列出与一页一页翻看,那么需要执行“ls-al| more”这个命令,我是觉得很烦,要输入好几个字符。那可不可以使用lm来简化呢?当然可以!你可以在命令行下面执行:

[root@www ~]# alias lm='ls -l | more'

立刻多出了一个可以执行的命令。这个命令名称为 lm,其实它是执行 ls -al| more。不过,要注意的是:alias 的定义规则与变量定义规则几乎相同,所以你只要在 alias 后面加上你的{"别名"='命令参数…'},以后你只要输入 lm 就相当于输入了 ls – al| more 这一串命令,很方便。

另外命令别名的设置还可以替代既有的命令。举例来说,我们知道root可以删除(rm)任何数据。所以当你以root的身份在进行工作时,需要特别小心,但是总有失手的时候,那么rm提供了一个参数来让我们确认是否要删除该文件,那就是-i这个参数。所以你可以这样做:

[root@www ~]# alias rm='rm -i'

那么以后使用 rm 的时候,就不用太担心会有错误删除的情况了。这也是命令别名的优点。那么如何知道目前有哪些的命令别名呢?就使用alias呀!

[root@www ~]# alias

alias cp='cp -i'

alias l.='ls -d .* —color=tty'

alias ll='ls -l —color=tty'

alias lm='ls -l | more'

alias ls='ls —color=tty'

alias mv='mv -i'

alias rm='rm -i'

alias which='alias | /usr/bin/which —tty-only —show-dot —show-tilde'

由上面的数据当中,你也会发现一件事情,我们在第10章的vim程序编辑器里面提到vi与vim是不太一样的,vim可以多做一些额外的语法检验与颜色显示,默认的root是单纯使用vi而已。如果你想要使用 vi 就直接以 vim 来打开文件的话,使用“alias vi='vim'”这个设置即可。至于如果要取消命令别名的话,那么就使用unalias。例如要将刚才的lm命令别名去掉,就使用:

[root@www ~]# unalias lm

那么命令别名与变量有什么不同呢?命令别名是新创一个新的命令,你可以直接执行该命令的,至于变量则需要使用类似“echo”命令才能够调用变量的内容。这两者当然不一样。很多初学者在这里老是搞不清楚。要注意。

figure_0333_0255

DOS年代,列出目录与文件就是dir,而清除屏幕就是cls,那么如果我想要在linux里面也使用相同的命令呢?

答:很简单,通过clear与ls来进行命令别名的构建:

alias cls='clear'

alias dir='ls -l'

11.3.2 历史命令:history

前面我们提过 bash 有提供命令历史的服务。那么如何查询我们曾经执行过的命令呢?就使用history。当然如果觉得histsory要输入的字符太多太麻烦,可以使用命令别名来设置呢。不要跟我说还不会设置。

[root@www ~]# alias h='history'

如此则输入h等于输入history。好了我们来谈一谈history的用法吧!

[root@www ~]# history [n]

[root@www ~]# history [-c]

[root@www ~]# history [-raw] histfiles

参数:

n :数字,是要列出最近的 n 条命令行的意思。

-c :将目前的 shell 中的所有 history 内容全部消除。

-a :将目前新增的 history 命令新增入 histfiles 中,若没有加 histfiles ,

则默认写入 ~/.bash_history。

-r :将 histfiles 的内容读到目前这个 shell 的 history 记忆中。

-w :将目前的 history 记忆内容写入 histfiles 中。

范例一:列出目前内存内的所有 history 记忆

[root@www ~]# history

前面省略

1017 man bash

1018 ll

1019 history

1020 history

列出的信息当中,共分两列栏,第一列为该命令在这个 shell 当中的代码,

另一个则是命令本身的内容。至于会显示出几条命令记录,则与 HISTSIZE 有关。

范例二:列出目前最近的 3 条数据

[root@www ~]# history 3

1019 history

1020 history

1021 history 3

范例三:立刻将目前的数据写入 histfile 当中

[root@www ~]# history -w

在默认的情况下,会将历史记录写入 ~/.bash_history 当中。

[root@www ~]# echo $HISTSIZE

1000

在正常的情况下,历史命令的读取与记录是这样的:

当我们以bash登录Linux主机之后,系统会主动由主文件夹的~/.bash_history读取以前曾经下过的命令,那么~/.bash_history会记录几条数据呢?这就与你bash的HISTSIZE这个变量设置值有关了。

假设我这次登录主机后,共执行过100次命令,等我注销时,系统就会将101~1100这总共1000笔历史命令更新到~/.bash_history当中。也就是说,历史命令在我注销时,会将最近的HISTSIZE条记录到我的记录文件当中。

当然也可以用 history -w 强制立刻写入的。那为何用“更新”两个字呢?因为~/.bash_history记录的条数永远都是HISTSIZE那么多,旧的信息会被主动去掉。仅保留最新的。

那么history这个历史命令只可以让我查询命令而已吗?当然不止。我们可以利用相关的功能来帮我们执行命令呢。举例来说:

[root@www ~]# !number

[root@www ~]# !command

[root@www ~]# !!

参数:

number :执行第几条命令的意思;

command :由最近的命令向前搜寻命令串开头为 command的那个命令,并执行;

!! :就是执行上一个命令(相当于按↑按键后,按[Enter])

[root@www ~]# history

66 man rm

67 alias

68 man history

69 history

[root@www ~]# !66 <==执行第 66 笔命令

[root@www ~]# !! <==执行上一个命令,本例中即 !66

[root@www ~]# !al <==执行最近以 al 为开头的命令(上面列出的第 67 个)

经过上面的介绍,明白了吗?历史命令用法可多了!如果我想要执行上一个命令,除了使用上下键之外,我可以直接以“!! ”来执行上个命令的内容,此外我也可以直接选择执行第 n 个命令 “!n”来执行,也可以使用命令标头,例如“!vi”来执行最近命令开头是vi的命令行,相当方便而且好用。

基本上 history 的用途很大的。但是需要小心安全的问题,尤其是 root 的历史记录文件,这是Cracker的最爱。因为不小心root会将很多的重要数据在执行的过程中记录在~/.bash_history当中,如果这个文件被解析的话,后果不堪设想。无论如何,使用 history 配合“!”曾经使用过的命令执行是很有效率的一个命令执行方法。

同一账号同时多次登录的history写入问题

有些朋友在练习linux的时候喜欢同时开好几个bash接口,这些bash的身份都是root。这样会有~/.bash_history的写入问题吗?想一想,因为这些bash在同时以root的身份登录,因此所有的bash都有自己的1000笔记录在内存中。因为等到注销时才会更新记录文件,所以最后注销的那个bash才会是最后写入的数据。如此一来其他bash的命令操作就不会被记录下来了(其实有被记录,只是被后来的最后一个bash所覆盖更新了)。

由于多重登录有这样的问题,所以很多朋友都习惯单一 bash 登录,再用作业控制(job control,第四篇会介绍)来切换不同工作。这样才能够将所以曾经执行过的命令记录下来,也才方便将来系统管理员进行命令的调试。

无法记录时间

历史命令还有一个问题,那就是无法记录命令执行的时间。由于这1000条历史命令是依序记录的,但是并没有记录时间,所以在查询方面会有一些不方便。如果读者们感兴趣,其实可以通过~/.bash_logout来进行history的记录,并加上date来增加时间参数,也是一个可以应用的方向。有兴趣的朋友可以先看看情境模拟题一吧!

11.4 Bash Shell 的操作环境

是否记得我们登录主机的时候屏幕上会有一些说明文字,告知Linux版本之类的信息,并且登录的时候我们还可以提供用户一些信息或者欢迎文字呢。此外我们习惯的环境变量、命令别名等是否可以登录就主动帮我设置好?这些都是需要注意的。另外这些设置值又可以分为系统整体设置值与各人喜好设置值,仅是一些文件放置的地点不同。这我们后面也会来介绍。

11.4.1 路径与命令查找顺序

我们在第6章与第7章都曾谈过相对路径与绝对路径的关系,在本章的前几小节也谈到了alias 与bash的内置命令。现在我们知道系统里面其实有不少的ls命令,或者是包括内置的echo命令,那么来想一想,如果一个命令(例如ls)被执行时,到底是哪一个ls被拿来运行?基本上,命令运行的顺序可以这样看:

1.以相对/绝对路径执行命令,例如“/bin/ls”或“./ls”;

2.由alias找到该命令来执行;

3.由bash内置的(builtin)命令来执行;

4.通过$PATH这个变量的顺序找到的第一个命令来执行。

举例来说,你可以执行/bin/ls及单纯的ls看看,会发现使用ls有颜色但是/bin/ls则没有颜色,因为/bin/ls 是直接取用该命令来执行,而 ls 会因为“alias ls='ls —color=tty' ”这个命令别名而先使用。如果想要了解命令查找的顺序,其实通过 type -a ls 也可以查询得到。上述的顺序最好先了解。

figure_0335_0256

设置echo的命令别名成为echo-n,然后再查看echo执行的顺序。

答:

[root@www ~]# alias echo='echo -n'

[root@www ~]# type -a echo

echo is aliased to `echo -n'

echo is a shell builtin

echo is /bin/echo

很清楚吧?先alias再builtin,再由$PATH找到/bin/echo。

11.4.2 bash 的登录与欢迎信息:/etc/issue,/etc/motd

bash 也有登录界面与欢迎信息?真的啊!还记得在终端机接口(tty1~tty6)登录的时候,会有几行提示的字符串吗?那就是登录界面。那个字符串写在哪里呢?在/etc/issue里面。先来看看:

[root@www ~]# cat /etc/issue

CentOS release 5.3 (Final)

Kernel \r on an \m

鸟哥是以完全未更新过的 CentOS 5.3 作为范例,里面默认有 3行,较有趣的地方在于\r 与\m。就如同$PS1 这变量一样,issue 这个文件的内容也是可以使用反斜杠作为变量调用。你可以man issue配合man mingetty得到下面的结果,如表 11-3 所示。

表11-3

figure_0336_0257

做一下下面这个练习,看看能不能取得你要的登录界面?

figure_0336_0258

如果你在tty3的进站界面看到如下显示,该如何设置才能得到如下界面?

CentOS release 5.3 (Final) (terminal: tty3)

Date: 2009-02-05 17:29:19

Kernel 2.6.18-128.el5 on an i686

Welcome!

注意,tty3在不同的tty下有不同的显示,日期则是在按下[Enter]后就会所有不同。

答:很简单,参考上述的反斜杠功能去修改/etc/issue成为如下模样即可:

CentOS release 5.3 (Final) (terminal: \l)

Date: \d \t

Kernel \r on an \m

Welcome!

曾有鸟哥的学生在这个/etc/issue内修改数据,光是利用简单的英文字母作出属于他自己的登录界面,界面里面 坃有他的中文名字呢。也有学生做成类似很大一个的“ ”在登录界面,都确实非常有趣。

你要注意的是,除了/etc/issue 之外还有个/etc/issue.net呢!这是什么?这个是提供给 telnet 这个远程登录程序用的。当我们使用telnet连接到主机时,主机的登录界面就会显示/etc/issue.net而不是/etc/issue。

如果你想要让用户登录后取得一些信息,例如你想要让大家都知道的信息,那么可以将信息加入/etc/motd里面去。例如,当登录后,告诉登录者系统将会在某个固定时间进行维护工作,可以这样做:

[root@www ~]# vi /etc/motd

Hello everyone,

Our server will be maintained at 2009/02/28 0:00 ~ 24:00.

Please don't login server at that time. ^_^

那么当你的用户(包括所有的一般账号与root)登录主机后,就会显示这样的信息出来:

Last login: Thu Feb 5 22:35:47 2009 from 127.0.0.1

Hello everyone,

Our server will be maintained at 2009/02/28 0:00 ~ 24:00.

Please don't login server at that time. ^_^

11.4.3 bash 的环境配置文件

你是否会觉得奇怪,怎么我们什么操作都没有进行,但是一进入bash就取得一堆有用的变量了?这是因为系统有一些环境配置文件的存在,让bash在启动时直接读取这些配置文件,以规划好bash的操作环境。而这些配置文件又可以分为全体系统的配置文件以及用户个人偏好配置文件。要注意的是,我们前几个小节谈到的命令别名、自定义的变量在你注销 bash 后就会失效,所以你想要保留你的设置,就得要将这些设置写入配置文件才行。下面就让我们来聊聊!

login 与non-login shell

在开始介绍 bash 的配置文件前,我们一定要先知道的就是 login shell 与 non-login shell。重点在于有没有登录(login)。

login shell:取得bash 时需要完整的登录流程的,就称为login shell。举例来说,你要由tty1~tty6 登录,需要输入用户的账号与密码,此时取得的bash 就称为“login shell”。

non-login shell:取得 bash 接口的方法不需要重复登录的举动,举例来说,你以 X Window登录Linux后,再以X 的图形界面启动终端机,此时那个终端接口并没有需要再次输入账号与密码,那个bash 的环境就称为non-login shell 了。你在原本的bash 环境下再次执行bash这个命令,同样也没有输入账号密码,那第二个bash(子进程)也是non-login shell。

为什么要介绍 login、non-login shell 呢?这是因为这两个取得 bash 的情况中,读取的配置文件数据并不一样所致。由于我们需要登录系统,所以先谈谈 login shell 会读取哪些配置文件?一般来说,login shell 其实只会读取这两个配置文件:

1./etc/profile:这是系统整体的设置,你最好不要修改这个文件;

2.~/.bash_profile 或~/.bash_login 或~/.profile:属于用户个人设置,你要改自己的数据,就写入这里。

那么就让我们来聊一聊这两个文件!这两个文件的内容可是非常繁杂的。

/etc/profile(login shell 才会读)

你可以使用vim去阅读一下这个文件的内容。这个配置文件可以利用用户的标识符(UID)来决定很多重要的变量数据,这也是每个用户登录取得bash时一定会读取的配置文件。所以如果你想要帮所有用户设置整体环境,那就是在这里修改。不过没事还是不要随便改这个文件,这个文件设置的变量主要有:

PATH:会依据UID决定PATH变量要不要含有sbin的系统命令目录;

MAIL:依据账号设置好用户的mailbox到/var/spool/mail/账号名;

USER:根据用户的账号设置此变量内容;

HOSTNAME:依据主机的hostname命令决定此变量内容;

HISTSIZE:历史命令记录条数。CentOS 5.x 设置为1000。

/etc/profile 可不止会做这些事而已,它还会去调用外部的设置数据。在 CentOS 5.x 默认的情况下,下面这些数据会依序被调用进来:

/etc/inputrc

其实这个文件并没有被执行。/etc/profile 会主动判断用户有没有自定义输入的按键功能,如果没有的话,/etc/profile 就会决定设置“INPUTRC=/etc/inputrc”这个变量。此文件内容为 bash 的热键、[Tab]有没有声音等的数据。因为鸟哥觉得bash默认的环境已经很棒了,所以不建议修改这个文件。

/etc/profile.d/*.sh

其实这是个目录内的众多文件。只要在/etc/profile.d/这个目录内且扩展名为.sh,另外用户能够具有 r 的权限,那么该文件就会被/etc/profile 调用。在 CentOS 5.x 中,这个目录下面的文件规定了 bash操作接口的颜色、语系、ll与ls命令的命令别名、vi的命令别名、which的命令别名等。如果你需要帮所有用户设置一些共享的命令别名时,可以在这个目录下面自行创建扩展名为.sh 的文件,并将所需要的数据写入即可。

/etc/sysconfig/i18n

这个文件是由/etc/profile.d/lang.sh调用的。这也是我们决定bash默认使用何种语系的重要配置文件。文件里最重要的就是LANG这个变量的设置。我们在前面的locale中讨论过这个文件。

反正你只要记得,bash 的login shell 情况下所读取的整体环境配置文件其实只有/etc/profile,但是/etc/profile还会调用其他的配置文件,所以让我们的bash操作接口变得非常的友善。接下来让我们来看看,那么个人偏好的配置文件又是怎么回事?

~/.bash_profile(login shell 才会读)

bash在读完了整体环境设置的/etc/profile并借此调用其他配置文件后,接下来则是会读取用户的个人配置文件。在 login shell 的 bash 环境中,所读取的个人偏好配置文件其实主要有三个,依序分别是:

1.~/.bash_profile

2.~/.bash_login

3.~/.profile

其实bash 的login shell 设置只会读取上面三个文件的其中一个,而读取的顺序则是依照上面的顺序。也就是说如果~/.bash_profile 存在,那么其他两个文件不论有没有存在,都不会被读取。如果~/.bash_profile不存在才会去读取~/.bash_login,而前两者都不存在才会读取~/.profile 的意思。会有这么多的文件,其实是照顾其他 shell 转换过来的用户的习惯而已。先让我们来看一下root的/root/.bash_profile的内容是怎样的。

[root@www ~]# cat ~/.bash_profile

.bash_profile

Get the aliases and functions

if [ -f ~/.bashrc ]; then <==下面这三行判断并读取 ~/.bashrc

. ~/.bashrc

fi

User specific environment and startup programs

PATH=$PATH:$HOME/bin  <==下面这几行处理个人化设置

export PATH

unset USERNAME

这个文件内有设置PATH这个变量,而且还使用了export将PATH变成环境变量。由于PATH在/etc/profile当中已经设置过,所以在这里就以累加的方式增加用户主文件夹下的~/bin/为额外的执行文件放置目录。这也就是说,你可以将自己创建的执行文件放置到你自己主文件夹下的~/bin/目录。那就可以直接执行该执行文件而不需要使用绝对/相对路径来执行该文件。

这个文件的内容比较有趣的地方在于if…then那一段。那一段程序代码我们会在第13章shellscript谈到,假设你现在是看不懂的。该段的内容指的是判断主文件夹下的~/.bashrc存在否,若存在则读入~/.bashrc的设置。bash配置文件的读入方式比较有趣,主要是通过一个命令“source”来读取的。也就是说~/.bash_profile其实会再调用~/.bashrc的设置内容。最后我们来看看整个 login shell 的读取流程,如图 11-5 所示。

figure_0338_0259

图11-5 login shell的配置文件读取流程

实线的方向是主线流程,虚线的方向则是被调用的配置文件。从上面我们也可以清楚地知道,在CentOS的login shell环境下,最终被读取的配置文件是“~/.bashrc”这个文件。所以你当然可以将自己的偏好设置写入该文件即可。下面我们还要讨论一下source与~/.bashrc。

source:读入环境配置文件的命令

由于/etc/profile 与~/.bash_profile 都是在取得 login shell 的时候才会读取的配置文件,所以如果你将自己的偏好设置写入上述的文件后,通常都是得注销再登录后该设置才会生效。那么能不能直接读取配置文件而不注销登录呢?可以的!那就得要利用source这个命令了。

[root@www ~]# source 配置文件名

范例:将主文件夹的 ~/.bashrc 的设置读入目前的 bash 环境中

[root@www ~]# source ~/.bashrc <==下面这两个命令是一样的。

[root@www ~]# . ~/.bashrc

利用source或小数点(.)都可以将配置文件的内容读进目前的shell环境中。举例来说,我修改了~/.bashrc,那么不需要注销,立即以source~/.bashrc就可以将刚才最新设置的内容读进目前的环境中。还有包括~/bash_profile 以及/etc/profile 的设置中,很多时候也都是利用到这个source(或小数点)的功能。

有没有可能会使用到不同环境配置文件的时候?有啊!最常发生在一个人的工作环境分为多种情况的时候了。举个例子来说,在鸟哥的大型主机中,经常需要负责两到三个不同的案子,每个案子所需要处理的环境变量设定并不相同,那么鸟哥就将这两三个案子分别编写属于该案子的环境变量配置文件案,当需要该环境时,就直接用“source变量文件”,如此一来环境变量的设置就变得更简便而灵活了。

~/.bashrc(non-login shell 会读)

谈完了 login shell 后,那么 non-login shell 这种非登录情况取得 bash 操作接口的环境配置文件又是什么?当你取得 non-login shell 时,该 bash 配置文件仅会读取~/.bashrc 而已。那么默认的~/.bashrc内容是如何?

[root@www ~]# cat~/.bashrc

.bashrc

User specific aliases and functions

alias rm='rm -i'   <==用户的个人设置

alias cp='cp -i'

alias mv='mv -i'

Source global definitions

if [ -f /etc/bashrc ]; then <==整体的环境设置

. /etc/bashrc

fi

特别注意一下,由于root的身份与一般用户不同,鸟哥是以root的身份取得上述的数据,如果是一般用户的~/.bashrc 会有些许不同。看一下你会发现在 root 的~/.bashrc 中其实已经规定了较为保险的命令别名了。此外咱们的 CentOS 5.x 还会主动调用/etc/bashrc 这个文件。为什么需要调用/etc/bashrc呢?因为/etc/bashrc帮我们的bash定义出下面的数据:

依据不同的UID规定umask的值;

依据不同的UID规定提示符(就是PS1变量);

调用/etc/profile.d/*.sh的设置。

你要注意的是,这个/etc/bashrc 是 CentOS 特有的(其实是 Red Hat 系统特有的),其他不同的distributions可能会放置在不同的文件名就是了。由于这个~/.bashrc会调用/etc/bashrc及/etc/profile.d/*.sh,所以万一你没有~/.bashrc(可能自己不小心将它删除了),那么你会发现你的bash提示符可能会变成这个样子:

-bash-3.2$

不要太担心!这是正常的,因为你并没有调用/etc/bashrc来规定PS1变量,而且这样的情况也不会影响你的bash使用。如果你想要将命令提示符调回来,那么可以复制/etc/skel/.bashrc到你的主文件夹,再修改一下你所想要的内容,并使用 source 去调用~/.bashrc,那你的命令提示符就会回来了。

其他相关配置文件

事实上还有一些配置文件可能会影响到你的bash操作的,下面就来谈一谈:

/etc/man.config

这个文件乍看之下好像跟bash没相关性,但是对于系统管理员来说,却也是很重要的一个文件。这文件的内容规定了使用man 的时候man page 的路径到哪里去寻找。所以说得简单一点,这个文件规定了执行man的时候该去哪里查看数据的路径设置。

那么什么时候要来修改这个文件呢?如果你是以 tarball 的方式来安装你的数据,那么你的man page 可能会放置在/usr/local/softpackage/man 里头,那个 softpackage 是你的套件名称,这个时候你就得以手动的方式将该路径加到/etc/man.config里头,否则使用man的时候就会找不到相关的说明文件。

事实上,这个文件内最重要的其实是 MANPATH 这个变量设置。我们查找 man page 时,会依据 MANPATH 的路径去分别查找。另外要注意的是,这个文件在各大不同版本的 Linux distributions中,文件名都不太相同,例如CentOS用的是/etc/man.config,而SuSE用的则是/etc/manpath.config,可以利用[Tab]按键来进行文件名的补齐。

~/.bash_history

还记得我们在历史命令提到过这个文件吧?默认的情况下,我们的历史命令就记录在这里。而这个文件能够记录几条数据则与HISTSIZE这个变量有关。每次登录bash 后,bash 会先读取这个文件,将所有的历史命令读入内存,因此当我们登录bash后就可以查看上次使用过哪些命令。至于更多的历史命令,请自行回去参考。

~/.bash_logout

这个文件则记录了当我注销bash后系统再帮我做完什么操作后才离开。你可以去读取一下这个文件的内容,默认的情况下,注销时bash只是帮我们清掉屏幕的信息而已。不过你也可以将一些备份或者是其他你认为重要的工作写在这个文件中(例如清空暂存盘),那么当你离开Linux的时候,就可以解决一些烦人的事情。

11.4.4 终端机的环境设置:stty,set

我们在第5章首次登录Linux时就提过,可以在tty1~tty6这六个命令行界面的终端机(terminal)环境中登录,登录的时候我们可以取得一些字符设置的功能。举例来说,我们可以利用退格键来删除命令行上的字符,也可以使用[ctrl]+C 来强制终止一个命令的运行,当输入错误时,就会有声音跑出来警告。这是怎么办到的呢?很简单!因为登录终端机的时候,会自动取得一些终端机的输入环境的设置。

事实上,目前我们使用的Linux distributions都帮我们设置了最棒的用户环境,所以大家可以不用担心操作环境的问题。不过在某些UNIX like的机器中,还是可能需要动用一些手脚,才能够让我们的输入更加舒畅,举例来说,利用[Backspace]删除,要比利用[Del]按键来得顺手。但是某些UNIX偏偏是以[Del]来进行字符的删除。所以这个时候就可以动动手脚了。

那么如何查阅目前的一些按键内容呢?可以利用 stty(setting tty 终端机的意思)。stty 也可以帮助设置终端机的输入按键代表意义。

[root@www ~]# stty [-a]

参数:

-a :将目前所有的 stty 参数列出来。

范例一:列出所有的按键与按键内容

[root@www ~]# stty -a

speed 38400 baud; rows 24; columns 80; line = 0;

intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;

eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z;

rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;

….(以下省略)….

我们可以利用stty-a来列出目前环境中所有的按键列表,在上面的列表当中,需要注意的是特殊字体那几个,此外如果出现^表示[Ctrl]那个按键的意思。举例来说,intr=^C表示利用[ctrl]+c来完成的。几个重要的代表意义是:

eof:End of file 的意思,代表结束输入;

erase:向后删除字符;

intr:送出一个interrupt(中断)的信号给目前正在运行的程序;

kill:删除在目前命令行上的所有文字;

quit:送出一个quit的信号给目前正在运行的进程;

start:在某个进程停止后,重新启动它的输出;

stop:停止目前屏幕的输出;

susp:送出一个 terminal stop 的信号给正在运行的进程。

记不记得我们在第 5 章讲过几个 Linux 热键?没错!就是这个 stty 设置值内的 intr / eof,至于删除字符,就是erase那个设置值。如果你想要用[ctrl]+h来进行字符的删除,那么可以执行:

[root@www ~]# stty erase ^h

那么从此之后,你的删除字符就得要使用[ctrl]+h,按下[Backspace]则会出现^?字样呢。如果想要回复利用[Backspace],就执行 stty erase ^?即可。至于更多的 stty 说明,记得参考一下 man stty的内容。

除了stty之外,其实我们的bash还有自己的一些终端机设置值呢。那就是利用set来设置的。我们之前提到一些变量时,可以利用set 来显示,除此之外,其实set还可以帮我们设置整个命令输出/输入的环境。例如记录历史命令、显示错误内容等。

[root@www ~]# set [-uvCHhmBx]

参数:

-u :默认不启用,若启用后,当使用未设置变量时,会显示错误信息;

-v :默认不启用,若启用后,在讯息被输出前,会先显示信息的原始内容;

-x :默认不启用,若启用后,在命令被执行前,会显示命令内容(前面有 ++ 符号);

-h :默认启用,与历史命令有关;

-H :默认启用,与历史命令有关;

-m :默认启用,与工作管理有关;

-B :默认启用,与刮号 [] 的作用有关;

-C :默认不启用,使用 > 等时,则若文件存在时,该文件不会被覆盖。

范例一:显示目前所有的 set 设置值

[root@www ~]# echo $-

himBH

那个 $- 变量内容就是 set 的所有设置。bash 默认是 himBH 。

范例二:若使用未定义变量时,则显示错误信息

[root@www ~]# set -u

[root@www ~]# echo $vbirding

-bash: vbirding: unbound variable

默认情况下,未设置/未声明 的变量都会是“空的”,不过若设置 -u 参数,

那么当使用未设置的变量时,就会有问题。很多的 shell 都默认启用 -u 参数。

若要取消这个参数,输入 set +u 即可。

范例三:执行前,显示该命令内容。

[root@www ~]# set -x

[root@www ~]# echo $HOME

  • echo /root

/root

++ echo -ne '\033]0;root@www:~'

看见否?要输出的命令都会先被打印到屏幕上。前面会多出 + 的符号。

另外,其实我们还有其他的按键设置功能,就是在前一小节提到的/etc/inputrc这个文件里面设置。

[root@www ~]# cat /etc/inputrc

do not bell on tab-completion

set bell-style none

set meta-flag on

set input-meta on

set convert-meta off

set output-meta on

…..以下省略…..

还有例如/etc/DIR_COLORS*与/etc/termcap等,也都是与终端机有关的环境配置文件。不过事实上,鸟哥并不建议你修改tty的环境,这是因为bash的环境已经设置得很亲和了,我们不需要额外的设置或者修改,否则反而会产生一些困扰。不过写在这里的数据,只是希望大家能够清楚地知道我们的终端机是如何进行设置的。最后我们将bash默认的组合键给它汇整如表11-4所示。

表11-4

figure_0342_0260

11.4.5 通配符与特殊符号

在bash的操作环境中还有一个非常有用的功能,那就是通配符(wildcard)。我们利用bash处理数据就更方便了。下面我们列出一些常用的通配符,如表11-5所示。

表11-5

figure_0342_0261

接下来让我们利用通配符来操作。首先,利用通配符配合ls查找文件名:

root@www ~]# LANG=C   <==由于与编码有关,先设置语系一下

范例一:找出 /etc/ 下面以 cron 为开头的文件名

[root@www ~]# ll -d /etc/cron* <==加上 -d 仅仅是为了显示目录而已

范例二:找出 /etc/ 下面文件名刚好是五个字母的文件名

[root@www ~]# ll -d /etc/????? <==由于 ? 一定有一个,所以五个 ? 就对了

范例三:找出 /etc/ 下面文件名含有数字的文件名

[root@www ~]# ll -d /etc/[0-9] <==记得中括号左右两边均需 *

范例四:找出 /etc/ 下面文件名开头非为小写字母的文件名:

[root@www ~]# ll -d /etc/[^a-z] <==注意中括号左边没有

范例五:将范例四找到的文件复制到 /tmp 中

[root@www ~]# cp -a /etc/[^a-z]* /tmp

除了通配符之外,bash 环境中的特殊符号有哪些呢?下面我们先汇整一下,如表 11-6 所示。

表11-6

figure_0343_0262

以上为bash环境中常见的特殊符号。理论上,你的“文件名”尽量不要使用到上述的字符。

11.5 数据流重定向

数据流重定向(redirect)由字面上的意思来看,好像就是将数据传导到其他地方去的样子?数据流重定向就是将某个命令执行后应该要出现在屏幕上的数据传输到其他的地方,例如文件或者是设备(例如打印机之类的)。这玩意儿在Linux的文本模式下面可重要的,尤其是如果我们想要将某些数据保存下来时,就更有用了。

11.5.1 什么是数据流重定向

什么是数据流重定向啊?这得要由命令的执行结果谈起。一般来说,如果你要执行一个命令,通常它会是这样的,如图11-6所示。

figure_0344_0263

图11-6 命令执行过程的数据传输情况

我们执行一个命令的时候,这个命令可能会由文件读入数据,经过处理之后,再将数据输出到屏幕上。在图 11-6 当中,standard output 与 standard error output 分别代表“标准输出”与“标准错误输出”,这两个命令默认都是输出到屏幕上面来。那么什么是标准输出与标准错误输出呢?

standard output 与standard error output

简单地说,标准输出指的是命令执行所回传的正确的信息,而标准错误输出可理解为命令执行失败后,所回传的错误信息。举个简单例子来说,我们的系统默认有/etc/crontab 但却无/etc/vbirdsay,此时若执行“cat/etc/crontab/etc/vbirdsay”这个命令时,cat会进行:

标准输出:读取/etc/crontab后,将该文件内容显示到屏幕上;

标准错误输出:因为无法找到/etc/vbirdsay,因此在屏幕上显示错误信息;

不管正确或错误的数据都是默认输出到屏幕上,所以屏幕当然是混乱的。那能不能通过某些机制将这两条数据分开呢?当然可以。那就是数据流重定向的功能。数据流重定向可以将standard output(简称 stdout)与 standard error output(简称 stderr)分别传送到其他的文件或设备去,而分别传送所用的特殊字符则如下所示:

1.标准输入(stdin):代码为0,使用<或<<;

2.标准输出(stdout):代码为1,使用>或>>;

3.标准错误输出(stderr):代码为2,使用2>或2>>。

为了理解stdout与stderr,我们先来进行一个范例的练习:

范例一:查看你的系统根目录 (/) 下各目录的文件名、权限与属性,并记录下来

[root@www ~]# ll / <==此时屏幕会显示出文件名信息

[root@www ~]# ll / > ~/rootfile <==屏幕并无任何信息

[root@www ~]# ll ~/rootfile <==有个新文件被创建了

-rw-r—r— 1 root root 1089 Feb 6 17:00 /root/rootfile

屏幕怎么会完全没有数据呢?这是因为原本“ll / ”所显示的数据已经被重新导向到~/rootfile文件中了。那个~/rootfile的文件名可以随便你取。如果你执行“cat~/rootfile”那就可以看到原本应该在屏幕上面的数据。如果我再次执行 “ll/home>~/rootfile”后,那个~/rootfile文件的内容变成什么?它将变成“仅有ll/home的数据”而已。原本的“ll/”数据就不见了吗?是的!因为该文件的创建方式是:

1.该文件(本例中是~/rootfile)若不存在,系统会自动将它创建起来;

2.当这个文件存在的时候,那么系统就会先将这个文件内容清空,然后再将数据写入;

3.也就是若以>输出到一个已存在的文件中,那个文件就会被覆盖掉。

那如果我想要将数据累加而不想要将旧的数据删除,那该如何是好?利用两个大于的符号(>>)就好。以上面的范例来说,你应该要改成“ll/>>~/rootfile”即可。如此一来,当~/rootfile 不存在时系统会主动创建这个文件;若该文件已存在,则数据会在该文件的最下方累加进去。

上面谈到的是 standard output 的正确数据,那如果是 standard error output 的错误数据呢?那就通过2>及2>>。同样是覆盖(2>)与累加(2>>)的特性。我们在刚才谈到stdout代码是1而stderr代码是2,所以这个2>是很容易理解的,而如果仅存在>时,则代表默认的代码1。也就是说:

1>:以覆盖的方法将正确的数据输出到指定的文件或设备上;

1>>:以累加的方法将正确的数据输出到指定的文件或设备上;

2>:以覆盖的方法将错误的数据输出到指定的文件或设备上;

2>>:以累加的方法将错误的数据输出到指定的文件或设备上。

要注意,“1>>”以及“2>>”中间是没有空格的。有了些概念之后让我们继续聊一聊怎么应用吧!当你以一般身份执行find这个命令的时候,由于权限的问题可能会产生一些错误信息。例如执行“find/-name testing”时,可能会产生类似“find: /root: Permission denied”之类的信息。例如下面这个范例:

范例二:利用一般身份账号查找 /home 下面是否有名为 .bashrc 的文件存在

[root@www ~]# su - dmtsai <==假设我的系统有名为 dmtsai 的账号

[dmtsai@www ~]$ find /home -name .bashrc <==身份是 dmtsai 。

find: /home/lost+found: Permission denied <== Starndard error

find: /home/alex: Permission denied  <== Starndard error

find: /home/arod: Permission denied  <== Starndard error

/home/dmtsai/.bashrc      <== Starndard output

由于/home 下面还有我们之前创建的账号存在,那些账号的主文件夹你当然不能进入。所以就会有错误及正确数据了。那么假如我想要将数据输出到 list 这个文件中呢?执行“find /home -name .bashrc > list”会有什么结果?你会发现list 里面存了刚才那个“正确”的输出数据,至于屏幕上还是会有错误的信息出现呢!伤脑筋。如果想要将正确的与错误的数据分别存入不同的文件中需要怎么做?

范例三:承范例二,将 stdout 与 stderr 分别存到不同的文件去

[dmtsai@www ~]$ find /home -name .bashrc > list_right 2> list_error

注意,此时屏幕上不会出现任何信息。因为刚才执行的结果中,有Permission的那几行错误信息都会跑到list_error这个文件中,至于正确的输出数据则会存到list_right这个文件中。这样可以了解了吗?如果有点混乱的话,去休息一下再来看看。

/dev/null垃圾桶黑洞设备与特殊写法

想象一下,如果我知道错误信息会发生,所以要将错误信息忽略掉而不显示或存储呢?这个时候黑洞设备/dev/null 就很重要了。这个/dev/null 可以吃掉任何导向这个设备的信息。将上述的范例修订一下:

范例四:承范例三,将错误的数据丢弃,屏幕上显示正确的数据

[dmtsai@www ~]$ find /home -name .bashrc 2> /dev/null

/home/dmtsai/.bashrc <==只有 stdout 会显示到屏幕上,stderr 被丢弃了

再想象一下,如果我要将正确与错误数据通通写入同一个文件去呢?这个时候就得要使用特殊的写法了。我们同样用下面的案例来说明:

范例五:将命令的数据全部写入名为 list 的文件中

[dmtsai@www ~]$ find /home -name .bashrc > list 2> list <==错误

[dmtsai@www ~]$ find /home -name .bashrc > list 2>&1 <==正确

[dmtsai@www ~]$ find /home -name .bashrc &> list  <==正确

上面第一行错误的原因是,由于两条数据同时写入一个文件,又没有使用特殊的语法,此时两条数据可能会交叉写入该文件内,造成次序的错乱。所以虽然最终 list 文件还是会产生,但是里面的数据排列就会怪怪的,而不是原本屏幕上的输出排序。至于写入同一个文件的特殊语法如上所示,你可以使用2>&1也可以使用&>。一般来说,鸟哥比较习惯使用2>&1的语法。

standard input:<与<<

了解了 stderr 与 stdout 后,那么那个<又是什么呀?以最简单的说法来说,那就是将原本需要由键盘输入的数据改由文件内容来替代。我们先由下面的cat命令操作来了解一下什么叫做“键盘输入”吧。

范例六:利用 cat 命令来创建一个文件的简单流程

[root@www ~]# cat > catfile

testing

cat file test

<==这里按下 [ctrl]+d 来离开

[root@www ~]# cat catfile

testing

cat file test

由于加入>在cat后,所以那个catfile会被主动创建,而内容就是刚才键盘上面输入的那两行数据了。唔!那我能不能用纯文本文件替代键盘的输入,也就是说,用某个文件的内容来替代键盘的敲击呢?可以的。如下所示:

范例七:用 stdin 替代键盘的输入以创建新文件的简单流程

[root@www ~]# cat > catfile < ~/.bashrc

[root@www ~]# ll catfile ~/.bashrc

-rw-r—r— 1 root root 194 Sep 26 13:36 /root/.bashrc

-rw-r—r— 1 root root 194 Feb 6 18:29 catfile

注意看,这两个文件的大小一模一样,几乎像是使用 cp复制的一般。

这东西非常有帮助,尤其是用在类似 mail 这种命令的使用上。理解<之后,再来则是怪可怕的<<这个连续两个小于的符号了。它代表的是结束输入的意思。举例来讲,我要用cat直接将输入的信息输出到catfile中,且当由键盘输入eof时,该次输入就结束,那我可以这样做:

[root@www ~]# cat > catfile << "eof"

> This is a test.

> OK now stop

> eof <==输入这关键字,立刻就结束而不需要输入 [ctrl]+d

[root@www ~]# cat catfile

This is a test.

OK now stop <==只有这两行,不会存在关键字那一行。

看到了吗?利用<<右侧的控制字符,我们可以终止一次输入,而不必输入[crtl]+d 来结束。这对程序写作很有帮助。好了,那么为何要使用命令输出重定向呢?我们来说一说它用于哪些情况吧!

屏幕输出的信息很重要,而且我们需要将它存下来的时候;

后台执行中的程序,不希望它干扰屏幕正常的输出结果时;

一些系统的例行命令(例如写在/etc/crontab中的文件)的执行结果,希望它可以存下来时;

一些执行命令的可能已知错误信息时,想以“2>/dev/null”将它丢掉时;

错误信息与正确信息需要分别输出时。

当然还有很多功能,最简单的就是网友们经常问到的:“为何我的root都会收到系统crontab寄来的错误信息呢?”这个是常见的错误,而如果我们已经知道这个错误信息是可以忽略的时候,“2>errorfile”这个功能就很重要了吧!了解了吗?

11.5.2 命令执行的判断依据:; ,&&,||

在某些情况下,很多命令我想要一次输入去执行,而不想要分次执行时,该如何是好?基本上你有两个选择,一个是通过第 13 章要介绍的 shell script编写脚本去执行,一种则是通过下面的介绍来一次输入多重命令。

cmd ; cmd(不考虑命令相关性的连续命令执行)

在某些时候,我们希望可以一次执行多个命令,例如在关机的时候我希望可以先执行两次sync同步写入磁盘后才shutdown计算机,那么可以这么做:

[root@www ~]# sync; sync; shutdown -h now

在命令与命令中间利用分号 (;) 来隔开,这样一来,分号前的命令执行完后就会立刻接着执行后面的命令了。这真是方便。再来换个角度来想,万一我想要在某个目录下面新建一个文件,也就是说,如果该目录存在的话,那我才新建这个文件,如果不存在,那就算了。也就是说这两个命令彼此之间是有相关性的,前一个命令是否成功的执行与后一个命令是否要执行有关,那就得动用到&&或||。

$?(命令回传码)与&&或||

如同上面谈到的,两个命令之间有相依性,而这个相依性主要判断的地方就在于前一个命令执行的结果是否正确。还记得本章之前我们曾介绍过命令回传码,就是通过这个回传码。再复习一次:若前一个命令执行的结果为正确,在Linux下面会回传一个$?=0的值。那么我们怎么通过这个回传码来判断后续的命令是否要执行呢?这就得要“&&”及“||”的帮忙了。注意,两个“&”之间是没有空格的。那个“|”则是[Shift]+[]的按键,结果如表11-7所示。

表11-7

figure_0347_0264

上述的cmd1及cmd2都是命令。好了,回到我们刚才假想的情况,就是想要先判断一个目录是否存在;若存在才在该目录下面创建一个文件。由于我们尚未介绍判断式(test)的使用,在这里我们使用ls以及回传码来判断目录是否存在。让我们进行下面这个练习看看:

范例一:使用 ls 查阅目录 /tmp/abc 是否存在,若存在则用 touch 创建 /tmp/abc/hehe

[root@www ~]# ls /tmp/abc && touch /tmp/abc/hehe

ls: /tmp/abc: No such file or directory

ls 很干脆地说明找不到该目录,但并没有 touch 的错误,表示 touch 并没有执行

[root@www ~]# mkdir /tmp/abc

[root@www ~]# ls /tmp/abc && touch /tmp/abc/hehe

[root@www ~]# ll /tmp/abc

-rw-r—r— 1 root root 0 Feb 7 12:43 hehe

看到了吧?如果/tmp/abc不存在时,touch就不会被执行,若/tmp/abc存在的话,那么touch就会开始执行。不过,我们还得手动自行新建目录,能不能自动判断,即如果没有该目录就自动创建呢?参考一下下面的例子先:

范例二:测试 /tmp/abc 是否存在,若不存在则予以创建,若存在就不做任何事情

[root@www ~]# rm -r /tmp/abc   <==先删除此目录以方便测试

[root@www ~]# ls /tmp/abc || mkdir /tmp/abc

ls: /tmp/abc: No such file or directory <==真的不存在。

[root@www ~]# ll /tmp/abc

total 0       <==结果出现了。有进行 mkdir

如果你一再重复“ls /tmp/abc || mkdir /tmp/abc”命令,界面也不会出现重复 mkdir 的错误。这是因为/tmp/abc已经存在,所以后续的mkdir就不会进行。这样理解没?让我们再次讨论一下,如果我想要创建/tmp/abc/hehe 这个文件,但我并不知道/tmp/abc 是否存在,那该如何是好?试看看:

范例三:我不清楚 /tmp/abc 是否存在,但就是要创建 /tmp/abc/hehe 文件

[root@www ~]# ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe

上面这个范例三总是会创建/tmp/abc/hehe的。不论/tmp/abc是否存在。那么范例三应该如何解释呢?由于Linux下面的命令都是由左往右执行的,所以范例三有几种结果我们来分析一下:

若/tmp/abc 不存在故回传$?≠0 ,则因为||遇到非 0 的$? ,故开始 mkdir/tmp/abc ,由于mkdir/tmp/abc 会成功进行,所以回传$?=0,因为&&遇到$?=0故会执行touch/tmp/abc/hehe,最终hehe就被创建了。

若/tmp/abc存在故回传$?=0,则因为||遇到0的$?不会进行,此时$?=0继续向后传,故因为&&遇到$?=0就开始创建/tmp/abc/hehe了。最终/tmp/abc/hehe被创建起来。

整个流程如图11-7所示。

figure_0348_0265

图11-7 命令依序执行的关系示意图

上面这张图显示的两条数据中,上方的线段为不存在/tmp/abc 时所进行的命令行为,下方的线段则是存在/tmp/abc 所在的命令行为。如上所述,下方线段由于存在/tmp/abc 所以导致$?=0,让中间的mkdir就不执行了,并将$?=0继续往后传给后续的touch去利用。在任何时刻你都可以拿上面这张图作为示意。让我们来想想下面这个例题吧!

figure_0348_0266

以 ls 测试/tmp/vbirding 是否存在,若存在则显示"exist",若不存在,则显示"not exist"。

答:这又牵涉到逻辑判断的问题,如果存在就显示某个数据,若不存在就显示其他数据,那我可以这样做:

ls /tmp/vbirding && echo "exist" || echo "not exist"

意思是说,当 ls/tmp/vbirding 执行后,若正确,就执行 echo"exist",若有问题,就执行 echo "notexist"。那如果写成如下的状况会出现什么?

ls /tmp/vbirding || echo "not exist" && echo "exist"

这其实是有问题的,为什么呢?由图 11-6 的流程介绍我们知道命令是一个一个往后执行,因此在上面的例子当中,如果 /tmp/vbirding 不存在时,它会进行如下操作:

1.若 ls /tmp/vbirding 不存在,因此回传一个非 0 的数值;

2.接下来经过||的判断,发现前一个命令回传非 0 的数值,因此程序开始执行 echo "not exist" ,而 echo "not exist" 程序肯定可以执行成功,因此会回传一个 0 值给后面的命令;

3.经过&&的判断是 0,所以就开始执行 echo "exist" 。

所以第二个例子里面竟然会同时出现 not exist 与 exist呢。真神奇!

经过这个例题的练习,你应该会了解,由于命令是一个接着一个去执行的,因此,如果真要使用判断,那么这个&&与||的顺序就不能搞错。一般来说,假设判断式有三个,也就是:

command1 && command2 || command3

而且顺序通常不会变,因为一般来说,command2与command3会放置肯定可以执行成功的命令,因此依据上面例题的逻辑分析,你就会知道为何要如此放置,这很有用的,而且考试也很常考。

11.6 管道命令(pipe)

就如同前面所说的,bash命令执行的时候有输出的数据会出现。那么如果这群数据必须要经过几道手续之后才能得到我们所想要的格式,应该如何来设置?这就牵涉到管道(pipe)命令的问题了,管道命令使用的是“|”这个界定符号。另外管道命令与“连续执行命令”是不一样的。这点下面我们会再说明。下面我们先举一个例子来说明一下简单的管道命令。

假设我们想要知道/etc/下面有多少文件,那么可以利用 ls/etc 来查阅,不过,因为/etc 下面的文件太多,导致一口气就将屏幕塞满了,不知道前面输出的内容是什么,此时我们可以通过less命令的协助,利用:

[root@www ~]# ls -al /etc | less

如此一来,使用ls命令输出后的内容就能够被less读取,并且利用less的功能,我们就能够前后翻动相关的信息了。很方便是吧?我们就来了解一下这个管道命令“|”的用途。其实这个管道命令“|”仅能处理经由前面一个命令传来的正确信息,也就是standard output 的信息,对于stdandard error并没有直接处理的能力。那么整体的管道命令可以使用图11-8表示。

figure_0349_0267

图11-8 管道命令的处理示意图

在每个管道后面接的第一个数据必定是“命令”,而且这个命令必须要能够接收 standard input的数据才行,这样的命令才可以是“管道命令”,例如less,more,head,tail等都是可以接收standardinput的管道命令。至于例如ls,cp,mv等就不是管道命令了。因为ls,cp,mv并不会接收来自stdin的数据。也就是说,管道命令主要有两个比较需要注意的地方:

管道命令仅会处理standard output,对于 standard error output 会予以忽略。

管道命令必须要能够接收来自前一个命令的数据成为standard input 继续处理才行。

多说无益,让我们来玩一些管道命令吧!下面的介绍对系统管理非常有帮助。

11.6.1 选取命令:cut,grep

什么是选取命令啊?说穿了,就是将一段数据经过分析后,取出我们所想要的,或者是经由分析关键字,取得我们所想要的那一行。不过要注意的是,一般来说,选取信息通常是针对“行”来分析的,并不是整篇信息分析的,下面我们介绍两个很常用的信息选取命令:

cut

cut不就是“切”吗?没错!这个命令可以将一段信息的某一段“切”出来,处理的信息是以“行”为单位。下面我们就来谈一谈。

[root@www ~]# cut -d'分隔字符' -f fields <==用于分隔字符

[root@www ~]# cut -c 字符范围   <==用于排列整齐的信息

参数:

-d :后面接分隔字符,与 -f 一起使用;

-f :依据 -d 的分隔字符将一段信息切割成为数段,用 -f 取出第几段的意思;

-c :以字符 (characters) 的单位取出固定字符区间。

范例一:将 PATH 变量取出,我要找出第五个路径。

[root@www ~]# echo $PATH

/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/X11R6/bin:/usr/games:

1 | 2 | 3 | 4 |  5 | 6  | 7

[root@www ~]# echo $PATH | cut -d ':' -f 5

如同上面的数字显示,我们是以“: ”作为分隔,因此会出现 /usr/local/bin

那么如果想要列出第 3 与第 5 呢?,就是这样:

[root@www ~]# echo $PATH | cut -d ':' -f 3,5

范例二:将 export 输出的信息取得第 12 字符以后的所有字符串

[root@www ~]# export

declare -x HISTSIZE="1000"

declare -x INPUTRC="/etc/inputrc"

declare -x KDEDIR="/usr"

declare -x LANG="zh_TW.big5"

…..(其他省略)…..

注意看,每个数据都是排列整齐地输出。如果我们不想要“declare -x ”时,就得这么做:

[root@www ~]# export | cut -c 12-

HISTSIZE="1000"

INPUTRC="/etc/inputrc"

KDEDIR="/usr"

LANG="zh_TW.big5"

…..(其他省略)…..

知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据。

我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等。

范例三:用 last 在显示的登录者的信息中仅留下用户大名

[root@www ~]# last

root pts/1 192.168.201.101 Sat Feb 7 12:35 still logged in

root pts/1 192.168.201.101 Fri Feb 6 12:13 - 18:46 (06:33)

root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16)

last 可以输出“账号/终端机/来源/日期时间”的数据,并且是排列整齐的

[root@www ~]# last | cut -d ' ' -f 1

由输出的结果我们可以发现第一个空白分隔的字段代表账号,所以使用如上命令:

但是因为 root pts/1 之间空格有好几个,并非仅有一个,所以如果要找出

pts/1 其实不能以 cut -d ' ' -f 1,2 ,输出的结果将不会是我们想要的。

cut主要的用途在于将同一行里面的数据进行分解,最常使用在分析一些数据或文字数据的时候。这是因为有时候我们会以某些字符当作切割的参数,然后来将数据加以切割,以取得我们所需要的数据。鸟哥也很常使用这个功能,尤其是在分析log文件的时候。不过cut在处理多空格相连的数据时,可能会比较吃力一点。

grep

刚才的cut是在一行信息当中取出某部分我们想要的,而grep则是分析一行信息,若当中有我们所需要的信息,就将该行拿出来,简单的语法是这样的:

[root@www ~]# grep [-acinv] [—color=auto] '查找字符串' filename

参数:

-a :将 binary 文件以 text 文件的方式查找数据;

-c :计算找到 '查找字符串' 的次数;

-i :忽略大小写的不同,所以大小写视为相同;

-n :顺便输出行号;

-v :反向选择,即显示出没有 '查找字符串' 内容的那一行;

—color=auto :可以将找到的关键字部分加上颜色显示;

范例一:将 last 当中有出现 root 的那一行就取出来。

[root@www ~]# last | grep 'root'

范例二:与范例一相反,只要没有 root 的就取出。

[root@www ~]# last | grep -v 'root'

范例三:在 last 的输出信息中,只要有 root 就取出,并且仅取第一列

[root@www ~]# last | grep 'root' |cut -d ' ' -f1

在取出 root 之后,利用上个命令 cut 的处理,就能够仅取得第一列。

范例四:取出 /etc/man.config 内含 MANPATH 的那几行。

[root@www ~]# grep —color=auto 'MANPATH' /etc/man.config

….(前面省略)….

MANPATH_MAP /usr/X11R6/bin  /usr/X11R6/man

MANPATH_MAP /usr/bin/X11   /usr/X11R6/man

MANPATH_MAP /usr/bin/mh   /usr/share/man

神奇的是,如果加上 —color=auto 的选项,找到的关键字部分会用特殊颜色显示喔!

grep是个很棒的命令。它支持的语法实在是太多了,用在正则表达式里头,能够处理的数据实在是很多,不过我们这里先不谈正则表达式,下一章再来说明,你先了解一下,grep可以解析一行文字,取得关键字,若该行有存在关键字,就会整行列出来。

11.6.2 排序命令:sort,wc,uniq

很多时候,我们都会去计算一次数据里头的相同类型的数据总数,举例来说,使用last可以查得这个月份有哪些用户登录了主机者。那么我可以针对每个用户查出他们的总登录次数吗?此时就得要排序与计算之类的命令来辅助了。下面我们介绍几个好用的排序与统计命令。

sort

sort是很有趣的命令,它可以帮我们进行排序,而且可以依据不同的数据类型来排序。例如数字与文字的排序就不一样。此外排序的字符与语系的编码有关,因此如果你需要排序时,建议使用LANG=C来让语系统一,数据排序比较好一些。

[root@www ~]# sort [-fbMnrtuk] [file or stdin]

参数:

-f :忽略大小写的差异,例如 A 与 a 视为编码相同;

-b :忽略最前面的空格符部分;

-M :以月份的名字来排序,例如 JAN, DEC 等的排序方法;

-n :使用“纯数字”进行排序(默认是以文字类型来排序的);

-r :反向排序;

-u :就是 uniq ,相同的数据中,仅出现一行代表;

-t :分隔符,默认是用 [Tab] 键来分隔;

-k :以那个区间 (field) 来进行排序的意思

范例一:个人账号都记录在 /etc/passwd 下,请将账号进行排序。

[root@www ~]# cat /etc/passwd | sort

adm:x:3:4:adm:/var/adm:/sbin/nologin

apache:x:48:48:Apache:/var/www:/sbin/nologin

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

鸟哥省略了很多的输出,由上面的数据看起来,sort 是默认“以第一个”数据来排序,

而且默认是以“文字”类型来排序的,所以由 a 开始排到最后。

范例二:/etc/passwd 内容是以 : 来分隔的,我想以第三列来排序,该如何办?

[root@www ~]# cat /etc/passwd | sort -t ':' -k 3

root:x:0:0:root:/root:/bin/bash

uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

operator:x:11:0:operator:/root:/sbin/nologin

bin:x:1:1:bin:/bin:/sbin/nologin

games:x:12:100:games:/usr/games:/sbin/nologin

看到特殊字体的输出部分了吧?怎么会这样排列?呵呵。没错啦!

如果是以文字类型来排序的话,原本就会是这样,想要使用数字排序:

cat /etc/passwd | sort -t ':' -k 3 -n

这样才行啊!用那个 -n 来告知 sort 以数字来排序。

范例三:利用 last将输出的数据仅取账号,并加以排序

[root@www ~]# last | cut -d ' ' -f1 | sort

sort同样是很常用的命令呢。因为我们经常需要比较一些信息。举个上面的第二个例子来说好了。今天假设你有很多的账号,而且你想要知道最大的用户 ID 目前到哪一号了。使用sort一下子就可以知道答案了。

uniq

如果我排序完成了,想要将重复的数据仅列出一个显示,可以怎么做呢?

[root@www ~]# uniq [-ic]

参数:

-i :忽略大小写字符的不同;

-c :进行计数。

范例一:使用 last 将账号列出,仅取出账号列,进行排序后仅取出一位。

[root@www ~]# last | cut -d ' ' -f1 | sort | uniq

范例二:承上例,如果我还想要知道每个人的登录总次数呢?

[root@www ~]# last | cut -d ' ' -f1 | sort | uniq -c

1

12 reboot

41 root

1 wtmp

从上面的结果可以发现 reboot 有 12 次,root 登录则有 41 次。

wtmp 与第一行的空白都是 last 的默认字符,那两个可以忽略。

这个命令用来将重复的行删除掉只显示一个,举个例子来说,你要知道这个月份登录你主机的用户有谁,而不在乎他的登录次数,那么就使用上面的范例,先将所有的数据行出,再将人名独立出来,经过排序,只显示一个。由于这个命令是在将重复的东西减少,所以当然需要配合排序过的文件来处理。

wc

如果我想要知道/etc/man.config 这个文件里面有多少字?多少行?多少字符的话,可以怎么做呢?其实可以利用wc这个命令来完成。它可以帮我们计算输出的信息的整体数据。

[root@www ~]# wc [-lwm]

参数:

-l :仅列出行;

-w :仅列出多少字(英文单字);

-m :多少字符。

范例一:那个 /etc/man.config 里面到底有多少相关字、行、字符数?

[root@www ~]# cat /etc/man.config | wc

141 722 4617

输出的三个数字中分别代表行、字数、字符数

范例二:我知道使用 last 可以输出登录者,但是 last 最后两行并非账号内容,

那么请问,我该如何以一行命令串取得这个月份登录系统的总人次?

[root@www ~]# last | grep [a-zA-Z] | grep -v 'wtmp' | wc -l

由于 last 会输出空白行与 wtmp 字样在最下面两行,因此我利用

grep 取出非空白行,以及去除 wtmp 那一行,再计算行数,就能够了解。

wc也可以当作命令?这可不是上洗手间的WC,这是相当有用的计算文件内容的一个工具组。举个例子来说,当你要知道目前你的账号文件中有多少个账号时,就使用这个方法:cat/etc/passwd|wc-l。因为/etc/passwd 里头一行代表一个用户呀!所以知道行数就晓得有多少的账号在里头了。而如果要计算一个文件里头有多少个字符时,就使用wc-c这个参数。

11.6.3 双向重定向:tee

我们由前一节知道>会将数据流整个传送给文件或设备,因此我们除非去读取该文件或设备,否则就无法继续利用这个数据流。那么万一我想要将这个数据流的处理过程中将某段信息存下来,应该怎么做?利用tee就可以,我们可以这样简单地看一下,如图11-9所示。

figure_0353_0268

图11-9 tee 的工作流程示意图

tee会同时将数据流送与文件与屏幕(screen);而输出到屏幕的,其实就是stdout,可以让下个命令继续处理。

[root@www ~]# tee [-a] file

参数:

-a :以累加 (append) 的方式,将数据加入 file 当中。

[root@www ~]# last | tee last.list | cut -d " " -f1

这个范例可以让我们将 last 的输出存一份到 last.list 文件中。

[root@www ~]# ls -l /home | tee ~/homefile | more

这个范例则是将 ls 的数据存一份到 ~/homefile ,同时屏幕也有输出信息。

[root@www ~]# ls -l / | tee -a ~/homefile | more

要注意,tee 后接的文件会被覆盖,若加上 -a 这个参数则能将信息累加。

tee 可以让 standard output 转存一份到文件内并将同样的数据继续送到屏幕去处理。这样除了可以让我们同时分析一份数据并记录下来之外,还可以作为处理一份数据的中间暂存盘记录之用。tee在很多认证考试中很容易考呢。

11.6.4 字符转换命令:tr,col,join,paste,expand

我们在 vim 程序编辑器当中提到过 DOS 断行字符与 UNIX 断行字符的不同,并且可以使用dos2UNIX与UNIX2dos来完成转换。好了,那么思考一下,是否还有其他常用的字符替代?举例来说,要将大写改成小写,或者是将数据中的[Tab]按键转成空格键?还有如何将两篇信息整合成一篇?下面我们就来介绍一下这些字符转换命令在管道当中的使用方法:

tr

tr可以用来删除一段信息当中的文字,或者是进行文字信息的替换。

[root@www ~]# tr [-ds] SET1 …

参数:

-d :删除信息当中的 SET1 这个字符串;

-s :替换掉重复的字符。

范例一:将 last 输出的信息中所有的小写字符变成大写字符:

[root@www ~]# last | tr '[a-z]' '[A-Z]'

事实上,没有加上单引号也是可以执行的,如 “last | tr [a-z] [A-Z] ”

范例二:将 /etc/passwd 输出的信息中的冒号 (:) 删除

[root@www ~]# cat /etc/passwd | tr -d ':'

范例三:将 /etc/passwd 转存成 dos 断行到 /root/passwd 中,再将 ^M 符号删除

[root@www ~]# cp /etc/passwd /root/passwd && UNIX2dos /root/passwd

[root@www ~]# file /etc/passwd /root/passwd

/etc/passwd: ASCII text

/root/passwd: ASCII text, with CRLF line terminators <==就是 DOS 断行

[root@www ~]# cat /root/passwd | tr -d '\r' > /root/passwd.linux

那个 \r 指的是 DOS 的断行字符,关于更多的字符,请参考 man tr

[root@www ~]# ll /etc/passwd /root/passwd*

-rw-r—r— 1 root root 1986 Feb 6 17:55 /etc/passwd

-rw-r—r— 1 root root 2030 Feb 7 15:55 /root/passwd

-rw-r—r— 1 root root 1986 Feb 7 15:57 /root/passwd.linux

处理过后,发现文件大小与原本的 /etc/passwd 就一致了。

其实这个命令也可以写在正则表达式里头,因为它也是由正则表达式的方式来替换数据的。以上面的例子来说,使用[]可以设置一串字,也经常用来替换文件中的怪异符号。例如上面第三个例子当中,可以去除DOS文件留下来的^M这个断行的符号。这东西相当有用。相信Linux、Windows系统中的人们最麻烦的一件事就是这个事情啦!即是 DOS 下面会自动在每行行尾加入 ^M 这个断行符号。这个时候我们可以使用这个tr来将^M去除。^M可以使用\r来代替。

col

[root@www ~]# col [-xb]

参数:

-x :将 tab 键转换成对等的空格键

-b :在文字内有反斜杠 (/) 时,仅保留反斜杠最后接的那个字符

范例一:利用 cat -A 显示出所有特殊按键,最后以 col 将 [tab] 转成空白

[root@www ~]# cat -A /etc/man.config <==此时会看到很多 ^I 的符号,那就是 tab

[root@www ~]# cat /etc/man.config | col -x | cat -A | more

如此一来,[tab] 按键会被替换成为空格键,输出就美观多了。

范例二:将 col 的 man page 转存成为 /root/col.man 的纯文本文件

[root@www ~]# man col > /root/col.man

[root@www ~]# vi /root/col.man

COL(1)  BSD General Commands Manual   COL(1)

N^HNA^HAM^HME^HE

c^Hco^Hol^Hl - filter reverse line feeds from input

S^HSY^HYN^HNO^HOP^HPS^HSI^HIS^HS

c^Hco^Hol^Hl [-^H-b^Hbf^Hfp^Hpx^Hx] [-^H-l^Hl ^Hn^Hu_^Hm]

你没看错。由于 man page 内有些特殊按钮会用来作为类似特殊按键与颜色显示,

所以这个文件内就会出现如上所示的一堆怪异字符(有 ^ 的)

[root@www ~]# man col | col -b > /root/col.man

虽然col有它特殊的用途,不过很多时候,它可以用来进行简单处理,如将[tab]按键替换成为空格键。例如上面的例子当中,如果使用cat-A则[tab]会以^I来表示,但经过col-x的处理,则会将[tab]替换成为对等的空格键。此外,col 经常被利用于将man page 转存为纯文本文件以方便查阅的功能,如上述的范例二。

join

join 看字面上的意义(加入/参加)就可以知道,它是在处理两个文件之间的数据,而且,主要是将两个文件当中有相同数据的那一行加在一起。我们利用下面的简单例子来说明:

[root@www ~]# join [-ti12] file1 file2

参数:

-t :join 默认以空格符分隔数据,并且对比“第一个字段”的数据,

如果两个文件相同,则将两条数据连成一行,且第一个字段放在第一个;

-i :忽略大小写的差异;

-1 :这个是数字的 1 ,代表第一个文件要用哪个字段来分析的意思;

-2 :代表第二个文件要用哪个字段来分析的意思。

范例一:用 root 的身份,将 /etc/passwd 与 /etc/shadow 相关数据整合成一列

[root@www ~]# head -n 3 /etc/passwd /etc/shadow

==> /etc/passwd <==

root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

==> /etc/shadow <==

root:$1$/3AQpE5e$y9A/D0bh6rElAs:14120:0:99999:7:::

bin:*:14126:0:99999:7:::

daemon:*:14126:0:99999:7:::

由输出的数据可以发现这两个文件的最左边字段都是账号,且以 : 分隔

[root@www ~]# join -t ':' /etc/passwd /etc/shadow

root:x:0:0:root:/root:/bin/bash:$1$/3AQpE5e$y9A/D0bh6rElAs:14120:0:99999:7:::

bin:x:1:1:bin:/bin:/sbin/nologin:*:14126:0:99999:7:::

daemon:x:2:2:daemon:/sbin:/sbin/nologin:*:14126:0:99999:7:::

通过上面这个操作,我们可以将两个文件的第一字段相同者整合成一行。

第二个文件的相同字段并不会显示(因为已经在第一行了)。

范例二:我们知道 /etc/passwd的第四个字段是 GID ,那个 GID 记录在

/etc/group 当中的第三个字段,请问如何将两个文件整合?

[root@www ~]# head -n 3 /etc/passwd /etc/group

==> /etc/passwd <==

root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

==> /etc/group <==

root:x:0:root

bin:x:1:root,bin,daemon

daemon:x:2:root,bin,daemon

从上面可以看到,确实有相同的部分。赶紧来整合一下!

[root@www ~]# join -t ':' -1 4 /etc/passwd -2 3 /etc/group

0:root:x:0:root:/root:/bin/bash:root:x:root

1:bin:x:1:bin:/bin:/sbin/nologin:bin:x:root,bin,daemon

2:daemon:x:2:daemon:/sbin:/sbin/nologin:daemon:x:root,bin,daemon

同样,相同的字段部分被移动到最前面了。所以第二个文件的内容就没再显示。

请读者们配合上述显示两个文件的实际内容来比对。

这个 join 在处理两个相关的数据文件时,就真的是很有帮助的。例如上面的案例当中,我的/etc/passwd、/etc/shadow、/etc/group 都是有相关性的,其中/etc/passwd、/etc/shadow 以账号为相关性,至于/etc/passwd、/etc/group 则以所谓的 GID(账号的数字定义)来作为它的相关性。根据这个相关性,我们可以将有关系的数据放置在一起。这在处理数据可是相当有帮助的。但是上面的例子有点难,希望你可以静下心好好看一看原因。

此外,需要特别注意的是,在使用join之前,你所需要处理的文件应该要事先经过排序(sort)处理。否则有些对比的项目会被略过。特别注意了。

paste

这个paste就要比join简单多了。相对于join必须要对比两个文件的数据相关性,paste就直接将两行贴在一起,且中间以[tab]键隔开而已。简单的使用方法如下:

[root@www ~]# paste [-d] file1 file2

参数:

-d :后面可以接分隔字符,默认是以 [tab] 来分隔的。

- :如果 file 部分写成 - ,表示来自 standard input 的数据的意思。

范例一:将 /etc/passwd 与 /etc/shadow 同一行粘贴在一起

[root@www ~]# paste /etc/passwd /etc/shadow

bin:x:1:1:bin:/bin:/sbin/nologin  bin:*:14126:0:99999:7:::

daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:14126:0:99999:7:::

adm:x:3:4:adm:/var/adm:/sbin/nologin adm:*:14126:0:99999:7:::

注意,同一行中间是以 [tab] 按键隔开的。

范例二:先将 /etc/group 读出(用 cat),然后与范例一粘贴在一起,且仅取出前三行

[root@www ~]# cat /etc/group|paste /etc/passwd /etc/shadow -|head -n 3

这个例子的重点在那个 - 的使用。那经常代表 stdin 。

expand

就是将[tab]按键转成空格键,可以这样做:

[root@www ~]# expand [-t] file

参数:

-t :后面可以接数字。一般来说,一个[tab]按键可以用 8 个空格键替换。

我们也可以自行定义一个 [tab] 按键代表多少个字符。

范例一:将 /etc/man.config 内行首为 MANPATH 的字样取出,仅取前三行。

[root@www ~]# grep '^MANPATH' /etc/man.config | head -n 3

MANPATH /usr/man

MANPATH /usr/share/man

MANPATH /usr/local/man

行首的代表标志为 ^ ,这个我们留待下节介绍,先有概念即可。

范例二:承上,如果我想要将所有的符号都列出来的话?(用 cat)

[root@www ~]# grep '^MANPATH' /etc/man.config | head -n 3 |cat -A

MANPATH^I/usr/man$

MANPATH^I/usr/share/man$

MANPATH^I/usr/local/man$

发现差别了吗?没错![tab] 按键可以被 cat -A 显示成为 ^I

范例三:承上,我将 [tab] 按键设置成 6 个字符的话?

[root@www ~]# grep '^MANPATH' /etc/man.config | head -n 3 | \

> expand -t 6 - | cat -A

MANPATH /usr/man$

MANPATH /usr/share/man$

MANPATH /usr/local/man$

123456123456123456…..

仔细看一下上面的数字说明,因为我是以 6 个字符来代表一个 [tab] 的长度,所以,

MAN… 到 /usr 之间会隔 12 (两个 [tab]) 个字符。如果 tab 改成 9 的话,

情况就又不同了。这里也不好理解,你可以多设置几个数字来查看就晓得。

expand 也是挺好玩的,会自动将[tab]转成空格键。所以以上面的例子来说,使用 cat -A 就会查不到^I的字符。此外,因为[tab]最大的功能就是格式排列整齐,我们转成空格键后,这个空格键也会依据我们自己的定义来增加大小,所以并不是一个^I就会换成8个空白。这个地方要特别注意的。此外你也可以参考一下unexpand这个将空白转成[tab]的命令。

11.6.5 切割命令:split

如果你有文件太大,导致一些携带式设备无法复制的问题。找split就对了!它可以帮你将一个大文件依据文件大小或行数来切割成为小文件了,快速又有效啊!

[root@www ~]# split [-bl] file PREFIX

参数:

-b :后面可接欲切割成的文件大小,可加单位,例如 b, k, m 等;

-l :以行数来进行切割;

PREFIX :代表前导符,可作为切割文件的前导文字。

范例一:我的 /etc/termcap 有七百多KB,若想要分成 300KB 一个文件时怎么办?

[root@www ~]# cd /tmp; split -b 300k /etc/termcap termcap

[root@www tmp]# ll -k termcap*

-rw-r—r— 1 root root 300 Feb 7 16:39 termcapaa

-rw-r—r— 1 root root 300 Feb 7 16:39 termcapab

-rw-r—r— 1 root root 189 Feb 7 16:39 termcapac

那个文件名可以随意取的。我们只要写上前导文字,小文件就会以

xxxaa, xxxab, xxxac 等方式来新建小文件的。

范例二:如何将上面的三个小文件合成一个文件,文件名为 termcapback?

[root@www tmp]# cat termcap* >> termcapback

很简单吧?就用数据流重定向就好。简单。

范例三:将使用 ls -al / 输出的信息中,每10行记录成一个文件

[root@www tmp]# ls -al / | split -l 10 - lsroot

[root@www tmp]# wc -l lsroot*

10 lsrootaa

10 lsrootab

6 lsrootac

26 total

重点在那个 -。一般来说,如果需要 stdout/stdin,但偏偏又没有文件,

有的只是 - 时,那么那个 - 就会被当成 stdin 或 stdout ~

在Windows操作系统下,你要将文件切割需要如何操作?伤脑筋吧!在Linux下面就简单多了!你要将文件切割的话,那么就使用-b size 来将一个切割的文件限制其大小,如果是行数的话,那么就使用-l line 来切割。好用得很!如此一来你就可以轻易地将你的文件切割成软盘(floppy)的大小。

11.6.6 参数代换:xargs

xargs是在做什么的呢?就以字面上的意义来看,x是加减乘除的乘号,args则是arguments(参数)的意思,所以说,这个玩意儿就是在产生某个命令的参数的意思。xargs可以读入stdin的数据,并且以空格符或断行字符进行分辨,将stdin的数据分隔成为arguments。因为是以空格符作为分隔,所以,如果有一些文件名或者是其他意义的名词内含有空格符的时候,xargs 可能就会误判了。它的用法其实也还蛮简单的。就来看一看先!

[root@www ~]# xargs [-0epn] command

参数:

-0 :如果输入的 stdin 含有特殊字符,例如 `, \, 空格键等字符时,这个参数

可以将它还原成一般字符。这个参数可以用于特殊状态。

-e :这个是 EOF (end of file) 的意思。后面可以接一个字符串,当 xargs 分析到

这个字符串时,就会停止继续工作。

-p :在执行每个命令的参数时,都会询问用户的意思。

-n :后面接次数,每次 command 命令执行时,要使用几个参数的意思。看范例三。

当 xargs 后面没有接任何的命令时,默认是以 echo 来进行输出。

范例一:将 /etc/passwd 内的第一列取出,仅取三行,使用 finger 这个命令将每个

账号内容显示出来。

[root@www ~]# cut -d':' -f1 /etc/passwd |head -n 3| xargs finger

Login: root      Name: root

Directory: /root     Shell: /bin/bash

Never logged in.

No mail.

No Plan.

……下面省略…..

由 finger account 可以取得该账号的相关说明内容,例如上面的输出就是 finger root

后的结果。在这个例子当中,我们利用 cut 取出账号名称,用 head 取出三个账号,

最后则是由 xargs 将三个账号的名称变成 finger 后面需要的参数。

范例二:同上,但是每次执行 finger 时,都要询问用户是否操作。

[root@www ~]# cut -d':' -f1 /etc/passwd |head -n 3| xargs -p finger

finger root bin daemon ?…y

…..(下面省略)….

这个 -p 的参数可以让用户使用过程中被询问每个命令是否执行。

范例三:将所有的 /etc/passwd 内的账号都以 finger 查阅,但一次仅查阅五个账号

[root@www ~]# cut -d':' -f1 /etc/passwd | xargs -p -n 5 finger

finger root bin daemon adm lp ?…y

…..(中间省略)….

finger uucp operator games gopher ftp ?…y

…..(下面省略)….

在这里鸟哥使用了 -p 这个参数来让你对于 -n 更有概念。一般来说,某些命令后面

可以接的参数是有限制的,不能无限制地累加,此时我们可以利用 -n

来帮助我们将参数分成数个部分,每个部分分别再以命令来执行。

范例四:同上,但是当分析到 lp 就结束这串命令。

[root@www ~]# cut -d':' -f1 /etc/passwd | xargs -p -e'lp' finger

finger root bin daemon adm ?…

仔细与上面的案例做比较。也同时注意,那个 -e'lp' 是连在一起的,中间没有空格键。

上个例子当中,第五个参数是 lp,那么我们执行 -e'lp' 后,则分析到 lp

这个字符串时,后面的其他 stdin 的内容就会被 xargs 舍弃掉了。

其实,在 man xargs 里面就有三四个小范例,你可以自行参考一下内容。此外,xargs 真的是很好用的。你真的需要好好参详。会使用xargs的原因是,很多命令其实并不支持管道命令,因此我们可以通过xargs 来提供该命令引用standard input 之用。举例来说,我们使用如下的范例来说明:

范例五:找出 /sbin 下面具有特殊权限的文件名,并使用 ls -l 列出详细属性。

[root@www ~]# find /sbin -perm +7000 | ls -l

结果竟然仅有列出 root 所在目录下的文件。这不是我们要的。

因为 ll (ls) 并不是管道命令的原因。

[root@www ~]# find /sbin -perm +7000 | xargs ls -l

-rwsr-xr-x 1 root root 70420 May 25 2008 /sbin/mount.nfs

-rwsr-xr-x 1 root root 70424 May 25 2008 /sbin/mount.nfs4

-rwxr-sr-x 1 root root 5920 Jun 15 2008 /sbin/netreport

….(下面省略)….

11.6.7 关于减号 - 的用途

管道命令在bash 的连续的处理程序中是相当重要的,在log file 的分析当中也是相当重要的一环,所以请特别留意。另外在管道命令当中,经常会使用到前一个命令的stdout作为这次的stdin,某些命令需要用到文件名(例如tar)来进行处理时,该stdin与stdout可以利用减号"-"来替代,举例来说:

[root@www ~]# tar -cvf - /home | tar -xvf –

上面这个例子是说我将/home 里面的文件打包,但打包的数据不是记录到文件,而是传送到stdout;经过管道后,将tar-cvf-/home传送给后面的tar-xvf-。后面的这个-则是取用前一个命令的stdout,因此我们就不需要使用文件了。这是很常见的例子。

11.7 重点回顾

由于内核在内存中是受保护的块,因此我们必须要通过“Shell”将我们输入的命令与Kernel通信,好让Kernel可以控制硬件来正确无误地工作。

学习shell的原因主要有:命令行界面的shell在各大distribution都一样;远程管理时命令行界面速度较快;shell是管理Linux系统非常重要的一环,因为Linux内很多控制都是以shell编写的。

系统合法的shell均写在/etc/shells文件中。

用户默认登录取得的shell记录于/etc/passwd的最后一个字段。

bash的功能主要有命令编辑功能、命令与文件补全功能、命令别名设置功能、作业控制、前台、后台控制、程序化脚本、通配符。

type可以用来找到执行命令为何种类型,也可用于与which相同的功能。

变量就是以一组文字或符号等来替换一些设置或者是一串保留的数据。

变量主要有环境变量与自定义变量,或称为全局变量与局部变量。

使用env与export可查看环境变量,其中export可以将自定义变量转成环境变量。

set可以查看目前bash环境下的所有变量。

$?也为变量,是前一个命令执行完毕后的回传码。在Linux回传码为0代表执行成功。

locale可用于查看语系数据。

可用read让用户由键盘输入变量的值。

ulimit可用以限制用户使用系统的资源情况。

bash 的配置文件主要分为 loginshell 与 non-login shell。login shell 主要读取/etc/profile 与~/.bash_profile,non-login shell 则仅读取~/.bashrc。

通配符主要有*、 ?、[]等。

数据流重定向通过>、2>、<之类的符号将输出的信息转到其他文件或设备去。

连续命令的执行可通过; &&、||等符号来处理。

管道命令的重点是它仅会处理 standard output,对于 standard error output 会予以忽略。管道命令必须要能够接收来自前一个命令的数据成为 standard input 继续处理才行。

本章介绍的管道命令主要有cut,grep,sort,wc,uniq,tee,tr,col,join,paste,expand, split,xargs等。

11.8 本章习题

情境模拟题

由于~/.bash_history仅能记录命令,我想要在每次注销时都记录时间,并将后续的命令50条记录下来,可以如何处理?

目标:了解history,并通过数据流重定向的方式记录历史命令;

前提:需要了解本章的数据流重定向,以及了解bash的各个环境配置文件信息。

其实处理的方式非常简单,我们可以了解 date 可以输出时间,利用~/.myhistory 来记录所有历史记录,而目前最新的50条历史记录可以使用history50来显示,故可以修改~/.bash_logout成为下面的模样:

[root@www ~]# vim ~/.bash_logout

date >> ~/.myhistory

history 50 > > ~/.myhistory

clear

简答题部分

在Linux上可以找到哪些shell(举出三个)?那个文件记录可用的shell是什么?而Linux默认的shell是什么?

在shell环境下,有个提示符(prompt),它可以修改吗?要改什么?默认的提示符内容是什么?

如何显示HOME这个环境变量什么?

如何得知目前的所有变量与环境变量的设置值?

我是否可以设置一个变量名称为3myhome?

在这样的练习中:“A=B”且“B=C”,若我执行“unset$A”,则取消的变量是A还是B?

如何取消变量与命令别名的内容?

如何设置一个变量名称为 name 内容为 It's my name?

bash环境配置文件主要分为哪两种类型的读取?分别读取哪些重要文件?

CentOS 5.x的 man page 的路径配置文件是什么?

试说明'、"、与`这些符号在变量定义中的用途。

转义符号\有什么用途?

连续命令中,;、&&、||有何不同?

如何将last的结果中独立出账号,并且打印出曾经登录过的账号?

请问 foo1 && foo2 | foo3 > foo4,这个命令串当中,foo1/foo2/foo3/foo4 是命令还是文件?整串命令的意义是什么?

如何列出在/bin下面任何以a为开头的文件文件名的详细数据?

如何显示/bin下面文件名为四个字符的文件?

如何显示/bin下面文件名开头不是a-d的文件?

我想要让终端机接口的登录提示符修改成我自己喜好的模样,应该要改哪里?

承上题,如果我是想要让用户登录后才显示欢迎信息,又应该要改哪里?

11.9 参考数据与扩展阅读

卧龙小三的教学文件:http://linux.tnc.edu.tw/techdoc/shell/book1.html

GNU计划的BASH说明:

http://www.gnu.org/manual/bash-2.05a/html_mono/bashref.html

鸟哥的备份:http://linux.vbird.org/linux_basic/0320bash/0320bash_reference.php

man bash