Unix Shell 介绍(3)

来源:互联网 发布:vehicle spy3保存数据 编辑:程序博客网 时间:2024/05/05 13:14

3.1 参数传送
当调用一个 shell 过程的时候可以同时提供位置和关键字参数。还可以通过预先指定某些参数是导出的,使一个 shell 过程隐蔽的获得关键字参数。例如,

 export user box
标记变量 user 和 box 为导出。在调用一个 shell 过程的时候,在这个被调用的过程中制作所有可导出的变量的使用副本。在这个过程中对这些变量的修改不影响在调用 shell 中的变量。一般而言,一个 shell 过程不向调用者发出显式的请求就不能改变它的调用者的状态。(共享的文件描述符是这个规则的一个例外)。


值要保持不变的名字可以声明为 readonly。这个命令的形式同于 export 命令,

 readonly name ...
后面的设置只读变量的尝试将是非法的。

3.2 参数替换
如果一个 shell 参数未设置则把它替换成空串。例如,如果变量 d 未设置

 echo $d

 echo ${d}
将什么都不回显。缺省的字符串可以给出为

 echo ${d-.}
如果变量 d 设置了则回显它的值否则回显‘.’。使用常规的引用惯例求值缺省字符串所以

 echo ${d-'*'}
表示如果变量 d 未设置则回显 *。类似的

 echo ${d-$1}
表示如果变量 d 的值未设置则回显这个值否则回显 $1。使用如下记号向一个变量赋予一个缺省值

 echo ${d=.}
它替换的字符串同于

 echo ${d-.}
并且如果变量 d 以前未设置则把它设置为字符串‘.’。(记号 ${...=...} 不适用于位置参数)。


如果没有合适的缺省则记号

 echo ${d?message}
表示如果变量 d 有值则回显它,否则 shell 打印 message 并且这个 shell 过程的执行异常中止。如果空缺 message 则打印一个标准消息。要求某些参数必须设置的 shell 过程可以如下面这样开始。

 : ${user?} ${acct?} ${bin?} ...
冒号(:)是内置到 shell 中的一个命令并且在求值了它的实际参数之后什么都不做。若变量 user、acct 或 bin 中任何一个未设置则 shell 将中止这个过程的执行。

3.3 命令替换
来自命令的标准输出可以按类似于参数的方式进行替换。命令 pwd 在它的标准输出上打印当前目录的名字。例如,如果当前目录是 /usr/fred/bin 则命令

 d=`pwd`
等价于

 d=/usr/fred/bin
译注: 在当前版本的 shell 中为命令替换增加了新的文法形式$(...),上面的例子也可以写成

 d=$(pwd)

