《UNIX编程环境》——5.2 which

5.2 which

建立自己的命令版本,如cal命令的新版本,会带来一些其他的问题。最明显的例子是,如果Mary一起工作,并且以mary登录,则此时的cal还是标准的版本,除非Mary把新的cal命令连接到她的bin目录里。你可能会非常疑惑—原先的cal命令给出的错误信息不足以使人弄清发生错误的原因。但是这只是这类问题的一个例子。因为shell通过PATH指定的一组目录搜索命令,得到的可能不是所期望的版本。例如,键入一条命令:echo,而实际运行的文件全路径名可能是./echo、/bin/echo、/usr/bin/echo或是别的什么目录,这取决于搜索命令的目录PATH以及文件存在的具体位置。如果存在一个名字相同,但行为完全不同的命令出现在比预期早的路径中,就可能把人弄糊涂。最常见的情况是test命令,关于这一命令我们在以后还要讨论到,因为大家往往喜欢用test来命名程序的临时版本,但通常会执行错误的test程序1。在这种情况下,一个能报告即将执行的程序版本的命令,能为用户提供很有用的服务。

一种实现方法是对PATH里的目录进行循环搜索,找出给定名字的每一个执行文件。在第3章里我们用for语句实现对文件名和参数的循环,这里我们给出所要求的循环结构:

因为我们可以在反括号…里运行任何命令,所以显而易见的解决方法是在$PATH上运行sed程序,将冒号转换为空格符。我们仍用echo程序做这一试验工作:

显然还存在一个问题。PATH中的空符号串与“.”意义相同。所以,将PATH里的分号转换成空格符不是个好办法-这将丢失空符号串元素。为了生成一个正确的目录列表,我们必须把PATH的空符号串元素转换为点字符(即.)。空字符串部分可以位于字符串中间或字符串任意一端,因而要做少许工作以处理所有情况,即:

我们可以把它写成4个分开的sed命令,不过由于sed的4个命令是按顺序执行的,所以实际上调用一次sed即可。

一旦我们有了PATH中的目录元素,前面提到的test(1)命令就可以告诉我们文件是否存在于各个目录里。test命令实际上是一个比较笨拙的UNIX程序。例如,test–r file测试file是否存在并且可读;test -w file命令测试文件是否存在而且可写,但第7版没有提供test–x(尽管System V和其他版本提供了这一功能),否则我们就可以使用它了。我们将使用test–f,这一命令测试文件是否存在,并且不是一个目录,而是一个普通文件。应该查看系统上关于test命令的手册,因为有好几个版本都在使用。

每条命令返回一个“退出状态”,即返回一个值给shell,指出执行的情况。退出状态是一个小整数,通常0表示“真”(命令运行成功),非0表示“假”(命令运行不成功)。注意,这个值和C语言中的真假正好相反。

由于很多不同的值可以表示“假”,所以不同的失败原因经常用不同的“假”退出状态值来表示,例如,grep程序在存在匹配时返回0,没有匹配时返回1,模式或文件名有错时返回2。每个程序返回一个状态,一般情况下我们对这个值不感兴趣,但对test程序却不一样,它的唯一目的是要返回一个状态值。除此外,test不产生任何输出,也不修改文件。

Shell将上一个程序的退出状态存放在变量$?中:

有几个命令,如cmp和grep,都有选择项-s,使它们仅以适当的状态返回,但不产生任何输出。

shell的if语句按照命令的退出状态执行选择,例如:

其中换行是很重要的,fi、then和else仅在换行符或分号之后才能被识别。else部分是可选的。

if语句至少要运行一条命令—条件中的命令,而case语句直接在shell中做模式匹配。在某些UNIX版本,包括System V中,test是shell的内部函数,所以if和test能和case运行得一样快。如果test不是内部函数,case语句就比if语句更有效,在模式匹配时应该使用case语句来执行,即:

这就是为什么有时我们使用shell中的case语句进行条件测试,尽管在大多数编程语言使用if语句就可以了;但另一方面,case语句不能方便地判断文件是否可以读;这时最好使用test命令和if语句。

