|
Shell 是一门高级语言,使用它可以方便地完成许多功能。但是,写 Shell 程序时一定要注意被调用的程序是在哪个 Shell 里执行:当前的 Shell 或者子 Shell。在极少数的情况下,这两种方式有着微妙的区别。我们用错误报告来说明这种差别。
对于一个 C 程序员来说,写出这样的代码是很自然的事情:
- #include <stdio.h>
- #include <stdarg.h>
- #include <errno.h>
- #include <string.h>
- #include <stdlib.h>
- void error(int n, char *fmt, ...)
- {
- extern char *progname;
- va_list v;
- if (!progname)
- fprintf(stderr, "%s: ", progname);
- va_start(v, fmt);
- vfprintf(stderr, fmt, v);
- va_end(v);
- if (errno)
- fprintf(stderr, ": %s", strerror(errno));
- fprintf(stderr, "\n");
- exit(n);
- }
复制代码
这个函数集中处理了错误报告。我们可以在代码中调用它来完成 UNIX Shell 里的日常操作:
- char *progname = argv[0];
- char *datafile="somefile";
- /* ... ... */
- error(1, "cannot open file %s", datafile);
复制代码
如果要用 Shell 来写一个希望具有良好用户界面的程序,可能会需要类似的代码。同大多数情况一样,Shell 版本的程序比 C 版本的简单许多:
- die()
- {
- local n=$1
- shift
- test ! -z $PROGNAME && echo -n "${PROGNAME}: " 1>&2
- echo $* 1>&2
- exit $n
- }
复制代码
通常,这种函数都会被写在一个单独的文件里,以便随时通过文件名来引用到这个函数。我们可以写一个简单的程序来验证它:
- $ ls
- environ f
- $ cat environ
- # die(int n, string s1, string s2, ...)
- die()
- {
- local n=$1
- shift
- test ! -z $PROGNAME && echo -n "${PROGNAME}: " 1>&2
- echo "$*" 1>&2
- exit $n
- }
- $ cat f
- #!/bin/sh
- . environ
- g()
- {
- test -z $ABC && die 1 "ABC does not exist"
- echo ABC exists
- }
- g
- $ ./f
- ABC does not exist
- $ ABC=aaa ./f
- ABC exists
- $
复制代码
这看上去很成功。但是,这样写是有问题的。考虑下面的代码:
- $ cat g
- #!/bin/sh
- . environ
- g()
- {
- test -z $ABC && die 1 "ABC does not exist"
- echo $ABC
- }
- COPY_OF_ABC=$(g)
- echo The value of ABC is: $COPY_OF_ABC
- $ ./g
- ABC does not exist
- The value of ABC is:
- $
复制代码
函数 g 的目的很明显:如果 ABC 存在,就打印 ABC 的值,这个值可以被其他的程序得到;否则,就 (向标准错误上) 输出 ABC does not exist 并且结束 Shell 程序的执行。$() 的作用与 `` 相同:用 $() 中程序执行的结果来代替 $() 本身。不过一些 Shell 会使用子 Shell 来执行 $() 或 `` 中的内容,在这样的 Shell 里 (如 Bash) 上面这样写是无效的,甚至有时连返回值也无法取到:
- $ cat g
- #!/bin/sh
- . environ
- g()
- {
- test -z $ABC && die 1 "ABC does not exist"
- echo $ABC $1
- }
- f()
- {
- local COPY_OF_ABC=$(g)
- echo $?
- echo The value of ABC is: $COPY_OF_ABC
- }
- f
- $ ./g
- ABC does not exist
- 0
- The value of ABC is:
- $
复制代码
只有依靠临时文件避免使用 $() 或 `` 才能解决这个问题。不过这就需要做好善后工作,避免进程意外退出时留下垃圾。下面是一个例子。
- $ cat g
- #!/bin/sh
- . environ
- g()
- {
- test -z $ABC && die 1 "ABC does not exist"
- echo $ABC $1
- }
- f()
- {
- local tmpfile="/tmp/f.$$"
- trap "rm -f $tmpfile" 0 SIGINT SIGQUIT SIGKILL SIGTERM
- g >$tmpfile
- COPY_OF_ABC=$(cat $tmpfile)
- echo The value of ABC is: $COPY_OF_ABC
- rm -f $tmpfile
- }
- f
- $ ./g
- ABC does not exist
- $
复制代码
现在似乎可以了:调用 die 可以保证 Shell 程序的结束,而且 trap 的使用导致不会留下任何垃圾。但是这样做还是有问题的,因为如果有一连串的动作的话,仍然会留下垃圾。
- $ cat g
- #!/bin/sh
- . environ
- g()
- {
- test -z $ABC && die 1 "ABC does not exist"
- echo $ABC $1
- }
- f()
- {
- local tmpfile="/tmp/f.$$"
- trap "rm -f $tmpfile" 0 SIGINT SIGQUIT SIGKILL SIGTERM
- g >$tmpfile
- COPY_OF_ABC=$(cat $tmpfile)
- echo $COPY_OF_ABC
- rm -f $tmpfile
- }
- h()
- {
- local tmpfile="/tmp/h.$$"
- trap "rm -f $tmpfile" 0 SIGINT SIGQUIT SIGKILL SIGTERM
- f >$tmpfile
- local COPY_OF_COPY_OF_ABC=$(cat $tmpfile)
- echo The value of COPY_OF_COPY_OF_ABC is: $COPY_OF_COPY_OF_ABC
- rm -f $tmpfile
- }
- h
- $ ls /tmp
- $ ./g
- ABC does not exist
- $ ls /tmp
- h.7776
- $
复制代码
这是因为函数 h 创建了临时文件,而函数 f 只考虑删除自己的临时文件。因此,我们需要维护一个完整的临时文件的列表,并保证所有的垃圾在退出时都可以被清理掉。最后的版本是这样的:
- $ cat environ
- # die(int n, string s1, string s2, ...)
- die()
- {
- local n=$1
- shift
- test ! -z $PROGNAME && echo -n "${PROGNAME}: " 1>&2
- echo "$*" 1>&2
- exit $n
- }
- # append(string env, string file)
- append()
- {
- if eval test -z "\$$1" ; then
- eval $1=$2
- else
- eval $1="\$$1 $2"
- fi
- }
- # deappend(string env, string file)
- deappend()
- {
- eval $1="$(eval echo \$$1 | awk '{ printf "%s", $1
- for (i = 2; i < NF; i++)
- printf " %s", $i
- if ($i != "'$2'")
- printf " %s", $i
- }')"
- }
- $ cat g
- #!/bin/sh
- . environ
- g()
- {
- test -z $ABC && die 1 "ABC does not exist"
- echo $ABC $1
- }
- f()
- {
- local tmpfile="/tmp/f.$$"
- append TMPFILE $tmpfile
- trap "rm -f $TMPFILE" 0 SIGINT SIGQUIT SIGKILL SIGTERM
- g >$tmpfile
- COPY_OF_ABC=$(cat $tmpfile)
- echo $COPY_OF_ABC
- rm -f $tmpfile
- deappend TMPFILE $tmpfile
- }
- h()
- {
- local tmpfile="/tmp/h.$$"
- append TMPFILE $tmpfile
- trap "rm -f $TMPFILE" 0 SIGINT SIGQUIT SIGKILL SIGTERM
- f >$tmpfile
- local COPY_OF_COPY_OF_ABC=$(cat $tmpfile)
- echo The value of COPY_OF_COPY_OF_ABC is: $COPY_OF_COPY_OF_ABC
- rm -f $tmpfile
- deappend TMPFILE $tmpfile
- }
- h
- $ ls /tmp
- $ ./g
- ABC does not exist
- $ ls /tmp
- $
复制代码
这里主要添加了 append 函数和 deappend 函数。append 函数的作用很简单,就是将一个值追加到一个环境变量的最后;deappend 则用来去掉一个环境变量最后的字符串,但前提是最后的字符串与要去掉的相同。这样,就可以保证无论在什么情况下都能即时地从 Shell 程序里退出并且不留垃圾了。
最后要说明的是,虽然上面的程序看起来是无用的,但是在实际情况中经常会遇到这样的代码,它们只是形式不同,实质上是一样的。 |
|