当前位置:文档之家› sh,ksh,csh介绍

sh,ksh,csh介绍

第10章shell II (sh)

Bourne shell(s h)、C shell(c s h)以及Korn shell(k s h)是命令解释程序及高级编程语言。作为命令解释程序,这些s h e l l处理用户在命令行提示符下所输入的命令。当用户把一个s h e l l作为一种编程语言使用时,s h e l l处理存储在称为s h e l l脚本的文件中的一组的命令。与其他语言一样,s h e l l具有变量和控制流命令(例如f o r循环和i f语句)。

使用s h e l l,用户可以自定义工作环境。可以使自己的提示符显示工作目录名,为c p命令创建一个函数或者别名,以防止它覆盖文件,另外还可以利用关键字变量来改变s h e l l的工作方式,等等。用户也可以编写s h e l l脚本来做希望的事情:从存储了一个长而复杂命令的单行脚本(可以使用户不必再输入它们)到运行一系列报告(为这些报告自动生成参数),然后打印它们,并在作业完成的时候发信提醒用户。另外更复杂的s h e l l脚本则本身就作为程序,它们不仅仅运行其他的程序(参见第11、1 2和1 3章的示例)。

通常情况下,用户的根s h e l l总是单用户模式的Bourne shell 。所有的系统s h e l l脚本都编写

为在Bourne shell下运行—如果用户在单用户模式下进行工作(或者是在引导系统的过程中,或者是进行系统维护、管理或修复工作),这是个不错的主意,至少可以熟悉这个s h e l l。

提示本章覆盖哪种s h e l l本章内容倾向于Bourne shell,并使用注意事项和参考页来

说明与C shell或Korn shell不同的地方。特别是1 0.2节、1 0.3节及1 0.6节的内容,既适

用于Bourne shell,同样也适用于C shell和Korn shell。

作业控制 1 0.5节不适用于Bourne shell,因为s h不支持作业控制。Job shell(jsh)等同于Bourne shell,但是支持作业控制的完全实现。如果用户想尝试作业控制命令,要

确保用户正在运行j s h而不是s h。c s h和k s h也支持作业控制,并且用户也可以使用它们

进行尝试。

如果用户都不熟悉这些s h e l l用户可能想推迟阅读本章的1 0.5节“作业控制”和本节,直到用户能够熟悉创建并运行简单的s h e l l脚本。无论怎样,用户应该阅读1 0.7

节“参数和变量”。除了用户创建的变量,s h e l l维持了几个关键字变量,这些变量控

制着s h e l l的重要的特性。

s h e l l编程因为许多用户相对于c s h编程语言来说,更喜欢使用Bourne shell编程语言,并且由于Bourne shell与Korn shell编程语言共享了许多通常的特性,所以本章

和接下来的一章详细描述了s h(和k s h)编程。

10.1 Bourne shell 背景

Bourne shell是一个早期的UNIX shell,这个s h e l l是AT &T的贝尔实验室的Steve Bourne所编写的。经过这些年,Bourne shell已经被扩展,并且仍然是与U N I X许多商业版本一起提供给用户的基本的s h e l l。由于Bourne shell悠久而成功的历史,许多帮助用户管理和使用S o l a r i s的s h e l l 脚本都是使用Bourne shell写的。

POSIX shells

P O S I X标准化组织已经为s h e l l功能定义了一个标准(POSIX 1003.2)。Bourne shell提供了符合这个P O S I X标准所要求的功能。

10.2 创建一个简单的s h e l l脚本

一个s h e l l脚本是一个包含s h e l l能够执行的命令的文件。在一个s h e l l脚本中的命令可以是用户在一个s h e l l提示中输入的任何命令。例如,在一个s h e l l脚本中的命令可能运行一个S o l a r i s命令,一个用户所编写的编译程序,或者是其他的s h e l l脚本。当使用用户在命令行给出的命令时,s h e l l脚本中的命令可以使用模糊文件引用,并能将输入重定向为来自一个文件或将输出重定向到一个文件或通过一个管道运送。用户也可以使用管道和以脚本做为输入和输出的重定向。

除了用户通常在命令行中使用的命令外,还有一组命令,即控制流命令(也称为控制结构,control structure),这组命令大多数应用在s h e l l脚本中。当用户想使用一个典型的结构化编程语言来更改命令的执行顺序时,那么控制流命令可以使用户更改一个脚本中的命令的执行顺序。

运行一个s h e l l脚本的最简单的方法是在命令行中给出它的文件名。然后这个s h e l l在脚本中一个接一个地解释并执行这个命令。通过使用s h e l l脚本,用户可以简化并快速地开始一个复杂的任务序列或者一个重复的过程。

使用c h m o d使文件可执行

就像使用一个命令一样,通过给出一个s h e l l脚本名字就可以执行它,但用户必须对包含这个脚本的文件具有读和执行的权限。执行权限告诉s h e l l和系统,文件的所有者、组或者公共用户具有执行此文件的权限。它同时也暗示了此文件的内容是可执行的。

当用户使用一个编辑器(例如v i)来创建一个s h e l l脚本时,文件没有执行权限的设置。下面的示例显示了一个文件w h o s o n,这是一个包含三个命令行的s h e l l脚本。当用户最初创建一个文件,如w h o s o n,用户不能像给出一个命令一样通过给出它的名字就执行它,因为用户还没有执行的权限。

s h e l l 不能将w h o s o n 识别为一个可执行的文件,并且当用户试图执行它时给出一个错误消息。用户可以通过给出这个文件名作为s h (sh whoson )的一个参数来执行它。当用户这么做时,s h 将这个参数作为一个s h e l l 脚本并且执行它。在这种情况下,s h 是可执行的,并且w h o s o n 作为s h 所执行的一个参数,所以用户不必具有执行w h o s o n 的权限。用户可以使用c s h 和k s h 完成同样的事情。

