Lyon

个人博客

授人以鱼,亦授人以渔.


Linux Shell编程笔记 第一部分 shell

第一部分 shell

[TOC]

第1章 文件安全与权限

1.1 文件

当你创建一个文件的时候,系统保存了有关该文件的全部信息,包括:

•文件的位置。
•文件类型。
•文件长度。
•哪位用户拥有该文件,哪些用户可以访问该文件。
•节点。
•文件的修改时间。
•文件的权限位。

1.2 文件类型

d 目录
l 符号链接(指向另一个文件)
s 套接字文件
b 块设备文件
c 字符设备文件
p 命名管道文件
- 普通文件,或者更准确地说,不属于以上几种类型的文件。

1.3 权限

(第一个字符) - 普通文件 (接下来的三个字符) r w - 文件属主的权限 (再接下来的三个字符) r- - 同组用户的权限 (最后三个字符) r- - 其他用户的权限 因此,这三组字符(除了第一个字符)分别定义了:

1) 文件属主所拥有的权限。
2) 文件属主缺省组(一个用户可以属于很多的组)所拥有的权限。
3) 系统中其他用户的权限。

在每一组字符中含有三个权限位: r 读权限 w 写/更改权限 x 执行该脚本或程序的权限

1.4 改变权限位

1.4.1 符号模式

chmod命令的一般格式为:

chmod [who] operator [permission] filename

who的含义是:

u 文件属主权限。
g 同组用户权限。
o 其他用户权限。
a 所有用户(文件属主、同组用户及其他用户)。

operator的含义:

+ 增加权限。
- 取消权限。
= 设定权限。

permission的含义:

r 读权限。
w 写权限。
x 执行权限。
s 文件属主和组s e t - I D。
t 粘性位*。
l 给文件加锁,使其他用户无法访问。
u,g,o 针对文件属主、同组用户及其他用户的操作。
*在列文件或目录时,有时会遇到“ t”位。“t”代表了粘性位。如果在一个目录上出现
“t”位,这就意味着该目录中的文件只有其属主才可以删除,即使某个同组用户具有和属主
同等的权限。不过有的系统在这一规则上并不十分严格。
如果在文件列表时看到“ t”,那么这就意味着该脚本或程序在执行时会被放在交换区(虚
存)。不过由于当今的内存价格如此之低,大可不必理会文件的“ t”的使用。

1.4.2 chmod命令举例

命令结果含义

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 赋予同组用户和其他用户执行权限	

如果这是我写的一个脚本,我希望能够具有执行权限,并取消其他用户(所有其他用户) 的写权限,可以用:

$ chmod u+x o-w myfile

1.4.3 绝对模式

chmod命令绝对模式的一般形式为:

chmod [mode] file

其中mode是一个八进制数。 在绝对模式中,权限部分有着不同的含义。每一个权限位用一个八进制数来代表, 如表1 - 3所示。 表1-3 八进制目录/文件权限表示

八进制数 含义 八进制数 含义
0400 文件属主可读 0010 同组用户可执行
0200 文件属主可写 0004 其他用户可读
0100 文件属主可执行 0002 其他用户可写
0040 同组用户可读 0001 其他用户可执行
0020 同组用户可写    

在设定权限的时候,只需按照表1 - 3查出与文件属主、同组用户和其他用户所具有的权限 相对应的数字,并把它们加起来,就是相应的权限表示。 从表1 - 3中可以看出,文件属主、同组用户和其他用户分别所能够具有的最大权限值就是7。 相应的权限表示应为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 计算权限值 文件属主同组用户其他用户

r w x r w x r w x
4 + 2 + 1 4 + 2 + 1 4 + 2 + 1

1.4.4 chmod命令的其他例子

命令结果含义

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- - 赋予所有用户读权限

还可以通过使用- R选项连同子目录下的文件一起设置,- R选项一定要谨慎,只有在 需要改变目录树下全部文件权限时才可以使用。

**1.4.5 可以选择使用符号模式或绝对模式 **

上面的例子中既有绝对模式的,也有符号模式的,我们可以从中看出,如果使用该命令 的符号模式,可以设置或取消个别权限位,而在绝对模式中则不然。我个人倾向于使用符号 模式,因为它比绝对模式方便快捷。

1.5 目录

目录的权限位和文件有所不同。现在我们来看看其 中的区别。目录的读权限位意味着可以列出其中的内容。写权限位意味着可以在该目录中创 建文件,如果不希望其他用户在你的目录中创建文件,可以取消相应的写权限位。执行权限 位则意味着搜索和访问该目录(见表1 - 5、表1 - 6) 表1-5 目录权限 r w x 可以列出该目录中的文件 可以在该目录中创建或删除文件 可以搜索或进入该目录 表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,那么他们将无法列出该目录 中的文件。如果该目录中有一个执行位置位的脚本或程序,只要用户知道它的路径和文件名, 仍然可以执行它。用户不能够进入该目录并不妨碍他的执行。 目录的权限将会覆盖该目录中文件的权限。

1.6 suid/guid

suid意味着如果某个用户对属于自己的shell脚本设置了这种权限,那么其他用户在执行这 一脚本时也会具有其属主的相应权限。于是,如果根用户的某一个脚本设置了这样的权限, 那么其他普通用户在执行它的期间也同样具有根用户的权限。同样的原则也适用于guid,执 行相应脚本的用户将具有该文件所属用户组中用户的权限。

1.6.1 为什么要使用suid/guid

为什么要使用这种类型的脚本?这里有一个很好的例子。我管理着几个大型的数据库系 统,而对它们进行备份需要有系统管理权限。我写了几个脚本,并设置了它们的g u i d,这样 我指定的一些用户只要执行这些脚本就能够完成相应的工作,而无须以数据库管理员的身份 登录,以免不小心破坏了数据库服务器。通过执行这些脚本,他们可以完成数据库备份及其 他管理任务,但是在这些脚本运行结束之后,他们就又回复到他们作为普通用户的权限。 有相当一些UNIX命令也设置了suid和guid。如果想找出这些命令,可以进入/bin或/sbin目 录,执行下面的命令:

$ ls -l | grep '^...s'
上面的命令是用来查找s u i d文件的;
$ ls -l | grep '^...s..s'
上面的命令是用来查找s u i d和g u i d的。

如果希望设置 suid,那么就将相应的权限位之前的那一位设置为4;如果希望设置guid,那么就将相应的权限 位之前的那一位设置为2;如果希望两者都置位,那么将相应的权限位之前的那一位设置为4+2。 一旦设置了这一位,一个s将出现在x的位置上。记住:在设置suid或guid的同时,相应的 执行权限位必须要被设置。例如,如果希望设置guid,那么必须要让该用户组具有执行权限。 如果想要对文件login设置suid,它当前所具有的权限为rwx rw- r– (741),需要在使用 chmod命令时在该权限数字的前面加上一个4,即chmod 4741,这将使该文件的权限变为r w s rw- r - -。

$ chmod 4741 logit

1.6.2 设置suid/guid的例子

表1-7 设置suid/guid

命令 结果 含义
chmod 4755 rws r-x r- x 文文件被设置了suid,文件属主具有读、写和执行的权限,所有其他用户具有读和执行的权限
chmod 6711 rws –s –s 文文件被设置了suid和guid,文件属主具有读、写和执行的权限,所有其他用户具有执行的权限
chmod 4764 rws rw- r- - 文文件被设置了suid,文件属主具有读、写和执行的权限,同组用户具有读和执行的权限,其他用户具有读权限

还可以使用符号方式来设置suid/guid。如果某个文件具有这样的权限: rwx r-x r- x,那么 可以这样设置其suid:

chmod u+s <filename>

于是该文件的权限将变为: rws r-x r-x 在查找设置了suid的文件时,没准会看到具有这样权限的文件:rwS r-x r- x,其中S为大写。 它表示相应的执行权限位并未被设置,这是一种没有什么用处的suid设置,可以忽略它的存在。 注意,chmod命令不进行必要的完整性检查,可以给某一个没用的文件赋予任何权限,但 chmod 命令并不会对所设置的权限组合做什么检查。 因此,不要看到一个文件具有执行权限,就认为它一定是一个程序或脚本。

1.7 chown和chgrp

当你创建一个文件时,你就是该文件的属主。一旦你拥有某个文件,就可以改变它的所 有权,把它的所有权交给另外一个/etc/passwd文件中存在的合法用户。可以使用用户名或用 户I D号来完成这一操作。在改变一个文件的所有权时,相应的suid也将被清除,这是出于安 全性的考虑。只有文件的属主和系统管理员可以改变文件的所有权。一旦将文件的所有权交 给另外一个用户,就无法再重新收回它的所有权。如果真的需要这样做,那么就只有求助于 系统管理员了。 chown命令的一般形式为:

chmod -R -h owner file

-R选项意味着对所有子目录下的文件也都进行同样的操作。- h选项意味着在改变符号链 接文件的属主时不影响该链接所指向的目标文件。

1.7.1 chown举例

chown user project 文件project的所有权现在由原用户交给了用户user

1.7.2 chgrp举例

chgrp user project 把该文件所属的组现在由原用户组变为user组

1.7.3 找出你所属于的用户组

如果你希望知道自己属于哪些用户组,可以用如下的命令: 使用id或者group

1.7.4 找出其他用户所属于的组

使用group username

1.8 umask

当最初登录到系统中时, umask命令确定了你创建文件的缺省模式。这一命令实际上和 chmod命令正好相反。你的系统管理员必须要为你设置一个合理的u mask值,以确保你创建的 文件具有所希望的缺省权限,防止其他非同组用户对你的文件具有写权限。 在已经登录之后,可以按照个人的偏好使用u mask命令来改变文件创建的缺省权限。相应 的改变直到退出该shell或使用另外的umask命令之前一直有效。 一般来说,umask命令是在/etc/profile文件中设置的,每个用户在登录时都会引用这个文 件,所以如果希望改变所有用户的umask,可以在该文件中加入相应的条目。如果希望永久性 地设置自己的umask值,那么就把它放在自己$HOME目录下的. profile或. bashprofile文件中。

1.8.1 如何计算umask值

umask命令允许你设定文件创建时的缺省模式,对应每一类用户(文件属主、同组用户、 其他用户)存在一个相应的umask值中的数字。对于文件来说,这一数字的最大值分别是6。系 统不允许你在创建一个文本文件时就赋予它执行权限,必须在创建后用chmod命令增加这一 权限。目录则允许设置执行权限,这样针对目录来说, umask中各个数字最大可以到7。 该命令的一般形式为:

umask nnn

其中nnn为umask值000-777。 让我们来看一些例子。 计算出你的umask值: 可以有几种计算umask值的方法,通过设置umask值,可以为新创建的文件和目录设置缺 省权限表1-8 umask值与权限。 表1 - 8列出了与权限位相对应的umask值。

umask 文件 目录
0 6 7
1 6 6
2 4 5
3 4 4
4 2 3
5 2 2
6 0 1
7 0 0

在计算umask值时,可以针对各类用户分别在这张表中按照所需要的文件/目录创建缺省 权限查找对应的umask值。 例如,umask值002 所对应的文件和目录创建缺省权限分别为664和775。 还有另外一种计算umask值的方法。我们只要记住umask是从权限中“拿走”相应的位即 可。 例如,对于umask值002,相应的文件和目录缺省创建权限是什么呢? 第一步,我们首先写下具有全部权限的模式,即777 (所有用户都具有读、写和执行权限)。 第二步,在下面一行按照umask值写下相应的位,在本例中是002。 第三步,在接下来的一行中记下上面两行中没有匹配的位。这就是目录的缺省创建权限。 稍加练习就能够记住这种方法。 第四步,对于文件来说,在创建时不能具有文件权限,只要拿掉相应的执行权限比特即 可。 这就是上面的例子,其中umask值为002:

1) 文件的最大权限rwx rwx rwx (777)
2) umask值为0 0 2 - - - - - - -w-
3) 目录权限rwx rwx r-x (775) 这就是目录创建缺省权限
4) 文件权限rw- rw- r-- (664) 这就是文件创建缺省权限

下面是另外一个例子,假设这次umask值为022:

1) 文件的最大权限rwx rwx rwx (777)
2) umask值为0 2 2 - - - -w- -w-
3) 目录权限rwx r-x r-x (755) 这就是目录创建缺省权限
4) 文件权限rw- r-- r-- (644) 这就是文件创建缺省权限

1.8.2 常用的umask值

表1-9 常用的umask值及对应的文件和目录权限

umask值 目录 文件
022 755 644
027 750 640
002 775 664
006 771 660
007 770 660

如果想知道当前的umask 值,可以使用umask命令 如果想要改变umask值,只要使用umask命令设置一个新的值即可 在使用umask命令之前一定要弄清楚到底希望具有什么样的文件/目录创建缺省权限。否 则可能会得到一些非常奇怪的结果;例如,如果将umask值设置为600,那么所创建的文件/目 录的缺省权限就是066!

1.9 符号链接

存在两种不同类型的链接,软链接和硬链接,这里我们只讨论软链接。软链接实际上就 是一个指向文件的指针。你将会发现这种软链接使用起来非常方便。

1.9.1 使用软链接来保存文件的多个映像

该命令的一般形式为:

ln [-s] source_path target_path

其中的路径可以是目录也可以是文件。

1.9.2 符号链接举例

不管是否在同一个文件系统中,都可以创建链接。在创建链接的时候,不要忘记在原有 目录设置执行权限。链接一旦创建,链接目录将具有权限777或rwx rwx rwx,但是实际的原 有文件的权限并未改变。

第2章 使用find和xargs

Find命令的一般形式为:

find pathname -options [-print -exec -ok]

让我们来看看该命令的参数:

pathname find命令所查找的目录路径。例如用.来表示当前目录,用/来表示系统根目录。
-print find命令将匹配的文件输出到标准输出。
-exec find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为'comm-and'{}\;,注意{}和\;之间的空格。
-ok 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。

2.1 find命令选项

Find命令有很多选项或表达式,每一个选项前面跟随一个横杠-。让我们先来看一下该命 令的主要选项,然后再给出一些例子。

-name 按照文件名查找文件。
-perm 按照文件权限来查找文件。
-prune 使用这一选项可以使f i n d命令不在当前指定的目录中查找,如果同时使用了-depth选项,那么-prune选项将被find命令忽略。
-user 按照文件属主来查找文件。
-group 按照文件所属的组来查找文件。
-mtime -n +n 按照文件的更改时间来查找文件, -n表示文件更改时间距现在n天以内,+n表示文件更改时间距现在n天以前。Find命令还有-atime和-ctime选项,但它们都和-mtime选项相似,所以我们在这里只介绍-mtime选项。
-nogroup 查找无有效所属组的文件,即该文件所属的组在/etc/groups中不存在。
-nouser 查找无有效属主的文件,即该文件的属主在/etc/passwd中不存在。
-newer file1 ! file2 查找更改时间比文件file1新但比文件file2旧的文件。
-type 查找某一类型的文件,诸如:
       b - 块设备文件。
       d - 目录。
       c - 字符设备文件。
       p - 管道文件。
       l - 符号链接文件。
       f - 普通文件。
-size n[c] 查找文件长度为n块的文件,带有c时表示文件长度以字节计。
-depth 在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找。
-fstype 查找位于某一类型文件系统中的文件,这些文件系统类型通常可以在配置文件/etc/fstab中找到,该配置文件中包含了本系统中有关文件系统的信息。
-mount 在查找文件时不跨越文件系统mount点。
-follow 如果find命令遇到符号链接文件,就跟踪至链接所指向的文件。
-cpio 对匹配的文件使用cpio命令,将这些文件备份到磁带设备中。

2.1.1 使用name选项

文件名选项是find命令最常用的选项,要么单独使用该选项,要么和其他选项一起使用。 可以使用某种文件名模式来匹配文件,记住要用引号将文件名模式引起来。

不管当前路径是什么,如果想要在自己的根目录$HOME中查找文件名符合*.txt的文件, 使用~作为’pathname参数,波浪号~代表了你的$HOME目录。

$ 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

想要查找$HOME目录中的文件,可以用:

$ find ~ -name "*" -pri或ntf ind . -print
$ find / -name "*" -print

如果想在当前目录查找文件名以两个小写字母开头,跟着是两个数字,最后是*.txt的文 件,下面的命令就能够返回名为ax37.txt的文件:

$ find . -name "[a-z][a-z][0--9][0--9].txt" -print

2.1.2 使用perm选项

如果希望按照文件权限模式来查找文件的话,可以采用-perm选项。你可能需要找到所有 用户都具有执行权限的文件,或是希望查看某个用户目录下的文件权限类型。在使用这一选 项的时候,最好使用八进制的权限表示法。 为了在当前目录下查找文件权限位为755的文件,即文件属主可以读、写、执行,其他用 户可以读、执行的文件,可以用:

$ find . -perm 755 -print

如果希望在当前目录下查找所有用户都可读、写、执行的文件(要小心这种情况),我们 可以使用find命令的-perm选项。在八进制数字前面要加一个横杠-。在下面的命令中-perm代 表按照文件权限查找,而‘ 007’和你在chmod命令的绝对模式中所采用的表示法完全相同。

$ find . -perm -007 -print

2.1.3 忽略某个目录

如果在查找文件时希望忽略某个目录,因为你知道那个目录中没有你所要查找的文件,那么可以使用-prune选项来指出需要忽略的目录。 在使用-prune选项时要当心,因为如果你同时使用了-depth选项,那么-prune选项就会被find命令忽略。 如果希望在/apps目录下查找文件,但不希望在/apps/bin目录下查找,可以用:

$ find /apps -name "/apps/bin" -prune -o -print

2.1.4 使用user和nouser选项

如果希望按照文件属主查找文件,可以给出相应的用户名。

例如, 在$HOME目录中查找文件属主为lyon的文件,可以用:

$ find ~ -user lyon -print

在/etc目录下查找文件属主为uucp的文件:

$ find /etc -user uucp -print

为了查找属主帐户已经被删除的文件,可以使用-nouser选项。 这样就能够找到那些属主在/etc/passwd文件中没有有效帐户的文件。 在使用-nouser选项时,不必给出用户名find命令能够为你完成相应的工作。

例如,希望在/home目录下查找所有的这类文件,可以用:

$ find /home -nouser -print

2.1.5 使用group和nogroup选项

就像user和nouser选项一样,针对文件所属于的用户组, find命令也具有同样的选项,为 了在/apps目录下查找属于accts用户组的文件,可以用:

$ find /apps -group accts -print

要查找没有有效所属用户组的所有文件,可以使用nogroup选项。下面的find命令从文件 系统的根目录处查找这样的文件

$ fine/-nogroup-print

2.1.6 按照更改时间查找文件

如果希望按照更改时间来查找文件,可以使用mtime选项。如果系统突然没有可用空间了, 很有可能某一个文件的长度在此期间增长迅速,这时就可以用mtime选项来查找这样的文件。 用减号-来限定更改时间在距今n日以内的文件,而用加号+来限定更改时间在距今n日以前的 文件。

希望在系统根目录下查找更改时间在5日以内的文件,可以用:

$ find / -mtime -5 -print

为了在/var/adm目录下查找更改时间在3日以前的文件,可以用:

$ find /var/adm -mtime +3 -print

2.1.7 查找比某个文件新或旧的文件

如果希望查找更改时间比某个文件新但比另一个文件旧的所有文件,可以使用- newer选 项。它的一般形式为:

newest_file_name ! oldest_file_name

其中,!是逻辑非符号。

下面给出的find命令能够查找更改时间比文件file1. awk新但比文件file2.awk旧的文件:

find .-newer file1.awk ! -newer file2.awk -exec ls -l {} \;

如果想使用find命令的这一选项来查找更改时间在两个小时以内的文件,除非有一个现成 的文件其更改时间恰好在两个小时以前,否则就没有可用来比较更改时间的文件。为了解决 这一问题,可以首先创建一个文件并将其日期和时间戳设置为所需要的时间。这可以用touch 命令来实现。 假设现在的时间是23:40,希望查找更改时间在两个小时以内的文件,可以首先创建这样 一个文件:

touch -t 05042150 file
ls -l file
find . -newer file -print

2.1.8 使用type选项

如果要在/etc目录下查找所有的目录,可以用:

$ find /etc -type d -print

为了在当前目录下查找除目录以外的所有类型的文件,可以用:

$ find . ! -type d -print

为了在/etc目录下查找所有的符号链接文件,可以用:

$ find /etc -type l -print

2.1.9 使用size选项

可以按照文件长度来查找文件,这里所指的文件长度既可以用块(block)来计量,也可 以用字节来计量。以字节计量文件长度的表达形式为Nc;以块计量文件长度只用数字表示即 可。 就我个人而言,我总是使用以字节计的方式,在按照文件长度查找文件时,大多数人都 喜欢使用这种以字节表示的文件长度,而不用块的数目来表示,除非是在查看文件系统的大 小,因为这时使用块来计量更容易转换。

为了在当前目录下查找文件长度大于1 M字节的文件,可以用:

$ find . -size +1000000c -print

为了在/home/apache目录下查找文件长度恰好为100字节的文件,可以用:

$ find /home/apache -size 100c -print

为了在当前目录下查找长度超过10块的文件(一块等于512字节),可以用:

$ find . -size +10 -print

2.1.10 使用depth选项

在使用find命令时,可能希望先匹配所有的文件,再在子目录中查找。使用depth选项就 可以使find命令这样做。 这样做的一个原因就是,当在使用find命令向磁带上备份文件系统时, 希望首先备份所有的文件,其次再备份子目录中的文件。 在下面的例子中, find命令从文件系统的根目录开始,查找一个名为CON.FILE的文件。 它将首先匹配所有的文件然后再进入子目录中查找。

$ find / -name "CON.FILE" -depth -print

2.1.11 使用mount选项

在当前的文件系统中查找文件(不进入其他文件系统),可以使用find命令的mount选项。 在下面的例子中,我们从当前目录开始查找位于本文件系统中文件名以XC结尾的文件:

$ find . -name "*.XC" -mount -print

2.1.12 使用cpio选项

cpio命令可以用来向磁带设备备份文件或从中恢复文件。可以使用find命令在整个文件系 统中(更多的情况下是在部分文件系统中)查找文件,然后用cpio命令将其备份到磁带上。 如果希望使用cpio命令备份/etc/home和/apps目录中的文件,可以使用下面所给出的命 令,不过要记住你是在文件系统的根目录下:

$ cd /
$ find etc home apps -depth -print | cpio -ivcdC65536 -o \
/dev/rmt0

(在上面的例子中,第一行末尾的\告诉shell命令还未结束,忽略\后面的回车) 在上面的例子中,应当注意到路径中缺少/。这叫作相对路径。之所以使用相对路径,是 因为在从磁带中恢复这些文件的时候,可以选择恢复文件的路径。例如,可以将这些文件先 恢复到另外一个目录中,对它们进行某些操作后,再恢复到原始目录中。如果在备份时使用 了绝对路径, 例如/etc,那么在恢复时,就只能恢复到/etc目录中去,别无其他选择。在上面 的例子中,我告诉find命令首先进入/etc目录,然后是/home和/apps目录,先匹配这些目录下 的文件,然后再匹配其子目录中的文件,所有这些结果将通过管道传递给cpio命令进行备份。 顺便说一下,在上面的例子中cpio命令使用了C65536选项,我本可以使用B选项,不过这 样每块的大小只有512 字节,而使用了C65536 选项后,块的大小变成了64K字节 (65536/1024)。

2.1.13 使用exec或ok来执行shell命令

当匹配到一些文件以后,可能希望对其进行某些操作,这时就可以使用-exec选项。一旦 find命令匹配到了相应的文件,就可以用-exec选项中的命令对其进行操作(在有些操作系统 中只允许-exec选项执行诸如ls或ls -l这样的命令)。大多数用户使用这一选项是为了查找旧文 件并删除它们。这里我强烈地建议你在真正执行rm命令删除文件之前,最好先用ls命令看一 下,确认它们是所要删除的文件。

exec选项后面跟随着所要执行的命令,然后是一对儿{},一个空格和一个\,最后是一个分号。 为了使用exec选项,必须要同时使用prin t选项。如果验证一下find命令,会发现该命令只 输出从当前路径起的相对路径及文件名。

为了用ls -l命令列出所匹配到的文件,可以把ls -l命令放在find命令的-exec选项中,例如:

$ find . -type f -exec  ls -l {} \;

上面的例子中,find命令匹配到了当前目录下的所有普通文件,并在-exec选项中使用ls -l 命令将它们列出。 为了在/logs目录中查找更改时间在5日以前的文件并删除它们,可以用:

$ find logs -type f -mtime +5 -exec rm {} \;

记住,在shell中用任何方式删除文件之前,应当先查看相应的文件,一定要小心!

当使用诸如mv或rm命令时,可以使用-exec选项的安全模式。它将在对每个匹配到的文件进行操作之前提示你。 在下面的例子中, find命令在当前目录中查找所有文件名以. LOG结尾、 更改时间在5日以上的文件,并删除它们,只不过在删除之前先给出提示。

$ find . -name "*.log" -mtime +5  -ok rm {} \;

按y键删除文件,按n键不删除。 任何形式的命令都可以在-exec选项中使用。在下面的例子中我们使用grep命令。find命令 首先匹配所有文件名为“passwd*”的文件,例如passwd、passwd.old、passwd.bak,然后执 行grep命令看看在这些文件中是否存在一个rounder用户。

$ find /etc  -name "passwd*"  -exec grep  "rounder" {} \;

2.1.14 find命令的例子

我们已经介绍了find命令的基本选项,下面给出find命令的一些其他的例子。 为了匹配$HOME目录下的所有文件,下面两种方法都可以使用:

$ find $HOME -print
$ find ~ -print

为了在当前目录中查找suid置位,文件属主具有读、写、执行权限,并且文件所属组的用 户和其他用户具有读和执行的权限的文件,可以用:

$ find . -type f -perm 4755 -print

为了查找系统中所有文件长度为0的普通文件,并列出它们的完整路径,可以用:

$ find / -type f -size 0 -exec ls -l {} \;

为了查找/var/logs目录中更改时间在7日以前的普通文件,并删除它们,可以用:

$ find /var/logs -type f -mtime +7 -exec rm {} \;

为了查找系统中所有属于audit组的文件,可以用:

$find /-name -group audit -print

我们的一个审计系统每天创建一个审计日志文件。日志文件名的最后含有数字,这样我 们一眼就可以看出哪个文件是最新的,哪个是最旧的。Admin.log 文件编上了序号: admin.log.001、admin.log.002等等。

下面的find命令将删除/logs目录中访问时间在7日以前、含有数字后缀的admin.log文件。该命令只检查三位数字,所以相应日志文件的后缀不要超过999。

$ find /logs -name 'admin.log[0-9][0-9]'[-0a-t9i]me +7 -exec rm {} \;

为了查找当前文件系统中的所有目录并排序,可以用:

$ find . -type d -print -local -mount |sort

为了查找系统中所有的rmt磁带设备,可以用:

$ find /dev/rmt -print

2.2 xargs

在使用find命令的-exec选项处理匹配到的文件时,find命令将所有匹配到的文件一起传递 给exec执行。不幸的是,有些系统对能够传递给exec的命令长度有限制,这样在find命令运行 几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是 xargs命令的用处所在,特别是与find命令一起使用。find命令把匹配到的文件传递给xargs命 令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理 最先获取的一部分文件,然后是下一批,并如此继续下去。在有些系统中,使用-exec选项会 为处理每一个匹配到的文件而发起一个相应的进程,并非将匹配到的文件全部作为参数一次 执行;这样在有些情况下就会出现进程过多,系统性能下降的问题,因而效率不高;而使用 xargs命令则只有一个进程。另外,在使用xargs命令时,究竟是一次获取所有的参数,还是分 批取得参数,以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参 数来确定。让我们来看看xargs命令是如何同find命令一起使用的,并给出一些例子。

下面的例子查找系统中的每一个普通文件,然后使用xargs命令来测试它们分别属于哪类 文件:

$ find / -type f -print | xargs file 

下面的例子在整个系统中查找内存信息转储文件(core dump),然后把结果保存到 /tmp/core.log 文件中:

$ find . -name "core" -print | xargs echo "" >/tmp/core.log

下面的例子在/apps/audit目录下查找所有用户具有读、写和执行权限的文件,并收回相应 的写权限:

$ find /apps/audit -perm -7 -print | xargs chmod o-w

在下面的例子中,我们用grep命令在所有的普通文件中搜索device这个词:

$ find / -type f -print | xargs grep "device"

在下面的例子中,我们用grep命令在当前目录下的所有普通文件中搜索DBO这个词:

$ find . -name *\-type f -print | xargs grep "DBO"

注意,在上面的例子中,\用来取消find命令中的*在shell中的特殊含义。

第3章 后台执行命令

当你在终端或控制台工作时,可能不希望由于运行一个作业而占住了屏幕,因为可能还 有更重要的事情要做,比如阅读电子邮件。对于密集访问磁盘的进程,你可能希望它能够在 每天的非负荷高峰时间段运行。为了使这些进程能够在后台运行,也就是说不在终端屏幕上 运行,有几种选择方法可供使用。 在本章中我们将讨论: • 设置crontab文件,并用它来提交作业。 • 使用at命令来提交作业。 • 在后台提交作业。 • 使用nohup命令提交作业。

名词解释: cron 系统调度进程,可以使用它在每天的非高峰负荷时间段运行作业,或在一周或一月中的不同时段运行。 At at命令,使用它在一个特定的时间运行一些特殊的作业,或在晚一些的非负荷高峰时间段或高峰负荷时间段运行。 & 使用它在后台运行一个占用时间不长的进程。 Nohup 使用它在后台运行一个命令,即使在用户退出时也不受影响。

3.1 cron和crontab

cron是系统主要的调度进程,可以在无需人工干预的情况下运行作业。有一个叫做 crontab的命令允许用户提交、编辑或删除相应的作业。每一个用户都可以有一个crontab文件 来保存调度信息。可以使用它运行任意一个shell脚本或某个命令,每小时运行一次,或一周 三次,这完全取决于你。每一个用户都可以有自己的crontab文件,但在一个较大的系统中, 系统管理员一般会禁止这些文件,而只在整个系统保留一个这样的文件。系统管理员是通过 cron.deny和cron.allow这两个文件来禁止或允许用户拥有自己的crontab文件。

3.1.1 crontab的域

为了能够在特定的时间运行作业,需要了解crontab文件每个条目中各个域的意义和格式。 下面就是这些域:

第1列 分钟1~59
第2列 小时1~23(0表示子夜)
第3列 日1~31
第4列 月1~12
第5列 星期0~6(0表示星期天)
第6列 要运行的命令

下面是crontab的格式:

分< >时< >日< >月< >星期< >要运行的命令
其中< >表示空格。

crontab文件的一个条目是从左边读起的,第一列是分,最后一列是要运行的命令,它位于星期的后面。 在这些域中,可以用横杠-来表示一个时间范围,例如你希望星期一至星期五运行某个作 业,那么可以在星期域使用1-5来表示。还可以在这些域中使用逗号“,”,例如你希望星期一 和星期四运行某个作业,只需要使用1,4来表示。可以用星号来表示连续的时间段。如果你 对某个表示时间的域没有特别的限定,也应该在该域填入。该文件的每一个条目必须含有5 个时间域,而且每个域之间要用空格分隔。该文件中所有的注释行要在行首用#来表示。

3.1.2 crontab条目举例

这里有crontab文件条目的一些例子:

30 21* * * /apps/bin/cleanup.sh

上面的例子表示每晚的21:30运行/apps/bin目录下的cleanup.sh。

45 4 1,10,22 * * /apps/bin/backup.sh

上面的例子表示每月1、1 0、2 2日的4: 45运行/apps/bin目录下的backup.sh。

10 1 * * 6,0 /bin/find -name "core" -exec rm {} \;

上面的例子表示每周六、周日的1:10运行一个find命令。

0,30 18-23 * * * /apps/bin/dbcheck.sh

上面的例子表示在每天18:00至23:00之间每隔30分钟运行/apps/bin目录下的dbcheck.sh。

0 23 * * 6 /apps/bin/qtrend.sh

上面的例子表示每星期六的11:00p m运行/apps/bin目录下的qtrend.sh。

你可能已经注意到上面的例子中,每个命令都给出了绝对路径。当使用crontab运行shell 脚本时,要由用户来给出脚本的绝对路径,设置相应的环境变量。记住,既然是用户向cron 提交了这些作业,就要向cron提供所需的全部环境。不要假定cron知道所需要的特殊环境,它 其实并不知道。所以你要保证在s h e l l脚本中提供所有必要的路径和环境变量,除了一些自动 设置的全局变量。 如果cron不能运行相应的脚本,用户将会收到一个邮件说明其中的原因。

3.1.3 crontab命令选项

crontab命令的一般形式为:

Crontab [-u user] -e -l -r
其中:
-u 用户名。
-e 编辑crontab文件。
-l 列出crontab文件中的内容。
-r 删除crontab文件。

如果使用自己的名字登录,就不用使用-u选项,因为在执行crontab命令时,该命令能够知道当前的用户。

3.1.4 创建一个新的crontab文件

在考虑向cron进程提交一个crontab文件之前,首先要做的一件事情就是设置环境变量 EDITOR。cron进程根据它来确定使用哪个编辑器编辑crontab文件。99%的UNIX和LINUX用 户都使用vi,如果你也是这样,那么你就编辑$HOME目录下的.profile文件,在其中加入这样 一行: EDITOR=vi; export EDITOR 然后保存并退出。 不妨创建一个名为cron的文件,其中是用户名,例如,lyoncron。在该文件 中加入如下的内容。

0,15,30,45,18-06 * * * /bin/echo 'date' >/dev.console

保存并退出。确信前面5个域用空格分隔。

在上面的例子中,系统将每隔1 5分钟向控制台输出一次当前时间。如果系统崩溃或挂起, 从最后所显示的时间就可以一眼看出系统是什么时间停止工作的。在有些系统中,用tty1来表 示控制台,可以根据实际情况对上面的例子进行相应的修改。 为了提交你刚刚创建的crontab文件,可以把这个新创建的文件作为cron命令的参数:

$ crontab lyoncron

现在该文件已经提交给cron进程,它将每隔15分钟运行一次。 同时,新创建文件的一个副本已经被放在/var/spool/cron目录中,文件名就是用户名(即, lyon)。

3.1.5 列出crontab文件

为了列出crontab文件,可以用:

$ crontab -l

你可以使用这种方法在$HOME目录中对crontab文件做一备份:

$ crontab -l > $HOME/mycron

这样,一旦不小心误删了crontab文件,可以用上述的方法迅速恢复。

3.1.6 编辑crontab文件

如果希望添加、删除或编辑crontab文件中的条目,而EDITOR环境变量又设置为vi,那么 就可以用vi来编辑crontab文件,相应的命令为:

$ crontab -e

可以像使用vi编辑其他任何文件那样修改crontab文件并退出。如果修改了某些条目或添加了新的条目,那么在保存该文件时, cron会对其进行必要的完整性检查。如果其中的某个 域出现了超出允许范围的值,它会提示你。 我们在编辑crontab文件时,没准会加入新的条目。例如,加入下面的一条:

lyon:crontab
30 3 1,7,14,21,26 * *  /bin/find/ -name "core" -exec rm {} \;

现在保存并退出。最好在crontab文件的每一个条目之上加入一条注释,这样就可以知道 它的功能、运行时间,更为重要的是,知道这是哪位用户的作业。 现在让我们使用前面讲过的crontab -l命令列出它的全部信息:

$ crontab -l

3.1.7 删除crontab文件

为了删除crontab文件,可以用:

$ crontab -r

3.1.8 恢复丢失的crontab文件

如果不小心误删了crontab文件,假设你在自己的$HOME目录下还有一个备份,那么可以 将其拷贝到/var/spool/cron/,其中是用户名。如果由于权限问题无法完 成拷贝,可以用:

$ crontab <filename>

其中,是你在`$HOME`目录中副本的文件名。 我建议你在自己的`$HOME`目录中保存一个该文件的副本。我就有过类似的经历,有数次 误删了crontab文件(因为r键紧挨在e键的右边⋯)。这就是为什么有些系统文档建议不要直接 编辑crontab文件,而是编辑该文件的一个副本,然后重新提交新的文件。 有些crontab的变体有些怪异,所以在使用crontab命令时要格外小心。如果遗漏了任何选 项,crontab可能会打开一个空文件,或者看起来像是个空文件。这时敲delete键退出,不要按 ``,否则你将丢失crontab文件。

3.2 at命令

at命令允许用户向cron守护进程提交作业,使其在稍后的时间运行。这里稍后的时间可能 是指10min以后,也可能是指几天以后。如果你希望在一个月或更长的时间以后运行,最好还是使用crontab文件。 一旦一个作业被提交,at命令将会保留所有当前的环境变量,包括路径,不象crontab, 只提供缺省的环境。该作业的所有输出都将以电子邮件的形式发送给用户,除非你对其输出 进行了重定向,绝大多数情况下是重定向到某个文件中。 和crontab一样,根用户可以通过/etc目录下的at.allow和at.deny文件来控制哪些用户可以使用at命令,哪些用户不行。不过一般来说,对at命令的使用不如对crontab的使用限制那么严 格。 at命令的基本形式为:

at [-f script] [-m -l -r] [time] [date]

其中, -f script 是所要提交的脚本或命令。 -l 列出当前所有等待运行的作业。at q命令具有相同的作用。 -r 清除作业。为了清除某个作业,还要提供相应的作业标识(ID);有些UNIX变体只 接受at rm作为清除命令。 -m 作业完成后给用户发邮件。 time at命令的时间格式非常灵活;可以是H、HH.HHMM、HH:MM或H:M,其中H和M 分别是小时和分钟。还可以使用a.m.或p.m.。 date 日期格式可以是月份数或日期数,而且at命令还能够识别诸如today、tomorrow这样 的词。 现在就让我们来看看如何提交作业。

3.2.1 使用at命令提交命令或脚本

使用at命令提交作业有几种不同的形式,可以通过命令行方式,也可以使用at命令提示符。 一般来说在提交若干行的系统命令时,我使用at命令提示符方式,而在提交shell脚本时,使用 命令行方式。 如果你想提交若干行的命令,可以在at命令后面跟上日期/时间并回车。然后就进入了at命 令提示符,这时只需逐条输入相应的命令,然后按‘’退出。下面给出一个例子:

$ at 21:10
at>find / -name "passwd" --print 
at><EOT>

其中,就是。在21:10系统将执行一个简单的find命令。你应当已经注 意到,我所提交的作业被分配了一个唯一标识job 1。该命令在完成以后会将全部结果以邮件 的形式发送给我。 下面就是我从这个邮件中截取的一部分:

下面这些日期/时间格式都是at命令可以接受的:

At 6.45am May12
At 11.10pm
At now + 1hour
At 9am tomorrow
At 15:00 May 24
At now+ 10minutes 

如果希望向at命令提交一个shell脚本,使用其命令行方式即可。在提交脚本时使用-f选项。

$ at 1.00am tomorrow -f /apps/bin/db_table.sh

在上面的例子中,一个叫做dbtable.sh的脚本将在明天上午1:00运行。

还可以使用echo命令向at命令提交作业:

$ echo find /etc -name "passwd" -print | at now +1 minute

3.2.2 列出所提交的作业

一个作业被提交后,可以使用at -l命令来列出所有的作业:

$ at -l

其中,第一行是作业标识,后面是作业运行的日期/时间。最后一列a代表at。还可以使用 at q命令来完成同样的功能,它是at命令的一个链接。当提交一个作业后,它就被拷贝到 /var/spool/at目录中,准备在要求的时间运行。

.2.3 清除一个作业

清除作业的命令格式为:

atrm [job no]或 at -r [job no]

要清除某个作业,首先要执行at -l命令,以获取相应的作业标识,然后对该作业标识使用 at -r 命令,清除该作业。

3.3 &命令

当在前台运行某个作业时,终端被该作业占据;而在后台运行作业时,它不会占据终端可以使用&命令把作业放到后台执行。 该命令的一般形式为:

命令&

为什么要在后台执行命令?因为当在后台执行命令时,可以继续使用你的终端做其他事 情。适合在后台运行的命令有find、费时的打印作业、费时的排序及一些shell脚本。在后台运 行作业时要当心:需要用户交互的命令不要放在后台执行,因为这样你的机器就会在那里傻 等。 不过,作业在后台运行一样会将结果输出到屏幕上,干扰你的工作。如果放在后台运行 的作业会产生大量的输出,最好使用下面的方法把它的输出重定向到某个文件中:

command >out.file 2>&1 &

在上面的例子中,所有的标准输出和错误输出都将被重定向到一个叫做out.file 的文件中。 当你成功地提交进程以后,就会显示出一个进程号,可以用它来监控该进程,或杀死它。

3.3.1 向后台提交命令

现在我们运行一个find命令,查找名为“srm.conf”的文件,并把所有标准输出和错误输 出重定向到一个叫作find.dt的文件中:

$ find/etc -name "srm.conf" -print >find.dt 2>&1 &

在上面的例子中,在我们成功提交该命令之后,系统给出了它的进程号27015。 当该作业完成时,按任意键(一般是回车键)就会出现一个提示

这里还有另外一个例子,有一个叫做ps1的脚本,它能够截断和清除所有的日志文件,我 把它放到后台去执行:

$  ps1 &

3.3.2 用ps命令查看进程

当一个命令在后台执行的时候,可以用提交命令时所得到的进程号来监控它的运行。在 前面的例子中,我们可以按照提交ps1时得到的进程号

用ps命令和grep命令列出这个进程:

$  ps x|grep 27015

如果系统不支持ps x命令,可以用:

$  ps -ef |grep 27015

记住,在用ps命令列出进程时,它无法确定该进程是运行在前台还是后台。

3.3.3 杀死后台进程

如果想杀死后台进程可以使用kill命令。当一个进程被放到后台运行时, shell会给出一个 进程号,我们可以根据这个进程号,用kill命令杀死该进程。该命令的基本形式为:

kill -signal [process_number]

现在暂且不要考虑其中的各种不同信号;我们会在后面的章节对这一问题进行介绍。 在杀进程的时候,执行下面的命令(你的进程号可能会不同)并按回车键。系统将会给出相 应的信息告诉用户进程已经被杀死。

$ kill 27015

如果系统没有给出任何信息,告诉你进程已经被杀死,那么不妨等一会儿,也许系统正 在杀该进程,如果还没有回应,就再执行另外一个kill命令,这次带上一个信号选项:

$ kill -9 27015

如果用上述方法提交了一个后台进程,那么在退出时该进程将会被终止。为了使后台进 程能够在退出后继续运行,可以使用nohup命令,下面我们就介绍这一命令。

3.4 nohup命令

如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用 nohup命令。该命令可以在你退出帐户之后继续运行相应的进程。Nohup就是不挂起的意思(nohang up)。

该命令的一般形式为:

nohup command &

3.4.1 使用nohup命令提交作业

如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名 为nohup.out的文件中,除非另外指定了输出文件:

nohup command > myout.file 2>&1

在上面的例子中,输出被重定向到myout.file文件中。 让我们来看一个例子,验证一下在退出帐户后相应的作业是否能够继续运行。我们先提 交一个名为ps1的日志清除进程:

$ nohup ps1 &

现在退出该shell,再重新登录,然后执行下面的命令:

$ ps x |grep ps1
我们看到,该脚本还在运行。如果系统不支持ps x命令,使用ps -ef |grep ps1命令。

3.4.2 一次提交几个作业

如果希望一次提交几个命令,最好能够把它们写入到一个shell脚本文件中,并用nohup命令来执行它。 例如,下面的所有命令都用管道符号连接在一起;我们可以把这些命令存入一个文件,并使该文件可执行。

cat /home/accounts/qtr_0499 | /apps/bin/trials.awk |sort|lp

示例:

$ cat > quarterend
cat /home/accounts/qtr_0499 | /apps/bin/trials.awk |sort|lp
<CTRL-D>

现在让它可执行:

$ chmod 744 quarterend

我们还将该脚本的所有输出都重定向到一个名为qtr.out的文件中。

$ nohup ./quarterend  qtr.out 2>&1 &

3.5 小结

创建一个定时清理日志文件或完成其他特殊工作的脚本,这样只要提交一次,就可以每 天晚上运行,而且无需你干预,只要看看相应的脚本日志就可以了。cron和其他工具可以使 系统管理任务变得更轻松。

第4章 文件名置换

当你在使用命令行时,有很多时间都用来查找你所需要的文件。Shell提供了一套完整的 字符串模式匹配规则,或者称之为元字符,这样你就可以按照所要求的模式来匹配文件。还 可以使用字符类型来匹配文件名。在命令行方式下,使用元字符更为快捷,所以在本章我们 只介绍这部分内容。 在本章我们将讨论: • 匹配文件名中的任何字符串。 • 匹配文件名中的单个字符。 • 匹配文件名中的字母或数字字符。 下面就是这些特殊字符:

  • 匹配文件名中的任何字符串,包括空字符串。 ? 匹配文件名中的任何单个字符。 […] 匹配[ ]中所包含的任何字符。 [!…] 匹配[ ]中非感叹号!之后的字符。 当shell遇到上述字符时,就会把它们当作特殊字符,而不是文件名中的普通字符,这样用户就可以用它们来匹配相应的文件名。

4.1 使用*

使用星号*可以匹配文件名中的任何字符串。app*它的意思是文件名以app开头,后面可以跟随任何字符串,包括空字符串:

 *.doc匹配所有以.doc结尾的文件名,cl*.sed用于匹配所有以cl开头。
  在使用cd命令切换路径时,使用星号还可以省去输入整个路径名。

4.2 使用?

使用可以匹配文件名中的任何单个字符

4.3 使用[…]和[!…]

使用[…]可以用来匹配方括号[]中的任何字符。在这一方法中,还可以使用一个横杠-来连 接两个字母或数字,以此来表示一个范围。

i或o开头的文件名

$ ls [io]*

使用[0-9]*来表示包含数字的字符串

$ ls log.[0-9]*

使用[!0-9]*来表示非数字开头的字符串

$ ls log.[!0-9]*

为了列出所有以大写字母开头的文件名,可以用:

$ ls [A-Z]*

为了列出所有以小写字母开头的文件名,可以用:

$ ls [a-z]*

为了列出所有以数字开头的文件名,可以用:

$ ls [0-9]*

为了列出所有以. 开头的文件名(隐含文件,例如.profile、.rhosts、.history等等),可以 用:

$ ls .*

第5章 shell输入与输出

在shell脚本中,可以用几种不同的方式读入数据:可以使用标准输入—缺省为键盘,或 者指定一个文件作为输入。对于输出也是一样:如果不指定某个文件作为输出,标准输出总 是和终端屏幕相关联。如果所使用命令出现了什么错误,它也会缺省输出到屏幕上,如果不 想把这些信息输出到屏幕上,也可以把这些信息指定到一个文件中。 大多数使用标准输入的命令都指定一个文件作为标准输入。如果能够从一个文件中读取 数据,何必要费时费力地从键盘输入呢?

5.1 echo

使用echo命令可以显示文本行或变量,或者把字符串输入到文件。它的一般形式为:

echo string

echo命令有很多功能,其中最常用的是下面几个:

\c 不换行。
\f 进纸。
\t 跳格。
\n 换行。
> 重定向符号

双引号echo命令的字符串中。引号是一个特殊字符,所以必须要使用反斜杠\来使shell忽略它的特殊含义。假设你希望使用 echo命令输出这样的字符串:“/dev/rmt0”,那么我们只要在引号前面加上反斜杠\即可:

$ echo "\"/dev/rmt0"\"
"/dev/rmt0"

如果是LINUX系统,那么…… 必须使用-n选项来禁止echo命令输出后换行:

5.2 read

可以使用read语句从键盘或文件的某一行文本中读入信息,并将其赋给一个变量。如果只指定了一个变量,那么read将会把所有的输入赋给该变量,直至遇到第一个文件结束符或回 车。它的一般形式为:

read varible1 varible2 ...

在编写shell脚本的时候,如果担心用户会对此感到迷惑,可以采用每一个read语句只给一 个变量赋值的办法:

5.3 cat

cat是一个简单而通用的命令,可以用它来显示文件内容,创建文件,还可以用它来显示 控制字符。在使用cat命令时要注意,它不会在文件分页符处停下来;它会一下显示完整个文 件。如果希望每次显示一页,可以使用more命令或把cat命令的输出通过管道传递到另外一个 具有分页功能的命令中,请看下面的例子:

$ cat myfile | more

$ cat myfile | pg

c a t命令的一般形式为:

cat [options] filename1 ... filename2 ...

cat命令最有用的选项就是: -v 显示控制字符 如果希望显示名为myfile的文件,可以用:

$ cat myfile

如果希望显示myfile1、myfile2、myfile3这三个文件,可以用:

$ cat myfile1 myfile2 myfile3

如果希望创建一个名为bigfile的文件,该文件包含上述三个文件的内容,可以把上面命令 的输出重定向到新文件中:

$ cat myfile1 myfile2 myfile3 > bigfile

如果希望创建一个新文件,并向其中输入一些内容,只需使用cat命令把标准输出重定向 到该文件中,这时c a t命令的输入是标准输入—键盘,你输入一些文字,输入完毕后按 ‘’结束输入。这真是一个非常简单的文字编辑器!

$ cat > myfile

还可以使用cat命令来显示控制字符。这里有一个对从DOS机器上ftp过来的文件进行检察 的例子,在这个例子中,所有的控制字符都在行末显示了出来。

$ cat -v life.tct

有一点要提醒的是,如果在敲入了cat以后就直接按回车,该命令会等你输入字符。那么它除了会在你输入时在屏幕上显示以外,还会再回显这些 内容;最后按结束输入即可。

5.4 管道

可以通过管道把一个命令的输出传递给另一个命令作为输入。管道用竖杠|表示。它的一 般形式为:

命令1 |命令2

其中|是管道符号。 在下面的例子中,在当前目录中执行文件列表操作,如果没有管道的话,所有文件就会 显示出来。当shell看到管道符号以后,就会把所有列出的文件交给管道右边的命令,因此管 道的含义正如它的名字所暗示的那样:把信息从一端传送到另外一端。 sed、awk和grep都很适合用管道,特别是在简单的一行命令中。 如果你希望列出系统中所有的文件系统,可以使用管道把df命令的输出传递给awk命令, awk显示出其中的第一列。你还可以再次使用管道把awk的结果传递给grep命令,去掉最上面 的题头filesystem。

$ df -k | awk '(print $1)'|grep - v "filesystem"

当然,你没准还会希望只显示出其中的分区名,不显示/dev/部分,这没问题;我们只要 在后面简单地加上另一个管道符号和相应的sed命令即可。

$ df -k | awk '(print $1)'|grep - v "filesystem" |sed s'/\/dev\///g'

在这个例子中,我们先对一个文件进行排序,然后通过管道输送到打印机。

$ sort myfile | lp

5.5 tee

tee命令作用可以用字母T来形象地表示。它把输出的一个副本输送到标准输出,另一个 副本拷贝到相应的文件中。如果希望在看到输出的同时,也将其存入一个文件,那么这个命 令再合适不过了。 它的一般形式为:

tee -a files

其中,-a表示追加到文件末尾。 当执行某些命令或脚本时,如果希望把输出保存下来,tee命令非常方便。

5.6 标准输入、输出和错误

当我们在shell中执行命令的时候,每个进程都和三个打开的文件相联系,并使用文件描 述符来引用这些文件。由于文件描述符不容易记忆, shell同时也给出了相应的文件名。 下面就是这些文件描述符及它们通常所对应的文件名: 文件文件描述符 输入文件—标准输入0 输出文件—标准输出1 错误输出文件—标准错误2 系统中实际上有1 2个文件描述符,但是正如我们在上表中所看到的, 0、1、2是标准输入、 输出和错误。可以任意使用文件描述符3到9。

5.6.1 标准输入

标准输入是文件描述符0。它是命令的输入,缺省是键盘,也可以是文件或其他命令的输出。

5.6.2 标准输出

标准输出是文件描述符1。它是命令的输出,缺省是屏幕,也可以是文件。

5.6.3 标准错误

标准错误是文件描述符2。这是命令错误的输出,缺省是屏幕,同样也可以是文件。你可 能会问,为什么会有一个专门针对错误的特殊文件?这是由于很多人喜欢把错误单独保存到 一个文件中,特别是在处理大的数据文件时,可能会产生很多错误。 如果没有特别指定文件说明符,命令将使用缺省的文件说明符(你的屏幕,更确切地说 是你的终端)。

5.7 文件重定向

在执行命令时,可以指定命令的标准输入、输出和错误,要实现这一点就需要使用文件 重定向。表5 - 1列出了最常用的重定向组合,并给出了相应的文件描述符。 在对标准错误进行重定向时,必须要使用文件描述符,但是对于标准输入和输出来说, 这不是必需的。为了完整起见,我们在表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 把command命令以filename文件作为标准输入,以filename2文件作为标准输出
command < filename 把command命令以filename文件作为标准输入
command « delimiter 把从标准输入中读入,直至遇到delimiter分界符
command <&m 把文件描述符m作为标准输入
command >&m 把标准输出重定向到文件描述符m中
command <&- 关闭标准输入

5.7.1 重定向标准输出

让我们来看一个标准输出的例子。在下面的命令中,把/etc/passwd文件中的用户ID域按 照用户命排列。该命令的输出重定向到s ort.out文件中。要提醒注意的是,在使用sort命令的时 候(或其他含有相似输入文件参数的命令),重定向符号一定要离开sort命令两个空格,否则该 命令会把它当作输入文件。

$ cat passwd | awk -F: '{print $1}' | sort 1>sort.out

从表5-1中可以看出,我们也可以使用如下的表达方式,结果和上面一样:

$ cat passwd | awk -F: '{print $1}' | sort >sort.out

可以把很多命令的输出追加到同一文件中。

ls -l | grep ^d >> files.out
ls account* >> files.out

在上面的例子中,所有的目录名和以account开头的文件名都被写入到file.out文件中。 如果希望把标准输出重定向到文件中,可以用>filename。在下面的例子中,ls命令的所 有输出都被重定向到ls.out文件中:

$ ls >ls.out

如果希望追加到已有的文件中(在该文件不存在的情况下创建该文件),那么可以使用

>>filename:

如果想创建一个长度为0的空文件,可以用’ >filename’:

$ >myfile

5.7.2 重定向标准输入

可以指定命令的标准输入。在awk一章就会遇到这样的情况。下面给出一个这样的例子:

$ sort < name.txt

在上面的命令中,sort命令的输入是采用重定向的方式给出的,不过也可以直接把相应的 文件作为该命令的参数:

$ sort name.txt

在上面的例子中,还可以更进一步地通过重定向为sort命令指定一个输出文件name.out。 这样屏幕上将不会出现任何信息(除了错误信息以外):

$ sort <name.txt >name.out

在发送邮件时,可以用重定向的方法发送一个文件中的内容。在下面的例子中,用户 louise将收到一个邮件,其中含有文件contents.txt中的内容:

$ mail louise < contents.txt

重定向操作符command « delimiter是一种非常有用的命令,通常都被称为“此处”文挡。 我们将在本书后面的章节深入讨论这一问题。现在只介绍它的功能。shell将分界符delimiter之 后直至下一个同样的分界符之前的所有内容都作为输入,遇到下一个分界符, shell就知道输 入结束了。这一命令对于自动或远程的例程非常有用。可以任意定义分界符delimiter,最常见 的是EOF,而我最喜欢用MAYDAY,这完全取决于个人的喜好。还可以在«后面输入变量。 下面给出一个例子,我们创建了一个名为myfile的文件,并在其中使用了TERM和LOGNAME 变量。

