当前位置:  开发笔记 > 编程语言 > 正文

Shell脚本模板

如何解决《Shell脚本模板》经验,为你挑选了6个好方法。

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

我通常#!使用带有文件名,概要,用法,返回值,作者,更改日志的注释掉头开始(在行之后)并且适合80-char行.

所有文档行我都以双哈希符号开头,##因此我可以轻松地为它们grep,本地var名称前面加上"__".

还有其他最佳做法吗?提示?命名约定?返回代码怎么样?

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



1> Jonathan Lef..:

我将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),SIGINT(2),则执行' rm'和' exit'命令, SIGQUIT(3),SIGPIPE(13)或SIGTERM(15).' rm'命令删除与模板匹配的所有中间文件; 该exit命令确保状态为非零,表示某种错误.''trap'为0表示如果shell因任何原因退出,也会执行代码 - 它在标记为"实际工作"的部分中包含粗心.最后的代码然后删除任何幸存的临时文件,然后在退出时解除陷阱,最后以零(成功)状态退出.显然,如果你想以其他状态退出,你可以 - 只需确保在运行rmtrap行之前将其设置在变量中,然后使用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(或者如果我使用back-ticks而不是' $(...)'.第二个工作在现代shell中;甚至可能有方括号而不是括号的替代方案,但这样做我的工作没有费心去弄清楚那是什么.

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

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

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



2> Mark Edgar..:

我使用第一组##行作为使用文档.我现在不记得我第一次看到这个.

#!/bin/sh
## Usage: myscript [options] ARG1
##
## Options:
##   -h, --help    Display this message.
##   -n            Dry-run; only show what would be done.
##

usage() {
  [ "$*" ] && echo "$0: $*"
  sed -n '/^##/,/^$/s/^## \{0,1\}//p' "$0"
  exit 2
} 2>/dev/null

