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

2538

假设我有一个脚本,它被使用这行调用:

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

或者这个:

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

有什么接受的解析方法,可以使$v$f$d中的每个变量(或其中某些组合)全部设置为true,并且$outFile等于/fizz/someOtherFile


1
对于zsh用户,有一个很棒的内置工具叫做zparseopts,它可以执行以下操作: zparseopts -D -E -M -- d=debug -debug=d 并将-d--debug都放入$debug数组中。 如果其中一个被使用,echo $+debug[1]将返回0或1。参考:http://www.zsh.org/mla/users/2011/msg00350.html - dza
2
非常好的教程:http://linuxcommand.org/lc3_wss0120.php。我特别喜欢“命令行选项”这个例子。 - Gabriel Staples
2
参见给bash脚本提供接受标志(如命令)的选项?以获取详细的、特定场景下的长短选项解析器。它不尝试处理附加到短选项的选项参数,也不处理使用=将选项名称与选项值分离的长选项(在这两种情况下,它只是假设选项值在下一个参数中)。它还不处理短选项聚集 - 这个问题不需要它。 - Jonathan Leffler
这篇由Baeldung提供的优秀教程展示了在bash中处理命令行参数的4种方法,包括:1)位置参数$1$2等,2)使用getopts${OPTARG}的标志,3)循环遍历所有参数($@),以及4)使用$#$1shift运算符循环遍历所有参数。 - Gabriel Staples
使用 Bash 空格分隔的方式来检查解决方案:https://bigdata-etl.com/bash-parse-input-arguments-funtions-parameters/ - Paweł Cieśla
显示剩余2条评论
43个回答

3539

Bash空格分隔(例如,--选项 参数

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

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


使用带有 getopt[s] 的 bash

getopt(1) 存在以下限制(旧版本和相对近期的getopt版本):

  • 无法处理空字符串参数
  • 无法处理包含空格的参数

更新一些的 getopt 版本没有这些限制。有关更多信息,请参见这些文档


POSIX getopts

此外,POSIX shell 和其他 shell 提供了 getopts,它没有这些限制。我已经包括了一个简单的 getopts 示例。

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. 它更加通用,也可以在其他像 dash 这样的 shell 中使用。
  2. 它可以自动处理类似于 Unix 典型方式中的多个单选项,例如:-vf filename
getopts 的缺点是它只能处理短选项(-h 而不是 --help)而不能直接处理长选项,除非编写额外的代码。 有一个 getopts 教程 ,其中解释了所有语法和变量的含义。此外,在bash中还有 help getopts 命令,其中可能包含一些有用信息。

58
这是真的吗?根据[Wikipedia](http://en.wikipedia.org/wiki/Getopts),有一个更新的GNU增强版`getopt`,包括了所有`getopts`的功能,还有其他一些功能。在Ubuntu 13.04上运行man getopt的输出为getopt - parse command options (enhanced),因此我认为这个增强版现在是标准版。 - Livven
56
仅因为某件事在您的系统上以某种方式存在,这并不足以成为“标准”的假设的坚实前提。 - szablica
17
@Livven,"getopt" 并不是 GNU 实用程序,而是 "util-linux" 的一部分。 - Stephane Chazelas
4
如果你使用了“-gt 0”,在 “esac” 后面删除你的“shift”,将所有的 “shift” 增加1,并添加这个 case:“*) break;;” 以处理非可选参数。 例如:http://pastebin.com/6DJ57HTc - user979222
4
getopts "h?vf:" 应该改为 getopts "hvf:",未被识别的参数会被存储为 $opt?。引用“man builtins”中的话:“冒号和问号字符不能作为选项字符使用”。 - Simon A. Eugster
显示剩余32条评论

783
没有答案展示了增强的 getopt。而且最受欢迎的答案是误导性的:它要么忽略了-vfd风格的短选项(由提问者要求),要么忽略了位置参数后的选项(也是提问者要求的);而且它还忽略了解析错误。相反:
  • 使用来自util-linux或以前的GNU glibc的增强版getopt1
  • 它与GNU glibc的C函数getopt_long()一起使用。
  • 此页面上没有其他解决方案可以完成所有这些操作
    • 处理空格、引号字符甚至二进制参数2(非增强版getopt无法做到这一点)
    • 它可以处理位于末尾的选项:script.sh -o outFile file1 file2 -vgetopts无法做到这一点)
    • 允许=风格的长选项:script.sh --outfile=fileOut --infile fileIn(如果自解析,两者都很冗长)
    • 允许组合的短选项,例如-vfd(如果自解析,真的很麻烦)
    • 允许接触选项参数,例如-oOutfile-vfdoOutfile
  • 它已经非常古老了3,在任何GNU系统(主要是Linux)上都预装了它;参见脚注1
  • 您可以使用以下命令测试其是否存在:getopt --test → 返回值为4。
  • 其他getopt或shell内置的getopts的用途有限。