在重音号(`...`)之间的全部字符串被接受为要执行的命令并且由这个命令的输出所替代。除了使用 \ 转义 `之外,使用常规的引用惯例表述这个命令。例如,

 ls `echo "$1"`
等价于

 ls $1
在发生参数替换的所有上下文中都发生命令替换(包括立即文档),并且在两种情况下对结果的文档的处理是 相同的。这个机制允许在 shell 内使用字符串处理命令。这种命令的例子是 basename,它从一个字符串删除指定的后缀。例如,

 basename main.c .c
将打印字符串 main。用来自 cc 命令的一个片断来展示它的用途。

 case $A in ... *.c) B=`basename $A .c` ... esac
将设置 B 为 $A 去除了后缀 .c 的那部分。

下面是一些符合的例子。

· for i in `ls -t`; do ...
变量 i 设置为按时间次序的文件名字,最新者最先。
· set `date`; echo $6 $2 $3, $4
将打印类似下面这样的字符串:1977 Nov 1, 23:59:59
3.4 求值和引用
shell 是向给命令的实际参数提供参数替换、命令替换和文件名生成的一个宏处理器。本节讨论这些求值发生的次序和各种引用机制的作用。


依据在附录 A 中给出的文法初步的分析命令。在命令执行之前发生下列替换。

参数替换,例如 $user
命令替换,例如 `pwd`
只发生一次求值,所以如果变量 X 的值是字符串 $y 则
 echo $X
将回显 $y。

空白(blank)解释
紧随上述替换之后把结果的字符串分解成非空白的字(空白解释)。用做‘空白’的是字符串 $IFS 的字符。缺省的,这个字符串由空格、tab 和换行组成。空串不作为一个字除非是被引用了。例如
 echo ''
将传递这个空串作为给 echo 的第一个实际参数。而
 echo $null
如若变量 null 未设置或被设置为空串则调用 echo 而没有任何实际参数。
文件名字生成
接着在每个字中检索文件模式字符 *、? 和 [...] 并生成文件名字的一个按字母顺序的列表来替代这个字。每个这样的文件名字都是一个独立的实际参数。 刚才描述的求值也在与 for 循环关联的字的列表中发生。用于 case 分支的字只发生替换。

与早先描述的使用 \ 和 '...'的引用同时存在的第三种引用机制使用双引号。在双引号内发生参数和命令替换但不发生文件名生成和空白解释。下列字符在双引号内有特殊意义并可以使用 \ 来引用。

$
参数替换
`
命令替换
"
终结引用的字符串
\
引用特殊字符 $ ` " \
例如,

 echo "$x"
将传递变量 x 的值作为给 echo 的一个单一的实际参数。类似的,

 echo "$*"
将传递位置参数作为一个单一的实际参数并等价于

 echo "$1 $2 ..."
记号 $@ 在除了被引用的时候之外都同于 $*。

 echo "$@"
将传递未求值的位置参数到 echo 并等价于

 echo "$1" "$2" ...
下面的表格给出对于每种引用机制,进行求值的 shell 元字符。

 元字符
  \ $ * ` " '