现在我们开始编写which命令的第一个版本,它将报告与给定命令相匹配的文件:

开始的case语句进行错误检查。注意echo后的重定向语句1>&2,表示不把错误信息送往管道。shell内部命令exit用于返回状态。当命令不工作时,exit 2用于返回错误状态;当没有找到文件时返回exit1,当找到文件时返回exit0。如果程序中这一位置没有给出exit语句,则退出状态就是shell文件最后一个命令执行的状态。

假如在当前目录中有一个test程序,那么会发生什么情况呢?(假定test不是shell内部命令。)

需要做更多的错误检查。可以运行which(假设在当前目录里没有test程序!)去寻找test文件的全路径,并打印它。但这还是不能令人满意:因为在不同的系统中,test可能位于不同的目录里,而且which要依靠sed和echo,所以还要指定它们的路径名。一个比较简单易行的办法是:在shell文件里固定PATH,使得在执行which命令时,仅搜索/bin和/usr/bin目录。当然,对于which命令,必须保存以前的PATH中的目录搜索顺序。

shell提供了另外两个操作符用于组合命令,这就是¦¦ 和&&,它们的使用形式比if语句更加简练方便。例如,¦¦ 能够代替一些if语句:

尽管形式上相似,但操作符¦¦ 并不是管道,而是表示“或”的条件操作符。执行¦¦ 左边的命令后,若退出状态为0(成功),则忽略¦¦ 右边的命令;若退出状态为非0(失败),执行右边的命令,并且整个表达式的值是右边命令退出时的状态值。换言之,¦¦ 是“或”条件操作符,左边的命令执行成功时不执行右边的命令。对应的&&条件操作符表示“与”,仅当左边的命令成功才执行右边的命令。

练习5-4 为什么which文件不在退出前把路径PATH恢复成opath?

练习5-5 shell使用esac终止case,使用fi终止if,为什么使用done终止do呢?

练习5-6 把选择项-a加到which命令中,使它打印在PATH里的所有文件,而不是在打印第一个文件之后便退出。提示:match=’exit 0’

练习5-7 修改which,使之可以识别shell的内部函数,如exit。

练习5-8 修改which,使之能检查文件的执行权限。当不能找到文件时,改变which使之打印错误信息。

时间: 2025-01-17 23:31:55

《UNIX编程环境》——5.2 which的相关文章

《UNIX编程环境》——5.10 后记

5.10 后记 当需要编写一个新程序时,自然立刻会想到如何用你最喜欢的语言来编写这个程序.对我们来说,最常用的语言是shell. shell是一种很好的编程语言,虽然它的语法有些特殊.shell属于高级语言,它的操作对象为整个程序.由于shell是交互式语言,所以shell程序能够交互式地开发,可以逐级求精直至它能够令人满意地工作.如果是一个面向更多的用户,可以对shell程序进一步改造,使之更精巧和更实用,以满足广泛使用的需要.不能用shell程序高效地解决问题的情况微乎其微.如果遇到这种例外

《UNIX编程环境》——导读

**前言**"UNIX安装的数量已经增加了10倍,预期还将更多." -UNIX程序员手册,1972年6月第2版 UNIX1操作系统是1969年首次在贝尔实验室的一台丢弃的DEC PDP-7计算机上启用的.当时Ken Thompson从Rudd Canaday.Doug McIlroy.Joe Ossanna和Dennis Ritchie那里获得理念和支持,编写了小型通用分时系统,其适用性能良好,足以吸引热心的用户,并最终为一台较大的计算机-PDP-11/20的购买提供了充分的可靠性.系

《UNIX编程环境》——1.4 shell

1.4 shell 当系统印出提示符$,你键入命令并得到了执行时,此时并不是内核在与读者对话,而是与一个称为命令解释器或外壳shell的在对话.shell是同date或who一样的普通程序,尽管它可以处理一些不同寻常的事.shell存在于用户和内核机制之间的事实对用户是有帮助的,有些会在这里说明.下面是三个要点. 文件名简写:可以通过指定文件名的模式来选取一套文件名作为程序的变量-shell会找出匹配该模式的文件名. 输入输出重定向:可以把任何程序的输出送到一个文件中而不是终端上,并当作来自文件

