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

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

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

How do I parse command line arguments in Bash?

bashcommand-linescriptingargumentsgetopts

提问by Lawrence Johnston

Say, I have a script that gets called with this line:

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

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

or this one:

或者这个:

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

What's the accepted way of parsing this such that in each case (or some combination of the two) $v, $f, and $dwill all be set to trueand $outFilewill be equal to /fizz/someOtherFile?

在每种情况下(或两者的某种组合)$v$f, 和 $d都将被设置为true并且$outFile将等于/fizz/someOtherFile

回答by Bruno Bronosky

Method #1: Using bash without getopt[s]

方法 #1:在没有 getopt[s] 的情况下使用 bash

Two common ways to pass key-value-pair arguments are:

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

Bash Space-Separated (e.g., --option argument) (without getopt[s])

Bash Space-Separated(例如,--option argument)(不带 getopt[s])

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

用法 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=""

case $key in
    -e|--extension)
    EXTENSION=""
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH=""
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH=""
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("") # 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  ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 ""
fi
EOF

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

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

output from copy-pasting the block above:

复制粘贴上面的块的输出:

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

Bash Equals-Separated (e.g., --option=argument) (without getopt[s])

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

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

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

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  ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 
fi
EOF

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

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

output from copy-pasting the block above:

复制粘贴上面的块的输出:

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