' n n n n n t
` y n n t n n
" y y n y t n


 t 终结符 y 解释 n 不解释
图 2. 引用机制


在要求多于一次字符串求值的情况下可以使用内置命令 eval。例如,如果变量 X 有值 $y, 并且若 y 有值 pqr 则

 eval echo $X
将回显字符串 pqr。

一般的,eval 命令求值它的实际参数(与所有命令一样)并把这个结果作为给 shell 的输入来对待。读这个输入并执行作为结果的命令。例如,

 wg=\'eval who|grep\' $wg fred
等价于

 who|grep fred
在这例子中,需要 eval 的原因是替换之后不解释元字符如 |。

3.5 错误处理
shell 检测到的错误的处理依赖于错误的类型和 shell 是否被交互式使用。交互式 shell 的输入和输出连接到终端上(由 gtty (2) 决定)。用 -i 标志调用的 shell 也是交互式的。


命令的执行(参见 3.7)可能由于下列原因而失败。

输入-输出重定向可能失败。例如,如果文件不存在或不能建立。
命令自身不存在或不能执行。
命令异常中止,例如,出现“总线错误”或“内存错误”。下面的图 2 列出的是 UNIX 信号的完整列表。
命令正常中止但返回一个非零退出状态。
在所有这些情况下 shell 将继续执行下一个命令。 除了最后一种情况下 shell 打印一个错误消息。所有余下的错误导致 shell 从命令过程中退出。交互式 shell 将返回来从终端读另一个命令。这样的错误包括如下。

语法错误如 if ... then ... done
一个信号如中断。shell 等待当前的命令,如果有的话,完成执行并接着要么退出要么返回到终端。
任何内置命令如 cd 的错误。
shell 标志 -e 导致 shell 在检测到任何错误的时候中止。

1
挂断
2
中断
3*
退出
4*
非法指令
5*
跟踪陷入
6*
IOT 指令
7*
EMT 指令
8*
浮点异常
9
杀死(不能被捕获或忽略)
10*
总线错误
11*
段违例
12*
给系统调用无效的实际参数
13
在没有读者的一个管道上写
14
定时时钟
15
软件中断(来自 kill (1))
图 3. UNIX 信号


标记星号的信号如果未捕获则生成一个内存转储(core dump)。但是,shell 自身忽略退出信号,它是可以导致内存转储的唯一的外部信号。这个列表中对 shell 程序可能有用的是 1、2、3、14 和 15。

3.6 故障处理
Shell 过程在从终端接收到信号的时候通常会中止。如果需要一些清除,比如删除一些临时文件,则可以使用 trap 命令。例如,

 trap 'rm /tmp/ps$$; exit' 2
为信号 2 (终端中断)设置一个陷入,并且如果接收到这个信号则执行命令

 rm /tmp/ps$$; exit
exit 是中止 shell 过程执行的另一个内置命令。exit 是必须的,否则,在陷入发生之后,shell 将在被中断的位置上恢复执行过程。

UNIX 信号可以用三种方式处理。它们可以被忽略,在这种情况下信号永不发送到进程。它们可以被捕获, 在这种情况下进程必须决定在收到信号的时候要做的动作。最后,它们可以被保留,导致进程中止而不做任何进一步动作。如果在进入 shell 过程时把一个信号忽略了,例如,是在后台调用的(参见 3.7 节),则忽略这个 trap 命令(和信号)。

用下面的这修改版本的 touch 命令展示 trap 的用途(图 4)。清除动作是删除文件 junk$$。

 flag= trap 'rm -f junk$$; exit' 1 2 3 15 for i do case $i in -c) flag=N ;; *) if test -f $i then ln $i junk$$; rm junk$$ elif test $flag then echo file \'$i\' does not exist else >$i fi esac done
图 4. touch 命令


trap 命令出现在这个临时文件建立之前;否则进程有可能死去而未删除这个文件。

因为 UNIX 中没有信号 0,shell 使用它来指示从 shell 过程中退出时要执行的命令。

过程自身可以通过指定空串作为给 trap 的参数来选择忽略信号。下列片断取自 nohup 命令。

 trap '' 1 2 3 15
这导致 hangup、interrupt、quit 和 kill 被这个过程和调用的命令所忽略。通过如下表述重置陷入

 trap 2 3
它把给信号 2 和 3 的陷入重置为缺省值。可以通过如下表述获得陷入的当前值的一个列表

 trap
过程 scan (图 5) 是使用 trap 的一个例子,这里的 trap 命令中没有 exit。scan 取出在当前目录中的每个目录,提示出它的名字,接着执行在终端键入的命令,直到收到一个文件结束或中断。中断在执行要求的命令期间被忽略,而在等待输入的时候导致 scan 中止。

 d=`pwd` for i in * do if test -d $d/$i then cd $d/$i while echo "$i:" trap exit 2 read x do trap : 2; eval $x; done fi done
图 5. scan 命令

read x 是从标准输入读入一行并把结果放置到变量 x 中的一个内置命令。如果收到文件结束或中断则返回一个非零退出状态。

3.7 命令执行
要运行一个命令(除了内置命令),shell 首先使用系统调用 fork 建立一个新进程。在命令执行之前,在子进程中建立这个命令的执行环境包括输入、输出和信号的状态。内置命令 exec 在罕见的情况下使用,这时不需要 fork 并用新命令简单的替换 shell。例如,一个简单版本的 nohup 命令如下

 trap \'\' 1 2 3 15 exec $*
trap 关闭指定的信号,所以它们被随后建立的命令所忽略,接着 exec 用指定的命令替代这个 shell。

已经描述了多种形式的输入输出重定向。下面的 word 只服从参数和命令替换。没有文件名字生成或空白解释发生,例如,

 echo ... >*.c
将把它的输出写到名字是 *.c 的一个文件中。输入输出指定按出现在命令中那样从左至右的求值。

> word
把标准输出(文件描述符 1)发送到文件 word,如果它不存在则建立之。
>> word
把标准输出发送到文件 word。如果文件存在则输出被添加(通过找到结尾);否则建立这个文件。
< word
标准输入(文件描述符 0)接收自文件 word。
<< word
标准输入接受自 shell 输入的行,从下一行开始直到但不包括只由 word 组成的那一行。 如果引用了 word 则不解释这个文档。如果未引用 word 则发生参数和命令替换并且使用 \ 来引用字符 \ $ ` 和 word 的第一个字符。在后面的情况下忽略 \newline(参见:引用字符串)。
>& digit
使用系统调用 dup (2) 复制文件描述符 digit 并把结果用作标准输出。
<& digit
标准输入复制自文件描述符 digit。
<&-
关闭标准输入。
>&-
关闭标准输出。
上述所有形式都可以前导一个数字,此时建立的文件描述符由这个数字指定而不是缺省的 0 或 1。例如,

 ... 2>file
