2012-12-23 66 views
42

数百万开发人员编写shell脚本来解决各种类型的任务。我使用shell脚本来简化部署,生命周期管理,安装或简单地将其作为glue languageShell脚本通用模板

我注意到没有人真正关心shell脚本的风格和质量。很多团队花了很多时间修复Java,C++,...样式问题,但完全忽略了他们的shell脚本中的问题。顺便说一下,在特定项目中通常没有实现shell脚本的标准方法,因此可能会发现许多不同的,丑陋的和错误的脚本,它们分布在代码库中。

为了在我的项目中克服这个问题,我决定创建一个通用的脚本模板,并且足够好。我将提供我的模板,以使这个问题更有用。开箱即用的这些模板提供:

  • 命令行参数处理
  • 同步
  • 一些基本的帮助

参数处理:getopts的(最新版本:[email protected]

#!/bin/bash 
# ------------------------------------------------------------------ 
# [Author] Title 
#   Description 
# ------------------------------------------------------------------ 

VERSION=0.1.0 
SUBJECT=some-unique-id 
USAGE="Usage: command -ihv args" 

# --- Options processing ------------------------------------------- 
if [ $# == 0 ] ; then 
    echo $USAGE 
    exit 1; 
fi 

while getopts ":i:vh" optname 
    do 
    case "$optname" in 
     "v") 
     echo "Version $VERSION" 
     exit 0; 
     ;; 
     "i") 
     echo "-i argument: $OPTARG" 
     ;; 
     "h") 
     echo $USAGE 
     exit 0; 
     ;; 
     "?") 
     echo "Unknown option $OPTARG" 
     exit 0; 
     ;; 
     ":") 
     echo "No argument value for option $OPTARG" 
     exit 0; 
     ;; 
     *) 
     echo "Unknown error while processing options" 
     exit 0; 
     ;; 
    esac 
    done 

shift $(($OPTIND - 1)) 

param1=$1 
param2=$2 

# --- Locks ------------------------------------------------------- 
LOCK_FILE=/tmp/$SUBJECT.lock 
if [ -f "$LOCK_FILE" ]; then 
    echo "Script is already running" 
    exit 
fi 

trap "rm -f $LOCK_FILE" EXIT 
touch $LOCK_FILE 

# --- Body -------------------------------------------------------- 
# SCRIPT LOGIC GOES HERE 
echo $param1 
echo $param2 
# ----------------------------------------------------------------- 

Shell Flags(shFlags)允许简化处理很多的命令行参数,所以在某个时刻我决定不会忽略这种可能性。

