如何使用内置 Bash getopts 的长选项?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12022592/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 22:33:49  来源:igfitidea点击:

How can I use long options with the Bash getopts builtin?

bashcommand-line-argumentsgetopts

提问by Hemang

I am trying to parse a -tempoption with Bash getopts. I'm calling my script like this:

我正在尝试-temp使用 Bash getopts解析一个选项。我这样调用我的脚本:

./myscript -temp /foo/bar/someFile

Here is the code I'm using to parse the options.

这是我用来解析选项的代码。

while getopts "temp:shots:o:" option; do
    case $option in
        temp) TMPDIR="$OPTARG" ;;
        shots) NUMSHOTS="$OPTARG" ;;
        o) OUTFILE="$OPTARG" ;;
        *) usage ;;
    esac
done
shift $(($OPTIND - 1))

[ $# -lt 1 ] && usage

回答by mcoolive

As other people explained, getopts doesn't parse long options. You can use getopt, but it's not portable (and it is broken on some platform...)

正如其他人解释的那样, getopts 不解析长选项。您可以使用 getopt,但它不可移植(并且在某些平台上已损坏...)

As a workaround, you can implement a shell loop. Here an example that transforms long options to short ones before using the standard getopts command (it's simpler in my opinion):

作为一种解决方法,您可以实现一个 shell 循环。这是一个在使用标准 getopts 命令之前将长选项转换为短选项的示例(我认为它更简单):

# Transform long options to short ones
for arg in "$@"; do
  shift
  case "$arg" in
    "--help") set -- "$@" "-h" ;;
    "--rest") set -- "$@" "-r" ;;
    "--ws")   set -- "$@" "-w" ;;
    *)        set -- "$@" "$arg"
  esac
done

# Default behavior
rest=false; ws=false

# Parse short options
OPTIND=1
while getopts "hrw" opt
do
  case "$opt" in
    "h") print_usage; exit 0 ;;
    "r") rest=true ;;
    "w") ws=true ;;
    "?") print_usage >&2; exit 1 ;;
  esac
done
shift $(expr $OPTIND - 1) # remove options from positional parameters

回答by geirha

getoptscan only parse short options.

getopts只能解析短选项。

Most systems also have an external getoptcommand, but getopt is not standard, and is generally broken by design as it can't handle all arguments safely (arguments with whitespace and empty arguments), only GNU getopt can handle them safely, but only if you use it in a GNU-specific way.

大多数系统也有一个外部getopt命令,但 getopt 不是标准的,并且通常被设计破坏,因为它不能安全地处理所有参数(带有空格和空参数的参数),只有 GNU getopt 可以安全地处理它们,但前提是你以特定于 GNU 的方式使用它。

The easier choice is to use neither, just iterate the script's arguments with a while-loop and do the parsing yourself.

更简单的选择是两者都不使用,只需使用 while 循环迭代脚本的参数并自己进行解析。

See http://mywiki.wooledge.org/BashFAQ/035for an example.

有关示例,请参见http://mywiki.wooledge.org/BashFAQ/035

回答by Stephane Rouberol

getoptsis used by shell procedures to parse positional parameters of 1 character only (no GNU-style long options (--myoption) or XF86-style long options (-myoption))

getoptsshell 程序使用它来解析只有 1 个字符的位置参数(没有 GNU 风格的长选项 (--myoption) 或 XF86 风格的长选项 (-myoption))

回答by Ashish Shetkar

the simplest way to achieve this is with the help of getoptand --longoptions

最简单的方法是借助getopt--longoptions

try this , hope this is useful

试试这个,希望这是有用的

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "
myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"
")" \ --options "" \ -- "$@" ) echo $opts eval set --$opts while true; do case "" in --input1) shift empId= ;; --input2) shift fromDate= ;; --input3) shift toDate= ;; --) shift break ;; esac shift done

and this is how - you call the shell script

这就是如何 - 你调用 shell 脚本

usage() { echo usage: error from ; exit -1; }

OPTIND=1
PREVOPTIND=$OPTIND
while getopts "t:s:o:" option; do
  GAP=$((OPTIND-(PREVOPTIND+1)))
  case $option in
    t) case "${OPTARG}" in
         emp)                  # i.e. -temp
           ((GAP)) && usage "-${option} and ${OPTARG}"
           TMPDIR="$OPTARG"
           ;;
         *)
           true
           ;;
       esac
       ;;
    s) case "${OPTARG}" in
         hots)                 # i.e. -shots
           ((GAP)) && usage
           NUMSHOTS="$OPTARG"
           ;;
         *) usage "-${option} and ${OPTARG}" ;;
       esac
       ;;
    o) OUTFILE="$OPTARG" ;;
    *) usage "-${option} and ${OPTARG}" ;;
  esac
  PREVOPTIND=$OPTIND