以下是呼叫内容。
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 大多数“bash系统”,包括Cygwin,都提供了增强的getopt;在OS X上,可以尝试使用brew install gnu-getoptbrew install util-linuxsudo port install getopt
2 POSIX的exec()约定没有可靠的方法来传递命令行参数中的二进制NULL;这些字节会提前结束参数
3 第一个版本在1997年或之前发布(我只追溯到1997年)


8
感谢这个信息。刚确认了 https://en.wikipedia.org/wiki/Getopts 上的属性表,如果您需要支持长选项并且您不在 Solaris 上,则应使用 getopt - johncip
8
我认为 getopt 的唯一缺点是在包装脚本中不能方便地使用,因为当一个人在包装脚本中有少量特定于包装脚本的选项,然后将非包装脚本选项传递给被包装的可执行文件时,无法轻松实现。假设我有一个名为 mygrepgrep 包装器,并且我有一个 --foo 选项是 mygrep 特有的,则我不能这样做 mygrep --foo -A 2 并自动将 -A 2 传递给 grep;我必须 这样做 mygrep --foo -- -A 2这是我的实现,建立在你的解决方案之上。 - Kaushal Modi
9
请注意,至少在当前的10.14.3版本上,此方法不能在Mac上使用。预装的getopt是来自1999年的BSD getopt... - jjj
5
脚注1涵盖了OS X。- 对于OS X开箱即用的解决方案,请查看其他问题和答案。或者说实话:要进行真正的编程,不要使用bash。;-) Translated: 脚注1覆盖了OS X。 - 如果您需要开箱即用的解决方案,请查看其他问题和答案。 或者说实话:对于真正的编程,请不要使用bash。 ;-) - Robert Siemer
3
@transcn 将返回值取反,同时产生的副作用是允许命令执行失败(否则 errexit 会在出错时中止程序)。脚本中的注释可以提供更多信息。否则,请查阅 man bash - Robert Siemer
显示剩余18条评论

395

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
这是我的工作内容。如果我想支持以布尔标记“./script.sh --debug dev --uglify fast --verbose”结束行,则必须使用“while [[ "$#" > 1 ]]”。示例:https://gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58 - hfossli
30
哇!简单清晰!我是这样使用它的:https://gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58 - hfossli
5
将这段内容粘贴到每个脚本中比处理源代码或让人想知道你的功能实际从哪里开始要好得多。 - RealHandy
5
警告:此命令允许重复参数,最后一个参数将被采用。例如,执行 ./script.sh -d dev -d prod 将导致 deploy == 'prod'。尽管如此,我还是使用了它 :P :) :+1: - yair
3
非常好的答案,谢谢!我稍微缩短了一下 - while (( "$#" )); do 而不是 while [[ "$#" -gt 0 ]]; do - CIsForCookies
显示剩余11条评论

162

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}

