27

新しく作成されたすべてのスクリプトの標準として使用するのに適した bash/ksh スクリプト テンプレートについて、あなたの提案は何ですか?

私は通常#!、ファイル名、概要、使用法、戻り値、作成者、変更ログを含むコメントアウトされたヘッダーから (行の後に) 開始し、80 文字の行に収まるようにします。

すべてのドキュメント行は、ダブル ハッシュ シンボルで開始する##ので、簡単に grep できるようになり、ローカル変数名の前に「__」が追加されます。

他のベストプラクティスはありますか? チップ?命名規則?リターンコードはどうですか?

バージョン管理に関するコメント: 私たちは SVN を問題なく使用していますが、企業内の別の部門には別のリポジトリがあり、これがそのスクリプトです。@author 情報がない場合、Q の連絡先を知るにはどうすればよいですか? javadocs に似たエントリを使用することには、シェルのコンテキストでもメリットがありますが、私見ですが、間違っている可能性があります。

4

9 に答える 9

24

Norman の回答を 6 行に拡張しますが、最後の行は空白です。

#!/bin/ksh
#
# @(#)$Id$
#
# Purpose
 

3 行目はバージョン管理識別文字列です。実際には@(#)、(SCCS) プログラムによって識別できる SCCS マーカー ' ' とwhat、ファイルがデフォルトの VCS である RCS の下に置かれたときに展開される RCS バージョン文字列とのハイブリッドです。私的用途で使用しています。RCS プログラムは、 のように見えるident拡張形式の を取得します。5 行目で、スクリプトの先頭にその目的の説明が必要であることを思い出しました。単語をスクリプトの実際の説明に置き換えます (たとえば、後にコロンがないのはそのためです)。$Id$$Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $

その後、シェルスクリプトの標準は本質的に何もありません。表示される標準フラグメントはありますが、すべてのスクリプトに表示される標準フラグメントはありません。(私の議論は、スクリプトが Bourne、Korn、または POSIX (Bash) シェル表記法で書かれていることを前提としています。シジルの後に C シェルの派生物を置く人がなぜ#!罪に生きているのかについては、まったく別の議論があります。)

たとえば、次のコードは、スクリプトが中間 (一時) ファイルを作成するたびに、何らかの形で表示されます。

tmp=${TMPDIR:-/tmp}/prog.$$
trap "rm -f $tmp.?; exit 1" 0 1 2 3 13 15

...real work that creates temp files $tmp.1, $tmp.2, ...

rm -f $tmp.?
trap 0
exit 0

最初の行は一時ディレクトリを選択します。ユーザーが別のディレクトリを指定しなかった場合、デフォルトは /tmp になります ($TMPDIR は非常に広く認識されており、POSIX によって標準化されています)。次に、プロセス ID を含むファイル名プレフィックスを作成します。これはセキュリティ対策ではありません。これは、スクリプトの複数のインスタンスが互いのデータを踏みにじるのを防ぐための単純な並行処理です。(セキュリティのために、非パブリック ディレクトリでは予測不可能なファイル名を使用してください。) 2 行目はrmexitシェルがシグナル SIGHUP (1)、SIGINT (2)、 SIGQUIT (3)、SIGPIPE (13) または SIGTERM (15)。' rm' コマンドは、テンプレートに一致するすべての中間ファイルを削除します。このexitコマンドは、ステータスがゼロ以外であることを保証します。何らかのエラーを示しています。'trap' of 0 は、何らかの理由でシェルが終了した場合にもコードが実行されることを意味します。これは、「実際の作業」とマークされたセクションの不注意をカバーします。最後のコードは、終了時にトラップを解除する前に、残っている一時ファイルをすべて削除し、最終的にゼロ (成功) ステータスで終了します。明らかに、別のステータスで終了したい場合は、rmおよびtrap行を実行する前に変数に設定してから、 を使用してexit $exitvalください。

私は通常、次を使用してスクリプトからパスとサフィックスを削除します。これにより、$arg0エラーを報告するときに使用できます。

arg0=$(basename $0 .sh)

エラーを報告するためにシェル関数をよく使用します。

error()
{
    echo "$arg0: $*" 1>&2
    exit 1
}

エラー終了が 1 つまたは 2 つしかない場合は、関数を気にしません。それ以上ある場合は、コーディングが簡単になるのでそうします。また、コマンドの使用方法の要約を提供するために呼び出される多かれ少なかれ精巧な関数も作成します。これもusage、コマンドが使用される場所が複数ある場合に限ります。

getoptsもう 1 つの非常に標準的なフラグメントは、組み込みシェルを使用したオプション解析ループです。

vflag=0
out=
file=
Dflag=
while getopts hvVf:o:D: flag
do
    case "$flag" in
    (h) help; exit 0;;
    (V) echo "$arg0: version $Revision$ ($Date$)"; exit 0;;
    (v) vflag=1;;
    (f) file="$OPTARG";;
    (o) out="$OPTARG";;
    (D) Dflag="$Dflag $OPTARG";;
    (*) usage;;
    esac
done
shift $(expr $OPTIND - 1)

また:

shift $(($OPTIND - 1))

"$OPTARG" を囲む引用符は、引数のスペースを処理します。Dflag は累積的ですが、ここで使用されている表記法では引数内のスペースを追跡できません。この問題を回避する (非標準の) 方法もあります。

最初のシフト表記は、どのシェルでも機能します (または、' $(...)' の代わりにバックティックを使用した場合も同様です。2 番目は最新のシェルで機能します。括弧の代わりに角括弧を使用する代替手段もあるかもしれませんが、これは機能するので、それが何であるかを理解することは気にしません。

今のところ最後の秘訣は、GNU 版と非 GNU 版の両方のプログラムを持っていることが多いので、どちらを使用するかを選択できるようにしたいということです。したがって、私のスクリプトの多くは、次のような変数を使用します。

: ${PERL:=perl}
: ${SED:=sed}

そして、Perl または を呼び出す必要がある場合sed、スクリプトは$PERLorを使用し$SEDます。これは、動作バージョンを選択できる場合や、スクリプトの開発中に動作が異なる場合に役立ちます (スクリプトを変更せずにコマンドにデバッグ専用オプションを追加できます)。(および関連する表記法については、シェル パラメーターの展開を参照してください。)${VAR:=value}

于 2009-01-10T06:43:25.983 に答える
17

最初の ## 行のセットを使用法ドキュメントに使用します。これを最初にどこで見たのか、今は思い出せない。

#!/bin/sh
## Usage: myscript [options] ARG1
##
## Options:
##   -h, --help    Display this message.
##   -n            Dry-run; only show what would be done.
##

usage() {
  [ "$*" ] && echo "$0: $*"
  sed -n '/^##/,/^$/s/^## \{0,1\}//p' "$0"
  exit 2
} 2>/dev/null

main() {
  while [ $# -gt 0 ]; do
    case $1 in
    (-n) DRY_RUN=1;;
    (-h|--help) usage 2>&1;;
    (--) shift; break;;
    (-*) usage "$1: unknown option";;
    (*) break;;
    esac
  done
  : do stuff.
}
于 2011-10-13T14:19:01.927 に答える
10

公開される予定のコードには、次の短いヘッダーが必要です。

# Script to turn lead into gold
# Copyright (C) 2009 Ima Hacker (i.m.hacker@foo.org)
# Permission to copy and modify is granted under the foo license
# Last revised 1/1/2009

変更ログをコードヘッダーに記録し続けることは、バージョン管理システムがひどく不便だったときからの逆戻りです。最終変更日は、スクリプトの古さを示しています。

bashismに依存する場合は、/ bin / shではなく#!/ bin / bashを使用します。これは、shが任意のシェルのPOSIX呼び出しであるためです。/ bin / shがbashを指している場合でも、/ bin / shを介して実行すると、多くの機能がオフになります。ほとんどのLinuxディストリビューションは、bashismに依存するスクリプトを使用せず、移植性を確保しようとします。

他の人のスクリプトを継承することになると、人々はコメントが不要な場所(例)に大量にコメントする傾向があり、コメント必要な場所(例:非常に長いPerlワンライナーまたは数十の引数を持つJVM実行# loop over $var)に非常に散発的にコメントする傾向があることがわかりました)。これはシェルスクリプトに固有のものではなく、多くの確立されたコードベースで問題になりますが、スクリプトでは特にイライラします。それを見て何が起こるかはわかりませんが、スクリプトを書くための構成は知っています。表面に少しナッツのように見える何かをしているところでも、コメントは非常に高く評価されています。/bin/foo -- {mile long list of arguments}

一部のシェルは、型指定された「ローカル」変数を供給されることを好みません。今日まで、Busybox(一般的なレスキューシェル)はその1つだと思います。代わりにGLOBALS_OBVIOUSを作成してください。特に、/ bin / sh -x ./script.shを使用してデバッグする場合は、読みやすくなります。

私の個人的な好みは、ロジックにそれ自体を語らせ、パーサーの作業を最小限に抑えることです。たとえば、多くの人が次のように書くかもしれません。

if [ $i = 1 ]; then
    ... some code 
fi

私がしたいところ:

[ $i = 1 ] && {
    ... some code
}

同様に、誰かが書くかもしれません:

if [ $i -ne 1 ]; then
   ... some code
fi

...私がしたいところ:

[ $i = 1 ] || {
   ... some code 
}

私が従来のif/then / elseを使用するのは、else-ifがミックスに投入される場合のみです。

autoconfを使用するほとんどのフリーソフトウェアパッケージの「configure」スクリプトを表示するだけで、非常に優れたポータブルシェルコードの恐ろしい非常識な例を調べることができます。UNIXライクなシェルを持つ人類に知られているすべてのシステムに対応する6300行のコードがあるので、私は非常識だと言います。この種の肥大化は望ましくありませんが、/ bin/shをzshにポイントする可能性のある人に役立つなどのさまざまな移植性ハックのいくつかを研究することは興味深いです:)

私が与えることができる他の唯一のアドバイスは、here-docsであなたの拡張を監視することです。

cat << EOF > foo.sh
   printf "%s was here" "$name"
EOF

...変数をそのままにしておきたい場合は、$nameを展開します。これを次の方法で解決します。

  printf "%s was here" "\$name"

これは、$ nameを展開するのではなく、変数として残します。

また、トラップを使用して信号をキャッチする方法を学び、それらのハンドラーをボイラープレートコードとして使用することを強くお勧めします。実行中のスクリプトに単純なSIGUSR1で速度を落とすように指示するのは非常に便利です:)

私が作成するほとんどの新しいプログラム(ツール/コマンドライン指向)は、シェルスクリプトとして開始されます。これは、UNIXツールのプロトタイプを作成するための優れた方法です。

SHCシェルスクリプトコンパイラもお気に召すかもしれません。こちらをご覧ください

于 2009-01-10T07:39:38.693 に答える
7

エラー検出を有効にすると、スクリプトの問題を早期に検出しやすくなります。

set -o errexit

最初のエラーでスクリプトを終了します。そうすれば、スクリプトの前の方にある何かに依存する何かを実行し続けることを回避でき、最終的には奇妙なシステム状態になる可能性があります。

set -o nounset

設定されていない変数への参照をエラーとして扱います。rm -you_know_what "$var/"unset のようなものを実行しないようにすることが非常に重要$varです。変数を設定解除できることがわかっていて、これが安全な状況である場合は、${var-value}設定解除されている場合は別の値を使用したり、設定されていないか${var:-value}の場合は別の値を使用したりできます。

set -o noclobber

>を挿入するつもりだった場所に を挿入<して、読み取るつもりだったファイルを上書きしてしまうという間違いを犯しがちです。スクリプトでファイルを上書きする必要がある場合は、関連する行の前でこれを無効にし、後で再度有効にすることができます。

set -o pipefail

コマンドの完全なセットの終了コードとして、パイプされたコマンドのセットの最初のゼロ以外の終了コード (存在する場合) を使用します。これにより、パイプされたコマンドのデバッグが容易になります。

shopt -s nullglob

その式に一致するファイルがない場合、 /foo/*glob が文字どおりに解釈されることは避けてください。

これらすべてを 2 行で組み合わせることができます。

set -o errexit -o nounset -o noclobber -o pipefail
shopt -s nullglob
于 2011-11-01T14:32:06.123 に答える
5

私のbashテンプレートは次のとおりです(私のvim構成で設定):

#!/bin/bash

## DESCRIPTION: 

## AUTHOR: $USER_FULLNAME

declare -r SCRIPT_NAME=$(basename "$BASH_SOURCE" .sh)

## exit the shell(default status code: 1) after printing the message to stderr
bail() {
    echo -ne "$1" >&2
    exit ${2-1}
} 

## help message
declare -r HELP_MSG="Usage: $SCRIPT_NAME [OPTION]... [ARG]...
  -h    display this help and exit
"

## print the usage and exit the shell(default status code: 2)
usage() {
    declare status=2
    if [[ "$1" =~ ^[0-9]+$ ]]; then
        status=$1
        shift
    fi
    bail "${1}$HELP_MSG" $status
}

while getopts ":h" opt; do
    case $opt in
        h)
            usage 0
            ;;
        \?)
            usage "Invalid option: -$OPTARG \n"
            ;;
    esac
done

shift $(($OPTIND - 1))
[[ "$#" -lt 1 ]] && usage "Too few arguments\n"

#==========MAIN CODE BELOW==========
于 2012-04-18T11:50:25.387 に答える
3

できることは、スクリプトのヘッダーを作成するスクリプトを作成し、それをお気に入りのエディターで自動的に開くことです。私はこのサイトで男がそれをしているのを見ました:

http://code.activestate.com/recipes/577862-bash-script-to-create-a-header-for-bash-scripts/?in=lang-bash

#!/bin/bash -       
#title           :mkscript.sh
#description     :This script will make a header for a bash script.
#author          :your_name_here
#date            :20110831
#version         :0.3    
#usage           :bash mkscript.sh
#notes           :Vim and Emacs are needed to use this script.
#bash_version    :4.1.5(1)-release
#===============================================================================
于 2011-10-28T02:54:17.393 に答える
3

私は提案します

#!/bin/ksh

以上です。シェルスクリプトの重いブロックコメント? 私はウィリーを手に入れます。

提案:

  1. ドキュメントは、コメントではなく、データまたはコードである必要があります。少なくともusage()機能。すべてのコマンドで --man オプションを使用して、ksh およびその他の AST ツールがどのように文書化されているかを確認してください。(サイトがダウンしているためリンクできません。)

  2. でローカル変数を宣言しtypesetます。それがそのためです。厄介なアンダースコアは必要ありません。

于 2009-01-10T05:16:53.567 に答える
3

一般的に、私は自分が書くすべてのスクリプトに固執したいいくつかの規則を持っています。私はすべてのスクリプトを、他の人が読む可能性があるという前提で書いています。

私はすべてのスクリプトをヘッダーから始めます。

#!/bin/bash
# [ID LINE]
##
## FILE: [Filename]
##
## DESCRIPTION: [Description]
##
## AUTHOR: [Author]
##
## DATE: [XX_XX_XXXX.XX_XX_XX]
## 
## VERSION: [Version]
##
## USAGE: [Usage]
##

grep/検索を容易にするために、その日付形式を使用します。「[」中かっこを使用して、ユーザーが自分で入力する必要があるテキストを示します。それらがコメントの外にある場合は、「#[」で始めようとします。こうすれば、誰かがそのまま貼り付けても、入力やテスト コマンドと間違われることはありません。例としてこのスタイルを確認するには、man ページの使用セクションを確認してください。

コード行をコメントアウトしたいときは、単一​​の「#」を使用します。メモとしてコメントするときは、二重の '##' を使用します。もその /etc/nanorc規則を使用します。実行しないように選択されたコメントを区別するのに役立つと思います。メモとして作成されたコメントを詩します。

すべてのシェル変数は、CAPS で行うことを好みます。特に必要がない限り、4 ~ 8 文字を維持するようにしています。名前は、その使用法と可能な限り関連しています。

また、成功した場合は常に 0 で終了し、エラーの場合は 1 で終了します。スクリプトにさまざまな種類のエラーがある場合 (実際に誰かを助ける、または何らかの方法でコードで使用できる場合)、1 よりも文書化されたシーケンスを選択します。一般に、終了コードは *ニクスワールド。残念ながら、私は良い一般的な数体系を見つけたことがありません。

私は標準的な方法で引数を処理するのが好きです。私は常にgetoptよりもgetoptsを好みます。「読み取り」コマンドとifステートメントでハックを行うことはありません。また、ネストされた if を避けるために、case ステートメントを使用するのも好きです。長いオプションには翻訳スクリプトを使用するので、 --help は getopts への -h を意味します。私はすべてのスクリプトを bash (許容できる場合) または一般的な sh のいずれかで記述します。

私は絶対に bash で解釈されたシンボル (または解釈されたシンボル) をファイル名やその他の名前に使用しません。具体的には... " ' ` $ & * # () {} [] -、スペースには _ を使用します。

これらは単なる慣例であることを忘れないでください。ベストプラクティスは粗いですが、時にはラインの外に追いやられることもあります。最も重要なのは、プロジェクト間およびプロジェクト内で一貫性を保つことです。

于 2011-10-11T16:36:39.230 に答える