bash Shell脚本通用模板

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14008125/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 23:08:19  来源:igfitidea点击:

Shell script common template

bashshell

提问by Renat Gilmanov

Millions of developers write shell scripts to solve various types of tasks. I use shell scripts to simplify deployment, life-cycle management, installation or simply as a glue language.

数以百万计的开发人员编写 shell 脚本来解决各种类型的任务。我使用 shell 脚本来简化部署、生命周期管理、安装或简单地作为一种胶水语言

What I've noticed is nobody actually cares about shell scripts style and quality. A lot of teams spend many hours fixing Java, C++, ... style issues, but totally ignore issues in their shell scripts. By the way, usually there is no standard way to implement a shell script within a particular project, so the one may find dozens different, ugly and buggy scripts, spread around the codebase.

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

To overcome that issue in my projects I decided to create a shell script template, universal and good enough. I will provide my templates as is to make this question a bit more useful. Out of the box these templates provides:

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

  • command-line arguments handling
  • synchronization
  • some basic help
  • 命令行参数处理
  • 同步
  • 一些基本的帮助

Arguments handling: getopts(latest version: shell-script-template@github)

参数处理: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=
param2=

# --- 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) allows to simplify command-line arguments handling a lot, so at some moment of time I decided not to ignore such possibility.

Shell Flags(shFlags) 允许大量简化命令行参数的处理,所以在某些时候我决定不忽略这种可能性。

Arguments handling: shflags(latest version: shell-script-template@github)

参数处理: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=
param2=

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

I do think these templates can be improved to simplify developer's life even more.

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

So the question is how to improve them to have the following:

所以问题是如何改进它们以具有以下内容:

  • built-in logging
  • better error handling
  • better portability
  • smaller footprint
  • built-in execution time tracking
  • 内置日志
  • 更好的错误处理
  • 更好的便携性
  • 占地面积更小
  • 内置执行时间跟踪

采纳答案by D.Shawley

I would steer clear of relying on bashas the shell and model your solution on top of shell syntax defined by POSIXand use /bin/shon the shebang. We had a number of surprises recently when Ubuntu changed /bin/shto dash.

我会避免依赖bash作为 shell,并在POSIX 定义shell 语法之上为您的解决方案建模/bin/sh在 shebang 上使用。最近当Ubuntu 更改/bin/shdash.

Another pandemic in the shell world is a general misunderstanding of exit status codes. Exiting with an understandable code is what lets other shell scripts programmatically react to specific failures. Unfortunately, there is not a lot of guidance on this beyond the "sysexits.h" header file.

Shell 世界中的另一个流行病是对退出状态代码的普遍误解。以可理解的代码退出是让其他 shell 脚本以编程方式对特定故障做出反应的原因。不幸的是,除了“sysexits.h”头文件之外没有太多关于此的指导。

If you are looking for more information about good shell scripting practices, concentrate on Korn shell scripting resources. Ksh programming tends to focus on really programming as opposed to writing haphazard scripts.

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

Personally, I haven't found much use for shell templates. The unfortunate truth is that most engineers will simply copy and paste your template and continue to write the same sloppy shell code. A better approach is to create a library of shell functions with well-defined semantics and then convince others to use them. This approach will also help with change control. For example, if you find a defect in a template, then every script that was based on it is broken and would require modifications. Using a library makes it possible to fix defects in one place.

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

Welcome to the world of shell scripting. Writing shell scripts is a bit of a lost art that seems to be entering a renaissance. There were some good books written on the subject in the late 90's - UNIX Shell Programmingby Burns and Arthurcomes to mind though the Amazon reviews for the book make it seem awful. IMHO, effective shell code embraces the UNIX philosophy as described by Eric S. Raymond in The Art of Unix Programming.

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

回答by Michel VONGVILAY uxora.com