《UNIX编程环境》——第1章 初学UNIX 1.1起步

第1章 初学UNIX 第1章初学UNIX 什么是UNIX?狭义地看,它是一个分时操作系统内核,即一个控制计算机的资源并将其分配给用户的程序.它让用户运行其程序,并控制与机器连接的外围设备(硬盘.终端.打印机等),提供一个文件系统用以管理诸如程序.数据及文档等长期存储的信息. 广义地看,UNIX通常不仅包含内核,还包括一些基本程序,如编译器.编辑器.命令语言.用以复制和显示文件的程序等. 从更广的角度来看,UNIX可以包括由用户开发的.运行于用户的UNIX操作系统上的程序,如文档处理工具.统计分析

《UNIX编程环境》——5.6 zap:使用名字终止进程

5.6 zap:使用名字终止进程 kill命令只能通过指定进程号来终止进程.要终止某个后台进程时,一般要运行ps命令以得到进程标识号,然后再把它作为kill的参数输入.通过一个命令程序打印一个参数,再把这个参数手工输入到另一个命令中,这个方法似乎有些笨拙.为什么不写一个程序,如zap,自动完成这些工作呢? 原因之一是终止进程是个危险的操作,执行时必须小心谨慎.一个保险的办法是交互地运行zap,用pick命令选择要终止的进程. 先简要回顾一下pick的功能:pick顺次打印它的每个参数,并请求用户

《UNIX编程环境》——5.5 overwrite:改写文件

5.5 overwrite:改写文件 sort排序命令有一个选顶-o,表示覆盖文件: 如果filel和file2是同一个文件,重定向符号>在排序之前就把输入文件截断.然而,加上-o选项的命令将能正常工作,因为在输出文件建立前,sort先将输入排序并存放在一个临时文件中. 很多其他命令也可用-o选择.例如,sed可以编辑文件如下: 要对所有这样的命令都加上选项-o显然是不现实的.另外,这种做法也不可取:最好是将功能集中起来处理,就像shell使用运算符>那样.我们给出一个程序overwrite完

《UNIX编程环境》——1.2 文件和常用命令

1.2 文件和常用命令 在UNIX系统中信息存储在文件中,它很像日常的办公室文件.每个文件有名字.内容.存放地点以及某些管理信息,诸如所有者以及文件大小等.文件可能是一封信,或者是人名及地址清单,或者是源程序,或者是供某个程序用的数据,甚至是程序的可执行形式以及其他的非文本类型材料. UNIX文件组织结构使你可以维护自己的文件而不会影响其他人的文件,并且也防止他人干涉你的文件.UNIX系统有大量的程序可操作文件,但是现在,我们只介绍最频繁使用的那些.第2章是关于文件系统的具体讨论,其中介绍了许多

《UNIX编程环境》——5.7 pick命令:空格和参数

5.7 pick命令:空格和参数 我们已经接触了书写shell的pick命令需要的多数命令.我们只需要一种新的机制来接收用户输入.shell内部命令read提供了这一功能,即从标准输入读一行正文,并把读到的文本(不含换行)赋给命名变量: read最常用于注册时在.profile文件里设置环境,主要是建立shell环境变量,如TERM. read只能读取标准输入,而且不能被重定向.shell内部命令(与控制流原语不同,如for)都不能使用>或<重定向: 这也许可以说是shell的一个缺陷,但这就

《UNIX编程环境》——5.4 trap:捕获中断

5.4 trap:捕获中断 如果在运行watchwho时突然按下Delete键或挂断电话,在目录/tmp中,将保存一个或两个临时文件.Watchwho应该在退出之前清除这些暂存文件.我们需要一定的手段来检测各种中断事件,并进行恢复处理. 按Delete键时,一个中断信号会送给终端上正在运行的所有进程:同样地,当挂断电话时,会传送一个挂断信号.其他信号发生的情形亦同.除非程序有专门处理中断信号措施,否则,中断信号将一律终止程序的运行.如果是中断信号,后台运行的进程(使用&运行)能得到保护,但如果是