如何在Bash中解析命令行参数?

Law*_*ton 1764 bash scripting command-line arguments getopts

说,我有一个用这行调用的脚本:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
Run Code Online (Sandbox Code Playgroud)

或者这个:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 
Run Code Online (Sandbox Code Playgroud)

什么是分析这使得在每一种情况下(或两者的组合)的接受的方式$v,$f以及 $d将全部设置为true$outFile将等于/fizz/someOtherFile

Bru*_*sky 2469

方法#1:使用bash而不使用getopt [s]

传递键 - 值对参数的两种常用方法是:

Bash Space-Separated(例如--option argument)(没有getopt [s])

用法 demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
Run Code Online (Sandbox Code Playgroud)

Bash Equals-Separated(例如--option=argument)(没有getopt [s])

用法 demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
Run Code Online (Sandbox Code Playgroud)

为了更好地理解本指南中的${i#*=} "子串删除"搜索.它在功能上等同于调用不必要的子进程或调用两个不必要的子进程.`sed 's/[^=]*=//' <<< "$i"``echo "$i" | sed 's/[^=]*=//'`

方法#2:使用带有getopt的bash [s]

来自:http://mywiki.wooledge.org/BashFAQ/035#getopts

getopt(1)限制(较旧的,相对较新的getopt版本):

  • 无法处理空字符串的参数
  • 无法处理嵌入空格的参数

更新的getopt版本没有这些限制.

此外,POSIX shell(和其他)提供getopts没有这些限制.这是一个简单的getopts例子:

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
Run Code Online (Sandbox Code Playgroud)

优点demo-getopts.sh -vf /etc/hosts foo bar是:

  1. 它更便携,并且可以在其他外壳中工作getopts.
  2. 它可以自动处理多种单一选项,如dash典型的Unix方式.

缺点-vf filename是它只能处理短选项(getopts而不是-h没有附加代码).

有一个getopts教程,解释了所有语法和变量的含义.在bash中,也有--help可能提供信息.

  • 在你的系统中某种某种方式是一个非常弱的前提,基于"标准"的假设. (43认同)
  • 这是真的吗?根据[维基百科](http://en.wikipedia.org/wiki/Getopts),有一个更新的GNU增强版"getopt",其中包括`getopts`的所有功能,然后是一些.在Ubuntu 13.04上的`man getopt`输出`getopt - parse命令选项(增强)`作为名称,所以我认为这个增强版本现在是标准的. (38认同)
  • @Livven,`getopt`不是GNU实用程序,它是`util-linux`的一部分. (10认同)
  • 如果使用`-gt 0`,则在`esac`之后删除`shift`,将所有`shift`加1并添加这种情况:`*)break ;;`你可以处理非optionnal参数.例如:http://pastebin.com/6DJ57HTc (4认同)
  • *grumble* 回复:使用全大写变量名称,违反了 [POSIX 约定](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html) 指定使用大写变量作为名称具有 POSIX 指定工具的含义,小写名称保留供应用程序使用。(这也与 shell 变量相关,因为 shell 变量赋值将覆盖任何存在的类似名称的环境变量)。 (2认同)
  • 你不回应`-default`.在第一个例子中,我注意到如果`-default`是最后一个参数,它不被处理(被视为非选择),除非`while [[$#-1]]`被设置为`while [[ $#-gt 0]]` (2认同)
  • `getopts "h?vf:"` 应该是不带问号的 `getopts "hvf:"`。无法识别的参数在 `$opt` 中存储为 `?`。引自`man builtins`:`“冒号和问号字符不能用作选项字符。”` (2认同)
  • 如果您以bash的“严格模式”(“#!/ bin / bash -u”或“ set -eu”)运行,则只有在“ POSITIONAL”数组不为空时才恢复位置参数,以避免“ unbound variable”错误,即:`if [$ {#POSITIONAL [@]} -gt 0]; 然后设置-“ $ {POSITIONAL [@]}”;fi#恢复位置参数 (2认同)

Rob*_*mer 500

没有回答提到增强的getopt.而得票最多的答案是误导性的:它忽略-?vfd风格期权短仓(由OP请求),选择定位参数后(也由OP的要求)而忽略解析-错误.代替:

  • 使用getoptutil-linux或以前的GNU glibc 增强版.1
  • 它适用于getopt_long()GNU glibc的C函数.
  • 所有有用的区别特征(其他没有它们):
    • 处理空格,在参数中引用字符甚至二进制2
    • 它可以在最后处理选项: getopt
    • 允许script.sh -o outFile file1 file2 -v式长选项:getopts
  • 太旧了已经3没有GNU系统缺少这个(例如,任何Linux有它).
  • 您可以使用以下方法测试其存在:=→返回值4.
  • 其他script.sh --outfile=fileOut --infile fileIn或壳内置物-vfd的用途有限.

以下电话

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile
Run Code Online (Sandbox Code Playgroud)

一切都归来

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
Run Code Online (Sandbox Code Playgroud)

以下内容 -oOutfile

#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"
Run Code Online (Sandbox Code Playgroud)

大多数"bash系统"都有 1个增强的getopt,包括Cygwin; 在OS X上尝试 brew install gnu-getopt-vfdoOutfile
2 POSIXgetopt --test约定没有可靠的方法在命令行参数中传递二进制NULL; 这些字节过早结束参数
3发布第一个版本在1997年或以前(I仅跟踪回1997)

  • @jjj 脚注 1 涵盖 OS X。 – 对于 OS X 开箱即用解决方案,请检查其他问题和答案。或者说实话:对于真正的编程,不要使用 bash。;-) (4认同)
  • 谢谢你.刚从https://en.wikipedia.org/wiki/Getopts的功能表中确认,如果您需要长选项的支持,并且您不在Solaris上,那么`getopt`就是您的选择. (3认同)
  • 我相信`getopt`的唯一警告是它不能*方便地*在包装器脚本中使用,其中一个可能没有特定于包装器脚本的选项,然后将非包装脚本选项传递给包装的可执行文件,完好无损.假设我有一个名为`mygrep`的`grep`包装器,我有一个特定于`mygrep`的选项`--foo`,然后我不能做`mygrep --foo -A 2`,并且有`-A 2 `自动传递给`grep`; 我需要**做`mygrep --foo - -A 2`.**这是[我的实施](https://gist.github.com/74e9875d5ab0a2bc1010447f1bee5d0a)在您的解决方案之上.** (3认同)
  • @bobpaul 你关于 util-linux 的说法是错误的,也是误导性的:该软件包在 Ubuntu/Debian 上被标记为“必不可少”。因此,它始终被安装。– 你在谈论哪些发行版(你说它需要特意安装)? (3认同)
  • @transang 返回值的布尔否定。它的副作用:允许命令失败(否则 errexit 将在出错时中止程序)。-- 脚本中的注释会告诉你更多信息。否则:`man bash` (3认同)
  • 请注意,至少在当前的10.14.3。上,这在Mac上不起作用。出厂的getopt是1999年的BSD getopt ... (2认同)
  • @BenjaminW。得票最高的答案涵盖了两个自解析解决方案和 getopts。前者不执行组合短选项,后者不解析非选项参数之后的选项。 (2认同)
  • 前面的感叹号是什么意思?有人可以添加一些参考吗? (2认同)

gun*_*sus 123

来自:digitalpeer.com稍作修改

用法 myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}
Run Code Online (Sandbox Code Playgroud)

为了更好地理解本指南中的${i#*=} "子串删除"搜索.它在功能上等同于调用不必要的子进程或调用两个不必要的子进程.`sed 's/[^=]*=//' <<< "$i"``echo "$i" | sed 's/[^=]*=//'`

  • 整齐!虽然这不适用于空格分隔的参数,例如`mount -t tempfs ...`.人们可以通过诸如`while [$#-ge 1]之类的东西来解决这个问题.做param = $ 1; 转移; case $ param in; -p)prefix = $ 1; 转移;;等 (3认同)
  • 这不能处理`-vfd`样式的组合短选项. (3认同)

Ina*_*mus 106

更简洁的方式

script.sh

#!/bin/bash

while [[ "$#" -gt 0 ]]; do case $1 in
  -d|--deploy) deploy="$2"; shift;;
  -u|--uglify) uglify=1;;
  *) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"
Run Code Online (Sandbox Code Playgroud)

用法:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify
Run Code Online (Sandbox Code Playgroud)

  • 哇!简单干净!这就是我使用它的方式:https://gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58 (8认同)
  • 这就是我在做的事情.必须`while [["$#"> 1]]`如果我想支持用布尔标志`./script.sh --debug dev --uglify fast --verbose`结束行.示例:https://gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58 (3认同)
  • 将其粘贴到每个脚本中要好得多,而不是处理源代码或让人们想知道您的功能实际上从哪里开始。 (3认同)
  • 很好的答案,tnx!我把它缩短了一点 - `while (( "$#" )); do` 而不是 `while [[ "$#" -gt 0 ]]; 做` (3认同)
  • 警告:这允许重复的参数,以最新的参数为准。例如 `./script.sh -d dev -d prod` 将导致 `deploy == 'prod'`。无论如何我都用过它:P :) :+1: (2认同)
  • @CIsForCookies 谢谢!那是因为,据我所知, `((...))` 语法仅存在于 ksh、bash 和 zsh 中,但不存在于普通 sh 中。 (2认同)
  • **错误!** 如果作为 `./deploy.sh -ut dev` 调用,则不起作用!请参阅[罗伯特·西默斯的回答](/sf/answers/2082840651/) (2认同)
  • @yair 据我所知,CLI 参数通常是这样工作的。这很好,因为您可以覆盖选项。例如,我可以 `alias build='./script.sh -a 1 -b 2 -c 3'` 作为我的默认选项,然后使用 `build -b 6` 来覆盖个人! (2认同)

Mat*_*t J 104

getopt()/ getopts()是一个不错的选择.从这里来的:

这个迷你脚本中显示了"getopt"的简单使用:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done
Run Code Online (Sandbox Code Playgroud)

我们所说的是允许使用-a,-b,-c或-d中的任何一个,但是-c后跟一个参数("c:"表示).

如果我们称之为"g"并试一试:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
Run Code Online (Sandbox Code Playgroud)

我们从两个参数开始,"getopt"拆分选项并将每个选项放在自己的参数中.它还添加了" - ".

  • 使用`$*`是`getopt`的破坏用法.(它用空格来填充参数.)请参阅[我的回答](http://stackoverflow.com/a/29754866/825924)以获得正确的用法. (4认同)

bro*_*son 93

冒着添加另一个要忽略的例子的风险,这是我的方案.

  • 处理-n arg--name=arg
  • 最后允许参数
  • 如果有任何拼写错误,则会显示正确的错误
  • 兼容,不使用bashisms
  • 可读,不需要在循环中维护状态

希望它对某人有用.

while [ "$#" -gt 0 ]; do
  case "$1" in
    -n) name="$2"; shift 2;;
    -p) pidfile="$2"; shift 2;;
    -l) logfile="$2"; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;

    -*) echo "unknown option: $1" >&2; exit 1;;
    *) handle_argument "$1"; shift 1;;
  esac
done
Run Code Online (Sandbox Code Playgroud)

  • 抱歉耽搁了.在我的脚本中,handle_argument函数接收所有非选项参数.你可以用你喜欢的任何东西替换那一行,也许`*)死"无法识别的参数:$ 1"`或者将args收集到一个变量`*)args + ="$ 1"; 转移1 ;;`. (4认同)
  • 漂亮简洁的代码,但是使用 -n 而没有其他 arg 会由于 `shift 2` 上的错误而导致无限循环,发出 `shift` 两次而不是 `shift 2`。建议编辑。 (2认同)

Sha*_*Day 41

我对这个问题迟到了4年,但是想要回馈.我使用前面的答案作为整理我的旧adhoc param解析的起点.然后我重构了以下模板代码.它使用=或空格分隔的参数处理长和短参数,以及组合在一起的多个短参数.最后,它将任何非参数参数重新插入到$ 1,$ 2 ..变量中.我希望它有用.

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT="$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE="$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done
Run Code Online (Sandbox Code Playgroud)

  • 我用这个有用的代码块遇到了两个问题:1)"-c = foo"情况下的"shift"最终吃了下一个参数; 2)'c'不应包含在可组合短期权的"[cfr]"模式中. (2认同)

leo*_*ama 33

ASAP:另一个 Shell 参数解析器

\n

编辑说明: 2.0 版,现在具有纯 POSIX shell 代码并且不含麸质!

\n

长话短说

\n

此解析器仅使用 POSIX 兼容的 shell 代码来处理以下格式的选项:、-o [ARG]或,其中是可选参数。它可以处理混合的选项和参数,还可以使用“ ”强制将其后面的任何参数视为位置参数。-abo [ARG]--opt [ARG]--opt=[ARG]ARG--

\n

这是一个只要命令正确就可以工作的最小版本,即它几乎不执行任何检查。脚本的顶部\xe2\x80\x94it won't work as a function\xe2\x80\x94 并替换您的选项定义。

\n
#!/bin/sh -e\n\nUSAGE="Usage:  ${CMD:=${0##*/}} [(-v|--verbose)] [--name=TEXT] [(-o|--output) FILE] [ARGS...]"\n\n# helper functions\nexit2 () { printf >&2 "%s:  %s: \'%s\'\\n%s\\n" "$CMD" "$1" "$2" "$USAGE"; exit 2; }\ncheck () { { [ "$1" != "$EOL" ] && [ "$1" != \'--\' ]; } || exit2 "missing argument" "$2"; }  # avoid infinite loop\n\n# parse command-line options\nset -- "$@" "${EOL:=$(printf \'\\1\\3\\3\\7\')}"  # end-of-list marker\nwhile [ "$1" != "$EOL" ]; do\n  opt="$1"; shift\n  case "$opt" in\n\n    #EDIT HERE: defined options\n         --name    ) check "$1" "$opt"; opt_name="$1"; shift;;\n    -o | --output  ) check "$1" "$opt"; opt_output="$1"; shift;;\n    -v | --verbose ) opt_verbose=\'true\';;\n    -h | --help    ) printf "%s\\n" "$USAGE"; exit 0;;\n\n    # process special cases\n    --) while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;;   # parse remaining as positional\n    --[!=]*=*) set -- "${opt%%=*}" "${opt#*=}" "$@";;                  # "--opt=arg"  ->  "--opt" "arg"\n    -[A-Za-z0-9] | -*[!A-Za-z0-9]*) exit2 "invalid option" "$opt";;    # anything invalid like \'-*\'\n    -?*) other="${opt#-?}"; set -- "${opt%$other}" "-${other}" "$@";;  # "-abc"  ->  "-a" "-bc"\n    *) set -- "$@" "$opt";;                                            # positional, rotate to the end\n  esac\ndone; shift\n\nprintf "name = \'%s\'\\noutput = \'%s\'\\nverbose = \'%s\'\\n\\$@ = (%s)\\n" \\\n    "$opt_name" "$opt_output" "$opt_verbose" "$*"\n
Run Code Online (Sandbox Code Playgroud)\n

输出示例

\n
$ ./asap-example.sh -vo path/to/camelot \'spam?\' --name=Arthur \'spam!\' -- +42 -17\nname = \'Arthur\'\noutput = \'path/to/camelot\'\nverbose = \'true\'\n$@ = (spam? spam! +42 -17)\n
Run Code Online (Sandbox Code Playgroud)\n
$ ./asap-example.sh -name Lancelot eggs bacon\nasap-example.sh:  invalid option: \'-n\'\nUsage:  asap-example.sh [(-v|--verbose)] [--name=TEXT] [(-o|--output) FILE] [ARG...]\n
Run Code Online (Sandbox Code Playgroud)\n

描述

\n

我受到@bronson 相对简单的答案的启发,并试图尝试改进它(而不增加太多的复杂性)。

\n

该解析器实现使用模式匹配参数扩展和 shell 自己的位置参数作为输出限制队列来循环和处理参数。这是结果:

\n
    \n
  • 接受任何-o [ARG]-abo [ARG]--long-option [ARG] 和样式的选项; --long-option=[ARG]
  • \n
  • 参数可以按任意顺序出现,循环后仅保留位置参数; $@
  • \n
  • 用于 -- 强制将剩余参数视为位置参数;
  • \n
  • 便携、紧凑、可读性强、具有正交特征;
  • \n
  • 不依赖getopt(s)或外部实用程序;
  • \n
  • 检测无效选项和缺失参数。
  • \n
\n

可移植性

\n

此代码经过测试和验证,可以与以下版本的相当新的版本一起使用:bashdashmkshksh93、和 BusyBox (均使用其标准可执行路径调用,而不是作为yash)。zshash/bin/sh

\n

如果您发现错误或它不适用于特定的 POSIX 兼容 shell,请留下评论。

\n
\n

PS:我知道...带有二进制值的参数0x01030307可能会破坏逻辑。但是,如果有人在命令行中传递二进制参数,这个问题应该是他们最后关心的问题。

\n


bub*_*bla 29

我发现在脚本中编写可移植解析非常令人沮丧,因为我编写了Argbash - 一个FOSS代码生成器,可以为脚本生成参数解析代码,还有一些很好的特性:

https://argbash.io

  • 这些问题在最新版本的 argbash 中得到了解决:文档得到了改进,引入了快速启动 argbash-init 脚本,您甚至可以在 https://argbash.io/generate 在线使用 argbash (3认同)
  • 实际上,您可以使用 Argbash 来生成专门为您量身定制的解析库,您可以将其包含在脚本中,也可以将其放在单独的文件中并直接获取它。我添加了一个[示例](http://argbash.readthedocs.io/en/latest/example.html#another-example)来证明这一点,并且我也在文档中使其更加明确。 (2认同)

Pon*_*y47 27

我的答案很大程度上是基于Bruno Bronosky的答案,但我将他的两个纯粹的bash实现混合成了一个我经常使用的实现.

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key="$1"
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE="$1"
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done
Run Code Online (Sandbox Code Playgroud)

这允许您同时具有空格分隔的选项/值以及相等的定义值.

所以你可以运行你的脚本:

./myscript --foo -b -o /fizz/file.txt
Run Code Online (Sandbox Code Playgroud)

以及:

./myscript -f --bar -o=/fizz/file.txt
Run Code Online (Sandbox Code Playgroud)

两者都应该有相同的最终结果.

优点:

  • 允许-arg = value和-arg值

  • 适用于您可以在bash中使用的任何arg名称

    • 含义-a或-arg或--arg或-arg或其他
  • 纯粹的bash.无需学习/使用getopt或getopts

缺点:

  • 无法组合args

    • 意思是没有-abc.你必须做-a -b -c

这些是我能想到的唯一优点/缺点


jch*_*ook 16

扩展@bruno-bronosky的答案,我添加了一个“预处理器”来处理一些常见的格式:

  • 扩展--longopt=val--longopt val
  • 扩展-xyz-x -y -z
  • 支持--指示标志结束
  • 显示意外选项的错误
  • 紧凑且易于阅读的选项开关
#!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename "$0") [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "$1" ]; then
    exit "$1"
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg="$1"; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg") ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}${1}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD="$1" ;;
    -u|--username)  shift; USERNAME="$1" ;;
    -n|--name)      shift; names+=("$1") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "$1" ;;
    *)              POSITIONAL+=("$1") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"
Run Code Online (Sandbox Code Playgroud)


Ale*_*lek 14

我认为这个很简单,可以使用:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done
Run Code Online (Sandbox Code Playgroud)

调用示例:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile
Run Code Online (Sandbox Code Playgroud)


uns*_*zed 13

扩展@guneysus的优秀答案,这是一个调整,让用户可以使用他们喜欢的任何语法,例如

command -x=myfilename.ext --another_switch 
Run Code Online (Sandbox Code Playgroud)

VS

command -x myfilename.ext --another_switch
Run Code Online (Sandbox Code Playgroud)

也就是说,equals可以用空格替换.

这种"模糊解释"可能不符合您的喜好,但如果您制作的脚本可以与其他实用程序互换(就像我的情况一样,必须与ffmpeg一起使用),灵活性很有用.

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "$@"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done
Run Code Online (Sandbox Code Playgroud)


Koi*_*ima 10

另一个选项解析器(生成器)

一个优雅的 shell 脚本选项解析器(完全支持所有 POSIX shell) https://github.com/ko1nksm/getoptions(更新:2021-05-02 发布的 v3.3.0)

getoptions是用符合 POSIX 标准的 shell 脚本编写并于 2020 年 8 月发布的新选项解析器(生成器)。它适用于希望在 shell 脚本中支持 POSIX / GNU 样式选项语法的人。

支持的语法是-a, +a, -abc, -vvv, -p VALUE, -pVALUE, --flag, --no-flag, --with-flag, --without-flag, --param VALUE, --param=VALUE, --option[=VALUE], --no-option --

它支持子命令、验证、缩写选项和自动帮助生成。并适用于所有 POSIX shell(dash 0.5.4+、bash 2.03+、ksh88+、mksh R28+、zsh 3.1.9+、yash 2.29+、busybox ash 1.1.3+ 等)。

#!/bin/sh

VERSION="0.1"

parser_definition() {
  setup   REST help:usage -- "Usage: example.sh [options]... [arguments]..." ''
  msg -- 'Options:'
  flag    FLAG    -f --flag                 -- "takes no arguments"
  param   PARAM   -p --param                -- "takes one argument"
  option  OPTION  -o --option on:"default"  -- "takes one optional argument"
  disp    :usage  -h --help
  disp    VERSION    --version
}

eval "$(getoptions parser_definition) exit 1"

echo "FLAG: $FLAG, PARAM: $PARAM, OPTION: $OPTION"
printf '%s\n' "$@" # rest arguments
Run Code Online (Sandbox Code Playgroud)

它解析以下参数:

example.sh -f --flag -p VALUE --param VALUE -o --option -oVALUE --option=VALUE 1 2 3
Run Code Online (Sandbox Code Playgroud)

并自动生成帮助。

$ example.sh --help

Usage: example.sh [options]... [arguments]...

Options:
  -f, --flag                  takes no arguments
  -p, --param PARAM           takes one argument
  -o, --option[=OPTION]       takes one optional argument
  -h, --help
      --version
Run Code Online (Sandbox Code Playgroud)

它也是一个选项解析器生成器,生成以下简单的选项解析代码。如果您使用生成的代码,则不需要getoptions. 实现真正的可移植性和零依赖性。

FLAG=''
PARAM=''
OPTION=''
REST=''
getoptions_parse() {
  OPTIND=$(($#+1))
  while OPTARG= && [ $# -gt 0 ]; do
    case $1 in
      --?*=*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
        ;;
      --no-*|--without-*) unset OPTARG ;;
      -[po]?*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
        ;;
      -[fh]?*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'}
        OPTARG= ;;
    esac
    case $1 in
      '-f'|'--flag')
        [ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break
        eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
        FLAG="$OPTARG"
        ;;
      '-p'|'--param')
        [ $# -le 1 ] && set "required" "$1" && break
        OPTARG=$2
        PARAM="$OPTARG"
        shift ;;
      '-o'|'--option')
        set -- "$1" "$@"
        [ ${OPTARG+x} ] && {
          case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac
          [ "${OPTARG:-}" ] && { shift; OPTARG=$2; } || OPTARG='default'
        } || OPTARG=''
        OPTION="$OPTARG"
        shift ;;
      '-h'|'--help')
        usage
        exit 0 ;;
      '--version')
        echo "${VERSION}"
        exit 0 ;;
      --)
        shift
        while [ $# -gt 0 ]; do
          REST="${REST} \"\${$(($OPTIND-$#))}\""
          shift
        done
        break ;;
      [-]?*) set "unknown" "$1"; break ;;
      *)
        REST="${REST} \"\${$(($OPTIND-$#))}\""
    esac
    shift
  done
  [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
  case $1 in
    unknown) set "Unrecognized option: $2" "$@" ;;
    noarg) set "Does not allow an argument: $2" "$@" ;;
    required) set "Requires an argument: $2" "$@" ;;
    pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;;
    notcmd) set "Not a command: $2" "$@" ;;
    *) set "Validation error ($1): $2" "$@"
  esac
  echo "$1" >&2
  exit 1
}
usage() {
cat<<'GETOPTIONSHERE'
Usage: example.sh [options]... [arguments]...

Options:
  -f, --flag                  takes no arguments
  -p, --param PARAM           takes one argument
  -o, --option[=OPTION]       takes one optional argument
  -h, --help
      --version
GETOPTIONSHERE
}
Run Code Online (Sandbox Code Playgroud)


Ole*_*iev 9

我给你的函数parse_params将从命令行解析params.

  1. 它是纯粹的Bash解决方案,没有其他实用程序.
  2. 不污染全球范围.
  3. 毫不费力地返回简单易用的变量,您可以构建更多逻辑.
  4. 参数之前的破折号无关紧要(--all等于-all等于all=all)

下面的脚本是一个复制粘贴工作演示.查看show_use功能以了解如何使用parse_params.

限制:

  1. 不支持空格分隔的params(-d 1)
  2. 帕拉姆名称将失去破折号所以--any-param-anyparam等价
  3. eval $(parse_params "$@")必须在bash 函数中使用(它不能在全局范围内工作)

#!/bin/bash

# Universal Bash parameter parsing
# Parse equal sign separated params into named local variables
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Puts un-named params as-is into ${ARGV[*]} array
# Additionally puts all named params as-is into ${ARGN[*]} array
# Additionally puts all standalone "option" params as-is into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4.1 (Jul-27-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
        _escaped=${1/\*/\'\"*\"\'}
        _escaped=${_escaped//\'/\\\'}
        _escaped=${_escaped//\"/\\\"}
        # If equals delimited named parameter
        nonspace="[^[:space:]]"
        if [[ "$1" =~ ^${nonspace}${nonspace}*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "$1" | cut -d = -f 1)
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key='$_val';"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        elif [[ "$1" =~ ^\-${nonspace}+ ]]; then
            # remove dashes
            local _key=${1//\-}
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "$@")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
Run Code Online (Sandbox Code Playgroud)


Ren*_*lva 9

EasyOptions不需要任何解析:

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi
Run Code Online (Sandbox Code Playgroud)


van*_*rra 8

如果安装了#1,那么getopts工作得很好;#2你打算在同一平台上运行它.OSX和Linux(例如)在这方面表现不同.

这是一个支持equals,non-equals和boolean标志的(非getopts)解决方案.例如,您可以通过以下方式运行脚本:

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done
Run Code Online (Sandbox Code Playgroud)


Tha*_*ung 8

我想提交我的项目:https : //github.com/flyingangel/argparser

source argparser.sh
parse_args "$@"
Run Code Online (Sandbox Code Playgroud)

就那么简单。环境将填充与参数同名的变量


CIs*_*ies 8

根据这里的其他答案,这是我的版本:

#!/bin/bash
set -e

function parse() {
    for arg in "$@"; do # transform long options to short ones
        shift
        case "$arg" in
            "--name") set -- "$@" "-n" ;;
            "--verbose") set -- "$@" "-v" ;;
            *) set -- "$@" "$arg"
        esac
    done

    while getopts "n:v" optname  # left to ":" are flags that expect a value, right to the ":" are flags that expect nothing
    do
        case "$optname" in
            "n") name=${OPTARG} ;;
            "v") verbose=true ;;
        esac
    done
    shift "$((OPTIND-1))" # shift out all the already processed options
}


parse "$@"
echo "hello $name"
if [ ! -z $verbose ]; then echo 'nice to meet you!'; fi
Run Code Online (Sandbox Code Playgroud)

用法:

$ ./parse.sh
hello
$ ./parse.sh -n YOUR_NAME
hello YOUR_NAME
$ ./parse.sh -n YOUR_NAME -v
hello YOUR_NAME
nice to meet you!
$ ./parse.sh -v -n YOUR_NAME
hello YOUR_NAME
nice to meet you!
$ ./parse.sh -v
hello 
nice to meet you!
Run Code Online (Sandbox Code Playgroud)


ako*_*nov 7

这是我在一个函数中做的工作,以避免在堆栈中更高的位置同时运行getopts:

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}
Run Code Online (Sandbox Code Playgroud)


gal*_*mok 6

我想提供我的选项解析版本,它允许以下内容:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello
Run Code Online (Sandbox Code Playgroud)

也允许这样做(可能不需要):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder
Run Code Online (Sandbox Code Playgroud)

您必须在使用前决定是否在选项上使用 =。这是为了保持代码干净(ish)。

while [[ $# > 0 ]]
do
    key="$1"
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE="$2"
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder="$2"
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done
Run Code Online (Sandbox Code Playgroud)


tmo*_*hou 6

有几种方法可以解析 cmdline args(例如 GNU getopt(不可移植)vs BSD(MacOS)getopt vs getopts) - 都有问题。这个解决方案

  • 便携!
  • 零依赖,只依赖 bash 内置
  • 允许短期和长期选择
  • 处理空格或同时=在选项和参数之间使用分隔符
  • 支持连接的短选项样式 -vxf
  • 处理带有可选参数的选项(例如--colorvs --color=always),
  • 正确检测并报告未知选项
  • 支持--发出选项结束的信号,以及
  • 与相同功能集的替代方案相比,不需要代码膨胀。即简洁,因此更容易维护

示例:任何

# flag
-f
--foo

# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"

# option with optional argument
--baz
--baz="Optional Hello"
Run Code Online (Sandbox Code Playgroud)
#!/usr/bin/env bash

usage() {
  cat - >&2 <<EOF
NAME
    program-name.sh - Brief description
 
SYNOPSIS
    program-name.sh [-h|--help]
    program-name.sh [-f|--foo]
                    [-b|--bar <arg>]
                    [--baz[=<arg>]]
                    [--]
                    FILE ...

REQUIRED ARGUMENTS
  FILE ...
          input files

OPTIONS
  -h, --help
          Prints this and exits

  -f, --foo
          A flag option
      
  -b, --bar <arg>
          Option requiring an argument <arg>

  --baz[=<arg>]
          Option that has an optional argument <arg>. If <arg>
          is not specified, defaults to 'DEFAULT'
  --     
          Specify end of options; useful if the first non option
          argument starts with a hyphen

EOF
}

fatal() {
    for i; do
        echo -e "${i}" >&2
    done
    exit 1
}

# For long option processing
next_arg() {
    if [[ $OPTARG == *=* ]]; then
        # for cases like '--opt=arg'
        OPTARG="${OPTARG#*=}"
    else
        # for cases like '--opt arg'
        OPTARG="${args[$OPTIND]}"
        OPTIND=$((OPTIND + 1))
    fi
}

# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "$@")  # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
    case "$optchar" in
        h) usage; exit 0 ;;
        f) foo=1 ;;
        b) bar="$OPTARG" ;;
        -) # long option processing
            case "$OPTARG" in
                help)
                    usage; exit 0 ;;
                foo)
                    foo=1 ;;
                bar|bar=*) next_arg
                    bar="$OPTARG" ;;
                baz)
                    baz=DEFAULT ;;
                baz=*) next_arg
                    baz="$OPTARG" ;;
                -) break ;;
                *) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
            esac
            ;;
        *) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
    esac
done

shift $((OPTIND-1))

if [ "$#" -eq 0 ]; then
    fatal "Expected at least one required argument FILE" \
    "See '${0} --help' for usage"
fi

echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"
Run Code Online (Sandbox Code Playgroud)


小智 5

保留未处理参数的解决方案。包括演示。

这是我的解决方案。它非常灵活,不像其他的,不应该需要外部包并干净地处理剩余的参数。

用法是: ./myscript -flag flagvariable -otherflag flagvar2

您所要做的就是编辑 validflags 行。它在前面加上一个连字符并搜索所有参数。然后它将下一个参数定义为标志名称,例如

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2
Run Code Online (Sandbox Code Playgroud)

主要代码(简短版本,详细的例子,还有一个错误的版本):

#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
Run Code Online (Sandbox Code Playgroud)

带有内置 echo 演示的详细版本:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
#   argval=$(echo $@ | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1: $1 arg2: $2

echo leftovers: $leftovers
echo rate $rate time $time number $number
Run Code Online (Sandbox Code Playgroud)

最后一个,如果传递了一个无效的参数,这个错误就会出错。

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
do
    argval=$1
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=$2
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers
Run Code Online (Sandbox Code Playgroud)

优点:它做什么,它处理得很好。它保留了许多其他解决方案没有的未使用参数。它还允许调用变量而无需在脚本中手动定义。如果没有给出相应的参数,它还允许预填充变量。(参见详细示例)。

缺点:无法解析单个复杂的 arg 字符串,例如 -xcvf 将作为单个参数处理。不过,您可以稍微轻松地将附加代码写入我的添加此功能的代码中。


phy*_*att 5

本示例说明如何使用getoptand evalHEREDOCand shift来处理带有和不带有必需值的短和长参数。同样,switch / case语句简洁明了,易于遵循。

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, dont change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<-EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below
Run Code Online (Sandbox Code Playgroud)

上面脚本中最重要的几行是:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done
Run Code Online (Sandbox Code Playgroud)

简而言之,可读性强,几乎可以处理所有内容(IMHO)。

希望能对某人有所帮助。

  • 这是最好的答案之一。 (2认同)

归档时间:

查看次数:

1358844 次

最近记录:

6 年,5 月 前