Unix Shell 介绍(1)

1.0 介

1.0 介绍
shell 既是一个命令语言又是提供到 UNIX 操作系统的接口的一个编程语言。这个备忘录用例子描述 UNIX shell。第一章覆盖多数终端用户的日常需要。熟悉 UNIX 对读本章是很有利的,否则可阅读如“UNIX for beginners”这样的文章。第 2 章描述主要意图用在 shell 过程中的那些特征。这包括 shell 提供的控制流原语(primitive)和字符串值变量。在读本章的时候编程语言的知识将是有帮助的。最后一章描述 shell 的更高级的特征。文中的“参见 pipe (2)”引用的是 UNIX 手册的一个章节。

1.1 简单命令
简单命令由一个或多个用空白分隔的字组成。第一个字是要执行的命令的名字;所有余下的字被作为传递给命令的实际参数。例如,

 who
是打印用户登录的名字的一个命令。命令

 ls -l
打印在当前目录中的文件的一个列表。实际参数 -l 告诉 ls 打印每个文件的状态信息、大小和建立日期。

1.2 后台命令
要执行一个命令,shell 通常建立一个新进程并等待它完成。可以执行一个命令而不用等待它完成。例如,

 cc pgm.c &
调用 C 编译器来编译文件 pgm.c。尾随的 & 是指示 shell 不等待命令完成的一个操作符。为了跟踪这样一个进程,shell 在建立它之后报告它的进程编号。可以使用 ps 命令来获得当前活跃进程的一个列表。

1.3 输入输出重定向
多数命令在最初连接到这个终端上的标准输出上生成输出。这个输出可以通过写操作发送到一个文件,例如,

 ls -l >file
记号 >file 由 shell 来解释并且不作为一个实际参数传递给 ls。如果文件不存在则 shell 建立它;否则文件的最初内容被来自 ls 的输出所替代。可以使用下面的记号把输出添加到一个文件

 ls -l >>file
在这种情况下如果 file 不存在则也建立它。

可以通过写操作使一个命令的标准输入接受自一个文件而不是终端,例如,

 wc <file
命令 wc 读它的标准输入(在这种情况下重定向自文件)并打印发现的字符、字和行的数目。如果只需要行的数目则可以使用

 wc -l <file
1.4 管道线和过滤器
可以通过写‘管道’操作符 | 把一个命令的标准输出连接到另一个命令标准输入上,如在

 ls -l | wc
中以这种方式连接的两个命令组成一个管道线与下面的表述

 ls -l >file; wc <file
除了未使用 file 之外整体效果上等同。但这两个进程是用管道连接的(参见 pipe (2))而且是并行运行。


管道是单向的,并通过当管道中没有东西可读的时候暂停 wc 和当管道满的时候暂停 ls 来实现同步。

过滤器是读它的标准输入,以某种方式转换它,并输出结果作为输出的命令。这样的一个过滤器如 grep, 从它的输入中选择出包含指定字符串的那些行。例如,

 ls | grep old
打印来自 ls 的输出中包含字符串 old 的那些行,如果有的话。另一个有用的过滤器是 sort。例如,

 who | sort

将打印登录的用户的按字符排序的一个列表。

一个管道线可以由多于两个的命令组成,例如,

 ls | grep old | wc -l
打印在当前目录中的文件名字中包含字符串 old 的数目。

1.5 文件名生成
许多命令接受的实际参数是文件名字。例如,

 ls -l main.c
打印与文件 main.c 相关的信息。


shell 提供一种机制来生成匹配一个模式的文件名字的一个列表。例如,

 ls -l *.c
生成在当前目录中的结束于 .c 的所有文件名字,作为给 ls 的实际参数。字符 * 是匹配包括空串的任何字符串的一个模式。一般的模式可以指定如下。

*
匹配包括空串的任何字符串。
?
匹配任何单一字符。
[...]
匹配包围的字符中的任何一个。用减号分隔的一对字符匹配在词法上位于这两个字符之间的任何字符(含这两个字符)。
例如,

 [a-z]*
匹配在当前目录中开始于 a 到 z 中的一个字母的所有名字。

 /usr/fred/test/?
匹配在目录 /usr/fred/test 中由一个单一字符组成的所有名字。如果没有找到匹配这个模式的名字,则把这个模式不做变动的作为实际参数传递。