参数处理:shflags(最新版本:[email protected]

#!/bin/bash 
# ------------------------------------------------------------------ 
# [Author] Title 
#   Description 
# 
#   This script uses shFlags -- Advanced command-line flag 
#   library for Unix shell scripts. 
#   http://code.google.com/p/shflags/ 
# 
# Dependency: 
#  http://shflags.googlecode.com/svn/trunk/source/1.0/src/shflags 
# ------------------------------------------------------------------ 
VERSION=0.1.0 
SUBJECT=some-unique-id 
USAGE="Usage: command -hv args" 

# --- Option processing -------------------------------------------- 
if [ $# == 0 ] ; then 
    echo $USAGE 
    exit 1; 
fi 

. ./shflags 

DEFINE_string 'aparam' 'adefault' 'First parameter' 
DEFINE_string 'bparam' 'bdefault' 'Second parameter' 

# parse command line 
FLAGS "[email protected]" || exit 1 
eval set -- "${FLAGS_ARGV}" 

shift $(($OPTIND - 1)) 

param1=$1 
param2=$2 

# --- Locks ------------------------------------------------------- 
LOCK_FILE=/tmp/${SUBJECT}.lock 

if [ -f "$LOCK_FILE" ]; then 
echo "Script is already running" 
exit 
fi 

trap "rm -f $LOCK_FILE" EXIT 
touch $LOCK_FILE 

# -- Body --------------------------------------------------------- 
# SCRIPT LOGIC GOES HERE 
echo "Param A: $FLAGS_aparam" 
echo "Param B: $FLAGS_bparam" 
echo $param1 
echo $param2 
# ----------------------------------------------------------------- 

我认为这些模板可以提高到更简化开发者的工作。

所以现在的问题是如何提高他们具备以下条件:

  • 内置记录
  • 更好的错误处理
  • 更好的便携性
  • 更小的尺寸
  • 内置的执行时间跟踪
+3

您正在寻求提供涵盖所有可能性的模板;麻烦,任何给定的脚本不需要涵盖所有可能性。你说得对,很多shell脚本都是非常糟糕的(当我看到在我工作的代码库中的Bourne/Korn/Bash脚本中有if(test -f“$ file”)'时,我会畏缩 - 有时可以修复它尽管没有这样做的许可)。但是,从经验来看,模板中足够的样板变得无用。我以前有100行模板;我现在有10个线模板。从长远来看,它们更有用 - 对我来说。举例说明是很好的。 –

+0

是的,我同意。最好是说我对元模板感兴趣,并将其用作特定项目中的骨架。基本上,这个想法是让所有项目脚本均匀,所以需要一些构建块。这些块可以是可选的,因此对于具体项目将提供20行模板。 –

+2

不要说“没有人”!我非常关心shell脚本的质量,并且经常被现有代码吓倒。 –

回答

15

我会避开依靠bash作为外壳和对shell syntax defined by POSIX顶部解决方案建模,并在使用/bin/sh家当。我们最近有一些惊喜Ubuntu changed /bin/sh to dash

壳世界的另一个流行病是对退出状态代码的一般误解。用可理解的代码退出是让其他shell脚本以编程方式对特定故障做出反应的。不幸的是,除了the "sysexits.h" header file以外,没有太多的指导。

如果您正在寻找有关良好shell脚本实践的更多信息,请专注于Korn shell脚本资源。 Ksh编程倾向于关注真正的编程,而不是编写随意的脚本。

个人而言,我还没有发现太多用于shell模板。不幸的事实是,大多数工程师会简单地复制和粘贴您的模板,并继续编写相同的草率shell代码。更好的方法是创建一个定义明确的语义的shell函数库,然后说服其他人使用它们。这种方法也有助于变更控制。例如,如果您发现模板中存在缺陷,则基于该模板的每个脚本都会中断并需要修改。使用库可以在一个地方修复缺陷。

欢迎来到shell脚本世界。编写shell脚本是一种失落的艺术,似乎正在进入复兴。在90年代后期写了一些关于这个主题的好书 - UNIX Shell Programming by Burns and Arthur想起来,尽管亚马逊评论这本书让它看起来很糟糕。恕我直言,有效的shell代码包含了由Eric S. Raymond在The Art of Unix Programming中描述的UNIX哲学。

+1

在shell库中定位共同性是一种双向的街道,您可以在一个地方轻松地修复事情 - 但您也可以通过对取决于它的脚本进行一次更改来轻松地分解一堆事物。这也许是为什么shell库永远不会成功的 - 依靠它们来完成重要的任务是岌岌可危 - 因为对shell库进行任何更改都需要测试所有依赖它的脚本,而这些脚本可能不适用于生产环境。 – 2015-03-27 20:52:25

+3

关于bash与sh,使用'#!/ bin/bash'应该是安全的。 (毕竟,有多少Linux安装在那里根本就没有bash?)问题是'#!/ bin/sh',但是然后编写依赖于bash特有功能的脚本;当Ubuntu的[DashAsBinSh](https://wiki.ubuntu.com/DashAsBinSh)等变化会破坏事情时, –

+0

高级Bash脚本指南:[附录E.具有特殊含义的退出代码](http://www.tldp.org/LDP/abs/html/exitcodes.html) – xebeche

5

如果你很在意便携性,在测试中不使用==。改为使用=。不要明确检查$#是否为0.相反,在第一次引用必需的参数时使用${n?error message}(例如${3?error message})。这可以防止发出使用说明而非错误消息的烦人做法。最重要的是,始终将错误消息放在正确的流中,并以正确的状态退出。例如:

echo "Unknown error while processing options" >&2 
exit 1; 

它往往是方便做这样的事情:

die() { echo "$*"; exit 1; } >&2 
+1

我不是'$ {var?msg}'的粉丝。它几乎和'set -u'一样糟糕。它会抛出一个真正的bash错误,这些错误与内部错误没有区别,还有那些不熟悉错误消息的错误。它可以发生在一个函数中的一个非显而易见的位置,这个位置可能会在子函数中被无意中调用。或者,如果您在多个分支中使用参数进行分支,则必须复制该检查。通过评估扩展可以在中途退出时发生有害的副作用。明确的检查要好得多,例如'$ {3+:}死信息或'! $ {3 + false} ||死信息'。 – ormaaj

2

我也会分享我的结果。所有这些例子背后的想法是鼓励整体质量。确保最终结果足够安全也很重要。

记录

这实在是至关重要的,以具有可从相同的开始正确记录。我只是想考虑生产使用情况。

TAG="foo" 
LOG_FILE="example.log" 

function log() { 
    if [ $HIDE_LOG ]; then 
     echo -e "[$TAG] [email protected]" >> $LOG_FILE 
    else 
     echo "[`date +"%Y/%m/%d:%H:%M:%S %z"`] [$TAG] [email protected]" | tee -a $LOG_FILE 
    fi 
} 

log "[I] service start" 
log "[D] debug message" 

命令测试

这是关于安全的,现实生活环境和适当的错误处理。可以是可选的。

function is_command() { 
    log "[I] check if commad $1 exists" 
    type "$1" &> /dev/null ; 
} 

CMD=zip 

if is_command ${CMD} ; then 
    log "[I] '${CMD}' command found" 
else 
    log "[E] '${CMD}' command not found" 
fi 

模板处理

可能是只是我主观意见,但无论如何。我使用了几种不同的方法来从脚本中生成一些配置/ etc。 Perl,sed和其他人完成这项工作,但看起来有点可怕。

最近我noticed一个更好的办法:

function process_template() { 
    source $1 > $2 

    result=$? 
    if [ $result -ne 0 ]; then 
     log "[E] Error during template processing: '$1' > '$2'" 
    fi 
    return $result 
} 

VALUE1="tmpl-value-1" 
VALUE2="tmpl-value-2" 
VALUE3="tmpl-value-3" 

process_template template.tmpl template.result 

模板例如

echo "Line1: ${VALUE1} 
Line2: ${VALUE2} 
Line3: ${VALUE3}" 

结果例如

Line1: tmpl-value-1 
Line2: tmpl-value-2 
Line3: tmpl-value-3 
1

有没有更多的有用的东西比一个有据可查的shell脚本行为与使用示例和已知错误列表。我认为没有一个程序可以被称为防弹,并且每个时刻都会出现错误(特别是当你的脚本被其他人使用时),所以我唯一关心的是good coding style,并且只使用这些东西剧本真的需要。你站在聚合的道路上,它总是会成为一个大型系统,带有很多未使用的模块,这些模块很难移植和难以支持。而越便携的系统越大,它越大。严重的是,shell脚本不需要以这种方式来实现。它们必须尽可能小以简化进一步的使用。

如果系统确实需要一些大而无懈可击的东西,现在该考虑C99甚至C++了。

15

这是我的脚本外壳模板(可以在这里找到:http://www.uxora.com/unix/shell-script/18-shell-script-template)的标题。

这是一个man看起来很相似,它用于()用于diplsay帮助以及。

#!/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/%[email protected]%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/%[email protected]%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