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

How do I parse command line arguments in Bash?

提问人:Redwood 提问时间:10/11/2008 最后编辑:Benjamin W.Redwood 更新时间:11/24/2023 访问量:2022347

问:

比如说,我有一个脚本,用以下行调用:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

或者这个:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

什么是公认的解析方式,以便在每种情况下(或两者的某种组合),,和都将设置为并将等于?$v$f$dtrue$outFile/fizz/someOtherFile

bash 命令行 脚本 参数 getopts sh

评论

1赞 dza 8/2/2016
对于 zsh-users 来说,有一个很棒的内置函数,叫做 zparseopts,它可以做: 并且同时拥有两者,如果使用其中之一,则在数组中将返回 0 或 1。编号: zsh.org/mla/users/2011/msg00350.htmlzparseopts -D -E -M -- d=debug -debug=d-d--debug$debugecho $+debug[1]
2赞 Gabriel Staples 2/11/2020
非常好的教程:linuxcommand.org/lc3_wss0120.php。我特别喜欢“命令行选项”示例。
0赞 Meir Gabay 8/7/2020
我创建了一个脚本来为你做这件事,它叫做 - github.com/unfor19/bargs
2赞 Jonathan Leffler 10/9/2020
另请参阅 Gives a bash script the option to accept flags, like a command?(为一个精心设计的、临时的、长短选项解析器)提供接受标志的选项。它不尝试处理附加到短选项的选项参数,也不尝试处理将选项名称与选项值分开的长选项(在这两种情况下,它只是假设选项值在下一个参数中)。它也不处理短选项聚类——这个问题不需要它。=
0赞 Gabriel Staples 12/27/2020
Baeldung 的这个很棒的教程展示了在 bash 中处理命令行参数的 4 种方法,包括:1) 位置参数等,2) 带有 和 的标志,3) 循环所有参数 (),以及 4) 使用 、 和运算符循环所有参数。$1$2getopts${OPTARG}$@$#$1shift

答:

112赞 Matt J 10/11/2008 #1

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

我们所说的是,-a, 允许使用 -b、-c 或 -d,但 -c 后面跟着一个参数(“c:”表示)。

如果我们称之为“g”并尝试一下:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

我们从两个论点开始,和 “getopt”将选项拆分,并且 将每个放在自己的参数中。它还 添加了“--”。

评论

5赞 Robert Siemer 4/16/2016
使用 是 的 中断用法。(它用空格来填充参数。请参阅我的答案以正确使用。$*getopt
0赞 SDsolar 8/10/2017
为什么要让它变得更复杂?
0赞 thebunnyrules 6/1/2018
@Matt J 中,如果您使用“$i”而不是 $i,脚本的第一部分(for i)将能够处理带有空格的参数。getopts 似乎无法处理带有空格的参数。使用 getopt 比 for i 循环有什么优势?
17赞 Alek 3/1/2012 #2

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

#!/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

调用示例:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile

评论

1赞 m3nda 5/21/2015
我读了所有内容,这是我的首选。我不喜欢使用 argc 样式。我更喜欢先放主要选项-选项,然后再放单间距的特殊选项。我正在寻找最简单与更好的方法来阅读 argvs。-a=1-o option
1赞 m3nda 5/21/2015
它运行得非常好,但是如果您将参数传递给非 a: 选项,则以下所有选项都将被视为参数。您可以使用自己的脚本检查此行。-d 选项未设置为 d:./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFile
161赞 guneysus 11/13/2012 #3

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}

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

评论