运行一个命令并把消息输出(文件描述符 2)定向到 file。

 ... 2<&1
运行一个命令并把它的标准输出和消息输出合并。(严格的说是通过复制文件描述符 1 来建立文件描述符 2 但效果通常是合并了两个流。)


在后台运行的如下命令的环境

 list *.c | lpr &
要进行两种方式的修改。首先,这种命令的缺省标准输入是空文件 /dev/null。这防止并行运行的两个进程(shell 和这个命令),尝试读相同输入。如果不是这样则混乱将继而发生。例如,

 ed file &
将允许编辑器和 shell 二者同时读相同的输入。


对后台命令的环境的另一种修改是关闭 QUIT 和 INTERRUPT 信号所以它们被这个命令所忽略。这允许在终端使用这些信号而不导致后台命令被中止。为此 UNIX 的信号惯例是如果它被设置为 1 (忽略)则永不改变即使是短时。注意 shell 命令 trap 对被忽略的信号无效。

3.8 调用 shell
在调用 shell 的时候解释下列标志。如果实际参数零的第一个字符是一个减号,则命令读自文件 .profile。

-c string
如果提供 -c 标志则命令读自 string。
-s
如果提供了 -s 标志或者没有余下的实际参数则命令读自标准输入。shell 输出被写到文件描述符 2。
-i
如果提供了 -i 标志或者 shell 输入和输出被连接到一个终端(通过 gtty 获得)则这个  shell 是交互的。在这种情况下忽略 TERMINATE(所以 kill 0 不能杀死一个交互式的 shell),捕获并忽略 INTERRUPT(所以 wait 是可中断的)。在所有情况下 shell 都忽略 QUIT。

--------------------------------------------------------------------------------

 

致谢
shell 的设计部分基于最初的 UNIX shell 和 PWB/UNIX shell,一些特征取自二者。与 Cambridge Multiple Access System 和 CTSS 的命令解释器也有类似之处。


我要感谢 Dennis Ritchie 和 John Mashey 在 shell 设计期间做的许多讨论。还要感谢 Computing Science Research Center 的成员和 Joe Maranzano 对这个文档的意见。


--------------------------------------------------------------------------------

 

附录 A - 文法
item:
word
input-output
name = value
simple-command:
item
simple-command item
command:
simple-command
( command-list )
{ command-list }
for name do command-list done
for name in word ... do command-list done
while command-list do command-list done
until command-list do command-list done
case word in case-part ... esac
if command-list then command-list else-part fi
pipeline:
command
pipeline | command
andor:
pipeline
andor && pipeline
andor || pipeline
command-list:
andor
command-list ;
command-list &
command-list ; andor
command-list & andor
input-output:
> file
< file
>> word
<< word
file:
word
& digit
& -
case-part:
pattern ) command-list ;;
pattern:
word
pattern | word
else-part:
elif command-list then command-list else-part
else command-list
empty
empty:
word:
非空白字符的一个序列
name:
以一个字母开始的字母、数字或下划线的一个序列
digit:
0 1 2 3 4 5 6 7 8 9

--------------------------------------------------------------------------------

 

附录 B - 元字符和保留字
a) 句法

|
管道符号
&&
‘andf’符号
||
‘orf’符号
;
命令分隔符
;;
case 分界符
&
后台命令
( )
命令组合
<
输入重定向
<<
输入字立即文件
>
输出重定向
>>
输出添加
b) 模式

*
匹配任何字符包括空
?
匹配任何单一字符
[...]
匹配包围的字符中的任何一个
c) 替换

${...}
替换 shell 变量
`...`
替换为命令输出
d) 引用

\
引用下一个字符
'...'
引用包围的字符,不包括 '
"..."
引用包围的字符,不包括 $ ` \ "
e) 保留字

if then else elif fi case in esac for while until do done { }

 

 

0 0
原创粉丝点击