《linux与unix_shell编程指南》中文


下载 第1章 文件安全与权限 为了防止未授权用户访问你的文件,可以在文件和目录上设置权限位。还可以设定文件 在创建时所具有的缺省权限:这些只是整个系统安全问题中的一小部分。在这里我们并不想 对系统安全问题的方方面面进行全面的探讨,只是介绍一下有关文件和目录的安全问题。 本章包含以下内容: • 文件和目录的权限。 • setuid。 • chown和c h g r p。 • umask。 • 符号链接。 创建文件的用户和他(她)所属于的组拥有该文件。文件的属主可以设定谁具有读、写、执 行该文件的权限。当然,根用户或系统管理员可以改变任何普通用户的设置。一个文件一经 创建,就具有三种访问方式: 1) 读,可以显示该文件的内容。 2) 写,可以编辑或删除它。 3) 执行,如果该文件是一个s h e l l脚本或程序。 按照所针对的用户,文件的权限可分为三类: 1) 文件属主,创建该文件的用户。 2) 同组用户,拥有该文件的用户组中的任何用户。 3) 其他用户,即不属于拥有该文件的用户组的某一用户。 1.1 文件 当你创建一个文件的时候,系统保存了有关该文件的全部信息,包括: • 文件的位置。 • 文件类型。 • 文件长度。 • 哪位用户拥有该文件,哪些用户可以访问该文件。 • i节点。 • 文件的修改时间。 • 文件的权限位。 让我们使用ls -l命令,来看一个典型的文件: 第一部分 s h e l l 下面让我们来分析一下该命令所得结果的前面两行,看看都包含了哪些信息: total 4232:这一行告诉我们该目录中所有文件所占的空间。 - r w x r- x r- x:这是该文件的权限位。如果除去最前面的横杠,这里一共是 9个字符,他们 分别对应9个权限位。通过这些权限位,可以设定用户对文件的访问权限。这 9个字符可以分 为三组: r w x:文件属主权限 这是前面三位 r- x:同组用户权限 这是中间三位 r- x:其他用户权限 这是最后三位 后面我们还将对这些权限位作更详细的介绍。出现在 r、w、x位置上的横杠表示相应的访 问权限被禁止。 1 该文件硬链接的数目。 root 文件的属主。 root 文件的属主r o o t所在的缺省组(也叫做r o o t )。 3578 用字节来表示的文件长度,记住,不是 K字节! Oct 14 04:44 文件的更新时间。 dmesg 文件名。 1.2 文件类型 还记得前面一节所提到的文件权限位前面的那个字符吗?我们现在就解释一下这个横杠 所代表的意思,文件类型有七种,它可以从 ls -l命令所列出的结果的第一位看出,这七种类型 是: d 目录。 l 符号链接(指向另一个文件)。 s 套接字文件。 b 块设备文件。 c 字符设备文件。 p 命名管道文件。 - 普通文件,或者更准确地说,不属于以上几种类型的文件。 1.3 权限 让我们用t o u c h命令创建一个文件: $ touch myfile 现在对该目录使用ls -l命令: 2 第一部分 shell 下载 我们已经创建了一个空文件,正如我们所希望的那样,第一个横杠告诉我们该文件是一 个普通文件。你将会发现所创建的文件绝大多数都是普通文件或符号链接文件 (后面将会出现 更多的符号链接文件)。 文件属主权限 组用户权限 其他用户权限 r w - r- - r— 接下来的三个权限位是文件属主所具有的权限;再接下来的三位是与你同组用户所具有 的权限,这里是a d m i n组;最后三位是其他用户所具有的权限。在该命令的结果中,我所属于 的缺省组也显示了出来。下面是对该文件权限的精确描述: 表1-1 ls -l命令输出的含义 (第一个字符) - 普通文件 (接下来的三个字符) r w - 文件属主的权限 (再接下来的三个字符) r- - 同组用户的权限 (最后三个字符 ) r- - 其他用户的权限 因此,这三组字符(除了第一个字符)分别定义了: 1) 文件属主所拥有的权限。 2) 文件属主缺省组(一个用户可以属于很多的组 )所拥有的权限。 3) 系统中其他用户的权限。 在每一组字符中含有三个权限位: r 读权限 w 写/更改权限 x 执行该脚本或程序的权限 这里我们采用另外一种方式来表示刚才所列出 m y f i l e的文件权限: - r w - r- - r-- 文件类型为普通文件 文件属主可以读、写 同组用户可以读 其他用户可以读 你可能已经注意到了,m y f i l e在创建的时候并未给属主赋予执行权限,在用户创建文件时, 系统不会自动地设置执行权限位。这是出于加强系统安全的考虑。必须手工修改这一权限位: 后面讲到u m a s k命令时,你就会明白为什么没有获得执行权限。然而,你可以针对目录设置执 行权限位,但这与文件执行权限位的意义有所不同,这一点我们将在后面讨论。 上面这段关于权限位的内容可能不太好理解,让我们来看几个例子 (见表1 - 2 )。 更令人迷惑的是,对于文件属主来说,在只有读权限位被置位的情况下,仍然可以通过 文件重定向的方法向该文件写入。过一会儿我们就会看到,能否删除一个文件还依赖于该文 件所在目录权限位的设置。 表1-2 文件权限及含义 权 限 所代表的含义 r-- --- --- 文文件属主可读,但不能写或执行 r-- r-- --- 文文件属主和同组用户(一般来说,是文件属主所在的缺省组 )可读 r-- r-- r- - 文任何用户都可读,但不能写或执行 rwx r-- r- - 文文件属主可读、写、执行,同组用户和其他用户只可读 rwx r-x --- 文文件属主可读、写、执行,同组用户可读、执行 第1章 文件安全与权限 3下载 (续) 权 限 所代表的含义 rwx r-x r- x 文文件属主可读、写、执行,同组用户和其他用户可读、执行 rw- rw- --- 文文件属主和同组用户可读、写 rw- rw- r- - 文文件属主和同组用户可读、写,其他用户可读 rw- rw- --- 文文件属主和同组用户及其他用户读可以读、写,慎用这种权限 设置,因为任何用户都可以写入该文件 1.4 改变权限位 对于属于你的文件,可以按照自己的需要改变其权限位的设置。在改变文件权限位设置 之前,要仔细地想一想有哪些用户需要访问你的文件 (包括你的目录)。可以使用c h m o d命令来 改变文件权限位的设置。这一命令有比较短的绝对模式和长一些的符号模式。我们先来看一 看符号模式。 1.4.1 符号模式 c h m o d命令的一般格式为: chmod [who] operator [permission] filename w h o的含义是: u 文件属主权限。 g 同组用户权限。 o 其他用户权限。 a 所有用户(文件属主、同组用户及其他用户 )。 o p e r a t o r的含义: + 增加权限。 - 取消权限。 = 设定权限。 p e r m i s s i o n的含义: r 读权限。 w 写权限。 x 执行权限。 s 文件属主和组s e t - I D。 t 粘性位*。 l 给文件加锁,使其他用户无法访问。 u,g,o 针对文件属主、同组用户及其他用户的操作。 *在列文件或目录时,有时会遇到“ t”位。“t”代表了粘性位。如果在一个目录上出现 “t”位,这就意味着该目录中的文件只有其属主才可以删除,即使某个同组用户具有和属主 同等的权限。不过有的系统在这一规则上并不十分严格。 如果在文件列表时看到“ t”,那么这就意味着该脚本或程序在执行时会被放在交换区 (虚 存)。不过由于当今的内存价格如此之低,大可不必理会文件的“ t”的使用。 4 第一部分 shell 下载 1.4.2 chmod命令举例 现在让我们来看一些使用 c h m o d命令的例子。假定 m y f i l e文件最初具有这样的权限: r w x rwx rwx : 命令 结果 含义 chmod a-x myfile rw- rw- rw- 收回所有用户的执行权限 chmod og-w myfile rw- r-- r- - 收回同组用户和其他用户的写权限 chmod g+w myfile rw- rw- r- - 赋予同组用户写权限 chmod u+x myfile rwx rw- r- - 赋予文件属主执行权限 chmod go+x myfile rwx rwx r- x 赋予同组用户和其他用户执行权限 当创建m y f i l e文件时,它具有这样的权限: 如果这是我写的一个脚本,我希望能够具有执行权限,并取消其他用户 (所有其他用户 ) 的写权限,可以用: $ chmod u+x o-w myfile 这样,该文件的权限变为: 现在已经使文件属主对 m y f i l e文件具有读、写执行的权限,而 a d m i n组的用户对该文件具 有读权限。 如果希望某个脚本文件对你自己来说可执行,而且你对该文件的缺省权限很放心,那么 只要使它对你来说具有执行权限即可。 $ chmod u+x dt 1.4.3 绝对模式 c h m o d命令绝对模式的一般形式为: chmod [mode] file 其中m o d e是一个八进制数。 在绝对模式中,权限部分有着不同的含义。每一个权限位用一个八进制数来代表,如表 1 - 3所示。 表1-3 八进制目录/文件权限表示 八进制数 含 义 八进制数 含 义 0 4 0 0 文件属主可读 0 0 1 0 同组用户可执行 0 2 0 0 文件属主可写 0 0 0 4 其他用户可读 0 1 0 0 文件属主可执行 0 0 0 2 其他用户可写 0 0 4 0 同组用户可读 0 0 0 1 其他用户可执行 0 0 2 0 同组用户可写 在设定权限的时候,只需按照表 1 - 3查出与文件属主、同组用户和其他用户所具有的权限 相对应的数字,并把它们加起来,就是相应的权限表示。 从表1 - 3中可以看出,文件属主、同组用户和其他用户分别所能够具有的最大权限值就是7。 第1章 文件安全与权限 5下载 再来看看前面举的例子: 相应的权限表示应为6 4 4,它的意思就是: 0 4 0 0 + 0 2 0 0 (文件属主可读、写) = 0 6 0 0 0 0 4 0 (同组用户可读) = 0 0 4 0 0 0 0 4 (其他用户可读) = 0 0 0 4 0 6 4 4 有一个计算八进制权限表示的更好办法,如表 1 - 4所示: 表1-4 计算权限值 文件属主 同组用户 其他用户 r w x r w x r w x 4 + 2 + 1 4 + 2 + 1 4 + 2 + 1 使用表1 - 4,可以更容易地计算出相应的权限值,只要分别针对文件属主、同组用户和其 他用户把相应权限下面的数字加在一起就可以了。 m y f i l e文件具有这样的权限: r w - r - - r - - 4 + 2 4 4 把相应权限位所对应的值加在一起,就是 6 4 4。 1.4.4 chmod命令的其他例子 以下是一些c h m o d命令绝对模式的例子: 命令 结果 含义 chmod 666 rw- rw- rw- 赋予所有用户读和写的权限 chmod 644 rw- r-- r- - 赋予所有文件属主读和写的权限,所有其他用户读权限 chmod 744 rwx r-- r- - 赋予文件属主读、写和执行的权限,所有其他用户读的权限 chmod 664 rw- rw- r- - 赋予文件属主和同组用户读和写的权限,其他用户读权限 chmod 700 rwx --- --- 赋予文件属主读、写和执行的权限 chmod 444 r-- r-- r- - 赋予所有用户读权限 下面举一个例子,假定有一个名为 y o a的文件,具有如下权限: 我现在希望使自己对该文件可读、写和执行, a d m i n组用户对该文件只读,可以键入: 如果希望自己对该文件可读、写和执行,对其他所有用户只读,我可以用: 如果希望一次设置目录下所有文件的权限,可以用: chmod 644* 这将使文件属主和同组用户都具有读和写的权限,其他用户只具有读权限。 6 第一部分 shell 下载 还可以通过使用- R选项连同子目录下的文件一起设置: chmod -R 664 /usr/local/home/dave/* 这样就可以一次将/ u s r / l o c a l / h o m e / d a v e目录下的所有文件连同各个子目录下的文件的权限 全部设置为文件属主和同组用户可读和写,其他用户只读。使用 - R选项一定要谨慎,只有在 需要改变目录树下全部文件权限时才可以使用。 1.4.5 可以选择使用符号模式或绝对模式 上面的例子中既有绝对模式的,也有符号模式的,我们可以从中看出,如果使用该命令 的符号模式,可以设置或取消个别权限位,而在绝对模式中则不然。我个人倾向于使用符号 模式,因为它比绝对模式方便快捷。 1.5 目录 还记得在前面介绍c h m o d命令时讲过,目录的权限位和文件有所不同。现在我们来看看其 中的区别。目录的读权限位意味着可以列出其中的内容。写权限位意味着可以在该目录中创 建文件,如果不希望其他用户在你的目录中创建文件,可以取消相应的写权限位。执行权限 位则意味着搜索和访问该目录(见表 1 - 5、表1 - 6)。 表1-5 目录权限 rwx 可以列出该目录中的文件 可以在该目录中创建或删除文件 可以搜索或进入该目录 表1-6 目录权限举例 权 限 文件属主 同组用户 其他用户 drwx rwx r- x ( 7 7 5 ) 读、写、执行 读、写、执行 读、执行 drwx r-x r- - ( 7 5 4 ) 读、写、执行 读、执行 读 drwx r-x r- x ( 7 5 5 ) 读、写、执行 读、执行 读、执行 如果把同组用户或其他用户针对某一目录的权限设置为 - - x,那么他们将无法列出该目录 中的文件。如果该目录中有一个执行位置位的脚本或程序,只要用户知道它的路径和文件名, 仍然可以执行它。用户不能够进入该目录并不妨碍他的执行。 目录的权限将会覆盖该目录中文件的权限。例如,如果目录 d o c s具有如下的权限: 而其中的文件p a y的权限为: 那么a d m i n组的用户将无法编辑该文件,因为它所属的目录不具有这样的权限。 该文件对任何用户都可读,但由于它所在的目录并未给 a d m i n组的用户赋予执行权限,所 以该组的用户都将无法访问该目录,他们将会得到“访问受限”的错误消息。 1.6 suid/guid 我们在前面曾经提到过 s u i d和g u i d。这种权限位近年来成为一个棘手的问题。很多系统供 第1章 文件安全与权限 7下载 应商不允许实现这一位,或者即使它被置位,也完全忽略它的存在,因为它会带来安全性风 险。那么人们为何如此大惊小怪呢? s u i d意味着如果某个用户对属于自己的 s h e l l脚本设置了这种权限,那么其他用户在执行这 一脚本时也会具有其属主的相应权限。于是,如果根用户的某一个脚本设置了这样的权限, 那么其他普通用户在执行它的期间也同样具有根用户的权限。同样的原则也适用于 g u i d,执 行相应脚本的用户将具有该文件所属用户组中用户的权限。 1.6.1 为什么要使用suid/guid 为什么要使用这种类型的脚本?这里有一个很好的例子。我管理着几个大型的数据库系 统,而对它们进行备份需要有系统管理权限。我写了几个脚本,并设置了它们的 g u i d,这样 我指定的一些用户只要执行这些脚本就能够完成相应的工作,而无须以数据库管理员的身份 登录,以免不小心破坏了数据库服务器。通过执行这些脚本,他们可以完成数据库备份及其 他管理任务,但是在这些脚本运行结束之后,他们就又回复到他们作为普通用户的权限。 有相当一些U N I X命令也设置了s u i d和g u i d。如果想找出这些命令,可以进入 / b i n或/ s b i n目 录,执行下面的命令: $ ls -l | grep '^...s' 上面的命令是用来查找s u i d文件的; $ ls -l | grep '^...s..s' 上面的命令是用来查找s u i d和g u i d的。 现在我们明白了什么是s u i d,可是如何设置它呢?下面就来介绍这个问题。如果希望设置 s u i d,那么就将相应的权限位之前的那一位设置为 4;如果希望设置g u i d,那么就将相应的权限 位之前的那一位设置为2;如果希望两者都置位,那么将相应的权限位之前的那一位设置为4+2。 一旦设置了这一位,一个 s将出现在x的位置上。记住:在设置 s u i d或g u i d的同时,相应的 执行权限位必须要被设置。例如,如果希望设置 g u i d,那么必须要让该用户组具有执行权限。 如果想要对文件 l o g i n设置s u i d,它当前所具有的权限为 rwx rw- r-- (741),需要在使用 c h m o d命令时在该权限数字的前面加上一个 4,即chmod 4741,这将使该文件的权限变为 r w s rw- r- -。 $ chmod 4741 logit 1.6.2 设置suid/guid的例子 下面给出几个例子: 表1-7 设置s u i d / g u i d 命令 结果 含义 chmod 4755 rws r-x r- x 文文件被设置了s u i d,文件属主具有读、写和执行的权限,所有其 他用户具有读和执行的权限 chmod 6711 rws --s --s 文文件被设置了 s u i d和g u i d,文件属主具有读、写和执行的权限, 所有其他用户具有执行的权限 chmod 4764 rws rw- r- - 文文件被设置了s u i d,文件属主具有读、写和执行的权限,同组用 户具有读和执行的权限,其他用户具有读权限 8 第一部分 shell 下载 还可以使用符号方式来设置 s u i d / g u i d。如果某个文件具有这样的权限: rwx r-x r- x,那么 可以这样设置其s u i d: chmod u+s 于是该文件的权限将变为: rws r-x r-x 在查找设置了s u i d的文件时,没准会看到具有这样权限的文件:rwS r-x r- x,其中S为大写。 它表示相应的执行权限位并未被设置,这是一种没有什么用处的s u i d设置,可以忽略它的存在。 注意,c h m o d命令不进行必要的完整性检查,可以给某一个没用的文件赋予任何权限,但 chmod 命令并不会对所设置的权限组合做什么检查。因此,不要看到一个文件具有执行权限, 就认为它一定是一个程序或脚本。 1.7 chown和chgrp 当你创建一个文件时,你就是该文件的属主。一旦你拥有某个文件,就可以改变它的所 有权,把它的所有权交给另外一个 / e t c / p a s s w d文件中存在的合法用户。可以使用用户名或用 户I D号来完成这一操作。在改变一个文件的所有权时,相应的 s u i d也将被清除,这是出于安 全性的考虑。只有文件的属主和系统管理员可以改变文件的所有权。一旦将文件的所有权交 给另外一个用户,就无法再重新收回它的所有权。如果真的需要这样做,那么就只有求助于 系统管理员了。 c h o w n命令的一般形式为: chmod -R -h owner file - R选项意味着对所有子目录下的文件也都进行同样的操作。 - h选项意味着在改变符号链 接文件的属主时不影响该链接所指向的目标文件。 1.7.1 chown举例 这里给出几个例子: 文件p r o j e c t的所有权现在由用户l o u i s e交给了用户p a u l i n e。 1.7.2 chgrp举例 c h g r p命令和c h o w n命令的格式差不多,下面给出一个例子。 用户p a u l i n e现在把该文件所属的组由a d m i n变为s y b a d m i n(系统中的另外一个用户组)。 1.7.3 找出你所属于的用户组 如果你希望知道自己属于哪些用户组,可以用如下的命令: 第1章 文件安全与权限 9下载 或者可以使用i d命令: 1.7.4 找出其他用户所属于的组 为了找出其他用户所属于的组,可以用如下的命令: 上面的命令告诉我们用户m a t t y属于s y b a d m i n、a p p s g e n和p o s t用户组。 1.8 umask 当最初登录到系统中时, u m a s k命令确定了你创建文件的缺省模式。这一命令实际上和 c h m o d命令正好相反。你的系统管理员必须要为你设置一个合理的 u m a s k值,以确保你创建的 文件具有所希望的缺省权限,防止其他非同组用户对你的文件具有写权限。 在已经登录之后,可以按照个人的偏好使用 u m a s k命令来改变文件创建的缺省权限。相应 的改变直到退出该s h e l l或使用另外的u m a s k命令之前一直有效。 一般来说,u m a s k命令是在/ e t c / p r o f i l e文件中设置的,每个用户在登录时都会引用这个文 件,所以如果希望改变所有用户的 u m a s k,可以在该文件中加入相应的条目。如果希望永久性 地设置自己的u m a s k值,那么就把它放在自己$ H O M E目录下的. p r o f i l e或. b a s h _ p r o f i l e文件中。 1.8.1 如何计算umask值 u m a s k命令允许你设定文件创建时的缺省模式,对应每一类用户 (文件属主、同组用户、 其他用户)存在一个相应的u m a s k值中的数字。对于文件来说,这一数字的最大值分别是 6。系 统不允许你在创建一个文本文件时就赋予它执行权限,必须在创建后用 c h m o d命令增加这一 权限。目录则允许设置执行权限,这样针对目录来说, u m a s k中各个数字最大可以到7。 该命令的一般形式为: umask nnn 其中n n n为u m a s k置0 0 0 - 7 7 7。 让我们来看一些例子。 计算出你的u m a s k值: 可以有几种计算u m a s k值的方法,通过设置 u m a s k值,可以为新创建的文件和目录设置缺 省权限。表1 - 8列出了与权限位相对应的u m a s k值。 在计算u m a s k值时,可以针对各类用户分别在这张表中按照所需要的文件 /目录创建缺省 权限查找对应的u m a s k值。 例如,u m a s k值002 所对应的文件和目录创建缺省权限分别为 6 6 4和7 7 5。 还有另外一种计算 u m a s k值的方法。我们只要记住 u m a s k是从权限中“拿走”相应的位即 可。 10 第一部分 shell 下载 表1-8 umask值与权限 u m a s k 文件 目录 067 166 245 344 423 522 601 700 例如,对于u m a s k值0 0 2,相应的文件和目录缺省创建权限是什么呢? 第一步,我们首先写下具有全部权限的模式,即 7 7 7 (所有用户都具有读、写和执行权限 )。 第二步,在下面一行按照u m a s k值写下相应的位,在本例中是 0 0 2。 第三步,在接下来的一行中记下上面两行中没有匹配的位。这就是目录的缺省创建权限。 稍加练习就能够记住这种方法。 第四步,对于文件来说,在创建时不能具有文件权限,只要拿掉相应的执行权限比特即 可。 这就是上面的例子,其中u m a s k值为0 0 2: 1) 文件的最大权限 rwx rwx rwx (777) 2) umask值为0 0 2 - - - - - - -w- 3) 目录权限 rwx rwx r-x (775) 这就是目录创建缺省权限 4) 文件权限 rw- rw- r-- (664) 这就是文件创建缺省权限 下面是另外一个例子,假设这次 u m a s k值为0 2 2: 1) 文件的最大权限 rwx rwx rwx (777) 2 ) u m a s k值为0 2 2 - - - -w- -w- 3) 目录权限 rwx r-x r-x (755) 这就是目录创建缺省权限 4) 文件权限 rw- r-- r-- (644) 这就是文件创建缺省权限 1.8.2 常用的umask值 表1 - 9列出了一些u m a s k值及它们所对应的目录和文件权限。 表1-9 常用的u m a s k值及对应的文件和目录权限 u m a s k值目录文件 0 2 2 7 5 5 6 4 4 0 2 7 7 5 0 6 4 0 0 0 2 7 7 5 6 6 4 0 0 6 7 7 1 6 6 0 0 0 7 7 7 0 6 6 0 如果想知道当前的umask 值,可以使用u m a s k命令: 第1章 文件安全与权限 11下载 如果想要改变u m a s k值,只要使用u m a s k命令设置一个新的值即可: $ umask 002 确认一下系统是否已经接受了新的 u m a s k值: 在使用u m a s k命令之前一定要弄清楚到底希望具有什么样的文件 /目录创建缺省权限。否 则可能会得到一些非常奇怪的结果;例如,如果将 u m a s k值设置为6 0 0,那么所创建的文件/目 录的缺省权限就是0 6 6! 1.9 符号链接 存在两种不同类型的链接,软链接和硬链接,这里我们只讨论软链接。软链接实际上就 是一个指向文件的指针。你将会发现这种软链接使用起来非常方便。 1.9.1 使用软链接来保存文件的多个映像 下面我们就解释一下符号链接是怎么回事。比方说在 / u s r / l o c a l / a d m i n / s a l e s目录下有一个 含有销售信息的文件,销售部门的每一个人都想看这份文件。你可以在每一位用户的 $ H O M E 目录下建立一个指向该文件的链接,而不是在每个目录下拷贝一份。这样当需要更改这一文 件时,只需改变一个源文件即可。每个销售 $ H O M E目录中的链接可以起任何名字,不必和源 文件一致。 如果有很多子目录,而进入这些目录很费时间,在这种情况下链接也非常有用。可以针 对$ H O M E目录下的一个很深的子目录创建一个链接。还有,比如在安装一个应用程序时,它 的日志被保存到/ u s r / o p t / a p p / l o g目录下,如果想把它保存在另外一个你认为更方便目录下,可 以建立一个指向该目录的链接。 该命令的一般形式为: ln [-s] source_path target_path 其中的路径可以是目录也可以是文件。让我们来看几个例子。 1.9.2 符号链接举例 假如系统中有 4 0个销售和管理用户,销售用户使用一个销售应用程序,而管理用户使用 一个管理应用程序。我作为系统管理员该怎么做呢?首先删除它们各自 $ H O M E目录下的所 有. p r o f i l e文件。然后在/ u s r / l o c a l / m e n u s /目录下创建两个p r o f i l e文件,一个是s a l e s . p r o f i l e,一 个是a d m i n . p r o f i l e,它们分别为销售和管理人员提供了所需的环境,并引导他们进入相应的应 用程序。现在我在所有销售人员的 $ H O M E目录下分别创建一个指向 s a l e s . p r o f i l e的链接,在所 有管理人员的$ H O M E目录下分别创建一个指向 a d m i n . p r o f i l e文件的链接。注意,不必在上面 命令格式中的t a rg e t _ p a t h端创建相应文件,如果不存在这样一个文件, l n命令会自动创建该文 12 第一部分 shell 下载 件。下面就是我对销售人员 m a t t y所做的操作。 (你所看到的可能会与此稍有差别 )。 这就是我所要做的全部工作;对于管理人员也是如此。而且如果需要作任何修改的话, 只要改变销售和管理人员的 p r o f i l e文件即可,而不必对4 0个用户逐一进行修改。 下面是另外一个例子。我所管理的系统中有一个网络监视器,它将日志写在 / u s r / o p t / m o n i t o r / r e g s t a r目录下,但其他所有的日志都保存在 / v a r / a d m / l o g s目录下,这样只需在该目录 下建立一个指向原有文件的链接就可以在一个地方看所有的日志了,而不必花费很多时间分 别进入各个相应的目录。下面就是所用的链接命令: $ ln -s /usr/opt/monitor/regstar/reg.log /var/adm/logs/monitor.log 如果链接太多的话,可以删掉一些,不过切记不要删除源文件。 不管是否在同一个文件系统中,都可以创建链接。在创建链接的时候,不要忘记在原有 目录设置执行权限。链接一旦创建,链接目录将具有权限 7 7 7或rwx rwx rwx,但是实际的原 有文件的权限并未改变。 在新安装的系统上,通常要进行这样的操作,在 / v a r目录中创建一个指向/ t m p目录的链接, 因为有些应用程序认为存在 / v a r / t m p目录(然而它实际上并不存在 ),有些应用程序在该目录中 保存一些临时文件。为了使所有的临时文件都放在一个地方,可以使用 l n命令在/ v a r目录下建 立一个指向/ t m p目录的链接。 现在如果我在/ v a r目录中列文件,就能够看到刚才建立的链接: 1.10 小结 本章介绍了一些有关文件安全的基本概念。如果这些命令能够使用得当而且使用得比较 谨慎,应该不会有什么问题。手指轻轻一敲就有可能输入 chmod -R这样的命令,它将改变整 个文件系统的权限,如果没有做备份的话,没有几年的时间恐怕是无法恢复了,所以在输入 这些命令时,千万不要在手指上贴膏药! 是否使用设置了s u i d的脚本完全取决于你自己。如果使用的话,一定要确保能够监控它的 使用,而且不要以根用户身份设置 s u i d。 第1章 文件安全与权限 13下载 下载 第2章使用find和xargs 有时可能需要在系统中查找具有某一特征的文件 (例如文件权限、文件属主、文件长度、 文件类型等等)。这样做可能有很多原因。可能出于安全性的考虑,或是一般性的系统管理任 务,或许只是为了找出一个不知保存在什么地方的文件。 F i n d是一个非常有效的工具,它可 以遍历当前目录甚至于整个文件系统来查找某些文件或目录。 在本章中,我们介绍以下内容: • find命令选项。 • 使用f i n d命令不同选项的例子。 • 配合f i n d使用x a rg s命令的例子。 由于f i n d具有如此强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间 来了解一下。即使系统中含有网络文件系统 ( N F S ),f i n d命令在该文件系统中同样有效,只要 你具有相应的权限。 在运行一个非常消耗资源的 f i n d命令时,很多人都倾向于把它放在后台执行,因为遍历一 个大的文件系统可能会花费很长的时间 (这里是指3 0 G字节以上的文件系统)。 F i n d命令的一般形式为: find pathname -options [-print -exec -ok] 让我们来看看该命令的参数: pathname find命令所查找的目录路径。例如用 .来表示当前目录,用/来表示系统根目录。 -print find命令将匹配的文件输出到标准输出。 -exec find命令对匹配的文件执行该参数所给出的 s h e l l命令。相应命令的形式为 ' c o m m - and' {} \;,注意{ }和\;之间的空格。 -ok 和- e x e c的作用相同,只不过以一种更为安全的模式来执行该参数所给出的 s h e l l命令, 在执行每一个命令之前,都会给出提示,让用户来确定是否执行。 2.1 find命令选项 f i n d命令有很多选项或表达式,每一个选项前面跟随一个横杠 -。让我们先来看一下该命 令的主要选项,然后再给出一些例子。 -name 按照文件名查找文件。 -perm 按照文件权限来查找文件。 -prune 使用这一选项可以使f i n d命令不在当前指定的目录中查找,如果同时使用了 - d e p t h 选项,那么- p r u n e选项将被f i n d命令忽略。 -user 按照文件属主来查找文件。 -group 按照文件所属的组来查找文件。 -mtime -n +n 按照文件的更改时间来查找文件, - n表示文件更改时间距现在n天以内,+ n 表示文件更改时间距现在 n天以前。F i n d命令还有- a t i m e和- c t i m e选项,但它们都和- m t i m e选项 相似,所以我们在这里只介绍 - m t i m e选项。 -nogroup 查找无有效所属组的文件,即该文件所属的组在 / e t c / g r o u p s中不存在。 -nouser 查找无有效属主的文件,即该文件的属主在 / e t c / p a s s w d中不存在。 -newer file1 ! file2 查找更改时间比文件f i l e 1新但比文件f i l e 2旧的文件。 -type 查找某一类型的文件,诸如: b - 块设备文件。 d - 目录。 c - 字符设备文件。 p - 管道文件。 l - 符号链接文件。 f - 普通文件。 -size n[c] 查找文件长度为n块的文件,带有c时表示文件长度以字节计。 -depth 在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找。 -fstype 查找位于某一类型文件系统中的文件,这些文件系统类型通常可以在配置文件 / e t c / f s t a b中找到,该配置文件中包含了本系统中有关文件系统的信息。 -mount 在查找文件时不跨越文件系统 m o u n t点。 -follow 如果f i n d命令遇到符号链接文件,就跟踪至链接所指向的文件。 -cpio 对匹配的文件使用c p i o命令,将这些文件备份到磁带设备中。 2.1.1 使用name选项 文件名选项是f i n d命令最常用的选项,要么单独使用该选项,要么和其他选项一起使用。 可以使用某种文件名模式来匹配文件,记住要用引号将文件名模式引起来。 不管当前路径是什么,如果想要在自己的根目录 $ H O M E中查找文件名符合 * . t x t的文件, 使用~作为' p a t h n a m e参数,波浪号~代表了你的$ H O M E目录。 $ find ~ -name "*.txt" -print 想要在当前目录及子目录中查找所有的‘ * . t x t’文件,可以用: $ find . -name "*.txt" -print 想要的当前目录及子目录中查找文件名以一个大写字母开头的文件,可以用: $ find . -name "[A-Z]*" -print 想要在/ e t c目录中查找文件名以h o s t开头的文件,可以用: $ find /etc -name "host*" -print 想要查找$ H O M E目录中的文件,可以用: $ find ~ -name "*" -print 或find . -print 要想让系统高负荷运行,就从根目录开始查找所有的文件。如果希望在系统管理员那里 保留一个好印象的话,最好在这么做之前考虑清楚! $ find / -name "*" -print 如果想在当前目录查找文件名以两个小写字母开头,跟着是两个数字,最后是 * . t x t的文 件,下面的命令就能够返回名为 a x 3 7 . t x t的文件: 第2章使用f i n d和x a rg s 15下载 $ find . -name "[a-z][a-z][0--9][0--9].txt" -print 2.1.2 使用perm选项 如果希望按照文件权限模式来查找文件的话,可以采用 - p e r m选项。你可能需要找到所有 用户都具有执行权限的文件,或是希望查看某个用户目录下的文件权限类型。在使用这一选 项的时候,最好使用八进制的权限表示法。 为了在当前目录下查找文件权限位为 7 5 5的文件,即文件属主可以读、写、执行,其他用 户可以读、执行的文件,可以用: $ find . -perm 755 -print 如果希望在当前目录下查找所有用户都可读、写、执行的文件(要小心这种情况),我们 可以使用f i n d命令的- p e r m选项。在八进制数字前面要加一个横杠 -。在下面的命令中 - p e r m代 表按照文件权限查找,而‘ 0 0 7’和你在c h m o d命令的绝对模式中所采用的表示法完全相同。 $ find . -perm -007 -print 2.1.3 忽略某个目录 如果在查找文件时希望忽略某个目录,因为你知道那个目录中没有你所要查找的文件, 那么可以使用- p r u n e选项来指出需要忽略的目录。在使用 - p r u n e选项时要当心,因为如果你同 时使用了- d e p t h选项,那么- p r u n e选项就会被f i n d命令忽略。 如果希望在/ a p p s目录下查找文件,但不希望在 / a p p s / b i n目录下查找,可以用: $ find /apps -name "/apps/bin" -prune -o -print 2.1.4 使用user和nouser选项 如果希望按照文件属主查找文件,可以给出相应的用户名。例如,在 $ H O M E目录中查找 文件属主为d a v e的文件,可以用: $ find ~ -user dave -print 在/ e t c目录下查找文件属主为u u c p的文件: $ find /etc -user uucp -print 为了查找属主帐户已经被删除的文件,可以使用 - n o u s e r选项。这样就能够找到那些属主 在/ e t c / p a s s w d文件中没有有效帐户的文件。在使用 - n o u s e r选项时,不必给出用户名; f i n d命令 能够为你完成相应的工作。例如,希望在 / h o m e目录下查找所有的这类文件,可以用: $ find /home -nouser -print 2.1.5 使用group和nogroup选项 就像u s e r和n o u s e r选项一样,针对文件所属于的用户组, f i n d命令也具有同样的选项,为 了在/ a p p s目录下查找属于a c c t s用户组的文件,可以用: $ find /apps -group accts -print 要查找没有有效所属用户组的所有文件,可以使用 n o g r o u p选项。下面的f i n d命令从文件 系统的根目录处查找这样的文件 $ fine/-nogroup-print 16 第一部分 shell 下载 2.1.6 按照更改时间查找文件 如果希望按照更改时间来查找文件,可以使用 m t i m e选项。如果系统突然没有可用空间了, 很有可能某一个文件的长度在此期间增长迅速,这时就可以用 m t i m e选项来查找这样的文件。 用减号-来限定更改时间在距今 n日以内的文件,而用加号 +来限定更改时间在距今 n日以前的 文件。 希望在系统根目录下查找更改时间在 5日以内的文件,可以用: $ find / -mtime -5 -print 为了在/ v a r / a d m目录下查找更改时间在3日以前的文件,可以用: $ find /var/adm -mtime +3 -print 2.1.7 查找比某个文件新或旧的文件 如果希望查找更改时间比某个文件新但比另一个文件旧的所有文件,可以使用 - n e w e r选 项。它的一般形式为: newest_file_name ! oldest_file_name 其中,!是逻辑非符号。 这里有两个文件,它们的更改时间大约相差两天。 下面给出的f i n d命令能够查找更改时间比文件 a g e . a w k新但比文件b e l t s . a w k旧的文件: 如果想使用f i n d命令的这一选项来查找更改时间在两个小时以内的文件,除非有一个现成 的文件其更改时间恰好在两个小时以前,否则就没有可用来比较更改时间的文件。为了解决 这一问题,可以首先创建一个文件并将其日期和时间戳设置为所需要的时间。这可以用 t o u c h 命令来实现。 假设现在的时间是 2 3 : 4 0,希望查找更改时间在两个小时以内的文件,可以首先创建这样 一个文件: 一个符合要求的文件已经被创建;这里我们假设今天是五月四日,而该文件的更改时间 是2 1 : 4 0,比现在刚好早两个小时。 现在我们就可以使用f i n d命令的- n e w e r选项在当前目录下查找所有更改时间在两个小时以 内的文件: $ find . -newer dstamp -print 2.1.8 使用type选项 U N I X或L I N U X系统中有若干种不同的文件类型,这部分内容我们在前面的章节已经做了 第2章使用f i n d和x a rg s 17下载 介绍,这里就不再赘述。如果要在 / e t c目录下查找所有的目录,可以用: $ find /etc -type d -print 为了在当前目录下查找除目录以外的所有类型的文件,可以用: $ find . ! -type d -print 为了在/ e t c目录下查找所有的符号链接文件,可以用: $ find /etc -type l -print 2.1.9 使用size选项 可以按照文件长度来查找文件,这里所指的文件长度既可以用块( b l o c k)来计量,也可 以用字节来计量。以字节计量文件长度的表达形式为 N c;以块计量文件长度只用数字表示即 可。 就我个人而言,我总是使用以字节计的方式,在按照文件长度查找文件时,大多数人都 喜欢使用这种以字节表示的文件长度,而不用块的数目来表示,除非是在查看文件系统的大 小,因为这时使用块来计量更容易转换。 为了在当前目录下查找文件长度大于 1 M字节的文件,可以用: $ find . -size +1000000c -print 为了在/ h o m e / a p a c h e目录下查找文件长度恰好为 1 0 0字节的文件,可以用: $ find /home/apache -size 100c -print 为了在当前目录下查找长度超过 1 0块的文件(一块等于5 1 2字节),可以用: $ find . -size +10 -print 2.1.10 使用depth选项 在使用f i n d命令时,可能希望先匹配所有的文件,再在子目录中查找。使用 d e p t h选项就 可以使f i n d命令这样做。这样做的一个原因就是,当在使用 f i n d命令向磁带上备份文件系统时, 希望首先备份所有的文件,其次再备份子目录中的文件。 在下面的例子中, f i n d命令从文件系统的根目录开始,查找一个名为 C O N . F I L E的文件。 它将首先匹配所有的文件然后再进入子目录中查找。 $ find / -name "CON.FILE" -depth -print 2.1.11 使用mount选项 在当前的文件系统中查找文件(不进入其他文件系统),可以使用f i n d命令的m o u n t选项。 在下面的例子中,我们从当前目录开始查找位于本文件系统中文件名以 X C结尾的文件: $ find . -name "*.XC" -mount -print 2.1.12 使用cpio选项 c p i o命令可以用来向磁带设备备份文件或从中恢复文件。可以使用 f i n d命令在整个文件系 统中(更多的情况下是在部分文件系统中)查找文件,然后用 c p i o命令将其备份到磁带上。 如果希望使用c p i o命令备份/ e t c、/ h o m e和/ a p p s目录中的文件,可以使用下面所给出的命 令,不过要记住你是在文件系统的根目录下: 18 第一部分 shell 下载 (在上面的例子中,第一行末尾的 \告诉s h e l l命令还未结束,忽略\后面的回车。) 在上面的例子中,应当注意到路径中缺少 /。这叫作相对路径。之所以使用相对路径,是 因为在从磁带中恢复这些文件的时候,可以选择恢复文件的路径。例如,可以将这些文件先 恢复到另外一个目录中,对它们进行某些操作后,再恢复到原始目录中。如果在备份时使用 了绝对路径,例如 / e t c,那么在恢复时,就只能恢复到 / e t c目录中去,别无其他选择。在上面 的例子中,我告诉 f i n d命令首先进入/ e t c目录,然后是/ h o m e和/ a p p s目录,先匹配这些目录下 的文件,然后再匹配其子目录中的文件,所有这些结果将通过管道传递给 c p i o命令进行备份。 顺便说一下,在上面的例子中 c p i o命令使用了C 6 5 5 3 6选项,我本可以使用B选项,不过这 样每块的大小只有 5 1 2 字节,而使用了 C 6 5 5 3 6 选项后,块的大小变成了 6 4 K 字节 (6 5 5 3 6 / 1 0 2 4)。 2.1.13 使用exec或ok来执行shell命令 当匹配到一些文件以后,可能希望对其进行某些操作,这时就可以使用 - e x e c选项。一旦 f i n d命令匹配到了相应的文件,就可以用 - e x e c选项中的命令对其进行操作(在有些操作系统 中只允许- e x e c选项执行诸如l s或ls -l这样的命令)。大多数用户使用这一选项是为了查找旧文 件并删除它们。这里我强烈地建议你在真正执行 r m命令删除文件之前,最好先用 l s命令看一 下,确认它们是所要删除的文件。 e x e c选项后面跟随着所要执行的命令,然后是一对儿 { },一个空格和一个 \,最后是一个 分号。 为了使用e x e c选项,必须要同时使用 p r i n t选项。如果验证一下f i n d命令,会发现该命令只 输出从当前路径起的相对路径及文件名。 为了用ls -l命令列出所匹配到的文件,可以把 ls -l命令放在f i n d命令的- e x e c选项中,例如: 上面的例子中,f i n d命令匹配到了当前目录下的所有普通文件,并在 - e x e c选项中使用ls -l 命令将它们列出。 为了在/ l o g s目录中查找更改时间在5日以前的文件并删除它们,可以用: $ find logs -type f -mtime +5 -exec rm {} \; 记住,在s h e l l中用任何方式删除文件之前,应当先查看相应的文件,一定要小心! 当使用诸如m v或r m命令时,可以使用- e x e c选项的安全模式。它将在对每个匹配到的文件 进行操作之前提示你。在下面的例子中, f i n d命令在当前目录中查找所有文件名以 . L O G结尾、 更改时间在5日以上的文件,并删除它们,只不过在删除之前先给出提示。 按y键删除文件,按n键不删除。 任何形式的命令都可以在 - e x e c选项中使用。在下面的例子中我们使用 g r e p命令。f i n d命令 第2章使用f i n d和x a rg s 19下载 首先匹配所有文件名为“ p a s s w d *”的文件,例如p a s s w d、p a s s w d . o l d、p a s s w d . b a k,然后执 行g r e p命令看看在这些文件中是否存在一个 r o u n d e r用户。 2.1.14 find命令的例子 我们已经介绍了f i n d命令的基本选项,下面给出 f i n d命令的一些其他的例子。 为了匹配$ H O M E目录下的所有文件,下面两种方法都可以使用: $ find $HOME -print $ find ~ -print 为了在当前目录中查找s u i d置位,文件属主具有读、写、执行权限,并且文件所属组的用 户和其他用户具有读和执行的权限的文件,可以用: $ find . -type f -perm 4755 -print 为了查找系统中所有文件长度为 0的普通文件,并列出它们的完整路径,可以用: $ find / -type f -size 0 -exec ls -l {} \; 为了查找/ v a r / l o g s目录中更改时间在7日以前的普通文件,并删除它们,可以用: $ find /var/logs -type f -mtime +7 -exec rm {} \; 为了查找系统中所有属于a u d i t组的文件,可以用: $find /-name -group audit -print 我们的一个审计系统每天创建一个审计日志文件。日志文件名的最后含有数字,这样我 们一眼就可以看出哪个文件是最新的,哪个是最旧的。 A d m i n . l o g文件编上了序号: a d m i n . l o g . 0 0 1、a d m i n . l o g . 0 0 2等等。下面的 f i n d命令将删除/ l o g s目录中访问时间在 7日以前、 含有数字后缀的a d m i n . l o g文件。该命令只检查三位数字,所以相应日志文件的后缀不要超过 9 9 9。 $ find /logs -name 'admin.log[0-9][0-9][0-9]'-atime +7 -exec rm {} \; 为了查找当前文件系统中的所有目录并排序,可以用: $ find . -type d -print -local -mount |sort 为了查找系统中所有的r m t磁带设备,可以用: $ find /dev/rmt -print 2.2 xargs 在使用f i n d命令的- e x e c选项处理匹配到的文件时, f i n d命令将所有匹配到的文件一起传递 给e x e c执行。不幸的是,有些系统对能够传递给 e x e c的命令长度有限制,这样在 f i n d命令运行 几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是 x a rg s命令的用处所在,特别是与 f i n d命令一起使用。 F i n d命令把匹配到的文件传递给 x a rg s命 令,而x a rg s命令每次只获取一部分文件而不是全部,不像 - e x e c选项那样。这样它可以先处理 最先获取的一部分文件,然后是下一批,并如此继续下去。在有些系统中,使用 - e x e c选项会 为处理每一个匹配到的文件而发起一个相应的进程,并非将匹配到的文件全部作为参数一次 执行;这样在有些情况下就会出现进程过多,系统性能下降的问题,因而效率不高;而使用 20 第一部分 shell 下载 x a rg s命令则只有一个进程。另外,在使用 x a rg s命令时,究竟是一次获取所有的参数,还是分 批取得参数,以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参 数来确定。 让我们来看看x a rg s命令是如何同f i n d命令一起使用的,并给出一些例子。 下面的例子查找系统中的每一个普通文件,然后使用 x a rg s命令来测试它们分别属于哪类 文件: 下面的例子在整个系统中查找内存信息转储文件 (core dump),然后把结果保存到 /tmp/core.log 文件中: $ find . -name "core" -print | xargs echo "" >/tmp/core.log 下面的例子在/ a p p s / a u d i t目录下查找所有用户具有读、写和执行权限的文件,并收回相应 的写权限: $ find /apps/audit -perm -7 -print | xargs chmod o-w 在下面的例子中,我们用g r e p命令在所有的普通文件中搜索 d e v i c e这个词: $ find / -type f -print | xargs grep "device" 在下面的例子中,我们用g r e p命令在当前目录下的所有普通文件中搜索 D B O这个词: $ find . -name \ď-type f -print | xargs grep "DBO" 注意,在上面的例子中,\用来取消f i n d命令中的*在s h e l l中的特殊含义。 2.3 小结 f i n d命令是一个非常优秀的工具,它可以按照用户指定的准则来匹配文件。使用 e x e c和 x a rg s可以使用户对所匹配到的文件执行几乎所有的命令。 第2章使用f i n d和x a rg s 21下载 下载 第3章 后台执行命令 当你在终端或控制台工作时,可能不希望由于运行一个作业而占住了屏幕,因为可能还 有更重要的事情要做,比如阅读电子邮件。对于密集访问磁盘的进程,你可能希望它能够在 每天的非负荷高峰时间段运行。为了使这些进程能够在后台运行,也就是说不在终端屏幕上 运行,有几种选择方法可供使用。 在本章中我们将讨论: • 设置c r o n t a b文件,并用它来提交作业。 • 使用a t命令来提交作业。 • 在后台提交作业。 • 使用n o h u p命令提交作业。 名词解释: cron 系统调度进程。可以使用它在每天的非高峰负荷时间段运行作业,或在一周或一月 中的不同时段运行。 At at命令。使用它在一个特定的时间运行一些特殊的作业,或在晚一些的非负荷高峰时 间段或高峰负荷时间段运行。 & 使用它在后台运行一个占用时间不长的进程。 Nohup 使用它在后台运行一个命令,即使在用户退出时也不受影响。 3.1 cron和crontab c r o n是系统主要的调度进程,可以在无需人工干预的情况下运行作业。有一个叫做 c r o n t a b的命令允许用户提交、编辑或删除相应的作业。每一个用户都可以有一个 c r o n t a b文件 来保存调度信息。可以使用它运行任意一个 s h e l l脚本或某个命令,每小时运行一次,或一周 三次,这完全取决于你。每一个用户都可以有自己的 c r o n t a b文件,但在一个较大的系统中, 系统管理员一般会禁止这些文件,而只在整个系统保留一个这样的文件。系统管理员是通过 c r o n . d e n y和c r o n . a l l o w这两个文件来禁止或允许用户拥有自己的 c r o n t a b文件。 3.1.1 crontab的域 为了能够在特定的时间运行作业,需要了解 c r o n t a b文件每个条目中各个域的意义和格式。 下面就是这些域: 第1列分钟1~5 9 第2列小时1~2 3(0表示子夜) 第3列日1~3 1 第4列月1~1 2 第5列星期0~6(0表示星期天) 第6列 要运行的命令 下面是c r o n t a b的格式: 分< >时< >日< >月< >星期< >要运行的命令 其中< >表示空格。 C r o n t a b文件的一个条目是从左边读起的,第一列是分,最后一列是要运行的命令,它位 于星期的后面。 在这些域中,可以用横杠 -来表示一个时间范围,例如你希望星期一至星期五运行某个作 业,那么可以在星期域使用 1 - 5来表示。还可以在这些域中使用逗号“,”,例如你希望星期一 和星期四运行某个作业,只需要使用 1 , 4来表示。可以用星号 *来表示连续的时间段。如果你 对某个表示时间的域没有特别的限定,也应该在该域填入 *。该文件的每一个条目必须含有 5 个时间域,而且每个域之间要用空格分隔。该文件中所有的注释行要在行首用 #来表示。 3.1.2 crontab条目举例 这里有c r o n t a b文件条目的一些例子: 30 21* * * /apps/bin/cleanup.sh 上面的例子表示每晚的2 1 : 3 0运行/ a p p s / b i n目录下的c l e a n u p . s h。 45 4 1,10,22 * * /apps/bin/backup.sh 上面的例子表示每月1、1 0、2 2日的4 : 4 5运行/ a p p s / b i n目录下的b a c k u p . s h。 10 1 * * 6,0 /bin/find -name "core" -exec rm {} \; 上面的例子表示每周六、周日的 1 : 1 0运行一个f i n d命令。 0,30 18-23 * * * /apps/bin/dbcheck.sh 上面的例子表示在每天1 8 : 0 0至2 3 : 0 0之间每隔3 0分钟运行/ a p p s / b i n目录下的d b c h e c k . s h。 0 23 * * 6 /apps/bin/qtrend.sh 上面的例子表示每星期六的 11 : 0 0 p m运行/ a p p s / b i n目录下的q t r e n d . s h。 你可能已经注意到上面的例子中,每个命令都给出了绝对路径。当使用 c r o n t a b运行s h e l l 脚本时,要由用户来给出脚本的绝对路径,设置相应的环境变量。记住,既然是用户向 c r o n 提交了这些作业,就要向 c r o n提供所需的全部环境。不要假定 c r o n知道所需要的特殊环境,它 其实并不知道。所以你要保证在 s h e l l脚本中提供所有必要的路径和环境变量,除了一些自动 设置的全局变量。 如果c r o n不能运行相应的脚本,用户将会收到一个邮件说明其中的原因。 3.1.3 crontab命令选项 c r o n t a b命令的一般形式为: Crontab [-u user] -e -l -r 其中: -u 用户名。 -e 编辑c r o n t a b文件。 -l 列出c r o n t a b文件中的内容。 -r 删除c r o n t a b文件。 如果使用自己的名字登录,就不用使用 - u选项,因为在执行 c r o n t a b命令时,该命令能够 第3章 后台执行命令 23下载 知道当前的用户。 3.1.4 创建一个新的crontab文件 在考虑向 c r o n进程提交一个 c r o n t a b文件之前,首先要做的一件事情就是设置环境变量 E D I TO R。c r o n进程根据它来确定使用哪个编辑器编辑 c r o n t a b文件。9 9 %的U N I X和L I N U X用 户都使用v i,如果你也是这样,那么你就编辑 $ H O M E目录下的. p r o f i l e文件,在其中加入这样 一行: EDITOR=vi; export EDITOR 然后保存并退出。 不妨创建一个名为 < u s e r > c r o n的文件,其中< u s e r >是用户名,例如, d a v e c r o n。在该文件 中加入如下的内容。 保存并退出。确信前面5个域用空格分隔。 在上面的例子中,系统将每隔 1 5分钟向控制台输出一次当前时间。如果系统崩溃或挂起, 从最后所显示的时间就可以一眼看出系统是什么时间停止工作的。在有些系统中,用 t t y 1来表 示控制台,可以根据实际情况对上面的例子进行相应的修改。 为了提交你刚刚创建的c r o n t a b文件,可以把这个新创建的文件作为 c r o n命令的参数: $ crontab davecron 现在该文件已经提交给c r o n进程,它将每隔1 5分钟运行一次。 同时,新创建文件的一个副本已经被放在 / v a r / s p o o l / c r o n目录中,文件名就是用户名(即, d a v e)。 3.1.5 列出crontab文件 为了列出c r o n t a b文件,可以用: 你将会看到和上面类似的内容。可以使用这种方法在 $ H O M E目录中对c r o n t a b文件做一备 份: $ crontab -l > $HOME/mycron 这样,一旦不小心误删了c r o n t a b文件,可以用上一节所讲述的方法迅速恢复。 3.1.6 编辑crontab文件 如果希望添加、删除或编辑 c r o n t a b文件中的条目,而E D I TO R环境变量又设置为v i,那么 就可以用v i来编辑c r o n t a b文件,相应的命令为: $ crontab -e 可以像使用v i编辑其他任何文件那样修改 c r o n t a b文件并退出。如果修改了某些条目或添 24 第一部分 shell 下载 加了新的条目,那么在保存该文件时, c r o n会对其进行必要的完整性检查。如果其中的某个 域出现了超出允许范围的值,它会提示你。 我们在编辑c r o n t a b文件时,没准会加入新的条目。例如,加入下面的一条: 现在保存并退出。最好在 c r o n t a b文件的每一个条目之上加入一条注释,这样就可以知道 它的功能、运行时间,更为重要的是,知道这是哪位用户的作业。 现在让我们使用前面讲过的 crontab -l命令列出它的全部信息: 3.1.7 删除crontab文件 为了删除c r o n t a b文件,可以用: $ crontab -r 3.1.8 恢复丢失的crontab文件 如果不小心误删了c r o n t a b文件,假设你在自己的 $ H O M E目录下还有一个备份,那么可以 将其拷贝到/ v a r / s p o o l / c r o n / < u s e r n a m e >,其中< u s e r n a m e >是用户名。如果由于权限问题无法完 成拷贝,可以用: $ crontab 其中,< f i l e n a m e >是你在$ H O M E目录中副本的文件名。 我建议你在自己的 $ H O M E目录中保存一个该文件的副本。我就有过类似的经历,有数次 误删了c r o n t a b文件(因为r键紧挨在e键的右边⋯)。这就是为什么有些系统文档建议不要直接 编辑c r o n t a b文件,而是编辑该文件的一个副本,然后重新提交新的文件。 有些c r o n t a b的变体有些怪异,所以在使用 c r o n t a b命令时要格外小心。如果遗漏了任何选 项,c r o n t a b可能会打开一个空文件,或者看起来像是个空文件。这时敲 d e l e t e键退出,不要按 < C t r l - D >,否则你将丢失c r o n t a b文件。 3.2 at命令 a t命令允许用户向c r o n守护进程提交作业,使其在稍后的时间运行。这里稍后的时间可能 是指1 0 m i n以后,也可能是指几天以后。如果你希望在一个月或更长的时间以后运行,最好还 是使用c r o n t a b文件。 一旦一个作业被提交, a t命令将会保留所有当前的环境变量,包括路径,不象 c r o n t a b, 只提供缺省的环境。该作业的所有输出都将以电子邮件的形式发送给用户,除非你对其输出 进行了重定向,绝大多数情况下是重定向到某个文件中。 和c r o n t a b一样,根用户可以通过 / e t c目录下的a t . a l l o w和a t . d e n y文件来控制哪些用户可以 第3章 后台执行命令 25下载 使用a t命令,哪些用户不行。不过一般来说,对 a t命令的使用不如对c r o n t a b的使用限制那么严 格。 a t命令的基本形式为: at [-f script] [-m -l -r] [time] [date] 其中, -f script 是所要提交的脚本或命令。 -l 列出当前所有等待运行的作业。 a t q命令具有相同的作用。 -r 清除作业。为了清除某个作业,还要提供相应的作业标识( I D);有些U N I X变体只 接受a t r m作为清除命令。 -m 作业完成后给用户发邮件。 time at命令的时间格式非常灵活;可以是 H、H H . H H M M、H H : M M或H : M,其中H和M 分别是小时和分钟。还可以使用 a . m .或p . m .。 date 日期格式可以是月份数或日期数,而且 a t命令还能够识别诸如t o d a y、t o m o r r o w这样 的词。 现在就让我们来看看如何提交作业。 3.2.1 使用at命令提交命令或脚本 使用a t命令提交作业有几种不同的形式,可以通过命令行方式,也可以使用 a t命令提示符。 一般来说在提交若干行的系统命令时,我使用 a t命令提示符方式,而在提交 s h e l l脚本时,使用 命令行方式。 如果你想提交若干行的命令,可以在 a t命令后面跟上日期/时间并回车。然后就进入了 a t命 令提示符,这时只需逐条输入相应的命令,然后按‘ < C T R L - D >’退出。下面给出一个例子: 其中,< E O T >就是< C T R L - D >。在2 1 : 1 0系统将执行一个简单的 f i n d命令。你应当已经注 意到,我所提交的作业被分配了一个唯一标识 job 1。该命令在完成以后会将全部结果以邮件 的形式发送给我。 下面就是我从这个邮件中截取的一部分: 下面这些日期/时间格式都是a t命令可以接受的: 26 第一部分 shell 下载 如果希望向a t命令提交一个s h e l l脚本,使用其命令行方式即可。在提交脚本时使用 - f选项。 在上面的例子中,一个叫做 d b _ t a b l e . s h的脚本将在明天下午3 : 0 0运行。 还可以使用e c h o命令向a t命令提交作业: $ echo find /etc -name "passwd" -print | at now +1 minute 3.2.2 列出所提交的作业 一个作业被提交后,可以使用 at -l命令来列出所有的作业: 其中,第一行是作业标识,后面是作业运行的日期 /时间。最后一列a代表a t。还可以使用 a t q命令来完成同样的功能,它是 a t命令的一个链接。当提交一个作业后,它就被拷贝到 / v a r / s p o o l / a t目录中,准备在要求的时间运行。 3.2.3 清除一个作业 清除作业的命令格式为: atrm [job no] 或at -r [job no] 要清除某个作业,首先要执行 at -l命令,以获取相应的作业标识,然后对该作业标识使用 at -r命令,清除该作业。 有些系统使用at-r [job no]命令清除作业。 3.3 &命令 当在前台运行某个作业时,终端被该作业占据;而在后台运行作业时,它不会占据终端。 第3章 后台执行命令 27下载 可以使用&命令把作业放到后台执行。 该命令的一般形式为: 命令 & 为什么要在后台执行命令?因为当在后台执行命令时,可以继续使用你的终端做其他事 情。适合在后台运行的命令有 f i n d、费时的打印作业、费时的排序及一些 s h e l l脚本。在后台运 行作业时要当心:需要用户交互的命令不要放在后台执行,因为这样你的机器就会在那里傻 等。 不过,作业在后台运行一样会将结果输出到屏幕上,干扰你的工作。如果放在后台运行 的作业会产生大量的输出,最好使用下面的方法把它的输出重定向到某个文件中: command >out.file 2>&1 & 在上面的例子中,所有的标准输出和错误输出都将被重定向到一个叫做 out.file 的文件中。 当你成功地提交进程以后,就会显示出一个进程号,可以用它来监控该进程,或杀死它。 3.3.1 向后台提交命令 现在我们运行一个 f i n d命令,查找名为“ s r m . c o n f”的文件,并把所有标准输出和错误输 出重定向到一个叫作f i n d . d t的文件中: 在上面的例子中,在我们成功提交该命令之后,系统给出了它的进程号 2 7 0 1 5。 当该作业完成时,按任意键(一般是回车键)就会出现一个提示: 这里还有另外一个例子,有一个叫做 p s 1的脚本,它能够截断和清除所有的日志文件,我 把它放到后台去执行: 3.3.2 用ps命令查看进程 当一个命令在后台执行的时候,可以用提交命令时所得到的进程号来监控它的运行。在 前面的例子中,我们可以按照提交 p s 1时得到的进程号,用p s命令和g r e p命令列出这个进程: 如果系统不支持ps x命令,可以用: 记住,在用p s命令列出进程时,它无法确定该进程是运行在前台还是后台。 3.3.3 杀死后台进程 如果想杀死后台进程可以使用 k i l l命令。当一个进程被放到后台运行时, s h e l l会给出一个 28 第一部分 shell 下载 进程号,我们可以根据这个进程号,用 k i l l命令杀死该进程。该命令的基本形式为: kill -signal [process_number] 现在暂且不要考虑其中的各种不同信号;我们会在后面的章节对这一问题进行介绍。 在杀进程的时候,执行下面的命令 (你的进程号可能会不同)并按回车键。系统将会给出相 应的信息告诉用户进程已经被杀死。 如果系统没有给出任何信息,告诉你进程已经被杀死,那么不妨等一会儿,也许系统正 在杀该进程,如果还没有回应,就再执行另外一个 k i l l命令,这次带上一个信号选项: 如果用上述方法提交了一个后台进程,那么在退出时该进程将会被终止。为了使后台进 程能够在退出后继续运行,可以使用 n o h u p命令,下面我们就介绍这一命令。 3.4 nohup命令 如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用 n o h u p命令。该命令可以在你退出帐户之后继续运行相应的进程。 N o h u p就是不挂起的意思( n o hang up)。 该命令的一般形式为: nohup command & 3.4.1 使用nohup命令提交作业 如果使用n o h u p命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名 为n o h u p . o u t的文件中,除非另外指定了输出文件: nohup command > myout.file 2>&1 在上面的例子中,输出被重定向到 m y o u t . f i l e文件中。 让我们来看一个例子,验证一下在退出帐户后相应的作业是否能够继续运行。我们先提 交一个名为p s 1的日志清除进程: 现在退出该s h e l l,再重新登录,然后执行下面的命令: 我们看到,该脚本还在运行。如果系统不支持 ps x命令,使用ps -ef|grep ps1命令。 3.4.2 一次提交几个作业 如果希望一次提交几个命令,最好能够把它们写入到一个 s h e l l脚本文件中,并用n o h u p命 令来执行它。例如,下面的所有命令都用管道符号连接在一起;我们可以把这些命令存入一 第3章 后台执行命令 29下载 个文件,并使该文件可执行。 现在让它可执行: $ chmod 744 quarterend 我们还将该脚本的所有输出都重定向到一个名为 q t r. o u t的文件中。 3.5 小结 本章中所讨论的工具主要是有关后台运行作业的。有时我们必须要对大文件进行大量更 改,或执行一些复杂的查找,这些工作最好能够在系统负荷较低时执行。 创建一个定时清理日志文件或完成其他特殊工作的脚本,这样只要提交一次,就可以每 天晚上运行,而且无需你干预,只要看看相应的脚本日志就可以了。 C r o n和其他工具可以使 系统管理任务变得更轻松。 30 第一部分 shell 下载 下载 第4章 文件名置换 当你在使用命令行时,有很多时间都用来查找你所需要的文件。 S h e l l提供了一套完整的 字符串模式匹配规则,或者称之为元字符,这样你就可以按照所要求的模式来匹配文件。还 可以使用字符类型来匹配文件名。在命令行方式下,使用元字符更为快捷,所以在本章我们 只介绍这部分内容。 在本章我们将讨论: • 匹配文件名中的任何字符串。 • 匹配文件名中的单个字符。 • 匹配文件名中的字母或数字字符。 下面就是这些特殊字符: * 匹配文件名中的任何字符串,包括空字符串。 ? 匹配文件名中的任何单个字符。 [...] 匹配[ ]中所包含的任何字符。 [!...] 匹配[ ]中非感叹号!之后的字符。 当s h e l l遇到上述字符时,就会把它们当作特殊字符,而不是文件名中的普通字符,这样 用户就可以用它们来匹配相应的文件名。 4.1 使用* 使用星号*可以匹配文件名中的任何字符串。在下面的例子中,我们给出文件名模式 a p p *, 它的意思是文件名以a p p开头,后面可以跟随任何字符串,包括空字符串: *也可以用在文件名模式的开头,在下面的例子中, * . d o c匹配所有以. d o c结尾的 文件名: *还可以用在文件名的当中,在下面的例子中, c l * . s e d用于匹配所有以 c l开头、后面跟任 何字符串、最后以. s e d结尾的文件名: 在使用c d命令切换路径时,使用星号还可以省去输入整个路径名的麻烦,下面给出一个 这样的例子: 4.2 使用? 使用可以匹配文件名中的任何单个字符。在下面的例子中,我们列出文件名以任意两个 字符开头,接着是R,后面跟任何字符的文件: 在下面的例子中,我们列出文件名以 c o n f开头、中间是任意两个字符、最后以 . l o g结尾的 文件: 在下面的例子中, f ? ? * s匹配所有以f开头、中间是任意两个字符、后面跟随任意字符串、 并以s结尾的文件名: 4.3 使用[...]和[!...] 使用[ . . . ]可以用来匹配方括号[ ]中的任何字符。在这一方法中,还可以使用一个横杠 -来连 接两个字母或数字,以此来表示一个范围。在下面的例子中,列出了以 i或o开头的文件名: 为了匹配所有以l o g .开头、后面跟随一个数字、然后可以是任意字符串的文件名,可以用 l o g . [ 0 - 9 ] *,其中[ 0 - 9 ]表示任意单个数字,星号*代表了其他字符: 下面的例子和刚才的有所不同,使用 [ ! 0 - 9 ] *来表示非数字开头的字符串,其中 !是非的意 思: 下面的例子中,列出了所有以LPS开头、中间可以是任何两个字符,最后以1结尾的文件名: 32 第一部分 shell 下载 下面的例子中,列出了所有以 L P S开头、中间可以是任何两个字符,后面跟随一个非数字 字符、然后是任意字符串的文件名: 为了列出所有以大写字母开头的文件名,可以用: $ ls [A-Z]* 为了列出所有以小写字母开头的文件名,可以用: $ ls [a-z]* 为了列出所有以数字开头的文件名,可以用: $ ls [0-9]* 为了列出所有以 . 开头的文件名(隐含文件,例如 . p r o f i l e、. r h o s t s、. h i s t o r y等等),可以 用: $ ls .* 4.4 小结 使用元字符可以大大减少你在查找文件名上的工作量。这是一种非常有效的模式匹配方 法,在后面的章节中,我们还将在讨论正则表达式的时候对文本处理中所涉及到的元字符进 行更为详尽的讨论。 第4章 文件名置换 33下载 下载 第5章 shell输入与输出 在s h e l l脚本中,可以用几种不同的方式读入数据:可以使用标准输入—缺省为键盘,或 者指定一个文件作为输入。对于输出也是一样:如果不指定某个文件作为输出,标准输出总 是和终端屏幕相关联。如果所使用命令出现了什么错误,它也会缺省输出到屏幕上,如果不 想把这些信息输出到屏幕上,也可以把这些信息指定到一个文件中。 大多数使用标准输入的命令都指定一个文件作为标准输入。如果能够从一个文件中读取 数据,何必要费时费力地从键盘输入呢? 本章我们将讨论以下内容: • 使用标准输入、标准输出及标准错误。 • 重定向标准输入和标准输出。 本章全面讨论了s h e l l对数据和信息的标准输入、标准输出,对重定向也做了一定的介绍。 5.1 echo 使用e c h o命令可以显示文本行或变量,或者把字符串输入到文件。它的一般形式为: echo string e c h o命令有很多功能,其中最常用的是下面几个: \c 不换行。 \f 进纸。 \t 跳格。 \n 换行。 如果希望提示符出现在输出的字符串之后,可以用: 上面的命令将会有如下的显示: 其中“□”是光标。 如果想在输出字符之后,让光标移到下一行,可以用: $ echo "The red pen ran out of ink" 还可以用e c h o命令输出转义符以及变量。在下面的例子中,你可以让终端铃响一声,显 示出$ H O M E目录,并且可以让系统执行 t t y命令(注意,该命令用键盘左上角的符号,法语中 的抑音符引起来,不是单引号, )。 如果是LINUX系统,那么...... 必须使用- n选项来禁止echo命令输出后换行: (续) $ echo -n "What is your name :" 必须使用-e选项才能使转义符生效: 如果希望在e c h o命令输出之后附加换行,可以使用 \ n选项: 运行时会出现如下输出: 还可以在e c h o语句中使用跳格符,记住别忘了加反斜杠 \: 如果是LINUX系统,那么... 别忘了使用- e选项才能使转义符生效: 如果想把一个字符串输出到文件中,使用重定向符号 >。在下面的例子中一个字符串被重 定向到一个名为m y f i l e的文件中: $ echo "The log files have all been done"> myfile 或者可以追加到一个文件的末尾,这意味着不覆盖原有的内容: $ echo "$LOGNAME carried them out at `date`">>myfile 现在让我们看一下m y f i l e文件中的内容: 初涉s h e l l的用户常常会遇到的一个问题就是如何把双引号包含到 e c h o命令的字符串中。 引号是一个特殊字符,所以必须要使用反斜杠 \来使s h e l l忽略它的特殊含义。假设你希望使用 e c h o命令输出这样的字符串:“/ d e v / r m t 0”,那么我们只要在引号前面加上反斜杠 \即可: $ echo "\"/dev/rmt0"\" " / d e v / r m t 0 " 5.2 read 可以使用r e a d语句从键盘或文件的某一行文本中读入信息,并将其赋给一个变量。如果只 第5章 s h e l l输入与输出 35下载 指定了一个变量,那么 r e a d将会把所有的输入赋给该变量,直至遇到第一个文件结束符或回 车。 它的一般形式为: read varible1 varible2 ... 在下面的例子中,只指定了一个变量,它将被赋予直至回车之前的所有内容: 在下面的例子中,我们给出了两个变量,它们分别被赋予名字和姓氏。 s h e l l将用空格作 为变量之间的分隔符: 如果输入文本域过长, Shell 将所有的超长部分赋予最后一个变量。下面的例子,假定要 读取变量名字和姓,但这次输入三个名字;结果如下; 在上面的例子中,如果我们输入字符串 John Lemon Doe,那么第一个单词将被赋给第一 个变量,而由于变量数少于单词数,字符串后面的部分将被全部赋给第二个变量。 在编写s h e l l脚本的时候,如果担心用户会对此感到迷惑,可以采用每一个 r e a d语句只给一 个变量赋值的办法: 用户在运行上面这个脚本的时候,就能够知道哪些信息赋给了哪个变量。 如果是LINUX系统,那么...... 别忘了使用“-n”选项。 36 第一部分 shell 下载 5.3 cat c a t是一个简单而通用的命令,可以用它来显示文件内容,创建文件,还可以用它来显示 控制字符。在使用 c a t命令时要注意,它不会在文件分页符处停下来;它会一下显示完整个文 件。如果希望每次显示一页,可以使用 m o r e命令或把c a t命令的输出通过管道传递到另外一个 具有分页功能的命令中,请看下面的例子: $ cat myfile | more 或 $ cat myfile | pg c a t命令的一般形式为: cat [options] filename1 ... filename2 ... c a t命令最有用的选项就是: -v 显示控制字符 如果希望显示名为m y f i l e的文件,可以用: $ cat myfile 如果希望显示m y f i l e 1、m y f i l e 2、m y f i l e 3这三个文件,可以用: $ cat myfile1 myfile2 myfile3 如果希望创建一个名为b i g f i l e的文件,该文件包含上述三个文件的内容,可以把上面命令 的输出重定向到新文件中: $ cat myfile1 myfile2 myfile3 > bigfile 如果希望创建一个新文件,并向其中输入一些内容,只需使用 c a t命令把标准输出重定向 到该文件中,这时 c a t命令的输入是标准输入— 键盘,你输入一些文字,输入完毕后按 < C T R L - D >结束输入。这真是一个非常简单的文字编辑器! 还可以使用c a t命令来显示控制字符。这里有一个对从 D O S机器上f t p过来的文件进行检察 的例子,在这个例子中,所有的控制字符 < C T R L - M >都在行末显示了出来。 有一点要提醒的是,如果在敲入了 c a t以后就直接按回车,该命令会等你输入字符。如果 你本来就是要输入一些字符,那么它除了会在你输入时在屏幕上显示以外,还会再回显这些 第5章 s h e l l输入与输出 37下载 (续) 内容;最后按< C T R L - D >结束输入即可。 5.4 管道 可以通过管道把一个命令的输出传递给另一个命令作为输入。管道用竖杠 |表示。它的一 般形式为: 命令1 |命令2 其中|是管道符号。 在下面的例子中,在当前目录中执行文件列表操作,如果没有管道的话,所有文件就会 显示出来。当 s h e l l看到管道符号以后,就会把所有列出的文件交给管道右边的命令,因此管 道的含义正如它的名字所暗示的那样:把信息从一端传送到另外一端。在这个例子中,接下 来g r e p命令在文件列表中搜索q u a r t e r 1 . d o c: 让我们再来用一幅图形象地讲解刚才的例子(见图 5 - 1): 图5-1 管道 s e d、a w k和g r e p都很适合用管道,特别是在简单的一行命令中。在下面的例子中, w h o命 令的输出通过管道传递给a w k命令,以便只显示用户名和所在的终端。 如果你希望列出系统中所有的文件系统,可以使用管道把 d f命令的输出传递给 a w k命令, a w k显示出其中的第一列。你还可以再次使用管道把 a w k的结果传递给g r e p命令,去掉最上面 的题头f i l e s y s t e m。 当然,你没准还会希望只显示出其中的分区名,不显示 / d e v /部分,这没问题;我们只要 在后面简单地加上另一个管道符号和相应的 s e d命令即可。 38 第一部分 shell 下载 这就是管道 Is 命令的输出 在这个例子中,我们先对一个文件进行排序,然后通过管道输送到打印机。 $ sort myfile | lp 5.5 tee t e e命令作用可以用字母 T来形象地表示。它把输出的一个副本输送到标准输出,另一个 副本拷贝到相应的文件中。如果希望在看到输出的同时,也将其存入一个文件,那么这个命 令再合适不过了。 它的一般形式为: tee -a files 其中,- a表示追加到文件末尾。 当执行某些命令或脚本时,如果希望把输出保存下来, t e e命令非常方便。 下面我们来看一个例子,我们使用 w h o命令,结果输出到屏幕上,同时保存在 w h o . o u t文 件中: 可以用图5 - 2来表示刚才的例子。 图5-2 tee 在下面的例子中,我们把一些文件备份到磁带上,同时将所备份的文件记录在 t a p e . l o g文 件中。由于需要不断地对文件进行备份,为了保留上一次的日志,我们在 t e e命令中使用了 - a 选项。 在上面的例子中,第一行末尾的反斜杠 \告诉s h e l l该命令尚未结束,应从下面一行继续读 入该命令。 可以在执行脚本之前,使用一个 e c h o命令告诉用户谁在执行这个脚本,输出结果保存在 第5章 s h e l l输入与输出 39下载 screen who命令的输出 $ who who.out 什么地方。 如果不想把输出重定向到文件中,可以不这样做,而是把它定向到某个终端上。在下面 的例子中,一个警告被发送到系统控制台上,表明一个磁盘清理进程即将运行。 $ echo "stand-by disk cleanup starting in 1 minute"| tee /dev/console 可以让不同的命令使用同一个日志文件,不过不要忘记使用 - a选项。 5.6 标准输入、输出和错误 当我们在s h e l l中执行命令的时候,每个进程都和三个打开的文件相联系,并使用文件描 述符来引用这些文件。由于文件描述符不容易记忆, s h e l l同时也给出了相应的文件名。 下面就是这些文件描述符及它们通常所对应的文件名: 文 件 文件描述符 输入文件—标准输入 0 输出文件—标准输出 1 错误输出文件 —标准错误 2 系统中实际上有1 2个文件描述符,但是正如我们在上表中所看到的, 0、1、2是标准输入、 输出和错误。可以任意使用文件描述符 3到9。 5.6.1 标准输入 标准输入是文件描述符0。它是命令的输入,缺省是键盘,也可以是文件或其他命令的输出。 5.6.2 标准输出 标准输出是文件描述符1。它是命令的输出,缺省是屏幕,也可以是文件。 5.6.3 标准错误 标准错误是文件描述符 2。这是命令错误的输出,缺省是屏幕,同样也可以是文件。你可 能会问,为什么会有一个专门针对错误的特殊文件?这是由于很多人喜欢把错误单独保存到 一个文件中,特别是在处理大的数据文件时,可能会产生很多错误。 如果没有特别指定文件说明符,命令将使用缺省的文件说明符(你的屏幕,更确切地说 是你的终端)。 5.7 文件重定向 在执行命令时,可以指定命令的标准输入、输出和错误,要实现这一点就需要使用文件 40 第一部分 shell 下载 重定向。表5 - 1列出了最常用的重定向组合,并给出了相应的文件描述符。 在对标准错误进行重定向时,必须要使用文件描述符,但是对于标准输入和输出来说, 这不是必需的。为了完整起见,我们在表 5 - 1中列出了两种方法。 表5-1 常用文件重定向命令 command > filename 把把标准输出重定向到一个新文件中 command >> filename 把把标准输出重定向到一个文件中 (追加) command 1 > fielname 把把标准输出重定向到一个文件中 command > filename 2>&1 把把标准输出和标准错误一起重定向到一个文件中 command 2 > filename 把把标准错误重定向到一个文件中 command 2 >> filename 把把标准输出重定向到一个文件中 (追加) command >> filename 2>&1 把把标准输出和标准错误一起重定向到一个文件中 (追加) command < filename >filename2 把c o m m a n d命令以 f i l e n a m e文件作为标准输入,以 f i l e n a m e 2文件 作为标准输出 command < filename 把c o m m a n d命令以f i l e n a m e文件作为标准输入 command << delimiter 把从标准输入中读入,直至遇到 d e l i m i t e r分界符 command <&m 把把文件描述符 m作为标准输入 command >&m 把把标准输出重定向到文件描述符 m中 command <&- 把关闭标准输入 5.7.1 重定向标准输出 让我们来看一个标准输出的例子。在下面的命令中,把 / e t c / p a s s w d文件中的用户 I D域按 照用户命排列。该命令的输出重定向到 s o r t . o u t文件中。要提醒注意的是,在使用 s o r t命令的时 候(或其他含有相似输入文件参数的命令 ),重定向符号一定要离开 s o r t命令两个空格,否则该 命令会把它当作输入文件。 $ cat passwd | awk -F: '{print $1}' | sort 1>sort.out 从表5 - 1中可以看出,我们也可以使用如下的表达方式,结果和上面一样: $ cat passwd | awk -F: '{print $1}' | sort >sort.out 可以把很多命令的输出追加到同一文件中。 在上面的例子中,所有的目录名和以 a c c o u n t开头的文件名都被写入到f i l e . o u t文件中。 如果希望把标准输出重定向到文件中,可以用 > f i l e n a m e。在下面的例子中, l s命令的所 有输出都被重定向到l s . o u t文件中: $ ls >ls.out 如果希望追加到已有的文件中 (在该文件不存在的情况下创建该文件 ),那么可以使用 > > f i l e n a m e: 如果想创建一个长度为0的空文件,可以用' > f i l e n a m e ': $ >myfile 第5章 s h e l l输入与输出 41下载 5.7.2 重定向标准输入 可以指定命令的标准输入。在 a w k一章就会遇到这样的情况。下面给出一个这样的例子: $ sort < name.txt 在上面的命令中,s o r t命令的输入是采用重定向的方式给出的,不过也可以直接把相应的 文件作为该命令的参数: $ sort name.txt 在上面的例子中,还可以更进一步地通过重定向为 s o r t命令指定一个输出文件 n a m e . o u t。 这样屏幕上将不会出现任何信息 (除了错误信息以外): $ sort name.out 在发送邮件时,可以用重定向的方法发送一个文件中的内容。在下面的例子中,用户 l o u i s e将收到一个邮件,其中含有文件 c o n t e n t s . t x t中的内容: $ mail louise < contents.txt 重定向操作符command << delimiter是一种非常有用的命令,通常都被称为“此处”文挡。 我们将在本书后面的章节深入讨论这一问题。现在只介绍它的功能。 s h e l l将分界符d e l i m i t e r之 后直至下一个同样的分界符之前的所有内容都作为输入,遇到下一个分界符, s h e l l就知道输 入结束了。这一命令对于自动或远程的例程非常有用。可以任意定义分界符 d e l i m i t e r,最常见 的是E O F,而我最喜欢用 M AY D AY,这完全取决于个人的喜好。还可以在 < <后面输入变量。 下面给出一个例子,我们创建了一个名为 m y f i l e的文件,并在其中使用了 T E R M和L O G N A M E 变量。 5.7.3 重定向标准错误 为了重定向标准错误,可以指定文件描述符 2。让我们先来看一个例子,因为举例子往往 会让人更容易明白。在这个例子中, g r e p命令在文件m i s s i l e s中搜索t r i d e n t字符串: g r e p命令没有找到该文件,缺省地向终端输出了一个错误信息。现在让我们把错误重定 向到文件/ d e v / n u l l中(实际就上是系统的垃圾箱): $ grep "trident" missiles 2>/dev/null 这样所有的错误输出都输送到了 / d e v / n u l l,不再出现在屏幕上。 如果你在对更重要的文件进行操作,可能会希望保存相应的错误。下面就是一个这样的 例子,这一次错误被保存到 g r e p . e r r文件中: 42 第一部分 shell 下载 还可以把错误追加到一个文件中。在使用一组命令完成同一个任务时,这种方法非常有 用。在下面的例子中,两个 g r e p命令把错误都输出到同一个文件中;由于我们使用了 > >符号 进行追加,后面一个命令的错误 (如果有的话)不会覆盖前一个命令的错误。 5.8 结合使用标准输出和标准错误 一个快速发现错误的方法就是,先将输出重定向到一个文件中,然后再把标准错误重定 向到另外一个文件中。下面给出一个例子: 我有两个审计文件,其中一个的确存在,而且包含一些信息,而另一个由于某种原因已 经不存在了(但我不知道)。我想把这两个文件合并到 a c c o u n t s . o u t文件中。 $ cat account_qtr.doc account_end.doc 1>accounts.out 2>accounts.err 现在如果出现了错误,相应的错误将会保存在 a c c o u n t s . e r r文件中。 我事先并不知道是否存在 a c c o u n t _ e n d . d o c文件,使用上面的方法能够快速发现其中的错 误。 5.9 合并标准输出和标准错误 在合并标准输出和标准错误的时候,切记 s h e l l是从左至右分析相应的命令的。下面给出 一个例子: $ cleanup >cleanup.out 2>&1 在上面的例子中,我们将 c l e a n u p脚本的输出重定向到 c l e a n u p . o u t文件中,而且其错误也 被重定向到相同的文件中。 $ grep "standard"* > grep.out 2>&1 在上面的例子中, g r e p命令的标准输出和标准错误都被重定向到 g r e p . o u t文件中。你在使 用前面提到的“此处”文挡时,有可能需要把所有的输出都保存到一个文件中,这样万一出 现了错误,就能够被记录下来。通过使用 2 > & 1就可以做到这一点,下面给出一个例子: 上面的例子演示了如何把所有的输出捕捉到一个文件中。在使用 c a t命令的时候,这可能 第5章 s h e l l输入与输出 43下载 没什么用处,不过如果你使用“此处”文挡连接一个数据库管理系统 (例如使用 i s q l连接 s y b a s e )或使用f t p,这一点就变得非常重要了,因为这样就可以捕捉到所有的错误,以免这些 错误在屏幕上一闪而过,特别是在你不在的时候。 5.10 exec e x e c命令可以用来替代当前s h e l l;换句话说,并没有启动子 s h e l l。使用这一命令时任何现 有环境都将会被清除,并重新启动一个 s h e l l。它的一般形式为: exec command 其中的c o m m a n d通常是一个s h e l l脚本。 我所能够想像得出的描述e x e c命令最贴切的说法就是:它践踏了你当前的 s h e l l。 当这个脚本结束时,相应的会话可能就结束了。 e x e c命令的一个常见用法就是在用户 的. p r o f i l e最后执行时,用它来执行一些用于增强安全性的脚本。如果用户的输入无效,该 s h e l l将被关闭,然后重新回到登录提示符。 e x e c还常常被用来通过文件描述符打开文件。 记住, e x e c在对文件描述符进行操作的时候(也只有在这时),它不会覆盖你当前的 s h e l l。 5.11 使用文件描述符 可以使用e x e c命令通过文件描述符打开和关闭文件。在下面的例子中,我选用了文件描 述符4,实际上我可以在 4到9之间任意选择一个数字。下面的脚本只是从 s t o c k . t x t文件中读了 两行,然后把这两行回显出来。 该脚本的第一行把文件描述符 4指定为标准输入,然后打开 s t o c k . t x t文件。接下来两行的 作用是读入了两行文本。接着,作为标准输入的文件描述符 4被关闭。最后,l i n e 1和l i n e 2两个 变量所含有的内容被回显到屏幕上。 下面是这个小小的股票文件 s t o c k . t x t的内容: 下面是该脚本的运行结果: 上面是一个关于文件描述符应用的简单例子。它看起来没有什么用处。在以后讲解循环 的时候,将会给出一个用文件描述符代替 c p命令拷贝文本文件的例子。 44 第一部分 shell 下载 5.12 小结 本书通篇可见重定向的应用,因为它是 s h e l l中的一个重要部分。通过重定向,可以指定 命令的输入;如果有错误的话,可以用一个单独的文件把它们记录下来,这样就可以方便快 捷地查找问题。 这里没有涉及的就是文件描述符的应用 ( 3~9 )。要想应用这些文件描述符,就一定会涉及 循环方法,在后面讲到循环方法的时候,我们会再次回过头来讲述有关文件描述符的问题。 第5章 s h e l l输入与输出 45下载 下载 第6章 命令执行顺序 在执行某个命令的时候,有时需要依赖于前一个命令是否执行成功。例如,假设你希望 将一个目录中的文件全部拷贝到另外一个目录中后,然后删除源目录中的全部文件。在删除 之前,你希望能够确信拷贝成功,否则就有可能丢失所有的文件。 在本章中,我们将讨论: • 命令执行控制。 • 命令组合。 如果希望在成功地执行一个命令之后再执行另一个命令,或者在一个命令失败后再执行 另一个命令,& &和| |可以完成这样的功能。相应的命令可以是系统命令或 s h e l l脚本。 S h e l l还提供了在当前s h e l l或子s h e l l中执行一组命令的方法,即使用()和 { }。 6.1 使用&& 使用& &的一般形式为: 命令1 && 命令2 这种命令执行方式相当地直接。 & &左边的命令(命令 1)返回真(即返回0,成功被执行) 后,& &右边的命令(命令 2)才能够被执行;换句话说,“如果这个命令执行成功 & &那么执 行这个命令”。 这里有一个使用& &的简单例子: 在上面的例子中, & &前面的拷贝命令执行成功,所以 & &后面的命令( e c h o命令)被执 行。 再看一个更为实用的例子: $ mv /apps/bin /apps/dev/bin && rm -r /apps/bin 在上面的例子中,/ a p p s / b i n目录将会被移到/ a p p s / d e v / b i n目录下,如果它没有被成功执行, 就不会删除/ a p p s / b i n目录。 在下面的例子中,文件 q u a r t e r _ e n d . t x t首先将被排序并输出到文件 q u a r t e r. s o r t e d中,只有 这一命令执行成功之后,文件 q u a r t e r. s o r t e d才会被打印出来: $ sort quarter_end.txt > quarter.sorted && lp quarter.sorted 6.2 使用|| 使用| |的一般形式为: 命令1 || 命令2 | |的作用有一些不同。如果 | |左边的命令(命令 1)未执行成功,那么就执行 | |右边的命令 (命令2);或者换句话说,“如果这个命令执行失败了 || 那么就执行这个命令”。 这里有一个使用| |的简单例子: 在上面的例子中,拷贝命令没有能够被成功执行,因此 | |后面的命令被执行。 这里有一个更为实用的例子。我希望从一个审计文件中抽取第 1个和第5个域,并将其输 出到一个临时文件中,如果这一操作未成功,我希望能够收到一个相应邮件: 在这里不只可以使用系统命令;这里我们首先对 m o n t h _ e n d . t x t文件执行了一个名为c o m e t 的s h e l l脚本,如果该脚本未执行成功,该 s h e l l将结束。 $ comet month_end.txt || exit 6.3 用()和{ }将命令结合在一起 如果希望把几个命令合在一起执行, s h e l l提供了两种方法。既可以在当前 s h e l l也可以在 子s h e l l中执行一组命令。 为了在当前s h e l l中执行一组命令,可以用命令分隔符隔开每一个命令,并把所有的命令 用圆括号()括起来。 它的一般形式为: č命令1;命令2;. . .Ď 如果使用{ }来代替(),那么相应的命令将在子 s h e l l而不是当前s h e l l中作为一个整体被执 行,只有在{ }中所有命令的输出作为一个整体被重定向时,其中的命令才被放到子 s h e l l中执 行,否则在当前s h e l l执行。它的一般形式为: {命令1;命令2;. . . } 我很少单独使用这两种方法。我一般只和 & &或| |一起使用这两种方法。 再回到前面那个c o m e t脚本的例子,如果这个脚本执行失败了,我很可能会希望执行两个 以上的命令,而不只是一个命令。我可以使用这两种方法。这是原先那个例子: $ comet month_end.txt || exit 现在如果该脚本执行失败了,我希望先给自己发个邮件,然后再退出,可以用下面的方 法来实现: 在上面的例子中,如果只使用了命令分隔符而没有把它们组合在一起, s h e l l将直接执行 最后一个命令(e x i t)。 我们再回头来看看前面那个使用 & &排序的例子,下面是原来的那个例子: $ sort quarter_end.txt > quarter.sorted && lp quarter.sorted 使用命令组合的方法,如果 s o r t命令执行成功了,可以先将输出文件拷贝到一个日志区, 第6章 命令执行顺序 47下载 然后再打印。 6.4 小结 在编写s h e l l脚本时,使用 & &和| |对构造判断语句非常有用。如果希望在前一个命令执行 失败的情况不执行后面的命令,那么本章所讲述的方法非常简单有效。使用这样的方法,可 以根据& &或| |前面命令的返回值来控制其后面命令的执行。 48 第一部分 shell 下载 下载 第7章 正则表达式介绍 随着对U N I X和L I N U X熟悉程度的不断加深,需要经常接触到正则表达式这个领域。使用 s h e l l时,从一个文件中抽取多于一个字符串将会很麻烦。例如,在一个文本中抽取一个词, 它的头两个字符是大写的,后面紧跟四个数字。如果不使用某种正则表达式,在 s h e l l中将不 能实现这个操作。 本章内容包括: • 匹配行首与行尾。 • 匹配数据集。 • 只匹配字母和数字。 • 匹配一定范围内的字符串集。 当从一个文件或命令输出中抽取或过滤文本时,可以使用正则表达式( R E),正则表达式 是一些特殊或不很特殊的字符串模式的集合。 为了抽取或获得信息,我们给出抽取操作应遵守的一些规则。这些规则由一些特殊字符 或进行模式匹配操作时使用的元字符组成。也可以使用规则字符作为模式中的一部分进行搜 寻。例如,A将查询A,x将查找字母x。 系统自带的所有大的文本过滤工具在某种模式下都支持正则表达式的使用,并且还包括 一些扩展的元字符集。这里只涉及其中之一,即以字符出现情况进行匹配的表达式,原因是 一些系统将这类模式划分为一组形成基本元字符的集合。这是一个好想法,本书也采用这种 方式。 本章设计的基本元字符使用在 g r e p和s e d命令中,同时结合{ \ \ }(以字符出现情况进行匹配 的元字符)使用在a w k语言中。 表7-1 基本元字符集及其含义 ^ 只只匹配行首 $ 只只匹配行尾 * 只一个单字符后紧跟*,匹配0个或多个此单字符 [ ] 只匹配[ ]内字符。可以是一个单字符,也可以是字符序列。可以使用 - 表示[ ]内字符序列范围,如用[ 1 - 5 ]代替[ 1 2 3 4 5 ] \ 只用来屏蔽一个元字符的特殊含义。因为有时在 s h e l l中一些元字符有 特殊含义。\可以使其失去应有意义 . 只匹配任意单字符 p a t t e r n \ { n \ } 只用来匹配前面 p a t t e r n出现次数。n为次数 p a t t e r n \ { n,\ } m 只含义同上,但次数最少为 n p a t t e r n \ { n,m \ } 只含义同上,但 p a t t e r n出现次数在n与m之间 现在详细讲解其中特殊含义。 第二部分 文 本 过 滤 7.1 使用句点匹配单字符 句点“.”可以匹配任意单字符。例如,如果要匹配一个字符串,以 b e g开头,中间夹一个 任意字符,那么可以表示为 b e g . n,“.”可以匹配字符串头,也可以是中间任意字符。 在ls -l命令中,可以匹配一定权限: . . . x . . x . . x 此格式匹配用户本身,用户组及其他组成员的执行权限。 假定正在过滤一个文本文件,对于一个有 1 0个字符的脚本集,要求前 4个字符之后为X C, 匹配操作如下: . . . .X C. . . . 以上例子解释为前4个字符任意,5,6字符为X C,后4个字符也任意,按下例运行: 注意,“.”允许匹配A S C I I集中任意字符,或为字母,或为数字。 7.2 在行首以^匹配字符串或字符序列 ^只允许在一行的开始匹配字符或单词。例如,使用 ls -l命令,并匹配目录。之所以可以 这样做是因为ls -l命令结果每行第一个字符是 d,即代表一个目录。 回到脚本(1),使用^ 0 0 1,结果将匹配每行开始为0 0 1的字符串或单词: 可以将各种模式结合使用,例如: ^ . . . 4 X C . . . . 结果为: 50 第二部分 文本过滤 下载 以上模式表示,在每行开始,匹配任意 3个字符,后跟4 X C,最后为任意4个字符。^在正 则表达式中使用频繁,因为大量的抽取操作通常在行首。 在行首第4个字符为1,匹配操作表示为: ^ . . . 1 结果为: 行首前4个字符为c o m p,匹配操作表示为: ^ c o m p 假定重新定义匹配模式,行首前 4个字符为c o m p,后面紧跟两个任意字符,并以 i n g结尾, 一种方法为: ^ c o m p . . i n g 以上例子太明显了,不是很有用,但仍讲述了混合使用正则模式的基本概念。 7.3 在行尾以$匹配字符串或字符 可以说$与^正相反,它在行尾匹配字符串或字符, $符号放在匹配单词后。假定要匹配以 单词t r o u b l e结尾的所有行,操作为: t r o u b l e $ 类似的,使用1 d $返回每行以1 d结尾的所有字符串。 如果要匹配所有空行,执行以下操作: ^ $ 具体分析为匹配行首,又匹配行尾,中间没有任何模式,因此为空行。 如果只返回包含一个字符的行,操作如下: ^ . $ 不像空白行,在行首与行尾之间有一个模式,代表任意单字符。 如果在行尾匹配单词j e t 0 1,操作如下: j e t 0 1 $ 7.4 使用*匹配字符串中的单字符或其重复序列 使用此特殊字符匹配任意字符或字符串的重复多次表达式。例如: c o m p u * t 将匹配字符u一次或多次: 另一个例子: 1 0 1 3 3 * 匹配 第7章 正则表达式介绍 51下载 7.5 使用\屏蔽一个特殊字符的含义 有时需要查找一些字符或字符串,而它们包含了系统指定为特殊字符的一个字符。什么 是特殊字符?一般意义上讲,下列字符可以认为是特殊字符: 假定要匹配包含字符“.”的各行而“,”代表匹配任意单字符的特殊字符,因此需要屏蔽 其含义。操作如下: \ . 上述模式不认为反斜杠后面的字符是特殊字符,而是一个普通字符,即句点。 假定要匹配包含^的各行,将反斜杠放在它前面就可以屏蔽其特殊含义。如下: \ ^ 如果要在正则表达式中匹配以 * . p a s结尾的所有文件,可做如下操作: \ * \ . p a s 即可屏蔽字符*的特定含义。 7.6 使用[]匹配一个范围或集合 使用[ ]匹配特定字符串或字符串集,可以用逗号将括弧内要匹配的不同字符串分开,但并 不强制要求这样做(一些系统提倡在复杂的表达式中使用逗号),这样做可以增加模式的可读 性。 使用“-”表示一个字符串范围,表明字符串范围从“ -”左边字符开始,到“ -”右边字 符结束。 如果熟知一个字符串匹配操作,应经常使用 [ ]模式。 假定要匹配任意一个数字,可以使用: [ 0 1 2 3 4 5 6 7 8 9 ] 然而,通过使用“-”符号可以简化操作: [ 0 - 9 ] 或任意小写字母 [ a - z ] 要匹配任意字母,则使用: [ A - Z a - z ] 表明从A - Z、a - z的字母范围。 如要匹配任意字母或数字,模式如下: [ A - Z a - z 0 - 9 ] 在字符序列结合使用中,可以用 [ ]指出字符范围。假定要匹配一单词,以 s开头,中间有 一任意字母,以t结尾,那么操作如下: s[a-z A-Z]t 52 第二部分 文本过滤 下载 上述过程返回大写或小写字母混合的单词,如仅匹配小写字母,可使用: s [ a - z ] t 如要匹配C o m p u t e r或c o m p u t e r两个单词,可做如下操作: [ C c ] o m p u t e r 为抽取诸如S c o u t、s h o u t、b o u g h t等单词,使用下列表达式: [ou] .*t 匹配以字母o或u开头,后跟任意一个字符任意次,并以 t结尾的任意字母。 也许要匹配所有包含s y s t e m后跟句点的所有单词,这里 S可大写或小写。使用如下操作: [ S,s ] y s t e m \ . [ ]在指定模式匹配的范围或限制方面很有用。结合使用 *与[ ]更是有益,例如 [ A - Z a - Z ] *将 匹配所有单词。 [ A - Z a - z ] * 注意^符号的使用,当直接用在第一个括号里,意指否定或不匹配括号里内容。 [^a-zA-Z] 匹配任一非字母型字符,而 [ ^ 0 - 9 ] 匹配任一非数字型字符。 通过最后一个例子,应可猜知除了使用 ^,还有一些方法用来搜索任意一个特殊字符。 7.7 使用\{\}匹配模式结果出现的次数 使用*可匹配所有匹配结果任意次,但如果只要指定次数,就应使用 \ { \ },此模式有三种 形式,即: pattern\{n\} 匹配模式出现n次。 pattern\{n,\} 匹配模式出现最少n次。 pattern\{n,m} 匹配模式出现n到m次之间,n , m为0 - 2 5 5中任意整数。 请看第一个例子,匹配字母 A出现两次,并以B结尾,操作如下: A \ { 2 \ } B 匹配值为A A B 匹配A至少4次,使用: A \ { 4 , \ } B 可以得结果A A A A B或A A A A A A A B,但不能为A A A B。 如给出出现次数范围,例如 A出现2次到4次之间: A \ { 2 , 4 \ } B 则结果为A A B、A A A B、A A A A B,而不是A B或A A A A A B等。 假定从下述列表中抽取代码: 格式如下:前4个字符是数字,接下来是x x,最后4个也是数字,操作如下: 第7章 正则表达式介绍 53下载 [ 0 - 9 ] \ { 4 \ }X X[ 0 - 9 ] \ { 4 \ } 具体含义如下: 1) 匹配数字出现4次。 2) 后跟代码x x。 3) 最后是数字出现4次。 结果为: 在写正则表达式时,可能会有点难度或达不到预期效果,一个好习惯是在写真正的正则 表达式前先写下预期的输出结果。这样做,当写错时,可以逐渐修改,以消除意外结果,直 至返回正确值。为节省设计基本模式的时间,表 7 - 2给出一些例子,这些例子并无特别顺序。 表7-2 经常使用的正则表达式举例 ^ 对行首 $ 对行尾 ^ [ t h e ] 对以t h e开头行 [ S s ] i g n a [ l L ] 对匹配单词s i g n a l、s i g n a L、 S i g n a l、S i g n a L [Ss]igna[lL]\. 对同上,但加一句点 [ m a y M A Y ] 对包含 m a y大写或小写字母的 行 ^ U S E R $ 对只包含U S E R的行 [tty]$ 对以t t y结尾的行 \ . 对带句点的行 ^ d . . x . . x . . x 对对用户、用户组及其他用户 组成员有可执行权限的目录 ^ [ ^ l ] 对排除关联目录的目录列表 [ . * 0 ] 对0之前或之后加任意字符 [ 0 0 0 * ] 对0 0 0或更多个 [ iI] 对大写或小写I [ i I ] [ n N ] 对大写或小写i或n [ ^ $ ] 对空行 [ ^ . * $ ] 对匹配行中任意字符串 ^ . . . . . . $ 对包括6个字符的行 [a- zA-Z] 对任意单字符 [ a - z ] [ a - z ] * 对至少一个小写字母 [ ^ 0 - 9 \ $ ] 对非数字或美元标识 [ ^ 0 - 0 A - Z a - z ] 对非数字或字母 [ 1 2 3 ] 对1到3中一个数字 [ D d ] e v i c e 对单词d e v i c e或D e v i c e D e . . c e 对前两个字母为D e,后跟两个 任意字符,最后为c e 54 第二部分 文本过滤 下载 (续) \ ^ q 对以^ q开始行 ^ . $ 对仅有一个字符的行 ^\.[0-9][0-9] 对以一个句点和两个数字开始 的行 ' " D e v i c e " ' 对单词d e v i c e D e [ V v ] i c e \ . 对单词D e v i c e或d e v i c e [ 0 - 9 ] \ { 2 \ } - [ 0 - 9 ] \ { 2 \ } - [ 0 - 9 ] \ { 4 \ } 对日期格式d d - m m - y y y y [ 0 - 9 ] \ { 3 \ } \ . [ 0 - 9 ] \ { 3 \ } \ . [ 0 - 9 ] \ { 3 \ } \ . [ 0 - 9 ] \ { 3 \ } 对I P地址格式nnn. nnn.nnn.nnn [ ^ . * $ ] 对匹配任意行 7.8 小结 在s h e l l编程中,一段好的脚本与完美的脚本间的差别之一,就是要熟知正则表达式并学 会使用它们。相比较起来,用一个命令抽取一段文本比用三四个命令得出同样的结果要节省 许多时间。 既然已经学会了正则表达式中经常使用的基本特殊字符,又通过一些例子简化了其复杂 操作,那么现在可以看一些真正的例程了。 好,下面将讲述大量的g r e p , s e d和a w k例程。 第7章 正则表达式介绍 55下载 下载 第8章 grep 家族 相信g r e p是U N I X和L I N U X中使用最广泛的命令之一。 g r e p(全局正则表达式版本)允许 对文本文件进行模式查找。如果找到匹配模式, g r e p打印包含模式的所有行。 g r e p支持基本正 则表达式,也支持其扩展集。 g r e p有三种变形,即: G r e p:标准g r e p命令,本章大部分篇幅集中讨论此格式。 E g r e p:扩展g r e p,支持基本及扩展的正则表达式,但不支持 \ q模式范围的应用,与之相 对应的一些更加规范的模式,这里也不予讨论。 F g r e p:快速g r e p。允许查找字符串而不是一个模式。不要误解单词 f a s t,实际上它与g r e p 速度相当。 在本章中我们将讨论: • grep(参数)选项。 • 匹配g r e p的一般模式。 • 只匹配字母或数字,或两者混用。 • 匹配字符串范围。 实际上应该只有一个g r e p命令,但不幸的是没有一种简单形式能够统一处理 g r e p的三种变 形,将之合而为一,并保持 g r e p单模式处理时的速度。 GNU grep虽然在融合三种变形上迈进 了一大步,但仍不能区分元字符的基本集和扩展集。上一章只讨论了基本的正则表达式,但 在查看g r e p时也涉及到一些扩展模式的匹配操作。然而,首先还是先讨论一下在 g r e p和f g r e p 及e g r e p中均可使用的g r e p模式吧。 开始讨论之前,先生成一个文件,插入一段文本,并在每列后加入 < Ta b >键,g r e p命令示 例中绝大多数将以此为例,其命名为 d a t a . . f。生成一个文件,但不知其含义,将是一件很枯燥 的事。那么先来看看d a t a . f的记录结构。 第1列:城市位置编号。 第2列:月份。 第3列:存储代码及出库年份。 第4列:产品代号。 第5列:产品统一标价。 第6列:标识号。 第7列:合格数量。 8.1 grep g r e p一般格式为: grep [选项]基本正则表达式[文件] 这里基本正则表达式可为字符串。 8.1.1 双引号引用 在g r e p命令中输入字符串参数时,最好将其用双引号括起来。例如:“m y s t r i n g”。这样做 有两个原因,一是以防被误解为 s h e l l命令,二是可以用来查找多个单词组成的字符串,例如: “jet plane”,如果不用双引号将其括起来,那么单词 p l a n e将被误认为是一个文件,查询结果 将返回“文件不存在”的错误信息。 在调用变量时,也应该使用双引号,诸如: g r e p“$ M Y VA R”文件名,如果不这样,将 没有返回结果。 在调用模式匹配时,应使用单引号。 8.1.2 grep选项 常用的g r e p选项有: -c 只输出匹配行的计数。 -i 不区分大小写(只适用于单字符)。 -h 查询多文件时不显示文件名。 -l 查询多文件时只输出包含匹配字符的文件名。 -n 显示匹配行及行号。 -s 不显示不存在或无匹配文本的错误信息。 -v 显示不包含匹配文本的所有行。 8.1.3 查询多个文件 如果要在当前目录下所有. d o c文件中查找字符串“s o r t”,方法如下: $ grep "sort"*.doc 或在所有文件中查询单词“ sort it” $ grep "sort it" * 现在讲述在文本文件中g r e p选项的用法。 8.1.4 行匹配 $ grep -c "48"data.f $ 4 g r e p返回数字4,意义是有4行包含字符串“4 8”。 现在显示包含“4 8”字符串的4行文本: 第8章 g rep 家族 57下载 8.1.5 行数 显示满足匹配模式的所有行行数: 行数在输出第一列,后跟包含 4 8的每一匹配行。 8.1.6 显示非匹配行 显示所有不包含4 8的各行: 8.1.7 精确匹配 可能大家已注意到,在上一例中,抽取字符串“ 4 8”,返回结果包含诸如 4 8 4和4 8 3等包含 “4 8”的其他字符串,实际上应精确抽取只包含 4 8的各行。注意在每个匹配模式中抽取字符串 后有一个< Ta b >键,所以应操作如下: < Ta b >表示点击t a b键。 使用g r e p抽取精确匹配的一种更有效方式是在抽取字符串后加 \ >。假定现在精确抽取 4 8, 方法如下: 8.1.8 大小写敏感 缺省情况下,g r e p是大小写敏感的,如要查询大小写不敏感字符串,必须使用 - i开关。在 d a t a . f文件中有月份字符 S e p t,既有大写也有小写,要取得此字符串大小写不敏感查询,方法 如下: 8.2 grep和正则表达式 使用正则表达式使模式匹配加入一些规则,因此可以在抽取信息中加入更多选择。使用 正则表达式时最好用单引号括起来,这样可以防止 g r e p中使用的专有模式与一些 s h e l l命令的 特殊方式相混淆。 58 第二部分 文本过滤 下载 8.2.1 模式范围 假定要抽取代码为4 8 4和4 8 3的城市位置,上一章中讲到可以使用 [ ]来指定字符串范围,这 里用4 8开始,以3或4结尾,这样抽出4 8 4或4 8 3。 8.2.2 不匹配行首 如果要抽出记录,使其行首不是 4 8,可以在方括号中使用^记号,表明查询在行首开始。 8.2.3 设置大小写 使用- i开关可以屏蔽月份 S e p t的大小写敏感,也可以用另一种方式。这里使用 [ ]模式抽取 各行包含S e p t和s e p t的所有信息。 如果要抽取包含S e p t的所有月份,不管其大小写,并且此行包含字符串 4 8 3,可以使用管 道命令,即符号“|”左边命令的输出作为“|”右边命令的输入。举例如下: 不必将文件名放在第二个g r e p命令中,因为其输入信息来自于第一个 g r e p命令的输出。 8.2.4 匹配任意字符 如果抽取以 L开头,以D结尾的所有代码,可使用下述方法,因为已知代码长度为 5个字 符: 将上述代码做轻微改变,头两个是大写字母,中间两个任意,并以 C结尾: 8.2.5 日期查询 一个常用的查询模式是日期查询。先查询所有以 5开始以1 9 9 6或1 9 9 8结尾的所有记录。使 用模式5 . . 1 9 9 [ 6 , 8 ]。这意味着第一个字符为 5,后跟两个点,接着是 1 9 9,剩余两个数字是 6或 8。 第8章 g rep 家族 59下载 查询包含1 9 9 8的所有记录的另外一种方法是使用表达式 [ 0 - 9 ] \ { 3 \ } [ 8 ],含义是任意数字重 复3次,后跟数字8,虽然这个方法不像上一个方法那么精确,但也有一定作用。 8.2.6 范围组合 必须学会使用[ ]抽取信息。假定要取得城市代码,第一个字符为任意字符,第二个字符在 0到5之间,第三个字符在0到6之间,使用下列模式即可实现。 这里返回很多信息,有想要的,也有不想要的。参照模式,返回结果是正确的,因此这 里还需要细化模式,可以以行首开始,使用 ^符号: 这样可以返回一个预期的正确结果。 8.2.7 模式出现机率 抽取包含数字4至少重复出现两次的所有行,方法如下: 上述语法指明数字4至少重复出现两次。 同样,抽取记录使之包含数字 9 9 9(三个9),方法如下: 如果要查询重复出现次数一定的所有行,语法如下,数字 9重复出现两次: 有时要查询重复出现次数在一定范围内,比如数字或字母重复出现 2到6次,下例匹配数 字8重复出现2到6次,并以3结尾: 60 第二部分 文本过滤 下载 8.2.8 使用grep匹配“与”或者“或”模式 g r e p命令加- E参数,这一扩展允许使用扩展模式匹配。例如,要抽取城市代码为 2 1 9或 2 1 6,方法如下: 8.2.9 空行 结合使用^和$可查询空行。使用- n参数显示实际行数: 8.2.10 匹配特殊字符 查询有特殊含义的字符,诸如$ . ' " * [] ^ | \ + ? ,必须在特定字符前加\。假设要查询包含“.” 的所有行,脚本如下: 或者是一个双引号: 以同样的方式,如要查询文件名 c o n f t r o l l . c o n f(这是一个配置文件),脚本如下: 8.2.11 查询格式化文件名 使用正则表达式可匹配任意文件名。系统中对文本文件有其标准的命名格式。一般最多 六个小写字符,后跟句点,接着是两个大写字符。例如,要在一个包含各类文件名的文件 f i l e n a m e . d e p o s i t中定位这类文件名,方法如下: 8.2.12 查询IP地址 查询D N S服务是日常工作之一,这意味着要维护覆盖不同网络的大量 I P地址。有时地址 I P会超过2 0 0 0个。如果要查看n n n . n n n网络地址,但是却忘了第二部分中的其余部分,只知有 两个句点,例如n n n . n n . .。要抽取其中所有nnn.nnn IP地址,使用[ 0 - 9 ] \ { 3 \ } \ . [ 0 - 0 \ { 3 \ } \。含义是 任意数字出现3次,后跟句点,接着是任意数字出现 3次,后跟句点。 第8章 g rep 家族 61下载 8.3 类名 g r e p允许使用国际字符模式匹配或匹配模式的类名形式。 表8-1 类名及其等价的正则表达式 类 等价的正则表达式 类 等价的正则表达式 [ [ : u p p e r : ] ] [ A - Z ] [ [ : a l n u m : ] ] [ 0 - 9 a - zA-Z] [ [ : l o w e r : ] ] [ a - z ] [ [ : s p a c e : ] ] 空格或t a b键 [ [ : d i g i t : ] ] [ 0 - 9 ] [ [ : a l p h a : ] ] [ a - z A - Z ] 现举例说明其使用方式。要抽取产品代码,该代码以 5开头,后跟至少两个大写字母。使 用的脚本如下: 如果要抽取以P或D结尾的所有产品代码,方法如下: 使用通配符*的匹配模式 现在讲述g r e p中通配符*的使用。现有文件如下: 下述g r e p模式结果显示如下: 如在行尾查询某一单词,试如下模式: 这将在所有文件中查询行尾包含单词 d e v i c e的所有行。 8.4 系统grep命令 使用已学过的知识可以很容易通过 g r e p命令获得系统信息。下面几个例子中,将用到管 62 第二部分 文本过滤 下载 道命令,即符号|,使用它左边命令的输出结果作为它右边命令的输入。 8.4.1 目录 如果要查询目录列表中的目录,方法如下: 如果在一个目录中查询不包含目录的所有文件,方法如下: 要查询其他用户和其他用户组成员有可执行权限的目录集合,方法如下: 8.4.2 passwd文件 上述脚本查询/ e t c / p a s s w d文件是否包含l o u i s e字符串,如果误输入以下脚本: 将返回g r e p命令错误代码'No such file or directory'。 上述结果表明输入文件名不存在,使用 g r e p命令- s开关,可屏蔽错误信息。 返回命令提示符,而没有文件不存在的错误提示。 如果g r e p命令不支持- s开关,可替代使用以下命令: 脚本含义是匹配命令输出或错误( 2 > $ 1),并将结果输出到系统池。大多数系统管理员称 / d e v / n u l l为比特池,没关系,可以将之看成一个无底洞,有进没有出,永远也不会填满。 上述两个例子并不算好,因为这里的目的只想知道查询是否成功。本书后面部分将讨论 g r e p命令的e x i t用法,它允许查询并不成功返回。 如要保存g r e p命令的查询结果,可将命令输出重定向到一个文件。 脚本将输出重定向到目录/ t m p下文件p a s s w d . o u t中。 8.4.3 使用ps命令 使用带有ps x命令的g r e p可查询系统上运行的进程。 ps x命令意为显示系统上运行的所有 进程列表。要查看D N S服务器是否正在运行(通常称为 n a m e d),方法如下: 输出也应包含此g r e p命令,因为g r e p命令创建了相应进程, ps x将找到它。在g r e p命令中 使用- v选项可丢弃p s命令中的g r e p进程。 第8章 g rep 家族 63下载 如果ps x不适用于用户系统,替代使用 ps -ef。 8.4.4 对一个字符串使用grep g r e p不只应用于文件,也可应用于字符串。为此使用 e c h o字符串命令,然后对g r e p命令使 用管道输入。 匹配成功实现。 因为没有匹配字符串,所以没有输出结果。 8.5 egrep e g r e p代表e x p r e s s i o n或extended grep,适情况而定。e g r e p接受所有的正则表达式, e g r e p 的一个显著特性是可以以一个文件作为保存的字符串,然后将之传给 e g r e p作为参数,为此使 用- f开关。如果创建一个名为g r e p s t r i n g s的文件,并输入4 8 4和4 7: 上述脚本匹配d a t a . f中包含4 8 4或4 7的所有记录。当匹配大量模式时, - f开关很有用,而在 一个命令行中敲入这些模式显然极为繁琐。 如果要查询存储代码3 2 L或2 C C,可以使用(|)符号,意即“|”符号两边之一或全部。 可以使用任意多竖线符“ |”,例如要查看在系统中是否有帐号 l o u i s e、m a t t y或pauline , 使用w h o命令并管道输出至e g r e p。 还可以使用^符号排除字符串。如果要查看系统上的用户,但不包括 m a t t y和p a u l i n e,方 法如下: 如果要查询一个文件列表,包括 s h u t d o w n、s h u t d o w n s、r e b o o t和r e b o o t s,使用e g r e p可容 易地实现。 64 第二部分 文本过滤 下载 8.6 小结 希望大家已经理解了 g r e p的灵活性,它是一个很强大而流行的工具,像其他许多 U N I X工 具一样,已经被使用在 D O S中。如果要通过文件快速查找字符串或模式, g r e p是一个很好的 选择。简单地说, g r e p是s h e l l编程中很重要的工具,在本书后面部分使用其他 U N I X工具和进 行变量替换时将发现这一点。 第8章 g rep 家族 65下载 下载 第9章 AWK 介绍 如果要格式化报文或从一个大的文本文件中抽取数据包,那么 a w k可以完成这些任务。它 在文本浏览和数据的熟练使用上性能优异。 整体来说,a w k是所有s h e l l过滤工具中最难掌握的,不知道为什么,也许是其复杂的语法 或含义不明确的错误提示信息。在学习 a w k语言过程中,就会慢慢掌握诸如 Bailing out 和 a w k : c m d . L i n e :等错误信息。可以说a w k是一种自解释的编程语言,之所以要在 s h e l l中使用a w k 是因为a w k本身是学习的好例子,但结合 a w k与其他工具诸如g r e p和s e d,将会使s h e l l编程更加 容易。 本章没有讲述a w k的全部特性,也不涉及 a w k的深层次编程,(这些可以在专门讲述 a w k的 书籍中找到)。本章仅注重于讲述使用a w k执行行操作及怎样从文本文件和字符串中抽取信息。 本章内容有: • 抽取域。 • 匹配正则表达式。 • 比较域。 • 向a w k传递参数。 • 基本的a w k行操作和脚本。 本书几乎所有包含 a w k命令的脚本都结合了 s e d和g r e p,以从文本文件和字符串中抽取信 息。为获得所需信息,文本必须格式化,意即用域分隔符划分抽取域,分隔符可能是任意字 符,在以后讲述a w k时再详细讨论。 a w k以发展这种语言的人 A h o . We n i n b e rg e r和K e r n i g h a m命名。还有n a w k和g a w k,它们扩 展了文本特性,但本章不予讨论。 a w k语言的最基本功能是在文件或字符串中基于指定规则浏览和抽取信息。 a w k抽取信息 后,才能进行其他文本操作。完整的 a w k脚本通常用来格式化文本文件中的信息。 9.1 调用awk 有三种方式调用a w k,第一种是命令行方式,如: 这里,c o m m a n d s是真正的a w k命令。本章将经常使用这种方法。 上面例子中,[ - F域分隔符]是可选的,因为 a w k使用空格作为缺省的域分隔符,因此如果 要浏览域间有空格的文本,不必指定这个选项,但如果要浏览诸如 p a s s w d文件,此文件各域 以冒号作为分隔符,则必须指明 - F选项,如: 第二种方法是将所有 a w k命令插入一个文件,并使 a w k程序可执行,然后用 a w k命令解释 器作为脚本的首行,以便通过键入脚本名称来调用它。 第三种方式是将所有的a w k命令插入一个单独文件,然后调用: - f选项指明在文件 a w k _ s c r i p t _ f i l e中的a w k脚本,i n p u t _ f i l e ( s )是使用a w k进行浏览的文件 名。 9.2 awk脚本 在命令中调用a w k时,a w k脚本由各种操作和模式组成。 如果设置了- F选项,则a w k每次读一条记录或一行,并使用指定的分隔符分隔指定域,但 如果未设置- F选项,a w k假定空格为域分隔符,并保持这个设置直到发现一新行。当新行出现 时,a w k命令获悉已读完整条记录,然后在下一个记录启动读命令,这个读进程将持续到文件 尾或文件不再存在。 参照表9 - 1,a w k每次在文件中读一行,找到域分隔符(这里是符号 #),设置其为域n,直 至一新行(这里是缺省记录分隔符),然后,划分这一行作为一条记录,接着 a w k再次启动下 一行读进程。 表9-1 awk读文件记录的方式 域1 分隔符 域2 分隔符 域3 分隔符 域4及换行 P. B u n n y (记录1 ) # 0 2 / 9 9 # 4 8 # Yellow \n J . Tr o l l (记录2 ) # 0 7 / 9 9 # 4 8 4 2 # Brown-3 \n 9.2.1 模式和动作 任何a w k语句都由模式和动作组成。在一个 a w k脚本中可能有许多语句。模式部分决定动 作语句何时触发及触发事件。处理即对数据进行的操作。如果省略模式部分,动作将时刻保 持执行状态。 模式可以是任何条件语句或复合语句或正则表达式。模式包括两个特殊字段 B E G I N和 E N D。使用B E G I N语句设置计数和打印头。 B E G I N语句使用在任何文本浏览动作之前,之后 文本浏览动作依据输入文件开始执行。 E N D语句用来在 a w k完成文本浏览动作后打印输出文 本总数和结尾状态标志。如果不特别指明模式, a w k总是匹配或打印行数。 实际动作在大括号{ }内指明。动作大多数用来打印,但是还有些更长的代码诸如 i f和循环 (l o o p i n g)语句及循环退出结构。如果不指明采取动作, a w k将打印出所有浏览出来的记录。 下面将深入讲解这些模式和动作。 9.2.2 域和记录 a w k执行时,其浏览域标记为 $ 1,$ 2 . . . $ n。这种方法称为域标识。使用这些域标识将更容 易对域进行进一步处理。 使用$ 1 , $ 3表示参照第 1和第3域,注意这里用逗号做域分隔。如果希望打印一个有 5个域 的记录的所有域,不必指明 $ 1 , $ 2 , $ 3 , $ 4 , $ 5,可使用$ 0,意即所有域。 Aw k浏览时,到达一新 行,即假定到达包含域的记录末尾,然后执行新记录下一行的读动作,并重新设置域分隔。 注意执行时不要混淆符号$和s h e l l提示符$,它们是不同的。 为打印一个域或所有域,使用 p r i n t命令。这是一个a w k动作(动作语法用圆括号括起来)。 第9章 AWK 介绍 67下载 1. 抽取域 真正执行前看几个例子,现有一文本文件 g r a d e . t x t,记录了一个称为柔道数据库的行信 息。 此文本文件有7个域,即(1)名字、(2)升段日期、(3)学生序号、(4)腰带级别、(5) 年龄、(6)目前比赛积分、(7)比赛最高分。 因为域间使用空格作为域分隔符,故不必用 - F选项划分域,现浏览文件并导出一些数据。 在例子中为了利于显示,将空格加宽使各域看得更清晰。 2. 保存a w k输出 有两种方式保存 s h e l l提示符下a w k脚本的输出。最简单的方式是使用输出重定向符号 >文 件名,下面的例子重定向输出到文件 w o w。 使用这种方法要注意,显示屏上不会显示输出结果。因为它直接输出到文件。只有在保 证输出结果正确时才会使用这种方法。它也会重写硬盘上同名数据。 第二种方法是使用 t e e命令,在输出到文件的同时输出到屏幕。在测试输出结果正确与否 时多使用这种方法。例如输出重定向到文件 d e l e t e _ m e _ a n d _ d i e,同时输出到屏幕。使用这种 方法,在a w k命令结尾写入 | tee delete_me_and_die。 3. 使用标准输入 在深入讲解这一章之前,先对 a w k脚本的输入方法简要介绍一下。实际上任何脚本都是从 标准输入中接受输入的。为运行本章脚本,使用 a w k脚本输入文件格式,例如: 也可替代使用下述格式: 使用重定向方法: 或管道方法: 4. 打印所有记录 a w k读每一条记录。因为没有模式部分,只有动作部分 {print $0}(打印所有记录),这个动 作必须用花括号括起来。上述命令打印整个文件。 68 第二部分 文本过滤 下载 5. 打印单独记录 假定只打印学生名字和腰带级别,通过查看域所在列,可知为 f i e l d - 1和f i e l d - 4,因此可以 使用$ 1和$ 4,但不要忘了加逗号以分隔域。 6. 打印报告头 上述命令输出在名字和腰带级别之间用一些空格使之更容易划分,也可以在域间使用 t a b 键加以划分。为加入t a b键,使用t a b键速记引用符 \ t,后面将对速记引用加以详细讨论。也可 以为输出文本加入信息头。本例中加入 n a m e和b e l t及下划线。下划线使用 \ n,强迫启动新行, 并在\ n下一行启动打印文本操作。打印信息头放置在 B E G I N模式部分,因为打印信息头被界 定为一个动作,必须用大括号括起来。在 a w k查看第一条记录前,信息头被打印。 7. 打印信息尾 如果在末行加入end of report信息,可使用E N D语句。E N D语句在所有文本处理动作执行 完之后才被执行。 E N D语句在脚本中的位置放置在主要动作之后。下面简单打印头信息并告 之查询动作完成。 8. awk错误信息提示 几乎可以肯定,在使用 a w k时,将会在命令中碰到一些错误。 a w k将试图打印错误行,但 由于大部分命令都只在一行,因此帮助不大。 系统给出的显示错误信息提示可读性不好。使用上述例子,如果丢了一个双引号, a w k将 返回: 第9章 AWK 介绍 69下载 当第一次使用a w k时,可能被错误信息搅得不知所措,但通过长时间和不断的学习,可总 结出以下规则。在碰到a w k错误时,可相应查找: • 确保整个a w k命令用单引号括起来。 • 确保命令内所有引号成对出现。 • 确保用花括号括起动作语句,用圆括号括起条件语句。 • 可能忘记使用花括号,也许你认为没有必要,但 a w k不这样认为,将按之解释语法。 如果查询文件不存在,将得到下述错误信息: 9. awk键盘输入 如果在命令行并没有输入文件 g r a d e . t x t,将会怎样? B E G I N部分打印了文件头,但a w k最终停止操作并等待,并没有返回 s h e l l提示符。这是因 为a w k期望获得键盘输入。因为没有给出输入文件, a w k假定下面将会给出。如果愿意,顺序 输入相关文本,并在输入完成后敲 键。如果敲入了正确的域分隔符, a w k会像第一个 例子一样正常处理文本。这种处理并不常用,因为它大多应用于大量的打印稿。 9.2.3 awk中正则表达式及其操作 在g r e p一章中,有许多例子用到正则表达式,这里将不使用同样的例子,但可以使用条 件操作讲述a w k中正则表达式的用法。 这里正则表达式用斜线括起来。例如,在文本文件中查询字符串 G r e e n,使用/ G r e e n /可以 查出单词G r e e n的出现情况。 9.2.4 元字符 这里是a w k中正则表达式匹配操作中经常用到的字符,详细情况请参阅本书第 7章正则表 达式概述。 \ ^ $ . [] | () * + ? 这里有两个字符第7章没有讲到,因为它们只适用于 a w k而不适用于g r e p或s e d。它们是: + 使用+匹配一个或多个字符。 ? 匹配模式出现频率。例如使用 /X Y?Z/匹配X Y Z或Y Z。 9.2.5 条件操作符 表9 - 2给出a w k条件操作符,后面将给出其用法。 70 第二部分 文本过滤 下载 表9-2 awk条件操作符 操作符 描 述 操作符 描 述 < 小于 > = 大于等于 < = 小于等于 ~ 匹配正则表达式 = = 等于 !~ 不匹配正则表达式 != 不等于 1. 匹配 为使一域号匹配正则表达式,使用符号‘~’后紧跟正则表达式,也可以用 i f语句。a w k 中i f后面的条件用()括起来。 观察文件g r a d e . t x t,如果只要打印 b r o w n腰带级别可知其所在域为 f i e l d - 4,这样可以写出 表达式{if($4~/brown/) print }意即如果f i e l d - 4包含b r o w n,打印它。如果条件满足,则打印匹 配记录行。可以编写下面脚本,因为这是一个动作,必须用花括号 { }括起来。 匹配记录找到时,如果不特别声明, a w k缺省打印整条记录。使用 i f语句开始有点难,但 不要着急,因为有许多方法可以跳过它,并仍保持同样结果。下面例子意即如果记录包含模 式b r o w n,就打印它: 2. 精确匹配 假定要使字符串精确匹配,比如说查看学生序号 4 8,文件中有许多学生序号包含 4 8,如 果在f i e l d - 3中查询序号4 8,a w k将返回所有序号带4 8的记录: 为精确匹配4 8,使用等号= =,并用单引号括起条件。例如 $ 3 = =“4 8”,这样确保只有 4 8 序号得以匹配,其余则不行。 3. 不匹配 有时要浏览信息并抽取不匹配操作的记录,与 ~相反的符号是!~,意即不匹配。像原来使 用查询b r o w n腰带级别的匹配操作一样,现在看看不匹配情况。表达式 $0 !~/brown/,意即查 询不包含模式b r o w n腰带级别的记录并打印它。 注意,缺省情况下,a w k将打印所有匹配记录,因此这里不必加入动作部分。 第9章 AWK 介绍 71下载 可以只对f i e l d - 4进行不匹配操作,方法如下: 如果只使用命令awk$4 !="brown"{print $0} grade.txt,将返回错误结果,因为用引号括起 了b r o w n,将只匹配‘b r o w n而不匹配b r o w n - 2和b r o w n - 3,当然,如果想要查询非b r o w n - 2的腰 带级别,可做如下操作: 4. 小于 看看哪些学生可以获得升段机会。测试这一点即判断目前级别分 f i e l d - 6是否小于最高分 f i e l d - 7,在输出结果中,加入这一改动很容易。 5. 小于等于 对比小于,小于等于只在操作符上做些小改动,满足此条件的记录也包括上面例子中的 输出情况。 6. 大于 大于符号大家都熟知,请看例子: 希望读者已经掌握了操作符的基本用法。 7. 设置大小写 为查询大小写信息,可使用[ ]符号。在测试正则表达式时提到可匹配 [ ]内任意字符或单词, 因此若查询文件中级别为g r e e n的所有记录,不论其大小写,表达式应为‘ / [ G g ] r e e n /:’ 8. 任意字符 抽取名字,其记录第一域的第四个字符是 a,使用句点.。表达式/ ^ . . . a /意为行首前三个字 符任意,第四个是a,尖角符号代表行首。 9. 或关系匹配 为抽取级别为y e l l o w或b r o w n的记录,使用竖线符 |。意为匹配 | 两边模式之一。注意,使 用竖线符时,语句必须用圆括号括起来。 72 第二部分 文本过滤 下载 上面例子输出所有级别为Ye l l o w或B r o w n的记录。 使用这种方法在查询级别为 G r e e n或g r e e n时,可以得到与使用[ ]表达式相同的结果。 10. 行首 不必总是使用域号。如果查询文本文件行首包含 4 8的代码,可简单使用下面^符号: 这里讲述了在a w k中怎样使用第7章中涉及的表达式。像第 7章的开头提到的,所有表达式 (除字符重复出现外)在a w k中都是合法的。 复合模式或复合操作符用于形成复杂的逻辑操作,复杂程度取决于编程者本人。有必要 了解的是,复合表达式即为模式间通过使用下述各表达式互相结合起来的表达式: && AND : 语句两边必须同时匹配为真。 || O R:语句两边同时或其中一边匹配为真。 ! 非求逆 11. AND 打印记录,使其名字为‘ P. B u n n y且级别为 Ye l l o w,使用表达式 ( $ 1 = = " P. B u n n y " & & $ 4 = = " Ye l l o w " ),意为& &两边匹配均为真。完整命令如下: 12. Or 如果查询级别为 Ye l l o w或B r o w n,使用或命令。意为“ | |”符号两边的匹配模式之一或全 部为真。 9.2.6 awk内置变量 a w k有许多内置变量用来设置环境信息。这些变量可以被改变。表 9 - 3显示了最常使用的 一些变量,并给出其基本含义。 表9-3 awk内置变量 A R G C 命令行参数个数 A R G V 命令行参数排列 E N V I R O N 支持队列中系统环境变量的使用 FILENAME a w k浏览的文件名 F N R 浏览文件的记录数 F S 设置输入域分隔符,等价于命令行 - F选项 第9章 AWK 介绍 73下载 (续) N F 浏览记录的域个数 N R 已读的记录数 O F S 输出域分隔符 O R S 输出记录分隔符 R S 控制记录分隔符 A R G C支持命令行中传入 a w k脚本的参数个数。 A R G V是A R G C的参数排列数组,其中每 一元素表示为A R G V [ n ],n为期望访问的命令行参数。 E N V I R O N 支持系统设置的环境变量,要访问单独变量,使用实际变量名,例如 E N V I R O N [“E D I TO R”] =“Vi”。 F I L E N A M E支持a w k脚本实际操作的输入文件。因为 a w k可以同时处理许多文件,因此如 果访问了这个变量,将告之系统目前正在浏览的实际文件。 F N R支持a w k目前操作的记录数。其变量值小于等于 N R。如果脚本正在访问许多文件, 每一新输入文件都将重新设置此变量。 F S用来在a w k中设置域分隔符,与命令行中 - F选项功能相同。缺省情况下为空格。如果用 逗号来作域分隔符,设置F S = ","。 N F支持记录域个数,在记录被读之后再设置。 O F S允许指定输出域分隔符,缺省为空格。如果想设置为 #,写入O F S = " # "。 O R S为输出记录分隔符,缺省为新行( \ n)。 R S是记录分隔符,缺省为新行 ( \ n )。 9.2.7 NF、NR和FILENAME 下面看一看a w k内置变量的例子。 要快速查看记录个数,应使用 N R。比如说导出一个数据库文件后,如果想快速浏览记录 个数,以便对比于其初始状态,查出导出过程中出现的错误。使用 N R将打印输入文件的记录 个数。print NR放在E N D语法中。 以下例子中,所有学生记录被打印,并带有其记录号。使用 N F变量显示每一条读记录中 有多少个域,并在E N D部分打印输入文件名。 在从文件中抽取信息时,最好首先检查文件中是否有记录。下面的例子只有在文件中至 少有一个记录时才查询 B r o w n级别记录。使用 A N D复合语句实现这一功能。意即至少存在一 个记录后,查询字符串B r o w n,最后打印结果。 74 第二部分 文本过滤 下载 N F的一个强大功能是将变量 $ P W D的返回值传入a w k并显示其目录。这里需要指定域分隔 符/。 另一个例子是显示文件名。 9.2.8 awk操作符 在a w k中使用操作符,基本表达式可以划分为数字型、字符串型、变量型、域及数组元素, 前面已经讲过一些。下面列出其完整列表。 在表达式中可以使用下述任何一种操作符。 = += *= / = %= ^ = 赋值操作符 ? 条件表达操作符 || && ! 并、与、非(上一节已讲到) ~!~ 匹配操作符,包括匹配和不匹配 < <= == != >> 关系操作符 + - * / % ^ 算术操作符 + + -- 前缀和后缀 前面已经讲到了其中几种操作,下面继续讲述未涉及的部分。 1. 设置输入域到域变量名 在a w k中,设置有意义的域名是一种好习惯,在进行模式匹配或关系操作时更容易理解。 一般的变量名设置方式为 n a m e = $ n,这里n a m e为调用的域变量名,n为实际域号。例如设置学 生域名为n a m e,级别域名为b e l t,操作为n a m e = $ 1 ; b e l t s = $ 4。注意分号的使用,它分隔 a w k命 令。下面例子中,重新赋值学生名域为 n a m e,级别域为b e l t s。查询级别为Ye l l o w的记录,并 最终打印名称和级别。 2. 域值比较操作 有两种方式测试一数值域是否小于另一数值域。 1) 在B E G I N中给变量名赋值。 2) 在关系操作中使用实际数值。 通常在B E G I N部分赋值是很有益的,可以在 a w k表达式进行改动时减少很多麻烦。 使用关系操作必须用圆括号括起来。 下面的例子查询所有比赛中得分在 2 7点以下的学生。 用引号将数字引用起来是可选的,“2 7”、2 7产生同样的结果。 第9章 AWK 介绍 75下载 第二个例子中给数字赋以变量名 B A S E L I N E和在B E G I N部分给变量赋值,两者意义相同。 3. 修改数值域取值 当在a w k中修改任何域时,重要的一点是要记住实际输入文件是不可修改的,修改的只是 保存在缓存里的a w k复本。a w k会在变量N R或N F变量中反映出修改痕迹。 为修改数值域,简单的给域标识重赋新值,如: $ 1 = $ 1 + 5,会将域1数值加5,但要确保赋 值域其子集为数值型。 修改M . Ta n s l e y的目前级别分域,使其数值从 4 0减为3 9,使用赋值语句 $ 6 = $ 6 - 1,当然在 实施修改前首先要匹配域名。 4. 修改文本域 修改文本域即对其重新赋值。需要做的就是赋给一个新的字符串。在 J . Tr o l l中加入字母, 使其成为J . L . Tr o l l,表达式为$ 1 = " J . L . Tr o l l ",记住字符串要使用双秒号( " "),并用圆括号括 起整个语法。 5. 只显示修改记录 上述例子均是对一个小文件的域进行修改,因此打印出所有记录查看修改部分不成问题, 但如果文件很大,记录甚至超过 1 0 0,打印所有记录只为查看修改部分显然不合情理。在模式 后面使用花括号将只打印修改部分。取得模式,再根据模式结果实施操作,可能有些抽象, 现举一例,只打印修改部分。注意花括号的位置。 6. 创建新的输出域 在a w k中处理数据时,基于各域进行计算时创建新域是一种好习惯。创建新域要通过其他 域赋予新域标识符。如创建一个基于其他域的加法新域 { $ 4 = $ 2 + $ 3 },这里假定记录包含3个域, 则域4为新建域,保存域2和域3相加结果。 在文件 g r a d e . t x t中创建新域 8保存域目前级别分与域最高级别分的减法值。表达式为 ‘{ $ 8 = $ 7 - $ 6 }’,语法首先测试域目前级别分小于域最高级别分。新域因此只打印其值大于零 的学生名称及其新域值。在 B E G I N部分加入t a b键以对齐报告头。 76 第二部分 文本过滤 下载 当然可以创建新域,并赋给其更有意义的变量名。例如: 7. 增加列值 为增加列数或进行运行结果统计,使用符号 + =。增加的结果赋给符号左边变量值,增加 到变量的域在符号右边。例如将 $ 1加入变量t o t a l,表达式为t o t a l + = $ 1。列值增加很有用。许 多文件都要求统计总数,但输出其统计结果十分繁琐。在 a w k中这很简单,请看下面的例子。 将所有学生的‘目前级别分’加在一起,方法是 t o t + = $ 6,t o t即为a w k浏览的整个文件的 域6结果总和。所有记录读完后,在 E N D部分加入一些提示信息及域 6总和。不必在 a w k中显 示说明打印所有记录,每一个操作匹配时,这是缺省动作。 如果文件很大,你只想打印结果部分而不是所有记录,在语句的外面加上圆括号()即 可。 8. 文件长度相加 在目录中查看文件时,如果想快速查看所有文件的长度及其总和,但要排除子目录,使 用ls -l命令,然后管道输出到a w k,a w k首先剔除首字符为d(使用正则表达式)的记录,然后 将文件长度列相加,并输出每一文件长度及在 E N D部分输出所有文件的长度。 本例中,首先用 ls -l命令查看一下文件属性。注意第二个文件属性首字符为 d,说明它是 一个目录,文件长度是第 5列,文件名是第 9列。如果系统不是这样排列文件名及其长度,应 适时加以改变。 下面的正则表达式表明必须匹配行首,并排除字符 d,表达式为^ [ ^ d ]。 使用此模式打印文件名及其长度,然后将各长度相加放入变量 t o t中。 第9章 AWK 介绍 77下载 9.2.9 内置的字符串函数 a w k有许多强大的字符串函数,见表 9 - 4。 表9-4 awk内置字符串函数 g s u b ( r, s ) 在整个$ 0中用s替代r g s u b ( r, s , t ) 在整个t中用s替代r i n d e x ( s , t ) 返回s中字符串t的第一位置 l e n g t h ( s ) 返回s长度 m a t c h ( s , r ) 测试s是否包含匹配 r的字符串 s p l i t ( s , a , f s ) 在f s上将s分成序列a s p r i n t ( f m t , e x p ) 返回经f m t格式化后的e x p s u b ( r, s ) 用$ 0中最左边最长的子串代替 s s u b s t r ( s , p ) 返回字符串s中从p开始的后缀部分 s u b s t r ( s , p , n ) 返回字符串s中从p开始长度为n的后缀部分 g s u b函数有点类似于 s e d查找和替换。它允许替换一个字符串或字符为另一个字符串或字 符,并以正则表达式的形式执行。第一个函数作用于记录 $ 0,第二个g s u b函数允许指定目标, 然而,如果未指定目标,缺省为 $ 0。 i n d e x(s,t)函数返回目标字符串 s中查询字符串t的首位置。l e n g t h函数返回字符串s字符 长度。m a t c h函数测试字符串 s是否包含一个正则表达式 r定义的匹配。 s p l i t使用域分隔符 f s将 字符串s划分为指定序列a。s p r i n t函数类似于p r i n t f函数(以后涉及),返回基本输出格式f m t的 结果字符串 e x p。s u b(r,s)函数将用 s替代$ 0中最左边最长的子串,该子串被( r)匹配。 s u b(s,p)返回字符串s在位置p后的后缀。s u b s t r(s,p,n)同上,并指定子串长度为 n。 现在看一看a w k中这些字符串函数的功能。 1. gsub 要在整个记录中替换一个字符串为另一个,使用正则表达式格式, /目标模式/,替换模式 /。例如改变学生序号4 8 4 2到4 8 9 9: 2. index 查询字符串 s中t出现的第一位置。必须用双引号将字符串括起来。例如返回目标字符串 B u n n y中n y出现的第一位置,即字符个数。 3. length 返回所需字符串长度,例如检验字符串 J . Tr o l l返回名字及其长度,即人名构成的字符个 数。 还有一种方法,这里字符串加双引号。 78 第二部分 文本过滤 下载 4. match m a t c h测试目标字符串是否包含查找字符的一部分。可以对查找部分使用正则表达式,返 回值为成功出现的字符排列数。如果未找到,返回 0,第一个例子在 A N C D中查找d。因其不 存在,所以返回0。第二个例子在A N C D中查找D。因其存在,所以返回 A N C D中D出现的首位 置字符数。第三个例子在学生 J . L u l u中查找u。 5. split 使用s p l i t返回字符串数组元素个数。工作方式如下:如果有一字符串,包含一指定分隔 符 - ,例如A D2 - K P 9 - J U 2 - L P - 1,将之划分成一个数组。使用 s p l i t,指定分隔符及数组名。此 例中,命令格式为 ( " A D 2 - K P 9 - J U 2 - L P - 1 ",p a r t s _ a r r a y," - "),s p l i t然后返回数组下标数,这 里结果为4。 还有一个例子使用不同的分隔符。 这个例子中,s p l i t返回数组m y a r r a y的下标数。数组m y a r r a y取值如下: 本章结尾部分讲述数组概念。 6. sub 使用s u b发现并替换模式的第一次出现位置。字符串 S T R包含‘poped popo pill’,执行下 列s u b命令s u b(/ o p /," o p ",S T R)。模式 o p第一次出现时,进行替换操作,返回结果如下: ‘pO Ped pope pill’。 本章文本文件中,学生J . Tr o l l的记录有两个值一样,“目前级别分”与“最高级别分”。只 改变第一个为2 9,第二个仍为2 4不动,操作命令为s u b(/ 2 6 /," 2 9 ",$ 0),只替换第一个出现 2 4的位置。注意J . Tr o l l记录需存在。 7. substr s u b s t r是一个很有用的函数。它按照起始位置及长度返回字符串的一部分。例子如下: 上面例子中,指定在域1的第一个字符开始,返回其前面 5个字符。 第9章 AWK 介绍 79下载 如果给定长度值远大于字符串长度, a w k将从起始位置返回所有字符,要抽取 L Ta n s l - e y 的姓,只需从第3个字符开始返回长度为7。可以输入长度9 9,a w k返回结果相同。 s u b s t r的另一种形式是返回字符串后缀或指定位置后面字符。这里需要给出指定字符串及 其返回字串的起始位置。例如,从文本文件中抽取姓氏,需操作域 1,并从第三个字符开始: 还有一个例子,在 B E G I N部分定义字符串,在 E N D部分返回从第 t个字符开始抽取的子 串。 8. 从s h e l l中向a w k传入字符串 本章开始已经提到过,a w k脚本大多只有一行,其中很少是字符串表示的。本书大多要求 在一行内完成a w k脚本,这一点通过将变量传入 a w k命令行会变得很容易。现就其基本原理讲 述一些例子。 使用管道将字符串s t a n d - b y传入a w k,返回其长度。 设置文件名为一变量,管道输出到 a w k,返回不带扩展名的文件名。 设置文件名为一变量,管道输出到 a w k,只返回其扩展名。 9.2.10 字符串屏蔽序列 使用字符串或正则表达式时,有时需要在输出中加入一新行或查询一元字符。 打印一新行时,(新行为字符 \ n),给出其屏蔽序列,以不失其特殊含义,用法为在字符 串前加入反斜线。例如使用 \ n强迫打印一新行。 如果使用正则表达式,查询花括号( { }),在字符前加反斜线,如 / \ { /,将在a w k中失掉其 特殊含义。 表9 - 5列出a w k识别的另外一些屏蔽序列 表9-5 awk中使用的屏蔽序列 \ b 退格键 \ t t a b键 \ f 走纸换页 \ d d d 八进制值 \ n 新行 \ c 任意其他特殊字符,例如 \ \为反斜线符号 \ r 回车键 80 第二部分 文本过滤 下载 使用上述符号,打印 May Day,中间夹t a b键,后跟两个新行,再打印 May Day,但这次 使用八进制数1 0 4、1 4 1、1 7 1、分别代表D、a、y。 注意,\ 1 0 4为D的八进制A S C I I码,\ 1 4 1为a的八进制A S C I I码,等等。 9.2.11 awk输出函数printf 目前为止,所有例子的输出都是直接到屏幕,除了 t a b键以外没有任何格式。 a w k提供函 数p r i n t f,拥有几种不同的格式化输出功能。例如按列输出、左对齐或右对齐方式。 每一种p r i n t f函数(格式控制字符)都以一个 %符号开始,以一个决定转换的字符结束。 转换包含三种修饰符。 p r i n t f函数基本语法是p r i n t f([格式控制符],参数),格式控制字符通常在引号里。 9.2.12 printf修饰符 表9-6 awk printf修饰符 - 左对齐 Wi d t h 域的步长,用 0表示0步长 . p r e c 最大字符串长度,或小数点右边的位数 表9-7 awk printf格式 % c A S C I I字符 % d 整数 % e 浮点数,科学记数法 % f 浮点数,例如(1 2 3 . 4 4) % g a w k决定使用哪种浮点数转换 e或者f % o 八进制数 % s 字符串 % x 十六进制数 1. 字符转换 观察A S C I I码中6 5的等价值。管道输出 6 5到a w k。p r i n t f进行A S C I I码字符转换。这里也加 入换行,因为缺省情况下p r i n t f不做换行动作。 当然也可以按同样方式使用 a w k得到同样结果。 所有的字符转换都是一样的,下面的例子表示进行浮点数转换后‘ 9 9 9’的输出结果。整 数传入后被加了六个小数点。 2. 格式化输出 第9章 AWK 介绍 81下载 打印所有的学生名字和序列号,要求名字左对齐, 1 5个字符长度,后跟序列号。注意 \ n 换行符放在最后一个指示符后面。输出将自动分成两列。 最好加入一些文本注释帮助理解报文含义。可在正文前嵌入头信息。注意这里使用 p r i n t 加入头信息。如果愿意,也可使用 p r i n t f。 3. 向一行a w k命令传值 在查看a w k脚本前,先来查看怎样在a w k命令行中传递变量。 在a w k执行前将值传入a w k变量,需要将变量放在命令行中,格式如下: awk 命令变量=输入文件值 (后面会讲到怎样传递变量到 a w k脚本中)。 下面的例子在命令行中设置变量 A G E等于1 0,然后传入 a w k中,查询年龄在 1 0岁以下的 所有学生。 要快速查看文件系统空间容量,观察其是否达到一定水平,可使用下面 a w k一行脚本。因 为要监视的已使用空间容量不断在变化,可以在命令行指定一个触发值。首先用管道命令将 df -k传入 a w k,然后抽出第 4列,即剩余可利用空间容量。使用 $ 4 ~ / ^ [ 0 - 9 ] /取得容量数值 (1 0 2 4块)而不是d f的文件头,然后对命令行与‘ i f ( $ 4 < T R I G G E R )’上变量T R I G G E R中指定 的值进行查询测试。 在系统中使用df -k命令,产生下列信息: 如果系统中d f输出格式不同,必须相应改变列号以适应工作系统。 当然可以使用管道将值传入 a w k。本例使用 w h o命令,w h o命令第一列包含注册用户名, 这里打印注册用户,并加入一定信息。 a w k也允许传入环境变量。下面的例子使用环境变量 L O G N A M E支持当前用户名。可从 w h o命令管道输出到a w k中获得相应信息。 82 第二部分 文本过滤 下载 如果r o o t为当前登录用户,输出如下: root you are connected to ttyp1 4. awk脚本文件 可以将a w k脚本写入一个文件再执行它。命令不必很长(尽管这是写入一个脚本文件的主 要原因),甚至可以接受一行命令。这样可以保存 a w k命令,以使不必每次使用时都需要重新 输入。使用文件的另一个好处是可以增加注释,以便于理解脚本的真正用途和功能。 使用前面的几个例子,将之转换成 a w k可执行文件。像原来做的一样,将学生目前级别分 相加awk ‘(t o t + = $ 6) END{print "club student total points:" t o t }’ g r a d e . t x t。 创建新文件s t u d e n t _ t o t . a w k,给所有a w k程序加入a w k扩展名是一种好习惯,这样通过查 看文件名就知道这是一个a w k程序。文本如下: 第一行是!/bin/awk -f。这很重要,没有它自包含脚本将不能执行。这一行告之脚本系统 中a w k的位置。通过将命令分开,脚本可读性提高,还可以在命令之间加入注释。这里加入头 信息和结尾的平均值。基本上这是一个一行脚本文件。 执行时,在脚本文件后键入输入文件名,但是首先要对脚本文件加入可执行权限。 系统中运用的帐号核实程序检验数据操作人的数据输入,不幸的是这个程序有一点错误, 或者应该说是“非文本特征”。如果一个记录被发现包含一个错误,它应该一次只打印一行 第9章 AWK 介绍 83下载 “E R R O R *”,但实际上打印了许多这样的错误行。这会给帐号管理员造成误解,因此需要用 a w k脚本过滤出错误行的出现频率,使得每一个失败记录只对应一个错误行。 在a w k实施过滤前先看看部分文件: a w k脚本如下: a w k过滤结果如下: 5. 在a w k中使用F S变量 如果使用非空格符做域分隔符(F S)浏览文件,例如# 或:,编写这样的一行命令很容易, 因为使用F S选项可以在命令行中指定域分隔符。 84 第二部分 文本过滤 下载 使用a w k脚本时,记住设置 F S变量是在B E G I N部分。如果不这样做, a w k将会发生混淆, 不知道域分隔符是什么。 下述脚本指定 F S变量。脚本从 / e t c / p a s s w d文件中抽取第 1和第5域,通过分号“;”分隔 p a s s w d文件域。第1域是帐号名,第5域是帐号所有者。 6. 向a w k脚本传值 向a w k脚本传值与向a w k一行命令传值方式大体相同,格式为: awk script_file var=value input_file 下述脚本对比检查文件中域号和指定数字。这里使用了 N F变量M A X,表示指定检查的域 号,使用双引号将域分隔符括起来,即使它是一个空格。 如果以/ e t c / p a s s w d作输入文件(p a s s w d文件有7个域),运行上述脚本。参数格式如下: 使用前面一行脚本的例子,将之转换成 a w k脚本如下: 文本包括了比实际命令更多的信息,没关系,仔细研读文本后,就可以精确知道其功能 及如何调用它。 不要忘了增加脚本的可执行权限,然后将变量和赋值放在命令行脚本名字后、输入文件 前执行。 第9章 AWK 介绍 85下载 同样可以使用前面提到的管道命令传值,下述 a w k脚本从d u命令获得输入,并输出块和字 节数。 为运行这段脚本,使用d u命令,并管道输出至a w k脚本。 9.2.13 awk数组 前面讲述s p l i t函数时,提到怎样使用它将元素划分进一个数组。这里还有一个例子: 在上面的例子中,s p l i t返回数组m y a r r a y下标数。实际上m y a r r a y数组为: 数组使用前,不必定义,也不必指定数组元素个数。经常使用循环来访问数组。下面是 一种循环类型的基本结构: For (element in array ) print array[element] 对于记录“1 2 3 # 4 5 6 # 6 7 8”,先使用s p l i t函数划分它,再使用循环打印各数组元素。操作 脚本如下: 数组和记录 86 第二部分 文本过滤 下载 要运行脚本,使用/ d e v / n u l l作为输入文件。 上面的例子讲述怎样通过s p l i t函数使用数组。也可以预先定义数组,并使用它与域进行比 较测试,下面的例子中将使用更多的数组。 下面是从空手道数据库卸载的一部分数据,包含了学生级别及是否是成人或未成年人的 信息,有两个域,分隔符为( #),文件如下: 脚本功能是读文件并输出下列信息。 1) 俱乐部中Ye l l o w、O r a n g e和R e d级别的人各是多少。 2 ) 俱乐部中有多少成年人和未成年人。 查看文件,也许 2 0秒内就会猜出答案,但是如果记录超过 6 0个又怎么办呢?这不会很容 易就看出来,必须使用a w k脚本。 首先看看a w k脚本,然后做进一步讲解。 第9章 AWK 介绍 87下载 B E G I N部分设置F S为符号#,即域分隔符,因为要查找 Ye l l o w、O r a n g e和R e d三个级别。 然后在脚本中手工建立数组下标对学生做同样的操作。注意,脚本到此只有下标或元素,并 没有给数组名本身加任何注释。初始化完成后, B E G I N部分结束。记住 B E G I N部分并没有文 件处理操作。 现在可以处理文件了。首先给数组命名为 c o l o r,使用循环语句测试域 1级别列是否等于数 组元素之一(Ye l l o w、O r a n g e或R e d),如果匹配,依照匹配元素将运行总数保存进数组。 同样处理数组‘ S e n i o r _ o r _ j u n i o r’,浏览域 2时匹配操作满足,运行总数存入 j u n i o r或 s e n i o r的匹配数组元素。 E N D部分打印浏览结果,对每一个数组使用循环语句并打印它。 注意在打印语句末尾有一个 \符号,用来通知 a w k(或相关脚本)命令持续到下一行,当 输入一个很长的命令,并且想分行输入时可使用这种方法。运行脚本前记住要加入可执行权 限。 9.3 小结 a w k语言学起来可能有些复杂,但使用它来编写一行命令或小脚本并不太难。本章讲述了 a w k的最基本功能,相信 大家已经掌握了a w k的基本用法。a w k是s h e l l编程的一个重要工具。 在s h e l l命令或编程中,虽然可以使用 a w k强大的文本处理能力,但是并不要求你成为这方面的 专家。 88 第二部分 文本过滤 下载 下载 第10章 sed 用法介绍 s e d是一个非交互性文本流编辑器。它编辑文件或标准输入导出的文本拷贝。标准输入可 能是来自键盘、文件重定向、字符串或变量,或者是一个管道的文本。 s e d可以做些什么呢? 别忘了,Vi也是一个文本编辑器。 s e d可以随意编辑小或大的文件,有许多 s e d命令用来编辑、 删除,并允许做这项工作时不在现场。 s e d一次性处理所有改变,因而变得很有效,对用户来 讲,最重要的是节省了时间。 本章内容有: • 抽取域。 • 匹配正则表达式。 • 比较域。 • 增加、附加、替换。 • 基本的s e d命令和一行脚本。 可以在命令行输入 s e d命令,也可以在一个文件中写入命令,然后调用 s e d,这与a w k基本 相同。使用s e d需要记住的一个重要事实是,无论命令是什么, s e d并不与初始化文件打交道, 它操作的只是一个拷贝,然后所有的改动如果没有重定向到一个文件,将输出到屏幕。 因为s e d是一个非交互性编辑器,必须通过行号或正则表达式指定要改变的文本行。 本章介绍s e d用法和功能。本章大多编写的是一行命令和小脚本。这样做可以慢慢加深对 s e d用法的了解,取得宝贵的经验,以便最终自己编出大的复杂 s e d脚本。 和g r e p与a w k一样,s e d是一种重要的文本过滤工具,或者使用一行命令或者使用管道与 g r e p与a w k相结合。 10.1 sed怎样读取数据 s e d从文件的一个文本行或从标准输入的几种格式中读取数据,将之拷贝到一个编辑缓冲 区,然后读命令行或脚本的第一条命令,并使用这些命令查找模式或定位行号编辑它。重复 此过程直到命令结束。 10.2 调用sed 调用s e d有三种方式:在命令行键入命令;将 s e d命令插入脚本文件,然后调用 s e d;将s e d 命令插入脚本文件,并使s e d脚本可执行。 使用s e d命令行格式为: sed [选项] s e d命令 输入文件。 记住在命令行使用s e d命令时,实际命令要加单引号。 s e d也允许加双引号。 使用s e d脚本文件,格式为: sed [选项] -f sed脚本文件 输入文件 要使用第一行具有s e d命令解释器的s e d脚本文件,其格式为: s e d脚本文件 [选项] 输入文件 不管是使用s h e l l命令行方式或脚本文件方式,如果没有指定输入文件, s e d从标准输入中 接受输入,一般是键盘或重定向结果。 s e d选项如下: n 不打印;s e d不写编辑行到标准输出,缺省为打印所有行(编辑和未编辑)。p命令可以 用来打印编辑行。 c 下一命令是编辑命令。使用多项编辑时加入此选项。如果只用到一条 s e d命令, 此选项无用,但指定它也没有关系。 f 如果正在调用s e d脚本文件,使用此选项。此选项通知 s e d一个脚本文件支持所有的 s e d 命令,例如:sed -f myscript.sed input_file,这里m y s c r i p t . s e d即为支持s e d命令的文件。 10.2.1 保存sed输出 由于不接触初始化文件,如果想要保存改动内容,简单地将所有输出重定向到一个文件 即可。下面的例子重定向 s e d命令的所有输出至文件‘ m y o u t f i l e’,当对结果很满意时使用这 种方法。 10.2.2 使用sed在文件中查询文本的方式 s e d浏览输入文件时,缺省从第一行开始,有两种方式定位文本: 1) 使用行号,可以是一个简单数字,或是一个行号范围。 2 ) 使用正则表达式,怎样构建这些模式请参见第 7章。 表1 0 - 1给出使用s e d定位文本的一些方式。 表10-1 使用s e d在文件中定位文本的方式 xx为一行号,如 1 x , y 表示行号范围从x到y,如2,5表示从第2行到第5行 / p a t t e r n / 查询包含模式的行。例如 / d i s k /或/[a-z]/ / p a t t e r n / p a t t e r n / 查询包含两个模式的行。例如 / d i s k / d i s k s / p a t t e r n / , x 在给定行号上查询包含模式的行。如 / r i b b o n / , 3 x , / p a t t e r n / 通过行号和模式查询匹配行。 3 . / v d u / x , y ! 查询不包含指定行号x和y的行。1 , 2 ! 10.2.3 基本sed编辑命令 表1 0 - 2列出了S e d的编辑命令。 表10-2 sed编辑命令 p 打印匹配行 = 显示文件行号 a \ 在定位行号后附加新文本信息 i \ 在定位行号后插入新文本信息 d 删除定位行 c \ 用新文本替换定位文本 90 第二部分 文本过滤 下载 (续) s 使用替换模式替换相应模式 r 从另一个文件中读文本 w 写文本到一个文件 q 第一个模式匹配完成后推出或立即推出 l 显示与八进制 A S C I I代码等价的控制字符 { } 在定位行执行的命令组 n 从另一个文件中读文本下一行,并附加在下一行 g 将模式2粘贴到/pattern n/ y 传送字符 n 延续到下一输入行;允许跨行的模式匹配语句 如果不特别声明,s e d例子中使用下述文本文件q u o t e . t x t。 10.3 sed和正则表达式 s e d识别任何基本正则表达式和模式及其行匹配规则。记住规则之一是:如果要定位一特 殊字符,必须使用( \)屏蔽其特殊含义,如有必要请参照第 7章正则表达式。第 7章使用的所 有正则表达式在s e d中都是合法的。 10.4 基本sed编程举例 下面通过例子实际检验一下 s e d的编辑功能。 10.4.1 使用p(rint)显示行 p r i n t命令格式为[ a d d r e s s [,a d d r e s s ] P。显示文本行必须提供s e d命令行号。 错误在哪儿?原意只打印第二行,但是却打印了文件中所有行,为此需使用 - n选项,显 示打印定位(匹配)行。 10.4.2 打印范围 可以指定行的范围,现打印 1到3行,用逗号分隔行号。 第10章 sed 用法介绍 91下载 10.4.3 打印模式 假定要匹配单词N e a v e,并打印此行,方法如下。使用模式 / p a t t e r n /格式,这里为/ N e a v e /。 10.4.4 使用模式和行号进行查询 为编辑某个单词浏览一个文件时, s e d返回包含指定单词的许多行。怎样使返回结果更精 确以满足模式匹配呢?可以将行号和模式结合使用。下面这个例子,假定要改动文件 q u o t e . t x t 最后一行中的单词t h e,使用s e d查询t h e,返回两行: 使用模式与行号的混合方式可以剔除第一行,格式为 l i n e _ n u m b e r, / p a t t e r n /。逗号用来分 隔行号与模式开始部分。为达到预期结果,使用 4 , / t h e /。意即只在第四行查询模式 t h e,命令 如下: 10.4.5 匹配元字符 匹配元字符$前,必须使用反斜线\屏蔽其特殊含义。模式为/\$/ p。 10.4.6 显示整个文件 要打印整个文件,只需将行范围设为第一行到最后一行 1 , $。$意为最后一行。 10.4.7 任意字符 匹配任意字母,后跟任意字母的 0次或多次重复,并以i n g结尾,模式为/ . * i n g /。可以使用 这个模式查询以i n g结尾的任意单词。 10.4.8 首行 要打印文件第一行,使用行号: 92 第二部分 文本过滤 下载 10.4.9 最后一行 要打印最后一行,使用$。$是代表最后一行的元字符。 10.4.10 打印行号 要打印行号,使用等号=。打印模式匹配的行号,使用格式 / p a t t e r n / =。 整个文件都打印出来,并且匹配行打印了行号。如果只关心实际行号,使用 - e选项。 如果只打印行号及匹配行,必须使用两个 s e d命令,并使用 e选项。第一个命令打印模式 匹配行,第二个使用=选项打印行号,格式为sed -n -e /pattern/p -e /pattern/=。 10.4.11 附加文本 要附加文本,使用符号a \,可以将指定文本一行或多行附加到指定行。如果不指定文本放 置位置,s e d缺省放在每一行后面。附加文本时不能指定范围,只允许一个地址模式。文本附 加操作时,结果输出在标准输出上。注意它不能被编辑,因为 s e d执行时,首先将文件的一行 文本拷贝至缓冲区,在这里 s e d编辑命令执行所有操作(不是在初始文件上),因为文本直接 输出到标准输出,s e d并无拷贝。 要想在附加操作后编辑文本,必须保存文件,然后运行另一个 s e d命令编辑它。这时文件 的内容又被移至缓冲区。 附加操作格式如下: 地址指定一个模式或行号,定位新文本附加位置。 a\ 通知s e d对a \后的文本进行实际附加 操作。观察格式,注意每一行后面有一斜划线,这个斜划线代表换行。 s e d执行到这儿,将创 建一新行,然后插入下一文本行。最后一行不加斜划线, s e d假定这是附加命令结尾。 当附加或插入文本或键入几个 s e d命令时,可以利用辅助的 s h e l l提示符以输入多行命令。 这里没有这样做,因为可以留给使用者自己编写,并且在一个脚本文件中写这样的语句更适 宜。现在马上讲述s e d脚本文件。另外,脚本可以加入空行和注释行以增加可读性。 第10章 sed 用法介绍 93下载 10.4.12 创建sed脚本文件 要创建脚本文件a p p e n d . s e d,输入下列命令: 保存它,增加可执行权限: 运行, 如果返回‘file not found’,试在脚本前加入. \。 现在查看其具体功能。第一行是 s e d命令解释行。脚本在这一行查找 s e d以运行命令,这里 定位在/ b i n。 第二行以/ c o m p a n y /开始,这是附加操作起始位置。 a \通知s e d这是一个附加操作,首先 应插入一个新行。第三行是附加操作要加入到拷贝的实际文本。 输出显示附加结果。如果要保存输出,重定向到一个文件。 10.4.13 插入文本 插入命令类似于附加命令,只是在指定行前面插入。和附加命令一样,它也只接受一个 地址。下面是插入命令的一般格式。地址是匹配模式或行号: 下面例子在以a t t e n d a n c e结尾的行前插入文本utter confusion followed。 运行结果是: 94 第二部分 文本过滤 下载 也可以使用行号指定文本插入位置,插入位置在模式或指定行号 4之前。脚本如下: 10.4.14 修改文本 修改命令将在匹配模式空间的指定行用新文本加以替代,格式如下: 将第一行The honeysuckle band played all night long for only $90替换为The office Di b b l e band played well。首先要匹配第一行的任何部分,可使用模式‘ / H o n e y s u c k l e /’。s e d脚本文 件为c h a n g e . s e d。内容如下: 运行它,不要忘了给脚本增加可执行权限。 chmod u+x change.sed。 像插入动作一样,可以使用行号代替模式,两种方式完成相同的功能。 可以对同一个脚本中的相同文件进行修改、附加、插入三种动作匹配和混合操作。 下面是一个带有注释的脚本例子。 第10章 sed 用法介绍 95下载 运行它,结果如下: 10.4.15 删除文本 s e d删除文本格式: [ a d d r e s s [đa d d r e s s ] ] d 地址可以是行的范围或模式,让我们看几个例子。 删除第一行;1 d意为删除第一行。 删除第一到第三行: 删除最后一行: 也可以使用正则表达式进行删除操作。下面的例子删除包含文本‘ N e a v e’的行。 10.4.16 替换文本 替换命令用替换模式替换指定模式,格式为: [ a d d r e s s [đaddress]] s/ pattern-to-find /replacement-pattern/[g p w n] s选项通知s e d这是一个替换操作,并查询 p a t t e r n - t o - f i n d,成功后用r e p l a c e m e n t - p a t t e r n替 换它。 替换选项如下: g 缺省情况下只替换第一次出现模式,使用 g选项替换全局所有出现模式。 p 缺省s e d将所有被替换行写入标准输出,加 p选项将使- n选项无效。- n选项不打印输出 结果。 w 文件名 使用此选项将输出定向到一个文件。 96 第二部分 文本过滤 下载 让我们看几个例子。替换 n i g h t为N I G H T,首先查询模式 n i g h t,然后用文本 N I G H T替换 它。 要从 $ 9 0中删除 $ 符号(记住这是一个特殊符号,必须用 \屏蔽其特殊含义),在 r e p l a c e m e n t - p a t t e r n部分不写任何东西,保留空白,但仍需要用斜线括起来。在 s e d中也可以这 样删除一个字符串。 要进行全局替换,即替换所有出现模式,只需在命令后加 g选项。下面的例子将所有 T h e 替换成Wo w!。 将替换结果写入一个文件用 w选项,下面的例子将 s p l e n d i d替换为S P L E N D I D的替换结果 写入文件s e d . o u t: 注意要将文件名括在s e d的单引号里。文件结果如下: 10.5 使用替换修改字符串 如果要附加或修改一个字符串,可以使用( &)命令,&命令保存发现模式以便重新调用 它,然后把它放在替换字符串里面。这里给出一个修改的设计思路。先给出一个被替换模式, 然后是一个准备附加在第一个模式后的另一个模式,并且后面带有 &,这样修改模式将放在 匹配模式之前。例如,s e d语句s/nurse/"Hello"&/p 的结果如下: 原句是文本行The local nurse Miss P.Neave was in attendance。 记住模式中要使用空格,因为输出结果表明应加入空格。 还有一个例子: 原句是The honeysuckle band played all night long for only $90。相信这种修改动作已经讲 解得很清楚了。 10.6 将sed结果写入文件命令 像使用>文件重定向发送输出到一个文件一样,在 s e d命令中也可以将结果输入文件。格 式有点像使用替换命令: 第10章 sed 用法介绍 97下载 [ a d d r e s s [đaddress]]w filename ‘w’选项通知s e d将结果写入文件。f i l e n a m e是自解释文件名。下面有两个例子。 文件q u o t e . t x t输出到屏幕。模式范围即1,2行输出到文件f i l e d t。 下面例子中查询模式N e a v e,匹配结果行写入文件f i l e d h t。 10.7 从文件中读文本 处理文件时,s e d允许从另一个文件中读文本,并将其文本附加在当前文件。此命令放在 模式匹配行后,格式为: address r filename 这里r通知s e d将从另一个文件源中读文本。 f i l e n a m e是其文件名。 现在创建一个小文件s e d e x . t x t,内容如下: 将s e d e x . t x t内容附加到文件q u o t e . t x t的拷贝。在模式匹配行/ c o m p a n y /后放置附加文本。本 例为第三行。注意所读的文件名需要用单引号括起来。 10.8 匹配后退出 有时需要在模式匹配首次出现后退出 s e d,以便执行其他处理脚本。退出命令格式为: address q 下面的例子假定查询模式 / . a . * /,意为任意字符后跟字符 a,再跟任意字符0次或任意多次。 查看文本文件,然后在下列行产生下列单词: 查询首次出现模式,然后退出。需要将 q放在s e d语句末尾。 98 第二部分 文本过滤 下载 10.9 显示文件中的控制字符 当从其他系统下载文件时,有时要删除整个文件的控制字符(非打印字符),从菜单中捕 获一个应用的屏幕输出有时也会将控制字符输出进文件,怎样知道文件中是否有控制字符? 使用cat -v filename命令,屏幕会乱叫,且到处都是一些垃圾字符,这可以确知文件中包含有 控制字符,如果有兴趣可以观察一下这些字符以便于更加确认它们是控制字符。 一些系统中使用cat filename而不是c a t - v来查看非打印字符。 s e d格式为: [ a d d r e s sđ[ a d d r e s s ] ] l ‘l’意为列表。 一般情况下要列出整个文件,而不是模式匹配行,因此使用 l要从第一到最后一行。模式 范围1,$即为此意。 如果c a t一个文件,发现实际上包含有控制字符。 现在运行s e d命令,观察输出结果。 s e d找到并显示了两个控制字符。 \ 0 3 3代表退格键,O P为F 1键值,放在退格键后。第二行 也是如此。 各系统控制字符键值可能不同,主要取决于其映射方式(例如使用 t e r m i n f o或t e r m c a p)。 如果要在文本文件中插入控制字符 F 1键,使用v i查看其键值,操作如下: • 启动v i。 • 进入插入模式。 • 按下< C t r l >键,然后按< v >键 (出现a ^)。 • 释放上述两个键。 • 按下F 1键(显示[ O P ]。 • 按下< E S C >键(显示F 1键值)。 10.10 使用系统sed 前面已经讲述了s e d的基本功能,但是在脚本或命令行中使用 s e d真正要做的是修改或删除 文件或字符串中文本。下面运用前面学过的知识讲述这一点。 10.10.1 处理控制字符 使用s e d实现的一个重要功能是在另一个系统中下载的文件中剔除控制字符。 下面是传送过来的文件(d o s . t x t)的部分脚本。必须去除所有可疑字符,以便于帐号所有 者使用文件。 第10章 sed 用法介绍 99下载 可采取以下动作: 1) 用一个空格替换所有的(# #)符号。 2) 删除起始域中最前面的0(0 0)。 3) 删除行尾控制字符(^ M)。 一些系统中,回车符为 ^ @和^ L,如果遇到一些怪异的字符,不必担心,只要是在行尾并 且全都相同就可以。 按步执行每一项任务,以保证在进行到下一任务前得到理想结果。使用输入文件 d o s . t x t。 任务1。 删除所有的#字符很容易,可以使用全局替换命令。这里用一个空格替换两个或 更多的#符号。 任务2。 删除所有行首的0。使用^符号表示模式从行首开始, ^ 0 *表示行首任意个0。模式 s / ^ 0 * / / g设置替换部分为空,即为删除模式,正是要求所在。 任务 3。 最后去除行尾 ^ M符号,为此需做全局替换。设置替换部分为空。模式为: ‘s / ^ m / / g’,注意‘^ M’,这是一个控制字符。 要产生控制字符( ^ M),需遵从前面产生 F 1键同样的处理过程。步骤如下;键入 sed s/, 然后按住< C t r l >键和v键,释放v键,再按住^键,并保持< C t r l >键不动,再释放两个键,最后 按< r e t u r n >键。下面命令去除行尾^ M字符。 分步测试预想功能对理解整个过程很有帮助。用 s e d在移到下一步前测试本步功能及结果 很重要。如果不这样,可能会有一大堆包含怪异字符的意料外的结果。 将所有命令结合在一起,使用管道将 c a t命令结果传入一系列 s e d命令,s e d命令与上面几 步精确过滤字符的s e d相同。 现在文件对帐号管理者可用。 可以将命令放在文件里,然后运行它。下面即为转换脚本。 100 第二部分 文本过滤 下载 通过仅指定一个s e d命令可以将命令行缩短,本书后面部分介绍脚本中 s e d的用法。 10.10.2 处理报文输出 当从数据库中执行语句输出时,一旦有了输出结果,脚本即可做进一步处理。通常先做 一些整理,下面是一个s q l查询结果。 为了使用上述输出信息做进一步自动处理,需要知道所存数据库名称,为此需执行以下 操作: 1) 使用s / - * / / g删除横线- - - - - -。 2) 使用/ ^ $ / d删除空行。 3) 使用$ d删除最后一行 4) 使用1 d删除第一行。 5) 使用awk {print $1}打印第一列。 命令如下,这里使用了c a t,并管道传送结果到s e d命令。 10.10.3 去除行首数字 对接下来卸载的这个文件实施的操作是去除行首所有数字,每个记录应以 U N H或U N D开 头,而不是U N H或U N D前面的数字。文件如下: 使用基本正则表达式完成这个操作。 [ 0 - 9 ]代表行首任意数字,替换部分为空格是为了确 保删除前面的匹配模式,即数字。 第10章 sed 用法介绍 101下载 10.10.4 附加文本 当帐户完成设置一个文件时,帐号管理者可能要在文件中每个帐号后面加一段文字,下 面是此类文件的一部分: 任务是在每一行末尾加一个字符串‘ p a s s e d’。 使用$命令修改各域会使工作相对容易些。首先需要匹配至少两个或更多的数字重复出现, 这样将所有的帐号加进匹配模式。 10.10.5 从shell向sed传值 要从命令行中向s e d传值,值得注意的是用双引号,否则功能不执行。 10.10.6 从sed输出中设置shell变量 从s e d输出中设置 s h e l l变量是一个简单的替换过程。运用上面的例子,创建 s h e l l变量 N E W- N A M E,保存上述s e d例子的输出结果。 10.11 快速一行命令 下面是一些一行命令集。([ ]表示空格,[ ]表示t a b键) ‘s / \ . $ / / g’ 删除以句点结尾行 ‘-e /abcd/d’ 删除包含a b c d的行 ‘s / [ ] [ ] [ ] * / [ ] / g’ 删除一个以上空格,用一个空格代替 ‘s / ^ [ ] [ ] * / / g’ 删除行首空格 ‘s / \ . [ ] [ ] * / [ ] / g’ 删除句点后跟两个或更多空格,代之以一个空格 ‘/ ^ $ / d’ 删除空行 ‘s / ^ . / / g’ 删除第一个字符 ‘s /CO L \ ( . . . \ ) / / g’ 删除紧跟C O L的后三个字母 ‘s / ^ \ / / / g’ 从路径中删除第一个\ ‘s / [ ] / [ ] / / g’ 删除所有空格并用t a b键替代 ‘S / ^ [ ] / / g’ 删除行首所有 t a b键 ‘s / [ ] * / / g’ 删除所有t a b键 102 第二部分 文本过滤 下载 在结束这一章前,看看一行脚本的一些例子。 1. 删除路径名第一个\符号 将当前工作目录返回给s e d,删除第一个\: 2. 追加/插入文本 将"Mr Wi l l i s "字串返回给s e d并在M r后而追加" B r u c e "。 3. 删除首字符 s e d删除字符串“a c c o u n t s . d o c”首字符。 4. 删除文件扩展名 s e d删除“a c c o u n t s . d o c”文件扩展名。 5. 增加文件扩展名 s e d附加字符串“. d o c”到字符串“a c c o u n t s”。 6. 替换字符系列 如果变量x含有下列字符串: 如果要实现下列转换: s e d命令是: 10.12 小结 s e d是一个强大的文本过滤工具。使用 s e d可以从文件或字符串中抽取所需信息。正像前面 讲到的,s e d不必写太长的脚本以取得所需信息。本章只讲述了 s e d的基本功能,但使用这些功 能就可以执行许多任务了。 如果使用s e d对文件进行过滤,最好将问题分成几步,分步执行,且边执行边测试结果。 经验告诉我们,这是执行一个复杂任务的最有效方式。 第10章 sed 用法介绍 103下载 下载 第11章 合并与分割 几年前,我习惯于使用运行在终端的 P I C K操作的U N I X集合,我实际使用 P I C K应用的大 部分时间花费在分类与连接过程中,且使用极其频繁。很幸运我没有成为一个全职的 P I C K操 作员。 有几种工具用来处理文本文件分类、合并和分割操作,本章详细介绍这些工具。 本章内容有: • 实用的分类(s o r t)操作。 • uniq。 • join。 • cut。 • paste。 • split。 11.1 sort用法 s o r t命令将许多不同的域按不同的列顺序分类。当查阅注册文件或为另一用户对下载文件 重排文本列时, s o r t工具很方便。实际上,使用其他 U N I X工具时,已假定工作文件已经被分 过类。无论如何,分类文件比不分类文件看起来更有意义。 11.1.1 概述 U N I X / L I N U X自带的s o r t功能很强大。尽管有时在使用 s o r t各种不同的选项时人们已经很 小心,但仍会产生意想不到的结果。 s o r t选项很长,甚至有时在各种不同开关的实际功能和结 果进行比较时也会遇到麻烦,原因可能是在结合使用 s o r t的不同选项时有些概念模糊不清。 本章不讨论各种不同的 s o r t方法(不能说s o r t不够强大;它很慢,但观察数值交替变化是 很有趣的)也不讨论各种不同开关的结合使用功效。本章只讲到主要的 s o r t选项,伴随有大量 实例。与s o r t结合使用的u n i q、j o i n、c u t和p a s t e方法与s p l i t方法也将会涉及到。 上面提到,s o r t命令选项很长,下面介绍本章使用的各种选项。 11.1.2 sort选项 s o r t命令的一般格式为: sort -cmu -o output_file [other options] +pos1 +pos2 input_files 下面简要介绍一下s o r t的参数: -c 测试文件是否已经分类。 -m 合并两个分类文件。 -u 删除所有复制行。 -o 存储s o r t结果的输出文件名。 其他选项有: -b 使用域进行分类时,忽略第一个空格。 -n 指定分类是域上的数字分类。 -t 域分隔符;用非空格或t a b键分隔域。 -r 对分类次序或比较求逆。 +n n为域号。使用此域号开始分类。 n n为域号。在分类比较时忽略此域,一般与 + n一起使用。 post1 传递到m,n。m为域号,n为开始分类字符数;例如 4,6意即以第5域分类,从第7 个字符开始。 11.1.3 保存输出 - o选项保存分类结果,然而也可以使用重定向方法保存。下面例子保存结果到 r e s u l t s . o u t: 11.1.4 sort启动方式 缺省情况下,s o r t认为一个空格或一系列空格为分隔符。要加入其他方式分隔,使用 - t选 项。 s o r t执行时,先查看是否为域分隔设置了 - t选项,如果设置了,则使用它来将记录分隔成 域0、域1等等;如果未设置,用空格代替。缺省时 s o r t将整个行排序,指定域号的情况例外。 下面是文件v i d e o . t x t的清单,包含了上个季度家电商场的租金情况。各域为:(1)名称, (2)供货区代码,(3)本季度租金,(4)本年租金。域分隔符为冒号。为此对此例需使用‘ - t’ 选项。文件如下: 11.1.5 sort对域的参照方式 关于s o r t的一个重要事实是它参照第一个域作为域 0,域1是第二个域,等等。 s o r t也可以 使用整行作为分类依据。为防止混淆,对于此文件用户应按如下方式参照域并做分类依据: s o r t将定位各域,因此应把域0作为分类键0,域1作为分类键1等等。 11.1.6 文件是否已分类 怎样分辨文件是否已分类?如果只有 3 0行,看看就知道了,但如果是 4 0 0行呢,使用s o r t - c 第11章 合并与分割 105下载 106 第二部分 文本过滤 下载 通知s o r t文件是否按某种顺序分类。 结果显示未分类,现在将之分类,再试一次: 返回提示符表明已分类。然而如果测试成功,返回一个信息行会更好。 11.1.7 基本sort 最基本的s o r t方式为sort filename,按第一域进行分类(分类键 0)。实际上读文件时s o r t操 作将行中各域进行比较,这里返回基于第一域 s o r t的结果,如下所示: 11.1.8 sort分类求逆 如果要逆向s o r t结果,使用- r选项。在通读大的注册文件时,使用逆向 s o r t很方便。下面是 按域0分类的逆向结果。 11.1.9 按指定域分类 有时需要只按第 2域(分类键 1)分类。这里为重排报文中供应区代码,使用 t 1,意义为 按分类键1分类。下面的例子中,所有供应区代码按分类键 1分类;注意分类键 2和3对应各域 也被分类。 11.1.10 数值域分类 依此类推,要按第三分类键分类,使用 t 3。但是因为这是数值域,即为数值分类,可以使 用- n选项。下面例子为按年租金分类命令及结果: 如果不加- n,结果会怎样?这里假定按第 3域分类,找出最好的季度租金。因为是分类键 2,所以使用t 2。 观察结果,分类进行了,但不是预想的结果,因为第 3域为数值域。当然这个结果也是某 种类型的排列,录像机The Hill应该在第二行,但结果是: s o r t只查看第3域每个数值的第一个 数,并按其分类,然后再按第二个数依次下去。 记住按数值域分类要加- n,这样才会得到预想结果。 现在对了,可以看出本季度卖点最高的是 A l i e n s。如果使用- r选项,将会把A l i e n s放在第 一行。 11.1.11 唯一性分类 有时,原文件中有重复行,这时可以使用 - u选项进行唯一性(不重复)分类以去除重复 行,本例中A l i e n有相同的两行。带重复行的文件如下,其中 A l i e n插入了两次: 使用- u选项去除重复行,不必加其他选项, s o r t会自动处理。 第11章 合并与分割 107下载 11.1.12 使用k的其他sort方法 s o r t还有另外一些方法指定分类键。可以指定 k选项,第1域(分类键)以 1开始。不要与 前面相混淆。我经常使用这个选项。因为我习惯于第一域为数值 1,这样使用s o r t时用同样的 数值做分类依据会更有意义。其他选项也可以使用 k,主要用于指定分类域开始的字符数目。 要在第1域进行分类,可以使用- k 4,这是按年租金分类的次序。 11.1.13 使用k做分类键排序 可以指定分类键次序。先以第 4域,再以第1域分类,命令为 -k4 -k1,也可以反过来,以 便在文件首行显示最高年租金,方法如下: 11.1.14 指定sort序列 可以指定分类键顺序,也可以使用 - n选项指定不使用哪个分类键进行查询。看下面的 s o r t 命令: 该命令意即开始以域0分类,忽略域2,然后再使用域3分类。 11.1.15 pos用法 指定开始分类的域位置的另一种方法是使用如下格式: 意即从f i e l d _ n u m b e r开始分类,但是要在此域的第 c h a r a c t e r s _ i n个字符开始。 这里是一个例子,供应区代码加入一些后缀。如: 108 第二部分 文本过滤 下载 第11章 合并与分割 109下载 要只使用供应区代码后缀部分将文件分类,其命令为 + 1 . 2,意即以第 1域最左边第3个字 符开始分类,其具体含义及脚本如下: 11.1.16 使用head和tail将输出分类 分类操作时,不一定要显示整个文件或一页以查看 s o r t结果中的第一和最后一行。如果只 显示最高年租金,按第 4域分类- k 4并求逆,然后使用管道只显示 s o r t输出的第一行,此命令为 h e a d,可以指定查阅行数。如果只有第一行,则为 head -1: 要查阅最低年租金,使用t a i l命令与h e a d命令刚好相反,它显示文件倒数几行。 1为倒数一 行,2为倒数两行等等。查阅最后一行为 tail -1。结合上述的s o r t命令和t a i l命令显示最低年租 金: 可以使用h e a d或t a i l查阅任何大的文本文件,h e a d用来查阅文件头,基本格式如下: Ta i l用来查阅文件尾,基本格式为: 如果使用h e a d或t a i l时想省略显示行数,缺省时显示 1 0行。 要查阅文件前2 0行: 要查阅文件后7行: 11.1.17 awk使用sort输出结果 对数据分类时,对 s o r t结果加一点附加信息很有必要,对其他用户尤其如此。使用 a w k可 以轻松完成这一功能。比如说采用上面最低租金的例子,需要将 s o r t结果管道输出到a w k,不 要忘了用冒号作域分隔符,显示提示信息和实际数据。 11.1.18 将两个分类文件合并 将文件合并前,它们必须已被分类。合并文件可用于事务处理和任何种类的修改操作。 下面这个例子,因为忘了把两个家电名称加入文件,它们被放在一个单独的文件里,现在将 之并入一个文件。分类的合并格式为‘ sort -m sorted_file1 sorted_file2,下面是包含两个新家 电名称的文件列表,它已经分类完毕: 使用-m +o。将这个文件并入已存在的分类文件 v i d e o . s o r t,要以名称域进行分类,实际上 没有必要加入+ o,但为了保险起见,还是加上的好。 11.2 系统sort s o r t可以用来对/ e t c / p a s s w d文件中用户名进行分类。这里需要以第 1域即注册用户名分类, 然后管道输出结果到a w k,a w k打印第一域。 s o r t还可以用于d f命令,以递减顺序打印使用列。下面是一般 d f输出。 使用- b选项,忽略分类域前面的空格。使用域 4(+ 4),即容量列将分类求逆,最后得出 文件系统自由空间的清晰列表。 在一个文本文件中存入所有 I P地址的拷贝,这样查看本机 I P地址更容易一些。有时如果 在管理员权限下,就需要将此文件分类。将 I P地址按文件中某种数值次序分类时,需要指定 110 第二部分 文本过滤 下载 域分隔符为句点。这里只需关心 I P地址的最后一段。分类应从此域即域 3开始,未分类文件如 下: 分类后结果如下: 11.3 uniq用法 u n i q用来从一个文本文件中去除或禁止重复行。一般 u n i q假定文件已分类,并且结果正确。 我们并不强制要求这样做,如果愿意,可以使用任何非排序文本,甚至是无规律行。 可以认为u n i q有点像s o r t命令中唯一性选项。对,在某种程度上讲正是如此,但两者有一 个重要区别。s o r t的唯一性选项去除所有重复行,而 u n i q命令并不这样做。重复行是什么?在 u n i q里意即持续不断重复出现的行,中间不夹杂任何其他文本,现举例如下: u n i q将前三个May Day看作重复副本,但是因为第 4行有不同的文本,故并不认为第五行 持续的May Day为其副本。u n i q将保留这一行。 命令一般格式: 其选项含义: -u 只显示不重复行。 -d 只显示有重复数据行,每种重复行只显示其中一行 -c 打印每一重复行出现次数。 -f n为数字,前n个域被忽略。 一些系统不识别- f选项,这时替代使用- n。 使用本节开始时的文本,创建文件 m y f i l e . t x t,在此文件上运行u n i q命令。 注意第5行保留下来,其文本为最后一行 May Day。如果运行sort -u,将只返回May Day 和Going Down。 第11章 合并与分割 111下载 112 第二部分 文本过滤 下载 连续重复出现 使用- c选项显示行数,即每个重复行数目。本例中,行 May Day重复出现三次。 1. 不唯一 使用- d显示重复出现的不唯一行: 2. 对特定域进行测试 使用- n只测试一行一部分的唯一性。例如 - 5意即测试第 5域后各域唯一性。域从 1开始记 数。 如果忽略第1域,只测试第 2域唯一性,使用 - n 2,下述文件包含一组数据,其中第 2域代 表组代码。 运行u n i q,将返回所有行。因为这个文件每一行都不同。 如果指定测试在第1域后,结果就会不同。u n i q会比较三个相同的O P,因此将返回一行。 如果‘- f’返回错误,替代使用: 11.4 join用法 j o i n用来将来自两个分类文本文件的行连在一起。如果学过 S Q L语言,可能会很熟悉 j o i n 命令。 下面讲述j o i n工作方式。这里有两个文件 f i l e 1和f i l e 2,当然已经分类。每个文件里都有一 些元素与另一个文件相关。由于这种关系, j o i n将两个文件连在一起,这有点像修改一个主文 件,使之包含两个文件里的共同元素。 文本文件中的域通常由空格或 t a b键分隔,但如果愿意,可以指定其他的域分隔符。一些 系统要求使用j o i n时文件域要少于2 0,为公平起见,如果域大于 2 0,应使用D B M S系统。 为有效使用j o i n,需分别将输入文件分类。 其一般格式为: 让我们看看它的可用选项列表: an n为一数字,用于连接时从文件 n中显示不匹配行。例如, - a 1显示第一个文件的不匹 配行,- a 2为从第二个文件中显示不匹配行。 o n.m n为文件号,m为域号。1 . 3表示只显示文件1第三域,每个n,m必须用逗号分隔, 如1 . 3,2 . 1。 j n m n为文件号,m为域号。使用其他域做连接域。 t 域分隔符。用来设置非空格或 t a b键的域分隔符。例如,指定冒号做域分隔符 - t:。 现有两个文本文件,其中一个包含名字和街道地址,称为 n a m e . t x t,另一个是名字和城镇, 为t o w n . t x t。 连接两个文件 连接两个文件,使得名字支持详细地址。例如 M . G o l l s记录指出地址为12 Hidd Rd。连接 域为域0—名字域。因为两个文件此域相同, j o i n将假定这是连接域: 好,工作完成。缺省j o i n删除或去除连接键的第二次重复出现,这里即为名字域。 1. 不匹配连接 如果一个文件与另一个文件没有匹配域时怎么办?这时 j o i n不可以没有参数选项,经常指 定两个文件的- a选项。下面的例子显示匹配及不匹配域。 输出表明P. H e l l e r不匹配第二个文件中任何一个记录。再运行这个命令,但指定只显示第 一个文件中不匹配行: 2. 选择性连接 使用- o选项选择连接域。例如要创建一个文件仅包含人名及城镇, j o i n执行时需要指定显 示域。方式如下: 使用1 . 1显示第一个文件第一个域,2 . 2显示第二个文件第二个域,其间用逗号分隔。命令为: 第11章 合并与分割 113下载 要创建此新文件,将输出结果重定向到一个文件即可。 使用-jn m进行其他域连接,例如用文件 1域3和文件域2做连接键,命令为: 下面观察一个具体实例。有两个文件: 文件p e r s包括名字、工作性质和个人 I D号。文件p e r s 2包括部门、个人 I D号及工龄。连接 应使用文件p e r s中域4,匹配文件p e r s 2中域2,命令及结果如下: 使用j o i n应注意连接域到底是哪一个,比如说你认为正在访问域 4,但实际上j o i n应该访问 域5,这样将不返回任何结果。如果是这样,用 a w k检查域号。例如,键入$ awk '{print $4}'文 件名,观察其是否匹配假想域。 11.5 cut用法 c u t用来从标准输入或文本文件中剪切列或域。剪切文本可以将之粘贴到一个文本文件。 下一节将介绍粘贴用法。 c u t一般格式为: cut [options] file1 file2 下面介绍其可用选项: -c list 指定剪切字符数。 -f field 指定剪切域数。 -d 指定与空格和t a b键不同的域分隔符。 - c用来指定剪切范围,如下所示: - c 1,5-7 剪切第1个字符,然后是第5到第7个字符。 -c1-50 剪切前5 0个字符。 -f 格式与- c相同。 -f 1,5 剪切 第1域,第5域。 - f 1,10-12 剪切第1域,第1 0域到第1 2域。 参照上一节中的文件‘p e r s’,现在从' p e r s '文件中剪切文本。使用冒号做其域分隔符。 114 第二部分 文本过滤 下载 11.5.1 使用域分隔符 文件中使用冒号“:”为域分隔符,故可用- d选项指定冒号,如- d:。如果有意观察第3域, 可以使用- f 3。要抽取I D域。可使用命令如下: 11.5.2 剪切指定域 c u t命令中剪切各域需用逗号分隔,如剪切域 1和3,即名字和I D号,可以使用: 要从文件/ e t c / p a s s w d中剪切注册名及缺省根目录,需抽取域 1和域3: 使用- c选项指定精确剪切数目。这种方法需确切知道开始及结束字符。通常我不用这种方 法,除非在固定长度的域或文件名上。 当信息文件传送到本机时,查看部分文件名就可以识别文件来源。要得到这条信息需抽 取文件名后三个字符。然后才决定将之存在哪个目录下。下面的例子显示文件名列表及相应 c u t命令: 如果使用ls -l命令作部分输出,情况将不同。需使用 - c选项。 要剪切字符,须计算 ls -l列表中的字符数。如显示权限用 cut -c1-10。然而这种方法可能 相当慢,因此需要使用其他工具将相应信息抽取出来。要剪切谁正在使用系统的用户信息, 方法如下: 第11章 合并与分割 115下载 l 11.6 paste用法 c u t用来从文本文件或标准输出中抽取数据列或者域,然后再用 p a s t e可以将这些数据粘贴 起来形成相关文件。粘贴两个不同来源的数据时,首先需将其分类,并确保两个文件行数相 同。 p a s t e将按行将不同文件行信息放在一行。缺省情况下, p a s t e连接时,用空格或t a b键分隔 新行中不同文本,除非指定 - d选项,它将成为域分隔符。 p a s t e格式为; paste -d -s -file1 file2 选项含义如下: -d 指定不同于空格或t a b键的域分隔符。例如用@分隔域,使用- d @。 -s 将每个文件合并成行而不是按行粘贴。 - 使用标准输入。例如ls -l |paste ,意即只在一列上显示输出。 从前面的剪切中取得下述两个文件: 基本p a s t e命令将之粘贴成两列: 11.6.1 指定列 通过交换文件名即可指定哪一列先粘: 11.6.2 使用不同的域分隔符 要创建不同于空格或t a b键的域分隔符,使用- d选项。下面的例子用冒号做域分隔符。 要合并两行,而不是按行粘贴,可以使用 - s选项。下面的例子中,第一行粘贴为名字,第 二行是I D号。 116 第二部分 文本过滤 下载 11.6.3 paste命令管道输入 p a s t e命令还有一个很有用的选项( -)。意即对每一个( -),从标准输入中读一次数据。 使用空格作域分隔符,以一个 4列格式显示目录列表。方法如下: 也可以以一列格式显示输出: 11.7 split用法 s p l i t用来将大文件分割成小文件。有时文件越来越大,传送这些文件时,首先将其分割可 能更容易。使用v i或其他工具诸如s o r t时,如果文件对于工作缓冲区太大,也会存在一些问题。 因此有时没有选择余地,必须将文件分割成小的碎片。 s p l i t命令一般格式: split -output_file-size input-filename output-filename 这里o u t p u t - f i l e - s i z e指的是文本文件被分割的行数。 s p l i t查看文件时,o u t p u t - f i l e - s i z e选项 指定将文件按每个最多 1 0 0 0行分割。如果有个文件有 2 8 0 0行,那么将分割成3个文件,分别有 1 0 0 0、1 0 0 0、8 0 0行。每个文件格式为x [ a a ]到x [ z z ],x为文件名首字母,[ a a ]、[ z z ]为文件名剩 余部分顺序字符组合,下面的例子解释这一点。 假定文件b i g o n e . t x t有2 8 0 0行,s p l i t命令产生下列文件: 文件大小为: 可以使用o u t p u t - f i l e - s i z e选项来分割文件。以下为一个 6行文件。 第11章 合并与分割 117下载 按每个文件2行分割,命令为: 观察其结果。 文件有6行,s p l i t按每个文件两行进行了分割,并按字母顺序命名文件。为进一步确信操 作成功,观察一个新文件内容: 11.8 小结 本章讲述了对文本文件进行基本的合并和分割处理的各种工具。诸如 s o r t、j o i n、s p l i t、 u n i q、c u t和p a s t e,并附有大量实例。使用这些工具将使你事半功倍。现在如果遇到一个未处 理文件,相信你已知道使用什么工具将数据转化为更有意义的信息。 118 第二部分 文本过滤 下载 下载 第12章 tr 用法 12.1 关于tr t r用来从标准输入中通过替换或删除操作进行字符转换。 t r主要用于删除文件中控制字符 或进行字符转换。使用t r时要转换两个字符串:字符串1用于查询,字符串2用于处理各种转换。 t r刚执行时,字符串1中的字符被映射到字符串2中的字符,然后转换操作开始。 本章内容有: • 大小写转换。 • 去除控制字符。 • 删除空行。 带有最常用选项的t r命令格式为: tr-c-d-s["string1_to_translate_from"]["string2_to_translate_to"]input_ f i l e 这里: -c 用字符串1中字符集的补集替换此字符集,要求字符集为 A S C I I。 -d 删除字符串1中所有输入字符。 -s 删除所有重复出现字符序列,只保留第一个;即将重复出现字符串压缩为一个字符 串。 I n p u t - f i l e是转换文件名。虽然可以使用其他格式输入,但这种格式最常用。 12.1.1 字符范围 使用t r时,可以指定字符串列表或范围作为形成字符串的模式。这看起来很像正则表达式, 但实际上不是。指定字符串 1或字符串2的内容时,只能使用单字符或字符串范围或列表。 [a-z] a-z内的字符组成的字符串。 [A-Z] A-Z内的字符组成的字符串。 [0-9] 数字串。 /octal 一个三位的八进制数,对应有效的 A S C I I字符。 [O*n] 表示字符O重复出现指定次数n。因此[ O * 2 ]匹配O O的字符串。 大部分t r变种支持字符类和速记控制字符。字符类格式为 [:c l a s s ],包含数字、希腊字母、 空行、小写、大写、 c n t r l键、空格、点记符、图形等等。表 1 2 - 1包括最常用的控制字符的速 记方式及三位八进制引用方式。 当用一个单字符替换一个字符串或字符范围时,注意字符并不放在方括号里( [ ])。一些 系统也可以使用方括号,例如可以写成 [“\ 0 1 2”]或“\ 0 1 2”,t r也允许不加引号,因此命令中 看到单引号而不是双引号时也不要感到奇怪。 像大多数系统工具一样, t r也受特定字符的影响。因此如果要匹配这些字符,需使用反斜 线屏蔽其特殊含义。例如,用 \ {指定花括号左边可以屏蔽其特殊含义。 表12-1 tr中特定控制字符的不同表达方式 速 记 符 含 义 八进制方式 \ a Ctrl-G 铃声 \ 0 0 7 \ b Ctrl-H 退格符 \ 0 1 0 \f Ctrl-L 走行换页 \ 0 1 4 \n Ctrl-J 新行 \ 0 1 2 \ r Ctrl-M 回车 \ 0 1 5 \t Ctrl-I tab键 \ 0 11 \ v Ctrl-X \ 0 3 0 12.1.2 保存输出 要保存输出结果,需将之重定向到一个文件。下面的例子重定向输出到文件 r e s u l t s . t x t。 输入文件是c o p s . t x t。 现在看一些例子。 12.1.3 去除重复出现的字符 下面文件包含了一些打印错误。这种情况时常发生,例如在 v i编辑器中,偶尔按住一个键 不放。 如果要去除重复字母或将其压缩在一起,使用 - s选项。因为都是字母,故使用 [ a - z ]。输入 文件重定向到t r命令。 所有重复字符被压缩成一个。如果使用 c a t命令,再将结果管道输出至 t r,结果是一样的。 12.1.4 删除空行 要删除空行,可将之剔出文件。下面是一个文件 p l a n e . t x t。文本间有许多空行。 120 第二部分 文本过滤 下载 使用- s来做这项工作。换行的八进制表示为 \ 0 1 2,命令为: 也可以使用换行速记方式\ n,这里用单引号(通常用双引号)。 12.1.5 大写到小写 除了删除控制字符,转换大小写是 t r最常用的功能。为此需指定即将转换的小写字符 [ a - z ] 和转换结果[ A - Z ]。 第一个例子,t r从一个包含大小写字母的字符串中接受输入。 同样,也可以使用字符类[:l o w e r:]和[:u p p e r:]。 将文本文件大写转换为小写并输出至一个新文件,格式为: 这里f i l e - t o - t r a n s l a t e保存即将转换的文件,n e w - f i l e - n a m e为保存结果的新文件名。例如: 12.1.6 小写到大写 转换小写到大写与上一节大写到小写过程刚好相反。以下有两个例子: 将文本文件从小写转换为大写并将结果存入一个新文件,格式为: f i l e - t o - t r a n s l a t e保存即将转换的文件,n e w - f i l e - n a m e保存结果文件,例如: 12.1.7 删除指定字符 偶尔会从下载文件中删除只包含字母或数字的列。需要结合使用 - c和- s选项完成此功能。 下面的文件包含一个星期的日程表。任务是从其中删除所有数字,只保留日期。日期有大 写,也有小写格式。因此需指定两个字符范围 [ a - z ]和[ A - Z ],命令tr -cs "[a-z][A-Z]""[\012*]"将 第12章 tr 用法 121下载 文件每行所有不包含在 [ a - z ]或[ A - Z ](所有希腊字母)的字符串放在字符串 1中并转换为一新 行。- s选项表明压缩所有新行,- c表明保留所有字母不动。原文件如下,后跟 t r命令: 12.1.8 转换控制字符 t r的第一个功能就是转换控制字符,特别是从 d o s向U N I X下载文件时,忘记设置f t p关于回 车换行转换的选项时更是如此。 下面是故意没有设置转换开关的一个文本文件,是关于文具需求的一部分内容。使用 c a t - v显示控制字符。 猜想‘^ ^ ^ ^ ^ ^’是t a b键。每一行以C t r l - M结尾,文件结尾C t r l - Z,以下是改动方法。 使用- s选项,查看A S C I I表。^的八进制代码是1 3 6,^ M是0 1 5,t a b键是0 11,^ Z是0 3 2 ,下 面将按步骤完成最终功能。 用t a b键替换^ ^ ^ ^ ^ ^,命令为" \ 1 3 6 " " [ \ 0 11 * ] "。将结果重定向到临时工作文件 s t a t . t m p。 用新行替换每行末尾的^ M,并用\ n去除^ Z,输入要来自于临时工作文件 s t a t . t m p。 最后去除所有的控制字符,文件就可以使用了。 12.1.9 快速转换 如果需要删除文件中^ M,并代之以换行。使用命令: 或者用下述命令得同样结果。 也可以用下述命令: 122 第二部分 文本过滤 下载 另一个一般的D o s到U N I X转换是命令: 将删除所有^ M和^ Z,代之以换行。 要删除所有的t a b键,代之以空格,使用命令: 替换p a s s w d文件中所有冒号,代之以 t a b键,可以增加可读性。将冒号引起来,指定替换 字符串中t a b键八进制值0 11,下面是p a s s w d文件,后跟t r命令结果: 或者用下述命令得同样结果。这里使用 t a b键的速记符。 12.1.10 匹配多于一个字符 可以使用[ c h a r a c t e r * n ]格式匹配多于一个字符。下述文件列出系统硬盘信息,其中包含了 系统已经注册的和未识别的。第一列是数字,如果不全是 0,表明第二列相应硬盘已经注册。 有时全部为0看起来很烦人,找个吸引人注意力的符号来代替它,以便一眼就能看出哪个 硬盘已注册,哪个不可识别。原文件如下: 从文件列表中知道,有一个硬盘未注册,因此用星号代替所有的 0。模式为[ 0 * 4 ],意即匹 配至少4个0,替换字符串为星号,过滤命令及结果如下: 现在从文件中可以直接看出哪个未注册。 12.2 小结 t r主要用于字符转换或者抽取控制字符。本章所有功能都可以用 s e d来完成,但有些人宁 愿使用t r,因为t r更加快捷、容易。 第12章 tr 用法 123下载 下载 第13章 登录环境 登录系统时,在进入命令提示符前,系统要做两个工作。键入用户名和密码后,系统检 查是否为有效用户,为此需查询 / e t c / p a s s w d文件。如果登录名正确并且密码有效,开始下一 步过程,即登录环境。 本章内容有: • 登录过程。 • 文件/ e t c / p a s s w d。 • $HOME.profile。 • 定制$ H O M E . p r o f i l e。 在进行下一步处理之前,先看看文件 / e t c / p a s s w d。这是一个文本文件,可以任意修改其中 的文本域,但要小心。此文本有 7个域,并用冒号作分隔符,以下是其部分文件内容列表。在 顶端加有列号,这样各域标识得更加清晰。 现在来看看各域,第 1域是登录名,第 2域是加密的密码,第 5域是用户全名。第 6域是用 户根目录,第7域是用户使用的s h e l l。这里/ b i n / s h意即缺省为常规Bourne Shell。 P a s s w d文件可能还有其他格式。其中的一个版本即为实际 p a s s w d域保存在另一个文件中。 以上即为最普通格式。 登录成功后,系统执行两个环境设置文件,第一个是 / e t c / p r o f i l e,第二个是 . p r o f i l e,位 于用户根目录下。 系统还会处理其他的初始化文件。这里只涉及 p r o f i l e文件。 13.1 /etc/profile 用户登录时,自动读取/ e t c目录下p r o f i l e文件,此文件包含: • 全局或局部环境变量。 • PAT H信息。 • 终端设置。 • 安全命令。 • 日期信息或放弃操作信息。 下面就来详细解释上述各项内容。设置全局环境变量便于用户及其进程和应用访问它。 第三部分 登 录 环 境 PAT H定位包含可执行文件,库文件及一般文本文件的目录位置,便于用户快速访问。终端设 置使系统获知用户终端的一般特性。安全命令包括文件创建模式或敏感区域的双登录提示。 日期信息是一个文本文件,保存用户登录时即将发生事件的记录或放弃登录的信息文件。 以下是文件/ e t c / p r o f i l e,列表后将予以讨论。 126 第三部分 登录环境 下载 其中一些命令可能不好理解,不必担心,本书以后将陆续予以介绍。如果愿意,可以参 照这个列表建立自己的p r o f i l e文件。 第一行捕获两个信号,即使用 Q U I T退出用户或< C t r l - c >键停止文件执行。 接下来导出L O G N A M E;然后指定系统额外增加的 m a n页查询的位置。M A N PAT H将此位 置加入存在的m a n页查询列表中。 检查时区文件,如果存在,指定它作为时区源,设置终端类型为 v t 2 2 0。 重新设置捕获信号,以便于用户读取日期文件信息,但此后必须再重新设置它。 建立邮件信息(当有新邮件到达时显示此信息)。 设置u m a s k值,使文件创建时带有一定的缺省权限位集。 初始化环境变量,设置路径并导出,以便于用户使用。 重新设置捕获信号< C t r l -C>和Q U I T。 保存缺省的s t t y设置,便于用户退出查询系统时能够重新初始化终端设置。 第13章 登录环境 127下载 将所有连接注册到文件/ v a r / a d m / m e s s a g e s,即缺省系统注册文件中。 使用u l i m i t命令限制内存溢出或十六进制溢出数目。 下面的一小段脚本限制用户最多同时登录两次,但不包括三个人( i d n k,p s a l o m,d a v e), 如果有人试图登录超过两次,则令其退出登录进程。 最后设置命令提示符到登录名。 此环境设置为全局使用,下面在用户自己的 p r o f i l e文件中定制环境。 13.2 用户的$HOME.profile / e t c / p r o f i l e文件执行时,用户将被放入到自己的 $ H O M E目录中,回过头来观察 p a s s w d文 件,用户的$ H O M E目录在倒数第2列。 可以将之看作用户根目录,因为正是在这里存储了所有的私有信息。 如果. p r o f i l e已经存在,系统将参照此文件,意即对此过程并不创建另一个 s h e l l,因而在 / e t c / p r o f i l e下设置的环境不做改动,除非在 . p r o f i l e中强制改动它。如果创建另一个进程,用户 本地的s h e l l变量将被覆盖。 回到 . p r o f i l e,一般来说创建帐户时,一个 p r o f i l e文件的基本框架即随之创建。不要忘了 在 . p r o f i l e文件中可以通过设置相关条目以不同的值或使用 u s e t命令来覆盖/ e t c / p r o f i l e文件中的 设置。如果愿意,可以定制用户自己的 . p r o f i l e文件。先来看看标准的. p r o f i l e文件。 现在改动此文件。 现在加入两个环境变量,如 E D I TO R,以使 c r o n或其他应用获知正在使用的编辑器;将 T E R M变量设置为v t 1 0 0,而不是v t 2 2 0。 也可以创建 b i n目录,将之加入路径( p a t h),目录结构中加一个 b i n目录是一个好习惯。 在这里可以保存所有脚本,将之加入 PAT H后,就不必写入脚本的文件路径名全称,只键入脚 本名即可。 几乎没有人想在命令提示符中显示自己的登录名,而宁愿使用现在的目录路径或是正在 使用的系统主机名做提示符。例如,下面显示了在命令提示符中如何设置主机名: 如果用户位于当前目录下: 如果上面的命令返回p w d,可使用如下命令: 我通常设置辅助命令提示符(一般用于命令提示符里的多行命令)为符号 ©,它的A S C I I 代码值八进制数为2 5 1,十进制为1 6 9。 128 第三部分 登录环境 下载 如果是L I N U X,那么⋯⋯ 在e c h o命令中使用八进制值,方法为: 如果需要访问管理区/ u s r / a d m i n,可将之加入环境变量,这样可以很容易地进入此目录。 A D M I N = / u s r / a d m 如果要知道用户本身登录后系统用户数,使用 w h o和w c命令。 将上述设置加入. p r o f i l e文件。如果要使. p r o f i l e或/ e t c / p r o f i l e文件改动生效,可以退出登录 然后再登入,或者参照此文件设置。要参照此文件设置,格式为: . / p a t h n a m e / f i l e n a m e 要参照. p r o f i l e设置,键入: $. .profile 如果未成功,试试: $. ./profile 以下为改动过的. p r o f i l e文件。 13.3 stty用法 s t t y用于设置终端特性。要查询现在的 s t t y选项,使用stty -a。 第13章 登录环境 129下载 设置终端时遇到的一个最普遍问题是退格键不起作用。这不是不可挽救的。本机 s t t y命令 中^ ?即为退格键,使用< C t r l - H >可能会退格并删除前一个字符。在命令行中设置一个 s t t y选项, 一般格式为: stty name character 以下将退格设置为^ H: $ stty erase '\^H' 在. p r o f i l e文件中使用上述命令可能会碰到一些问题,因为 s t t y期望输入一个实际 ' C o n t r o l H '序列,在v i编辑器环境下使用下述方法解决它: 按住C t r l键,同时按下V键,释放V键,再按下H键。 最常用的s t t y命令使用在下述设置上: 名称 键 含 义 i n t r ^ C 终止进程 e c h o 打开e c h o功能 - e c h o 关闭e c h o功能 e o f ^ D 文件尾;注销 k i l l ^ Y 删除一行 s t a r t ^ Q 滚动屏幕文本 s t o p ^ S 停止滚动屏幕文本 s t t y的一个可用选项为: stty -gb 此选项允许以可读格式保存 s t t y现有设置,便于以后重置回 s t t y。正像前面在文件 / e t c / p r o f i l e中看到的一样。将 stty -g内容放入一个变量中,工作完成后,任何改动的设置将被 写回s t t y。 在改变s t t y设置值并和终端打交道时,此方法很有用。这样可以很容易地存储其初始设置。 下面的例子将 s t t y的现有设置保存。使用 stty -g关掉e c h o,然后在脚本结尾处保存 s t t y初始设 置。 如果是LINUX ,那么⋯⋯ 要使L I N U X知道正在使用字符串中转义字符, e c h o命令应加入- e,即echo -e。 130 第三部分 登录环境 下载 s t t y命令可以与终端、打印机、调制解调器打交道,功能十分丰富。使用 s t t y时要慎重, 不要使用已经使用的键或无效值。 13.4 创建.logout文件 使用Bourne shell与其他s h e l l不同,其缺点是不包含. l o g o u t文件。此文件保存有执行e x i t命 令时,在进程终止前执行的命令。 但是通过使用t r a p命令(t r a p和信号将在本书后面讨论),Bourne shell也可以创建自己的 . l o g o u t文件。方法如下:编辑. p r o f i l e文件,在最后一行加入下列命令,然后保存并退出。 trap "$HOME /.logout"0 再键入一个. l o g o u t文件,敲入下列执行命令。如果愿意,可以在此脚本中加入任何命令。 用户退出时,调用 . l o g o u t文件。过程如下:用户退出一个 s h e l l时,传送了一个信号 0,意 即从现在s h e l l中退出,在控制返回s h e l l继续退出命令前,. p r o f i l e文件中t r a p行将捕获此信号并 执行. l o g o u t。 13.5 小结 可以定制用户本身的. p r o f i l e以满足需求,本章讲述了如何覆盖系统设置以满足用户需求。 从显示友好信息到终端特性设置,定制用户环境可以有许多方式。 第13章 登录环境 131下载 (续) 下载 第14章 环境和shell变量 为使s h e l l编程更有效,系统提供了一些 s h e l l变量。s h e l l变量可以保存诸如路径名、文件 名或者一个数字这样的变量名。 s h e l l将其中任何设置都看做文本字符串。 有两种变量,本地和环境。严格地说可以有 4种,但其余两种是只读的,可以认为是特殊 变量,它用于向s h e l l脚本传递参数。 本章内容有: • shell变量。 • 环境变量。 • 变量替换。 • 导出变量。 • 特定变量。 • 向脚本传递信息。 • 在系统命令行下使用位置参数。 14.1 什么是shell变量 变量可以定制用户本身的工作环境。使用变量可以保存有用信息,使系统获知用户相关 设置。变量也用于保存暂时信息。例如:一变量为 E D I TO R,系统中有许多编辑工具,但哪一 个适用于系统呢?将此编辑器名称赋给 E D I TO R,这样,在使用c r o n或其他需要编辑器的应用 时,这就是你将一直使用的 E D I TO R取值,并将之用作缺省编辑器。 下面是一个例子,登录的审核系统需要编辑。在菜单中选择此选项时,应用查询 E D I TO R 变量值,其值为v i。系统知道可使用此编辑器。 另一个例子需要登录数据库系统,键入下列命令: $ isql -Udavet -Pabcd -Smethsys 这里- S为正在连接的服务器名称。有一变量 D S Q U E RY保存服务器名称值。设置服务器名 称值到D S Q U E RY变量,这样如果登录时不使用 - S提供服务器名称,应用将查询 D S Q U E RY变 量,并使用其取值作为服务器名称。需要做的全部工作就是键入下列命令: $ isql -Udavet -Pabcd 工作方式同上例。 14.2 本地变量 本地变量在用户现在的 s h e l l生命期的脚本中使用。例如,本地变量 f i l e - n a m e取值为 l o o p . d o c,这个值只在用户当前 s h e l l生命期有意义。如果在 s h e l l中启动另一个进程或退出,此 值将无效。这个方法的优点就是用户不能对其他的 s h e l l或进程设置此变量有效。 表1 4 - 1列出各种实际变量模式 使用变量时,如果用花括号将之括起来,可以防止 s h e l l误解变量值,尽管不必一定要这 样做,但这确实可用。 要设置一本地变量,格式为: $ variable-name=value 或 $ { v a r i a b l e - n a m e = v a l u e } 注意,等号两边可以有空格。如果取值包含空格,必须用双引号括起来。 s h e l l变量可以 用大小写字母。 表14-1 变量设置时的不同模式 Va r i a b l e - n a m e = v a l u e 设置实际值到 v a r i a b l e - n a m e Va r i a b l e - n a m e + v a l u e 如果设置了v a r i a b l e - n a m e,则重设其值 Va r i a b l e - n a m e : ? v a l u e 如果未设置v a r i a b l e - n a m e,显示未定义用户错误信息 Va r i a b l e - n a m e ? v a l u e 如果未设置v a r i a b l e - n a m e,显示系统错误信息 Va r i a b l e - n a m e : = v a l u e 如果未设置v a r i a b l e - n a m e,设置其值 Va r i a b l e - n a m e : - v a l u e 同上,但是取值并不设置到 v a r i a b l e - n a m e,可以被替换 14.2.1 显示变量 使用e c h o命令可以显示单个变量取值,并在变量名前加 $,例如: 可以结合使用变量,下面将错误信息和环境变量 L O G N A M E设置到变量e r r o r- m s g。 上面例子中,s h e l l首先显示文本,然后查找变量 $ L O G N A M E,最后扩展变量以显示整个 变量值。 14.2.2 清除变量 使用u n s e t命令清除变量。 unset variable-name 14.2.3 显示所有本地shell变量 使用s e t命令显示所有本地定义的s h e l l变量。 第14章 环境和shell变量 133下载 s e t输出可能很长。查看输出时可以看出 s h e l l已经设置了一些用户变量以使工作环境更加 容易使用。 14.2.4 结合变量值 将变量并排可以使变量结合在一起: 14.2.5 测试变量是否已经设置 有时要测试是否已设置或初始化变量。如果未设置或初始化,就可以使用另一值。此命 令格式为: $ { v a r i a b l e : - v a l u e } 意即如果设置了变量值,则使用它,如果未设置,则取新值。例如: 变量c o l o u r取值b l u e,e c h o打印变量c o l o u r时,首先查看其是否已赋值,如果查到,则使 用该值。现在清除该值,再来看看结果。 上面的例子并没有将实际值传给变量,需使用下述命令完成此功能: $ { v a r i a b l e : = v a l u e } 下面是一个更实用的例子。查询工资清单应用的运行时间及清单类型。在运行时间及类 型输入时,敲回车键表明用户并没有设置两个变量值,将使用缺省值( 0 3 : 0 0和We e k l y),并 传入a t命令中以按时启动作业。 134 第三部分 登录环境 下载 在输入域敲回车键,输出结果如下: 也可以编写脚本测试变量是否取值,然后返回带有系统错误信息的结果。下面的例子测 试变量f i l e是否取值。 以上结果可读性不好,但是可以加入自己的脚本以增加可读性。 测试变量是否取值,如果未设置,则返回一空串。方法如下: $ { v a r i a b l e : + v a l u e } 使用下述方法初始化变量为空字符串。 v a r i a b l e = " " $ D E T I N A T I O N = " " 14.2.6 使用变量来保存系统命令参数 可以用变量保存系统命令参数的替换信息。下面的例子使用变量保存文件拷贝的文件名 信息。变量s o u r c e保存p a s s w d文件的路径,d e s t保存c p命令中文件目标。 下面例子中,变量d e v i c e保存磁带设备路径,然后用于在 m t命令中倒带。 14.2.7 设置只读变量 如果设置变量时,不想再改变其值,可以将之设置为只读方式。如果有人包括用户本人 想要改变它,则返回错误信息。格式如下: v a r i a b l e - n a m e = v a l u e readonly variable-name 下面的例子中,设置变量为系统磁带设备之一的设备路径,将之设为只读,任何改变其 第14章 环境和shell变量 135下载 值的操作将返回错误信息。 要查看所有只读变量,使用命令 r e a d o n l y即可。 14.3 环境变量 环境变量用于所有用户进程(经常称为子进程)。登录进程称为父进程。 s h e l l中执行的用 户进程均称为子进程。不像本地变量(只用于现在的 s h e l l)环境变量可用于所有子进程,这 包括编辑器、脚本和应用。 环境变量可以在命令行中设置,但用户注销时这些值将丢失,因此最好在 . p r o f i l e文件中 定义。系统管理员可能在 / e t c / p r o f i l e文件中已经设置了一些环境变量。将之放入 p r o f i l e文件意 味着每次登录时这些值都将被初始化。 传统上,所有环境变量均为大写。环境变量应用于用户进程前,必须用 e x p o r t命令导出。 环境变量与本地变量设置方式相同。 14.3.1 设置环境变量 V A R I A B L E - N A M E = v a l u eĠexport VARIABLE-NAME 在两个命令之间是一个分号,也可以这样写: V A R I A B L E - N A M E = v a l u e Export VARIABLE-NAME 14.3.2 显示环境变量 显示环境变量与显示本地变量一样,例子如下: 使用e n v命令可以查看所有的环境变量。 136 第三部分 登录环境 下载 14.3.3 清除环境变量 使用u n s e t命令清除环境变量: 14.3.4 嵌入shell变量 Brourne shell有一些预留的环境变量名,这些变量名不能用作其他用途。通常在 / e t c / p r o f i l e中建立这些嵌入的环境变量,但也不完全是,这取决于用户自己。以下是嵌入 s h e l l 变量列表。 1. CDPAT H 改变目录路径变量,保留一系列由冒号隔开的路径名,用于 c d命令。如果设置了C D PAT H, c d一个目录时,首先查找 C D PAT H,如果C D PAT H指明此目录,则此目录成为当前工作目录。 例子如下: $ CDPATH=:/home/dave/bin:/usr/local/appsĠexport CDPATH. 如果要 $ cd apps c d命令首先在C D PAT H中查找目录列表,如果发现 a p p s,则它成为当前工作目录。 2. EXINIT E X I N I T变量保存使用v i编辑器时的初始化选项。例如,调用 v i时,要显示行号,且在第 1 0个空格加入t a b键,命令为: $ EXINIT='set nu tab=10';export EXINIT 3. HOME H O M E目录,通常定位于 p a s s w d文件的倒数第 2列,用于保存用户自身文件。设置了 H O M E目录,可以简单使用c d命令进入它。 也可以用 $ cd $ HOME 4. IFS 第14章 环境和shell变量 137下载 I F S用作s h e l l指定的缺省域分隔符。原理上讲域分隔符可以是任意字符,但缺省通常为空 格、新行或t a b键。I F S在分隔文件或变量中各域时很有用。下面的例子将 I F S设置为冒号,然 后echo PAT H变量,给出一个目录分隔开来的可读性很强的路径列表。 要设置其返回初始设置: $ IFS=; export IFS 这里< s p a c e > < t a b >为空格和t a b键。 5. LOGNAME 此变量保存登录名,应该为缺省设置,但如果没有设置,可使用下面命令完成它: 6. MAIL M A I L变量保存邮箱路径名,缺省为 /var/spool/mail/。s h e l l周期性检查新邮 件,如果有了新邮件,在命令行会出现一个提示信息。如果邮箱并不在以上指定位置,可以 用M A I L设置。 $ MAIL=/usr/mail/daveĠexport MAIL 7. MAILCHECK M A I L C H E C K缺省每6 0 s检查新邮件,但如果不想如此频繁检查新邮件,比如设为每 2 m, 使用命令: $ MAILCHECK=120Ġexport MAILCHECK 8. MAILPAT H 如果有多个邮箱要用到M A I L PAT H,此变量设置将覆盖M A I L设置。 $ MAILPATH=/var/spool/dave:/var/spool/adminĠexport MAILPATH 上面的例子中,M A I L检测邮箱d a v e和a d m i n。 9. PAT H PAT H变量保存进行命令或脚本查找的目录顺序,正确排列这个次序很重要,可以在执行 命令时节省时间。你一定不想在已知命令不存在的目录下去查找它。通常情况,最好首先放 在H O M E目录下,接下来是从最常用到一般使用到不常用的目录列表次序。如果要在当前工 作目录下查询,无论在哪儿,均可以使用句点操作。目录间用冒号分隔,例如: $ PATH=$HOME/bin:.:/bin:/usr/binĠexport PATH 使用上面的例子首先查找 H O M E / b i n目录,然后是当前工作目录,然后是 / b i n,最后是 / u s r / b i n。 PAT H可以在系统目录下/ e t c / p r o f i l e中设置,也可以使用下面方法加入自己的查找目录。 $ PATH=$PATH:/$HOME/bin:.Ġ export PATH 这里使用了 / e t c / p r o f i l e中定义的PAT H,并加入$ H O M E / b i n和当前工作目录。一般来说, 在查找路径开始使用当前工作目录不是一个好办法,这样很容易被其他用户发现。 10. PS1 基本提示符包含 s h e l l提示符,缺省对超级用户为 #,其他为$。可以使用任何符号作提示 138 第三部分 登录环境 下载 符,以下为两个例子: 11. PS2 P S 2为附属提示符,缺省为符号 > 。P S 2用于执行多行命令或超过一行的一个命令。 12. SHELL S H E L L变量保存缺省s h e l l,通常在/ e t c / p a s s w d中已设置,但是如有必要使用另一个 s h e l l, 可以用如下方法覆盖当前s h e l l: 13. TERMINFO 终端初始化变量保存终端配置文件的位置。通常在 / u s r / l i b / t e r m i n f o或/ u s r / s h a r e / t e r m i n f o $ TERMINFO=/usr/lib/terminfoĠexport TERMINFO 14. TERM T E R M变量保存终端类型。设置T E R M使应用获知终端对屏幕和键盘响应的控制序列类型, 常用的有v t 1 0 0、v t 2 0 0、v t 2 2 0 - 8等。 $ TERM=vt100Ġexport TERM 15. TZ 时区变量保存时区值,只有系统管理员才可以更改此设置。例如: 返回值表明正在使用格林威治标准时间,与 G M T时差为0,并作E D T保存。 14.3.5 其他环境变量 还有一些预留的环境变量。其他系统或命令行应用将用到它们。以下是最常用的一些, 注意这些值均未有缺省设置,必须显示说明。 1. EDITO R 设置编辑器,最常用。 $ EDITOR=viĠexport EDITOR 2. PWD 当前目录路径名,用c d命令设置此选项。 3. PA G E R 保存屏幕翻页命令,如p g、m o r e,在查看m a n文本时用到此功能。 $ PAGER='pg -f -p%d'Ġexport PAGER 4. MANPAT H 第14章 环境和shell变量 139下载 保存系统上m a n文本的目录。目录间用冒号分隔。 $ MANPATH=/usr/apps/man:/usr/local/manĠexport MANPATH 5. LPDEST或P R I N T E R 保存缺省打印机名,用于打印作业时指定打印机名。 $ LPDEST=hp3si-systems 14.3.6 set命令 在$ H O M E . p r o f i l e文件中设置环境变量时,还有另一种方法导出这些变量。使用 s e t命令- a 选项,即set -a指明所有变量直接被导出。不要在 / e t c / p r o f i l e中使用这种方法,最好只在自己 的$ H O M E . p r o f i l e文件中使用。 14.3.7 将变量导出到子进程 s h e l l新用户碰到的问题之一是定义的变量如何导出到子进程。前面已经讨论过环境变量 的工作方式,现在用脚本实现它,并在脚本中调用另一脚本(这实际上创建了一个子进程)。 以下是两个脚本列表f a t h e r和c h i l d。 f a t h e r脚本设置变量f i l m,取值为A Few Good Men,并将变量信息返回屏幕,然后调用脚 本c h i l d,这段脚本显示第一个脚本里的变量 f i l m,然后改变其值为Die Hard,再将其显示在屏 幕上,最后控制返回f a t h e r脚本,再次显示这个变量。 140 第三部分 登录环境 下载 看看脚本显示结果。 因为在f a t h e r中并未导出变量f i l m,因此c h i l d脚本不能将f i l m变量返回。 如果在f a t h e r脚本中加入e x p o r t命令,以便c h i l d脚本知道f i l m变量的取值,这就会工作: 因为在脚本中加入了 e x p o r t命令,因此可以在任意多的脚本中使用变量 f i l m,它们均继承 了f i l m的所有权。 不可以将变量从子进程导出到文进程, 然面通过重定向就可做到这一点 14.4 位置变量参数 本章开始提到有4种变量,本地、环境,还有两种变量被认为是特殊变量,因为它们是只 读的。这两种变量即为位置变量和特定变量参数。先来看一看位置变量。 如果要向一个 s h e l l脚本传递信息,可以使用位置参数完成此功能。参数相关数目传入脚 本,此数目可以任意多,但只有前 9个可以被访问,使用 s h i f t命令可以改变这个限制。本书后 面将讲到s h i f t命令。参数从第一个开始,在第 9个结束;每个访问参数前要加 $符号。第一个 参数为0,表示预留保存实际脚本名字。无论脚本是否有参数,此值均可用。 如果向脚本传送Did You See Th e Full Mo o n信息,下面的表格讲解了如何访问每一个参 数。 $ 0 $ 1 $ 2 $ 3 $ 4 $ 5 $ 6 $ 7 $ 8 $ 9 脚本名字 Did Yo u S e e T h e F u l l M o o n 第14章 环境和shell变量 141下载 14.4.1 在脚本中使用位置参数 在下面脚本中使用上面的例子。 这里只传递6个参数,7、8、9参数为空,正像预计的那样。注意,第一个参数表示脚本 名,当从脚本中处置错误信息时,此参数有很大作用。 下面的例子返回脚本名称。 注意$ 0返回当前目录路径,如果只返回脚本名,在 b a s e n a m e命令下参数设为 $ 0,刚好得 到脚本名字。 14.4.2 向系统命令传递参数 可以在脚本中向系统命令传递参数。下面的例子中,在 f i n d命令里,使用 $ 1参数指定查 找文件名。 142 第三部分 登录环境 下载 另一个例子中,以 $ 1向g r e p传递一个用户 i d号,g r e p使用此i d号在p a s s w d中查找用户全 名。 14.4.3 特定变量参数 既然已经知道了如何访问和使用 s h e l l脚本中的参数,多知道一点相关信息也是很有用的, 有必要知道脚本运行时的一些相关控制信息,这就是特定变量的由来。共有 7个特定变量,见 表1 4 - 2。 表14-2 特定s h e l l变量 $ # 传递到脚本的参数个数 $ * 以一个单字符串显示所有向脚本传递的参数。与位置变量不同,此选项参数可超过 9个 $ $ 脚本运行的当前进程I D号 $ ! 后台运行的最后一个进程的进程 I D号 $ @ 与$ #相同,但是使用时加引号,并在引号中返回每个参数 $ - 显示s h e l l使用的当前选项,与s e t命令功能相同 $ ? 显示最后命令的退出状态。 0表示没有错误,其他任何值表明有错误。 现在来修改脚本p a r a m并替换各种特定变量,与以前的例子不同,用不同的传递文本重新 运行脚本。 第14章 环境和shell变量 143下载 特定变量的输出使用户获知更多的脚本相关信息。可以检查传递了多少参数,进程相应 的I D号,以免我们想杀掉此进程。 14.4.4 最后的退出状态 注意,$ ?返回0。可以在任何命令或脚本中返回此变量以获得返回信息。基于此信息,可 以在脚本中做更进一步的研究,返回 0意味着成功,1为出现错误。 下面的例子拷贝文件到/ t m p,并使用$ ?检查结果。 现在尝试将一个文件拷入一个不存在的目录: 使用$ ?检验返回状态,可知脚本有错误,但同时发现 c p:c a n n o t . . .,因此检验最后退出状 态已没有必要。在脚本中可以用系统命令处理输出格式,要求命令输出不显示在屏幕上。为 此可以将输出重定向到 / d e v / n u l l,即系统b i n中。现在怎样才能知道脚本正确与否?好,这时 可以用最后退出状态命令了。请看上一个例子的此形式的实际操作结果。 通过将包含错误信息的输出重定向到系统 b i n中,不能获知最后命令返回状态,但是通过 使用$ !,(其返回值为1)可知脚本失败。 检验脚本退出状态时,最好将返回值设置为一个有意义的名字,这样可以增加脚本的可 读性。 144 第三部分 登录环境 下载 14.5 小结 变量可以使s h e l l编程更容易。它能够保存输入值并提高效率。 s h e l l变量几乎可以包含任 何值。特定变量增强了脚本的功能并提供了传递到脚本的参数的更多信息。 第14章 环境和shell变量 145下载 下载 第15章引 号 上一章介绍了变量和替换操作,在脚本中执行变量替换时最容易犯的一个错误就是由于 引用错误。在命令行中引用是很重要的。 本章内容有: • 引用的必要性。 • 双引、单引和反引号。 • 使用反斜线实现屏蔽。 15.1 引用必要性 这里只讲述引用的基本规则。因为使用引用的例子很多。本书接下来的两个部分将一一 予以讲述。脚本中执行行操作时, s h e l l将对脚本设置予以解释。要采取一种方法防止 s h e l l这 样做,即使用引用号,包括各式引用或使用反斜线。 一些用户在对文本字符串进行反馈操作时觉得使用引用很麻烦。有时不注意,只引用了 一半,这时问题出现了。最好在反馈文本字符串时使用双引号。下面是各种引用的例子。 文本返回了,但由于未使用双引号, *被s h e l l误解,s h e l l认为用户要做目录列表。用双引 号得结果如下: 这样就不会有误解产生。表 1 5 - 1列出各种引用类型。 表15-1 shell引用类型 " " 双引号 ` 反引号 ' ' 单引号 \ 反斜线 15.2 双引号 使用双引号可引用除字符 $、`、\外的任意字符或字符串。这些特殊字符分别为美元符号, 反引号和反斜线,对 s h e l l来说,它们有特殊意义。如果使用双引号将字符串赋给变量并反馈 它,实际上与直接反馈变量并无差别。 现在假定要设置系统时间输出到变量 m y d a t e。 因为s h e l l将" "符号里的字符串赋予变量 m y d a t e,d a t e已没有特定意义,故此变量只保存单 词d a t e。 如果要查询包含空格的字符串,经常会用到双引号。以下使用g r e p抽取名字“Davey Wi r e”, 因为没有加双引号,g r e p将“D a v e y”认作字符串,而把“Wi r e”当作文件名。 要解决这个问题,可将字符串加双引号。这样 s h e l l会忽略空格,当使用字符时,应总是 使用双引号,无论它是单个字符串或是多个单词。 在一个反馈的文本行里可以使用双引号将变量引起来。下面的例子中, s h e l l反馈文本行, 遇到符号$,知道这是一个变量,然后用变量值 b o y替换变量$ B O Y。 15.3 单引号 单引号与双引号类似,不同的是 s h e l l会忽略任何引用值。换句话说,如果屏蔽了其特殊 含义,会将引号里的所有字符,包括引号都作为一个字符串。使用上一个例子,结果如下: 15.4 反引号 反引号用于设置系统命令的输出到变量。 s h e l l将反引号中的内容作为一个系统命令,并 执行其内容。使用这种方法可以替换输出为一个变量。反引号可以与引号结合使用。下面将 举例说明。 下面的例子中, s h e l l试图替代单词h e l l o为系统命令并执行它,因为 h e l l o脚本或命令不存 在,返回错误信息。 现在用d a t e命令再试一次。 这次命令有效,s h e l l正确执行。 第15章引 号 147下载 下面将命令输出设置为变量 m y d a t e,时间格式如下: 设置到m y d a t e,并显示其值: 当然也可以将d a t e命令输出至m y d a t e: 另一个例子中,将反引号嵌在双引号里: 打印当前系统上用户数目: 上面的例子中,打印字符串后, s h e l l遇到反引号,将其看作一条命令执行它。 15.5 反斜线 如果下一个字符有特殊含义,反斜线防止 s h e l l误解其含义,即屏蔽其特殊含义。下述字 符包含有特殊意义:& * + ^ $ ` " | ?。 假定e c h o命令加*,意即以串行顺序打印当前整个目录列表,而不是一个星号 *。 为屏蔽星号特定含义,可使用反斜线。 $ echo \* * 上述语句同样可用于 $ $命令,s h e l l解释其为现在进程 I D号,使用反斜线屏蔽此意,仅打 印$ $。 在打印字符串时要加入八进制字符( A S C I I相应字符),必须在前面加反斜线,否则 s h e l l 将其当作普通数字处理。 148 第三部分 登录环境 下载 如果是L I N U X,则⋯⋯ 记住使用- e选项来显示控制字符。 使用命令e x p r时,用*表示乘法会出现错误,在*前加上反斜线才会正确。 在e c h o命令中加入元字符,必须用反斜线起屏蔽作用。下面的例子要显示价格 $ 1 9 . 9 9。其 中$屏蔽与不屏蔽将产生不同的结果。 使用反斜线屏蔽$,可得更好的结果。 15.6 小结 在引用时会遇到一些问题且经常出错。我在使用引用时遵循两条规则: 1) 反馈字符串用双引号;但不要引用反馈本身。 2) 如果使用引用得到的结果不理想,再试另一种,毕竟只有三种引用方式,可以充分尝 试。 第15章引 号 149下载 下载 第16章 shell脚本介绍 一个s h e l l脚本可以包含一个或多个命令。当然可以不必只为了两个命令就编写一个 s h e l l 脚本,一切由用户自己决定。 本章内容有: • 使用s h e l l脚本的原因。 • shell脚本基本元素。 • shell脚本运行方式。 16.1 使用shell脚本的原因 s h e l l脚本在处理自动循环或大的任务方面可节省大量的时间,且功能强大。如果你有处 理一个任务的命令清单,不得不一个一个敲进去,然后观察输出结果,再决定它是否正确, 如果正确,再继续下一个任务,否则再回到清单一步步观察。一个任务可能是将文件分类、 向文件插入文本、迁移文件、从文件中删除行、清除系统过期文件、以及系统一般的管理维 护工作等等。创建一个脚本,在使用一系列系统命令的同时,可以使用变量、条件、算术和 循环快速创建脚本以完成相应工作。这比在命令行下一个个敲入要节省大量的工作时间。 s h e l l脚本可以在行命令中接收信息,并使用它作为另一个命令的输入。 对于不同的U N I X和L I N U X,使用一段s h e l l脚本将需要一些小小的改动才能运行通过。实 际上s h e l l的可迁移性不成问题,但是系统间命令的可迁移性存在差别。 试试新思路 如果写一段脚本,其执行结果与预想的不同,不必着急。无论多不可思议的结果,记住 先把它保存起来,这是修改的基础。这里要说的意思是不要害怕对待新事物,否则将不能树 立信心,学起来会更加困难。 16.2 脚本内容 本章不讲怎样设计精巧的脚本,而是怎样使脚本重复利用率高。当通过一些易理解的脚 本就可实现同样功能时,没有必要使脚本复杂化。如果作者要写这样一本书,可能会给你留 下深刻印象,但这要花费更多的时间和精力去研读和体会脚本。这不是本书的目标。本书脚 本流程仅使用基本的脚本技术,十分容易学,然后使用者就可以着手实践了。 脚本不是复杂的程序,它是按行解释的。脚本第一行总是以 # ! / b i n / s h开始,这段脚本通知 s h e l l使用系统上的Bourne shell解释器。 任何脚本都可能有注释,加注释需要此行的第一个字符为 #,解释器对此行不予解释。在 第四部分 基础s h e l l编程 第二行注释中写入脚本名是一个好习惯。 脚本从上到下执行,运行脚本前需要增加其执行权限。确保正确建立脚本路径,这样只 用文件名就可以运行它了。 16.3 运行一段脚本 下面是一个已经讨论过的例子,此文件为 c l e a n u p。 上述脚本通过将目录下文件名截断,清除 / u s r / a d m /下信息,并删除/ u s r / l o c a l / a p p s / l o g下所 有注册信息。 可以使用c h m o d命令增加脚本执行权限。 $ chmod u+x cleanup 现在运行脚本,只敲入文件名即可。 $ cleanup 如果返回错误信息: $ cleanup sh:cleanup:command not found 再试: $. /cleanup 如果脚本运行前必须键入路径名,或者 s h e l l结果通知无法找到命令,就需要在 . p r o f i l e PAT H下加入用户可执行程序目录。要确保用户在自己的 $ H O M E可执行程序目录下,应键入: $ pwd $ /home/dave/bin 如果p w d命令最后一部分是 b i n,那么需要在路径中加入此信息。编辑用户 . p r o f i l e文件, 加入可执行程序目录$ H O M E / b i n如下: P A T H = $ P A T H : $ H O M E / b i n 如果没有b i n目录,就创建它。首先确保在用户根目录下。 $ cd $HOME $ mkdir bin 现在可以在. p r o f i l e文件中将b i n目录加入PAT H变量了,然后重新初始化. p r o f i l e。 $. ./profile 脚本将会正常运行。 如果还有问题,见第2章和第1 3章,那里详细介绍了如何解决这一问题。 全书有许多脚本清单,这些脚本都是完整的。将这些脚本输入文件,保存并退出,再使 152 第四部分 基础s h e l l编程 下载 用c h m o d命令增加其执行权限,这些脚本就可以实际操作了。 16.4 小结 本章介绍了s h e l l脚本的基本原理,相信关于脚本的功能原理这些已经足够了,读本章时 可加快速度。本章目标只是要用户知道运行 s h e l l脚本需要做些什么。 第16章 shell 脚本介绍 153下载 下载 第17章 条件测试 写脚本时,有时要判断字符串是否相等,可能还要检查文件状态或是数字测试。基于这 些测试才能做进一步动作。 Te s t命令用于测试字符串,文件状态和数字,也很适合于下一章将 提到的i f、t h e n、e l s e条件结构。 本章内容有: • 对文件、字符串和数字使用 t e s t命令。 • 对数字和字符串使用e x p r命令。 e x p r命令测试和执行数值输出。使用最后退出状态命令 $ ?可测知t e s t和e x p r,二者均以0表 示正确,1表示返回错误。 17.1 测试文件状态 t e s t一般有两种格式,即: test condition 或 [ c o n d i t i o n ] 使用方括号时,要注意在条件两边加上空格。 测试文件状态的条件表达式很多,但是最常用的可在表 1 7 - 1中查到。 表17-1 文件状态测试 - d 目录 - s 文件长度大于 0、非空 - f 正规文件 - w 可写 - L 符号连接 - u 文件有s u i d位设置 - r 可读 - x 可执行 使用两种方法测试文件 s c o r e s . t x t是否可写并用最后退出状态测试是否成功。记住, 0表示 成功,其他为失败。 两种状态均返回0,可知文件s c o r e s . t x t可写,现在测试其是否可执行: 查看文件s c o r e s . t x t权限列表,可知结果正如所料。 下面的例子测试是否存在a p p s b i n目录 目录a p p s b i n果然存在。 测试文件权限是否设置了s u i d位 从结果知道s u i d位已设置。 17.2 测试时使用逻辑操作符 测试文件状态是否为 O K,但是有时要比较两个文件状态。 s h e l l提供三种逻辑操作完成此 功能。 -a 逻辑与,操作符两边均为真,结果为真,否则为假。 -o 逻辑或,操作符两边一边为真,结果为真,否则为假。 ! 逻辑否,条件为假,结果为真。 下面比较两个文件: 下面的例子测试两个文件是否均可读。 结果为真。 要测试其中一个是否可执行,使用逻辑或操作。 s c o r e s . t x t不可执行,但r e s u l t s . t x t可执行。 要测试文件r e s u l t s . t x t是否可写、可执行: 结果为真。 17.3 字符串测试 字符串测试是错误捕获很重要的一部分,特别在测试用户输入或比较变量时尤为重要。 字符串测试有5种格式。 第17章 条件测试 155下载 这里,s t r i n g _ o p e r a t o r可为: = 两个字符串相等。 != 两个字符串不等。 -z 空串。 -n 非空串。 要测试环境变量E D I TO R是否为空: 非空,取值是否是v i? 是的,用e c h o命令反馈其值: 测试变量t a p e与变量t a p e 2是否相等: 不相等。没有规定在设置变量时一定要用双引号,但在进行字符串比较时必须这样做。 测试变量t a p e与t a p e 2是否不相等。 是的,它们不相等。 17.4 测试数值 测试数值可以使用许多操作符,一般格式如下: " n u m b e r " n u m e r i c _ o p e r a t o r " n u m b e r " 或者 [ " n u m b e r " n u m e r i c _ o p e r a t o r " n u m b e r " ] n u m e r i c _ o p e r a t o r可为: -eq 数值相等。 -ne 数值不相等。 -gt 第一个数大于第二个数。 -lt 第一个数小于第二个数。 -le 第一个数小于等于第二个数。 -ge 第一个数大于等于第二个数。 下面的例子返回结果都一样。均为测试两个数是否相等( 1 3 0是否等于1 3 0)。 156 第四部分 基础s h e l l编程 下载 结果果然正确。 改变第二个数,结果返回失败,状态 1(1 3 0不等于2 0 0) 测试1 3 0是否大于1 0 0: 当然。 也可以测试两个整数变量。下面测试变量 s o u r c e _ c o u n t是否小于d e s t _ c o u n t : 可以不必将整数值放入变量,直接用数字比较即可,但要加引号。 可以用逻辑操作符将两个测试表达式结合起来。仅需要用到一对方括号,而不能用两个, 否则将返回错误信息“too many arg u m e n t s”。 下面例子测试两个表达式,如果都为真,结果为真,正确使用方式应为: 17.5 expr用法 e x p r命令一般用于整数值,但也可用于字符串。一般格式为: expr argument operator argument e x p r也是一个手工命令行计数器。 使用乘号时,必须用反斜线屏蔽其特定含义。因为 s h e l l可能会误解显示星号的意义。 第17章 条件测试 157下载 17.5.1 增量计数 e x p r在循环中用于增量计算。首先,循环初始化为 0,然后循环值加 1,反引号的用法意 即替代命令。最基本的一种是从( e x p r)命令接受输出并将之放入循环变量。 17.5.2 数值测试 可以用e x p r测试一个数。如果试图计算非整数,将返回错误。 这里需要将一个值赋予变量(不管其内容如何),进行数值运算,并将输出导入 d e v / n u l l, 然后测试最后命令状态,如果为 0,证明这是一个数,其他则表明为非数值。 这是一个数。 这是一个非数值字符。 e x p r也可以返回其本身的退出状态,不幸的是返回值与系统最后退出命令刚好相反,成 功返回1,任何其他值为无效或错误。下面的例子测试两个字符串是否相等,这里字符串为 “h e l l o”和“h e l l o”。 e x p r返回1。不要混淆了,这表明成功。现在检验其最后退出状态,返回 0表示测试成功, “h e l l o”确实等于“h e l l o”。 17.5.3 模式匹配 e x p r也有模式匹配功能。可以使用 e x p r通过指定冒号选项计算字符串中字符数。 . *意即任 何字符重复0次或多次。 在e x p r中可以使用字符串匹配操作,这里使用模式 . d o c抽取文件附属名。 158 第四部分 基础s h e l l编程 下载 17.6 小结 本章涉及e x p r和t e s t基本功能,讲到了怎样进行文件状态测试和字符串赋值,使用其他的 条件表达式如if then else和c a s e可以进行更广范围的测试及对测试结果采取一些动作。 第17章 条件测试 159下载 下载 第18章 控制流结构 所有功能脚本必须有能力进行判断,也必须有能力基于一定条件处理相关命令。本章讲 述这方面的功能,在脚本中创建和应用控制结构。 本章内容有: • 退出状态。 • while、f o r和until loops循环。 • if then else语句。 • 脚本中动作。 • 菜单。 18.1 退出状态 在书写正确脚本前,大概讲一下退出状态。任何命令进行时都将返回一个退出状态。如 果要观察其退出状态,使用最后状态命令: $ echo $? 主要有4种退出状态。前面已经讲到了两种,即最后命令退出状态 $ ?和控制次序命令($ $、 | |)。其余两种是处理 s h e l l脚本或s h e l l退出及相应退出状态或函数返回码。在第 1 9章讲到函数 时,也将提到其返回码。 要退出当前进程,s h e l l提供命令e x i t,一般格式为: exit n 其中,n为一数字。 如果只在命令提示符下键入 e x i t,假定没有在当前状态创建另一个 s h e l l,将退出当前s h e l l。 如果在脚本中键入e x i t,s h e l l将试图(通常是这样)返回上一个命令返回值。有许多退出脚本 值,但其中相对于脚本和一般系统命令最重要的有两种,即: 退出状态0 退出成功,无错误。 退出状态1 退出失败,某处有错误。 可以在s h e l l脚本中加入自己的退出状态(它将退出脚本)。本书鼓励这样做,因为另一个 s h e l l脚本或返回函数可能要从 s h e l l脚本中抽取退出脚本。另外,相信加入脚本本身的退出脚 本值是一种好的编程习惯。 如果愿意,用户可以在一个用户输入错误后或一个不可覆盖错误后或正常地处理结束后 退出脚本。 注意 从现在起,本书所有脚本都将加入注释行。注释行将解释脚本具体含义,帮助用户 理解脚本。可以在任何地方加入注释行,因为其本身被解释器忽略。注释行应以#开头。 18.2 控制结构 几乎所有的脚本里都有某种流控制结构,很少有例外。流控制是什么?假定有一个脚本 包含下列几个命令: 上述脚本问题出在哪里?如果目录创建失败或目录创建成功文件拷贝失败,如何处理? 这里需要从不同的目录中拷贝不同的文件。必须在命令执行前或最后的命令退出前决定处理 方法。s h e l l会提供一系列命令声明语句等补救措施来帮助你在命令成功或失败时,或需要处 理一个命令清单时采取正确的动作。 这些命令语句大概分两类: 循环和流控制。 18.2.1 流控制 i f、t h e n、e l s e语句提供条件测试。测试可以基于各种条件。例如文件的权限、长度、数 值或字符串的比较。这些测试返回值或者为真( 0),或者为假(1)。基于此结果,可以进行 相关操作。在讲到条件测试时已经涉及了一些测试语法。 c a s e语句允许匹配模式、单词或值。一旦模式或值匹配,就可以基于这个匹配条件作其他 声明。 18.2.2 循环 循环或跳转是一系列命令的重复执行过程,本书提到了 3种循环语句: for 循环 每次处理依次列表内信息,直至循环耗尽。 Until 循环 此循环语句不常使用,u n t i l循环直至条件为真。条件部分在循环末尾部分。 While 循环 w h i l e循环当条件为真时,循环执行,条件部分在循环头。 流控制语句的任何循环均可嵌套使用,例如可以在一个 f o r循环中嵌入另一个f o r循环。 现在开始讲解循环和控制流,并举一些脚本实例。 从现在起,脚本中 e c h o语句使用L I N U X或B S D版本,也就是说使用 e c h o方法echo -e -n, 意即从e c h o结尾中下一行执行命令。应用于 U N I X(系统V和B S D)的统一的e c h o命令参阅1 9 章s h e l l函数。 18.3 if then else语句 i f语句测试条件,测试条件返回真( 0)或假(1)后,可相应执行一系列语句。 i f语句结 构对错误检查非常有用。其格式为: if 条件1 then 命令1 elif 条件2 then 命令2 else 命令3 第18章 控制流结构 161下载 f i 让我们来具体讲解i f语句的各部分功能。 If 条件1 如果条件1为真 Then 那么 命令1 执行命令1 elif 条件2 如果条件1不成立 then 那么 命令2 执行命令2 else 如果条件1,2均不成立 命令3 那么执行命令3 fi 完成 i f语句必须以单词f i终止。在i f语句中漏写f i是最一般的错误。我自己有时也是这样。 e l i f和e l s e为可选项,如果语句中没有否则部分,那么就不需要 e l i f和e l s e部分。I f语句可以 有许多e l i f部分。最常用的i f语句是if then fi结构。 下面看一些例子。 18.3.1 简单的if语句 最普通的i f语句是: i f条件 then 命令 f i 使用i f语句时,必须将t h e n部分放在新行,否则会产生错误。如果要不分行,必须使用命 令分隔符。本书其余部分将采取这种形式。现在简单 i f语句变为: if 条件;t h e n 命令 f i 注意,语句可以不这样缩排,但建议这样做,因为可以增强脚本的清晰程度。在条件流 下采取命令操作更方便。下面的例子测试 1 0是否小于1 2,此条件当然为真。因为条件为真, i f 语句内部继续执行,这里只有一个简单的 e c h o命令。如果条件为假,脚本退出,因为此语句 无e l s e部分。 18.3.2 变量值测试 通过测试设置为接受用户输入的变量可以测知用户是否输入信息。下面的例子中测试用 162 第四部分 基础s h e l l编程 下载 户键入r e t u r n键后变量n a m e是否包含任何信息。 18.3.3 grep输出检查 不必拘泥于变量或数值测试,也可以测知系统命令是否成功返回。对 g r e p使用i f语句找出 g r e p是否成功返回信息。下面的例子中 g r e p用于查看D a v e是否在数据文件 d a t a . f i l e中,注意 ‘D a v e \ >’用于精确匹配。 上面的例子中,g r e p输出定向到系统垃圾堆。如果匹配成功, g r e p返回0,将g r e p嵌入i f语 句;如果g r e p成功返回,i f部分为真。 18.3.4 用变量测试grep输出 正像前面看到的,可以用 g r e p作字符串操作。下面的脚本中,用户输入一个名字列表, g r e p在变量中查找,要求其包含人名 P e t e r。 以下是对应输入名称的输出信息。 第18章 控制流结构 163下载 18.3.5 文件拷贝输出检查 下面测试文件拷贝是否正常,如果 c p命令并没有拷贝文件 m y f i l e到m y f i l e . b a k,则打印错 误信息。注意错误信息中` basename $0`打印脚本名。 如果脚本错误退出,一个好习惯是显示脚本名并将之定向到标准错误中。用户应该知道 产生错误的脚本名。 注意,文件可能没找到,系统也产生本身的错误信息,这类错误信息可能与输出混在一 起。既然已经显示系统错误信息获知脚本失败,就没必要显示两次。要去除系统产生的错误 和系统输出,只需简单的将标准错误和输出重定向即可。修改脚本为: >/dev/null 2>&1。 脚本运行时,所有输出包括错误重定向至系统垃圾堆。 18.3.6 当前目录测试 当运行一些管理脚本时,可能要在根目录下运行它,特别是移动某种全局文件或进行权 限改变时。一个简单的测试可以获知是否运行在根目录下。下面脚本中变量 D I R E C TO RY使用 当前目录的命令替换操作,然后此变量值与 " / "字符串比较(/为根目录)。如果变量值与字符 串不等,则用户退出脚本,退出状态为 1意味错误信息产生。 164 第四部分 基础s h e l l编程 下载 18.3.7 文件权限测试 可以用i f语句测试文件权限,下面简单测试文件 t e s t . t x t是否被设置到变量L O G N A M E。 18.3.8 测试传递到脚本中的参数 i f语句可用来测试传入脚本中参数的个数。使用特定变量 $ #,表示调用参数的个数。可以 测试所需参数个数与调用参数个数是否相等。 以下测试确保脚本有三个参数。如果没有,则返回一个可用信息到标准错误,然后代码 退出并显示退出状态。如果参数数目等于 3,则显示所有参数。 如果只传入两个参数,则显示一可用信息,然后脚本退出。 这次传入三个参数。 18.3.9 决定脚本是否为交互模式 有时需要知道脚本运行是交互模式(终端模式)还是非交互模式( c r o n或a t)。脚本也许 第18章 控制流结构 165下载 需要这个信息以决定从哪里取得输入以及输出到哪里,使用 t e s t命令并带有- t选项很容易确认 这一点。如果t e s t返回值为1,则为交互模式。 18.3.10 简单的if else语句 下一个i f语句有可能是使用最广泛的: if 条件 t h e n 命令1 e l s e 命令2 f i 使用i f语句的e l s e部分可在条件测试为假时采取适当动作。 18.3.11 变量设置测试 下面的例子测试环境变量 E D I TO R是否已设置。如果 E D I TO R变量为空,将此信息通知用 户。如果已设置,在屏幕上显示编辑类型。 18.3.12 检测运行脚本的用户 下面例子中,环境变量用于测试条件,即 L O G N A M E是否包含r o o t值。这类语句是加在脚 本开头作为一安全性准则的普遍方法。当然 L O G N A M E可用于测试任何有效用户。 如果变量不等r o o t,返回信息到标准错误输出即屏幕,也就是通知用户不是 r o o t,脚本然 后退出,并带有错误值1。 如果字符串r o o t等于L O G N A M E变量,e l s e部分后面语句开始执行。 实际上,脚本会继续进行正常的任务处理,这些语句在 f i后面,因为所有非 r o o t用户在脚 本的前面测试部分已经被剔出掉了。 166 第四部分 基础s h e l l编程 下载 18.3.13 将脚本参数传入系统命令 可以向脚本传递位置参数,然后测试变量。这里,如果用户在脚本名字后键入目录名, 脚本将重设$ 1特殊变量为一更有意义的名字。即 D I R E C TO RY。这里需测试目录是否为空,如 果目录为空,ls -A将返回空,然后对此返回一信息。 也可以使用下面的脚本替代上面的例子并产生同样的结果。 18.3.14 null:命令用法 到目前为止,条件测试已经讲完了 t h e n和e l s e部分,有时也许使用者并不关心条件为真或 为假。 不幸的是i f语句各部分不能为空—一些语句已经可以这样做。为解决此问题, s h e l l提供 了:空命令。空命令永远为真(也正是预想的那样)。回到前面的例子,如果目录为空,可以 只在t h e n部分加入命令。 第18章 控制流结构 167下载 18.3.15 测试目录创建结果 现在继续讨论目录,下面的脚本接受一个参数,并用之创建目录,然后参数被传入命令 行,重设给变量D I R E C TO RY,最后测试变量是否为空。 if ["$DIRECTORY"=""] 也可以用 if[$# -lt 1] 来进行更普遍的参数测试。 如果字符串为空,返回一可用信息,脚本退出。如果目录已经存在,脚本从头至尾走一 遍,什么也没做。 创建前加入提示信息,如果键入Y或y,则创建目录,否则使用空命令表示不采取任何动作。 使用最后命令状态测试创建是否成功执行,如果失败,返回相应信息。 168 第四部分 基础s h e l l编程 下载 执行上述脚本,显示: 18.3.16 另一个拷贝实例 在另一个拷贝实例中,脚本传入两个参数(应该包含文件名),系统命令c p将$ 1拷入$ 2, 输出至/ d e v / n u l l。如果命令成功,则仍使用空命令并且不采取任何动作。 另一方面,如果失败,在脚本退出前要获知此信息。 脚本运行,没有拷贝错误: 脚本运行带有拷贝错误: 下面的脚本用s o r t命令将文件a c c o u n t s . q t r分类,并输出至系统垃圾堆。没人愿意观察屏幕 上3 0 0行的分类页。成功之后不采取任何动作。如果失败,通知用户。 18.3.17 多个if语句 可能有时要嵌入i f语句。为此需注意i f和f i的相应匹配使用。 18.3.18 测试和设置环境变量 前面已经举例说明了如何测试环境变量 E D I TO R是否被设置。现在如果未设置,则进一步 为其赋值,脚本如下: 第18章 控制流结构 169下载 脚本工作方式如下:首先检查是否设置了该变量,如果已经赋值,输出信息提示使用 v i作 为缺省编辑器。v i被设置为编辑器,然后脚本退出。 如果未赋值,则提示用户,询问其是否要设置该值。检验用户输入是否为大写或小写 y, 输入为其他值时,脚本退出。 如果输入Y或y,再提示输入编辑类型。使用 $ E D I TO R =“”测试用户是否未赋值和未点 击r e t u r n键。一种更有效的方法是使用 -z $EDITO R方法,本文应用了这两种方法。如果测试 失败,返回信息到屏幕,即使用 v i做缺省编辑器,因而E D I TO R赋值为v i。 如果用户输入了一个名字到变量 E D I TO R,则使用它作为编辑器并马上让其起作用,即导 出变量E D I TO R。 18.3.19 检测最后命令状态 前面将目录名传入脚本创建了一个目录,脚本然后提示用户是否应创建目录。下面的例 子创建一个目录,并从当前目录将所有 * . t x t文件拷入新目录。但是这段脚本中用最后状态命 令检测了每一个脚本是否成功执行。如果命令失败则通知用户。 170 第四部分 基础s h e l l编程 下载 18.3.20 增加和检测整数值 下面的例子进行数值测试。脚本包含了一个计数集,用户将其赋予一个新值就可改变它。 脚本然后将当前值1 0 0加入一个新值。工作流程如下: 用户输入一个新值改变其值,如果键入回车键,则不改变它,打印当前值,脚本退出。 如果用户用y或Y响应新值,将提示用户输入增量。如果键入回车键,原值仍未变。键入 一个增量,首先测试是否为数字,如果是,加入计数 C O U N TO R中,最后显示新值。 第18章 控制流结构 171下载 运行结果如下: 18.3.21 简单的安全登录脚本 以下是用户登录时启动应用前加入相应安全限制功能的基本框架。首先提示输入用户名 和密码,如果用户名和密码均匹配脚本中相应字符串,用户登录成功,否则用户退出。 脚本首先设置变量为假— 总是假定用户输入错误, s t t y当前设置被保存,以便隐藏 p a s s w d域中字符,然后重新保存s t t y设置。 如果用户I D和密码正确(密码是m a y d a y),明亮I N VA L I D _ U S E R和I N VA L I D _ PA S S W D设 置为n o表示有效用户或密码,然后执行测试,如果两个变量其中之一为 y e s,缺省情况下,脚 本退出用户。 键入有效的I D和密码,用户将允许进入。这是一种登录脚本的基本框架。下面的例子中 有效用户I D为d a v e或p a u l i n e。 172 第四部分 基础s h e l l编程 下载 如果运行上述脚本并给一个无效用户: 现在给出正确的用户和密码: 18.3.22 elif用法 if then else语句的e l i f部分用于测试两个以上的条件。 18.3.23 使用elif进行多条件检测 使用一个简单的例子,测试输入脚本的用户名。脚本首先测试是否输入一个名字,如果 没有,则什么也不做。如果输入了,则用 e l i f测试是否匹配r o o t、l o u i s e或d a v e,如果不匹配其 中任何一个,则打印该名字,通知用户不是 r o o t、l o u i s e或d a v e。 第18章 控制流结构 173下载 运行上述脚本,给出不同信息,得结果如下: 18.3.24 多文件位置检测 假定要定位一个用户登录文件,已知此文件在 / u s r / o p t s / a u d i t / l o g s或/ u s r / l o c a l / a u d i t / l o g s中, 具体由其安装人决定。在定位此文件前,首先确保文件可读,此即脚本测试部分。如果未找 到文件或文件不可读,则返回错误信息。脚本如下: 174 第四部分 基础s h e l l编程 下载 运行上面脚本,如果文件在上述两个目录之一中并且可读,将可以找到它。如果不是, 返回错误并退出,下面结果失败,因为假想的文件并不存在。 18.4 case语句 c a s e语句为多选择语句。可以用 c a s e语句匹配一个值与一个模式,如果匹配成功,执行相 匹配的命令。c a s e语句格式如下: case 值 i n 模式1 } 命令1 . . . ; ; 模式2) 命令2 . . . ;; e s a c c a s e工作方式如上所示。取值后面必须为单词 i n,每一模式必须以右括号结束。取值可以 为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至;;。 取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续 其他模式。如果无一匹配模式,使用星号 *捕获该值,再接受其他输入。 模式部分可能包括元字符,与在命令行文件扩展名例子中使用过的匹配模式类型相同, 即: * 任意字符。 ? 任意单字符。 [..] 类或范围中任意字符。 下面举例说明。 18.4.1 简单的case语句 下面的脚本提示输入 1到5,输入数字传入 c a s e语句,变量 A N S设置为c a s e取值测试变量 A N S,A N S将与每一模式进行比较。 如果匹配成功,则执行模式里面的命令直至;;,这里只反馈非用户数字选择的信息,然 后c a s e退出,因为匹配已找到。 进程在c a s e语句后仍可继续执行。 如果匹配未找到,则使用*模式捕获此情况,这里执行错误信息输出。 第18章 控制流结构 175下载 给出不同输入,运行此脚本。 使用模式*捕获范围之外的取值情况。 18.4.2 对匹配模式使用| 使用c a s e时,也可以指定“|”符号作为或命令,例如v t 1 0 0 | v t 1 0 2匹配模式v t 1 0 0或v t 1 0 2。 下面的例子中,要求用户输入终端类型。如果输入为v t 1 0 0或v t 1 0 2,将匹配模式‘v t 1 0 0 | v t 1 0 2’, 执行命令是设置 T E R M变量为v t 1 0 0。如果用户输入与模式不匹配, *用来捕获输入,其中命 令为将T E R M设置为v t 1 0 0。最后在c a s e语句外,导出T E R M变量。由于使用*模式匹配,无论 用户输入什么,T E R M都将有一个有效的终端类型值。 176 第四部分 基础s h e l l编程 下载 运行脚本,输入一无效终端类型, 如果输入一正确的终端类型, 无论怎样,一个有效的终端类型被赋予用户。 18.4.3 提示键入y或n c a s e的一个有效用法是提示用户响应以决定是否继续进程。这里提示输入 y以继续处理,n 退出。如果用户输入Y、y或y e s,处理继续执行c a s e语句后面部分。如果用户输入 N、n或n o或 其他响应,用户退出脚本。 运行脚本,输入无效响应,得结果: 给出有效响应: 18.4.4 case与命令参数传递 可以使用c a s e控制到脚本的参数传递。 下面脚本中,测试特定变量 $ #,它包含传递的参数个数,如果不等于 1,退出并显示可用 信息。 第18章 控制流结构 177下载 然后c a s e语句捕获下列参数: p a s s w d、s t a r t、s t o p或h e l p,相对于每一种匹配模式执行进 一步处理脚本。如果均不匹配,显示可用信息到标准错误输出。 运行脚本,输入无效参数。 输入有效参数,结果为: 18.4.5 捕获输入并执行空命令 不一定要在匹配模式后加入命令,如果你原本不想做什么,只是在进一步处理前过滤出 意外响应,这样做是一种好办法。 如果要运行对应于一个会计部门的帐目报表,必须首先在决定运行报表的类型前确认用 户输入一个有效的部门号,匹配所有可能值,其他值无效。用 c a s e可以很容易实现上述功能。 下面的脚本中如果用户输入部门号不是 2 3 4、4 5 3、6 5 5或4 5 4,用户退出并返回可用信息。 一旦响应了用户的有效部门号,脚本应用同样的技术取得报表类型,在 c a s e语句末尾显示有 效的部门号和报表类型。脚本如下: 178 第四部分 基础s h e l l编程 下载 输入有效部门号: 输入无效部门号: 输入无效的报表类型: 18.4.6 缺省变量值 如果在读变量时输入回车键,不一定总是退出脚本。可以先测试是否已设置了变量,如 果未设置,可以设置该值。 下面的脚本中,要求用户输入运行报表日期。如果用户输入回车键,则使用缺省日期星 期六,并设置为变量w h e n的取值。 如果用户输入另外一天,这一天对于 c a s e语句是运行的有效日期,即星期六、星期四、星 第18章 控制流结构 179下载 期一。注意,这里结合使用了日期缩写作为捕获的可能有效日期。 脚本如下: 对于正确输入: 对于错误输入: 可以推断出 c a s e语句有时与 if then else语句功能相同,在某些条件下,这种假定是正确 的。 18.5 for循环 f o r循环一般格式为: for 变量名i n列表 d o 命令1 命令2⋯ d o n e 180 第四部分 基础s h e l l编程 下载 当变量值在列表里, f o r循环即执行一次所有命令,使用变量名访问列表中取值。命令可 为任何有效的s h e l l命令和语句。变量名为任何单词。 I n列表用法是可选的,如果不用它, f o r 循环使用命令行的位置参数。 i n列表可以包含替换、字符串和文件名,下面看一些例子。 18.5.1 简单的for循环 此例仅显示列表1 2 3 4 5,用变量名访问列表。 运行上述脚本,输出: 18.5.2 打印字符串列表 下面f o r循环中,列表包含字符串“ orange red blue grey”,命令为e c h o,变量名为l o o p, e c h o命令使用$ l o o p反馈出列表中所有取值,直至列表为空。 也可以在循环体中结合使用变量名和字符串。 结果为: 18.5.3 对for循环使用ls命令 这个循环执行l s命令,打印当前目录下所有文件。 第18章 控制流结构 181下载 18.5.4 对for循环使用参数 在f o r循环中省去i n列表选项时,它将接受命令行位置参数作为参数。实际上即指明: for params in"$@" 或 for params in"$*" 下面的例子不使用i n列表选项,f o r循环查看特定参数$ @或$ *,以从命令行中取得参数。 下面的脚本包含i n"$ @",结果与上面的脚本相同。 对上述脚本采取进一步动作。如果要查看一系列文件,可在 f o r循环里使用f i n d命令,利 用命令行参数,传递所有要查阅的文件。 182 第四部分 基础s h e l l编程 下载 脚本执行时,从命令行参数中取值并使用 f i n d命令,这些取值形成- n a m e选项的参数值。 18.5.5 使用for循环连接服务器 因为f o r循环可以处理列表中的取值,现设变量为网络服务器名称,并使用 f o r循环连接每 一服务器。 18.5.6 使用for循环备份文件 可以用f o r循环备份所有文件,只需将变量作为 c p命令的目标参数。这里有一变量 . b a k, 当在循环中使用c p命令时,它作为此命令目标文件名。列表命令为 l s。 18.5.7 多文件转换 匹配所有以L P S O开头文件并将其转换为大写。这里使用了 l s和c a t命令。l s用于查询出相 关文件,c a t用于将之管道输出至t r命令。目标文件扩展名为 .U C,注意在f o r循环中使用l s命令 时反引号的用法。 第18章 控制流结构 183下载 18.5.8 多sed删除操作 下面的例子中, s e d用于删除所有空文件,并将输出导至以 . H O L D . m v为扩展名的新文件 中,m v将这些文件移至初始文件中。 18.5.9 循环计数 前面讨论e x p r时指出,循环时如果要加入计数,使用此命令。下面使用 l s在f o r循环中列出 文件及其数目。 使用w c命令可得相同结果。 18.5.10 for循环和本地文档 在f o r循环体中可使用任意命令。下面的例子中,一个变量包含所有当前登录用户。使用 w h o命令并结合a w k语言可实现此功能。然后 f o r循环循环每一用户,给其发送一个邮件,邮件 信息部分用一个本地文档完成。 184 第四部分 基础s h e l l编程 下载 上述脚本的邮件信息输出为: 18.5.11 for循环嵌入 嵌入循环可以将一个f o r循环嵌在另一个f o r循环内: for 变量名1 in列表1 d o for 变量名2 in 列表2 d o 命令1 . . . d o n e d o n e 下面脚本即为嵌入f o r循环,这里有两个列表A P P S和S C R I P T S。第一个包含服务器上应用 的路径,第二个为运行在每个应用上的管理脚本。对列表 A P P S上的每一个应用,列表 S C R I P T S里的脚本将被运行,脚本实际上为后台运行。脚本使用 t e e命令在登录文件上放一条 目,因此输出到屏幕的同时也输出到一个文件。查看输出结果就可以看出嵌入 f o r循环怎样使 用列表S C R I P T S以执行列表A P P S上的处理。 第18章 控制流结构 185下载 18.6 until循环 u n t i l循环执行一系列命令直至条件为真时停止。 u n t i l循环与w h i l e循环在处理方式上刚好 相反。一般w h i l e循环优于u n t i l循环,但在某些时候—也只是极少数情况下, u n t i l循环更加 有用。 u n t i l循环格式为ğ until 条件 命令1 . . . d o n e 条件可为任意测试条件,测试发生在循环末尾,因此循环至少执行一次—请注意这一 点。 下面是一些实例。 18.6.1 简单的until循环 这段脚本不断的搜寻w h o命令中用户r o o t,变量I S - R O O T保存g r e p命令结果。 如果找到了r o o t,循环结束,并向用户 s i m o n发送邮件,通知他用户 r o o t已经登录,注意 这里s l e e p命令用法,它经常用于 u n t i l循环中,因为必须让循环体内命令睡眠几秒钟再执行, 否则会消耗大量系统资源。 186 第四部分 基础s h e l l编程 下载 18.6.2 监视文件 下面例子中,u n t i l循环不断挂起做睡眠,直至文件 / t m p / m o n i t o r. l c k被删除。文件删除后, 脚本进入正常处理过程。 上述例子是使脚本与其他处理过程协调工作的一种方法。还有另外一种方法使脚本间互 相通信。假定有另一段脚本 p r o c e s s . m a i n用于搜集本地网络所有机器的信息并将之放入一个报 表文件。 当脚本 p r o c e s s . m a i n运行时,创建了一个 L C K文件(锁文件),上面脚本必须接收 p r o c e s s . m a i n搜集的信息,但是如果 p r o c e s s仍然在修改报表文件时试图处理该文件就不太好 了。 为克服这些问题,脚本p r o c e s s . m a i n创建了一个L C K文件,当它完成时,就删除此文件。 上述脚本将挂起,等待 L C K文件被删除,一旦 L C K文件删除,上述脚本即可处理报表文 件。 18.6.3 监视磁盘空间 u n t i l循环做监视条件也很有用。假定要监视文件系统容量,当它达到一定水平时通知超 级用户。 下面的脚本监视文件系统 / l o g s,不断从变量 $L O O K_O U T中抽取信息, $ L O O K _ O U T包 含使用a w k和g r e p得到的/ l o g s容量。 如果容量达到 9 0 %,触发命令部分,向超级用户发送邮件,脚本退出。必须退出,如果 不退出,条件保持为真(例如,容量总是保持在 9 0 %以上),将会不断的向超级用户发送邮 件。 第18章 控制流结构 187下载 18.7 while循环 w h i l e循环用于不断执行一系列命令,也用于从输入文件中读取数据,其格式为: while 命令 d o 命令1 命令2 . . . d o n e 虽然通常只使用一个命令,但在 w h i l e和d o之间可以放几个命令。命令通常用作测试条 件。 只有当命令的退出状态为 0时,d o和d o n e之间命令才被执行,如果退出状态不是 0,则循 环终止。 命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假。 18.7.1 简单的while循环 以下是一个基本的 w h i l e循环,测试条件是:如果 C O U N T E R小于5,那么条件返回真。 C O U N T E R从0开始,每次循环处理时,C O U N T E R加1。 运行上述脚本,返回数字1到5,然后终止。 18.7.2 使用while循环读键盘输入 w h i l e循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量 F I L M,按< C t r l - D >结束循环。 188 第四部分 基础s h e l l编程 下载 脚本运行时,输入可能是: 18.7.3 用while循环从文件中读取数据 w h i l e循环最常用于从一个文件中读取数据,因此编写脚本可以处理这样的信息。 假定要从下面包含雇员名字、从属部门及其 I D号的一个文件中读取信息。 可以用一个变量保存每行数据,当不再有读取数据时条件为真。 w h i l e循环使用输入重定 向以保证从文件中读取数据。注意整行数据被设置为单变量 $ L I N E .。 18.7.4 使用IFS读文件 输出时要去除冒号域分隔符,可使用变量 I F S。在改变它之前保存 I F S的当前设置。然后 在脚本执行完后恢复此设置。使用 I F S可以将域分隔符改为冒号而不是空格或 t a b键。这里有3 个域需要加域分隔,即N A M E、D E P T和I D。 为使输出看起来更清晰,对 e c h o命令使用t a b键将域分隔得更开一些,脚本如下: 第18章 控制流结构 189下载 脚本运行,输出果然清晰多了。 18.7.5 带有测试条件的文件处理 大部分w h i l e循环里都带有一些测试语句,以决定下一步的动作。 这里从人员文件中读取数据,打印所有细节到一个保留文件中,直至发现 James Lenod, 脚本退出。测试前反馈的信息要确保“ James Lenod”加入保留文件中。 注意,所有变量在脚本顶端被设置完毕。这样当不得不对变量进行改动时可以节省时间 和输入。所有编辑都放在脚本顶端,而不是混于整个脚本间。 还可以采取进一步动作,列出多少个雇员属于同一部门。这里保持同样的读方式。假定 每个域都有一个变量名,然后在 c a s e语句里用e x p r增加每行匹配脚本。任何发现的未知部门知 190 第四部分 基础s h e l l编程 下载 识反馈到标准错误中,如果一个无效部门出现,没有必要退出。 运行脚本,输出: 18.7.6 扫描文件行来进行数目统计 一个常用的任务是读一个文件,统计包含某些数值列的数值总和。下面的文件包含有部 门S TAT和G I F T所卖的商品数量。 现在的任务是要统计部门 G I F T所卖的各种商品数量。使用 e x p r保存统计和,看下面的 e x p r语句。变量L O O P和TO TA L首先在循环外初始化为 0,循环开始后, I T E M S加入TO TA L, 第一次循环只包含第一种商品,但随着过程继续, I T E M S逐渐加入TO TA L。 第18章 控制流结构 191下载 下面的e x p r语句不断增加计数。 使用e x p r语句时容易犯的一个错误是开始忘记初始化变量。 如果真的忘了初始化,屏幕上将布满 e x p r错误。 如果愿意,可以在循环内初始化循环变量。 上面一行如果变量TO TA L未赋值,将其初始化为 0。这是在e x p r里初始化变量的第一个例 子。另外在循环外要打印出最后总数。 运行脚本,得到: 192 第四部分 基础s h e l l编程 下载 18.7.7 每次读一对记录 有时可能希望每次处理两个记录,也许可从记录中进行不同域的比较。每次读两个记录 很容易,就是要在第一个 w h i l e语句之后将第二个读语句放在其后。使用这项技术时,不要忘 了不断进行检查,因为它实际上读了大量的记录。 每次读两个记录,下面的例子对记录并不做实际测试。 脚本如下: 首先来检查确实读了很多记录,可以使用 w c命令: 共有6个记录,观察其输出: 18.7.8 忽略#字符 读文本文件时,可能要忽略或丢弃遇到的注释行,下面是一个典型的例子。 假定要使用一般的w h i l e循环读一个配置文件,可拣选每一行,大部分都是实际操作语句。 有时必须忽略以一定字符开头的行,这时需要用 c a s e语句,因为#是一个特殊字符,最好首先 第18章 控制流结构 193下载 用反斜线屏蔽其特殊意义,在 #符号后放一个星号*,指定*后可包含任意字符。 配置文件如下: 忽略#符号的实现脚本如下: 运行得: 18.7.9 处理格式化报表 读报表文件时,一个常用任务是将不想要的行剔除。以下是库存商品水平列表,我们感 兴趣的是那些包含商品记录当前水平的列 我们的任务是读取其中取值,决定哪些商品应重排。如果重排,重排水平应为现在商品 194 第四部分 基础s h e l l编程 下载 的两倍。输出应打印需要重排的每种商品数量及重排总数。 我们已经知道可以忽略以某些字符开始的行,因此这里没有问题。首先读文件,忽略所 有注释行和以‘ I T E M’开始的标注行。读取文件至一临时工作文件中,为确保不存在空行, 用s e d删除空行,需要真正做的是过滤文本文件。脚本如下: 输出为: 现在要在另一个w h i l e循环中读取临时工作文件,使用 e x p r对数字进行数值运算。 第18章 控制流结构 195下载 以下为依据报表文件运行所得输出结果。 将两段脚本结合在一起很容易。实际上这本来是一个脚本,为讲解方便,才将其分成两 个。 18.7.10 while循环和文件描述符 第5章查看文件描述符时,提到有必要用 w h i l e循环将数据读入一个文件。使用文件描述符 3和4,下面的脚本进行文件m y f i l e . t x t到m y f i l e . b a k的备份。注意,脚本开始测试文件是否存在, 如果不存在或没有数据,脚本立即终止。还有 w h i l e循环用到了空命令(:),这是一个死循环, 因为n u l l永远返回真。尝试读至文件结尾将返回错误,那时脚本也终止执行。 196 第四部分 基础s h e l l编程 下载 18.8 使用break和continue控制循环 有时需要基于某些准则退出循环或跳过循环步。 s h e l l提供两个命令实现此功能。 • break。 • continue。 18.8.1 break b r e a k命令允许跳出循环。 b r e a k通常在进行一些处理后退出循环或 c a s e语句。如果是在一 个嵌入循环里,可以指定跳出的循环个数。例如如果在两层循环内,用 break 2刚好跳出整个 循环。 18.8.2 跳出case语句 下面的例子中,脚本进入死循环直至用户输入数字大于 5。要跳出这个循环,返回到 s h e l l 提示符下,b r e a k使用脚本如下: 18.8.3 continue c o n t i n u e命令类似于b r e a k命令,只有一点重要差别,它不会跳出循环,只是跳过这个循 环步。 第18章 控制流结构 197下载 18.8.4 浏览文件行 下面是一个前面用过的人人文件列表,但是现在加入了一些头信息。 假定现在需要处理此文件,看过文件之后知道头两行并不包含个人信息,因此需要跳过 这两行。 也不需要处理雇员Peter James,这个人已经离开公司,但没有从人员文件中删除。 对于头信息。只需简单计算所读行数,当行数大于 2时开始处理,如果处理人员名字为 Peter James,也将跳过此记录。脚本如下: 运行上面脚本,得出: 198 第四部分 基础s h e l l编程 下载 18.9 菜单 创建菜单时,在w h i l e循环里n u l l空命令很合适。 h i l e加空命令n u l l意即无限循环,这正是 一个菜单所具有的特性。当然除非用户选择退出或是一个有效选项。 创建菜单只需用w h i l e循环和c a s e语句捕获用户输入的所有模式。如果输入无效,则报警, 反馈错误信息,然后继续执行循环直到用户完成处理过程,选择退出选项。 菜单界面应是友好的,不应该让用户去猜做什么,主屏幕也应该带有主机名和日期,并 伴随有运行此菜单的用户名。由于测试原因,所有选项使用的是系统命令。 下面是即将显示的菜单。 首先,使用命令替换设置日期,主机名和用户。日期格式为 / D D / M M / Y Y Y Y,参数格式 为: $ date +%d/%m/%y 3 2 / 0 5 / 1 9 9 9 对于主机名,使用h o s t n a m e - s选项只抽取主机名部分。主机名有时也包括了完全确认的域 名。当然如果要在屏幕上显示这些,那就更好了。 可以给变量一个更有意义的名字: 对于w h i l e循环,只需将空命令直接放在 w h i l e后,即为无限循环,格式为: while ğ d o 命令 d o n e 要注意实际屏幕显示,不要浪费时间使用大量的 e c h o语句或不断地调整它们。这里使用 本地文档,在分界符后面接受输入,直至分界符被再次定位。格式为: 此技术用于菜单屏幕,也将用于帮助屏幕。帮助屏幕不像这样复杂。 用c a s e语句控制用户选择。菜单选择有: 第18章 控制流结构 199下载 c a s e语句应控制所有这些模式,不要忘了将大写与小写模式并列在一起,因为有时用户会 关闭或打开CAPS LOCK键。因为菜单脚本不断循环,所以应该允许用户退出,如果用户选择 Q或q键,脚本应退出,此时脚本带有 0值。 如果用户选择无效,应发出警报并带有警告信息。虽然本章开始说过从现在开始一直使 用L I N U X或BSD echo语句版本,这里必须使用系统 V版本发出警报: echo "\007 the bell ring" 用一个简单的e c h o和读语句锁屏直到用户点击回车键,这样任何信息或命令输出将可视。 也需要清屏,为此可使用t p u t命令(后面讨论t p u t),如果不这样做,使用c l e a r命令也可以。 到此所有功能已经具备了,脚本如下: 200 第四部分 基础s h e l l编程 下载 18.10 小结 在任何合理脚本的核心部分都有某种流控制,如果要求脚本具有智能性,必须能够进行 判断和选择。 本章讲述了怎样使用控制流写一段优美的脚本,而不只是完成基本功能。这里也学到了 怎样处理列表和循环直至条件为真或为假。 第18章 控制流结构 201下载 下载 第19章 shell 函数 本书目前为止所有脚本都是从头到尾执行。这样做很好,但你也许已经注意到有些脚本 段间互相重复。 s h e l l允许将一组命令集或语句形成一个可用块,这些块称为 s h e l l函数。 本章内容有: • 定义函数。 • 在脚本中使用函数。 • 在函数文件中使用函数。 • 函数举例。 函数由两部分组成: 函数标题。 函数体。 标题是函数名。函数体是函数内的命令集合。标题名应该唯一;如果不是,将会混淆结 果,因为脚本在查看调用脚本前将首先搜索函数调用相应的 s h e l l。 定义函数的格式为: 函数名() { 命令1 . . . } 或者 函数名(){ 命令1 . . . } 两者方式都可行。如果愿意,可在函数名前加上关键字 f u n c t i o n,这取决于使用者。 f u n c t i o n 函数名() { ... } 可以将函数看作是脚本中的一段代码,但是有一个主要区别。执行函数时,它保留当前 s h e l l和内存信息。此外如果执行或调用一个脚本文件中的另一段代码,将创建一个单独的 s h e l l,因而去除所有原脚本中定义的存在变量。 函数可以放在同一个文件中作为一段代码,也可以放在只包含函数的单独文件中。函数 不必包含很多语句或命令,甚至可以只包含一个 e c h o语句,这取决于使用者。 19.1 在脚本中定义函数 以下是一个简单函数 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 s h e l l解释器 首次发现它时,才可以使用。调用函数仅使用其函数名即可。上面的例子中,函数名为 h e l l o, 函数体包含一个e c h o语句,反馈当天日期。 19.2 在脚本中使用函数 现在创建函数,观察其在脚本中的用法。 运行脚本,结果为: 上面例子中,函数定义于脚本顶部。可以在脚本中使用函数名 h e l l o调用它。函数执行后, 控制返回函数调用的下一条语句,即反馈语句 back from the function。 19.3 向函数传递参数 向函数传递参数就像在一般脚本中使用特殊变量 $ 1 , $ 2 . . . $ 9一样,函数取得所传参数后, 将原始参数传回 s h e l l脚本,因此最好先在函数内重新设置变量保存所传的参数。这样如果函 数有一点错误,就可以通过已经本地化的变量名迅速加以跟踪。函数里调用参数(变量)的 转换以下划线开始,后加变量名,如: _ F I L E N A M E或_ f i l e n a m e。 19.4 从调用函数中返回 当函数完成处理或希望函数基于某一测试语句返回时,可做两种处理: 1) 让函数正常执行到函数末尾,然后返回脚本中调用函数的控制部分。 2) 使用r e t u r n返回脚本中函数调用的下一条语句,可以带返回值。0为无错误,1为有错误。 这是可选的,与最后状态命令报表例子极其类似。其格式为: 第19章 shell 函数 203下载 r e t u r n 从函数中返回, 用最后状态命令决定返回值。 Return 0 无错误返回。 Return 1 有错误返回 19.5 函数返回值测试 可以直接在脚本调用函数语句的后面使用最后状态命令来测试函数调用的返回值。例如: 更好的办法是使用 i f语句测试返回0或者返回1。最好在i f语句里用括号将函数调用括起来 以增加可读性。例如: 如果函数将从测试结果中反馈输出,那么使用替换命令可保存结果。函数调用的替换格 式为: v a r i a b l e _ n a m e = f u n c t i o n _ n a m e 函数f u n c t i o n _ n a m e输出被设置到变量v a r i a b l e _ n a m e中。 不久我们会接触到许多不同的函数及使用函数的返回值和输出的不同方法。 19.6 在shell中使用函数 当你收集一些经常使用的函数时,可以将之放入函数文件中并将文件载入 s h e l l。 文件头应包含语句# ! / b i n / s h,文件名可任意选取,但最好与相关任务有某种实际联系。例 如,f u n c t i o n s . m a i n。 一旦文件载入s h e l l,就可以在命令行或脚本中调用函数。可以使用 s e t命令查看所有定义 的函数。输出列表包括已经载入 s h e l l的所有函数。 如果要改动函数,首先用 u n s e t命令从s h e l l中删除函数,尽管 u n s e t删除了函数以便于此函 数对于s h e l l或脚本不可利用,但并不是真正的删除。改动完毕后,再重新载入此文件。有些 s h e l l会识别改动,不必使用u n s e t命令,但为了安全起见,改动函数时最好使用 u n s e t命令。 19.7 创建函数文件 下面创建包容函数的函数文件并将之载入 s h e l l,进行测试,再做改动,之后再重新载入。 函数文件名为f u n c t i o n s . m a i n,内容如下: 204 第四部分 基础s h e l l编程 下载 上述脚本本书前面用过,现在将之转化为一个函数。这是一个基本 f i n d命令的前端。如果 不加参数,函数将返回 1,即发生错误。注意错误语句中用到了实际函数名,因为这里用 $ 0, s h e l l将只返回s h -信息,原因是文件并不是一个脚本文件。这类信息对用户帮助不大。 19.8 定位文件 定位文件格式为: . / p a t h n a m e / f i l e n a m e 现在文件已经创建好了,要将之载入 s h e l l,试键入: $. functions.main 如果返回信息file not found,再试: $. /functions.main 此即<点> <空格> <斜线> <文件名>,现在文件应该已载入 s h e l l。如果仍有错误,则应该仔 细检查是否键入了完整路径名。 19.9 检查载入函数 使用s e t命令确保函数已载入。s e t命令将在s h e l l中显示所有的载入函数。 19.10 执行shell函数 要执行函数,简单地键入函数名即可。这里是带有一个参数的 f i n d i t函数,参数是某个系 统文件。 第19章 shell 函数 205下载 19.10.1 删除shell函数 现在对函数做一些改动。首先删除函数,使其对 s h e l l不可利用。使用 u n s e t命令完成此功 能。删除函数时u n s e t命令格式为: unset function_name $ unset findit 如果现在键入s e t命令,函数将不再显示。 19.10.2 编辑shell函数 编辑函数f u n c t i o n s . m a i n,加入f o r循环以便脚本可以从命令行中读取多个参数。改动后函 数脚本如下: 再次定位函数 $. /functions.main 使用s e t命令查看其是否被载入,可以发现 s h e l l正确解释f o r循环以接受所有输入参数。 现在执行改动过的f i n d i t函数,输入两个参数: 206 第四部分 基础s h e l l编程 下载 19.10.3 函数举例 既然已经学习了函数的基本用法,现在就用它来做一些工作。函数可以节省大量的编程 时间,因为它是可重用的。 1. 变量输入 以下脚本询问名,然后是姓。 要求输入字符必须只包含字母。如果不用函数实现这一点,要写大量脚本。使用函数可 以将重复脚本删去。这里用 a w k语言测试字符。以下是取得只有小写或大写字符的测试函数。 首先设置变量$ 1为一有意义的名字,然后用 a w k测试整个传送记录只包含字母,此命令输 出(1为非字母,空为成功)保存在变量 _ L E T T E R S _ O N LY中。 然后执行变量测试,如果为空,则为成功,如果有值,则为错误。基于此项测试,返回 码然后被执行。在对脚本的函数调用部分进行测试时,使用返回值会使脚本清晰易懂。 使用i f语句格式测试函数功能: 如果有错误,可编写一个函数将错误反馈到屏幕上。 第19章 shell 函数 207下载 函数n a m e _ e r r o r用于显示所有无效输入错误。使用特殊变量 $ @显示所有参数,这里为变 量F _ N A M E和S _ N A M E值。完成脚本如下: 注意每个输入的w h i l e循环,这将确保不断提示输入直至为正确值,然后跳出循环。当然, 208 第四部分 基础s h e l l编程 下载 实际脚本拥有允许用户退出循环的选项,可使用适当的游标,正像控制 0长度域一样。 2. echo问题 e c h o语句的使用类型依赖于使用的系统是 L I N U X、B S D还是系统V,本书对此进行了讲解。 下面创建一个函数决定使用哪种 e c h o语句。 使用e c h o时,提示应放在语句末尾,以等待从 r e a d命令中接受进一步输入。 L I N U X和B S D为此使用e c h o命令- n选项。 以下是L I N U X(B S D)e c h o语句实例,这里提示放于e c h o后面: 系统V使用\ C保证在末尾提示: 在e c h o语句开头L I N U X使用- e选项反馈控制字符。其他系统使用反斜线保证 s h e l l获知控 制字符的存在。 有两种方法测试e c h o语句类型,下面讲述这两种方法,这样,就可以选择使用其中一个。 第一种方法是在e c h o语句里包含测试控制字符。如果键入 \ 0 0 7和一个警铃,表明为系统V, 如果只键入\ 0 0 7,显示为L I N U X。 以下为第一个控制字符测试函数。 注意这里又用到了特殊变量 $ @以反馈字符串,要在脚本中调用上述函数,可以使用: uni_prompt "\007 there goes the bell ,What is your name:" 这将发出警报并反馈‘ What is your name:’,并在行尾显示字符串。如果在末尾出现字 符,则为系统V版本,否则为L I N U X / B S D版本。 第二种方法使用系统V \c测试字母z是否悬于行尾。 第19章 shell 函数 209下载 要在脚本中调用上述函数,可以使用: uni_prompts "\007 there goes the bellđWhat is your name:" 使用两个函数中任意一个,并加入一小段脚本: 将产生下列输出: There goes the bellđWhat is your name: 3. 读单个字符 在菜单中进行选择时,最麻烦的工作是必须在选择后键入回车键,或显示“ press any key to continue”。可以使用d d命令解决不键入回车符以发送击键序列的问题。 d d命令常用于对磁带或一般的磁带解压任务中出现的数据问题提出质疑或转换,但也可 用于创建定长文件。下面创建长度为 1兆的文件m y f i l e。 dd if:/dev/zero of=myfile count=512 bs=2048 d d命令可以翻译键盘输入,可被用来接受多个字符。这里如果只要一个字符, d d命令需 要删除换行字符,这与用户点击回车键相对应。 d d只送回车前一个字符。在输入前必须使用 s t t y命令将终端设置成未加工模式,并在 d d执行前保存设置,在d d完成后恢复终端设置。 函数如下: 要调用函数,返回键入字符,可以使用命令替换操作,例子如下: 210 第四部分 基础s h e l l编程 下载 4. 测试目录存在 拷贝文件时,测试目录是否存在是常见的工作之一。以下函数测试传递给函数的文件名 是否是一个目录。因为此函数返回时带有成功或失败取值,可用 i f语句测试结果。 函数如下: 要调用函数并测试结果,可以使用: 5. 提示Y或N 许多脚本在继续处理前会发出提示。大约可以提示以下动作: • 创建一个目录。 • 是否删除文件。 • 是否后台运行。 • 确认保存记录。 等等 以下函数是一个真正的提示函数,提供了显示信息及缺省回答方式。缺省回答即用户按 下回车键时采取的动作。c a s e语句用于捕获回答。 第19章 shell 函数 211下载 要调用上述函数,须给出显示信息或参数 $ 1,或字符串变量。缺省回答 Y或N方式也必须 指定。 以下是几种函数c o n t i n u e _ p r o m p t的调用格式。 在脚本中加入上述语句,给出下列输入: 现在可以看出为什么函数要有指定的缺省回答。 以下是函数调用的另一种方式: 212 第四部分 基础s h e l l编程 下载 也可以使用字符串变量$ 1调用此函数: 6. 从登录I D号中抽取信息 当所在系统很庞大,要和一登录用户通信时,如果忘了用户的全名,这是很讨厌的事。 比如有时你看到用户锁住了一个进程,但是它们的用户 I D号对你来说没有意义,因此必须要 用grep passwd文件以取得用户全名,然后从中抽取可用信息,向其发信号,让其他用户开锁。 以下函数用于从grep /etc/passwd命令抽取用户全名。 本系统用户全名位于p a s s w d文件域5中,用户的系统可能不是这样,这时必须改变其域号 以匹配p a s s w d文件。 这个函数需要一个或多个用户 I D号作为参数。它对密码文件进行 g r e p操作。 函数脚本如下: 以下为w h o i s函数调用方式: 7. 列出文本文件行号 在v i编辑器中,可以列出行号来进行调试,但是如果打印几个带有行号的文件,必须使用 n l命令。以下函数用n l命令列出文件行号。原始文件中并不带有行号。 第19章 shell 函数 213下载 要调用n u m b e r _ f i l e函数,可用一个文件名做参数,或在 s h e l l中提供一文件名,例如: $ number_file myfile 也可以在脚本中这样写或用: number_file $1 输出如下: 8. 字符串大写 有时需要在文件中将字符串转为大写,例如在文件系统中只用大写字符创建目录或在有 效的文本域中将输入转换为大写数据。 以下是相应功能函数,可以想像要用到 t r命令: 变量 u p p e r保存返回的大写字符串,注意这里用到特定参数 $ @来传递所有参数。 s t r _ t o _ u p p e r可以以两种方式调用。在脚本中可以这样指定字符串。 或者以函数输入参数$ 1的形式调用它。 214 第四部分 基础s h e l l编程 下载 两种方法均可用替换操作以取得函数返回值。 9. is_upper 虽然函数s t r _ t o _ u p p e r做字符串转换,但有时在进一步处理前只需知道字符串是否为大写。 i s _ u p p e r实现此功能。在脚本中使用 i f语句决定传递的字符串是否为大写。 函数如下: 要调用i s _ u p p e r,只需给出字符串参数。以下为其调用方式: 要测试字符串是否为小写,只需在函数 i s _ u p p e r中替换相应的 a w k语句即可。此为 i s _ l o w e r。 10. 字符串小写 现在实现此功能,因为已经给出了 s t r _ t o _ u p p e r,最好相应给出s t r _ t o _ l o w e r。函数工作方 式与前面一样。 函数如下: 第19章 shell 函数 215下载 变量 L O W E R保存最近返回的小写字符串。注意用到特定参数 $ @传递所有参数。 s t r _ t o _ l o w e r调用方式也分为两种。可以在脚本中给出字符串: 或在函数中用参数代替字符串: 11. 字符串长度 在脚本中确认域输入有效是常见的任务之一。确认有效包括许多方式,如输入是否为数 字或字符;域的格式与长度是否为确定形式或值。 假定脚本要求用户交互输入数据到名称域,你会想控制此域包含字符数目,比如人名最 多为2 0个字符。有可能用户输入超过 5 0个字符。以下函数实施控制功能。需要向函数传递两 个参数,实际字符串和字符串最大长度。 函数如下: 调用函数c h e c k _ l e n g t h: 216 第四部分 基础s h e l l编程 下载 循环持续直到输入到变量 N A M E的数据小于最大字符长度,这里指定为 1 0,b r e a k命令然 后跳出循环。 使用上述脚本段,输出结果如下: 可以使用w c命令取得字符串长度。但是要注意,使用 w c命令接受键盘输入时有一个误操 作。如果用户输入了一个名字后,点击了几次空格键, w c会将这些空格也作为字符串的一部 分,因而给出其错误长度。 a w k在读取键盘时缺省截去字符串末尾处空格。 以下是w c命令的缺点举例(也可以称为特征之一) 运行上述脚本(其中□为空格) 12. chop c h o p函数删除字符串前面字符。可以指定从第一个字符起删去的字符数。假定有字符串 M Y D O C U M E N T. D O C,要删去M Y D U C U M E N T部分,以便函数只返回 . D O C,需要把下述命 令传给c h o p函数: M Y D O C U M E N T . D O C1 0 Chop 函数如下: 第19章 shell 函数 217下载 删除后字符串保存于变量C H O P P E D中,使用下面方法调用c h o p函数: 或者: 13. MONTHS 产生报表或创建屏幕显示时,为方便起见有时要快速显示完整月份。函数 m o n t h s,接受 月份数字或月份缩写作为参数,返回完整月份。 例如,传递3或者0 3可返回M a r c h。函数如下: 218 第四部分 基础s h e l l编程 下载 用下面方法调用函数m o n t h s months 04 上面例子显示A p r i l,脚本中使用: 返回月份J u n e。 19.10.4 将函数集中在一起 本章目前讲到的函数没有一定的顺序。这些例子只表明函数不一定很长或不一定为一些 复杂的脚本。 本书许多函数脚本简单实用,并不需要任何新的后备知识。这些函数只是防止重复输入 脚本,实际上这就是函数的基本功能。 本章开始部分,讲到怎样在 s h e l l中使用函数。第一次使用函数时,也许要花一段时间才 能理解其返回值的用法。 本章讲到了几种不同的调用函数及其返回值的方法。如果遇到问题,查看一下实例返回 值及其测试方法即可。 以下是一些小技巧。测试函数时,首先将其作为代码测试,当结果满意时,再将其转换 为函数,这样做可以节省大量的时间。 19.11 函数调用 本章最后讲述使用函数的两种不同方法:从原文件中调用函数和使用脚本中的函数。 19.11.1 在脚本中调用函数 要在脚本中调用函数,首先创建函数,并确保它位于调用之前。以下脚本使用了两个函 数。此脚本前面提到过,它用于测试目录是否存在。 第19章 shell 函数 219下载 上述脚本中,两个函数定义于脚本开始部分,并在脚本主体中调用。所有函数都应该在 任何脚本主体前定义。注意错误信息语句,这里使用函数 e r r o r _ m s g显示错误,反馈所有传递 到该函数的参数,并加两声警报。 19.11.2 从函数文件中调用函数 前面讲述了怎样在命令行中调用函数,这类函数通常用于系统报表功能。 现在再次使用上面的函数,但是这次将之放入函数文件 f u n c t i o n s . s h里。s h意即s h e l l脚本。 220 第四部分 基础s h e l l编程 下载 现在编写脚本就可以调用 f u n c t i o n s . s h中的函数了。注意函数文件在脚本中以下述命令格 式定位: .\ 使用这种方法不会创建另一个 s h e l l,所有函数均在当前s h e l l下执行。 运行上述脚本,可得同样输出结果,好像函数在脚本中一样。 第19章 shell 函数 221下载 19.12 定位文件不只用于函数 定位文件不只针对于函数,也包含组成配置文件的全局变量。 假定有两个备份文件备份同一系统的不同部分。最好让它们共享一个配置文件。为此需 要在一个文件里创建用户变量,然后将一个备份脚本删除后,可以载入这些变量以获知用户 在备份开始前是否要改变其缺省值。有时也许要备份到不同的媒体中。 当然这种方法可用于共享配置以执行某一过程的任何脚本。下面的例子中,配置文件 b a c k f u n c包含一些备份脚本所共享的缺省环境。文件如下: 缺省文件很清楚,第1域_ C O D E包含一个脚本关键字。要查看并且改变缺省值,用户必须 首先输入匹配_ C O D E取值的脚本,即“c o m e t”。 以下脚本要求输入密码,成功后显示缺省配置。 脚本运行时,首先要求输入脚本。脚本匹配后,可以查看缺省值。然后就可以编写脚本 让用户改变缺省值。 222 第四部分 基础s h e l l编程 下载 19.13 小结 使用函数可以节省大量的脚本编写时间。创建可用和可重用的脚本很有意义,可以使主 脚本变短,结构更加清晰。 当创建了许多函数后,将之放入函数文件里,然后其他脚本就可以使用这些函数了。 第19章 shell 函数 223下载 下载 第20章 向脚本传递参数 前面已经讲到如何使用特定变量 $ 1 . . $ 9向脚本传递参数。$ #用于统计传递参数的个数。可 以创建一个u s a g e语句,需要时可通知用户怎样以适当的调用参数调用脚本或函数。 本章内容有: • shift。 • getopts。 • shift和g e t o p t s例子。 简单地说,下述脚本框架控制参数开始与停止。脚本需要两个参数,如果没有输入两个 参数,那么产生一个u s a g e语句。注意这里使用c a s e语句处理输入脚本的不同参数。 执行脚本,输入以下参数,结果为: 任何U N I X或L I N U X命令均接受一般格式: 命令 选项 文件 选项部分最多可包含 1 2个不同的值。上述脚本中,如果必须控制不同的命令选项,就要 加入大量脚本。这里只控制两个选项:开始和停止。 幸运的是s h e l l提供s h i f t命令以帮助偏移选项,使用 s h i f t可以去除只使用$ 1到$ 9传递参数的 限制。 20.1 shift命令 向脚本传递参数时,有时需要将每一个参数偏移以处理选项,这就是 s h i f t命令的功能。 它每次将参数位置向左偏移一位,下面用一段简单脚本详述其功能。脚本使用 w h i l e循环反馈 所有传递到脚本的参数。 你可能想像,上述脚本一直执行,直到命令行中不再有更多的参数输入。错了,因为没 有办法偏移到脚本中下一个参数,将只会反馈出第一个参数。执行结果如下: 20.1.1 shift命令简单用法 使用s h i f t命令来处理传递到脚本的每一个参数。改动后脚本如下: 现在再执行,结果将会不同: 20.1.2 命令行输入的最后一个参数 虽然还没有讲e v a l命令,如果需要知道命令行中输入的最后一个参数(通常是一个文件名), 可以有两种选择:使用命令 eval echo \$$#;使用s h i f t命令:shift 'expr $# -2'。 第20章 向脚本传递参数 225下载 20.1.3 使用shift处理文件转换 s h i f t可使控制命令行选项更加容易。下面构造一个转换脚本,使用 t r将文件名转换为大写 或小写。 脚本选项为: -l 用于小写转换。 -u 用于大写转换。 使用s h i f t命令将脚本放在一起以控制 - l和- u选项。脚本的第一版本如下: 首先检查脚本是否有参数,如果没有,打印 u s a g e语句,如果有需要处理的参数,使用 c a s e语句捕获每一个传送过来的选项。当处理完此选项后,使用 s h i f t命令搜集命令行中下一选 项,如果未发现匹配选项,打印 u s a g e语句。 当向脚本传递两个无效参数时,输出如下: 下一步就是要用c a s e语句处理选项后传递过来的文件名。为此需改动 c a s e语句。c a s e语句 中捕获任意模式*应该为- *以允许传递无效选项,例如 - p或- q。 226 第四部分 基础s h e l l编程 下载 *模式也匹配传递过来的所有文件名,以便用 f o r循环处理每一个文件,这里也将使用 - f选 项检测文件是否存在。 改动后的c a s e语句如下: 还需要指定与选项(- l,- u)相关的变量设置。这些变量是: T R C A S E 保存转换类型(大写或小写)。 E X T 所有文件转换后,大写文件名为 . U C,小写为. L C,不保存初始文件状态。 O P T 如果给出此选项,设其为 y e s,否则为n o。如果没有给出此选项,捕获此信息并反 馈出来。 其他部分脚本用于实际转换处理,这里即 t r命令。t r命令放在c a s e语句f o r循环中读取文件 名进行处理的脚本末尾部分。 以下为完整脚本: 第20章 向脚本传递参数 227下载 228 第四部分 基础s h e l l编程 下载 执行上述脚本,给出不同选项,得结果如下: 转换一个不存在的文件: 传递不正确选项: 只键入文件名,希望脚本提示更多帮助信息: 输入两个有效文件及第三个无效文件: 使用上述脚本可以将许多文件转换为同样的格式。编写一段脚本,使其控制不同的命令 行选项,这种方式编程量很大,是一件令人头疼的事。 假定要写一段脚本,要求控制以下各种不同的命令行选项: ଁ਷-l -c 23 -v໓ࡱ1໓ࡱ2 s h i f t命令显得力不从心,这就需要用到 g e t o p t s命令。 20.2 getopts g e t o p t s可以编写脚本,使控制多个命令行参数更加容易。 g e t o p t s用于形成命令行处理标 准形式。原则上讲,脚本应具有确认带有多个选项的命令文件标准格式的能力。 20.2.1 getopts脚本实例 通过例子可以更好地理解g e t o p t s。以下g e t o p t s脚本接受下列选项或参数。 •a 设置变量A L L为t r u e。 •h 设置变量H E L P为t r u e。 第20章 向脚本传递参数 229下载 •f 设置变量F I L E为t r u e。 •v 设置变量V E R B O S E为t r u e。 对于所有变量设置,一般总假定其初始状态为 f a l s e: g e t o p t s一般格式为: getopts option_string variable 在上述例子中使用脚本: while getopts ahfgv OPTION 可以看出w h i l e循环用于读取命令行,o p t i o n _ s t r i n g为指定的5个选项(- a,- h,- f,- g,- v), 脚本中v a r i a b l e为O P T I O N。注意这里并没有用连字符指定每一单个选项。 运行上述脚本,给出几个有效和无效的选项,结果为: 可以看出不同选项的结合方式。 230 第四部分 基础s h e l l编程 下载 20.2.2 getopts使用方式 g e t o p t s读取o p t i o n _ s t r i n g,获知脚本中使用了有效选项。 g e t o p t s查看所有以连字符开头的参数,将其视为选项,如果输入选项,将把这与 o p t i o n _ s t r i n g对比,如果匹配发现,变量设置为 O P T I O N,如果未发现匹配字符,变量能够设 置为?。重复此处理过程直到选项输入完毕。 g e t o p t s接收完所有参数后,返回非零状态,意即参数传递成功,变量 O P T I O N保存最后处 理参数,一会儿就可以看出处理过程中这样做的好处。 20.2.3 使用getopts指定变量取值 有时有必要在脚本中指定命令行选项取值。 g e t o p t s为此提供了一种方式,即在 o p t i o n _ s t r i n g中将一个冒号放在选项后。例如: getopts ahfvc: OPTION 上面一行脚本指出,选项 a、h、f、v可以不加实际值进行传递,而选项 c必须取值。使用 选项取值时,必须使用变量 O P TA R G保存该值。如果试图不取值传递此选项,会返回一个错 误信息。错误信息提示并不明确,因此可以用自己的反馈信息屏蔽它,方法如下: 将冒号放在o p t i o n _ s t r i n g开始部分。 while getopts :ahfgvc: OPTION 在c a s e语句里使用?创建一可用语句捕获错误。 改动后g e t o p t s脚本如下: 第20章 向脚本传递参数 231下载 运行上述脚本,选项- c不赋值,将返回错误,但显示的是脚本语句中的反馈信息: 现在,输入所有合法选项: 20.2.4 访问取值方式 g e t o p t s的一种功能是运行后台脚本。这样可以使用户加入选项,指定不同的磁带设备以 备份数据。使用g e t o p t s实现此任务的基本框架如下: 232 第四部分 基础s h e l l编程 下载 上述脚本中如果指定选项 d,则需为其赋值。该值为磁带设备路径。用户也可以指定是否 备份输出到登录文件中的内容。运行上述脚本,指定下列输入: g e t o p t s检查完之后,变量 O P TA R G取值可用来进行任何正常的处理过程。当然,如果输 入选项,怎样进行进一步处理及使该选项有有效值,完全取决于用户。 以上是使用g e t o p t s对命令行参数处理的基本框架。 实际处理文件时,使用f o r循环,就像在t r- c a s e脚本中使用s h i f t命令过滤所有选项一样。 使用g e t o p t s与使用s h i f t方法比较起来,会减少大量的编程工作。 20.2.5 使用getopts处理文件转换 现在用所学知识将t r- c a s e脚本转换为g e t o p t s版本。命令行选项g e t o p t s方法与s h i f t方法的唯 一区别是一个V E R B O S E选项。 变量V E R B O S E缺省取值为n o,但选择了命令行选项后, c a s e语句将捕获它,并将其设为 y e s,反馈的命令是一个简单的 i f语句。 如果正在使用其他系统命令包,它总是反馈用户动作,只需简单地将包含错误的输出重 定向到/ d e v / n u l l中即可。如: 命令 >/dev/null 2 >&1 缺省时V E R B O S E关闭(即不显示),使用- v选项可将其打开。例如要用 V E R B O S E将 m y f i l e文件系列转换为小写,方法如下: tr-case -l -v myfile1 myfile2 ... 或者 tr-case -v -l myfile1 myfile2 ... 可能首先注意的是使用 g e t o p t s后脚本的缩减效果。这里用于文件处理的脚本与 s h i f t版本 相同。 脚本如下: 第20章 向脚本传递参数 233下载 在脚本中指定命令行选项时,最好使其命名规则与 U N I X或L I N U X一致。下面是一些选项 及其含义的列表。 234 第四部分 基础s h e l l编程 下载 选项 含义 - a 扩展 - c 计数、拷贝 - d 目录、设备 - e 执行 - f 文件名、强制 - h 帮助 - i 忽略状态 - l 注册文件 - o 完整输出 - q 退出 - p 路径 -v 显示方式或版本 20.3 小结 正确控制命令行选项会使脚本更加专业化,对于用户来说会使之看起来像一个系统命令。 本章讲到了控制命令行选项的两种方法, s h i f t和g e t o p t s。使用g e t o p t s检测脚本的数量远远小 于使用s h i f t方法检测脚本的数量。 s h i f t也克服了脚本参数 $ 1 . . $ 9的限制。使用s h i f t命令,脚本可以很容易偏移至所有调用参 数,因此脚本可以做进一步处理。 第20章 向脚本传递参数 235下载 下载 第21章 创建屏幕输出 用户可以使用 s h e l l脚本创建交互性的、专业性强的屏幕输出。要实现这一点,系统上需 要一个彩色监视器和t p u t命令。 本章内容有: • tput命令。 • 使用转义序列和产生控制码。 • 使用颜色。 作者写这本书时,遇到了t p u t命令的三种不同变形。至今为止最好的是 GNU tput,如果没 有这个版本,首先下载它并安装在你的系统里。 t p u t使用文件/ e t c / t e r m i n f o或/ e t c / t e r m c a p,这 样就可以在脚本中使用终端支持的大部分命令了。 虽然t p u t不识别颜色设置,但是可以使用控制字符实现这一点。 21.1 tput 在使用t p u t前,需要在脚本或命令行中使用 t p u t命令初始化终端。 $ tput init t p u t产生三种不同的输出:字符型、数字型和布尔型(真 /假)。以下分别介绍其使用功 能。 21.1.1 字符串输出 下面是大部分常用字符串: 名字 含 义 b e l 警铃 b l i n k 闪烁模式 b o l d 粗体 c i v i s 隐藏光标 c l e a r 清屏 c n o r m 不隐藏光标 c u p 移动光标到屏幕位置(x,y) e l 清除到行尾 e l l 清除到行首 s m s o 启动突出模式 r m s o 停止突出模式 s m u l 开始下划线模式 r m u l 结束下划线模式 s c 保存当前光标位置 rc 恢复光标到最后保存位置 s g r 0 正常屏幕 r e v 逆转视图 21.1.2 数字输出 以下是大部分常用数字输出。 名字 含义 c o l s 列数目 i t t a b设置宽度 l i n e s 屏幕行数 21.1.3 布尔输出 在t p u t中只有两种布尔操作符。 名字 含义 c h t s 光标不可见 h s 具有状态行 21.2 tput用法 上面讲过了可能用到的t p u t的大多数常用名。现在学习在一段脚本中使用 t p u t。 21.2.1 设置tput命令 可以取得所有t p u t名字输出,将其保存为更有意义的变量名。格式如下: variable_name='tput name' 21.2.2 使用布尔输出 可以在i f语句中使用布尔型t p u t输出。 21.2.3 在脚本中使用tput 以下脚本设置tput bel和c l为更有意义的变量名。 下面脚本改变两个视图属性,并将光标关闭和打开。 第21章 创建屏幕输出 237下载 21.2.4 产生转义序列 注意,如果正在使用一个仿真器,要使光标不可见,这个操作可能会有问题。这是因为: 1) 一些仿真器并不捕获使光标不可见的控制字符。必须要求正在使用的软件仿真的制作 者修改源脚本以关闭光标。 2) tput civis命令的一些旧版本工作不正常。 关闭光标的控制字符是?2 5 l(这是字母l),将之打开是?2 5 h。 所有控制字符均以一个转义序列开始。通常转义键后紧跟字符 [。然后实际序列打开或关 闭某终端属性。 可以使用两种不同的方法产生转义序列。下面的列表依据用户系统列出两种方法。第三 种方法对于U N I X和L I N U X支持的变量均有效,因为控制序列嵌在 e c h o语句中。本书将使用这 种方法。 要发送一转义序列以关闭光标: \ 0 3 3为转义键取值, \通知e c h o命令接下来是一个八进制值。例如要反馈一个 @字符,键 入: echo o@p或者e c h o - eo\ 1 0 0p 对于系统v ,使用 echo o\ 1 0 0p 结果是一样的。 命令c l e a r表示清屏并发送光标到屏幕左上角,此位置一般也称为 h o m e。在一个V T终端范 围实现此功能所需序列为E S C I J,可以使用e c h o语句发送这一序列。 对于嵌入在文本中的任何控制字符,不要试图剪切和粘贴,因为这样会失去其特殊含义。 例如,要插入控制字符,打开光标,方法如下: e c h o ' < CT R L - V> hit the key then [?25h' 即先击< C T R L - V > ,再击退格键,确保这不是一个仿真器。然后加入一小段脚本将之 238 第四部分 基础s h e l l编程 下载 打开和关闭。可以将这段脚本编成一个函数或者在后面几页找一下这段脚本。 21.2.5 光标位置 可以用t p u t将光标放在屏幕任意位置。格式为: cup r c r为从上至下屏幕行数,c为穿过屏幕列数。 最好将之编成函数,这样就可以把行和列的值传递给它。 当然再传递一个字符串给它也很合适。以下是稍加改动后的函数脚本。 第21章 创建屏幕输出 239下载 这可以像下面这样调用: 21.2.6 在屏幕中心位置显示文本 在屏幕中心位置显示文本不是很麻烦。首先从 t p u t中得到列数,然后算出所提供的字符串 长度,从t p u t列数中减去该值,结果再除以 2,所得结果可用于显示的字符串的行数。 以下脚本段实现此功能。只需稍加改动即可从文件中读取各行并在屏幕中间位置显示文本。 输入一些字符,点击回车键,文本将显示在屏幕中间第 1 0行。 将上述脚本编成函数,并带有两个参数:文本和行数,这样调用更加灵活,函数如下: 可使用下述格式调用上述函数: centertxt 15 oTHE MAIN EVENTpb 或者用字符串作参数 : centertxt 15 $1b 21.2.7 查找终端属性 下面脚本使用t p u t访问t e r m i n f o,显示前面提到过的t p u t命令下的一些终端转义码。 240 第四部分 基础s h e l l编程 下载 命令i n f o c m p从t e r m i n f o数据库中抽取终端信息,如果要查看终端定义文件的完整列表, 可使用命令: $ infocmp $TERM 以下是t e r m p u t脚本的终端输出: 21.2.8 在脚本中使用功能键 使用c a t命令可以查看发送的任意特殊键控制序列( F 1,上箭头等),键入cat -v,然后按 任意控制键,回车,在下一行就可以知道终端发送了什么功能键。结束后按 < C t r l - c >退出。 下面的例子运行c a t命令,输入键为F 1(^ [ O P ]),F 2 ( [ O Q ] ),上箭头[ ^ [ [ A ]。 有了这些信息,就可以在脚本中插入这些字符作为用户选择的另外一些方法。 下面脚本识别F 1、F 2和箭头键,取值可能不同,因此要先用 c a t命令查看用户终端控制键 发送的取值。 第21章 创建屏幕输出 241下载 21.2.9 使用颜色 对域使用颜色可以使数据输入屏幕看起来更加专业。下面将使用的颜色是 A N S I标准颜 色,并不是所有颜色都适合于所有系统。下面列出了大部分常用颜色。 1. 前景色: 数字 颜色 数字 颜色 3 0 黑色 3 4 蓝色 3 1 红色 3 5 紫色 3 2 绿色 3 6 青色 3 3 黄(或棕)色 3 7 白(或灰)色 2. 背景色: 数字 颜色 数字 颜色 4 0 黑色 4 4 青色 4 1 红色 4 5 蓝色 4 2 绿色 4 6 青色 4 3 黄(或棕)色 4 7 白(或灰)色 显示前景或背景颜色格式为: 242 第四部分 基础s h e l l编程 下载 [background_number;foreground_number m 21.2.10 产生颜色 产生颜色需要在 e c h o语句中嵌入控制字符。这种方法适用于带有彩色终端的任何系统。 与在控制字符里一样,可以在 e c h o语句里使用转义序列产生颜色。 要产生一个黑色背景加绿色前景色: 一般方法是先击< C t r l - v >,然后是< E S C A P E >键,接着是[ 4 0;32 m。本书使用这种方法。 可能发现将颜色设置与e c h o语句放在一个c a s e语句里,然后将之编成一个函数,这样做最 好。下面是作者编写的颜色函数。 要调用颜色r e d - y e l l o w(红色背景,黄色前景),方法如下: colour red-yellow 在脚本中可以这样使用颜色: 作者终端的缺省屏幕颜色是黑色和白色。但是如果要用黑色背景加绿色前景,可插入一 个e c h o语句,同时将之放入用户. p r o f i l e文件中。 图2 1 - 2显示加入颜色设置后的基本输出屏幕。这种颜色看起来更加吸引人 第21章 创建屏幕输出 243下载 图21-1 下述脚本使用颜色和tput命令所得的屏幕概貌 下面是显示图2 1 - 1屏幕的脚本: 244 第四部分 基础s h e l l编程 下载 第21章 创建屏幕输出 245下载 如你所见,这个脚本没有经过验证,这样也行,因为这里脚本的目标只是显示怎样为屏 幕上色。 21.2.11 创建精致菜单 在讲述w h i l e循环时曾经创建过菜单,现在增加菜单脚本,菜单将具有下列选项: 本脚本使用r e a d _ c h a r函数,使用户在选择菜单选项时不必敲入回车键。 t r a p命令(本书后 面提到)用于忽略信号2、3和1 5,这样将防止用户试图跳出菜单。 此菜单还有一些控制访问形式。授权用户可以修改和删除记录,其余用户只能增加,查 看或打印记录。带有访问级别的有效用户列表保存在文件 p r i v. u s e r中。 用户运行菜单时,如果菜单名在文件中不存在,将被告之不能运行此应用并且退出。 只出于显示目的,系统命令就替换了实际的选项执行操作。执行时我们会发现用户 r o o t, d a v e和m a t t y不能修改数据库文件,而p e t e r和l o u i s e可以。 要检查用户权限,首先需要读入列表文件,忽略注释行,将其他行重定向到一个临时文 件中。 246 第四部分 基础s h e l l编程 下载 下一步是读取新形成的格式化文件,变量 F O U N D首先设置为假,临时文件保存名字和权 限级别。分别用用户名和权限级别设置为一个变量,然后执行测试文件中名字是否匹配 U S E R。 U S E R取值是从脚本开始时 w h o a m i命令中获得的。如果不匹配,则寻找其他用户,使用命令 c o n t i n u e循环进入下一步。 处理过程直至所有用户名读取和匹配完毕。如果整个文件中均未找到匹配用户名,脚本 末尾的t e s t语句捕获权限级别,对一般访问级别为 1,对高级访问权限返回0。 当用户选择修改或删除记录时,基于上述函数的返回值进行了一项测试。这个例子中 p a s s w d文件被分类或列出一个目录清单。 图21-2 带有访问限制的菜单屏幕概貌 第21章 创建屏幕输出 247下载 R e s t r i c t是一个只打印违规操作提示符的函数。 上述测试在一个循环里即可完成,但考虑到脚本清晰性,使用了两个文件的方法,这样 调试起来会更容易。 图2 1 - 2显示用户d a v e试图修改记录,但只具有一般权限,因此被提示权限不够。 用户可以选择q或Q退出菜单。退出时调用一个清屏函数。这样做的好处在于可以随意增 加要运行的命令,同时也增加脚本的可读性。 脚本如下: 248 第四部分 基础s h e l l编程 下载 第21章 创建屏幕输出 249下载 250 第四部分 基础s h e l l编程 下载 这种菜单可以在p r o f i l e文件中用e x e c命令调用,用户不能够退出。它们将始终位于菜单或 菜单子选项的应用里面。这对于只使用 U N I X或L I N U X应用而不关心s h e l l的用户来说是一种好 方法。 21.3 小结 使用t p u t命令可以增强应用外观及脚本的控制。颜色设置可以增加应用的专业性。注意使 用颜色不要太过火,这也许对你来说很好,但其他用户使用这段脚本时看到这种屏幕设置也 许会感到厌烦。可以使用和读取控制字符来增加脚本的灵活性,特别是对用户击键输入操作 更是如此。 第21章 创建屏幕输出 251下载 下载 第22章 创建屏幕输入 屏幕输入或数据输入是接受输入(这里指键盘)并验证其有效的能力。如果有效,接受 它,如果无效,放弃该输入。 前面讲到了基于一些条件的测试函数,例如字符串长度、字符串是数字或字符型,这一 章在脚本中将继续使用这些函数。 本章内容有: • 验证有效输入。 • 增加、删除、修改和查看记录。 • 修改脚本的工作文件。 本章开始读起来可能有些累人,因此可以先大概看一下,再慢慢细看。验证有效性的代 码量很大,这是因为为捕获所有错误,脚本必须测试几乎所有可能的错误。 现在在创建一个一般文件以修改系统地过程中逐步实现每一个任务:增加、删除、修改 和查看记录。这里也将创建一个个人文件以修改系统。记录文件 D B F I L E保存下列信息: 域 长 度 允许输入 描 述 职员号码 1 0 数字 雇员代码 名 2 0 字符 雇员名 姓 2 0 字符 雇员姓 部门 - 记帐 雇员所在部门 IT 服务 销售 权利 域间用冒号:分隔,例如: ::: 每一个任务即是一个完整脚本。脚本中一小部分复制于前面的两个例子。这样做是因为 本章主要用于显示怎样用文件修改系统。刚开始编写脚本时,最令人头疼的事就是将修改的 文件或数据库系统放在一起后的文档清理工作。 运行脚本应具有一些菜单选项,与任务或模块相连接或包含在文件里与菜单脚本相关的 一系列函数相连接。每一段脚本均执行 t r a p命令,信号2、3和1 5被忽略。 22.1 增加记录 将记录加入文件,包含以下两个步骤: 1) 确认输入有效。 2) 将记录写入文件。 第一个任务就是将一些函数放在一起,这些函数测试域是字符型或数字型及域的长度限 制,即数据输入有效性检验。有效性检验将用于增加数据和修改数据。幸运的是前面已经编 好这些函数,检测字符串及长度的函数脚本如下: 检测字符串是否为数字型,函数脚本如下: 检测字符串是否为字符型,函数脚本如下: 当域读取完毕时,调用相应函数,测试其返回值。 这里也需要提示以保存屏幕信息直到键入某键删除这些信息,下列函数用到了 r e a d _ a _ c h a r函数。 第22章 创建屏幕输入 253下载 当用户输入雇员代码后,要确保编号还没有用到,此域必须唯一。测试这一点有几种方 法,这里使用g r e p。g r e p搜寻字符串_ C O D E中的雇员编号,如果a w k返回空值,则不存在匹配 编号,函数返回状态码 0。函数如下(这里在 g r e p中使用“$ _ C O D E \ >”抽取相应匹配,变量 用双引号括起来,如果用单引号,则返回空值): 以下是检测雇员编号代码段,之后继续讲解其功能。 254 第四部分 基础s h e l l编程 下载 所有检测语句均在 w h i l e循环中(实际上每一个数据输入域均在一单独的 w h i l e循环中), 如果没有有效数据,提示返回初始读位置。 读完雇员编号,继续检测域中数据是否存在: if ["$NUM"!=""] 如果域中没有输入数据。则不执行 i f语句t h e n部分。e l s e部分在脚本结尾部分,用于显示 下列信息: Staff Number:No Input DetectedđThis Field Requires a Number t h e n部分包括对域输入数据的所有有效性检测。假定存在输入,调用 a _ n u m b e r函数,测 试传输字符串是否为一数字,如果是,函数返回 0,如果不是,函数返回 1。基于此返回值, 设置指针 NU M_ PA S S为0,表示返回值正确(数字型),设置为 1,表示返回失败(非数字 型)。 然后调用函数l e n g t h _ c h e c k,参数为字符串及字符串包含字符最大数目。这里为 1 0,如果 字符串长度小于1 0,则返回0,否则返回1。指针L E N _ PA S S设置为0,表示返回成功(长度不 超过最大长度),设置为1,表示返回失败(长度超出最大长度)。 接下来检测是否有重复雇员编号。调用函数 c h e c k _ d u p l i c a t e,如果没有发现重复编号,设 置指针D U P L I C AT E为0,最后测试三个指针变量均为 0(无错误),为此使用A N D测试,如果 条件同时成立,执行t h e n部分语句。 如果测试通过,则存在有效域。这时处在 w h i l e循环中,因此需要用b r e a k命令跳出循环。 如果有效性测试任何一部分失败,即长度测试或类型测试之一不通过,返回错误信息并 显示在屏幕底部。 Staff Number : Non_Numeric or Too Many Numbers In Fieldb 验证第2和第3域有效性,处理过程一样。有效性验证这次在另一个循环中。这次调用 c h a r a c t e r s函数,检验域是否只包含字符。下述脚本做名字有效性检验: 第22章 创建屏幕输入 255下载 使用c a s e语句检验部门域(列表见下面),因为公司只包含5个部门,部门域必须是其中之 一。注意对每个部门有三个不同的匹配模式,可以由用户键入部门名称加以验证。如果找到 匹配模式,用户跳出c a s e语句,并显示有效部门列表。 当所 有域 的有 效性验 证完 成后 ,将 提示 用户 是否保 存此 记录 ,这 里使 用函数 c o n t i n u e _ p r o m p t Y N,前面讲过这个函数,它处理 Y或N响应,用户也可以点击回车键表示缺 省回答。 如果用户选择N,进入i f语句代码段并退出脚本。如果用户输入许多记录,然后在 w h i l e循 环中调用函数以增加记录,这种方法将不会返回菜单或增加记录后退出。 如果用户选择Y,保存记录。将记录加入一个文件的脚本是: echo "$NUM:$F_NAME:$S_NAME:$DEPART">>$DBFILEb 然后显示信息通知用户记录已存入文件。 s l e e p命令将脚本进程挂起 1 s,以使用户有足够 的时间查看该信息。 域分隔符是冒号,文件以姓域分类,输出存入一临时工作文件,然后文件移入初始文件 D B F I L E。文件转移操作时,会测试其最后状态,如果存在问题,则通知用户该信息。 加入一个记录后,输出如下: 256 第四部分 基础s h e l l编程 下载 增加记录后,D B F I L E文件内容如下: 以下是增加一个记录的完整脚本: 第22章 创建屏幕输入 257下载 258 第四部分 基础s h e l l编程 下载 第22章 创建屏幕输入 259下载 260 第四部分 基础s h e l l编程 下载 第22章 创建屏幕输入 261下载 262 第四部分 基础s h e l l编程 下载 22.2 删除记录 要从文件中删除记录,首先要将记录传给用户以确保该记录是正确删除的记录。得到确 认后才执行下列任务: 1) 查询记录。 2) 显示记录。 3) 确认删除。 4) 修改文件。 首先使用姓域查询记录,一旦从用户处得到需查询的姓,则使用 g r e p或a w k进行处理,但 是因为此文件不会有超过1 0 0个记录,将直接从文件中执行读取操作,进行匹配测试。 如果文件包括超过几百个记录,建议使用 a w k,因为使用a w k比直接从文件中读取数据 快得多,同样也比用g r e p将各域分开存入变量要快一些。 可以使用a w k或g r e p查询文件D B F I L E: 注意使用a w k时,变量用单引号括起来,如果不这样做,就不会返回任何数据。 将各域分开,并设置与其对应的变量(记住这里用冒号作分隔符)。必须改变I F S设置为 冒号。如果不这样,就不能读取记录。改变 I F S设置时,最好先保存其设置,以便于脚本完成 时再恢复它。 要保存I F S,使用: S A V E D I F S = $ I F S 将其改为冒号: I F S = : 当用I F S完成操作后,简单的恢复它: I F S = $ S A V E D I F S 查询记录函数为g e t _ r e c,此函数不带参数。 用户可以输入姓或q退出任务。一旦输入姓,执行测试以确保输入存在。比较好的测试方 法是: if ["$STR"!=""]Ġt h e n 然后是: [-2 $STR] 第一个测试捕获只键入回车符的用户。第二个测试 0长度字符串。 使用有意义的变量名从文件中读取各域,读取记录时使用计数,通过计数变化告之用户 查询记录时发生某种动作。如果发现匹配模式,调用另一过程显示此域,用户然后使用 b r e a k 命令跳出循环。如果未找到匹配模式,脚本进入下一循环步。找到匹配记录后,询问用户是 否删除记录,缺省回答是n o。 第22章 创建屏幕输入 263下载 使用grep -v执行记录删除,并使用字符串 S T R(S T R保存用户删除的雇员姓)显示所有未 匹配记录。 g r e p命令输出重定向到一临时工作文件中。然后文件移入初始 D B F I L E中,删除后执行文 件分类,输出重定向到一临时工作文件,然后再移回初始文件 D B F I L E。临时工作文件可能首 先被分类,然后再移回初始文件,而不是先移后分类。 使用最后状态命令执行测试所有文件移动操作。以下是删除一个记录的输出结果: 删除记录的完整脚本如下: 264 第四部分 基础s h e l l编程 下载 第22章 创建屏幕输入 265下载 22.3 修改记录 实际上已经编写了修改记录的大部分脚本,这些脚本在记录删除操作中。 找到正确记录后,设置所有该记录域变量到一临时工作文件,然后使用缺省设置变量: ğ{ d e f a u l t _ v a r i a b l e = v a r i a b l e } 266 第四部分 基础s h e l l编程 下载 对于不想修改的域,简单输入回车键即可,然后缺省值放入临时工作变量中。在相关域 内键入新值即可修改此域。 使用grep -v执行文件的实际修改操作。除了正在被修改的记录,所有记录重定向到一临 时工作文件,这里雇员编号用作 g r e p命令字符串参数: grep -v $CODE $DBFILE >$HOLD1 然后提示用户是否保存记录。如果保存,重新修改的记录也写入临时工作文件,然后此 临时工作文件移入原文件D B F I L E中。 输出被重定向到一临时工作文件,然后将此文件分类,再移回原文件 D B F I L E。最后状态 命令测试文件移动操作。如果存在问题,告之用户。实际脚本中,在增加记录时执行的有效 性测试也同样用于修改记录中。修改一个记录的输出结果如下: 完整脚本如下: 第22章 创建屏幕输入 267下载 268 第四部分 基础s h e l l编程 下载 第22章 创建屏幕输入 269下载 22.4 查看记录 用户可能要查看所有记录或其中一部分。如果查看所有记录,使用 c a t命令和a w k,如果 记录包含很多域,那么很有必要定量显示输出结果,使其对用户更加实用。 在删除和修改记录中,已经讲过了怎样显示单一记录,用户有选择的查看记录选项时唯 一增加的功能就是打印一个记录。以下脚本段将记录发往打印机: 270 第四部分 基础s h e l l编程 下载 查看记录时输出结果如下: 查看记录的完整脚本如下: 第22章 创建屏幕输入 271下载 272 第四部分 基础s h e l l编程 下载 22.5 小结 验证用户输入的有效性很重要,也是一种高级技巧。虽然你可能有时知道记录接受输入 的内容,但用户通常并不知道。 在计算机工业发展史上有一句老话:送进的是垃圾,出来的肯定是垃圾,但知道这一点 时已经太晚了,意即如果没有在脚本中测试垃圾数据,就会输出垃圾信息。 第22章 创建屏幕输入 273下载 下载 第23章 调试脚本 s h e l l编程最烦人的一项工作是调试问题。有一些方法可以借鉴,但是最好能在问题出现 前防止大部分错误,为此应遵循以下规则。 将设计脚本分成几个任务或过程,然后在继续下一步前分别予以测试。 本章内容有: • 一般错误。 • set命令介绍。 没有比在脚本中发现一个难以察觉的错误更令人头疼的了,然而,随着编程经验不断丰 富,查询手段也相应增加。 经常碰到的问题是忘了使用引号或在 i f语句末尾未加f i。 需要牢记的一点是当 s h e l l打印出一个脚本错误后,不要只看那些疑问行。而是要观察整 个相关代码段。s h e l l不会对错误进行精确定位,而是在试图结束一个语句时进行错误统计。 23.1 一般错误 23.1.1 循环错误 f o r、w h i l e、u n t i l和c a s e语句中的错误是指实际语句段不正确。也许漏写了固定结构中的 一个保留字。 下面错误打印信息 d o n e,这是一个很好的线索。因为这时知道正在处理一个 w h i l e语句。 回溯脚本段,检查w h i l e语句,是否漏写或错写了关键字,如 d o或者正在使用的条件语句。 23.1.2 典型的漏写引号 第二个典型错误是漏写引号错误。经常要注意这个问题,因为此错误经常出现。这里给 出解决这类错误的唯一方案是在脚本中确保所有引号成对出现。 当s h e l l打印出错误行后,通常在 v i编辑器中查看文件。使用 v i的set nu选项调试错误,先 进入v i,然后点击< E S C >键,后跟一冒号,再键入 set nu ,这时给出文本行号,然后 进入s h e l l打印错误行。 23.1.3 测试错误 另一个常见错误是在使用- e q语句时忘记在测试条件一边使用数字取值。 如果得到下列错误提示,通常是由于两件事情:需要在变量和方括号间加空格;在方括 号里漏写操作符。 [: missing ']' 23.1.4 字符大小写 经验上讲大多数错误是由于使用变量时大小写保持不一致。例如经常在开始定义时用大 写,然后在变量调用时用了小写字符,这样难免变量会没有赋值。 23.1.5 for循环 使用f o r循环时,有时会忘了在循环的列表部分用 $符号,特别是在读取字符串时。 23.1.6 echo 最有用的调试脚本工具是 e c h o命令。一般在可能出现问题的脚本重要部分加入 e c h o命令, 例如在变量读取或修改操作其前后加入 e c h o命令。 使用最后状态命令判断命令是否成功,这里需要注意的是,不要使用 e c h o命令后直接加 最后状态命令,因为此命令永远为真。 23.2 set命令 s e t命令可辅助脚本调试。以下是 s e t命令常用的调试选项: set -n 读命令但并不执行。 set -v 显示读取的所有行。 set -x 显示所有命令及其参数。 将s e t选项关闭,只需用+替代-。有人总认为+应该为开,而-应为关闭,但实际刚好相反。 可以在脚本开始时将 s e t选项打开,然后在结束时关闭它。或在认为有问题的特殊语句段 前后打开及关闭它。 下面看一个例子。以下脚本将名字保存在变量列表中。用户输入名字, f o r循环循环变量 列表查看是否有匹配模式。注意这里在脚本开始时使用了 set -x,并在结尾部分关闭它。 运行此脚本,给出一个不在列表中的名字,输出如下: 第23章 调试脚本 275下载 输出显示对变量列表进行循环时所有的比较操作。当读取文件或进行字符串或取值的比 较发现问题时,使用s e t命令是很有必要的。 23.3 小结 跟踪错误的最好方式是亲自查阅脚本,并使用 s e t命令并加大量的e c h o语句。 276 第四部分 基础s h e l l编程 下载 下载 第24章 shell嵌入命令 实际上已经用过了许多 s h e l l嵌入命令。可能要想什么是 s h e l l嵌入,这些命令是在实际的 Bourne shell里创建而不是存在于/ b i n或u s r / b i n目录里。嵌入命令比系统里的相同命令要快。 本章内容有: • 标准的Bourne shell嵌入命令列表 例如,c d和p w d命令可同时在系统和嵌入命令中发现。如果要运行系统版,简单输入 命令路径即可: / b i n / p w d 24.1 shell嵌入命令完整列表 表2 4 - 1给出标准嵌入命令的完整列表。 表24-1 标准嵌入命令 : 空,永远返回为 t r u e . 从当前s h e l l中执行操作 b r e a k 退出f o r、w h i l e、u n t i l或c a s e语句 c d 改变到当前目录 c o n t i n u e 执行循环的下一步 e c h o 反馈信息到标准输出 e v a l 读取参数,执行结果命令 e x e c 执行命令,但不在当前s h e l l exit 退出当前s h e l l e x p o r t 导出变量,使当前s h e l l可利用它 p w d 显示当前目录 r e a d 从标准输入读取一行文本 r e a d o n l y 使变量只读 r e t u r n 退出函数并带有返回值 s e t 控制各种参数到标准输出的显示 s h i f t 命令行参数向左偏移一个 test 评估条件表达式 t i m e s 显示s h e l l运行过程的用户和系统时间 t r a p 当捕获信号时运行指定命令 u l i m i t 显示或设置s h e l l资源 u m a s k 显示或设置缺省文件创建模式 u n s e t 从s h e l l内存中删除变量或函数 w a i t 等待直到子进程运行完毕,报告终止 下面讲述一些未涉及或前面讲解不深的命令。 24.1.1 pwd 显示当前目录 $ pwd / t m p 24.1.2 set 在查看调试脚本、打开或关闭 s h e l l选项时,曾用到 s e t命令。s e t也可用于在脚本内部给出 其运行参数,以下举例说明。假定有一段脚本控制两个参数,但并不向脚本传递参数而是在 脚本内部设置其取值。可以用 s e t命令完成此功能。 格式为: set param1 param2 .. 下面的脚本设置参数为a c c o u n t s . d o c和a c c o u n t s . b a k,然后对参数进行循环处理。 当测试一段脚本且脚本包含参数时,这样使用 s e t命令有很多用处。其一就是不必在每次 运行脚本时重复输入参数。 24.1.3 times t i m e s命令给出用户脚本或任何系统命令的运行时间。第一行给出 s h e l l消耗时间,第二行 给出运行命令消耗的时间。下面是 t i m e s命令的输出结果: 相信以后会经常用到该命令。 24.1.4 type 使用t y p e查询命令是否仍驻留系统及命令类型。 t y p e打印命令名是否有效及该命令在系统 的位置。例如: 278 第四部分 基础s h e l l编程 下载 24.1.5 ulimit u l i m i t设置运行在s h e l l上的显示限制。通常此命令定位于文件 / e t c / p r o f i l e中,但是可以从 当前s h e l l或用户. p r o f i l e文件中将之移入用户需要的位置。 u l i m i t一般格式如下: ulimit options u l i m i t有几个选项,以下是一些常用的选项: 选项 含 义 - a 显示当前限制 - c 限制内核垃圾大小 - f 限制运行进程创建的输出文件的大小 例如u l i m i t取值为: 将内核文件垃圾数目设置为 0: 24.1.6 wait w a i t命令等待直到一个用户子进程完成,可以在 w a i t命令中指定进程I D号。如果并未指定, 则等待直到所有子进程完成。 等待所有子进程运行完毕: $ wait 24.2 小结 上面讲述了s h e l l嵌入命令,其中大部分前面已经用过,本章仅详细讲述了其使用方法。 第24章 s h e l l嵌入命令 279下载 下载 第25章 深入讨论<< 我们在介绍标准输入和标准输出以及 w h i l e循环的时候已经几次遇到 < <的应用。我们学会 了如何发送邮件,如何构建一个菜单,不过 < <还有很多其他的用法。 本章将介绍以下内容: • 快速创建一个文件。 • 自动进入菜单。 • ftp传输。 • 连接至其他应用系统。 该命令的一般形式为: command <> myfile <> myfile <<- NEWFILE . . . 25.2 快速创建打印文档 假如希望打印一小段信息,可以采用这种方法而不必使用 v i编辑器。在本例中,一旦在输 入Q U I C K D O C之后按回车键,相应的文档就会被送到打印机。 第五部分 高级shell编程技巧 25.3 自动选择菜单 不但可以很方便地使用 < <创建菜单屏幕,还可以使用它来自动选择菜单,而不是由用户 手工进行选择。 我编写了一个菜单驱动的数据库管理脚本,可以使用它来完成备份和其他系统管理任务。 该脚本本来是在白天由用户来运行的,现在决定把这些工作交给 c r o n夜间完成,我不想再另 外写一个自动运行的脚本,于是我使用 < <中的输入来选择s y b _ b a c k u p脚本的菜单选项。下面 介绍一下该脚本的菜单。 主菜单如下,选择2: 第二层菜单如下,选择3: 第三级菜单如下,选择Y: 从菜单来看,如果要备份所有的数据库,需要键入: 1) 菜单脚本的名字,s y b _ b a c k u p。 2) 键入2。 3) 键入3。 4) 键入Y。 下面的脚本能够自动运行数据库备份脚本 s y b _ b a c k u p: 282 第五部分 高级s h e l l编程技巧 下载 该脚本中的重定向部分是: 让我们来分析一下这一部分,这里给出了脚本 s y b _ b a c k u p的全路径;>>$log_f 2>&1意味 着所有的输出都重定向到 $ l o g _ f中,该变量的值为/ l o g s / s q l . b a c k u p . l o g。这是一个良好的习惯, 因为这样就能够捕捉到所运行的程序或脚本的所有输出,如果出现错误的话,也能够被记录 下来。 < < M AY D AY之后的内容就是手工运行 s y b _ b a c k u p脚本所需要输入的内容,直到遇到另外 一个M AY D AY结束。 这样,我就不需要重新再写一个脚本;如果已经有一个菜单驱动的脚本,只需再编写一 个使用< <输入的脚本就可以自动运行原先的脚本。 25.4 自动ftp传输 < <的另外一个流行的应用就是自动 f t p传输。在使用f t p时,如果能够向用户提供一个简单 的界面就好了。下面的脚本使用了匿名用户 a n o n y m o u s建立了一个f t p连接。这是一个特殊的 用户,它使得系统能够创建一个含有公共目录的安全帐户。一般来说,所有以匿名用户身份 进行连接的用户都只能从公共目录中下载文件,不过只要权限允许,用户也可以上载。 匿名用户的口令可以是任何字符串,不过最好使用主机名加上本地用户名,或电子邮件 地址。 下面的脚本将会提示如下的信息: 1) 希望登录的远程主机。 2) 文件传输的类型是二进制方式还是 A S C I I方式。 3) 要下载的文件名。 4) 存放下载文件的本地目录。 当用户输入想要连接的主机之后,首先执行一个名为 t r a c e r o u t e的脚本验证本地主机是否 能够连接到远程主机。如果 t r a c e r o u t e执行失败,这个自动 f t p传输的脚本将会再次提示用户输 入主机名。 第25章 深入讨论< < 283下载 用户在看到传输模式选择的提示之后按回车键,将会选择缺省的二进制模式。 用户在输入所要下载的文件名之后,将会被提示输入保存下载文件的本地目录。缺省的 本地目录是/ t m p。如果用户所给出的目录无法找到,仍将使用缺省的 / t m p目录。 下载文件在本地的文件名将是原文件名加上 . f t p后缀。 最后,用户所有的选择都将在屏幕上显示出来,待用户确认后开始进行传输。 下面就是该脚本运行时在屏幕上的显示: 下面就是该脚本的内容: 284 第五部分 高级s h e l l编程技巧 下载 第25章 深入讨论< < 285下载 在f t p命令中使用< <时,使用了ftp -i -n选项,这意味着不要自动登录,而且关闭交互模式。 这样就使得脚本可以使用 u s e r命令进行登录。口令是 $ U S E R @ T H I S H O S T,在这里就是 d a v e @ b u m p e r。 如果用户每天从同一台主机上下载相同的文件,比如说是包含前一天销售数据的文件,那 么用户就没有必要每天都输入同样的主机名和文件名。可以设置 D E S T _ H O S T和F I L E N A M E变 量的缺省值,这样就可以使用户不必每天都输入同样的主机名和文件名。 下面是f t p自动传输脚本中提示用户输入主机名的一段,但是现在不同的是, D E S T _ H O S T 变量已设置了缺省值m y _ f a v o u r i t e _ h o s t。现在用户可以另外输入一个不同的主机名,也可以敲 回车键选择缺省值。 注意,现在不必再检查用户是否输入了一个值,因为如果用户没有输入的话,该变量将 被赋予缺省值。 25.5 访问数据库 s h e l l脚本一个常用的用途就是访问数据库系统获得信息。实现这样的功能, < <是再理想 不过了。可以用它来输入你在面对数据库提示时所做的各种选择。下面的例子并不是数据库 中的一个练习,而是为了用来介绍如何使用 < <来连接其他应用程序,完成相应的任务。 对于某一个数据库系统来说,在使用某种第三方产品进行访问时, select into功能将会被 286 第五部分 高级s h e l l编程技巧 下载 关闭。这意味着该数据库不能被用来插入数据或创建临时表。 为了解决这个问题,我们使用 < <进行数据库连接,并使用一个 f o r循环来提供各个数据库 名,一旦连接成功,< <将用来向s q l命令提供选项。 下面就是该脚本。 让我们来看一看使用< <的部分,s h e l l在执行了变量替代以后将运行下面的一段命令。 当s h e l l看到结束的分界符 M AY D AY时,该脚本将开始下一次循环,对另外一个数据库进 行操作。下面就是运行的结果: 第25章 深入讨论< < 287下载 25.6 小结 本章进一步给出了一些使用 < <来自动完成某些任务的例子。 < <的用途很广,特别是在连 接某些应用程序或使用 f t p时。你可以灵活地使用 < <来自动运行以前编写的脚本,从而完成各 种不同的任务。 288 第五部分 高级s h e l l编程技巧 下载 下载 第26章 shell 工具 本章将讨论以下内容: • 创建以日期命名的文件及临时文件。 • 信号。 • t r a p命令以及如何捕获信号。 • e v a l命令。 • l o g g e r命令。 26.1 创建保存信息的文件 任何脚本都应该能够创建临时文件或日志文件。在运行脚本做备份时,最好是保存一个 日志文件。这些日志文件通常在文件系统中保留几周,过时将被删除。 在开发脚本的时候,可能总要创建一些临时的文件。在正常运行脚本的时候,也要使用 临时文件保存信息,以便作为另外一个进程的输入。可以使用 c a t命令来显示一个临时文件的 内容或把它打印出来。 26.1.1 使用date命令创建日志文件 在创建日志文件时,最好能够使它具有唯一性,可以按照日志文件创建的日期和时间来 识别这些文件。我们可以使用 d a t e命令做到这一点。这样就能够使日期和时间成为日志文件名 中的一部分。 为了改变日期和时间的显示格式,可以使用如下的命令: date option + %format 使用加号‘+’可以设置当前日期和时间的显示格式。下面的例子将日期以日、月、年的 格式显示: 下面是一些常用的日期格式: 下面的命令可以使时间按照 h h : m m的格式显示: 下面的命令可以显示完整的时间: 注意,如果希望在日期和时间的显示中包含空格,要使用双引号。 在文件名中含有日期的一个简单办法就是使用置换。把含有你所需要的日期格式的变量 附加在相应的日志文件名后面即可。 在下面的例子中我们创建了两个日志文件,一个使用了 d d,m m,y y的日期格式,另一个 使用了d d,h h,m m的时间格式。 下面就是这个脚本。 运行上面的脚本后,得到这样两个日志文件。 26.1.2 创建唯一的临时文件 在本书的前面讨论特殊变量时,曾介绍变量 $ $,该变量中保存有你所运行的当前进程的 进程号。可以使用它在我们运行的脚本中创建一个唯一的临时文件,因为该脚本在运行时的 进程号是唯一的。我们只要创建一个文件并在后面附加上 $ $即可。在脚本结束时,只需删除 带有$ $扩展的临时文件即可。 S h e l l将会把$ $解析为当前的进程号,并删除相应的文件,而不 会影响以其他进程号做后缀的文件。 在命令行中输入如下的命令: 这就是当前的进程号,如果你执行这个命令,看到的结果可能会有所不同。现在如果我 创建另一个登录进程并输入同样的命令,将会得到一个不同的进程号,因为我已经启动了一 个新的进程。 下面的例子中,创建了两个临时文件,并进行了相应的操作,最后在结束时删除了这些 文件。 290 第五部分 高级s h e l l编程技巧 下载 当上面的脚本运行时,将会创建这样两个文件: 在执行rm /tmp/*.$$时,s h e l l实际上将该命令解析为rm /tmp/*.408。 记住,该进程号只在当前进程中唯一。例如,如果我再次运行上面的脚本,将会得到一 个新的进程号,因为我已经创建了一个新的进程。 如果文件有特殊用途的话,那么创建含有日期的文件,就可以使你很容易地查找到它们。 而且还可以很容易地按照日期删除文件,因为这样一眼就能看出哪个文件是最新的,哪个文 件是最“旧”的。 还可以使用这种方法来快速地创建临时文件,它们在当前进程中是唯一的。在脚本结束 之前,也很容易删除这些临时文件。 26.2 信号 信号就是系统向脚本或命令发出的消息,告知它们某个事件的发生。这些事件通常是内 存错误 ,访问权限问题或某个用户试图停止你的进程。信号实际上是一些数字。下表列出了 最常用的信号及它们的含义。 信号 信号名 含义 1 S I G H U P 挂起或父进程被杀死 2 S I G I N T 来自键盘的中断信号,通常是 < C T R L - C > 3 S I G Q U I T 从键盘退出 9 S I G K I L L 无条件终止 11 S I G S E G V 段(内存)冲突 1 5 S I G T E R M 软件终止(缺省杀进程信号) 还有信号0,我们前面在创建 . l o g o u t文件时已经遇到过。该信号为“退出 s h e l l”信号。为 了发出信号0,只要从命令行键入e x i t,或在一个进程或命令行中使用 < C T R L - D >即可。 发送信号可以使用如下的格式: kill [-signal no:| signal name] process ID 使用k i l l命令时不带任何信号或名字意味着使用缺省的信号 1 5。 可以使用如下的命令列出所有的信号: 第2 6章 shell 工具 291下载 26.2.1 杀死一个进程 发送信号1将使一个进程重新读入配置文件。例如,你在运行域名服务( D N S)守护进程 n a m e d,现在你对域名数据库文件做了某些修改,这时不需要杀死该守护进程再重新启动,只 需使用kill -1命令向其发送信号1。N a m e d进程将重新读入它的配置文件。 下面的例子向系统中一个名为 m o n _ w e b的进程发送信号9(无条件终止)来杀死它。首先 使用p s命令得到相应的进程号。 如果系统不支持ps -ef命令,那么可以使用ps xa。为了杀死该进程,我可以使用下面的两 种方法之一: kill -9 157 或 kill -s SIGKILL 157 在有些系统中,不必使用- s,例如:kill SIGKILL 157。 下面的脚本将根据进程名来杀死一个进程,拟被杀死的进程名作为该脚本的一个参数。 在执行相应的命令之前,将会首先检查是否存在这样的进程。在这里使用 g r e p命令来匹配相 应的进程名。如果匹配成功,则向用户提示进程已经找到,并询问用户是否杀死该进程。最 后使用kill -9命令杀死相应的进程。 下面就是该脚本。 292 第五部分 高级s h e l l编程技巧 下载 运行该脚本将会产生如下的输出: 在使用该脚本时,要确信存在相应的进程: 26.2.2 检测信号 有些信号可以被应用程序或脚本捕获,并依据该信号采取相应的行动。另外一些信号不 能被捕获。例如,如果一个命令收到了信号 9,就无法再捕捉其他信号。 在编写s h e l l脚本时,只需关心信号 1、2、3和1 5。当脚本捕捉到一个信号后,它可能会采 取下面三种操作之一: 1) 不采取任何行动,由系统来进行处理。 2) 捕获该信号,但忽略它。 第2 6章 shell 工具 293下载 3) 捕获该信号,并采取相应的行动。 大多数的脚本都使用第一种处理方法,这也是到目前为止本书中所有脚本所采取的处理 方法。 如果想要采取另外两种处理方法,必须使用 t r a p命令。 26.3 trap t r a p可以使你在脚本中捕捉信号。该命令的一般形式为: trap name signal(s) 其中,n a m e是捕捉到信号以后所采取的一系列操作。实际生活中, n a m e一般是一个专门 用来处理所捕捉信号的函数。 N a m e需要用双引号(“”)引起来。S i g n a l就是待捕捉的信号。 脚本在捕捉到一个信号以后,通常会采取某些行动。最常见的行动包括: 1) 清除临时文件。 2) 忽略该信号。 3) 询问用户是否终止该脚本的运行。 下表列出了一些最常见的t r a p命令用法: trap "" 2 3 忽略信号2和信号3,用户不能终止该脚本 trap"commands" 2 3 如果捕捉到信号2或3,就执行相应的c o m m a n d s命令 trap 2 3 复位信号2和3,用户可以终止该脚本 也可以使用单引号(‘’)来代替双引号(“”);其结果是一样的。 26.3.1 捕获信号并采取相应的行动 下面的例子一经运行就开始计数直至用户按 < C t r l - C >(信号2)。这时该脚本将会显示出当 前的循环数字,然后退出。 在本例中t r a p命令的格式为: trap "do_something" signal no:(s) 下面就是该脚本: 现在让我们来仔细分析一下该脚本。 294 第五部分 高级s h e l l编程技巧 下载 trap "my_exit" 2 在本例中,由于设置了 t r a p命令,所以在捕捉到信号 2以后,双引号内的m y _ e x i t函数将被 执行。 函数m y _ e x i t将在脚本捕捉到信号 2后被调用;用户将会看到 $ L O O P变量的内容,即用户 按< C t r l - C >时的计数值。在实际中,通常捕捉到信号 2后所调用的函数是用来完成清除临时文 件等任务的。 下面是该脚本的运行结果: 26.3.2 捕获信号并采取行动的另一个例子 下面就是一个捕获信号后清除临时文件的例子。 下面的脚本在运行时不断使用 d f和p s命令向临时文件 H O L D 1 . $ $和H O L D 2 . $ $中写入相应 的信息。你应该还记得 $ $表示当前的进程号。当用户按 < C T R L - C >时,这些临时文件将被清 除。 第2 6章 shell 工具 295下载 上面的脚本在运行时会产生如下的结果: 当收到信号2或3时,尽管一般情况下这都不是误操作,但是为了安全起见,不妨给用户 一个选择的机会,这样用户在不小心按下 < C T R L - C >后,仍然可以撤消刚才的动作。 在下面的例子中,在脚本捕捉到信号 2后将会向用户提供一个选择,询问用户是否真的要 退出。这里使用c a s e语句来决定采取何种操作。 如果用户希望退出,他或她可以选择 1,此时当前函数会以状态 1退出,而另一个清除进 程将会据此启动。如果用户并不希望退出,那么可以选择 2或不做任何选择,此时 c a s e语句将 会使用户退回到脚本中原来的地方。在 c a s e语句中一定要包含用户输入空字符串的情况。 下面的函数在收到信号后,将会向用户提供选择: 下面是完整的脚本: 296 第五部分 高级s h e l l编程技巧 下载 当上面的脚本运行时,只要在输入任何域时按下 < C T R L - C >,就会得到一个选择:是继续 运行还是退出。 26.3.3 锁住终端 下面的脚本是另一个捕获信号的例子。该脚本名为 l o c k i t,它将使用一个连续不断的 w h i l e 循环锁住终端。在该脚本中, t r a p命令捕捉信号2、3和1 5。如果一个用户试图中断该脚本的运 行,将会得到一个不成功的提示。 在脚本初次执行时,将会被提示输入一个口令。在解锁终端时没有任何提示,可以直接 输入口令并按回车键。该脚本会从终端读入所输入的口令,并与预先设置的口令做比较,如 果一致就解锁终端。 如果忘记了自己的口令,那么只好登录到另一个终端上并杀死该进程。在本例中没有对 口令的长度加以限制—这完全取决于你。 如果你从另外一个终端上杀死了该进程,当再次回到这个终端时,可能会遇到终端设置 问题,例如回车键不起作用。这时可以试着使用下面的命令,这样可以解决大部分问题。 $ stty sane 下面就是该脚本。 第2 6章 shell 工具 297下载 下面是l o c k i t脚本运行时的输出: 接着屏幕就被清除。如果按回车键或其他错误的口令,该脚本将会输出: 现在输入正确的口令: 现在又回到命令提示符下了。 26.3.4 忽略信号 在用户登录时,系统将会执行/ e t c / p r o f i l e文件,根用户不希望其他普通用户打断这一进程。 他通常通过设置t r a p来屏蔽信号1、2、3和1 5,然后在用户读当天的消息时重新打开这些信号。 最后仍然回到屏蔽这些信号的状态。 在编写脚本时也可以采用类似的办法。在脚本运行的某些关键时刻,比如打开了很多文 298 第五部分 高级s h e l l编程技巧 下载 件时,不希望该脚本被中断,以免破坏这些文件。通过设置 t r a p来屏蔽某些信号就可以解决这 个问题。在这些关键性的处理过程结束后,再重新打开信号。 忽略信号的一般格式为(信号 9除外): trap""signal no:(s) 注意,在双引号之间没有任何字符,为了重新回到捕捉信号的状态,可以使用如下的命 令: trap"do something" signal no:(s) 下面我们来总结一下上述方法。 trap ""1 2 3 15:忽略信号。 关键性的处理过程 trap"my_exit" 1 2 3 15:重新回到捕捉信号的状态,在捕捉到信号后调用 m y _ e x i t函数。 下面就是一个这样的例子,其中的“关键”过程实际上是一个 w h i l e循环,但它能够很好 地说明这种方法。在第一个循环中,通过设置 t r a p来屏蔽信号,但是在第二个例子中,又回到 捕捉信号的状态。 两个循环都只数到6,不过在循环中使用了一个 s l e e p命令,这样就可以有充分的时间来实 验中断该循环。 下面就是脚本。 第2 6章 shell 工具 299下载 在上面的脚本在运行时,如果我们在第一个循环期间按下 < C t r l - C >,它不会有任何反应, 这是因为我们通过设置 t r a p屏蔽了信号;而在第二个循环中由于重新回到捕捉信号的状态,按 下< C t r l - C >就会调用m y _ e x i t函数。 当脚本捕获到信号时,通过使用 t r a p命令,可以更好地控制脚本的运行。捕获信号并进行 处理是一个脚本健壮性的标志。 26.4 eval e v a l命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一 次扫描无法实现其功能的变量。该命令对变量进行两次扫描。这些需要进行两次扫描的变量 有时被称为复杂变量。不过我觉得这些变量本身并不复杂。 e v a l命令也可以用于回显简单变量,不一定是复杂变量。 解释e v a l命令是怎么回事的最好办法就是看几个例子。 26.4.1 执行含有字符串的命令 我们首先创建一个名为 t e s t f的小文件,在这个小文件中含有一些文本。接着,将 cat testf 赋给变量M Y F I L E,现在我们e c h o该变量,看看是否能够执行上述命令。 现在我们将cat testf赋给变量M Y F I L E。 $ MYFILE=ocat testfp 300 第五部分 高级s h e l l编程技巧 下载 如果我们e c h o该变量,我们将无法列出t e s t f文件中的内容。 让我们来试一下e v a l命令,记住e v a l命令将会对该变量进行两次扫瞄。 从上面的结果可以看出,使用 e v a l命令不但可以置换该变量,还能够执行相应的命令。第 一次扫描进行了变量置换,第二次扫描执行了该字符串中所包含的命令 cat testf。 下面是另一个例子。一个名为 C AT _ PA S S W D的变量含有字符串“cat /etc/passwd | more”。 eval 命令可以执行该字符串所对应的命令。 e v a l命令还可以用来显示出传递给脚本的最后一个参数。现在来看下面的这个例子。 在运行上述脚本时,我们会看到如下的结果(你所看到进程号可能会不一样): 在上面的脚本中, e v a l命令首先把$ $ #解析为当前s h e l l的参数个数,然后在第二次扫描时 得出最后一个参数。 26.4.2 给每个值一个变量名 可以给一个值一个变量名。下面我对此做些解释,假定有一个名为 d a t a的文件: 你希望该文件中的第一列成为变量名,第二列成为该变量的值,这样就可以: 怎样才能做到这一点呢?当然是使用 e v a l命令。 第2 6章 shell 工具 301下载 我们用d a t a文件的第一行来解释上述脚本的执行过程,该脚本读入“ P C”和“4 8 6”两个 词,把它们分别赋给变量 N A M E和T Y P E。E v a l命令的第一次扫描把 N A M E和T Y P E分别置换 为“P C”和“4 8 6”,第二次扫描时将P C作为变量,并将“4 8 6”作为变量的值。 下面是运行上述脚本的结果: e v a l命令并不是一个在脚本中很常见的命令,但是如果需要对变量进行两次扫瞄的话,就 要使用e v a l命令了。 26.5 logger命令 系统中含有相当多的日志文件。其中的一个日志文件叫作 m e s s a g e s,它通常位于/ v a r / a d m 或/ v a r / l o g目录下。一个名为s y s l o g的配置文件可以用来定义记录在 m e s s a g e s文件中的消息,这 些消息有一定的格式。如果想知道系统中的相应配置,可以查看 / e t c / s y s l o g . c o n f文件。该文件 中包含了用于发送各种不同类型消息的工具及它们的优先级。 这里我们并不想深入探讨 U N I X和L I N U X是如何向该文件中记录信息的。我们现在只要知 道这些消息有不同的级别,从信息性的消息到关键性的消息。 还可以使用l o g g e r命令向该文件发送消息。在使用该命令之前,最好查阅连机手册,因为 在不同供应商所提供的操作系统上该命令的语法也有所不同。 不过,由于这里只涉及到信息性的消息,因此不必担心下面的命令不安全。 你可能会出于下列的原因向该文件中发送消息: • 在某一个特定的时间段出现的访问或登录。 • 你的某些执行关键任务的脚本运行失败。 • 监控脚本的报告。 下面是/ v a r / a d m / m e s s a g e s文件的例子。在系统上所看到的相应文件可能和下面的例子有 少许差别。 l o g g e r命令的一般形式为: logger -p -I message 302 第五部分 高级s h e l l编程技巧 下载 其中: - p:为优先级,这里只涉及到提示用户注意的优先级,这也是缺省值。 - i:在每个消息中记录发送消息的进程号。 26.5.1 使用logger命令 可以使用如下命令: 可能需要等几分钟才能看到该消息被记录到 m e s s a g e文件中。 如你所见,发送这一消息的用户也被记录了下来。 现在来创建一个小小的脚本,用它来记录当前系统中的用户数。该脚本可以在一天的时 段中记录系统的使用率。只要把它放进 c r o n t a b文件中,使它每3 0分钟运行一次即可。 运行下面的脚本。 $ test_logger 现在来看看m e s s a g e文件的末尾: 26.5.2 在脚本中使用logger命令 向日志文件中发送信息的一个更为合理的用途就是用于脚本非正常退出时。如果希望向 日志文件中发送消息,只要在捕获信号的退出函数中包含 l o g g e r命令即可。 在下面的清除脚本中,如果该脚本捕获到信号 2、3或1 5的话,就向该日志文件发送一个 消息。 第2 6章 shell 工具 303下载 这样只要看一下这个日志文件就可以知道脚本的运行结果是否正常。 除了使用l o g g e r命令对一些关键性的脚本处理过程做日志外,我还用它来记录使用调制解 调器连接系统的用户。下面的一段脚本记录了从串口 t t y 0和t t y 0 2连接到系统中的用户。这部分 代码来自于我编写的一个/ e t c / p r o f i l e文件。 304 第五部分 高级s h e l l编程技巧 下载 当希望在系统全局的日志文件中记录信息的时候, l o g g e r命令是一个非常好的工具。 26.6 小结 理解信号和对信号的捕获可以使脚本的退出更为完整。通过在系统日志文件中记录信息, 你或系统管理员就能够更容易地发现存在的问题。 第2 6章 shell 工具 305下载 下载 第27章 几个脚本例子 本章包含了我最常用的几个脚本。你会发现它们都相当短小而简单。这就是脚本的一个优 点;它不是很长、很复杂,只需很短的代码就能够完成相当多的功能,可以节约大量的时间。 本章中包含以下内容: • 各种脚本的例子。 我本来打算在本章中提供一个通用的数据验证数据库脚本,但是由于它超过了 5 0 0行,我 觉得编辑肯定不会同意把它收入书中。那个脚本几年前只有几行,后来由于不断增加功能, 变成了现在这么长。最后,我选择了如下六个脚本作为例子: p i n g a l l:一个按照/ e t c / h o s t s文件中的条目逐一p i n g所有主机的脚本。 b a c k u p _ g e n:一个通用的备份脚本,能够加载缺省设置。 d e l . l i n e s:一个引用s e d命令的脚本,能从文件中删除若干行。 a c c e s s _ d e n y:一个能够阻止某些特定用户登录的工具。 l o g r o l l:一个能够清除超过某一长度的日志的工具。 n f s d o w n:一个快速u n m o u n t所有n f s文件系统的工具。 27.1 pingall 几年前我写了一个名为 p i n g a l l的脚本在夜间运行,把它作为常规报告脚本的一部分。它 能够按照/ e t c / h o s t s文件中的条目逐一p i n g所有的主机。 该脚本列出/ e t c / h o s t s文件并查找其中的非注释行(不以 #开头的行)。然后使用一个 w h i l e 循环读入所有的行,接下来使用 a w k分析出每行的第一个域,并把它赋给变量 A D D R。最后使 用f o r循环逐一p i n g相应的地址。 下面就是该脚本。 上述脚本可以很容易地进行扩展,加进其他网络报告工具。 27.2 backup_gen 在本章中我选择了这个脚本并不是因为它展示了如何备份目录,而是因为它是一个同其 他脚本共享设置的很好例子。 b a c k u p _ g e n是一个用于备份的脚本,它从一个缺省的配置文件中读入设置,然后根据这 些参数对系统进行备份。用户可以根据自己的需要改变这些缺省设置。这是一个不同脚本如 何使用相同设置或仅在自己运行期间改变相应设置的极好例子。当该脚本执行时,它首先确 认源文件b a c k u p . d e f a u l t s是否存在,如果不存在,则退出。 该脚本在运行时,会显示出一个题头和缺省设置,并询问用户是否需要改变任何缺省设 置。如果用户回答“是”,在他们修改设置之前,该脚本就会提示他们输入一个代码,用户可 以有三次机会;如果输入正确的代码后仍无法改变设置,这就意味着用户必须要使用缺省设 置。一般来说,在输入正确代码后,用户可以改变下列设置( [ ]中的为缺省设置): • 磁带设备[ r m t 0 ] 可以选择r m t 1和r m t 3 • 备份完成后是否向系统管理员发邮件 [是] 可以选择否 • 备份的类型[全备份] 可以选择普通备份或s y b a s e备份 脚本中使用了一些临时变量来保存被修改的设置。用户可以按回车键选择缺省设置。下 列设置不能被改变: 备份日志文件名。 用户代码。 接着所有的改变会生效。在这些改变生效之后,相应的临时变量又会被重新赋予缺省值。 在备份进行之前,首先要测试磁带设备。备份过程使用 f i n d和c p i o命令,它们从设置文件中读 入相应变量的缺省值,或使用用户设定的值。 下面就是该脚本。 第27章 几个脚本例子 307下载 308 第五部分 高级s h e l l编程技巧 下载 第27章 几个脚本例子 309下载 源文件backup.defaults中包含函数continue_prompt,还有所有缺省设置。下面就是该源文件。 310 第五部分 高级s h e l l编程技巧 下载 下面是该脚本运行时的输出,缺省设置被显示在屏幕上,用户被询问是否要改变这些设 置: 第27章 几个脚本例子 311下载 下面是用户改变缺省值的过程。在下面的例子中,备份类型被用户改变,但是该脚本在 检查了相应的磁带设备之后,发现它有点问题。在使用了最后一个状态命令之后,该脚本将 会退出。 27.3 del.lines 之所以要编写这个脚本,是因为应用程序开发者总是问我“用s e d的哪个命令删除空行?”。 我决定写一个小脚本给他们使用,以免他们老是打电话问我这个命令。 这个脚本只是包装了一下s e d命令,但它能够使用户很方便地使用,他们非常喜欢用。 脚本一般都不长。如果你认为写一个脚本能够使某些任务自动化,能够节约时间,那么 你就可以编写一个脚本。 这个脚本可以处理一个或多个文件。每个文件在用 s e d删除空行之前要先核实是否存在。 s e d的输出被导入一个文件名中含有 $ $的临时文件,最后这个临时文件又被移回到原来的文件 中。 该脚本使用s h i f t命令取得所有的文件名,用 w h i l e循环逐个处理所有的文件,直至处理完 为止。 可以使用del.lines -help获得一个简短的帮助。你也可以创建一个更好的帮助。 下面是该脚本。 312 第五部分 高级s h e l l编程技巧 下载 27.4 access.deny 在对系统进行某些更新时,你可能不希望用户登录,这时可以使用 / e t c / n o l o g i n文件,大 多数系统都提供这个文件。一旦在 / e t c目录中使用 t o u c h命令创建了一个名为 n o l o g i n的文件, 除r o o t以外的任何用户都将无法登录。 如果系统不支持这种方法,你一样还可以做到这点—可以自己创建这个文件,下面就是 具体的做法。 可以在/ e t c / p r o f i l e文件中加入下面的代码: 现在,可以通过在/ e t c目录下创建n o l o g i n文件来阻止除根用户以外的其他用户登录。记住, 该文件要对所有用户可读。 第27章 几个脚本例子 313下载 当决定恢复用户登录时,只要删除该文件即可。 rm /etc/nologin 上述办法可以很方便地组织除根用户外的所有用户登录。如果希望临时禁止某个用户登 录,可以修改/ e t c / p a s s w d文件,把该用户的口令域的第一个字符变成 *。不过,这个问题比较 复杂,在操作之前一定要搞清楚,否则会带来系统性的问题。 L I N U X提供了一个工具,可以通过它在 l o g i n . a c c e s s文件中写入用户名和用户组。该文件 可以用来允许或禁止用户对系统的访问。 这里有一个上述工具的简化版本 d e n y. a c c e s s。该脚本从/ e t c / p r o f i l e文件中运行,它读入一 个名为l o c k o u t . u s e r s的文件。该文件包含有禁止登录的用户名。如果该文件中出现了 a l l这个单 词,那么除r o o t以外的所有用户都将被禁止登录。 下面是l o c k o u t . u s e r s文件的一个例子,该文件可以包含注释行。 下面解释该脚本的工作过程。首先,通过设置t r a p忽略所有的信号,这样用户就无法中断它 的执行。如果文件l o c k o u t . u s e r s存在,那么脚本将会继续运行。它首先检查该文件中是否存在单 词a l l,如果存在,就不再检查该文件中的其他用户名,并禁止除根用户以外的所有其他用户登 录。不要使用注释来屏蔽单词a l l,因为这样它仍然有可能起作用。不过你可以注释用户名。 如果单词a l l被找到,那么除r o o t外的所有用户都将无法登录。为了准确起见,在该脚本中 使用了g r e p的精确匹配模式a l l \ >。这时用户将会在屏幕上看到系统不可用的消息。 该脚本中的主要函数是get_users 。它读入文件l o c k o u t . u s e r s,忽略所有以#开头的注释行。 它通过比较用户名来确保用户名 r o o t没有出现在该文件中,即使出现也不会禁止 r o o t登录,否 则后果将难以想象。 当前正在登录的用户名可以从变量 L O G N A M E中得到,并与变量N A M E S做比较,而变量 N A M E S的内容来自于l o c k o u t . u s e r s文件。如果匹配,相应用户的登录进程将被终止。 我在几个拥有近 4 0个用户的系统上运行该脚本,它并没有影响用户登录的速度。当用户 外出超过一周或者用户午餐,而我需要对系统进行更新时,我就使用该脚本临时锁住相应的 帐户。 需要在/ e t c / p r o f i l e文件中加入这样一行。我把它加在该文件的末尾,这样即使用户无法登 录,也可以在此之前看见当前发给他的新消息。 . /apps/bin/deny.access / a p p s / b i n目录是我存放全局性脚本的地方—你可能把这些脚本放在另外的目录中,不过 314 第五部分 高级s h e l l编程技巧 下载 一定要确保所有用户都对该脚本及存放它的目录具有执行权限。 如果得到“权限不足”的错误提示,那说明该脚本或目录的权限不足。 我的l o c k o u t . u s e r s文件放在/ a p p s / e t c目录下。如果你的系统的目录结构有所不同的话,应 该作出相应的调整。由于该文件在登录时被引用,可以使用 s e t命令看到相应的函数(不过无 法看到l o c k o u t . u s e r s文件)。如果你觉得这不妥,只要在这些函数执行后使用 u n s e t命令去掉它 们即可。可以把u n s e t命令直接放在/ e t c / p r o f i l e文件中该命令行之后,就像这样: unset getusers 下面就是该脚本。 第27章 几个脚本例子 315下载 27.5 logroll 我的系统中的有些日志文件增长十分迅速,每天手工检查这些日志文件的长度并倒换这 些日志文件(通常是给文件名加个时间戳)是非常乏味的。于是我决定编写一个脚本来自动 完成这项工作。该脚本将提交给 c r o n进程来运行,如果某个日志文件超过了特定的长度,那 么它的内容将被倒换到另一个文件中,并清除原有文件中的内容。 你可以很容易地改编这个脚本用于清除其他的日志文件。我使用另外一个脚本来清除我的 系统日志文件,它每周运行一次,截断相应的日志文件。如果我需要再回头看这些日志文件, 只需在备份中寻找即可,这些日志文件的备份周期为 1 6周,这个周期长度应该说是足够了。 该脚本中日志文件的长度限制是由变量 B L O C K _ L I M I T设定的。这一数字代表了块数目, 在本例中是8块(每块大小为 4 K字节)。可以按照自己的需求把这一数字设得更高。所有我要 检查的日志文件名都保存在变量 L O G S中。 这里使用了一个 f o r循环来依次检查每一个日志文件,使用 d u命令来获取日志文件长度。 如果相应的文件长度大于 B L O C K _ L I M I T变量所规定的值,那么该文件将被拷贝到一个文件 名含有时间戳的文件中,并改变这个文件所属的组,原先的文件长度将被截断为 0。 该脚本由c r o n每周运行几次,生成了一些文件名中含有时间戳的日志文件备份,这样如 果系统出现了任何问题,我还可以回到这些备份中查找。 316 第五部分 高级s h e l l编程技巧 下载 27.6 nfsdown 如果系统中包含 n f s文件系统,你将发现下面的脚本非常实用。我管理着几台主机,不时 地需要在工作时间重启动其中的某台机器。这种重启动过程当然是越快越好。 由于我在好几个机器上都挂接了远程目录,我不想依靠系统的重启动过程来卸载这些 n f s 文件系统,宁愿自己来完成这个工作。这样还可以更快一些。 只要运行这个脚本就可以迅速卸载所有的 n f s文件系统,这样就能更快的重新启动机器。 该脚本的L I S T变量中含有提供 n f s目录的主机名。使用 f o r循环逐一卸载相应的目录,用 g r e p命令在d f命令的结果中查找n f s文件系统。n f s目录的m o u n t形式为: machine: remote_directory 这一字符串被保存在变量N F S _ M A C H I N E中。在u m o u n t命令中使用了该变量。 下面就是该脚本: 27.7 小结 本章中所提供的脚本都是我最常用的。正如前面所提到的,脚本不一定很长、很复杂, 但是它却不失为一种高效的方法。 第27章 几个脚本例子 317下载 下载 第28章 运行级别脚本 如果希望在系统启动时自动运行某些应用程序、服务或脚本,或者在系统重启动时能够 正确地关闭这些程序,那么需要创建运行级别脚本。除一种 L I N U X变体外,所有的L I N U X版 本都含有这种基于系统V的运行级别配置目录,就像其他 U N I X版本那样。 既然所有的系统都含有这种类型的配置,我们在本章中将会对它加以介绍,但如果你的 系统不含有这种目录,也不要紧。还可以通过其他方法在系统启动时自动运行程序;本章的 后半部分也将介绍这些方法。 本章包含下列内容: • 运行级别。 • 如何创建r c . s c r i p t s。 • 如何在不同的运行级别实现相应的 r c . s c r i p t s。 • 如何从i n i t t a b中启动应用程序。 能够创建运行级别脚本就意味着能够更灵活地控制系统。如果需要在某一个特定的运行 级别启动或停止程序,就得创建运行级别脚本(它们通常被称为 r c . s c r i p t)。 任何用关键字 s t a r t或s t o p调用的、能够启动或停止程序运行的脚本都可以看作是一个 r c . s c r i p t。注意,应当由用户来保证他或她所提交的脚本是一个有效的脚本,能够正确地启动 或停止某一服务。 运行级别配置目录的机制使得 r c . s c r i p t只在系统切换运行级别时有效。它不负责检查某一 运行级别中所有的特定服务是否都已经被启动或停止。这是 s h e l l编程者的事。 还可以按照希望运行的服务来控制系统的运行级别,但是这已经超出了本书的讨论范围。 28.1 怎么知道系统中是否含有运行级别目录 r c . s c r i p t s一般保存在 (实际上是个链接,这一点我们将在后面讲述 ) / e t c / r c N . d或 / e t c / r c . d / r c N . d目录下, 其中,N是一个数字。通常是 7个,因为r c N . d目录的序号是从0到6,不过在系统上可能会 有另外几个目录,如r c S . d。这并不重要,这里我们只关心带有数字的目录。 如果是L I N U X系统,那么⋯⋯ (续) 如果我们使用c d命令进入这些r c N . d目录,会发现这些目录中的 r c . s c r i p t s实际上是一些链 接。 28.2 确定当前的运行级别 本章不是针对系统管理员的,但是作为 s h e l l编程者,应当了解r c . s c r i p t s是什么,它们是被 怎样放置到运行级别配置目录中的。顺便说一下,如果想知道当前的运行级别,可以用下面 的命令: 在‘run level’后面的数字就是当前的运行级别。后面的时间是系统最近一次重启动的时 间。 如果是L I N U X系统,那么⋯⋯ $ runlevel 2 3 第一列表示系统的前一个运行级别,第二列表示系统当前的运行级别,在这里是 3。 28.3 快速熟悉inittab 运行级别目录中含有一系列启动服务的脚本。这里的“服务”可以是守护进程、应用程 序、服务器、子系统或脚本进程。在系统启动的过程中,将会启动一个名为 i n i t的进程(它是 系统中所有进程的祖先)。它所要完成的一部分工作就是看看需要启动哪些服务,应当缺省地 进入哪一个运行级别。它通过查看一个名为 i n i t t a b的配置文件来获得上述信息,该配置文件位 于/ e t c目录下。i n i t进程还按照该文件中的设置加载特定的进程。如果需要编辑这个配置文件, 一定要先做一个备份。如果该文件被破坏或出现“降级”错误,系统将无法正常启动,到那 时,将不得不进入单用户模式并修正该文件。 i n i t t a b文件所包含的域具有严格的格式。该文件中每个条目的格式为: i d : r s t a r t : a c t i o n : p r o c e s s 其中,i d域是相应进程的唯一标识。 r s t a r t域所包含的数字表示运行该进程的级别。 a c t i o n域告诉i n i t进程如何对待p r o c e s s所对应的进程。这里可以有很多种动作,但是最常 第28章 运行级别脚本 319下载 见的是w a i t和r e s p a w n。w a i t意味着当进程启动后等待它结束。 r e s p a w n则意味着如果该进程不 存在,则启动相应的进程,如果它存在,那么只要它一掉下来就立即重新启动它。 p r o c e s s域包含了实际要运行的命令。下面是 i n i t t a b文件的一部分。 该文件的第一行是系统缺省的运行级别,这里是级别 3,一般都是这样。 以数字1 0到1 6开始的行启动或停止该运行级别所对应的全部运行级别脚本。例如,该文 件中有这样一行: 15:5:wait:/etc/rc.d/rc 5 它的意思是,在运行级别 5应该以参数5执行脚本/ e t c / r c . d / r c,即/ e t c / r c . d / r c执行/ e t c / r c . d / r c 5 . d 目录中的所有脚本。 在上述文件的最后一行,在运行级别 2、3、4和5,该进程将会始终存在,即使暂时掉下 来,大概也不会超过 1 s。这一始终存在的进程是串口 t t y S 1上的m i n g e t t y。该命令含有一个参 数,即终端类型为v t 1 0 0。 28.4 运行级别 i n i t进程在系统完全就绪之前所做的最后几项工作之一就是执行缺省运行级别所包含的所 有脚本。该进程是通过 / e t c / r c . d / r c或/ e t c / r c . i n i t来启动这些脚本的。它的作用是首先杀死该运 行级别所包含的进程再启动这些进程。 但是它怎么知道该启动或停止哪些服务呢? r c或r c . i n i t脚本将会使用f o r循环来依次查看相 应运行级别目录中的文件,给每一个链接名以 K开头的相应脚本赋予参数s t o p; 给每一个链接 名以S开头的相应脚本赋予参数 s t a r t。在运行级别切换时,上述脚本也会完成同样的工作,只 不过根据相应的运行级别来启动或停止对应的脚本。 r c N . d目录中的脚本只是一些链接—真正的脚本保存在其他的目录中。它们通常都放置 320 第五部分 高级s h e l l编程技巧 下载 在/ u s r / s b i n / i n i t . d或/ e t c / i n i t . d目录中。 如果是L I N U X系统,那么⋯⋯ / e t c / r c . d / i n i t . d 在这个目录中含有一些能够启动或停止某一服务的脚本。这些脚本的名字最好能够表示 出它所实现的功能,形如 r c . <功能>,其中r c表示运行命令(run command)或运行控制(r u n c o n t r o l),或者就像某些系统管理员所称的那样“真正关键的”(‘real crucial’)。 下面是这类文件的部分列表。 一般来说,r c . s c r i p t s都应当能够接受这样的参数: rc.name stop:停止该服务。 rc.name start:启动该服务。 可选的参数包括 r e s t a r t和s t a t u s。其他任何参数都应当给出相应的用法说明。注意,可以 手工运行这些脚本。 现在我们已经知道运行级别脚本应当具有什么样的功能,下一步就是把它们放置在相应 的r c N . d目录中。不过在此之前我们先来了解一下系统运行级别。 28.4.1 各种运行级别 系统含有七种运行级别(见表2 8 - 1 )。不同的系统在某些运行级别上稍有差别。 在将一个脚本放置在不同的运行级别目录中之前,首先应当弄清楚打算在哪一个运行级 别启动或停止相应的服务?一旦弄清楚这一点,就可以接着进行下面的步骤了。 表28-1 各个运行级别的用途 运行级别0 启动和停止整个系统 运行级别1 单用户或管理模式 运行级别2 多用户模式;部分网络服务被启动。有些系统将其作为正常运行模式,而不是级别 3 运行级别3 正常操作运行模式,启动所有的网络服务 运行级别4 用户定义的模式,可以使用该级别来定制所需要运行的服务 运行级别5 有些U N I X操作系统变体将其作为缺省 X - w i n d o w s模式,还有些系统把它作为系统维护模式 运行级别6 重启动 28.4.2 运行级别脚本的格式 r c N . d目录中的脚本都是一些链接,这样是为了省去不必要的副本。这些链接的格式为: S n n n . s c r i p t _ n a m e 或 K n n n . s c r i p t _ n a m e 其中, S:代表启动相应的进程 K:代表杀死相应的进程 n n:是0 0至9 9的两位数字,不过在有些系统中是 0 0 0至9 9 9三位数字。在不同目录中的链 第28章 运行级别脚本 321下载 接应采用同一数字。例如,如果某个服务在 r c 3 . d中启动时名为S 4 5 . m y s c r i p t,那么如果希望它 在r c 2 . d中启动,应当使用链接名S 4 5 . m y s c r i p t。 s c r i p t _ n a m e:相应脚本的文件名,根据所在操作系统的不同,它们可能位于下列目录中: / u s r / s b i n / i n i t . d / e t c / r c . d / e t c / i n i t . d 当i n i t 进程调用相应的运行级别脚本时,杀进程按照从高到低的 K 序号进行,即 K23,myscript K12.named;而启动进程按照从低到高的序号进行。如果使用的是 L I N U X系统, K序号将按照从高到低的顺序执行。 28.4.3 安装运行级别脚本 如果想要安装自己的运行级别脚本,必须: • 编写该脚本,确保它符合调用标准。 • 确信它能够启动或终止相应的服务。 • 将该脚本放置于(取决于操作系统) / e t c / i n i t . d或/ u s r / s b i n / i n i t . d或/ e t c / r c . d中。 • 在相应的r c N . d目录中按照合理的命名方式创建链接。 下面的脚本能够启动或停止一个名为 r c . a u d i t的审核应用程序。该服务运行于级别 3、5、4, 停止于级别 6、2、1。通过查看 r c N . d中的条目,我们发现序号 3 5空闲,于是就使用该序号。 实际上,系统并不对使用已占用的序号作任何检查。 下面就是这个脚本。可以看到,该脚本使用了一个简单的 c a s e语句来接收s t a r t和s t o p参数。 322 第五部分 高级s h e l l编程技巧 下载 如果是LINUX系统,那么⋯⋯ 有些L I N U X变体在启动服务时要求创建一个锁文件。如果没有锁文件,在杀死该脚本 时就可能会出现问题。 s t a r t选项将使该审核进程启动相应的审核系统,而 s t o p选项将使它终止该系统的运行。当 然,在将自己的运行级别脚本放置在 i n i t . d目录中之前,应该首先对该脚本进行测试。 让我们假定该脚本已经通过了测试。它能够正确地启动和停止审核服务。现在我们把该 脚本放置在相应的运行级别目录中。 在本系统中,r c N . d目录位于/ e t c / r c . d目录下,而我的运行级别脚本保存在 / e t c / r c . d / i n i t . d目 录下。如果系统目录结构与上面的不同,那么需要对下面的命令作相应的调整。 我们首先启动该脚本—记住启动脚本所使用的链接名是以 S打头的。 我们已经创建了相应的链接。 ls -l命令的结果显示该链接指向 / e t c / i n i t . d / r c . a u d i t文件。我 本应该在链接命令中给出全路径,不过没有这个必要。现在我只要进入其他的相关目录 ( r c 4 . d 和r c 5 . d )使用同样的命令就可以启动其他相应的服务。 如果希望停止某个脚本的运行,可以使用如下命令: 在其他相关目录中,也可以如法炮制,停止相应的审核服务。现在当系统重启动时 (运行 级别6 ),它将被停止;在运行级别切换到 2或1时也是如此。该服务在运行级别 4或5中同样也 会被启动。 28.5 使用inittab来启动应用程序 我们还可以用其他的方法来启动应用程序。可以通过在 i n i t t a b文件中加入相应的条目来做 到这一点。在我所管理的有些系统上,我就使用了这种方法,这倒不是因为这些系统中没有 运行级别目录,而是由于我有几个用于系统检查的脚本需要在系统刚刚就绪之后运行。使用 i n i t t a b是实现上述功能的理想途径。 第28章 运行级别脚本 323下载 这里我们给出一个例子,我打算在系统运行在级别 3时运行我的一个磁盘镜像检查脚本。 首先我确定该脚本能够正确运行,然后对 i n i t t a b文件做备份。 $ cp /etc/initab /etc/inittab.bak 接下来编辑i n i t t a b文件,在该文件末尾加入这样一个条目: 保存并退出。 上面的一条意思是: 行首的r c . d i s k c h e c k e r是该进程在运行级别3中的唯一标识。该进程只运行一次。所要运行 的脚本是/ u s r / l o c a l / e t c / r c . d i s k c h e c k e r,所有的输出都被送到控制台。 28.6 启动和停止服务的其他方法 如果不想把/ e t c / i n i t t a b文件弄得过于杂乱,还有其他的方法可以实现启动和停止服务的功 能。大多数系统都含有一个名为 r c . l o c a l的文件,一般来说也是位于 / e t c目录下。该脚本文件将 在i n i t t a b和运行级别脚本之后运行。可以在该文件中加入任何命令,或从中调用最习惯用的启 动脚本。 有些系统还在/ b i n目录下(更多的是在/ u s r / s b i n目录下)含有一个名为s h u t d o w n的脚本文件。 可以使用它来关闭某些服务。 28.7 小结 运行级别的确是一个系统管理问题。本章的目的在于:使你了解在系统启动时,如何按 照需求灵活地控制各种服务和脚本的启动。 这还意味着在系统重启动时,能够手工启动和停止某些服务。 324 第五部分 高级s h e l l编程技巧 下载 下载 第29章 cgi 脚本 现在差不多每个人的 P C上都安装了We b服务器,在这样一本关于 s h e l l编程的书中似乎很 有必要包含一章关于c g i脚本的内容。 本章包含以下内容: • 基本c g i脚本。 • 使用服务器端内嵌(Server Side Includes,SSI)。 • get方法。 • post方法。 • 创建交互式脚本。 • 能够自动重载We b页面的c g i脚本。 运行We b服务器并不一定需要有网络环境,可以在本地主机上运行它。这里,我们假定 你已经安装了 We b服务器( a p a c h e、C e r n等等)以及浏览器( N e t s c a p e、Internet Explorer等等)。 另外,该服务器应当允许运行 c g i脚本。一般来说缺省值是禁止运行 c g i脚本的,要运行,只要 将配置文件中相应的一行注释掉即可。后面我们会更详细地讨论这一问题。 如何安装并配置 We b服务器已经超出了本书的讨论范围,不过我认为只需 2 0分钟就可以 安装并运行一个 We b服务器。本章中的例子运行于 apache We b服务器下,我所使用的浏览器 为N e t s c a p e。 本章不打算深入探讨有关 H T M L或We b的细节问题,因为市面上已经有大量关于这方面的 书籍。另外,如果要深入探讨 H T M L的话,还要花费数章的笔墨。 29.1 什么是Web页面? We b页面或文挡是包含有H T M L标记的文件。当浏览器连接到一个 We b页面上时,浏览器 就会根据相应的H T M L标记来显示该页面。We b页面中可以含有非常丰富的信息,它可以包含 指向其他页面的链接、各种色彩、高亮标题、各种字体、直线、表格,还可以包含图像和声 音。 We b页面可以分为两类:动态的页面和静态的页面。静态的页面是用于显示信息或下载 文件。而动态的页面是交互型的,它们可以按照你所提供的信息产生相应的结果。动态页面 还可以用于显示实时变化的信息,如股票价格,或用于完成某些监视任务。如果想要执行这 种类型的处理,就需要编写脚本。 如果一个We b服务器能够交换信息脚本,那么它必须支持一种被称为公共网关接口的协 议,即大家所熟悉的c g i。 29.2 cgi c g i是一种规范,它规定了获取信息的脚本如何从服务器中取得信息或向服务器中写入信 息。这种脚本或 c g i脚本可以用任何语言来实现。最为流行的是 P e r l语言,不过你将会发现, 也可以用普通的s h e l l脚本来实现(见图2 9 - 1)。 图29-1 浏览器和服务器可以通过cgi来交换信息 29.3 连接Web服务器 可以使用统一资源定位符( U R L )连接We b服务器。U R L包含两部分信息: • 协议。 • 地址和数据。 其中,协议包括h t t p、f t p、m a i l t o、f i l e、t e l n e t和n e w s。这里我们只关心 h t t p协议(超文本 传输协议)。 地址一般是D N S域名或服务器主机名,也可以是 I P地址。其他数据可以是你所要访问文 件的实际路径名。 所有的连接都基于T C P协议之上,缺省的端口号为 8 0。 如果We b服务器在你的本地主机上,而相应的主页为 i n d e x . h t m l,那么可以使用下面的 U R L: h t t p : / / l o c a l h o s t / i n d e x . h t m l 一般来说, i n d e x . h t m l是缺省下载的文件,即该页面是你的 We b服务器的缺省页。这样, 你可以只输入如下的U R L: h t t p : / / l o c a l h o s t / 29.4 cgi和HTM脚本 当浏览器发出下载页面的请求时, We b服务器将会对收到的 U R L进行分析。如果其中含 有c g i - b i n,服务器将打开一个连接,通常是连接相应 c g i脚本的管道。该c g i脚本所有的输入输 出都将通过该管道。如果该 c g i脚本用于显示 We b页面,那么它的输出中必须要包含必要的 H T M L标记,这样该页面才能够按照服务器所能够理解的格式被显示出来,因此我们有必要 了解一些H T M L的知识。We b服务器将该页面返回给发出请求的浏览器显示出来。表 2 9 - 1列出 了一些常用的H T M L标记。 29.4.1 基本cgi脚本 所有的c g i脚本都应当位于 We b服务器的c g i - b i n目录中,不过在不同的服务器中该目录会 有所不同。可以通过查看配置文件 s r m . c o n f中S c r i p t A l i a s一段来改变该目录的位置,并允许该 服务器运行 c g i脚本。所有的脚本文件名都应以 . c g i做后缀。而其他 We b页面都位于 h t m l或 h t d o c s目录下,并且带有. h t m l后缀。所有的脚本都应具有这样的权限。 chmod 755 script.cgi 所有We b页面连接的缺省用户身份为 n o b o d y,不过可以通过配置 h t t p d . c o n f文件来改变这 326 第五部分 高级s h e l l编程技巧 下载 用户使用浏览器连接 Web页面 Web服务器 (apache?) cgi脚本获取信息,进行处 理后返回结果 一设置。尽管我曾经说过本章并不是关于 We b服务器配置的,不过正好可以借这个机会检查 一下p a s s w d文件,看看n o b o d y用户的登录是否被禁止。如果想防止任何用户以 n o b o d y的身份 从某一个物理端口上登录,只要在 / e t c / p a s s w d文件中该用户口令域的第一个字符前面加入一 个星号*即可。 表29-1 基本H T M L标记 < H T M L > < / H T M L > H T M L文挡的开头和结束 < H E A D > < / H E A D > 标题部分的开头和结束 < T I T L E > < / T I T L E > 主题的开头和结束 < B O D Y > < / B O D Y > 页面部分的开头和结束 < H n > < / H n > 不同层次的标题,1代表最大的字体 < P > < / P > 段落的开头和结束 < B R > 换行 < H R > 水平线 < P R E > < / P R E > 预定义格式文本的开头和结束,其中的所有跳格、行都保持原样。 < B > < / B > 黑体 < I > < / I > 斜体 < O L > < / O L > 有序号的列表 link 超文本链接 < F O R M > < / F O R M > 表单 M E T H O D p o s t或g e t方法 A C T I O N 地址 < I N P U T. . . > 数据输入 N A M E 变量名 S I Z E 以字符计的文本框的宽度 T Y P E 复选框、单选框、复位、提交 < S E L E C T. . . > 下拉式菜单 N A M E 变量名 S I Z E 要显示的列表的项数
还剩345页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

ericaz

贡献于2017-03-31

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf