Bourne Shell 脚本/运行命令
在我们开始对 Bourne Shell 的能力进行任何形式的考察以及如何利用其能力之前,我们必须先了解一些基本内容:我们必须讨论如何将命令输入 shell 以供该 shell 执行。
如果你可以访问基于 Unix 的机器(或其他操作系统上的模拟器),你可能已经在使用 Bourne Shell - 或它的后代 - 了,可能没有意识到。惊喜:你已经做了一段时间的 shell 脚本了!
在你的 Unix 环境中,进入一个终端;无论是文本登录终端,还是如果你正在使用 X Window System 的窗口中的终端(如果你还没有这样做,可以查找名为 *xterm* 或 *rxvt* 或 *terminal* 的东西)。你最终可能会看到一个类似这样的屏幕
Ben_Tels:Local_Machine:~>_
或者
The admin says: everybody, STOP TRYING TO CRASH THE SYSTEM Have a lot of fun! bzt:Another_Machine:~>_
甚至像这样简单的东西
$_
就是这样。这就是你的 shell:你直接访问系统提供的所有内容。
具体来说,你刚才访问的程序是你的 shell,它在 *交互模式* 下运行:shell 以这样一种方式运行,它会显示一个提示符和一个光标(那个闪烁的小线),并在等待你输入一个命令供它执行。你在交互模式下执行命令的方式是输入命令,然后按下 **Enter** 键。shell 然后将你的命令转换为操作系统能够理解的内容,并将控制权交给操作系统,以便它能够实际执行你发送的任务。你会注意到,当命令正在执行时,你的光标会短暂地消失,并且你不能再输入任何内容(此时,Bourne Shell 程序不再控制你的终端 - 你通过执行命令启动的另一个程序正在控制)。在某个时刻,操作系统将完成你的命令的工作,shell 将显示一个新的提示符和光标,然后开始再次等待你输入另一个命令。试试看:输入命令
- ls enter
过一小段时间后,你会看到工作目录(你的 shell 认为是“当前”目录)中的文件列表,一个新的提示符和光标。
这是执行 shell 命令最简单的方法:一次输入一个命令,并等待每个命令按顺序完成。shell 经常以这种方式使用,既用于执行属于 Bourne Shell 编程语言的命令,也用于简单地启动其他程序(例如上面的示例中的 ls 程序)。
在我们继续之前,我们提一下在使用 shell 时两个有用的按键组合:用于中断正在运行的程序和 shell 命令的命令,以及用于退出 shell 的命令(尽管,为什么你永远想要 *停止* 使用 shell,我无法理解……)。
要中断正在运行的程序或 shell 命令,请同时按下 Control 和 C 键。我们将在后面的章节中详细了解这到底做了什么,但现在请记住,这是中断操作的方式。
要退出 shell 会话,请按下 Control+d。此按键组合会产生 Unix 文件结束符 - 我们将在后面讨论为什么这也会终止你的 shell 会话。一些现代 shell 禁用了 Control+d 的使用,转而使用“exit”命令(可耻)。如果你使用的是这种 shell,只需输入“exit”一词(就像其他任何命令一样),然后按下 Enter(从现在开始,我会在示例中省略“Enter”)。
正如我们在上一节中看到的,你可以通过启动一个交互式 shell 会话并在提示符下输入命令,非常容易地执行所有目的的 shell 命令。但是,有时你有一组命令需要定期重复,即使是在不同的时间和不同的 shell 会话中。当然,在 Unix 系统的以编程为中心的環境中,你可以编写一个程序来获得相同的结果(例如在 C 语言中)。但是,使用 shell 来完成这项任务是否会方便得多?是否会有更方便的方法来重放一组命令?以及能够像在 shell 的交互式会话中输入单个命令一样轻松地编写该组命令?
幸运的是,确实有这样的方法:Bourne Shell 的 *非交互* 模式。在这种模式下,shell 没有提示符,也不会等待你的命令。相反,shell 从一个文本文件(它告诉 shell 要做什么,有点像演员从剧本中获取命令 - 因此,shell 脚本)中读取命令。该文件包含一系列命令,就像你在交互式会话中的提示符下输入它们一样。shell 从上到下读取文件,并按顺序执行命令。
shell 脚本非常容易编写;你可以使用任何你喜欢的文本编辑器(甚至可以使用任何文字处理器或其他编辑器,只要记住将你的脚本保存为纯文本格式即可)。你编写命令的方式与你在交互式 shell 中编写命令的方式相同。你可以在保存脚本后立即运行脚本;不需要编译它或任何其他操作。
要运行 shell 脚本(让 shell 读取它并执行脚本中的所有命令),您可以在交互式 shell 提示符下输入命令,就像您执行其他操作时一样(如果您使用的是图形用户界面,您也可以通过单击鼠标来执行脚本)。在这种情况下,您要启动的程序本身就是 shell 程序。例如,要运行名为 MyScript 的脚本,您可以在交互式 shell 中输入以下命令(假设脚本位于您的工作目录中)
sh MyScript
从 shell 程序内部启动 shell 程序乍听起来可能很奇怪,但如果您仔细想想就会明白。毕竟,您在交互模式的 shell 会话中键入命令。要运行脚本,您需要在非交互模式下启动一个 shell。这就是上面命令中发生的事情。您会注意到,在上面的示例中,Bourne Shell 可执行文件接收一个参数:要执行的脚本的名称。
如果您碰巧使用的是符合 POSIX 1003.1 标准的 shell,您也可以在这个新的非交互式会话中执行单个命令。您必须使用 -c 命令行开关来告诉 shell 您正在传递一个命令,而不是脚本的名称。
sh -c ls
我们将在稍后进一步了解为什么要这样做(而不是简单地直接将您的命令输入交互式 shell)。
还有一种从交互式 shell 运行脚本的方法:键入执行命令(一个点)后跟脚本的名称。
. MyScript
它与使用 sh 命令的区别在于,sh 命令启动一个新的进程,而执行命令不会启动新进程。我们将在下一节中讨论这个问题(及其重要性)。顺便说一下,这种带点的表示法通常被称为源代码一个脚本。
还有一种方法可以执行 shell 脚本,即更直接地使用 Unix 操作系统的一个功能:可执行模式。
在 Unix 中,每个文件都有三个不同的权限(读、写和执行),可以针对三个不同的实体设置:拥有该文件的用户、该文件所属的组以及“世界”(所有其他人)。输入以下命令
在交互式 shell 中查看工作目录中所有文件的权限(包含最多九个字母的列,r、w 和 x 代表读、写和执行,前三个代表用户,中间三个代表组,后面的三个代表世界)。只要其中一个实体具有“执行”权限,该实体就可以简单地将该文件作为程序运行。要使您的脚本可供所有人执行,请使用以下命令
就像这样
chmod +x MyScript
然后,您可以使用简单的命令执行该脚本(假设它位于您的 PATH 中的目录中,即 shell 在您没有告诉它确切的位置时搜索程序的目录)
MyScript
如果失败,那么当前目录可能不在您的 PATH 中。您可以使用以下方法强制执行该脚本
./MyScript
在该命令下,操作系统会检查该文件,将其放入内存并允许它像任何其他程序一样运行。当然,并非所有文件都有意义作为程序;二进制文件不一定是计算机可以识别的一组命令,而文本文件根本无法被计算机读取。因此,要使我们的脚本像这样运行,我们必须做一些额外的操作。
正如我们之前提到的,Unix 操作系统首先会检查程序。如果程序是文本文件而不是二进制文件(不能简单地执行),操作系统会期望文件的第一行命名解释器,操作系统应该启动该解释器来解释文件的其余部分。Unix 操作系统期望找到的行如下所示
在我们的例子中,以下行几乎可以在任何地方使用
Bourne Shell 可执行文件,位于 bin 目录中,该目录位于文件系统树的顶层下方。例如
以这种方式执行 shell 脚本有几个优点。首先,它比其他表示法更简洁(需要更少的输入)。其次,如果您要将脚本传递给他人,这是一种额外的安全措施。您不必依赖他们拥有正确的 shell,而只需要指定他们应该使用哪个 shell。如果 Bourne Shell 足够,您就可以要求使用它。如果您绝对需要 ksh 或 bash,您可以指定使用它们(注意,这并非万无一失——其他人可以通过使用我们上面讨论过的其他命令之一来运行您的脚本,即使脚本可能无法正常运行,即使他们这样做了)。
顺便说一句,Unix 并不将这种技巧限制在 shell 脚本上。任何期望其脚本为纯文本的脚本解释器都可以用这种方式指定。您可以使用相同的技巧来创建直接可执行的 Perl 脚本或 Python、Ruby 等脚本,以及 Bourne Shell 脚本。
还要注意,在使用 bash 作为默认 shell 的发行版中,您可以使用 #!/bin/sh shebang 并使用典型的 bash 语法在您的脚本中。它可以工作。但是,为了使相同的脚本在不使用 bash 作为默认 shell 的发行版(例如 Debian)中也能正常工作,您必须修改该脚本或将它的 shebang 更改为 #!/bin/bash。
虽然这不是一本关于 Unix 的书,但我们必须了解 Unix 操作系统的一些方面,才能完全理解 Bourne Shell 偶尔为何以这种方式工作。
Unix 操作系统最重要的方面之一——实际上,它是将它与所有其他主流操作系统区分开来的主要方面——是 Unix 操作系统一直都是一个多用户、多处理操作系统(与其他操作系统相比,例如 MacOS 和 Microsoft 的 DOS/Windows 操作系统)。Unix OS 一直旨在运行由多个用户同时使用的机器,这些用户都希望同时运行至少一个,甚至可能是多个程序。操作系统将机器处理器的运行时间分配给多个程序的能力,使其对用户来说似乎都在同时运行,被称为多处理。Unix 操作系统从核心开始就被设计为具有这种可能性,它会影响 shell 会话的行为方式。
每当您在 Unix 机器上启动一个新进程(例如,通过运行一个程序)时,操作系统都会为该进程提供它自己的操作环境。该环境包括一些供进程使用的内存,还可以包括所有进程的某些预定义设置。每当您运行 shell 程序时,它都在自己的环境中运行。
每当您从另一个进程启动一个新进程时(例如,在交互模式下向您的 shell 程序发出命令),新进程就会成为第一个进程的子进程(例如,ls 程序作为您的 shell 的子进程运行)。这就是了解多处理和进程交互变得重要的原因:子进程总是以副本的形式开始父进程的环境。这意味着两件事
- 子进程永远无法更改其父进程的操作环境——它只能访问该环境的副本;
- 如果您确实想要更改 shell 的环境(或者具体而言,想要避免更改它),您必须了解何时命令作为子进程运行,何时命令在当前 shell 中运行;否则,您可能会选择一个与您想要的结果相反的效果。
我们已经看到了几种运行 shell 命令或脚本的方法。关于多处理,它们以以下方式运行
运行方式 | 运行为 |
---|---|
交互模式命令 |
|
Shell 非交互模式 | 子进程 |
点符号运行命令 (. MyScript) | 当前环境 |
通过 Unix 可执行权限和解释器选择 | 子进程 |
通过以上内容,您可能会认为多进程在 shell 脚本中很麻烦。但是,如果真是这样,我们就不会有“多进程”了——Unix 不会保留无用之物。多进程是与系统其余部分交互的宝贵工具,可以用来提高工作效率。关于多进程在程序开发中的优势有很多书籍,但从 Bourne Shell 用户和脚本编写者的角度来看,主要优势是可以将进程的控制权交给操作系统, *并在子进程运行时继续工作*。实现此目的的方法是将您的进程作为 *后台进程* 运行。
将进程作为后台进程运行意味着告诉操作系统您想启动一个进程,但它不应附加到其父进程正在使用的任何交互式设备(键盘、屏幕等)。更重要的是,它还告诉操作系统应立即返回启动子进程的请求,并且应允许父进程继续工作,而不必等待其子进程结束。
这听起来很复杂,但您必须记住,这种能力完全融入到 Unix 操作系统中,Bourne Shell 旨在作为 Unix 强大功能的简单接口。换句话说:Bourne Shell 包括以其自身简单命令的形式启动子进程的能力。让我们通过一个例子来演示如何做到这一点以及这种能力有多有用。在提示符下输入以下(有点无用但仍很耗时)命令:
- N=0 && while [ $N -lt 10000 ]; do date >> scriptout; N=`expr $N + 1`; done
我们将在后面的章节中介绍它的含义;现在,您只需知道此命令向系统请求日期和时间,并将结果写入名为“scriptout”的文件。由于它随后重复此过程 10000 次,因此可能需要一些时间才能完成。
现在输入以下命令:
- N=0 && while [ $N -lt 10000 ]; do date >> scriptout; N=`expr $N + 1`; done&
您会注意到,您可以立即恢复使用 shell(如果您没有看到这种情况,请按 Ctrl+C 并检查您在末尾是否添加了额外的 ampersand)。一段时间后,后台进程将完成,scriptout 文件将包含另外 10000 次时间读取。
在 Bourne Shell 中启动后台进程的方法是在命令后面附加一个 ampersand (&)。
^ 实际上,您也可以在这里强制执行子进程 - 我们将在讨论命令分组时了解如何执行。