shell 脚本里更好命令行参数

Table of Contents

我们经常需要在脚本中获取命令行参数,而 getopts 就是实现解析命令行参数的可靠工具,但也有它的局限性。

1 getopts

在频繁使用的小脚本中,getopts 可以说是完美的选择,它可以让你写更少的代码:

paste=vpaste    # default is vertical pasting
seplist="\t"    # default separator is tab

while getopts d:s o
do      case "$o" in
        d)      seplist="$OPTARG";;
        s)      paste=hpaste;;
        [?])    print >&2 "Usage: $0 [-s] [-d seplist] file ..."
                exit 1;;
        esac
done
shift $OPTIND-1

# perform actual paste command
$paste -d "$seplist" "$@"

上述程序接受两个可选参数,-d xxx 和 -s,程序中把 xxx 保存在变量 seplist 中;如果指定了 -s 选项,则变量 paste=hpaste, (默认是 vpasste)。

这么做虽然简单,但很快,你会忘记 -abc 到底是什么意思,而你的同事也会反复问你 -d 后面应该填什么。而且在功能复杂的脚本中,26个字母很快就会用完。这时候就需要自己解析命令行参数了。

2 目标

我们希望脚本可以接受类似 \(--var-a=xxx\) 和 \(--enable-feature-c\) 的参数。一般的,需要设置一个 \(--help\) 参数,如果被指定则打印每个参数的用途。

这只是一个脚本程序,我们希望它尽量简单明了,不需要太多的抽象。对脚本文件而言,“一把梭”可能是就是最好的。你的同事已经在主要功能上花费了太多的精力,当他以为已经完成了所有工作的时候,你告诉他有一个辅助脚本需要添加一个参数,为了添加这个参数需要理解其中复杂的逻辑,那就太令人绝望了。所以脚本应该让人看一眼就知道怎么修改,并且没有任何低估。因为是“一把梭”,我认为中间过程是没有必要的,所以代码就像这样:

3 示例代码

# 设置一些变量的默认值
# 这些变量用于保存命令行参数的解析结果
help=no

VAR_A=
VAR_B=
HAVE_FEATURE_C=NO

# for 循环遍历命令行参数,参数使用空格隔开
for option
do
    # 获得参数的值,保存到变量 $value 中
    case "$option" in
        -*=*) value=`echo "$option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
        *) value="" ;;
    esac

    # 枚举接受的命令行参数,逐一处理,根据参数对最开始的变量赋值
    case "$option" in
        --help)             help=yes            ;;

        --var-a=*)          VAR_A="$value"      ;;
        --var-b=*)          VAR_B="$value"      ;;

        --have-feature-c)   HAVE_FEATURE_C=YES  ;;

        # ...

        *)
            echo "$0: error: invalid option \"$option\""
            exit 1
        ;;
    esac
done

# 打印帮助信息
if [ $help = yes ]; then

cat << END

    --help              print this message

    --var-a=ARG         set var-a ...
    --var-b=ARG         est var-b ...

    --have-feature-c    enaable feature C
    ...
END

    exit 1
fi

# 如果参数为空,设置一些默认值,这是可选的
VAR_A=${VAR_A:-default_value1}
# ...

# 现在可以使用这些表示命令行参数的变量了
echo "VAR_A=$VAR_A; VAR_B=$VAR_B; HAVE_FEATURE_C=$HAVE_FEATURE_C"
# ...

By .