Linux 下很多命令都带有选项,比如:

ls -l ~/
sort -n 
tail -f XXX.log

是否想让 shell 脚本也具有这样的功能呢?如果直接使用 shell 的字符串处理来分析参数,还是比较麻烦的,我们需要考虑传入的参数哪些是选项,哪些是选项的值,哪些是正常的参数等等,为了更好的体验,可能还要支持选项合并,类似于 sort -rn

幸运的是,Linux 有一个命令可以帮我们完成这些事情,它就是 getopts ,使用起来也比较简单。可以先看一个示例脚本[1]:

#!/bin/bash
echo "OPTIND: $OPTIND"
while getopts ":pq:" optname
  do
   	case "$optname" in
      "p")
        echo "选项 $optname 被指定"
        ;;
      "q")
        echo "选项 $optname 有参数值 $OPTARG"
        ;;
      "?")
        echo "未知的选项 $OPTARG"
        ;;
      ":")
        echo "$OPTARG 未指定参数值"
        ;;
      *)
        # Should not occur
        echo "当处理选项时,发生未知错误"
        ;;
    esac
    echo "OPTIND: $OPTIND"
  done

我们将错误处理的部分去除,核心逻辑其实是这样的:

#!/bin/bash
while getopts ":pq:" optname
  do
   	case "$optname" in
      "p")
        echo "选项 $optname 被指定"
        ;;
      "q")
        echo "选项 $optname 有参数值 $OPTARG"
        ;;
    esac
  done

这个脚本有两个选项,-p-q,你一定会问的问题:

  1. getopts 第一个参数中的两个冒号分别什么意思
  2. $optname$OPTARG 代表什么

为了更好理解,我们再增加一个选项 -t,将 ":pq:" 改为 ":pq:t:",再来解释。

问题一

第一个冒号,表示静默模式,也就是阻止报错,阻止什么报错呢?比如不合法的选项,或者未指定选项的参数值之类的。

选项字母后的冒号表示该选项需要一个参数值,在这个例子中, -t-q 都需要跟一个参数值,而 -p 则不要求。

正确的运行该脚本,要像这样子:

./testargs.sh -p -t 1 -q 2

问题二

第二个问题,聪明如你,应该已经猜出来了,$optname 是选项名(p, t, q等),$OPTARG中存了对应选项名的参数值(前提是选项字母后有冒号)。

错误处理

现在我们再回头看完整的脚本示例,来到错误处理的部分。

在静默模式下,可能出现以下两种错误情况。

  • 如果发现不能识别的选项,则 $optname 将包含一个 ?,而 $OPTARG 将包含该未知选项的名称。
  • 如果一个选项需要值,但是找不到这个值,则 $optname 将包含一个 :,而 $OPTARG 将包含丢失参数的选项的名称。

至于$OPTIND,它代表待处理的下一个参数的索引,初始值为1.