用户可以使用c h m o d 命令来改变与一个文件相关联的访问权限。图1 0-1展现了使用- l 选项的l s 命令所列出的在使用c h m o d 命令给用户执行权限之前及之后w h o s o n 文件的许可权限。

图10-1 使用c h m o d 命令来使得一个s h e l l 脚本可执行

第一个l s 命令显示了一个连字符(-)作为第四个字符,指出了所有者没有执行这个文件的权限。然后,c h m o d 命令使用一个参数赋予文件所有者执行权限。u +x 使得c h m o s 命令增加(+)可执行权限(x )给所有者(u )(u 代表u s e r ,尽管实际上u 是指文件的所有者,这个所有者可能会是任意时刻的文件使用者)。第二个参数是文件的名字。第二个l s 命令在第四个字符的位置显示了一个x ,指出了所有者现在具有了执行的权限。

如果其他用户要执行这个文件,用户也必须改变组和(或)公共用户的访问权限。任何用户想使用文件名作为一个命令,就必须对这个文件具有执行权限。如果这个文件是一个s h e l l 脚本(一个s h e l l 命令文件),那么试图执行这个文件的用户也必须对这个文件具有读权限。用户不需要读访问权限来执行一个二进制可执行的文件(已编译的程序)。

最后,当文件名作为一个命令给出时,s h e l l 将执行这个文件。如果用户在s h e l l 提示符下输入w h o s o n ,并且得到whoson:command not found 之类的错误消息时,表示用户的登录s h e l l 没有被设置成为可在工作目录中搜索可执行的文件。这时可以试着使用以下命令:

$. /w h o s o n

. /明确地告诉了s h e l l 在工作目录中寻找一个可执行文件。为了改变用户的环境以便s h e l l 查找这个工作目录,可以参考第1 0.7.2节的PA T H 变量。

现在,用户知道了如何写简单的s h e l l 脚本并执行它。接下来的两节内容描述了一些功能,当用户在命令行或在一个脚本内运行命令时,这些功能很有用。另外,1 0.5节解释了命令、s h e l l 脚本以及S o l a r i s 系统进程之间的关系。它同时也说明了一个s h e l l 脚本是如何被调用并运行

的,另外还描述了它运行的环境。

10.3 命令分隔及组合

当用户输入s h e l l命令或者编写一个s h e l l脚本时,用户必须将命令一个一个分隔开。本节回顾了第5章所包括的进行命令分隔的有关方法,并且介绍了几个新的方法。本节同样适用于Bourne shell、C shell及Korn shell。

10.3.1 使用“;”和NEWLINE 分隔命令

N E W L I N E字符是一个独特的命令,因为它可以启动位于它之前的命令的执行过程。在这本书中,每次用户在一个命令行的末尾按R E T U R N键时,便可以看到这一点。

分号(;)是一个命令分隔符号,这个分隔符号不能启动一个命令的执行程序,也不改变命令的行为表现。用户可以通过在单个命令行中输入一系列的命令并且使用分号(;)来分隔命令,从而按顺序执行这些命令。为了启动命令序列的执行程序,用户必须使用R E T U R N键来作为命令行的终止符。

$x; y; z

如果以上的x、y和z是命令,那么前面的命令行将产生与下面三个命令行相同的结果。不同之处是在下面的示例中,s h e l l在每一个命令(x、y和z)结束执行之后产生一个提示,而前面的命令行使得s h e l l只在z命令完成之后产生一个提示。

$x

$y

$z

尽管在前面示例中的分号周围的空格使命令行更容易阅读,但是这是不必要的。命令分隔符号不需要被S PA C E或TA B键所包围。

10.3.2 使用\ 继续一个命令

当用户正在输入一个非常长的命令行,并且用户已经到了用户显示屏幕的最右边时,用户可以使用一个反斜杠(\)字符来在下一行中继续此命令。反斜杠引用或者转义了跟在它后面的N E W L I N E字符,以便s h e l l不会将它视为命令的终止符。

10.3.3 使用|和& 分隔命令并完成其他事情

其他的命令分隔符是管道符号(|)和后台任务符(&)。这些命令分隔符不能启动命令的执行过程,而是改变命令的一些行为表现。管道符号可以更改标准输入的来源或标准输出的目的地,而后台任务符使s h e l l在后台执行这项任务,这样用户能立刻得到返回的提示符,并可以继续其他的工作。

下面的每一个命令行都将启动包含三项任务的一个作业:

在第一项作业中,s h e l l 将任务x 的输出定向到任务y ,并将y 的输出定向到z 。由于s h e l l 在前台运行整个的作业,所以用户直到任务z 运行完后才得到返回的提示符(并且任务z 直到任务y 结束之后才结束,而任务y 直到任务x 结束之后才结束)。在第二项作业中,x 是一个ls -l 命令,任务y 是grep tmp 命令,而任务z 是标页符命令,p g 。用户以一个长的(宽的)列表结束,这个列表是工作目录中所有文件的文件名列表,它包含了字符串t m p ,通过p g 命令进行管道运输。

下一个命令行在后台执行任务d 和任务e ,在前台执行任务f 。这个s h e l l 显示了方括号(j s h ,c s h ,k s h )之间的作业编号以及为每一个后台运行的进程分配的P I D (进程标识符)号。f 命令一结束,用户就可以得到一个返回的提示符。

在s h e l l 为一个新的命令显示一个提示符之前,它检查是否有后台作业已经完成。对于每一个已经完成的作业,s h e l l (j s h ,c s h ,k s h )将显示它的作业编号,即字样D o n e 以及调用此作业的命令行;然后s h e l l 将显示提示符。当作业编号被列出时,最后一个开始的作业的作业编号后面跟着一个加号(+),而前面的作业的作业编号后面跟着一个减号(-)。任何列出来的其他作业将显示一个S PA C E 字符。在运行了最后一个命令之后,s h e l l 在产生提示符之前,显示下面的信息:下面的命令行全部在后台执行所有的三项任务。用户可以立即得到一个s h e l l 提示符:

