如何在使用位置参数编写的脚本中引入对命令行选项的支持?

如何在使用位置参数编写的脚本中引入对命令行选项的支持?

我有一个当前与位置参数一起使用的脚本,如下所示:

./script.sh fname lname address

当我调用这个脚本时,我还想支持普通的命令行选项,这样我就可以跳过我不想给出的参数:

./script.sh -f fname -a address

fname是唯一的强制参数。

由于历史原因和自动化,脚本需要向后兼容。

到目前为止,我最好的猜测是寻找-f字符串(由空格包围),

  • 如果找到,则处理标志
  • 如果没有,则依次处理
flags='(\ |^)-f\ '
if [[ $* =~ $flags ]]; then
while [ $# -ne 0 ]
do
    name="$1"
    case "$name" in
        -f)
            shift
            fname=$1
            ;;
        -l)
            shift
            lname=$1
            ;;
        -a)
            shift
            address=$1
            ;;
    esac
    shift
done
else
    fname=${1}
    lname=${2}
    address=${3}
fi

但在这里,我需要使用正则表达式来检查选项,这可能不可靠。

是否有一种结合命令行选项和位置参数的本机方法?

答案1

使用 实现普通命令行解析getopts。如果该解析被触发,请设置一个标志来跟踪这一事实。在标准getopts循环之后,如果尚未设置标志,则返回到旧的行为。

在下面的代码中,标志是变量new_behavior

#!/bin/bash

# As written now, this should run without issues with
# /bin/sh too (there are no bash-isms in this code).

unset address
unset fname
unset lname

new_behavior=false

while getopts 'a:f:l:' opt; do
        new_behavior=true

        case $opt in
                a)
                        address=$OPTARG
                        ;;
                f)
                        fname=$OPTARG
                        ;;
                l)
                        lname=$OPTARG
                        ;;
                *)
                        echo 'Error in command line parsing' >&2
                        exit 1
        esac
done

shift "$(( OPTIND - 1 ))"

if ! "$new_behavior"; then
        # Fall back on old behavior.

        fname=$1;       shift
        lname=$1;       shift
        address=$1;     shift
fi

if [ -z "$fname" ]; then
        echo 'Missing mandatory argument "fname"' >&2
        exit 1
fi

# The rest is unimportant to the actual command line parsing code
# and only here for your information and for debugging.

printf 'fname="%s", lname="%s", address="%s"\n' \
        "$fname" "$lname" "$address"

if [ "$#" -gt 0 ]; then
        printf 'Extra argument: "%s"\n' "$@"
fi

一旦用户提供选项作为脚本的第一个参数,就会触发新行为。该选项是否有效并不重要。

测试旧行为:

$ ./script eff ell addr
fname="eff", lname="ell", address="addr"
$ ./script eff ell addr "hello world"
fname="eff", lname="ell", address="addr"
Extra argument: "hello world"
$ ./script eff
fname="eff", lname="", address=""
$ ./script
Missing mandatory argument "fname"
$ ./script eff -l ell
fname="eff", lname="-l", address="ell"

测试新行为:

$ ./script -a addr -l ell -f eff
fname="eff", lname="ell", address="addr"
$ ./script -a addr -f eff "hello world"
fname="eff", lname="", address="addr"
Extra argument: "hello world"
$ ./script -f eff "hello world"
fname="eff", lname="", address=""
Extra argument: "hello world"
$ ./script -l eff "hello world"
Missing mandatory argument "fname"
$ ./script -f eff -- -l ell -f "eff again"
fname="eff", lname="", address=""
Extra argument: "-l"
Extra argument: "ell"
Extra argument: "-f"
Extra argument: "eff again"

(请注意,在最后一个示例中,非选项额外参数与选项之间用 分隔---l第二个参数都不-f是选项。)

答案2

那么你为什么要使用它if呢?只需检查参数即可。

flaggiven=0
# This is very unsafe - use `getopt` or `getopts`. `shift` can fail - should be checked for errors.
while (($#)); do
    case "$1" in
    -f) fname=$2; flaggiven=1; shift; ;;
    -l) lname=$2; flaggiven=1; shift; ;;
    -a) address=$2; flaggiven=1; shift; ;;
    *) break;
    esac
    shift
done

if (($# > 0)); then
     if ((flaggiven)); then
        echo "ERROR: you have given -f or -l or -a argument and arguments. One or the other, not both!"
        exit 1
     fi
     fname=$1
     lname=$2
     address=$3
else
   if ((!flaggiven)); then
        echo "ERROR: you have to pass -f -l -a flags or pass 3 arguments"
        exit 1
    fi
fi

相关内容