Shell脚本常用模板

时间:2012-12-23 02:15:53

标签: bash shell

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

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

为了在我的项目中克服这个问题,我决定创建一个通用且足够好的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
# -----------------------------------------------------------------

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

所以问题是如何改进它们以获得以下内容:

  • 内置日志记录
  • 更好的错误处理
  • 更好的便携性
  • 占地面积小
  • 内置执行时间跟踪

6 个答案:

答案 0 :(得分:20)

这是我的脚本shell模板的标题(可在此处找到: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
#================================================================

以下是使用功能:

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

答案 1 :(得分:18)

我会避免依赖bash作为shell并在shell syntax defined by POSIX之上为您的解决方案建模,并在shebang上使用/bin/sh。最近Ubuntu changed /bin/sh to dash时我们有很多惊喜。

shell世界中的另一个大流行是对退出状态代码的一般误解。退出可理解的代码是让其他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哲学。

答案 2 :(得分:6)

如果您担心可移植性,请不要在测试中使用==。请改用=。不要显式检查$#是否为0.而是在第一次引用必需参数时使用${n?error message}(例如${3?error message})。这可以防止发出使用声明而非错误消息的极其恼人的做法。最重要的是,始终将错误消息放在正确的流上并以正确的状态退出。例如:

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

通常可以方便地执行以下操作:

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

答案 3 :(得分:2)

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

<强>登录

从同一个开始提供适当的日志记录非常重要。我只是想考虑生产用途。

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"

命令测试

这是关于安全,真实环境和正确的错误处理。可以是可选的。

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

答案 4 :(得分:1)

对于shell脚本而言,没有比使用示例和使用已知错误列表的记录良好的行为更有用的东西了。我认为没有一个程序可以标题为防弹,并且每时每刻都会出现错误(特别是当你的脚本被其他人使用时),所以我唯一需要注意的是good coding style和使用只有脚本真正需要的东西。你正站在聚合的道路上,它总是会成为一个大型系统,它带有很多未使用的模块,很难移植和难以支持。越是系统试图移植,它就越大。说真的,shell脚本不需要以这种方式实现它。它们必须尽可能小,以简化进一步的使用。

如果系统确实需要大而且防弹的东西,那么就该考虑C99甚至是C ++。

答案 5 :(得分:1)

这是我的bash样板,在注释中解释了一些理智的选择

#!/usr/bin/env bash

set -e  # Abort script at first error, when a command exits with non-zero status (except in until or while loops, if-tests, list constructs)
set -u  # Attempt to use undefined variable outputs error message, and forces an exit
set -x  # Similar to verbose mode (-v), but expands commands
set -o pipefail  # Causes a pipeline to return the exit status of the last command in the pipe that returned a non-zero return value.