4赞 Tobias Kienzler 11/12/2013
整洁!虽然这不适用于空格分隔的参数 à la .人们可能可以通过类似的东西来解决这个问题mount -t tempfs ...while [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;;
4赞 Robert Siemer 3/19/2016
这无法处理样式组合的短选项。-vfd
1赞 user8162 4/11/2021
如果要进行一般计算,并且不要每次都重复,请使用 as match pattern 和 。--option-optionOPTION=$i-*=*)eval ${i##*-}
3539赞 Bruno Bronosky 1/8/2013 #4

Bash 空格分隔(例如,--option argument)

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

POSITIONAL_ARGS=()

while [[ $# -gt 0 ]]; do
  case $1 in
    -e|--extension)
      EXTENSION="$2"
      shift # past argument
      shift # past value
      ;;
    -s|--searchpath)
      SEARCHPATH="$2"
      shift # past argument
      shift # past value
      ;;
    --default)
      DEFAULT=YES
      shift # past argument
      ;;
    -*|--*)
      echo "Unknown option $1"
      exit 1
      ;;
    *)
      POSITIONAL_ARGS+=("$1") # save positional arg
      shift # past argument
      ;;
  esac
done

set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
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 /etc/hosts
复制粘贴上面的块的输出
FILE EXTENSION  = conf
SEARCH PATH     = /etc
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
用法
demo-space-separated.sh -e conf -s /etc /etc/hosts

Bash 等于分离(例如,--option=argument)

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
      ;;
    --default)
      DEFAULT=YES
      shift # past argument with no value
      ;;
    -*|--*)
      echo "Unknown option $i"
      exit 1
      ;;
    *)
      ;;
  esac
done

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
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 /etc/hosts
复制粘贴上面的块的输出
FILE EXTENSION  = conf
SEARCH PATH     = /etc
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
用法
demo-equals-separated.sh -e=conf -s=/etc /etc/hosts

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


将 bash 与 getopt 一起使用

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

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

较新的版本没有这些限制。有关详细信息,请参阅以下文档getopt


POSIX getopts

此外,POSIX shell 和其他产品没有这些限制。我举了一个简单的例子。getoptsgetopts

cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
  case "$opt" in
    h|\?)
      show_help
      exit 0
      ;;
    v)  verbose=1
      ;;
    f)  output_file=$OPTARG
      ;;
  esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar
复制粘贴上面的块的输出
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
用法
demo-getopts.sh -vf /etc/hosts foo bar

其优点是:getopts

  1. 它更便携,可以在其他 shell 中工作,例如 .dash
  2. 它可以自动处理多个单个选项,就像典型的 Unix 方式一样。-vf filename

缺点是它只能处理短选项(,不能),而不需要额外的代码。getopts-h--help

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

评论

58赞 Livven 6/7/2013
这是真的吗?根据维基百科,有一个更新的 GNU 增强版本,它包括所有功能,然后是一些功能。 在 Ubuntu 13.04 上输出作为名称,所以我认为这个增强版本现在是标准的。getoptgetoptsman getoptgetopt - parse command options (enhanced)
56赞 szablica 7/17/2013
某些东西是你的系统上的某种方式,这是一个非常薄弱的前提,可以作为“标准”的假设的基础。
17赞 Stephane Chazelas 8/21/2014
@Livven,这不是一个GNU实用程序,它是.getoptutil-linux
4赞 6/20/2016
如果使用 ,请删除 ,将所有参数增加 1 并添加以下情况:您可以处理非选项参数。例如:pastebin.com/6DJ57HTc-gt 0shiftesacshift*) break;;
4赞 Simon A. Eugster 10/6/2017
应该没有问号。无法识别的参数存储在 中。引自:getopts "h?vf:"getopts "hvf:"?$optman builtins“The colon and question mark characters may not be used as option characters.”
7赞 akostadinov 7/19/2013 #5

这就是我在函数中的做法,以避免在堆栈中更高的位置同时破坏 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
...
}
21赞 unsynchronized 6/9/2014 #6

如果您正在制作可与其他实用程序互换的脚本,则以下灵活性可能很有用。

也:

command -x=myfilename.ext --another_switch 

艺术

command -x myfilename.ext --another_switch

代码如下:

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
44赞 Shane Day 7/1/2014 #7

我使用前面的答案作为起点来整理我的旧临时参数解析。然后,我重构了以下模板代码。它使用 = 或空格分隔的参数处理长参数和短参数,以及组合在一起的多个短参数。最后,它将所有非参数参数重新插入到 $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

评论