5.7.3 重定向标准错误

$ grep "trident" missiles 2>/dev/null

这样所有的错误输出都输送到了/dev/null,不再出现在屏幕上。 如果你在对更重要的文件进行操作,可能会希望保存相应的错误。下面就是一个这样的 例子,这一次错误被保存到grep.err文件中:

$ grep "trident" missiles 2>>grep.err

还可以把错误追加到一个文件中。在使用一组命令完成同一个任务时,这种方法非常有 用。在下面的例子中,两个grep命令把错误都输出到同一个文件中;由于我们使用了»符号 进行追加,后面一个命令的错误(如果有的话)不会覆盖前一个命令的错误。

5.8 结合使用标准输出和标准错误

个快速发现错误的方法就是,先将输出重定向到一个文件中,然后再把标准错误重定 向到另外一个文件中。下面给出一个例子: 我有两个审计文件,其中一个的确存在,而且包含一些信息,而另一个由于某种原因已 经不存在了(但我不知道)。我想把这两个文件合并到accounts.out文件中。

$ cat account_qtr.doc account_end.doc 1>accounts.out 2>accounts.err

现在如果出现了错误,相应的错误将会保存在accounts.err文件中。 我事先并不知道是否存在accountend.doc文件,使用上面的方法能够快速发现其中的错 误。

5.9 合并标准输出和标准错误

在合并标准输出和标准错误的时候,切记shell是从左至右分析相应的命令的。 下面给出一个例子:

$ cleanup >cleanup.out 2>&1

在上面的例子中,我们将cleanup脚本的输出重定向到cleanup.out文件中,而且其错误也 被重定向到相同的文件中。

$ grep "standard"* > grep.out 2>&1

在上面的例子中,grep命令的标准输出和标准错误都被重定向到grep.out文件中。你在使 用前面提到的“此处”文挡时,有可能需要把所有的输出都保存到一个文件中,这样万一出 现了错误,就能够被记录下来。上面的例子演示了如何把所有的输出捕捉到一个文件中。在使用cat命令的时候, 这可能没什么用处,不过如果你使用“此处”文挡连接一个数据库管理系统(例如使用i sql连接 sybase)或使用ftp,这一点就变得非常重要了,因为这样就可以捕捉到所有的错误,以免这些 错误在屏幕上一闪而过,特别是在你不在的时候。

5.10 exec

exec命令可以用来替代当前shell;换句话说,并没有启动子shell。使用这一命令时任何现 有环境都将会被清除,并重新启动一个shell。它的一般形式为:

exec command

其中的command通常是一个shell脚本。 我所能够想像得出的描述exec命令最贴切的说法就是:它践踏了你当前的shell。 当这个脚本结束时,相应的会话可能就结束了。exec命令的一个常见用法就是在用户 的.profile最后执行时,用它来执行一些用于增强安全性的脚本。如果用户的输入无效,该 shell将被关闭,然后重新回到登录提示符。e x e c还常常被用来通过文件描述符打开文件。 记住, exec在对文件描述符进行操作的时候(也只有在这时),它不会覆盖你当前的 shell。

5.11 使用文件描述符

可以使用exec命令通过文件描述符打开和关闭文件。在下面的例子中,我选用了文件描 述符4,实际上我可以在4到9之间任意选择一个数字。下面的脚本只是从1.txt文件中读了 两行,然后把这两行回显出来。 该脚本的第一行把文件描述符4指定为标准输入,然后打开1.txt文件。接下来两行的 作用是读入了两行文本。接着,作为标准输入的文件描述符4被关闭。最后line1和line2两个 变量所含有的内容被回显到屏幕上。

$ pg f_desc
#!/bin/sh
#f_desc
exec 4<&0 0<1.txt
read line1
read line2
exec 0<&4
echo $line1
echo $line2

下面是这个小小的文件1.txt的内容:
$ cat 1.txt
1 test1
2 test2
下面是该脚本的运行结果:
$ f_desc
1 test1
2 test2

第6章 命令执行顺序

• 命令执行控制。 • 命令组合。 如果希望在成功地执行一个命令之后再执行另一个命令,或者在一个命令失败后再执行 另一个命令,&&和||可以完成这样的功能。相应的命令可以是系统命令或shell脚本。 Shell还提供了在当前shell或子shell中执行一组命令的方法,即使用()和{}。

6.1 使用&&

使用&&的一般形式为:

命令1 && 命令2

这种命令执行方式相当地直接。&&左边的命令(命令1)返回真(即返回0,成功被执行) 后,&&右边的命令(命令2)才能够被执行;换句话说,“如果这个命令执行成功&&那么执 行这个命令”。 这里有一个使用&&的简单例子:

$  cp 1.doc 1.doc.bak && echo "cp was OK"

在上面的例子中,&&前面的拷贝命令执行成功,所以&&后面的命令(echo命令)被执行。

再看一个更为实用的例子:

$ mv /apps/bin /apps/dev/bin && rm -r /apps/bin

在上面的例子中,/apps/bin目录将会被移到/apps/dev/bin目录下,如果它没有被成功执行, 就不会删除/apps/bin目录。 在下面的例子中,文件quarterend.txt首先将被排序并输出到文件quarter.sorted中,只有 这一命令执行成功之后,文件quarter.sorted才会被打印出来:

$ sort quarter_end.txt > quarter.sorted && lp quarter.sorted

6.2 使用||

使用||的一般形式为:

命令1 || 命令2

||的作用有一些不同。如果||左边的命令(命令1)未执行成功,那么就执行||右边的命令 (命令2);或者换句话说,“如果这个命令执行失败了|| 那么就执行这个命令”。 这里有一个使用||的简单例子: 在上面的例子中,拷贝命令没有能够被成功执行,因此||后面的命令被执行。 这里有一个更为实用的例子。我希望从一个审计文件中抽取第1个和第5个域,并将其输 出到一个临时文件中,如果这一操作未成功,我希望能够收到一个相应邮件: 在这里不只可以使用系统命令;这里我们首先对monthend.txt文件执行了一个名为comet 的shell脚本,如果该脚本未执行成功,该shell将结束。

$ comet month_end.txt || exit

6.3 用()和{}将命令结合在一起

如果希望把几个命令合在一起执行,shell提供了两种方法。既可以在当前shell也可以在 子shell中执行一组命令。 为了在当前shell中执行一组命令,可以用命令分隔符隔开每一个命令,并把所有的命令 用圆括号()括起来。 它的一般形式为:

(命令1;命令2;...)

如果使用{}来代替(),那么相应的命令将在子shell而不是当前shell中作为一个整体被执 行,只有在{}中所有命令的输出作为一个整体被重定向时,其中的命令才被放到子shell中执 行,否则在当前shell执行。它的一般形式为:

{命令1;命令2;. . . }

我很少单独使用这两种方法。我一般只和&&或||一起使用这两种方法。 再回到前面那个comet脚本的例子,如果这个脚本执行失败了,我很可能会希望执行两个 以上的命令,而不只是一个命令。我可以使用这两种方法。这是原先那个例子:

$ comet month_end.txt || exit

现在如果该脚本执行失败了,我希望先给自己发个邮件,然后再退出,可以用下面的方 法来实现: 在上面的例子中,如果只使用了命令分隔符而没有把它们组合在一起, shell将直接执行 最后一个命令(exit)。 我们再回头来看看前面那个使用& &排序的例子,下面是原来的那个例子:

$ sort quarter_end.txt > quarter.sorted && lp quarter.sorted

使用命令组合的方法,如果sort命令执行成功了,可以先将输出文件拷贝到一个日志区,然后再打印。 在编写shell脚本时,使用&&和||对构造判断语句非常有用。如果希望在前一个命令执行 失败的情况不执行后面的命令,那么本章所讲述的方法非常简单有效。使用这样的方法,可 以根据&&或||前面命令的返回值来控制其后面命令的执行。