To better understand ${i#*=}search for "Substring Removal" in this guide. It is functionally equivalent to `sed 's/[^=]*=//' <<< "$i"`which calls a needless subprocess or `echo "$i" | sed 's/[^=]*=//'`which calls twoneedless subprocesses.

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

Method #2: Using bash with getopt[s]

方法 #2:使用 bash 和 getopt[s]

from: http://mywiki.wooledge.org/BashFAQ/035#getopts

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

getopt(1) limitations(older, relatively-recent getoptversions):

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

  • can't handle arguments that are empty strings
  • can't handle arguments with embedded whitespace
  • 无法处理空字符串的参数
  • 无法处理带有嵌入空格的参数

More recent getoptversions don't have these limitations.

较新的getopt版本没有这些限制。

Additionally, the POSIX shell (and others) offer getoptswhich doesn't have these limitations. I've included a simplistic getoptsexample.

此外,POSIX shell(和其他)getopts没有这些限制。我已经包含了一个简单的getopts例子。

Usage demo-getopts.sh -vf /etc/hosts foo bar

用法 demo-getopts.sh -vf /etc/hosts foo bar

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

output from copy-pasting the block above:

复制粘贴上面的块的输出:

verbose=1, output_file='/etc/hosts', Leftovers: foo bar

The advantages of getoptsare:

的优点getopts是:

  1. It's more portable, and will work in other shells like dash.
  2. It can handle multiple single options like -vf filenamein the typical Unix way, automatically.
  1. 它更便携,可以在其他 shell 中工作,比如dash.
  2. 它可以像-vf filename典型的 Unix 方式一样自动处理多个单个选项。

The disadvantage of getoptsis that it can only handle short options (-h, not --help) without additional code.

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

There is a getopts tutorialwhich explains what all of the syntax and variables mean. In bash, there is also help getopts, which might be informative.

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

回答by Robert Siemer

No answer mentions enhanced getopt. And the top-voted answeris misleading:It either ignores -?vfdstyle short options (requested by the OP) or options after positional arguments (also requested by the OP); and it ignores parsing-errors. Instead:

没有答案提到增强的 getopt。并且最高投票的答案具有误导性:它要么忽略-?vfd样式短选项(由 OP 请求),要么忽略位置参数之后的选项(也由 OP 请求);它忽略解析错误。反而:

  • Use enhanced getoptfrom util-linux or formerly GNU glibc.1
  • It works with getopt_long()the C function of GNU glibc.
  • Has alluseful distinguishing features (the others don't have them):
    • handles spaces, quoting characters and even binary in arguments2(non-enhanced getoptcan't do this)
    • it can handle options at the end: script.sh -o outFile file1 file2 -v(getoptsdoesn't do this)
    • allows =-style long options: script.sh --outfile=fileOut --infile fileIn(allowing both is lengthy if self parsing)
    • allows combined short options, e.g. -vfd(real work if self parsing)
    • allows touching option-arguments, e.g. -oOutfileor -vfdoOutfile
  • Is so old already3that no GNU system is missing this (e.g. any Linux has it).
  • You can test for its existence with: getopt --test→ return value 4.
  • Other getoptor shell-builtin getoptsare of limited use.
  • 使用getoptutil-linux 或以前的 GNU glibc增强1
  • 它与getopt_long()GNU glibc 的 C 函数一起工作。
  • 具有所有有用的区别特征(其他人没有):
    • 处理参数2 中的空格、引用字符甚至二进制(非增强getopt不能这样做)
    • 它可以处理在最后的选项:script.sh -o outFile file1 file2 -vgetopts不这样做)
    • 允许=- 样式长选项:(script.sh --outfile=fileOut --infile fileIn如果自解析,则允许两者都很长)
    • 允许组合短选项,例如-vfd(如果自解析的实际工作)
    • 允许触摸选项参数,例如-oOutfile-vfdoOutfile
  • 已经是3岁了,以至于没有 GNU 系统会遗漏这个(例如,任何 Linux 都有)。
  • 您可以通过以下方式测试它的存在:getopt --test→ 返回值 4。
  • 其他getopt或 shell-builtingetopts的用途有限。

The following calls

以下调用

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

all return

全部返回

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

with the following myscript

与以下 myscript

#!/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 "
#!/bin/bash

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

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"
" -- "$@") 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 "" in -d|--debug) d=y shift ;; -f|--force) f=y shift ;; -v|--verbose) v=y shift ;; -o|--output) outFile="" shift 2 ;; --) shift break ;; *) echo "Programming error" exit 3 ;; esac done # handle non-option arguments if [[ $# -ne 1 ]]; then echo "
./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify
: A single input file is required." exit 4 fi echo "verbose: $v, force: $f, debug: $d, in: , out: $outFile"


1enhanced getopt is available on most “bash-systems”, including Cygwin; on OS X try brew install gnu-getoptor sudo port install getopt
2the POSIX exec()conventions have no reliable way to pass binary NULL in command line arguments; those bytes prematurely end the argument
3first version released in 1997 or before (I only tracked it back to 1997)

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

回答by Inanc Gumus

More succinct way

更简洁的方式

script.sh

脚本文件

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

Usage:

用法:

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

回答by guneysus

from : digitalpeer.comwith minor modifications

来自:digitalpeer.com稍作修改

Usage myscript.sh -p=my_prefix -s=dirname -l=libname

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

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

To better understand ${i#*=}search for "Substring Removal" in this guide. It is functionally equivalent to `sed 's/[^=]*=//' <<< "$i"`which calls a needless subprocess or `echo "$i" | sed 's/[^=]*=//'`which calls twoneedless subprocesses.

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

回答by Matt J

getopt()/getopts()is a good option. Stolen from here:

getopt()/getopts()是一个不错的选择。从这里被盗:

The simple use of "getopt" is shown in this mini-script:

这个迷你脚本显示了“getopt”的简单用法:

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

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

    -*) echo "unknown option: " >&2; exit 1;;
    *) handle_argument ""; shift 1;;
  esac
done

What we have said is that any of -a, -b, -c or -d will be allowed, but that -c is followed by an argument (the "c:" says that).

If we call this "g" and try it out:

我们所说的是 -a、-b、-c 或 -d 中的任何一个都将被允许,但 -c 后跟一个参数(“c:”表示)。

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

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash 
# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key=""
    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=""
        ;;
        # 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
$@ ; 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 "" ]; do # Copy so we can modify it (can't modify ) OPT="" # 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="" 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 ( ..) eval set -- $REMAINS echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'" for i ; do echo - $i ; done

We start with two arguments, and "getopt" breaks apart the options and puts each in its own argument. It also added "--".

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

回答by bronson

At the risk of adding another example to ignore, here's my scheme.

冒着增加另一个例子被忽略的风险,这是我的计划。

  • handles -n argand --name=arg
  • allows arguments at the end
  • shows sane errors if anything is misspelled
  • compatible, doesn't use bashisms
  • readable, doesn't require maintaining state in a loop
  • 把手-n arg--name=arg
  • 最后允许参数
  • 如果有任何拼写错误,则显示正常的错误
  • 兼容,不使用 bashisms
  • 可读,不需要在循环中维护状态

Hope it's useful to someone.

希望它对某人有用。

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

回答by Shane Day

I'm about 4 years late to this question, but want to give back. I used the earlier answers as a starting point to tidy up my old adhoc param parsing. I then refactored out the following template code. It handles both long and short params, using = or space separated arguments, as well as multiple short params grouped together. Finally it re-inserts any non-param arguments back into the $1,$2.. variables. I hope it's useful.

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

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

回答by bubla

I have found the matter to write portable parsing in scripts so frustrating that I have written Argbash- a FOSS code generator that can generate the arguments-parsing code for your script plus it has some nice features:

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

https://argbash.io

https://argbash.io

回答by Ponyboy47

My answer is largely based on the answer by Bruno Bronosky, but I sort of mashed his two pure bash implementations into one that I use pretty frequently.

我的回答主要基于Bruno Bronosky 的回答,但我将他的两个纯 bash 实现混为一谈,我经常使用。

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

This allows you to have both space separated options/values, as well as equal defined values.

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

So you could run your script using:

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

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

as well as:

也:

##代码##

and both should have the same end result.

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

PROS:

优点:

  • Allows for both -arg=value and -arg value

  • Works with any arg name that you can use in bash

    • Meaning -a or -arg or --arg or -a-r-g or whatever
  • Pure bash. No need to learn/use getopt or getopts

  • 允许 -arg=value 和 -arg value

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

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

CONS:

缺点:

  • Can't combine args

    • Meaning no -abc. You must do -a -b -c
  • 无法组合 args

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

These are the only pros/cons I can think of off the top of my head

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

回答by Alek

I think this one is simple enough to use:

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

##代码##

Invocation example:

调用示例:

##代码##