我会将 Norman 的答案扩展到 6 行,最后一个是空白的:
#!/bin/ksh
#
# @(#)$Id$
#
# Purpose
第三行是一个版本控制标识字符串——它实际上是一个混合了一个@(#)
可以被(SCCS)程序识别的SCCS标记''what
和一个RCS版本字符串,当文件放在RCS下时扩展,默认VCS我用于我的私人用途。RCS 程序ident
采用 的扩展形式$Id$
,它可能看起来像$Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $
. 第五行提醒我,脚本顶部应该有其用途的描述;我将这个词替换为脚本的实际描述(例如,这就是它后面没有冒号的原因)。
在那之后,shell 脚本基本上没有任何标准。出现了标准片段,但没有出现在每个脚本中的标准片段。(我的讨论假设脚本是用 Bourne、Korn 或 POSIX (Bash) shell 表示法编写的。关于为什么有人在#!
sigil 之后放置 C Shell 衍生品的人生活在罪恶中,有一个完整的单独讨论。)
例如,每当脚本创建中间(临时)文件时,此代码就会以某种形式出现:
tmp=${TMPDIR:-/tmp}/prog.$$
trap "rm -f $tmp.?; exit 1" 0 1 2 3 13 15
...real work that creates temp files $tmp.1, $tmp.2, ...
rm -f $tmp.?
trap 0
exit 0
第一行选择一个临时目录,如果用户没有指定替代目录,则默认为 /tmp($TMPDIR 已被广泛认可并由 POSIX 标准化)。然后它会创建一个包含进程 ID 的文件名前缀。这不是安全措施;这是一个简单的并发措施,防止脚本的多个实例践踏彼此的数据。(为了安全起见,在非公共目录中使用不可预测的文件名。)第二行确保如果 shell 接收到任何信号 SIGHUP (1) rm
、exit
SIGINT (2)、 SIGQUIT (3)、SIGPIPE (13) 或 SIGTERM (15)。' rm
' 命令删除任何与模板匹配的中间文件;该exit
命令确保状态不为零,表示某种错误。这 'trap
' of 0 表示如果 shell 出于任何原因退出,代码也会被执行——它涵盖了标记为“实际工作”部分中的粗心。最后的代码然后删除所有幸存的临时文件,然后在退出时解除陷阱,最后以零(成功)状态退出。显然,如果您想以其他状态退出,您可以 - 只需确保在运行rm
andtrap
行之前将其设置在变量中,然后使用exit $exitval
.
我一般用下面的方法把脚本中的路径和后缀去掉,这样$arg0
报错的时候就可以用了:
arg0=$(basename $0 .sh)
我经常使用一个shell函数来报错:
error()
{
echo "$arg0: $*" 1>&2
exit 1
}
如果只有一个或两个错误退出,我不会打扰该功能;如果还有更多,我会这样做,因为它简化了编码。我还创建了或多或少复杂的函数usage
,以提供如何使用命令的摘要 - 同样,只有在不止一个地方可以使用它的情况下。
另一个相当标准的片段是一个选项解析循环,使用getopts
shell 内置:
vflag=0
out=
file=
Dflag=
while getopts hvVf:o:D: flag
do
case "$flag" in
(h) help; exit 0;;
(V) echo "$arg0: version $Revision$ ($Date$)"; exit 0;;
(v) vflag=1;;
(f) file="$OPTARG";;
(o) out="$OPTARG";;
(D) Dflag="$Dflag $OPTARG";;
(*) usage;;
esac
done
shift $(expr $OPTIND - 1)
或者:
shift $(($OPTIND - 1))
"$OPTARG" 周围的引号处理参数中的空格。Dflag 是累积的,但这里使用的符号会丢失参数中的空格。也有(非标准)方法可以解决该问题。
第一个移位符号适用于任何 shell(或者如果我使用反引号而不是 ' $(...)
' 会这样做。第二个适用于现代 shell;甚至可能有方括号而不是括号的替代方案,但这有效,所以我已经没有费心去弄清楚那是什么。
现在的最后一个技巧是,我经常同时拥有 GNU 和非 GNU 版本的程序,我希望能够选择我使用的程序。因此,我的许多脚本都使用以下变量:
: ${PERL:=perl}
: ${SED:=sed}
然后,当我需要调用 Perl orsed
时,脚本使用$PERL
or $SED
。当某些行为不同时,这对我很有帮助——我可以选择操作版本——或者在开发脚本时(我可以在不修改脚本的情况下向命令添加额外的仅调试选项)。(有关和相关符号的信息,请参阅Shell 参数扩展。)${VAR:=value}