这个机制对保存键入和依据某个模式选择名字二者都有用。它还用于查找文件。例如,

 echo /usr/fred/*/core
找到并打印在 /usr/fred 的子目录中所有 core 文件的名字。(echo 是标准 UNIX 命令,打印它的由空白分隔的实际参数)。最后的特征可能是昂贵的,它需要检索 /usr/fred 的所有子目录。


对针对模式的一般规则有一个例外。在一个文件名字开始处的字符‘.’必须被显式的匹配。

 echo *
将回显在当前目录中不以‘.’开始的所有文件名字。

 echo .*
将回显以‘.’开始的所有文件名字。这避免了无意中匹配了名字‘.’ 和‘..’,它们分别意味着‘当前目录’和‘父目录’。(注意 ls 抑制针对‘.’和‘..’的信息。)

1.6 引用
对 shell 有特定意义的字符,如 < > * ? | & 叫做元字符。在附录 B 中给出元字符的一个完整列表。以 \ 为前导的任何字符是被引用的并失去了它的特殊意义,如果有的话。删除 \ 所以

 echo \?
将回显一个单一 ?,并且

 echo \\
将回显一个单一的 \。为了允许长字符串在多于一行上延续,忽略序列 \换行。


\ 便于引用单一字符。当多于一个字符需要引用的时候上述机制就是蠢笨的和错误的倾向。字符串可以通过用单引号包围来引用。例如,

 echo xx'****'xx
将回显

 xx****xx
引用的字符串不可以包含单引号但可以包含并保留换行。这种引用机制是最简单的并建议偶尔使用。


还有第三种引用机制使用双引号,它防止对一些但不是全部元字符的解释。详情参见 3.4 节。

1.7 提示
在从终端使用 shell 的时候,在读一个命令之前它发出一个提示。缺省的这个提示是‘$’。可用下面的方法改变它,例如,

 PS1=yesdear
设置提示为字符串 yesdear。如果键入了换行并且需要进一步的输入,则 shell 将发出提示‘>’。有时这是缺少引号所导致的。如果这是意外的则一次中断(DEL)将使 shell 返回来读另一个命令。这个提示可以用下面的方法改变,例如,

 PS2=more
1.8 shell 和登录
紧随 login (1) 之后调用 shell 来读取和执行在终端上键入的命令。如果这个用户的登录目录中包含文件 .profile 则假定它包含命令并且在 shell 从终端读取任何命令之前读取它。

1.9 总结
ls
打印在当前目录中的文件的名字。
ls >file
把来自 ls 的输出放置到 file 中。
ls | wc -l
打印在当前目录中文件的数目。
ls | grep old
打印包含字符串 old 的那些文件名字。
ls | grep old | wc -l
打印名字中包含字符串 old 的那些文件的数目。
cc pgm.c &
在后台运行 cc。

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

 

2.0 shell 过程
可以用 shell 读取和执行包含在文件中的命令。例如,

 sh file [ args ... ]
调用 shell 来从 file 读取命令。这样的文件叫做命令过程或 shell 过程。实际参数可以提供给调用并在 file 中用位置参数 $1、$2、....来引用。例如,如果文件 wg 包含

 who | grep $1

 sh wg fred
等价于

 who | grep fred
UNIX 文件有三个独立的属性,读、写和执行。可以使用 UNIX 命令 chmod (1)来使文件可执行。例如,

 chmod +x wg
将确保文件 wg 有可执行状态。此后,命令

 wg fred
等同于

 sh wg fred
这允许 shell 过程和程序被交替使用。在任何一种情况下都建立一个新进程来运行命令。


同为位置参数提供名字一样,在调用中位置参数的数目可获得为 $#。被执行的文件的名字可获得为 $0。

一个特殊的 shell 参数是 $* 被用来替换除了 $0 之外的所有位置参数。它的典型用途是提供一些缺省实际参数,如在

 nroff -T450 -ms $*
它简单的把那些给 shell 的实际参数准备转给这个命令。


译注:shell 还有一个内置命令 . file。它读这个文件中的命令并执行之。

2.1 控制流 - for
shell 过程的一个常见用途是遍历(loop through)实际参数 ($1, $2, ...)并且对每一个实际参数执行命令一次。这样的一个过程的例子是 tel 它查找包含如下行的文件 /usr/lib/telnos

 ... fred mh0123 bert mh0789 ...
tel 的文本是

 for i do grep $i /usr/lib/telnos; done
命令

 tel fred
打印在 /usr/lib/telnos 中包含 fred 的那些行。

 tel fred bert
打印包含 fred 的行随后是包含 bert 的行。

for 循环记号由 shell 识别并有一般形式

 for name in w1 w2 ... do command-list done
命令列表(command-list)是由换行或分号分隔或结束的一个或多个命令的一个序列。进一步,保留字如 do 和 done 只有紧随一个换行或分号之后才被识别。名字(name)是一个 shell 变量,在每次执行 do 后面的命令列表时它将被依次设置为字 w1 w2 ...。如果省略了 in w1 w2 ... 则为每个位置参数执行一次循环;就是说,假定为 in $*。


使用 for 循环的另一个例子是 create 命令,它的文本是

 for i do >$i; done
命令

 create alpha beta
确保两个空文件 alpha 和 beta 存在并且是空的。使用记号 >file 主动的建立一个文件或清除它的内容。 还要注意在 done 之前需要一个分号(或换行)。

2.2 控制流 - case
case 记号提供一种多路分支。例如,

 case $# in 1) cat >>$1 ;; 2) cat >>$2 <$1 ;; *) echo \'usage: append [ from ] to\' ;; esac
是一个 append 命令。在调用时带有一个实际参数如

 append file
$# 是字符串 1 并使用 cat 命令把标准输入复制到 file 的末端。

 append file1 file2
添加 file1 的内容到 file2 上。如果提供给 append 的实际参数数目不是 1 或 2 则打印指示正确用法的一个消息。


case 命令的一般形式是

 case word in pattern) command-list;; ... esac
shell 按模式(pattern)出现的次序对每个模式尝试匹配字(word)。如果找到一个匹配则执行相关的命令列表(command-list)并且 case 的执行完成。因为 * 是匹配任何字符串的模式它可以用作缺省情况。


一句警告: shell 不做检查来确保只有一个模式匹配 case 实际参数。找到的一个匹配定义要执行的命令集。在下面的例子中在第二个 * 之后的命令将永不执行。

 case $# in *) ... ;; *) ... ;; esac
使用 case 构造的另一个例子是区别一个实际参数的不同形式。下列的例子是 cc 命令的一个片断。

 for i do case $i in -[ocs]) ... ;; -*) echo \'unknown flag $i\' ;; *.c) /lib/c0 $i ... ;; *) echo \'unexpected argument $i\' ;; esac done
为了允许同一个命令与多于一个模式相关联,case 命令提供了由 | 分隔的可选择的模式。例如,

 case $i in -x|-y) ... esac
等价于

 case $i in -[xy]) ... esac
使用普通的引用惯例所以

 case $i in \?) ...
将匹配字符 ?。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值