done
shift $(($OPTIND - 1))

回答by user2023370

Although this question was posted over 2 years ago, I found myself needing support for XFree86-style long options too; and I also wanted to take what I could from getopts. Consider the GCC switch -rdynamic. I mark ras the flag letter, and expect dynamicwithin $OPTARG...but, I want to reject -r dynamic, while accepting other options following r.

尽管这个问题是在 2 年前发布的,但我发现自己也需要支持 XFree86 风格的长选项;我也想从 getopts 中获取我能做的。考虑 GCC 开关-rdynamic。我标记r为标志字母,并期望dynamic$OPTARG...但是,我想拒绝-r dynamic,同时接受以下其他选项r

The idea I've put below builds on the observation that $OPTINDwill be one larger than otherwise if space (a gap) follows the flag. So, I define a bash variable to hold the previous value of $OPTIND, called $PREVOPTIND, and update it at the end of the whileloop. If $OPTINDis 1greater than $PREVOPTIND, we have no gap (i.e. -rdynamic); and $GAPis set to false. If instead $OPTINDis 2greater than $PREVOPTIND, we dohave a gap (e.g. -r dynamic), and $GAPis set to true.

我在下面提出的想法建立在观察的基础上,$OPTIND如果空间(间隙)跟随标志,则该观察将比其他情况大。因此,我定义了一个 bash 变量来保存 的先前值$OPTIND,称为$PREVOPTIND,并在while循环结束时更新它。如果$OPTIND1大于$PREVOPTIND,我们没有任何间隙(即-rdynamic); 并$GAP设置为false。相反,如果$OPTIND2大于$PREVOPTIND,我们做的有一定的差距(例如-r dynamic),并$GAP设置为true

# convert long word options to short word for ease of use and portability

for argu in "$@"; do
  shift
  #echo "curr arg = "
  case "$argu" in
"-start"|"--start")
                   # param=param because no arg is required
                   set -- "$@" "-s"
                   ;;
"-pb"|"--pb"|"-personalbrokers"|"--personalbrokers")
                   # pb +arg required
                   set -- "$@" "-p "; #echo "arg=$argu"
                   ;;
"-stop"|"--stop")
                   # param=param because no arg is required 
                   set -- "$@" "-S" 
                   ;;
                #  the catch all option here removes all - symbols from an
                #  argument. if an option is attempted to be passed that is
                #  invalid, getopts knows what to do...
               *)  [[ $(echo $argu | grep -E "^-") ]] && set -- "$@" "${argu//-/}" || echo "no - symbol. not touching $argu" &>/dev/null
                   ;;
esac
done

#echo -e "\n final option conversions = $@\n"
# remove options from positional parameters for getopts parsing
shift $(expr $OPTIND - 1)

declare -i runscript=0
# only p requires an argument hence the p:
 while getopts "sSp:" param; do
[[ "$param" == "p" ]] && [[ $(echo $OPTARG | grep -E "^-") ]] && funcUsage "order" 
#echo $param
#echo "OPTIND=$OPTIND"
case $param in
s)
       OPTARG=${OPTARG/ /}
       getoptsRan=1
       echo "$param was passed and this is it's arg $OPTARG"
       arg0=start
       ;;
 p)
       OPTARG=${OPTARG/ /}
       getoptsRan=1
       echo "$param was passed and this is it's arg $OPTARG"
       [[ "$OPTARG" == "all" ]] && echo -e "argument \"$OPTARG\" accepted. continuing." && (( runscript += 1 )) || usage="open"
       [[ $( echo $pbString | grep -w "$OPTARG" ) ]] && echo -e "pb $OPTARG was validated. continuing.\n" && (( runscript += 1 )) || usage="personal"
       [[ "$runscript" -lt "1" ]] && funcUsage "$usage" "$OPTARG"
       arg0=start
       ;;
S)
       OPTARG=${OPTARG/ /}
       getoptsRan=1
       echo "$param was passed and this is it's arg $OPTARG"
       arg0=stop
       ;;
*)
       getoptsRan=1
       funcUsage
       echo -e "Invalid argument\n"
       ;;
esac
done
funcBuildExcludes "$@"
shift $((OPTIND-1))

回答by Benjamin West

thanks to @mcoolive .

感谢@mcoolive。

I was able to use your $@ idea to convert whole word and long options to single letter options. Wanted to note to anyone using this idea that I also had to include shift $(expr $OPTIND - 1)prior to running the arguments through the getopts loop.

