Shell 脚本模板 [英] Shell script templates

查看:38
本文介绍了Shell 脚本模板的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于将一个好的 bash/ksh 脚本模板用作所有新创建脚本的标准,您有什么建议?

What would be your suggestions for a good bash/ksh script template to use as a standard for all newly created scripts?

我通常以注释掉的标题开头(在 #! 行之后),其中包含文件名、概要、用法、返回值、作者、更改日志,并且适合 80 字符线.

I usually start (after the #! line) with a commented-out header with a filename, synopsis, usage, return values, author(s), changelog and would fit into 80-char lines.

我以双哈希符号开头的所有文档行 ## 以便我可以轻松地为它们 grep 并且本地 var 名称以__"开头.

All documentation lines I start with double-hash symbols ## so I can grep for them easily and local var names are prepended with "__".

还有其他最佳做法吗?尖端?命名约定?返回码呢?

Any other best practices? Tips? Naming conventions? What about return codes?

关于版本控制的评论:我们用SVN没问题,但是企业的另一个部门有一个单独的repo,这是他们的脚本.如果没有@author 信息,我如何知道与 Q 联系的人?使用类似于 javadocs 的条目即使在 shell 上下文中也有一些优点,恕我直言,但我可能是错的.

Comments on version control : we use SVN all right, but another dept in the enterprise has a separate repo and this is their script. How do I know who to contact with Q's if there is no @author info? Using entries similar to javadocs has some merit even in the shell context, IMHO, but I might be wrong.

推荐答案

我将诺曼的答案扩展到 6 行,最后一行是空白的:

I'd extend Norman's answer to 6 lines, and the last of those is blank:

#!/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 $.第五行提醒我,脚本的顶部应该有对其用途的描述;我用脚本的实际描述替换了这个词(例如,这就是为什么它后面没有冒号的原因).

The third line is a version control identification string - it is actually a hybrid with an SCCS marker '@(#)' that can be identified by the (SCCS) program what and an RCS version string which is expanded when the file is put under RCS, the default VCS I use for my private use. The RCS program ident picks up the expanded form of $Id$, which might look like $Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $. The fifth line reminds me that the script should have a description of its purpose at the top; I replace the word with an actual description of the script (which is why there's no colon after it, for example).

在那之后,shell 脚本基本上没有任何标准.出现了标准片段,但没有出现在每个脚本中的标准片段.(我的讨论假设脚本是用 Bourne、Korn 或 POSIX (Bash) shell 符号编写的.关于为什么有人在 #! 符号后面放置 C Shell 派生词的问题有一个完整的单独讨论.)

After that, there is essentially nothing standard for a shell script. There are standard fragments that appear, but no standard fragment that appears in every script. (My discussion assumes that scripts are written in Bourne, Korn, or POSIX (Bash) shell notations. There's a whole separate discussion on why anyone putting a C Shell derivative after the #! sigil is living in sin.)

例如,每当脚本创建中间(临时)文件时,此代码就会以某种形状或形式出现:

For example, this code appears in some shape or form whenever a script creates intermediate (temporary) files:

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 的文件名前缀.这不是安全措施;它是一种简单的并发措施,可防止脚本的多个实例相互践踏数据.(为了安全起见,在非公共目录中使用不可预测的文件名.)第二行确保 'rm' 和 'exit' 命令被执行,如果shell 接收任何信号 SIGHUP (1)、SIGINT (2)、SIGQUIT (3)、SIGPIPE (13) 或 SIGTERM (15).'rm' 命令删除任何与模板匹配的中间文件;exit 命令确保状态非零,表示某种错误.'trap' 为 0 意味着如果 shell 因任何原因退出,代码也会被执行 - 它涵盖了标记为实际工作"的部分中的粗心大意.然后,最后的代码删除所有幸存的临时文件,之前 在退出时解除陷阱,最后以零(成功)状态退出.显然,如果您想以其他状态退出,您可以 - 只需确保在运行 rmtrap 行之前将其设置在一个变量中,然后使用 退出 $exitval.

The first line chooses a temporary directory, defaulting to /tmp if the user did not specify an alternative ($TMPDIR is very widely recognized and is standardized by POSIX). It then creates a file name prefix including the process ID. This is not a security measure; it is a simple concurrency measure, preventing multiple instances of the script from trampling on each other's data. (For security, use non-predictable file names in a non-public directory.) The second line ensures that the 'rm' and 'exit' commands are executed if the shell receives any of the signals SIGHUP (1), SIGINT (2), SIGQUIT (3), SIGPIPE (13) or SIGTERM (15). The 'rm' command removes any intermediate files that match the template; the exit command ensures that the status is non-zero, indicating some sort of error. The 'trap' of 0 means that the code is also executed if the shell exits for any reason - it covers carelessness in the section marked 'real work'. The code at the end then removes any surviving temporary files, before lifting the trap on exit, and finally exits with a zero (success) status. Clearly, if you want to exit with another status, you may - just make sure you set it in a variable before running the rm and trap lines, and then use exit $exitval.

我一般用下面的来去掉脚本中的路径和后缀,所以报错时可以用$arg0:

I usually use the following to remove the path and suffix from the script, so I can use $arg0 when reporting errors:

arg0=$(basename $0 .sh)

我经常使用一个shell函数来报告错误:

I often use a shell function to report errors:

error()
{
    echo "$arg0: $*" 1>&2
    exit 1
}

如果只有一个或两个错误退出,我不理会这个函数;如果还有更多,我会这样做,因为它简化了编码.我还创建了或多或少称为 usage 的复杂函数,以总结如何使用该命令——同样,只有在不止一个地方会使用它的情况下.

If there's only one or maybe two error exits, I don't bother with the function; if there are any more, I do because it simplifies the coding. I also create more or less elaborate functions called usage to give the summary of how to use the command - again, only if there's more than one place where it would be used.

另一个相当标准的片段是一个选项解析循环,使用 getopts shell 内置:

Another fairly standard fragment is an option parsing loop, using the getopts shell built-in:

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 是累积的,但这里使用的符号丢失了参数中的空格.也有(非标准的)方法可以解决这个问题.

The quotes around "$OPTARG" handle spaces in arguments. The Dflag is cumulative, but the notation used here loses track of spaces in arguments. There are (non-standard) ways to work around that problem, too.

第一个 shift 符号适用于任何 shell(或者如果我使用反引号而不是 '$(...)' .第二个适用于现代 shell;甚至可能有用方括号代替圆括号的替代方法,但这是有效的,所以我没有费心去弄清楚那是什么.

The first shift notation works with any shell (or would do if I used back-ticks instead of '$(...)'. The second works in modern shells; there might even be an alternative with square brackets instead of parentheses, but this works so I've not bothered to work out what that is.

现在的最后一个技巧是,我经常同时拥有 GNU 和非 GNU 版本的程序,并且我希望能够选择使用哪个版本.因此,我的许多脚本都使用变量,例如:

One final trick for now is that I often have both the GNU and a non-GNU version of programs around, and I want to be able to choose which I use. Many of my scripts, therefore, use variables such as:

: ${PERL:=perl}
: ${SED:=sed}

然后,当我需要调用 Perl 或 sed 时,脚本使用 $PERL$SED.当某些行为不同时,这对我有帮助 - 我可以选择操作版本 - 或者在开发脚本时(我可以在不修改脚本的情况下向命令添加额外的仅调试选项).(请参阅 Shell 参数扩展,了解有关${VAR:=value} 和相关符号.)

And then, when I need to invoke Perl or sed, the script uses $PERL or $SED. This helps me when something behaves differently - I can choose the operational version - or while developing the script (I can add extra debug-only options to the command without modifying the script). (See Shell parameter expansion for information on the ${VAR:=value} and related notations.)

这篇关于Shell 脚本模板的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