0赞 Robert Siemer 12/6/2015
此代码无法处理具有如下参数的选项:.使用空头选项与他们的论点分开是不寻常的......-c1=
2赞 sfnd 6/7/2016
我在这个有用的代码块中遇到了两个问题:1)在“-c=foo”的情况下,“shift”最终会吃掉下一个参数;2)“C”不应包含在可组合空头期权的“[CFR]”模式中。
11赞 vangorra 2/13/2015 #8

如果 #1 您安装了它并且 #2 您打算在同一平台上运行它,则 Getopts 效果很好。OSX 和 Linux(例如)在这方面的表现不同。

下面是一个(非 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
783赞 Robert Siemer 4/21/2015 #9

没有答案展示了增强的 getopt得票最多的答案具有误导性:它要么忽略了样式空头选项(OP要求),要么忽略了位置论证之后的选项(OP也要求);它忽略了解析错误。相反:-⁠vfd

  • 使用来自 util-linux 或以前的 GNU glibc 的增强 getopt1
  • 它与 GNU glibc 的 C 函数一起工作。getopt_long()
  • 此页面上没有其他解决方案可以做到这一切
    • 在参数2 中处理空格、引用字符甚至二进制(非增强型无法做到这一点)getopt
    • 它可以在末尾处理选项:(不这样做)script.sh -o outFile file1 file2 -vgetopts
    • 允许 -style long 选项:(如果自解析,则允许两者都很长)=script.sh --outfile=fileOut --infile fileIn
    • 允许组合空头期权,例如 (如果自我解析,则实际工作)-vfd
    • 允许触摸选项参数,例如 或-oOutfile-vfdoOutfile
  • 已经3 年了,它预装在任何 GNU 系统(即主要是 Linux)上;见脚注1
  • 您可以使用以下命令测试它是否存在: →返回值 4。getopt --test
  • 其他或 shell 内置的用途有限。getoptgetopts

以下调用

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

全部返回

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

具有以下内容myscript

#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# ${PIPESTATUS[0]} with a simple $?, but I prefer safety.
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

# option --output/-o requires 1 argument
LONGOPTS=debug,force,output:,verbose
OPTIONS=dfo:v

# -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"

1 增强的 getopt 在大多数“bash-systems”上可用,包括 Cygwin;在 OS X 上,尝试 brew install gnu-getopt、brew install util-linux
2 POSIX 约定没有可靠的方法在命令行参数中传递二进制 NULL;这些字节过早地结束了 1997 年或之前发布的 argument
3 第一个版本(我只追溯到 1997 年)
sudo port install getoptexec()

评论

8赞 johncip 1/12/2017
谢谢你。刚刚从 en.wikipedia.org/wiki/Getopts 的功能表中确认,如果您需要对长选项的支持,并且您不在 Solaris 上,那么这是要走的路。getopt
8赞 Kaushal Modi 4/27/2017
我相信唯一需要注意的是,它不能方便地在包装脚本中使用,因为包装脚本可能只有很少的特定于包装脚本的选项,然后将非包装脚本选项完整地传递给包装的可执行文件。假设我有一个包装器调用,并且我有一个特定于 的选项,那么我就不能做,并自动传递给 ;我需要做.这是我在您的解决方案之上的实现getoptgrepmygrep--foomygrepmygrep --foo -A 2-A 2grepmygrep --foo -- -A 2
3赞 Robert Siemer 3/21/2018
@bobpaul 你关于 util-linux 的陈述是错误的,也具有误导性:该软件包在 Ubuntu/Debian 上被标记为“必不可少”。因此,它始终处于安装状态。– 你说的是哪个发行版(你说它需要故意安装)?
9赞 jjj 4/10/2019
请注意,至少在当前的 10.14.3 之前,这在 Mac 上不起作用。发布的 getopt 是 1999 年的 BSD getopt......
5赞 Robert Siemer 4/11/2019
@jjj脚注 1 涵盖 OS X。 – 对于 OS X 开箱即用的解决方案,请查看其他问题和答案。或者说实话:对于真正的编程,不要使用 bash。;-)
3赞 Mark Fox 4/27/2015 #10

混合基于位置的参数和基于标志的参数

--param=arg (等于分隔)

在位置参数之间自由混合标志:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

可以通过相当简洁的方法完成:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

--param arg(空格分隔)

通常更清楚不要混合和风格。--flag=value--flag value

./script.sh dumbo 127.0.0.1 --environment production -q -d

这读起来有点危险,但仍然有效

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2
7赞 galmok 6/24/2015 #11

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

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

还允许这样做(可能是不需要的):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

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

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

评论

1赞 Luca Davanzo 11/15/2016
${key+x} 上的 “+x” 是什么意思?
1赞 galmok 11/15/2016
这是一个测试,用于查看“密钥”是否存在。再往下,我取消了设置键,这打破了内部的 while 循环。
119赞 bronson 7/16/2015 #12
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

此解决方案:

  • 句柄和-n arg--name=arg
  • 允许末尾的参数
  • 如果有任何拼写错误,则显示理智的错误
  • 兼容,不使用 bashisms
  • 可读,不需要在循环中维护状态

评论

4赞 bronson 10/9/2015
很抱歉耽搁了。在我的脚本中,handle_argument 函数接收所有非选项参数。您可以将该行替换为您想要的任何内容,或者将参数收集到变量中。*) die "unrecognized argument: $1"*) args+="$1"; shift 1;;
0赞 Guilherme Garnier 4/14/2018
了不起!我已经测试了几个答案,但这是唯一一个适用于所有情况的答案,包括许多位置参数(在标志之前和之后)
3赞 lauksas 4/28/2019
简洁的代码,但是使用 -n 而没有其他 arg 会导致无限循环,因为 上的错误,发出两次而不是 。建议编辑。shift 2shiftshift 2
3赞 schily 10/19/2015 #13

请注意,这是AT&T的一个短暂的错误。getopt(1)

GetOpt 创建于 1984 年,但在 1986 年就已经埋没了,因为它并不真正可用。

一个非常过时的事实的一个证明是,手册页仍然提到而不是 ,这是在 1986 年与内置的 shell 一起添加到 Bourne Shell 中的,以便处理内部空格的参数。getoptgetopt(1)"$*""$@"getopts(1)

顺便说一句:如果您有兴趣解析 shell 脚本中的长选项,那么了解 libc (Solaris) 的实现可能会感兴趣,并且两者都添加了一个统一的长选项实现,该实现支持长选项作为短选项的别名。这导致并通过 为长选项实现统一接口。getopt(3)ksh93ksh93Bourne Shellgetopts

从 Bourne Shell 手册页中获取的长选项示例:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

显示选项别名在 Bourne Shell 和 ksh93 中可以使用的时间。

请参阅最近 Bourne Shell 的手册页:

http://schillix.sourceforge.net/man/man1/bosh.1.html

以及来自 OpenSolaris 的 getopt(3) 的手册页:

http://schillix.sourceforge.net/man/man3c/getopt.3c.html

最后, getopt(1) 手册页来验证过时的 $*:

http://schillix.sourceforge.net/man/man1/getopt.1.html

评论

0赞 Neil Mayhew 8/6/2022
这是特定于 Solaris 的,不包括 OP 特别要求的 bash
395赞 Inanc Gumus 11/20/2015 #14

deploy.sh

#!/bin/bash

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

echo "Where to deploy: $target"
echo "Should uglify  : $uglify"

用法:

./deploy.sh -t dev -u

# OR:

./deploy.sh --target dev --uglify

评论

4赞 hfossli 4/8/2018
这就是我正在做的事情。如果我想支持用布尔标志结束该行,则必须这样做。示例:gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58while [[ "$#" > 1 ]]./script.sh --debug dev --uglify fast --verbose
30赞 hfossli 4/8/2018
哇!简单干净!这就是我使用它的方式:gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
5赞 RealHandy 2/1/2019
这最好粘贴到每个脚本中,而不是处理源代码或让人们想知道您的功能实际从哪里开始。
5赞 yair 9/16/2019
警告:这允许重复的参数,以最新的参数为准。例如: 将导致 .无论如何我都用过它:P :) :+1:./script.sh -d dev -d proddeploy == 'prod'
3赞 CIsForCookies 1/8/2021
很好的答案,tnx!我把它缩短了一点 - 而不是while (( "$#" )); dowhile [[ "$#" -gt 0 ]]; do
12赞 Oleksii Chekulaiev 7/2/2016 #15

我给你一个函数,它将从命令行解析参数。parse_params

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

下面的脚本是一个复制粘贴工作演示。请参阅函数以了解如何使用 。show_useparse_params

局限性:

  1. 不支持以空格分隔的参数 (-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

评论

0赞 Oleksii Chekulaiev 9/28/2016
要使用演示来解析 bash 脚本中的参数,只需执行show_use "$@"
0赞 Oleksii Chekulaiev 9/28/2016
基本上,我发现 github.com/renatosilva/easyoptions 以相同的方式做同样的事情,但比这个函数更大一些。
54赞 bubla 7/11/2016 #16

我发现在脚本中编写可移植解析的问题非常令人沮丧,以至于我编写了 Argbash - 一个 FOSS 代码生成器,可以为您的脚本生成参数解析代码,并且它具有一些不错的功能:

https://argbash.dev

评论

0赞 RichVel 8/18/2016
感谢您编写 argbash,我刚刚使用它并发现它运行良好。我主要选择 argbash,因为它是一个代码生成器,支持 OS X 10.11 El Capitan 上的旧 bash 3.x。唯一的缺点是,与调用模块相比,代码生成器方法意味着主脚本中有相当多的代码。
2赞 bubla 8/24/2016
实际上,你可以以一种为你生成量身定制的解析库的方式使用 Argbash,你可以将其包含在你的脚本中,或者你可以把它放在一个单独的文件中,然后只提供它。我添加了一个示例来证明这一点,并且在文档中也更明确地说明了这一点。
1赞 RichVel 8/24/2016
很高兴知道。这个例子很有趣,但仍然不是很清楚 - 也许你可以将生成的脚本的名称更改为“parse_lib.sh”或类似名称,并显示主脚本调用它的位置(就像在包装脚本部分,这是更复杂的用例)。
3赞 bubla 12/3/2016
这些问题已在最新版本的 argbash 中得到解决:文档已得到改进,引入了快速入门 argbash-init 脚本,您甚至可以在 argbash.io/generate 在线使用 argbash
5赞 user6768257 8/29/2016 #17

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

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

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

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

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

主要代码(简短版本,冗长,后面有示例,也是一个有错误的版本):

#!/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

带有内置 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

最后一个,如果传递了无效的 -argument,则此错误。

#!/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

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

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

32赞 phyatt 9/8/2016 #18

此示例演示如何使用 and 和 和 以及 处理短参数和长参数,以及后面的必需值和不包含必需值。此外,switch/case 语句简明扼要且易于理解。getoptevalHEREDOCshift

#!/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

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

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

简短、切中要害、可读性强,几乎可以处理所有事情(恕我直言)。

希望对某人有所帮助。

评论

4赞 Mr. Polywhirl 3/5/2020
这是最好的答案之一。
0赞 phyatt 10/7/2022
请注意,对于 macOs,此脚本需要正常工作。gnu-getoptbrew install gnu-getopt
0赞 phyatt 10/7/2022
这会将其从 带到 .opensource.apple.com/source/shell_cmds/shell_cmds-216.60.1/......getopt v1.10getopt 2.38+
0赞 Juergen Schulze 3/6/2023
很好。但文件/文件夹/参数列表失败。
34赞 Ponyboy47 9/9/2016 #19
# 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

这允许您既有空格分隔的选项/值,也有相等的定义值。

因此,您可以使用以下命令运行脚本:

./myscript --foo -b -o /fizz/file.txt

以及:

./myscript -f --bar -o=/fizz/file.txt

两者应该具有相同的最终结果。

优点:

  • 允许 -arg=value 和 -arg 值

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

    • 意思是 -a 或 -arg 或 --arg 或 -a-r-g 或其他
  • 纯粹的狂欢。无需学习/使用 getopt 或 getopts

缺点:

  • 无法组合参数

    • 意思是没有 -abc。你必须执行 -a -b -c

评论

0赞 KasRoudra 5/21/2022
我这里有一个问题。你为什么用而不是?也许它有一个简单的答案,但我是 bash 的新手shift; OUTPUTFILE="$1"OUTPUTFILE="$2"
1赞 Ponyboy47 5/25/2022
我相信你可以做任何一个,这真的只是归结为个人喜好。在这种情况下,我只想在任何地方保持“活跃”的论点$1
3赞 Emeric Verschuur 2/21/2017 #20

我写了一个 bash 助手来编写一个不错的 bash 工具

项目主页:https://gitlab.mbedsys.org/mbedsys/bashopts

例:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "$@"

# Process argument
bashopts_process_args

将给予帮助:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

享受:)