用户可以使用管道来从一个任务发送输出结果到接下来的任务中,并且使用&符号来将整个作业作为一个后台任务运行。提示符将再次立即返回。s h e l l 将以一个管道连接的命令看作是单一的作业。所有的管道都被s h e l l 看作是单一的作业,而不管使用管道符(|)连接了多少个任务以及它们有多么复杂。C shell 显示了放置在后台中的三个进程(所有的进程都属于作业1)。这个作业和Korn shells 仅仅显示一个进程。

选读内容

用户可以演示正在前台和后台运行的连续的和同时发生的进程。创建名为a 、b 和c 的可执行文件,并且类似于以下对文件a 的操作那样对每个文件使用e c h o (s h ,c s h ,k s h )命令来回

322计计第二部分S o l a r i s 中、高级知识下载

替分布的。这些结果在每次运行不总是相同的,因为每次运行时

所不同。同时执行并不能保证比连续进行完成更快,所有的后台执行只是保证能够更快地返回提示符。以下是两个所运行的示例:

10.3.4 使用括号()来组合命令

用户可以使用括号()来组合命令。s h e l l 可以创建它自己的一个备份,称为子s h e l l ,对于每一个组,子s h e l l 将每组命令看作是一项作业,并创建一个新的进程来执行命令组的每一个命令。每一个子s h e l l (作业)具有自己的环境—其中,这意味着它有自己的变量集,这些变量的值可以与其他子s h e l l 的变量值不同。

接下来的命令行在后台连续地执行命令a 和命令b ,同时也在后台执行命令c 。s h e l l 立即返回提示符。

这个示例与前面的示例a &b &c &不同,因为任务a 和任务b 是连续地开始运行,而不是同时开始的。

同样,下面的命令行在后台连续地执行a 和b ,同时在后台连续地执行c 和d 。运行a 和b 的子s h e l l 与运行c 和d 的子s h e l l 是同时运行的。s h e l l 立即返回提示符。

在下面的s h e l l 脚本中,第二对括号创建了一个子s h e l l 来运行跟在管道后面的命令。由于这些括号,第一个t a r 命令的输出对于第二个t a r 命令来说是可用的,虽然其中插入了c d 命令。若没有这些括号的话,第一个t a r 的输出将被送给c d 而丢失,因为c d 命令不从标准输入中接收输入。$ 1和$ 2是s h e l l 变量,它们代表了第一个和第二个命令行参数。第一对括号是有必要的,它创建了一个子s h e l l 来运行头两个命令,以便用户能使用相对路径名调用c p d i r 命令。没有这些括号,第一个c d 命令将改变脚本的工作目录(并且,同时地,改变第二个c d 命令的工作目录),而带有这些括号,只有子s h e l l 的工作目录被改变。

前面的命令行将包括在/ h o m e / a l e x / s o u r c e s 目录下的文件和子目录复制到名为

/ h o m e /a l e x /m e m o /b i b l i o 目录下。这个s h e l l 脚本几乎与使用带有- r 选项的c p 命令相同。要得到更多关于子s h e l l 的信息,可参见1 0.5节。参见第三部分获得关于c p 命令和t a r 命令的更多信息。324计计第二部分S o l a r i s 中、高级知识下载

10.4 重定向标准错误第5章包括了标准输出的概念,并解释了如何重定向一个命令的标准输出。除了标准输出之外,命令还可以将它们的输出送到另一个地方:标准错误。一个命令可以将错误消息发送到标准错误,以避免与发送到标准输出的信息混淆。正如s h e l l 对待标准输出一样,除非用户进行了重定向,否则s h e l l 将一个命令的标准错误送到终端。因而,除非用户进行重定向,否则用户可能不知道一个命令送到标准输出的输出和送到标准错误的输出之间的不同之处。本节包括了Bourne shell 和Korn shell 使用的语法。

当用户执行一个程序时,运行这个程序的进程打开三个文件描述符,程序将输出发送到这些地方,并且从0(标准输入)、1(标准输出)和2(标准错误)中得到它的输入。重定向输出符号(>)是1 >的速记,告诉了s h e l l 重定向标准输出。同样,<符号是< 0的速记,重定向标准输入。符号2 >重定向标准错误。这个程序不知道它的输入来自哪里,它的输出要送到哪里;这一点由s h e l l 负责区分。为获得更多的信息,参见1 3.3.4节。

下面的示例展示了如何重定向标准输出和标准错误到不同的文件和到相同的文件。当用户使用不存在的文件的文件名和现有的文件的文件名运行c a t 命令时,c a t 命令发送一个错误消息到标准错误,并复制现有的那个文件到标准输出。除非用户重定向它们,否则它们的信息都显示在终端上。当用户使用大于号(>)重定向一个命令的输出时,错误输出不会受影响—它仍然显示在屏幕上。同样,当用户通过一个管道发送标准输出时,标准错误也不会受影响。在接下来的示例中, c a t 命令的标准输出通过一个管道发送给t r 命令,这个命令在这个示例中将小写字符转换成大写字符。c a t 命令送到标准错误的文本不会被转换,因为它直接到屏幕而不是通过管道。

下面的示例重定向标准输出和标准错误到不同的文件。符号2 >告诉s h e l l 将标准错误重定向到何处(文件描述符2)。1 >告诉s h e l l 将标准输出重定向到何处(文件描述符1)。用户可以使用>来代替1 >。

第1 0章shell II (sh)计计325

下载

在接下来的示例中,1 >重定向标准输出到h o l d。然后2 >&1声明了文件描述符2是文件描述符1的一个副本。结果是标准输出和标准错误都被重定向到h o l d。