我能够使用您的 $@ 想法将整个单词和长选项转换为单字母选项。想提醒任何使用这个想法的人,shift $(expr $OPTIND - 1)在通过 getopts 循环运行参数之前,我也必须包含这个想法。

Totally different purpose but this is working well.

完全不同的目的,但这运作良好。

$ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
a1 = "a1"
a2 = "a 2"
a3 = "TRUE"
a4 = ""
a5 = "a5"
a6 = "a 6"
a7 = ""

回答by lmcarreiro

A simple DIY if you get trouble to use the built-in getopts:

如果您在使用内置文件时遇到麻烦,请进行简单的 DIY getopts

Use:

用:

#!/bin/bash

function main() {
    ARGS=`getArgs "$@"`

    a1=`echo "$ARGS" | getNamedArg a1`
    a2=`echo "$ARGS" | getNamedArg a2`
    a3=`echo "$ARGS" | getNamedArg a3`
    a4=`echo "$ARGS" | getNamedArg a4`
    a5=`echo "$ARGS" | getNamedArg a5`
    a6=`echo "$ARGS" | getNamedArg a6`
    a7=`echo "$ARGS" | getNamedArg a7`

    echo "a1 = \"$a1\""
    echo "a2 = \"$a2\""
    echo "a3 = \"$a3\""
    echo "a4 = \"$a4\""
    echo "a5 = \"$a5\""
    echo "a6 = \"$a6\""
    echo "a7 = \"$a7\""

    exit 0
}


function getArgs() {
    for arg in "$@"; do
        echo "$arg"
    done
}


function getNamedArg() {
    ARG_NAME=

    sed --regexp-extended --quiet --expression="
        s/^--$ARG_NAME=(.*)$//p  # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
        /^--$ARG_NAME$/ {          # Get arguments in format '--arg value' ou '--arg'
            n                       # - [n]ext, because in this format, if value exists, it will be the next argument
            /^--/! p                # - If next doesn't starts with '--', it is the value of the actual argument
            /^--/ {                 # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
                # Then just repla[c]ed by TRUE
                c TRUE
            }
        }
    "
}


main "$@"

Script:

脚本:

  #== set options ==#
SCRIPT_OPTS=':fbF:B:-:h'
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
    [foo]=f
    [bar]=b
    [foobar]=F
    [barfoo]=B
    [help]=h
    [man]=h
)

  #== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
    #== translate long options to short ==#
    if [[ "x$OPTION" == "x-" ]]; then
        LONG_OPTION=$OPTARG
        LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
        LONG_OPTIND=-1
        [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
        [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="$$LONG_OPTIND"
        OPTION=${ARRAY_OPTS[$LONG_OPTION]}
        [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

        if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
            if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                OPTION=":" OPTARG="-$LONG_OPTION"
            else
                OPTARG="$LONG_OPTARG";
                if [[ $LONG_OPTIND -ne -1 ]]; then
                    [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                    shift $OPTIND
                    OPTIND=1
                fi
            fi
        fi
    fi

    #== options follow by another option instead of argument ==#
    if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
        OPTARG="$OPTION" OPTION=":"
    fi

    #== manage options ==#
    case "$OPTION" in
        f  ) foo=1 bar=0                    ;;
        b  ) foo=0 bar=1                    ;;
        B  ) barfoo=${OPTARG}               ;;
        F  ) foobar=1 && foobar_name=${OPTARG} ;;
        h ) usagefull && exit 0 ;;
        : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
        ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
    esac
done
shift $((${OPTIND} - 1))

回答by Todd A. Jacobs

You can't use the getoptsBash builtin for long options--at least, not without having to build your own parsing functions. You should take a look at the /usr/bin/getoptbinary instead (provided on my system by the util-linuxpackage; your mileage may vary).

您不能将getoptsBash 内置用于长选项——至少,不必构建您自己的解析函数。您应该查看/usr/bin/getopt二进制文件(由util-linux包在我的系统上提供;您的里程可能会有所不同)。

See getopt(1) for specific invocation options.

有关特定调用选项,请参阅 getopt(1)。

回答by Michel VONGVILAY uxora.com

It's true that builtin bash getoptsonly parse short options, but you can still add few lines of scripting to make getopts handles long options.

确实,内置 bashgetopts只解析短选项,但您仍然可以添加几行脚本来让 getopts 处理长选项。

Here is a part of code found in http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

这是在http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts 中找到的部分代码

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

Here is a test:

这是一个测试:

##代码##

Otherwise in recent Korn Shellksh93, getoptscan naturally parse long options and even display a man page alike. (see http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options)

否则在最近的 Korn Shellksh93 中,getopts可以自然地解析长选项,甚至可以显示类似的手册页。(见http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options

Michel VONGVILAY.

米歇尔·冯维莱。