main() {
  while [ $# -gt 0 ]; do
    case $1 in
    (-n) DRY_RUN=1;;
    (-h|--help) usage 2>&1;;
    (--) shift; break;;
    (-*) usage "$1: unknown option";;
    (*) break;;
    esac
  done
  : do stuff.
}


尽管脚本在任何地方执行`cd`并且'$ 0`不是绝对文件名,但grepping脚本的usage()函数最初看起来很酷.我选择使用usage()函数实际回显/ printf/cat使用消息.

3> Michel VONGV..:

这是我用于脚本shell(bash或ksh)的头文件.它man看起来很相似,也用于显示用法().

#!/bin/ksh
#================================================================
# HEADER
#================================================================
#% SYNOPSIS
#+    ${SCRIPT_NAME} [-hv] [-o[file]] args ...
#%
#% DESCRIPTION
#%    This is a script template
#%    to start any good shell script.
#%
#% OPTIONS
#%    -o [file], --output=[file]    Set log file (default=/dev/null)
#%                                  use DEFAULT keyword to autoname file
#%                                  The default value is /dev/null.
#%    -t, --timelog                 Add timestamp to log ("+%y/%m/%d@%H:%M:%S")
#%    -x, --ignorelock              Ignore if lock file exists
#%    -h, --help                    Print this help
#%    -v, --version                 Print script information
#%
#% EXAMPLES
#%    ${SCRIPT_NAME} -o DEFAULT arg1 arg2
#%
#================================================================
#- IMPLEMENTATION
#-    version         ${SCRIPT_NAME} (www.uxora.com) 0.0.4
#-    author          Michel VONGVILAY
#-    copyright       Copyright (c) http://www.uxora.com
#-    license         GNU General Public License
#-    script_id       12345
#-
#================================================================
#  HISTORY
#     2015/03/01 : mvongvilay : Script creation
#     2015/04/01 : mvongvilay : Add long options and improvements
# 
#================================================================
#  DEBUG OPTION
#    set -n  # Uncomment to check your syntax, without execution.
#    set -x  # Uncomment to debug this shell script
#
#================================================================
# END_OF_HEADER
#================================================================

以下是使用功能:

  #== needed variables ==#
SCRIPT_HEADSIZE=$(head -200 ${0} |grep -n "^# END_OF_HEADER" | cut -f1 -d:)
SCRIPT_NAME="$(basename ${0})"

  #== usage functions ==#
usage() { printf "Usage: "; head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#+" | sed -e "s/^#+[ ]*//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }
usagefull() { head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#[%+-]" | sed -e "s/^#[%+-]//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }
scriptinfo() { head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#-" | sed -e "s/^#-//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g"; }

这是你应该得到的:

# Display help
$ ./template.sh --help

    SYNOPSIS
    template.sh [-hv] [-o[file]] args ...

    DESCRIPTION
    This is a script template
    to start any good shell script.

    OPTIONS
    -o [file], --output=[file]    Set log file (default=/dev/null)
    use DEFAULT keyword to autoname file
    The default value is /dev/null.
    -t, --timelog                 Add timestamp to log ("+%y/%m/%d@%H:%M:%S")
    -x, --ignorelock              Ignore if lock file exists
    -h, --help                    Print this help
    -v, --version                 Print script information

    EXAMPLES
    template.sh -o DEFAULT arg1 arg2

    IMPLEMENTATION
    version         template.sh (www.uxora.com) 0.0.4
    author          Michel VONGVILAY
    copyright       Copyright (c) http://www.uxora.com
    license         GNU General Public License
    script_id       12345

# Display version info
$ ./template.sh -v

    IMPLEMENTATION
    version         template.sh (www.uxora.com) 0.0.4
    author          Michel VONGVILAY
    copyright       Copyright (c) http://www.uxora.com
    license         GNU General Public License
    script_id       12345

您可以在此处获取完整的脚本模板:http://www.uxora.com/unix/shell-script/18-shell-script-template


您的帖子不包含您的整个模板,并且您的网站将代码置于"类似社交墙"之后,这应被视为不良行为.

4> Tim Post..:

任何将在野外发布的代码都应该有以下短标题:

# Script to turn lead into gold
# Copyright (C) 2009 Ima Hacker (i.m.hacker@foo.org)
# Permission to copy and modify is granted under the foo license
# Last revised 1/1/2009

保持代码头中的更改日志是版本控制系统非常不方便的回归.最后修改日期显示某人脚本的年龄.

如果您将依赖于bashisms,请使用#!/ bin/bash,而不是/ bin/sh,因为sh是任何shell的POSIX调用.即使/ bin/sh指向bash,如果通过/ bin/sh运行它,许多功能也将被关闭.大多数Linux发行版都不会采用依赖于bashisms的脚本,尝试移植.

对我来说,shell脚本中的注释有点愚蠢,除非它们读取如下内容:

# I am not crazy, this really is the only way to do this

Shell脚本非常简单(除非你写一个演示来教别人怎么做)代码几乎总是避免自己.

有些shell不喜欢被输入类型的"本地"变量.我相信直到今天Busybox(一种常见的救援外壳)就是其中之一.改为GLOBALS_OBVIOUS,它更容易阅读,特别是在通过/ bin/sh -x ./script.sh进行调试时.

我个人的偏好是让逻辑说明一切,并尽量减少解析器的工作.例如,许多人可能会写:

if [ $i = 1 ]; then
    ... some code 
fi

在哪里我只是:

[ $i = 1 ] && {
    ... some code
}

同样,有人可能写道:

if [ $i -ne 1 ]; then
   ... some code
fi

......我在哪里:

[ $i = 1 ] || {
   ... some code 
}

唯一一次我使用传统的if/then/else是否有其他 - 如果投入混合.

只需在大多数使用autoconf的免费软件包中查看"configure"脚本,就可以研究出非常好的便携式shell代码的疯狂例子.我说疯了,因为它的6300行代码满足了人类已知的具有UNIX shell的每个系统.你不想要那种臃肿,但研究一些各种可移植性黑客很有趣..比如对那些可能指向/ bin/sh到zsh的人很好:)

我可以给出的唯一其他建议是在here-docs中观察你的扩展,即

cat << EOF > foo.sh
   printf "%s was here" "$name"
EOF

...当你可能想要保留变量时,将扩展$ name.解决这个问题:

  printf "%s was here" "\$name"

这会将$ name作为变量,而不是扩展它.

我还强烈建议学习如何使用陷阱捕获信号..并使用这些处理程序作为样板代码.使用简单的SIGUSR1告诉正在运行的脚本减速非常方便:)

我编写的大多数新程序(面向工具/命令行)最初都是shell脚本,它是UNIX工具原型的好方法.

您可能也喜欢SHC shell脚本编译器,请在此处查看.


如果你不想在这里扩展docs,使用<<'EOF'来抑制扩展.当你想要扩展一些东西而不扩展一些东西时,只使用反斜杠.

5> l0b0..:

启用错误检测可以更容易地及早发现脚本中的问题:

set -o errexit

第一个错误时退出脚本.这样你就可以避免继续做一些依赖于脚本中某些东西的东西,可能最终会出现一些奇怪的系统状态.

set -o nounset

将对未设置变量的引用视为错误.非常重要的是要避免像rm -you_know_what "$var/"未设置的那样运行$var.如果您知道该变量可以取消设置,并且这是一种安全的情况,您可以使用${var-value}不同的值(如果未设置)或${var:-value}使用其他值(如果未设置或为空).

set -o noclobber

插入一个>你想要插入的地方的错误很容易<,并覆盖你想读的一些文件.如果您需要在脚本中删除文件,可以在相关行之前禁用该文件,然后再次启用它.

set -o pipefail

使用一组管道命令的第一个非零退出代码(如果有)作为完整命令集的退出代码.这样可以更轻松地调试管道命令.

shopt -s nullglob

如果没有与该表达式匹配的文件,请避免/foo/*字面上的glob进行解释.

您可以将所有这些组合成两行:

set -o errexit -o nounset -o noclobber -o pipefail
shopt -s nullglob



6> Hui Zheng..:

我的bash模板如下(在我的vim配置中设置):

#!/bin/bash

## DESCRIPTION: 

## AUTHOR: $USER_FULLNAME

declare -r SCRIPT_NAME=$(basename "$BASH_SOURCE" .sh)

## exit the shell(default status code: 1) after printing the message to stderr
bail() {
    echo -ne "$1" >&2
    exit ${2-1}
} 

## help message
declare -r HELP_MSG="Usage: $SCRIPT_NAME [OPTION]... [ARG]...
  -h    display this help and exit
"

## print the usage and exit the shell(default status code: 2)
usage() {
    declare status=2
    if [[ "$1" =~ ^[0-9]+$ ]]; then
        status=$1
        shift
    fi
    bail "${1}$HELP_MSG" $status
}

while getopts ":h" opt; do
    case $opt in
        h)
            usage 0
            ;;
        \?)
            usage "Invalid option: -$OPTARG \n"
            ;;
    esac
done

shift $(($OPTIND - 1))
[[ "$#" -lt 1 ]] && usage "Too few arguments\n"

#==========MAIN CODE BELOW==========

推荐阅读
乐韵答题
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有