在前面的示例中1 >h o l d在2 >&1的前面。如果它们没有以相反的顺序列出,那么在标准输出被重定向到h o l d之前,标准错误将被重定向为标准输出的一个副本。在那种情况下,只有标准输出被重定向到文件h o l d中。

接下来的示例声明了文件描述符2是文件描述符1的一个副本,并且通过一个管道为文件描述符1将输出送给t r命令:

用户也可以使用1 >&2来重定向一个命令的标准输出到标准错误。这项技术经常在s h e l l脚本中使用,用来将e c h o命令的输出送到标准错误中。在下面的脚本中,第一个e c h o的标准输出被重定向到标准错误中:

如果用户重定向m e s s a g e_d e m o的标准输出,那么像上面由第一个e c h o产生的错误消息仍然会发送到屏幕上(因为用户没有重定向标准错误)。由于一个s h e l l脚本的(标准)输出通常被重定向到另一个文件中,所以这项技术经常使用,以便由脚本产生的错误消息显示在屏幕上。l i n k s脚本和下一章中的几个其他的脚本使用了这项技术。

用户也可以使用e x e c内置命令(s h,c s h,k s h)来创建其他的文件描述符和重定向s h e l l脚本

中的标准输入、标准输出和标准错误。

10.5 作业控制

一项作业是一个命令传输途径。无论什么时候用户给S o l a r i s一个命令(例如在命令行中输入d a t e命令,然后按R E T U R N键,用户便运行一项作业),便可运行一项简单的作业。另外,用户也可以在一个单个的命令行中使用多个命令来创建几个作业。

内置命令是一种嵌入在s h e l l中成为s h e l l一部分的命令,此处s h e l l是这个内置命令名字后面跟着的括号中所列

出的shell (s h,c s h,k s h)。所有在s h中可用的内置命令在jsh 中都是可用的。使用命令man shell_builtins可以看到s h(和其他)内置命令的列表。

上面命令行中,到第一个&为止的命令行部分是一项作业。它由三个进程组成( f i n d、s o r t 及l p),这三个进程通过管道连接。第二项作业是一个单一的进程,运行g r e p命令。这两个作业通过后面的&字符被放入后台运行,所以s h不用等待它们完成就给用户提示。在提示之前,s h e l l显示关于每个后台作业的信息:在方括号中的作业号,后面跟着此项作业中最后进程的P I D。l p命令生成其常规的信息输出。

使用作业控制,用户可以将命令从前台移到后台,反之亦然,用户可以暂时停止作业及

得到目前正在运行的(或已经停止的)命令的列表。Berkeley UNIX C shell支持作业控制已经有很多年了。而在运行System V的计算机中,C shell中作业控制功能通常未实现。

10.5.1 使用j o b s列出作业

j o b s内置命令(jsh , csh , ksh)可列出所有的后台作业。接下来的命令序列表明了当用户从Bourne shell和Job shell中输入j o b s命令时所发生的情况。当用户从Bourne shell中输入j o b s命令时运行,其中有一个名为j o b s(/ u s r/b i n/j o b s)的命令将运行。它不生成任何输出,但是它也不生成一个错误。当用户启动Job shell时,它会增加一些命令到包括j o b s的这个环境中(与内置命令类似)。接下来,s h确保了用户正在使用Bourne shell,在后台运行的s l e e p命令创建了一个j o b s可以报告的后台作业,而j o b s(/ u s r/b i n/j o b s)不报告任何作业。接下来,j s h命令使用户进入Job shell环境中,s l e e p命令像上面一样执行,而j o b s环境命令则显示作业信息。

10.5.2 使用fg 将一个作业移到前台

正如前面所提到的,s h e l l(除了Bourne shell)分配一个作业编号给用户在后台运行的任何命令。在下面的示例中,有几个作业在后台启动运行。对于每个作业,Job shell在产生一个提示符之前将列出作业编号和P I D号。

j o b s命令列出了第一个作业x m a n,它作为作业1。d a t e命令不在作业列表中出现,因为在j o b s 运行之前它就完成了。由于date命令在find命令运行之前已经完成,因此find命令称为作业2。

要将一个后台作业移到前台,可以使用带有一个百分号(%)的f g内置命令(j s h,c s h,k s h),后面跟随作为参数而使用的作业编号。下面的示例将作业2移到前台:

用户也可以通过在百分号后面跟着一个字符串来指代某项作业,这个字符串唯一地指定了用来启动这项作业的命令行的开始。例如,用户可以使用fg %find 或者fg %f来代替上面的命令,因为任一个都唯一地指定了作业2。如果用户在百分号后面跟有一个问号和一个字符串,那么这个字符串在命令行中的任何位置进行匹配。在上面的示例中% ?a c e也是指代作业2的。

通常,用户希望移到前台的作业仅仅是运行在后台的作业或者是使用一个加号(+)的j o b s列出的作业。在这些情况下,用户可以使用不带有任何参数的f g命令。

10.5.3 使用b g将一项作业移到后台

要将当前的前台作业转移到后台,用户必须首先按下C O N T R O L-Z键或者是C O N T R O L-Y 键来挂起这项作业。按C O N T R O L-Z将立即停止这项作业;按C O N T R O L-Y使得此项作业继续运行,直到它试图从终端读取输入,此时这项作业将挂起。一旦这项作业被挂起,使用b g内置命令(j s h,c s h,k s h)可重新开始这项作业的执行,并将它移到后台。

$ b g

如果一个后台作业试图从终端读取数据,则s h e l l将使它停止(使它处于休眠状态),并通知用户这项作业已经被停止,并等待输入。当发生了这种情况,用户必须将这项作业移到前

台,以便它可以从终端获得输入。当将这项作业移到前台时,s h e l l将显示命令行。

