实用程序的稳定性是很重要的。容易崩溃或无法处理真实数据的实用程序不是有用的实用程序。实用程序应该能够处理任意长度的行、巨型文件,等等。实用程序无法处理超过其内存容量的数据集或许是可以容忍的,但是有些实用程序不是这样;例如,sort 通过使用临时文件,一般能够对比其内存容量大得多的数据集排序。
应该尽量确保弄清楚您的实用程序可能要操作哪些数据。不要简单地忽略无法处理的数据的可能性。应该检查这种情况并诊断您的实用程序。错误消息越明确,您对用户就越有帮助。尽量给用户提供足够的信息,以便让他们知道发生了什么情况以及如何解决。当处理数据文件时,尽量准确识别出不良的数据。当尝试解析数字时,不要简单地放弃;应该告诉用户您得到了什么数据,而且如果可能的话,还应该告诉用户该数据位于输入流中的哪一行上。
作为一个很好的例子,请考虑 dc 的两种实现之间的区别。如果您运行 dc /home ,其中一种实现会显示“Cannot use directory as input!”而另一种实现只是无声地返回,没有错误消息,也没有不寻常的退出代码。当您错误地键入一个 cd 命令时,您更希望当前路径中有哪一种实现呢?类似地,如果您提供某个目录中的数据流(或许是执行 dc < /home),前者会给出详细的错误消息。另一方面,当它在获得无效数据的早期就选择放弃可能是理想的。
安全漏洞经常植根于在意料之外的数据面前表现得不够健壮的程序中。务必记住,优秀的实用程序能够设法在 shell 脚本中作为根(root)用户身份运行。诸如 find 这样的程序中的缓冲区溢出可能会给大量的系统带来风险。
程序对意料之外的数据处理得越好,它就更可能适应变化的环境。通常,设法使程序更健壮会导致您更好地理解该程序的作用,从而更好地使之通用化。
新颖
要编写的最糟糕的实用程序种类之一就是您已经有了的实用程序。我编写过一个名为 count 的美妙的实用程序。它允许我执行几乎任何计数任务。它是一个出色的实用程序,但是已经有一个名为 jot 的标准 BSD 实用程序做同样的事情。同样地,我的一个用于将数据转换为列的灵活的程序重复了一个现有实用程序 rs 的功能,这个实用程序同样可以在 BSD 系统上找到,只不过 rs 更灵活,设计得更好。请参阅下面的 参考资料 以了解关于 jot 和 rs 的更多信息。
如果您即将开始编写一个实用程序,请花一点时间浏览一下各种系统,以确定那样的实用程序是否已经存在。不要害怕在 BSD 上借用 Linux 实用程序,或在 Linux 上借用 BSD 实用程序;实用程序代码的乐趣之一在于,几乎所有实用程序都具有很好的可移植性。
不要忘了考察一下组合现有应用程序来形成一个实用程序的可能性。从理论上讲,组合现有程序来形成的实用程序运行得不足够快是可能的,但是编写一个新的实用程序很少会比等待一个稍慢的管道更快。
一个例子实用程序
从某种意义上,这个程序是一个可执行文件,因为对于作为过滤器来说,它决不会有任何用处。然而,它作为一个命令行实用程序却工作得非常好。
这个程序仅做一件事情。它以近乎完美的输出格式输出 /usr/include/sys/errno.h 中的 errno 行。例如:
$ errno 22
EINVAL [22]: Invalid argument
#!/bin/sh
usage() {
echo >&2 'usage: errno [numbers or error names]\n'
exit 1
}
for i
do
case '$i' in
[0-9]*)
awk '/^#define/ && $3 == ''$i'' {
for (i = 5; i < NF; ++i) {
foo = foo ' ' $i;
}
printf('%-22s%s\n', $2 ' [' $3 ']:', foo);
foo = ''
}' < /usr/include/sys/errno.h
;;
E*)
awk '/^#define/ && $2 == '''$i''' {
for (i = 5; i < NF; ++i) {
foo = foo ' ' $i;
}
printf('%-22s%s\n', $2 ' [' $3 ']:', foo);
foo = ''
}' < /usr/include/sys/errno.h
;;
*)
echo >&2 'errno: can't figure out
whether '$i' is a name or a number.'
usage
;;
esac
done
清单 2. errno 查找器
这个程序通用化了吗?是的,非常理想。它同时支持数字和符号名称。另一方面,它不知道关于可能具有相同格式的其他文件的信息,比如 /usr/include/sys/signal.h。可以容易地扩展它来做到这点,但是对于这样一个便利的实用能够程序,简单地创建一个名为“signal”的拷贝来读取 signal.h,同时使用“SIG*”作为模式来匹配名称,这样会更容易。