对于一个好的bash / ksh脚本模板,您有什么建议可以用作所有新创建脚本的标准?

我通常使用带有文件名,概要,用法,返回值,作者,更改日志的注释掉的标题开始(在#!行之后)并且适合80-char行。

所有文档行我都以双哈希符号##开头,因此我可以轻松地为它们进行grep,并且本地var名称前面带有<!> quot; __ <!>“;

还有其他最佳做法吗?提示?命名约定?返回代码怎么样?

关于版本控制的评论:我们使用SVN可以,但企业中的另一个部门有一个单独的repo,这是他们的脚本。如果没有@author信息,我如何知道与Q联系的人?使用类似于javadocs的条目即使在shell上下文中也有一些优点,恕我直言,但我可能错了。

有帮助吗?

解决方案

我将Norman的答案扩展到6行,其中最后一行是空白的:

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

第三行是版本控制标识字符串 - 它实际上是一个SCCS标记'@(#)'的混合体,可由(SCCS)程序what标识,并且RCS版本字符串在文件放在RCS下,这是我用于私人使用的默认VCS。 RCS程序ident选择$Id$的扩展形式,可能看起来像$Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $。第五行提醒我,剧本应该在顶部描述其目的;我用脚本的实际描述替换了这个单词(这就是为什么后面没有冒号的原因)。

之后,shell脚本基本上没有标准。出现了标准片段,但没有出现在每个脚本中的标准片段。 (我的讨论假设脚本是用Bourne,Korn或POSIX(Bash)shell表示法编写的。有一个完整的单独讨论,为什么任何人在#! sigil之后使用C Shell衍生物生活在罪中。)

例如,只要脚本创建中间(临时)文件,此代码就会以某种形式或形式出现:

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的文件名前缀。这不是一项安全措施;这是一个简单的并发度量,可以防止脚本的多个实例践踏彼此的数据。 (为了安全起见,在非公共目录中使用不可预测的文件名。)第二行确保在shell收到任何信号SIGHUP(1)时执行'rm'和'exit'命令,SIGINT(2),SIGQUIT(3),SIGPIPE(13)或SIGTERM(15)。 'trap'命令删除与模板匹配的所有中间文件; exit $exitval命令确保状态为非零,表示存在某种错误。 '$arg0'为0表示如果shell因任何原因退出,也会执行代码 - 它在标记为“实际工作”的部分中包含粗心。最后的代码然后删除任何幸存的临时文件,退出时解除陷阱,最后以零(成功)状态退出。显然,如果要退出其他状态,可以 - 只需确保在运行usagegetopts行之前将其设置在变量中,然后使用$(...)

我通常使用以下命令从脚本中删除路径和后缀,因此我可以在报告错误时使用sed

arg0=$(basename $0 .sh)

我经常使用shell函数来报告错误:

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

如果只有一个或两个错误退出,我不打扰该功能;如果还有,我这样做是因为它简化了编码。我还创建了更多或更少的详细函数,名为$PERL,以提供如何使用命令的摘要 - 再次,只有在有多个地方才能使用它。

另一个相当标准的片段是一个选项解析循环,使用 $SED shell内置:

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是累积的,但这里使用的符号会丢失参数中的空格。有(非标准的)方法可以解决这个问题。

第一个移位符号适用于任何shell(或者如果我使用反向标记而不是'${VAR:=value}'。第二个适用于现代shell;甚至可能有一个替代方括号而不是括号,但是这是有效的,所以我不打算弄清楚那是什么。

现在最后一个技巧是我经常同时拥有GNU和非GNU版本的程序,我希望能够选择我使用的程序。因此,我的许多脚本都使用vari像这样的人:

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

然后,当我需要调用Perl或<=>时,脚本使用<=>或<=>。这有助于我在行为不同的时候 - 我可以选择操作版本 - 或者在开发脚本时(我可以在不修改脚本的情况下为命令添加额外的仅调试选项)。 (有关以下内容的信息,请参阅 Shell参数扩展 <=>和相关的符号。)

其他提示

我使用第一组##行作为使用文档。我现在不记得我第一次看到这个。

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

任何将在野外发布的代码都应该包含以下短标题:

# Script to turn lead into gold
# Copyright (C) 2009 Joe Q Hacker - All Rights Reserved
# Permission to copy and modify is granted under the foo license
# Last revised 1/1/2009

保持代码头中的更改日志是版本控制系统非常不方便的回归。最后修改日期显示某人脚本的年龄。

如果您要依赖bashisms,请使用#!/ bin / bash,而不是/ bin / sh,因为sh是任何shell的POSIX调用。即使/ bin / sh指向bash,如果通过/ bin / sh运行它,许多功能也将被关闭。大多数Linux发行版都不会使用依赖于bashisms的脚本,试着可移植。

对我来说,shell脚本中的注释有点愚蠢,除非他们阅读的内容如下:

# I am not crazy, this really is the only way to do this

Shell脚本非常简单(除非你写一个演示来教别人怎么做)代码几乎总是避免自己。

有些shell不喜欢输入键入的“本地”变量。我相信直到今天Busybox(一种常见的救援外壳)就是其中之一。改为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就是如果有其他的 - 如果要投入混音。

在大多数使用autoconf的免费软件包中查看'configure'脚本,可以研究非常好的便携式shell代码的可怕疯狂示例。我说疯了,因为它的6300行代码满足了人类已知的具有UNIX shell的每个系统。你不想要那种臃肿,但研究一些各种可移植性黑客很有意思......比如对那些可能指向/ bin / sh到zsh的人很好:)

我能给出的唯一其他建议是在here-docs中观察你的扩展,即

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

...当你想要保留变量时,将扩展$ name。通过以下方式解决这个问题:

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

将$ name作为变量,而不是扩展它。

我还强烈建议学习如何使用陷阱捕获信号..并使用这些处理程序作为样板代码。使用简单的SIGUSR1告诉正在运行的脚本减速非常方便:)

我编写的大多数新程序(面向工具/命令行)都是以shell脚本开头的,这是构建UNIX工具原型的好方法。

您可能也喜欢SHC shell脚本编译器,在此处查看

这是我用于脚本shell(bash或ksh)的标头。 它看起来很像man,它也用于显示用法()。

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

启用错误检测可以更容易地及早发现脚本中的问题:

set -o errexit

第一次出错时退出脚本。这样你就可以避免继续做一些依赖于脚本中早期事情的事情,或许最终会出现一些奇怪的系统状态。

set -o nounset

将对未设置变量的引用视为错误。非常重要的是避免使用未设置的rm -you_know_what "$var/"运行$var。如果您知道该变量可以取消设置,并且这是一种安全的情况,您可以使用${var-value}使用不同的值(如果未设置)或${var:-value}使用不同的值(如果它未设置或空。

set -o noclobber

很容易错误地插入>你想要插入<的地方,并覆盖你想读的一些文件。如果您需要在脚本中破坏文件,可以在相关行之前禁用它,然后再次启用它。

set -o pipefail

使用一组管道命令的第一个非零退出代码(如果有)作为完整命令集的退出代码。这样可以更轻松地调试管道命令。

shopt -s nullglob

如果没有与该表达式匹配的文件,请避免/foo/* glob 按字面解释

您可以将所有这些组合成两行:

set -o errexit -o nounset -o noclobber -o pipefail
shopt -s nullglob

我的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==========

我建议

#!/bin/ksh

就是这样。 shell脚本的重量级块注释?我得到了我的意志。

建议:

  1. 文档应该是数据或代码,而不是注释。至少一个usage()功能。看看ksh和其他AST工具如何在每个命令上使用--man选项记录自己。 (无法链接,因为网站已关闭。)

  2. 使用typeset声明局部变量。这就是它的用途。不需要令人讨厌的下划线。

您可以做的是制作一个脚本,为脚本<!>放大器创建标题;并在您喜欢的编辑器中自动打开它。我在这个网站上看到一个人这样做:

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

一般来说,我喜欢为我编写的每个脚本坚持一些约定。 我写了所有脚本,并假设其他人可能会阅读它们。

我用标题启动每个脚本,

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

我使用该日期格式,以便更容易地进行grep /搜索。 我使用'['大括号来表示人们需要进入的文本。 如果它们出现在评论之外,我会尝试用'#['开头。 这样,如果有人按原样粘贴它们,它就不会被误认为是输入或测试命令。检查手册页上的使用部分,以此样式为例。

当我想要注释掉一行代码时,我会使用一个'#'。当我作为注释做评论时,我使用了一个双'##'。 /etc/nanorc也使用该约定。我发现区分选择不执行的注释是有帮助的;与作为注释创建的评论相对应。

我所有的shell变量,我更喜欢在CAPS中。除非另有必要,否则我会尝试保留4到8个字符。这些名称尽可能与其用法相关。

如果成功,我也总是退出0,或者错误退出1。如果脚本有许多不同类型的错误(并且实际上可以帮助某些人,或者可能以某种方式在某些代码中使用),我会选择一个超过1的文档序列。 通常,退出代码在* nix世界中并不严格执行。不幸的是,我从未找到一个好的通用数字方案。

我喜欢以标准方式处理参数。我总是喜欢getopts,以及getopt。我从来没有使用'read'命令和if语句进行操作。我也喜欢使用case语句,以避免嵌套ifs。我使用翻译脚本作为长选项,所以--help意味着-h到getopts。我用bash(如果可以接受)或通用sh。

编写所有脚本

我永远不会在文件名中使用bash解释符号(或任何解释符号),或者任何名称。 特别是...... <!> ''$ <!>放大器; *#(){} [] - ,我用_作空格。

请记住,这些只是惯例。最佳做法,粗略,但有时你被迫在线外。最重要的是在项目内和项目内保持一致。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top