在这个示例中,这个后台作业一开始,s h e l l就显示了它的作业编号和P I D号,然后出现一个提示符。在此处,用户输入d a t e命令,它的输出显示在屏幕上。直到在它产生一个提示符(在d a t e命令完成之后)之前s h e l l将等待通知用户作业1正在等待输入。产生这个延迟的原因是

激活这些功能的热键是可以改变的。为获得更多的信息,参见第三部分的“作业控制参数”。

如果用户想改变这项缺省的行为,参见9 04页的“t o s t o p”

因为这个通知不会中断用户的作业—这是s h e l l缺省的行为。在s h e l l把这项作业转移到前台之后,用户可以输入命令所正在等待的输入。使用C O N T R O L-D键来表示E O F(文件结尾)以结束输入,此时s h e l l将显示另一个提示符。

s h e l l始终通知用户某项作业的状态改变:当一个后台作业启动、完成或者正在等待从终端的输入时,它都会通知用户。当一个前台的作业被挂起时,它也让用户知道。由于有关某项在后台运行作业的通知会干扰用户的工作,因此s h e l l将延缓通知其信息,直到它准备好显示下一个提示符。

如果用户具有已停止作业的情况下试图离开s h e l l,那么s h e l l给用户一个警告,并不允许用户退出。在这个警告之后,用户可使用j o b s来检查作业列表,或者立即再次试图离开s h e l l,此时s h e l l可允许用户离开,并结束用户的已停止作业。此时运行在后台的作业(没有被停止)将继续运行。在下面的示例中,在第二个e x i t命令停止s h e l l之后, find(作业1)继续运行,而c a t (作业2)被停止。

10.6 进程

一个进程是S o l a r i s对一个命令的执行过程。当用户登录时所启动的s h e l l是一个命令,或者是一个进程。无论用户什么时候在命令行中给出一个S o l a r i s命令名,一个进程就开始了。当用户运行一个s h e l l脚本时,另一个s h e l l进程将启动,并且为脚本中的每一个命令创建额外的进程。根据用户调用这个s h e l l脚本的不同方式,这个脚本或者由一个新的s h e l l运行,或者由目前s h e l l 的子s h e l l运行。当用户从命令行或者一个脚本中运行像c d(s h,c s h,k s h)这样的s h e l l内置命令时,并不会启动一个进程。本节的内容适用于Bourne shell、C shell及Korn shell。

10.6.1 进程结构

和文件结构一样,进程结构是层次化的。进程结构有父进程、子进程,甚至一个根进程。一个父进程可以分叉出(f o r k)子进程,子进程依次也可以分叉出其他的进程,(用户也可以

使用术语分散(s p a w n);这两个词是可以相互交换的)。创建一个新进程的操作系统例程或者

术语分叉(f o r k)用来表示这样的事实:就象路中有一个分岔一样,一个进程变成两个进程。最初这两个分

支是相同的,除了一个被标识为父进程,而另一个被标识为子进程。

系统调用,被命名为f o r k 。当一个计算机启动时,S o l a r i s 为开始执行所做的第一件事情就是启动i n i t 命令—一个单一的进程,被称作自发进程,其P I D 号为1。这个进程在进程结构中的位置与根目录在文件结构中的位置相同。它是每一个用户所使用的进程的祖先。其中,i n i t 命令运行一个名为t t y m o n 的进程,这个进程监视用户可以登录的所有线路,并当它检测某一线路处于活动时,便启动一个l o g i n 进程。在用户登录后,登录进程便成为用户的s h e l l 进程。

10.6.2 进程标识

S o l a r i s 在每个进程的开始分配一个唯一的P I D 号。只要一个进程存在,它就保持相同的P I D 号。在一个会话期间,相同的进程总是执行登录s h e l l 。当用户对一个新的进程进行分叉时—例如当用户使用一个编辑器时,新的(子)进程就有一个与它的父进程不同的P I D 号。当用户返回到登录shell 时,用户将发现它仍然由用户登录时的相同的进程所执行,并具有相同的PID 号。

下面的交互示例显示了运行s h e l l 的进程分叉了运行p s 命令的进程(也就是说运行s h e l l 的进程是运行p s 命令的进程的父进程)。当用户调用带有- l 选项的p s 命令时,它显示了关于每个进程的长信息列表。p s 显示中,在C M D 列显示为S H 的那一行指代了运行s h e l l 的进程。而以P I D 开头的列则列出了P I D 号。以P P I D 开头的列,列出了每个进程的父进程的P I D 号。从P I D 和P P I D 列中,用户可以看出运行s h e l l 的进程(P I D 为1 8835)是运行s l e e p 进程(P I D 为1 8836)的父进程。s l e e p 的父进程的P I D 号与s h e l l 的P I D 号(1 8835)相同。参考第三部分以获得有关p s 命令以及它和-l 选项一起使用时显示的所有列的信息。

当用户输入另一个ps -l 命令时,用户可以看出s h e l l 仍然由相同的进程运行,但是它分叉出

另一个进程来运行

s l

e e p 。

用户可以用来察看进程的父/子关系的另一个工具是p t r e e (位于/ u s r /p r o c /b i n 目录)。用户可以在命令行中在p t r e e 后面跟随一个用户名或一个P I D 号。下面的示例运行p t r e e 命令三次,两次使用j e n n y ,一次使用3 64(J e n n y 的s h e l l 的P I D 号)。那些分叉出J e n n y 的s h e l l 进程的P I D 号与J e n n y 的s h e l l 本身的P I D 号一样,自始至终保持不变。当每次调用分叉出一个新进程时,p t r e e 进程将在每次调用时改变一次它的P I D 号。

10.6.3 执行一个命令

