如何在Shell脚本中添加帮助方法?

166

如何检查一个shell脚本是否传递了-h属性? 当用户调用myscript.sh -h时,我想显示帮助消息。


相关示例:https://dev59.com/lWYr5IYBdhLWcg3wBl3p - Anton Tarasenko
8个回答

211

这里是一个bash的示例:

usage="$(basename "$0") [-h] [-s n] -- program to calculate the answer to life, the universe and everything

where:
    -h  show this help text
    -s  set the seed value (default: 42)"

seed=42
while getopts ':hs:' option; do
  case "$option" in
    h) echo "$usage"
       exit
       ;;
    s) seed=$OPTARG
       ;;
    :) printf "missing argument for -%s\n" "$OPTARG" >&2
       echo "$usage" >&2
       exit 1
       ;;
   \?) printf "illegal option: -%s\n" "$OPTARG" >&2
       echo "$usage" >&2
       exit 1
       ;;
  esac
done
shift $((OPTIND - 1))

在函数内使用:

  • 使用"$FUNCNAME"代替$(basename "$0")
  • 在调用getopts之前添加local OPTIND OPTARG

1
我试图在一个函数中尝试这个操作,但是当我尝试运行函数时,我会得到这个错误"basename: invalid option -- 'b'"。看起来它正在尝试通过前导破折号将"-bash"传递给basename - Morgan Estes
5
函数内部使用"$FUNCNAME"而不是"$0"。另外,加上local OPTIND OPTARG - glenn jackman
谢谢。FUNCNAME 可以使用。我把所有的函数都放在了一个文件里,所以这对于将它们扩展成对他人有用的东西来说非常完美。 - Morgan Estes
7
@sigur,请确保在使用"$usage"的每个地方都引用它。 - glenn jackman
5
shift $((OPTIND - 1))的作用是什么? - hpaknia
2
这将从位置参数中删除所有选项,以便$1成为跟随选项的第一个参数。如果您正在编写ls函数,并键入ls -l -a /,那么$1将是/ - glenn jackman

62

Shell脚本的第一个参数可以通过变量$1获取,因此最简单的实现方式为

if [ "$1" == "-h" ]; then
  echo "Usage: `basename $0` [somestuff]"
  exit 0
fi

但是anubhava所说的话。


你应该养成一个习惯,即在条件语句中使用 [[ ... ]] 来包装 if,以避免变量解析错误。来源:https://github.com/bahamas10/bash-style-guide#bashisms - JREAM
3
是的,尽管原发帖并没有指定使用Bash,并且 [ 是符合POSIX标准的版本。 - seb
2
注意 - 用于函数内使用: 如果您不想在运行函数后终止您的 shell,请将 exit 0 替换为 return。(我之前已经这样做过) - Illuminator

38

这里是我用来启动VNC服务器的一部分

#!/bin/bash
start() {
echo "Starting vnc server with $resolution on Display $display"
#your execute command here mine is below
#vncserver :$display -geometry $resolution
}

stop() {
echo "Killing vncserver on display $display"
#vncserver -kill :$display
}

#########################
# The command line help #
#########################
display_help() {
    echo "Usage: $0 [option...] {start|stop|restart}" >&2
    echo
    echo "   -r, --resolution           run with the given resolution WxH"
    echo "   -d, --display              Set on which display to host on "
    echo
    # echo some stuff here for the -a or --add-options 
    exit 1
}

################################
# Check if parameters options  #
# are given on the commandline #
################################
while :
do
    case "$1" in
      -r | --resolution)
          if [ $# -ne 0 ]; then
            resolution="$2"   # You may want to check validity of $2
          fi
          shift 2
          ;;
      -h | --help)
          display_help  # Call your function
          exit 0
          ;;
      -d | --display)
          display="$2"
           shift 2
           ;;

      -a | --add-options)
          # do something here call function
          # and write it in your help function display_help()
           shift 2
           ;;

      --) # End of all options
          shift
          break
          ;;
      -*)
          echo "Error: Unknown option: $1" >&2
          ## or call function display_help
          exit 1 
          ;;
      *)  # No more options
          break
          ;;
    esac