评论

0赞 Josh Wulf 6/25/2017
我在Mac OS X上得到这个:''' lib/bashopts.sh:第138行:声明:-A:无效选项声明:用法:声明[-afFirtx] [-p] [名称[=值] ...]lib/bashopts.sh:138 中的错误。'declare -x -A bashopts_optprop_name' 退出,状态为 2 调用树:1:lib/controller.sh:4 source(...)退出状态为 1 '''
0赞 Josh Wulf 6/25/2017
您需要 Bash 版本 4 才能使用它。在 Mac 上,默认版本为 3。您可以使用 home brew 来安装 bash 4。
4赞 a_z 3/15/2017 #21

这是我的方法 - 使用正则表达式。

  • 没有 getopts
  • 它处理短参数块-qwerty
  • 它处理短参数-q -w -e
  • 它处理长选项--qwerty
  • 您可以将属性传递给空头或多头选项(如果您使用的是空头选项块,则属性将附加到最后一个选项)
  • 您可以使用空格或提供属性,但属性匹配直到遇到连字符+空格“分隔符”,所以 in 是一个属性=--q=qwe tyqwe ty
  • 它处理上述所有内容的混合,因此是有效的-o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute

脚本:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
options=$@

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done

评论

0赞 mauron85 6/21/2017
像这个。也许只需添加 -e 参数即可与新行相呼应。
3赞 John 10/11/2017 #22

假设我们创建了一个名为 Below 的 shell 脚本test_args.sh

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

运行以下命令后:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

输出将为:

year=2017 month=12 day=22 flag=true

评论

6赞 Will Barnwell 10/11/2017
这与诺亚的回答采用相同的方法,但安全检查/保障措施较少。这允许我们将任意参数写入脚本的环境中,我很确定您在此处使用 eval 可能会允许命令注入。
9赞 Thanh Trung 9/17/2018 #23

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

source argparser.sh
parse_args "$@"

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

24赞 jchook 3/6/2019 #24

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

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

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

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage
  exit 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; exit 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[@]}"

评论

0赞 Lukas S. 5/1/2021
这看起来很棒 - 但想知道在这条线上是否真的有必要:.如果保留在那里,则无法解析它是否包含在之后(或任何其他长样式布尔选项)。例如,失败(尽管工作正常)。我在我的脚本中把它拿出来,现在它可以工作了,但不确定这是否打破了我不知道的其他场景。END_OF_OPT=1--*) ARGV+=("$arg"); END_OF_OPT=1 ;;--username=fred--quietscript.sh --quiet --username=fredUnrecognized argument: --username=fredscript.sh --quiet --username fredEND_OF_OPT=1
3赞 mjs 12/1/2019 #25

下面是一个 getopts,它用最少的代码实现解析,并允许您使用带有子字符串的 eval 来定义您希望在一种情况下提取的内容。

基本上eval "local key='val'"

function myrsync() {

        local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k="$1";
                case "$k" in
                    ---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
                        eval "local ${k:3}='${2}'"; shift; shift    # Past two arguments
                    ;;
                    *)  # Unknown option  
                        args+=("$1"); shift;                        # Past argument only
                    ;;                                              
                esac                                                
        done; set -- "${backup[@]}"                                 # Restore $@


        echo "${sourceurl}"
}