当用户为s h e l l输入一个命令时,s h e l l通常分叉(分散)出一个子进程来执行这个命令。当这个子进程执行这个命令时,父进程进入休眠状态。当一个进程休眠时,它不使用任何计算机时间;它保持非活动的状态并等待醒来。当子进程执行完这个命令,它通过退出状态告诉它的父进程执行过程是成功还是失败,然后子进程死去。父进程(正在运行s h e l l的进程)将醒来并提示用户输入其他的命令。

当用户通过在命令后面使用一个&符号来要求s h e l l在后台运行一个进程时,s h e l l将在不进入休眠状态也不等待子进程运行结束的情况下,来分叉出一个子进程。执行s h e l l的父进程报告了作业编号和子进程的P I D号,并提示用户输入其他命令。子进程独立于它的父进程,在后台运行。

尽管s h e l l通过分叉出一个进程的方式来运行用户给出的大多数命令,但是一些命令内置在s h e l l中。s h e l l不必分岔出一个进程来运行内置命令。要了解内置命令,可以参见11 .3.3节的表11 -5或者输入命令man shell_buildins。

在一个给定的进程内,例如用户的登录s h e l l或一个子s h e l l中,可以声明、初始化、读取或修改变量。然而,在缺省情况下。变量对于进程而言是本地的,当一个进程分岔出一个子进程时,父进程不会传递变量值给子进程。但用户通过使用e x p o r t(s h,k s h)内置命令,可使一个变量的值对于子进程可用。

10.6.4 调用一个s h e l l脚本

除了内置到s h e l l中的命令(s h e l l的内置命令)以外,无论什么时候用户在命令行中为s h e l l 输入一个命令,s h e l l将启动f o r k进程,它可以创建s h e l l进程的一个副本(也就是说,一个子s h e l l)。这个新进程试图启动e x e c进程,或者是执行该命令。与f o r k相同,e x e c是一个由操作系统执行的例程(一个系统调用)。如果这个命令是一个可执行的程序(如一个编译过的C程序),则e x e c成功启动,并且系统使用这个可执行程序覆盖新近创建的子s h e l l。如果这个命令是一个s h e l l脚本,e x e c启动失败。当e x e c启动失败时,此命令被假定为一个s h e l l脚本,这个子s h e l l在脚本中运行这些命令。与从命令行中获得输入的用户登录s h e l l不同,子s h e l l从一个文件,即

s h e l l 脚本中获得它的输入。

正如原先所讨论的一样,如果用户有一个s h e l l 脚本,而这个脚本位于一个用户对它没有执行权限的文件中,那么用户可以通过使用s h 命令来执行一个s h e l l 来运行脚本中的命令,进而直接运行这个脚本。在下面的示例中,s h 创建了一个新的s h e l l ,这个s h e l l 从名为w h o s o n 的文件中获得输入:由于s h 命令期待读取包含命令的一个文件,所以用户不需要对w h o s o n 文件具有执行权限(然而用户需要读权限)。尽管s h 命令读取并执行文件w h o s o n ,但是标准输入、标准输出和标准错误仍然与终端相连。用户可以以同样的方式使用c s h 和k s h 。

尽管用户可以使用s h 来执行一个s h e l l 脚本(并且用户不需要对拥有这个脚本的文件具有执行访问权限),这项技术使得脚本运行要比赋予用户执行权限并直接地调用脚本时慢得多。用户更典型的使用是使此文件可执行,并通过在命令行中键入脚本的名字来运行它。同时,仅键入名字操作也是比较容易的,并且它与其他程序调用方式是保持一致的(因此用户不必知道是否正在运行一个s h e l l 脚本或者另一种程序)。但是,如果s h 不是用户的交互s h e l l ,或者如果用户想看看这个脚本如何用不同的s h e l l 运行,则用户应该输入s h (或另一个s h e l l 的名字)命令,这个命令的后面跟着包含这个脚本的文件的名字,正如前面所示。

1. 使用#! 指定一个s h e l l

用户也可以将一个特殊的字符序列放在一个s h e l l 脚本的第一行中,来向操作系统指出它是一个脚本或其他类型的文件。因为操作系统在试图执行一个程序之前,要检查它的开始的几个字符,这些字符可以避免系统进行不成功的尝试。同时它们还告诉系统要使用哪个命令(通常是s h 、c s h 或k s h )。如果一个脚本的头两个字符是# !,那么系统将把后面的字符解释为要执行这个脚本的程序的绝对路径名。这可以是任何程序的路径名,不仅仅是一个s h e l l 。下面的示例指定了将要被s h 运行的当前脚本:

如果用户打算使用除s h 交互s h e l l 之外的其他的s h e l l 来运行一个脚本,那么这个功能也是有用的。下面的示例显示了一个打算由csh 运行的脚本。它可以从任何shell 中运行,但csh 必须执行它。由于#!这一行的存在,操作系统将负责使csh 来执行它,而不管用户是从哪个shell 来运行它的。

接下来是一段演示程序,它显示了自己所在其中运行的s h e l l 名:

332计计第二部分S o l a r i s 中、高级知识下载

- f 选项使得p s 命令显示全部的命令行,这个命令行包括了运行这个脚本的s h e l l 的名字。$ 0变量(s h ,c s h ,k s h )保存调用程序的名称,这样g r e p 命令浏览整个由命令ps -f 所产生的输出行来查程序的名称。

如果用户没有使用一个可执行程序的名字跟在# !的后面,那么s h e l l 将报告它不能找到用户要求它运行的命令。用户可以可选地在# !后面跟随空格。如果用户忽略# !行,并试图运行一个来自s h 的c s h 脚本,那么s h e l l 将生成错误消息或者脚本可能不正确运行。

2. 使用#生成一个注释

注释可以使s h e l l 脚本(和所有的代码)更容易阅读和维护。如果用户在s h e l l 脚本中加入注释,它们就可以更容易地被自己或其他用户所维护。注释的语法对于Bourne shell 、C shell 以及Korn shell 是通用的。

