64

何百万もの開発者が、さまざまなタイプのタスクを解決するためのシェルスクリプトを作成しています。私はシェルスクリプトを使用して、展開、ライフサイクル管理、インストールを簡素化するか、単にグルー言語として使用しています。

私が気付いたのは、シェルスクリプトのスタイルと品質を実際に気にする人は誰もいないということです。多くのチームは、Java、C ++、...スタイルの問題の修正に多くの時間を費やしていますが、シェルスクリプトの問題を完全に無視しています。ちなみに、通常、特定のプロジェクト内にシェルスクリプトを実装する標準的な方法はありません。そのため、コードベース全体に数十の異なる、醜くバグのあるスクリプトが散在している場合があります。

私のプロジェクトでその問題を克服するために、私は、普遍的で十分に優れたシェルスクリプトテンプレートを作成することにしました。この質問をもう少し便利にするために、テンプレートをそのまま提供します。箱から出して、これらのテンプレートは以下を提供します:

  • コマンドライン引数の処理
  • 同期
  • いくつかの基本的なヘルプ

引数の処理: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
# -----------------------------------------------------------------

シェルフラグ(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
# -----------------------------------------------------------------

これらのテンプレートは、開発者の生活をさらに簡素化するために改善できると思います。

したがって、問題は、次のようにそれらを改善する方法です。

  • 組み込みのロギング
  • より良いエラー処理
  • 移植性の向上
  • フットプリントが小さい
  • 組み込みの実行時間追跡
4

6 に答える 6

29

これは私のスクリプトシェルテンプレートのヘッダーです(ここにあります:http ://www.uxora.com/unix/shell-script/18-shell-script-template )。

これは、manusage()がヘルプを表示するためにも使用する類似の外観です。

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

于 2015-04-11T14:52:27.210 に答える
20

私はシェルとして依存することを避け、POSIXで定義されたシェル構文bashに基づいてソリューションをモデル化し、シバンで使用します。最近、 Ubuntuがに変更さたときに多くの驚きがありました。/bin/sh/bin/shdash

シェルの世界でのもう1つのパンデミックは、終了ステータスコードの一般的な誤解です。理解しやすいコードで終了することで、他のシェルスクリプトが特定の障害にプログラムで反応できるようになります。残念ながら、これに関するガイダンスは「sysexits.h」ヘッダーファイル以外にはあまりありません。

優れたシェルスクリプトの実践に関する詳細情報を探している場合は、Kornシェルスクリプトのリソースに集中してください。Kshプログラミングは、無計画なスクリプトを作成するのではなく、実際のプログラミングに重点を置く傾向があります。

個人的には、シェルテンプレートの用途はあまりありません。残念なことに、ほとんどのエンジニアはテンプレートをコピーして貼り付け、同じずさんなシェルコードを書き続けます。より良いアプローチは、明確に定義されたセマンティクスを持つシェル関数のライブラリを作成し、他の人にそれらを使用するように説得することです。このアプローチは、変更管理にも役立ちます。たとえば、テンプレートに欠陥が見つかった場合、それを基にしたすべてのスクリプトが壊れており、変更が必要になります。ライブラリを使用すると、欠陥を1か所で修正できます。

シェルスクリプトの世界へようこそ。シェルスクリプトを書くことは、ルネッサンスに入っているように見える少し失われた芸術です。90年代後半にこのテーマについて書かれた良い本がいくつかありました。この本に対するAmazonのレビューはひどいように見えますが、BurnsとArthurによるUNIXシェルプログラミングが思い浮かびます。IMHO、効果的なシェルコードは、 The Art ofUnixProgrammingのEricS.Raymondによって説明されているUNIX哲学を取り入れています。

于 2012-12-23T14:04:16.263 に答える
6

移植性が心配な場合は==、テストで使用しないでください。=代わりに使用してください。$#が0であるかどうかを明示的にチェックしないでください。代わりに${n?error message}、必要な引数を初めて参照するときに使用してください(例${3?error message})。これにより、エラーメッセージの代わりに使用法ステートメントを発行するという非常に煩わしい慣習を防ぐことができます。そして最も重要なことは、常にエラーメッセージを正しいストリームに配置し、正しいステータスで終了することです。例えば:

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

多くの場合、次のようなことを行うと便利です。

die() { echo "$*"; exit 1; } >&2
于 2012-12-23T12:56:15.870 に答える
5

これが私の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.
于 2019-09-24T22:11:36.933 に答える
3

結果も共有します。これらすべての例の背後にある考え方は、全体的な品質を促進することです。最終結果が十分に安全であることを確認することも重要です。

ロギング

同じ最初から適切なロギングを利用できるようにすることは非常に重要です。私は本番環境の使用法について考えようとしています。

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

テンプレート処理

私の主観的な意見に過ぎないかもしれませんが、とにかく。スクリプトから直接構成などを生成するために、いくつかの異なる方法を使用しました。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
于 2012-12-25T17:42:16.810 に答える
1

使用例と既知のバグのリストを含む十分に文書化された動作ほど、シェルスクリプトに役立つものはありません。防弾というタイトルのプログラムは1つもないと思います。また、バグは常に発生する可能性があります(特に、スクリプトが他の人に使用される場合)。そのため、私が注意しているのは、優れたコーディングスタイルとこれらのみを使用することだけです。スクリプトが本当に必要とするもの。あなたは集約の途上に立っており、それは常に、移植やサポートが難しい未使用のモジュールがたくさんある大規模なシステムになります。そして、移植しようとするシステムが多ければ多いほど、システムは大きくなります。真剣に、シェルスクリプトはそのように実装する必要はありません。さらなる使用を簡素化するために、それらは可能な限り小さく保つ必要があります。

システムが本当に大きくて防弾の何かを必要とするなら、それはC99あるいはC++について考える時です。

于 2012-12-26T00:48:49.680 に答える