Shell 脚本模板

发布于 2024-07-12 02:03:43 字数 1436 浏览 5 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

霊感 2024-07-19 02:03:43

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

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

第三行是版本控制标识字符串 - 它实际上是与 SCCS 标记“@(#)”的混合体可以通过 (SCCS) 程序 what 和 RCS 版本字符串来识别,当文件放在 RCS 下时,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 符号编写的。关于为什么任何人在 #! 符号后面放置 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 的文件名前缀。 这不是一项安全措施; 这是一个简单的并发措施,可以防止脚本的多个实例破坏彼此的数据。 (为了安全起见,请在非公共目录中使用不可预测的文件名。)第二行确保在以下情况下执行“rm”和“exit”命令: shell 接收任何信号 SIGHUP (1)、SIGINT (2)、SIGQUIT (3)、SIGPIPE (13) 或 SIGTERM (15)。 'rm' 命令删除任何与模板匹配的中间文件; exit 命令确保状态不为零,表明存在某种错误。 0 的“trap”意味着如果 shell 由于任何原因退出,代码也会被执行 - 它涵盖了标记为“实际工作”的部分中的粗心大意。 最后的代码会在退出时解除陷阱之前删除所有幸存的临时文件,最后以零(成功)状态退出。 显然,如果您想以其他状态退出,您可以 - 只需确保在运行 rmtrap 行之前将其设置在变量中,然后使用 退出$exitval

我通常使用以下方法从脚本中删除路径和后缀,以便在报告错误时可以使用 $arg0

arg0=$(basename $0 .sh)

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

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

如果只有一个或可能两个错误退出,我不关心该功能; 如果还有更多,我会这样做,因为它简化了编码。 我还创建了或多或少复杂的函数,称为 usage 来给出如何使用该命令的摘要 - 再次强调,前提是有多个地方需要使用该命令。

另一个相当标准的片段是选项解析循环,使用 < code>getopts 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(或者如果我使用反引号而不是 '$(...)' 即可。第二个适用于现代 shell;甚至可能有替代方案方括号而不是圆括号,但这很有效,所以我现在还没有费心去弄清楚它是什么,

我经常同时拥有 GNU 和非 GNU 版本的程序,而且我想成为。因此,我的许多脚本都使用以下变量:

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

然后,当我需要调用 Perl 或 sed 时,脚本使用 $PERL$SED。当某些行为不同时,这对我有帮助 - 我可以选择操作版本 - 或者在开发脚本时(我可以向命令添加额外的仅调试选项,而无需修改脚本)。有关 Shell 参数扩展的信息,请参阅code>${VAR:=value} 和相关符号。)

I'd extend Norman's answer to 6 lines, and the last of those is blank:

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

The third line is a version control identification string - it is actually a hybrid with an SCCS marker '@(#)' that can be identified by the (SCCS) program what and an RCS version string which is expanded when the file is put under RCS, the default VCS I use for my private use. The RCS program ident picks up the expanded form of $Id$, which might look like $Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $. The fifth line reminds me that the script should have a description of its purpose at the top; I replace the word with an actual description of the script (which is why there's no colon after it, for example).

After that, there is essentially nothing standard for a shell script. There are standard fragments that appear, but no standard fragment that appears in every script. (My discussion assumes that scripts are written in Bourne, Korn, or POSIX (Bash) shell notations. There's a whole separate discussion on why anyone putting a C Shell derivative after the #! sigil is living in sin.)

For example, this code appears in some shape or form whenever a script creates intermediate (temporary) files:

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

The first line chooses a temporary directory, defaulting to /tmp if the user did not specify an alternative ($TMPDIR is very widely recognized and is standardized by POSIX). It then creates a file name prefix including the process ID. This is not a security measure; it is a simple concurrency measure, preventing multiple instances of the script from trampling on each other's data. (For security, use non-predictable file names in a non-public directory.) The second line ensures that the 'rm' and 'exit' commands are executed if the shell receives any of the signals SIGHUP (1), SIGINT (2), SIGQUIT (3), SIGPIPE (13) or SIGTERM (15). The 'rm' command removes any intermediate files that match the template; the exit command ensures that the status is non-zero, indicating some sort of error. The 'trap' of 0 means that the code is also executed if the shell exits for any reason - it covers carelessness in the section marked 'real work'. The code at the end then removes any surviving temporary files, before lifting the trap on exit, and finally exits with a zero (success) status. Clearly, if you want to exit with another status, you may - just make sure you set it in a variable before running the rm and trap lines, and then use exit $exitval.

I usually use the following to remove the path and suffix from the script, so I can use $arg0 when reporting errors:

arg0=$(basename $0 .sh)

I often use a shell function to report errors:

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

If there's only one or maybe two error exits, I don't bother with the function; if there are any more, I do because it simplifies the coding. I also create more or less elaborate functions called usage to give the summary of how to use the command - again, only if there's more than one place where it would be used.

Another fairly standard fragment is an option parsing loop, using the getopts shell built-in:

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)

or:

shift $(($OPTIND - 1))

The quotes around "$OPTARG" handle spaces in arguments. The Dflag is cumulative, but the notation used here loses track of spaces in arguments. There are (non-standard) ways to work around that problem, too.

The first shift notation works with any shell (or would do if I used back-ticks instead of '$(...)'. The second works in modern shells; there might even be an alternative with square brackets instead of parentheses, but this works so I've not bothered to work out what that is.

One final trick for now is that I often have both the GNU and a non-GNU version of programs around, and I want to be able to choose which I use. Many of my scripts, therefore, use variables such as:

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

And then, when I need to invoke Perl or sed, the script uses $PERL or $SED. This helps me when something behaves differently - I can choose the operational version - or while developing the script (I can add extra debug-only options to the command without modifying the script). (See Shell parameter expansion for information on the ${VAR:=value} and related notations.)

夜雨飘雪 2024-07-19 02:03:43

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

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

I use the first set of ## lines for the usage documentation. I can't remember now where I first saw this.

#!/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.
}
回梦 2024-07-19 02:03:43

任何要公开发布的代码都应该具有以下短标头:

# Script to turn lead into gold
# Copyright (C) 2009 Ima Hacker ([email protected])
# Permission to copy and modify is granted under the foo license
# Last revised 1/1/2009

在代码标头中保留更改日志是版本控制系统非常不方便时的倒退。 最后修改日期向某人显示脚本有多旧。

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

当谈到继承其他人的脚本时,我发现人们倾向于在不需要的地方大量注释(例如#loop over $var),并且在有注释的地方非常零星地注释 需要(例如,带有数十个参数的超长 Perl 单行代码或 JVM 执行)。 这根本不是 shell 脚本所独有的,它是许多已建立的代码库中的一个问题,但在脚本中尤其令人沮丧。 我不知道 /bin/foo -- {mile long list of argument} 通过查看它会做什么,但我确实知道编写脚本的构造。 如果你正在做一些表面上看起来有点疯狂的事情,评论也非常受欢迎。

有些 shell 不喜欢输入类型化的“本地”变量。 我相信直到今天 Busybox(一种常见的救援 shell)就是其中之一。 改为 GLOBALS_OBVIOUS,它更容易阅读,尤其是通过 /bin/sh -x ./script.sh 进行调试时。

我个人的偏好是让逻辑自己说话并尽量减少解析器的工作。 例如,许多人可能会写:

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

Where I'd just:

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

同样,有人可能会写:

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

... where Id:

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

我唯一使用传统 if / then / else 的时候是如果有一个 else-if 可以放入混合。

只需查看大多数使用 autoconf 的免费软件包中的“配置”脚本,就可以研究一个非常好的可移植 shell 代码的可怕的疯狂示例。 我说“疯狂”是因为它的 6300 行代码满足了人类已知的每一个具有类 UNIX shell 的系统。 你不想要那种膨胀,但是研究其中的一些各种可移植性黑客是很有趣的..比如对那些可能将 /bin/sh 指向 zsh 的人友善:)

我能给出的唯一其他建议是请注意此处文档中的扩展,即

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

...当您可能想将变量保留在适当位置时,将扩展 $name 。 通过以下方式解决这个问题:

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

这会将 $name 保留为变量,而不是扩展它。

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

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

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

Any code that is going to be released in the wild should have the following short header:

# Script to turn lead into gold
# Copyright (C) 2009 Ima Hacker ([email protected])
# Permission to copy and modify is granted under the foo license
# Last revised 1/1/2009

Keeping a change log going in code headers is a throwback from when version control systems were terribly inconvenient. A last modified date shows someone how old the script is.

If you are going to be relying on bashisms, use #!/bin/bash , not /bin/sh, as sh is the POSIX invocation of any shell. Even if /bin/sh points to bash, many features will be turned off if you run it via /bin/sh. Most Linux distributions will not take scripts that rely on bashisms, try to be portable.

When it comes to inheriting other people's scripts, I've found that folks tend to comment heavily where it's not needed (e.g. # loop over $var) and very sporadically where comments are needed (e.g. a super-long Perl one-liner or JVM execution with dozens of arguments). This is not unique to shell scripting at all, it's a problem in many established code bases, but it's especially frustrating in scripts. I don't know what /bin/foo -- {mile long list of arguments} does by looking at it, but I do know the constructs of writing a script. Comments are also very much appreciated where you're doing something that looks a little nuts on the surface.

Some shells don't like to be fed typed 'local' variables. I believe to this day Busybox (a common rescue shell) is one of them. Make GLOBALS_OBVIOUS instead, it's much easier to read, especially when debugging via /bin/sh -x ./script.sh.

My personal preference is to let logic speak for itself and minimize work for the parser. For instance, many people might write:

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

Where I'd just:

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

Likewise, someone might write:

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

... where I'd:

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

The only time I use conventional if / then / else is if there's an else-if to throw in the mix.

A horribly insane example of very good portable shell code can be studied by just viewing the 'configure' script in most free software packages that use autoconf. I say insane because its 6300 lines of code that caters to every system known to humankind that has a UNIX like shell. You don't want that kind of bloat, but it is interesting to study some of the various portability hacks within.. such as being nice to those who might point /bin/sh to zsh :)

The only other advice I can give is watch your expansion in here-docs, i.e.

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

... is going to expand $name, when you probably want to leave the variable in place. Solve this via:

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

which will leave $name as a variable, instead of expanding it.

I also highly recommend learning how to use trap to catch signals .. and make use of those handlers as boilerplate code. Telling a running script to slow down with a simple SIGUSR1 is quite handy :)

Most new programs that I write (which are tool / command line oriented) start out as shell scripts, it's a great way to prototype UNIX tools.

You might also like the SHC shell script compiler, check it out here.

月朦胧 2024-07-19 02:03:43

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

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

This is the header I use for my script shell (bash or ksh).
It is a man look alike and it is used to display usage() as well.

#!/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 ${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"; }

Here is what you should obtain:

# 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

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

凉月流沐 2024-07-19 02:03:43

启用错误检测可以更轻松地及早检测脚本中的问题:

set -o errexit

出现第一个错误时退出脚本。 这样,您就可以避免继续执行依赖于脚本中较早部分内容的操作,最终可能会出现一些奇怪的系统状态。

set -o nounset

将对未设置变量的引用视为错误。 避免在未设置 $var 的情况下运行 rm -you_know_what "$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

Enabling error detection makes it much easier to detect problems in the script early:

set -o errexit

Exit script on first error. That way you avoid continuing on to do something which depended on something earlier in the script, perhaps ending up with some weird system state.

set -o nounset

Treat references to unset variables as errors. Very important to avoid running things like rm -you_know_what "$var/" with an unset $var. If you know that the variable can be unset, and this is a safe situation, you can use ${var-value} to use a different value if it's unset or ${var:-value} to use a different value if it's unset or empty.

set -o noclobber

It's easy to make the mistake of inserting a > where you meant to insert <, and overwrite some file which you meant to read. If you need to clobber a file in your script, you can disable this before the relevant line and enable it again afterwards.

set -o pipefail

Use the first non-zero exit code (if any) of a set of piped command as the exit code of the full set of commands. This makes it easier to debug piped commands.

shopt -s nullglob

Avoid that your /foo/* glob is interpreted literally if there are no files matching that expression.

You can combine all of these in two lines:

set -o errexit -o nounset -o noclobber -o pipefail
shopt -s nullglob
╭⌒浅淡时光〆 2024-07-19 02:03:43

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

My bash template is as below(set in my vim configuration):

#!/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==========
香草可樂 2024-07-19 02:03:43

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

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

What you can do is to make a script that creates a header for a script & and have it auto open in your favorite editor. I saw a guy do that at this site:

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
#===============================================================================
秋日私语 2024-07-19 02:03:43

我建议

#!/bin/ksh

就是这样。 shell 脚本的重量级块注释? 我明白了。

建议:

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

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

I would suggest

#!/bin/ksh

and that's it. Heavyweight block comments for shell scripts? I get the willies.

Suggestions:

  1. Documentation should be data or code, not comments. At least a usage() function. Have a look at how ksh and the other AST tools document themselves with --man options on every command. (Can't link because the web site is down.)

  2. Declare local variables with typeset. That's what it's for. No need for nasty underscores.

楠木可依 2024-07-19 02:03:43

一般来说,我编写的每个脚本都有一些我喜欢遵守的约定。
我编写所有脚本时都假设其他人可能会阅读它们。

我用我的标题开始每个脚本,

#!/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 变量,我更喜欢使用大写字母。 除非另有必要,我尽量保留 4 - 8 个字符。 这些名称尽可能与其用途相关。

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

我喜欢以标准方式处理争论。 我总是更喜欢 getopts,而不是 getopt。 我从不使用“read”命令和 if 语句进行黑客攻击。 我还喜欢使用 case 语句,以避免嵌套 if。 我对长选项使用翻译脚本,因此 --help 意味着 -h 到 getopts。 我用 bash(如果可以的话)或通用 sh 编写所有脚本。

我从不在文件名或任何与此相关的名称中使用 bash 解释符号(或任何解释符号)。
具体来说... " ' ` $ & * # () {} [] -,我使用 _ 表示空格。

记住,这些只是约定。最佳实践,粗略的,但有时你被迫超出界限。最重要的是在项目之间和项目内部保持一致。

Generally, I have a few conventions I like to stick to for every script I write.
I write all scripts with assumption that other people might read them.

I start every script with my header,

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

I use that date format, for easier grep/searching.
I use the '[' braces to indicate text people need to enter themselves.
if they occur outside a comment, I try to start them with '#['.
That way if someone pastes them as is, it wont be mistaken for input or a test command. Check the usage section on a man page, to see this style as an example.

When I want to comment out a line of code, I use a single '#'. When I am doing a comment as a note, I use a double '##'. The /etc/nanorc uses that convention also. I find it helpful, to differentiate a comment that was chosen not to execute; verses a comment that was created as a note.

All my shell variables, I prefer to do in CAPS. I try to keep between 4 - 8 characters, unless otherwise necessary. The names relate, as best as possible, with their usage.

I also always exit with 0 if successful, or a 1 for errors. If the script has many different types of errors (and would actually help someone, or could be used in some code in some way), I would choose a documented sequence over 1.
In general, exit codes are not as strictly enforced in the *nix world. Unfortunately I have never found a good general number scheme.

I like to process arguments in the standard manner. I always prefer getopts, to getopt. I never do some hack with 'read' commands and if statements. I also like to use the case statement, to avoid nested ifs. I use a translating script for long options, so --help means -h to getopts. I write all scripts in either bash (if acceptable) or generic sh.

I NEVER use bash interpreted symbols (or any interpreted symbol) in filenames, or any name for that matter.
specifically... " ' ` $ & * # () {} [] -, I use _ for spaces.

Remember, these are just conventions. Best practice, of coarse, but sometimes you are forced outside the lines. The most important is to be consistent across and within your projects.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文