如果一个脚本的第一行的第一个字符的位置上# 符号后面未跟随一个感叹号(!),或者如果一个#符号出现在脚本中的任何位置而不是第一个字符的位置,那么s h e l l 就将它解释成一个注释的开始,并忽略在#符号和下一个N E W L I N E 符号之间的任何内容。

3. 启动文件

当用户登录到用户系统时,Bourne shell 查找一个名为/ e t c /p r o f i l e 的文件。这个文件为包含了用于Bourne shell 的系统范围内的启动命令,并且只有系统管理员可以改变它。在执行完/ e t c /p r o f i l e 中的命令后,Bourne shell 在用户的宿主目录下寻找一个名为. p r o f i l e 的文件。在执行完/ e t c / 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 文件中的命令来覆盖在/ e t c /p r o f i l e 中给出的任何系统范围内的命令。如果任一个文件都不存在,那么s h e l l 就跳过它。Korn shell 使用与s h 相同的启动文件。如果用户正在使用c s h ,请参见1 2.2节有关内容。

下面的. p r o f i l e 文件设置了T E R M 、PAT H 、P S 1以及C D PAT H 变量,同时还设置了

C O N T R O L -U 作为行删除键。

10.7 参数与变量

在s h e l l 中,一个s h e l l 参数与一个值相关联,这个值是用户可以访问到的。有几种s h e l l 参数。名称由字母、数字和下划线组成的参数经常被称为s h e l l 变量,或仅仅称为变量。一个变量名不能以一个数字开头。因而A 67、M Y _C A T 和_ _ _X_ _ _是有效的变量名,而6 9T H _S T R E E T (以一个数字开头)和M Y - N A M E (包括一个连字符)则不是有效的变量名。用户可以命名和赋值的s h e l l 变量属于用户创建的变量。一种惯例是只使用大写字母来作为全局变量(环境变量)的名字,而使用大小写混合的或者小写字母来命名其他变量。用户在任何时候都可以改变用户创建的变量的值,而且用户可以使它们为只读,以使它们的值不能在以后被改变。用户也可以使第1 0章shell II (sh)计计333

下载

334

用户创建的变量成为全局变量。一个全局变量在所有的s h e l l以及用户从最初的s h e l l分叉出来的其他程序中都是可用的。

在Bourne shell和Korn shell中,当用户想给一个变量分配值时,可使用它的名字(在等号的两边都没有空格)。如果用户正在使用c s h,请参见第1 2章的开始部分。

当用户想在任何一个s h e l l中使用一个变量的值时,使用前面带有一个美元符号($)的变量的名字。

那些对s h e l l有特殊含意的变量被称为关键字s h e l l变量(或仅仅称为关键字变量),它们通常具有短的、容易记忆的名字。当用户启动一个s h e l l(例如,通过登录方式)时,s h e l l将从环境中继承几个关键字变量。在这些变量中有H O M E变量,这个变量识别用户的宿主目录;PAT H变量,这个变量确定s h e l l以什么顺序、查找哪个目录来定位用户为s h e l l所输入的命令。在用户启动s h e l l时,s h e l l创建并初始化(使用缺省值)其他的关键字变量;其他的变量直到用户设置的时候才存在。用户可以在任何时候改变大多数关键字s h e l l变量(而不是全部)的值,但通常没有必要改变在/ e t c/p r o f i l e中初始化的关键字变量的值。如果用户需要改变一个变量的值,可以在用户的宿主目录下的. p r o f i l e(s h,k s h)和. l o g i n(c s h)文件中这样做。正如用户可以使用户创建的变量全局化那样,用户也可以使关键字变量全局化,这通常在s h e l l启动时自动地完成。用户也可以使一个关键字变量变成只读的。

