2.5 作为程序设计语言的shell
现在你已了解了一些基本的shell操作,是时候开始介绍一些真正的shell脚本程序了。编写shell脚本程序有两种方式。你可以输入一系列命令让shell交互地执行它们,也可以把这些命令保存到一个文件中,然后将该文件作为一个程序来调用。
2.5.1 交互式程序
在命令行上直接输入shell脚本是一种测试短小代码段的简单而快捷的方式。如果你正在学习shell脚本或仅仅是为了进行测试,使用这种方式是非常有用的。
假设你想要从大量C语言源文件中查找包含字符串POSIX的文件。与其使用grep命令在每个文件中搜索字符串,然后再分别列出包含该字符串的文件,不如用下面的交互式脚本来执行整个操作:
请注意,当shell期待进一步的输入时,正常的$ shell提示符将改变为>提示符。你可以一直输入下去,由shell来判断何时输入完毕并立刻执行脚本程序。
在这个例子中,grep命令输出它找到的包含POSIX字符串的文件,然后more命令将文件的内容显示在屏幕上。最后,返回shell提示符。还要注意的是,你用shell变量来处理每个文件,以使该脚本自文档化。你也可以将变量名起为i,但是变量名file更容易理解。
shell还提供了通配符扩展(通常称为globbing)。你一定已注意到可以用通配符*来匹配一个字符串。但是你可能不知道可以用通配符?来匹配单个字符,而[set]允许匹配方括号中任何一个单个字符,[^set]对方括号中的内容取反,即匹配任何没有出现在给出的字符集中的字符。扩展的花括号{}(只能用在部分shell中,其中包括bash)允许你将任意的字符串组放在一个集合中,以供shell进行扩展。例如:
这个命令将列出文件my_fingers和my_toes,它使用shell来检查当前目录下的每个文件。当我们在本章结尾详细介绍grep命令和正则表达式的强大功能时,我们将回过头来再次研究匹配模式中的这些规则。
有经验的Linux用户可能会用一种更有效的方式来执行这个简单的操作。也许使用如下的命令:
或使用功能相同的另一种命令形式:
此外,下面的命令将输出包含POSIX字符串的文件名:
在上面的脚本中,你看到shell利用其他命令(如grep和more)来完成主要的工作。shell本身只是允许你将几个现有的命令结合在一起,以构成一个新的功能强大的命令。你将在后面的脚本示例中看到通配符扩展的多次应用,并且我们还将在本章中介绍grep命令和正则表达式时详细讨论整个扩展的细节。
如果每次想要执行一系列命令时,你都要经过这么一个冗长的输入过程,将非常令人烦恼。你需要将这些命令保存到一个文件中,即我们常说的shell脚本,这样你就可以在需要的时候随时执行它们了。
2.5.2 创建脚本
首先,你必须用一个文本编辑器来创建一个包含命令的文件,将其命名为first,它的内容如下所示:
程序中的注释以#符号开始,一直持续到该行的结束。按照惯例,我们通常把#放在第一列。在作出这样一个笼统的陈述之后,请注意第一行#! /bin/sh,它是一种特殊形式的注释,#!字符告诉系统同一行上紧跟在它后面的那个参数是用来执行本文件的程序。在这个例子中,/bin/sh是默认的shell程序。
请注意注释中使用的是绝对路径。考虑到向后兼容性,这个路径按惯例最好不要超过32个字符,因为一些老版本的UNIX在使用#!时只能使用这个限制之内的字符数,虽然Linux通常不存在这样的限制。
因为脚本程序本质上被看作是shell的标准输入,所以它可以包含任何能够通过你的PATH环境变量引用到的Linux命令。
exit命令的作用是确保脚本程序能够返回一个有意义的退出码(在本章的后面将对此进行详细介绍)。当程序以交互方式运行时,我们很少需要检查它的退出码,但如果你打算从另一个脚本程序里调用这个脚本程序并查看它是否执行成功,那么返回一个适当的退出码就很重要了。即使你从来也没打算允许你的脚本程序被另一个脚本程序调用,你也应该在退出时返回一个合理的退出码。请相信自己的脚本程序是有用的,它总有一天会作为其他脚本程序的一部分而被重用。
在shell程序设计里,0表示成功。因为这个脚本程序并不能检查到任何错误,所以它总是返回一个表示成功的退出码。我们将在本章后面详细介绍exit命令时,再回过头来解释用0表示成功的原因。
请注意,这个脚本没有使用任何的文件扩展名或后缀。一般情况下,Linux和UNIX很少利用文件扩展名来决定文件的类型。你可以为脚本使用.sh或者其他扩展名,但shell并不关心这一点。大多数预安装的脚本程序并没有使用任何文件扩展名,检查这些文件是否是脚本程序的最好方法是使用file命令,例如,file first或file /bin/bash。你可以使用任何适用于你的工作环境或适合于你的方式。
2.5.3 把脚本设置为可执行
现在你已经有了自己的脚本文件,运行它有两种方法。比较简单的方法是调用shell,并把脚本文件名当成一个参数,如下所示:
这可以工作,但如果能像对待其他Linux命令那样,只输入脚本程序的名字就可以调用它就更好了。你可以使用chmod命令来改变这个文件的模式,使得这个文件可以被所有用户执行,如下所示:
当然,这并不是使用chmod命令将一个文件设置为可执行的唯一方式,请用man chmod命令查看它的八进制参数和其他选项用法。
然后你可以用下面的命令来执行它:
你可能会看到一条错误信息告诉你未找到命令。这种情况很可能发生,因为shell环境变量PATH并没有被设置为在当前目录下查找要执行的命令。要解决这个问题,一种办法是在命令行上直接输入命令PATH=$PATH:.或编辑你的.bash_profile文件,将刚才这条命令添加到文件的末尾,然后退出登录后再重新登录进来。另外,你也可以在保存脚本程序的目录中输入命令./first,该命令的作用是把脚本程序的完整的相对路径告诉shell。
用./来指定路径还有另一个好处,它能够保证你不会意外执行系统中与你的脚本文件同名的另一个命令。
你不应该用这种方法来修改超级用户(一般其用户名为root)的PATH变量。这是一个安全方面的漏洞,因为以root用户身份登录的系统管理员可能会因此误调用了某个标准命令的伪装版本。本书其中一位作者就曾经这样做过一次,目的当然是为了向系统管理员指出这一点!即使对于普通用户,把当前目录包括在PATH变量中也多少有些危险。因此,如果你非常关心系统的安全,最好的办法是养成在执行当前目录中的所有命令时,在其前面都加上一个./的好习惯。
在确信你的脚本程序能够正确执行后,你可以把它从当前目录移到一个更合适的地方去。如果这个命令只供你本人使用,你可以在自己的家目录中创建一个bin目录,并且将该目录添加到你自己的PATH变量中。如果你想让其他人也能够执行这个脚本程序,你可以将/usr/local/bin或其他系统目录作为添加新程序的适当位置。如果你在系统上没有root权限,你可以要求系统管理员帮你复制你的文件,当然你首先必须让他们相信这些程序的价值才行。为了防止其他用户修改脚本程序,哪怕只是意外地修改,你也应该去掉脚本程序的写权限。系统管理员用来设置文件属主和访问权限的一系列命令如下所示:
注意,你在这里不是修改访问权限标志的特定部分,而是使用chmod命令的绝对格式,因为你清楚地知道你需要的访问权限。
如果你愿意,还可以使用chmod命令相对长一些但可能含义更明确的格式,如下所示:
更多chmod命令的详细资料请参考它的使用手册。
在Linux系统中,如果你拥有包含某个文件的目录的写权限,就可以删除这个文件。为安全起见,应该确保只有超级用户才能对你想保证文件安全的目录执行写操作。因为目录只是另一种类型的文件,所以拥有对一个目录文件写权限的用户可以添加和删除目录文件中的名称。