将变量声明为局部变量,而不是此处大多数答案的全局变量。

称为:

myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ... 

${k:3} 基本上是一个子字符串,用于从键中删除第一个。---

7赞 tmoschou 12/24/2019 #26

有几种方法可以解析 cmdline 参数(例如 GNU getopt(不可移植)与 BSD (MacOS) getopt 与 getopts) - 都是有问题的。此解决方案

  • 便携!
  • 零依赖,仅依赖 bash 内置
  • 允许做空和做多选项
  • 处理空格或同时使用选项和参数之间的分隔符=
  • 支持串联短选项样式-vxf
  • handles 选项与可选参数(例如 vs ),--color--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"

#!/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=${@}"

评论

0赞 Kalec 6/16/2023
这是一些东西。与经典的 getopts 垃圾相比,我更喜欢它,但它的解析方式并不“自然”。 生成,它认为这是文件名的一部分。它没有设置.它没有做我想让它做的事情。./program-name.sh NOTES.md -ffoo=, bar=, baz=, files=NOTES.md -f-ffoo=1
0赞 tmoschou 7/6/2023
@Kalec这是因为在非选项参数之后,并且根据设计,选项处理在第一个非选项参数之后停止,就像许多 UNIX 命令一样,因为您可以有不同的子命令和不同的子选项。 在这种情况下,是存储在 with in 中。如果您愿意,您可以重复选项解析,直到-fNOTES.md-f$2NOTES.md$1$# -eq 0
3赞 Mihir Luthra 4/10/2020 #27

我想分享我为解析选项所做的工作。 这里的答案没有满足我的一些需求,所以我不得不想出这个:https://github.com/MihirLuthra/bash_option_parser

这支持:

  • 子选项解析
  • 选项的别名
  • 可选参数
  • 变量参数
  • 打印使用情况和错误

假设我们有一个命令,其用法如下:fruit

fruit <fruit-name> ...
   [-e|—-eat|—-chew]
   [-c|--cut <how> <why>]
   <command> [<args>] 

-e不带参数
取两个参数,即如何切割和为什么要切割
本身至少需要一个参数。
用于子选项,如 等(类似于其中有子选项等)
-cfruit<command>appleorangegitcommitpush

所以解析它:

parse_options \
    'fruit'                         '1 ...'  \
    '-e'     , '--eat' , '--chew'   '0'      \
    '-c'     , '--cut'              '1 1'    \
    'apple'                         'S'      \
    'orange'                        'S'      \
    ';' \
    "$@"

现在,如果有任何使用错误,可以使用以下方法打印:option_parser_error_msg

retval=$?

if [ $retval -ne 0 ]; then
    # this will manage error messages if
    # insufficient or extra args are supplied

    option_parser_error_msg "$retval"

    # This will print the usage
    print_usage 'fruit'
    exit 1
fi

现在检查是否通过了某些选项,

if [ -n "${OPTIONS[-c]}" ]
then
    echo "-c was passed"

    # args can be accessed in a 2D-array-like format
    echo "Arg1 to -c = ${ARGS[-c,0]}"
    echo "Arg2 to -c = ${ARGS[-c,1]}"

fi

子选项解析也可以通过传递 which 来完成,使其在移动参数以到达子选项的参数后开始解析。此示例对此进行了演示。$shift_countparse_options_detailed

自述文件和示例中提供了详细说明 在存储库中。

32赞 leogama 6/28/2020 #28

尽快:另一个 shell 参数解析器

编辑说明:2.0 版,现在具有纯 POSIX shell 代码和无麸质!

TL的;博士

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

这是一个最小版本,只要命令正确就可以工作,即它几乎不执行任何检查。您可以将其粘贴到 脚本的顶部(它不会作为函数使用)并替换您的选项定义。

#!/bin/sh -e

USAGE="Usage:  ${CMD:=${0##*/}} [(-v|--verbose)] [--name=TEXT] [(-o|--output) FILE] [ARGS...]"