为了更好地理解 ${i#*=},请在这篇指南中搜索“Substring Removal”。它在功能上等同于`sed 's/[^=]*=//' <<< "$i"`,该命令调用了一个不必要的子进程,或者等同于`echo "$i" | sed 's/[^=]*=//'`,该命令调用了 两个 不必要的子进程。


4
好的!不错!但是这个方法无法处理以空格分隔的参数,比如mount -t tempfs ...。可以通过类似于以下代码来解决这个问题:while [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;;等等。 - Tobias Kienzler
4
无法处理 -vfd 风格的组合短选项。 - Robert Siemer
1
如果您想要通用地评估 --option-option,而不需要每次重复 OPTION=$i,请使用 -*=*) 作为匹配模式和 eval ${i##*-} - user8162

119
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
  • 允许在结尾使用参数
  • 如果有任何拼写错误,会显示明确的错误信息
  • 兼容性强,不使用bash特有语法
  • 易读性高,无需在循环中维护状态

4
抱歉耽搁了。在我的脚本中,handle_argument函数接收所有非选项参数。您可以将那行替换为任何您喜欢的内容,比如*) die "unrecognized argument: $1" 或者将参数收集到一个变量中 *) args+="$1"; shift 1;; - bronson
太棒了!我测试了几个答案,但只有这一个适用于所有情况,包括许多位置参数(在标志之前和之后)。 - Guilherme Garnier
3
代码很简洁,但是仅使用-n参数而不使用其他参数会因在shift 2中出现错误而导致无限循环,因为它两次调用了shift而不是shift 2。建议进行修改。 - lauksas

112

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
使用 $* 是对 getopt 的错误使用。(它会破坏带有空格的参数。)请查看我的答案,了解正确的用法。 - Robert Siemer
你为什么想要让它变得更加复杂呢? - SDsolar
@Matt J,如果你使用"$i"而不是$i,脚本的第一部分(for i)将能够处理带有空格的参数。getopts似乎无法处理带有空格的参数。相比于for i循环,使用getopt的优点是什么? - thebunnyrules

54
我发现在脚本中编写可移植解析的问题非常令人沮丧,因此我编写了一个名为Argbash的自由开源软件代码生成器,它可以为您的脚本生成参数解析代码,并具有一些不错的功能:

https://argbash.dev


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

44

我以早期的答案为起点,整理了我的旧adhoc参数解析。然后我重构了以下模板代码。它处理长参数和短参数,使用=或空格分隔的参数,以及多个短参数一起分组。最后,它会重新插入任何非参数参数回到$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

这段代码无法处理像 -c1 这样带参数的选项。而使用 = 来分隔短选项和它们的参数是不寻常的... - Robert Siemer
2
我在使用这段有用的代码时遇到了两个问题:1)在“-c=foo”的情况下,“shift”会吃掉下一个参数;2)'c'不应该包含在可组合短选项的“[cfr]”模式中。 - sfnd

34
# 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 value 两种方式

  • 适用于在bash中可以使用的任何参数名称

    • 这意味着 -a 或 -arg 或 --arg 或 -a-r-g 等等
  • 纯bash。无需学习/使用 getopt 或 getopts

缺点:

  • 不能组合参数

    • 这意味着不能使用 -abc,必须使用 -a -b -c

我有一个问题。为什么你使用shift; OUTPUTFILE="$1"而不是OUTPUTFILE="$2"?也许它有一个简单的答案,但我是Bash的新手。 - KasRoudra
1
我相信你可以选择任何一种方式,这只是个人偏好的问题。在这种情况下,我只想让$1作为“活动”参数出现在所有地方。 - Ponyboy47

32
这个例子展示了如何使用getoptevalHEREDOCshift来处理带有或不带有必需值的短参数和长参数。此外,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

上面脚本中最重要的几行是这些:
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
请注意,对于macOS系统,此脚本需要gnu-getopt才能正常工作。请执行brew install gnu-getopt命令进行安装。 - phyatt
这将把它从 getopt v1.10 升级到 getopt 2.38+。https://opensource.apple.com/source/shell_cmds/shell_cmds-216.60.1/getopt/getopt.c.auto.html - phyatt
非常好。但是在文件/文件夹/参数列表方面失败了。 - Juergen Schulze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接