This is the header of my script shell template (which can be found here: http://www.uxora.com/unix/shell-script/18-shell-script-template).

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

It is a manlook alike which is used to by usage() to diplsay help as well.

man与usage() 用于diplsay help的外观相似。

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

And here is the usage functions to go with:

这是要使用的使用函数:

  #== needed variables ==#
SCRIPT_HEADSIZE=$(head -200 
# 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
|grep -n "^# END_OF_HEADER" | cut -f1 -d:) SCRIPT_NAME="$(basename
echo "Unknown error while processing options" >&2
exit 1;
)" #== usage functions ==# usage() { printf "Usage: "; head -${SCRIPT_HEADSIZE:-99}
die() { echo "$*"; exit 1; } >&2
| grep -e "^#+" | sed -e "s/^#+[ ]*//g" -e "s/${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; } usagefull() { head -${SCRIPT_HEADSIZE:-99}
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"
| grep -e "^#[%+-]" | sed -e "s/^#[%+-]//g" -e "s/${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; } scriptinfo() { head -${SCRIPT_HEADSIZE:-99}
function is_command () {
    log "[I] check if commad  exists"
    type "" &> /dev/null ;
}

CMD=zip

if is_command ${CMD} ; then
   log "[I] '${CMD}' command found"
else
   log "[E] '${CMD}' command not found"
fi
| grep -e "^#-" | sed -e "s/^#-//g" -e "s/${SCRIPT_NAME}/${SCRIPT_NAME}/g"; }

Here is what you should obtain:

以下是您应该获得的:

function process_template() {
    source  > 

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

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

process_template template.tmpl template.result

You can get the full script template here: http://www.uxora.com/unix/shell-script/18-shell-script-template

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

回答by William Pursell

If you're concerned about portability, do not use ==in tests. Use =instead. Do not explicitly check if $#is 0. Instead, use ${n?error message}the first time you reference a required argument (eg ${3?error message}). This prevents the extremely annoying practice of emitting a usage statement instead of an error message. And most importantly, always put error messages on the right stream and exit with the correct status. For example:

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

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

It is often convenient to do something like:

执行以下操作通常很方便:

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

回答by Renat Gilmanov

I will also share my results. The idea behind all these examples is to encourage overall quality. It is also important to make sure final result is safe enough.

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

Logging

日志记录

It is really crucial to have proper logging available from the same beginning. I'm just trying to think about production usage.

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

#!/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.

Command test

命令测试

This is about safety, real-life environments and proper error-handling. Could be optional.

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

##代码##

Template processing

模板处理

Could be only my subjective opinion, but anyway. I used several different ways to generate some configuration/etc right from the script. Perl, sed and others do the job, but look a little bit scary.

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

Recently I noticeda better way:

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

##代码##

Template example

模板示例

##代码##

Result example

结果示例

##代码##

回答by tijagi

There is no more helpful thing to a shell script than a well documented behaviour with examples of usage and a list of known bugs. I think that no one program can be titled bulletproof, and bugs may appear in every moment (especially when your script gets used by the other people), so the only thing I'm taking care of is the good coding styleand using only these things that the script really need. You're standing on the way of aggregating, and it always going to become a large system which comes with a lot of unused modules, which is hard to port and hard to support. And the more system trying to be portable, the bigger it grows. Seriously, shell scripts do not need it to be implemented in that way. They must be kept as small as possible to simplify further use.

对 shell 脚本来说,没有什么比带有使用示例和已知错误列表的详细记录的行为更有用的了。我认为没有一个程序可以称得上是防弹的,bug 可能在每一刻都出现(尤其是当你的脚本被其他人使用时),所以我唯一关心的是良好的编码风格,并且只使用这些脚本真正需要的东西。你正站在聚合的道路上,它总是会变成一个带有大量未使用模块的大型系统,难以移植和支持。尝试便携的系统越多,它的增长就越大。说真的,shell 脚本不需要以这种方式实现它。它们必须尽可能小以简化进一步的使用。

If the system really needs something big and bulletproof, it's time to think about C99 or even C++.

如果系统真的需要一些大而防弹的东西,那么是时候考虑 C99 甚至 C++ 了。

回答by crizCraig

Here's my bash boilerplate with some sane options explained in the comments

这是我的 bash 样板,在评论中解释了一些合理的选项

##代码##