Shell脚本通用模板

数百万开发人员编写shell脚本来解决各种types的任务。 我使用shell脚本来简化部署,生命周期pipe理,安装或简单地作为粘合剂语言 。

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

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

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

处理参数:getopts (最新版本: shell-script-template @ github

#!/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 (最新版本: shell-script-template @ github

 #!/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 "$@" || 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 # ----------------------------------------------------------------- 

我认为这些模板可以改进,以更加简化开发人员的生活。

所以问题是如何改善他们有以下几点:

  • 内置日志logging
  • 更好的error handling
  • 更好的便携性
  • 更小的占地面积
  • 内置的执行时间跟踪

我将避免依赖bash作为shell,并在POSIX定义的shell语法之上build模您的解决scheme,并在shebang上使用/bin/sh 。 当Ubuntu将/bin/sh改为dash时,我们有了一些惊喜。

shell世界的另一个stream行病是对退出状态代码的普遍误解。 用一个可以理解的代码退出是让其他shell脚本以编程方式对特定故障作出反应。 不幸的是, 在“sysexits.h”头文件之外没有太多的指导。

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

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

欢迎来到shell脚本的世界。 编写shell脚本是一个失落的艺术,似乎正在进入复兴。 在90年代后期写了一些关于这个主题的好书 – 伯恩斯和亚瑟的UNIX Shell编程让人想起,尽pipe亚马逊对这本书的评论让人觉得很糟糕。 恕我直言,有效的shell代码包含了UNIX哲学,如Eric S. Raymond在“艺术的Unix编程”中所描述的

这是我的脚本shell模板的标题(可以在这里find: 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/%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 #================================================================ 

以下是使用function:

  #== 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

如果您担心可移植性,请不要在testing中使用== 。 用=代替。 不要显式地检查$#是否为0,而是在第一次引用一个必需的参数(例如${3?error message} )时使用${n?error message} 。 这可以防止发出使用说明而不是错误消息的烦人的做法。 而最重要的是,总是把错误消息放在正确的stream中,并以正确的状态退出。 例如:

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

这样做通常很方便:

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

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

logging

从同一个开始就有适当的日志logging是非常重要的。 我只是想考虑生产的使用情况。

 TAG="foo" LOG_FILE="example.log" function log() { if [ $HIDE_LOG ]; then echo -e "[$TAG] $@" >> $LOG_FILE else echo "[`date +"%Y/%m/%d:%H:%M:%S %z"`] [$TAG] $@" | tee -a $LOG_FILE fi } log "[I] service start" log "[D] debug message" 

命令testing

这是关于安全性,现实生活环境和适当的error handling。 可以是可选的。

 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 

模板处理

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

最近我注意到一个更好的方法:

 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 

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

如果系统确实需要一些强大的防御措施,那么就该考虑C99甚至C ++了。