done

###################### 
# Check if parameter #
# is set too execute #
######################
case "$1" in
  start)
    start # calling function start()
    ;;
  stop)
    stop # calling function stop()
    ;;
  restart)
    stop  # calling function stop()
    start # calling function start()
    ;;
  *)
#    echo "Usage: $0 {start|stop|restart}" >&2
     display_help

     exit 1
     ;;
esac

我把开始、停止、重新启动放在了一个单独的案例里,这有点奇怪,但应该可以正常工作。


如果你给 -d 选项一个空值,它不会无限循环吗? - zerobane
为什么在辅助函数中使用 exit 1? - jeantimex

25

如果只有一个选项需要检查且总是第一个选项,使用if语句

如果您只需要检查一个选项并且它总是第一个选项($1),则最简单的解决方案是使用带有测试条件的if语句([)。例如:

if [ "$1" == "-h" ] ; then
    echo "Usage: `basename $0` [-h]"
    exit 0
fi

请注意,为了符合posix兼容性,===一样有效。
为什么要引用$1$1需要用引号括起来的原因是,如果没有$1,那么shell将尝试运行if [ == "-h" ]并失败,因为==只被赋予了一个参数,而它期望有两个参数。
$ [ == "-h" ]
bash: [: ==: unary operator expected

如果需要更复杂的内容,请使用getoptgetopts

正如建议 他人,如果您有多个简单选项或需要您的选项接受参数,则绝对应该选择使用getopts的额外复杂性。

作为快速参考,我喜欢The 60 second getopts tutorial

你可能想考虑使用 getopt 程序,而不是内置的 shell getopts。它允许使用长选项和非选项参数之后的选项(例如:foo a b c --verbose 而不仅仅是 foo -v a b c)。这个 Stackoverflow 回答 解释了如何使用 GNU getopt

jeffbyrnes 提到 原始链接 已经失效,但幸运的是 way back machine 已经将其归档。


谢谢!我一直很愉快地使用getopts一年了,但我也会看看getopt。 - tttppp
3
遗憾的是,“The 60 Second getopts Tutorial” 的链接已经失效了;看起来 bashcurescancer.com 已经不存在了。这里提供 Wayback Machine 上的版本链接,希望对你有所帮助。 - jeffbyrnes
60秒的getopts教程是黄金。 - bio

7

有一种简单的实现方法,可以不使用getoptgetopts:

display_help() {
    # taken from https://stackoverflow.com/users/4307337/vincent-stans
    echo "Usage: $0 [option...] {start|stop|restart}" >&2
    echo
    echo "   -r, --resolution           run with the given resolution WxH"
    echo "   -d, --display              Set on which display to host on "
    echo
    # echo some stuff here for the -a or --add-options 
    exit 1
}

log() {
    echo "This is a log"
}
while [[ "$#" -gt 0 ]]; do
    case $1 in
        -h|--help) display_help; shift ;;
        -l|--log) log; shift ;;
        # ... (same format for other required arguments)
        *) echo "Unknown parameter passed: $1" ;;
    esac
    shift
done

太好了!我在stackoverflow上找到了最简单的答案,可以轻松自定义参数。谢谢。 - Vincent Decaux

5

1
这是一种超紧凑但易读的方式,可基于 @zeb 版本添加基本帮助信息。它处理两个常用标志 -h--help
[ "$1" = "-h" -o "$1" = "--help" ] && echo "
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  incididunt ut labore et dolore magna aliqua.

  Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
  voluptate velit esse cillum dolore eu fugiat nulla pariatur.
" && exit

结果看起来像这样:

$ ./myscript.sh -h

  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  incididunt ut labore et dolore magna aliqua.

  Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
  voluptate velit esse cillum dolore eu fugiat nulla pariatur.

$ _

0

我认为你可以使用case来解决这个问题...

case $1 in 
 -h) echo $usage ;; 
  h) echo $usage ;;
help) echo $usage ;;
esac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接