# helper functions
exit2 () { printf >&2 "%s:  %s: '%s'\n%s\n" "$CMD" "$1" "$2" "$USAGE"; exit 2; }
check () { { [ "$1" != "$EOL" ] && [ "$1" != '--' ]; } || exit2 "missing argument" "$2"; }  # avoid infinite loop

# parse command-line options
set -- "$@" "${EOL:=$(printf '\1\3\3\7')}"  # end-of-list marker
while [ "$1" != "$EOL" ]; do
  opt="$1"; shift
  case "$opt" in

    #EDIT HERE: defined options
         --name    ) check "$1" "$opt"; opt_name="$1"; shift;;
    -o | --output  ) check "$1" "$opt"; opt_output="$1"; shift;;
    -v | --verbose ) opt_verbose='true';;
    -h | --help    ) printf "%s\n" "$USAGE"; exit 0;;

    # process special cases
    --) while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;;   # parse remaining as positional
    --[!=]*=*) set -- "${opt%%=*}" "${opt#*=}" "$@";;                  # "--opt=arg"  ->  "--opt" "arg"
    -[A-Za-z0-9] | -*[!A-Za-z0-9]*) exit2 "invalid option" "$opt";;    # anything invalid like '-*'
    -?*) other="${opt#-?}"; set -- "${opt%$other}" "-${other}" "$@";;  # "-abc"  ->  "-a" "-bc"
    *) set -- "$@" "$opt";;                                            # positional, rotate to the end
  esac
done; shift

printf "name = '%s'\noutput = '%s'\nverbose = '%s'\n\$@ = (%s)\n" \
    "$opt_name" "$opt_output" "$opt_verbose" "$*"

示例输出

$ ./asap-example.sh -vo path/to/camelot 'spam?' --name=Arthur 'spam!' -- +42 -17
name = 'Arthur'
output = 'path/to/camelot'
verbose = 'true'
$@ = (spam? spam! +42 -17)
$ ./asap-example.sh -name Lancelot eggs bacon
asap-example.sh:  invalid option: '-n'
Usage:  asap-example.sh [(-v|--verbose)] [--name=TEXT] [(-o|--output) FILE] [ARG...]

描述

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

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

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

可移植性

此代码已经过测试和验证,可以与相当新版本的 : 、 、 、 和 BusyBox 一起使用(所有代码都使用其标准可执行路径调用,而不是 )。bashdashmkshksh93yashzshash/bin/sh

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


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

评论

0赞 leogama 4/4/2022
谢谢你,@Liso!我需要更新这个答案。我分析了正则表达式的决策树,发现了一些小错误(虽然没什么大不了的)。
1赞 Pedro A 7/21/2022
@leogama ur 不是二进制序列。它只是一个长度为 12(包含四个反斜杠)的可打印字符串。你是想用代替吗?我不知道这是否会影响 POSIX 合规性。$EOLprintfecho
1赞 leogama 7/21/2022
@PedroA 太好了!来自 GNU echo info 页面:“POSIX 不需要支持任何选项,并表示如果任何 STRING 包含反斜杠或第一个参数是 '-n',则 'echo' 的行为是实现定义的。如果可移植程序需要省略尾随换行符或输出控制字符或反斜杠,则可以使用'printf'命令。至少在POSIX中...printf
0赞 Liso 9/12/2023
使用 shebang 运行它会导致无限循环。#!/bin/bash
12赞 Koichi Nakashima 8/14/2020 #29

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

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

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

它解析以下参数:

example.sh -f --flag -p VALUE --param VALUE -o --option -oVALUE --option=VALUE 1 2 3

并自动生成帮助。

$ 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

它也是一个选项解析器生成器,生成以下简单的选项解析代码。如果使用生成的代码,则不需要 .实现真正的可移植性和零依赖性。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
}
7赞 CIsForCookies 10/27/2021 #30

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

#!/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

用法:

$ ./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!

评论

0赞 bfontaine 10/26/2022
您能解释一下为什么您认为这个版本比其他版本更好吗?由于没有解释,很难理解为什么我们应该使用这个版本而不是另一个版本。