另外,还有一些参数,它们的名称不像变量的名称。这些参数大多数都有一个字符的名字(例如,1、?和#),这些参数引用时要在名字前面加上一个美元符号$(例如,$ 1、$ ?和$ #)。这些参数的值反映了用户与s h e l l交互时的各个方面。例如,无论什么时候,用户在命令行中给出一个命令时,命令行中的每个参数都变成一个位置参数的值。位置参数使得用户能够访问命令行参数,这项功能是在用户编写复杂的s h e l l脚本时经常需要的。s h e l l脚本经常需要另外一些值,比如最后一个执行命令的名称,命令行参数的数目,以及最近执行命令的状态。这些都是作为特殊的参数存在的。除了s e t内置命令之外,用户不能为位置参数和特殊的参数赋值。

下面的内容描述了用户创建的变量、关键字变量、位置参数以及特殊参数。

10.7.1 用户创建的变量

用户可以声明任何由字母、数字和下划线组成的序列作为一个变量的名字,只要第一个字符不是数字即可。下一个示例的第一行声明了名为p e r s o n的变量,并使用值a l e x将它初始化(在c s h中,使用set person=alex)。当用户在s h和k s h中分配一个值给一个变量时,用户必须保证在等号的前后没有空格键或TA B键。由于e c h o内置命令(s h,c s h,k s h)复制它的参数到标准输出,所以用户可以使用它来显示变量的值。

示例中的第二行表明p e r s o n 不代表a l e x 。字符串p e r s o n 作为p e r s o n 显示出来。当用户在变量的名字之前使用一个美元符号$时,s h e l l 仅仅替代变量的值。命令e c h o $p e r s o n 显示的是变量p e r s o n 的值,它不显示$ p e r s o n ,这是因为s h e l l 没有将$ p e r s o n 作为一个参数传递给e c h o 命令,而是将变量的值传递给e c h o 。e c h o 内置命令显示了变量的值,而不是它的名字,它不会知道用户是使用一个变量来调用它的。最后的命令(在前面的例子中)显示了变量p e r s o n 的值。

用户可以通过将在前的$引用起来,以防止s h e l l 替代一个变量的值。使用双引号不能防止这种替代;单引号或一个反斜杠可以防止这种替代。双引号不能防止变量的替代,但是可以关闭大多数其他字符的特殊意义。为了分配一个含有空格键或TA B 键的值给变量,可以在该值的周围使用双引号。尽管双引号可以不需要,但是在用户正在使用的变量周围使用它们是一个好主意,用户可以从下面的第二个示例中看出这点。当用户引用一个包含TA B 键或多个相邻的空格键的变量时,用户需要使用引号来保留间隔。如果用户没有将变量引用起来,那么当e c h o 命令把它们复制到标准输出时,e c h o 将会把每一个由非空白字符组成的字符串压缩成一个空格键。

当用户使用变量作为命令参数来执行命令时,s h e l l 将用变量值替代变量名,并将其传递给正在执行的程序。如果变量值中含有一个诸如*或者?的特殊字符,则s h e l l 将可能按如下说明来扩展变量。

下面命令序列中的第一行将把字符串a l e x *分配给变量m e m o 。Bourne shell 将不会扩展字符串,因为s h 并不执行路径名扩展。当把一个值分配给一个变量时,所有s h e l l 将按一个特定的顺序处理命令行。在这一特定顺序中,Bourne shell (而不是c s h )将在转换命令前对变量进行扩展。在以下e c h o 命令行中,使用了双引号将星号( *)引起,以避免Bourne shell 在将变量m e m o 的值传递给e c h o 命令前,对变量m e m o 进行扩展:

第1 0章shell II (sh)计计335

下载

当用户引用一个包含未被引用的特殊字符的变量时,所有的s h e l l 将把特殊字符转换为特殊格式。在接下来的示例中,s h e l l 将扩展变量m e m o 的值,因为变量m e m o 没有被引起:

前一个示例说明:当用户不将变量m e m o 引起时,s h e l l 将会用字符串a l e x *来匹配当前工作目录下的两个文件:a l e x .r e p o r t 和a l e x .s u m m a r y 。当用户在s h 下将变量引起时,e c h o 将显示字符串a l e x *。

1. 使用u n s e t 删除一个变量

除非用户删除一个变量(如下所示),否则只要产生该变量的s h e l l 存在,则变量将一直存在。要删除一个变量值(而不是删除变量本身),可以将变量设置为空。(在c s h 中可以使用命令set person=)。

用户可以使用unset 内置命令( s h ,c s h ,k s h )来删除一个变量。例如要删除变量p e r s o n ,用户可以使用以下的命令。

2. 使用r e a d o n l y 设置一个变量为固定值

用户可以使用readonly 内置命令( s h ,k s h )来保证一个变量值不被改变。下一个示例将声明变量p e r s o n 为只读变量。在用户声明变量为只读变量前,必须给变量分配一个值;在声明变量为只读变量后,将不能改变变量的值。当试图改变只读变量的值时,s h e l l 将显示一个错误消息。

如果用户使用readonly 内置命令而不带任何参数,则readonly 内置命令将给出一个所有s h e l l 只读变量的列表。该列表包含自动设置为只读变量的关键字变量及任何关键字和用户声明为只读的用户创建变量。

3. 使用e x p o r t 设置一个变量为全局变量

通常变量只作为声明该变量的进程中的局部变量:一个s h e l l 脚本不能访问在用户的登录s h e l l 中的变量,除非用户将变量设置为可用(设置为全局变量)。用户可以使用export (sh,ksh)声明一个变量被某个子进程使用。使用C shell 中的s e t e n v (c s h )可以替代s h 命令所需要的分配和导出。本小节的示例可以适用于所有的s h e l l ,只要用户使用适当的语法来分配变量值,并设置变量为全局变量。

336计计第二部分S o l a r i s 中、高级知识下载

一旦用户使用带一个变量名作为参数的export 内置命令,s h e l l 将在子进程的调用环境中赋予变量值。这种按值调用方式可以给每个子进程一个变量的副本,供子进程使用。

接下来,extest1 shell 脚本首先将分配一个a m e r i c a n 的值给变量名为c h e e s e 的变量。接着将显示文件名( e x t e s t 1)和变量c h e e s e 的值。脚本e x t e s t 1接下来将调用子进程s u b t e s t ,该子进程将显示相同的信息。再接下来子进程s u b t e s t 将声明一个c h e e s e 变量,同时显示该变量的值。当子进程s u b t e s t 结束,该子进程将向正在执行e x t e s t 1的父进程交回控制权,然后进程e x t e s t 1将再次显示最初变量c h e e s e 的值。

脚本s u b t e s t 不从e x t e s t 1获得变量c h e e s e 的值,而且e x t e s t 1不会丢失该变量值。一个子进程不会影响父进程的属性。当一个进程试图显示一个未声明变量的值时,该进程将不显示任何值—未声明变量的值为一个空字符串。

接下来的脚本e x t e s t 2和e x t e s t 1相同,只是它使用e x p o r t 将变量c h e e s e 设置为全局变量,以便可以使s u b s e t 脚本使用。

以上示例中,子进程继承了变量c h e e s e 的值a m e r i c a n ,同时在显示该变量值后,子进程将变量值复制到变量s w i s s 。当控制权返回到父进程时,父进程变量c h e e s e 的副本仍然为其初始值a m e r i c a n 。

4. 使用r e a d 接受用户输入

当用户开始编写s h e l l 脚本时,很快就会发现,用户创建变量最常见的用处是保存用户在提示符下所输入的信息。使用r e a d (s h ,k s h )命令,用户的脚本可以接受用户的输入,并将输入保存到用户所创建的变量中。。read 内置命令从标准输入中读取一行,同时将该输入行分配给一个

第1 0章shell II (sh)计计337

下载

相关主题
文本预览
相关文档 最新文档