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

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

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

1928133 次浏览

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

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

#!/bin/bashecho "Before getopt"for idoecho $idoneargs=`getopt abc:d $*`set -- $argsecho "After getopt"for idoecho "-->$i"done

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

如果我们称之为“G”并尝试它:

bash-2.05a$ ./g -abc fooBefore getopt-abcfooAfter getopt-->-a-->-b-->-c-->foo-->--

我们从两个参数开始,并且“getop”拆分了选项并每个人都有自己的论点。它也添加“--”。

我认为这个很容易使用:

#!/bin/bash#
readopt='getopts $opts opt;rc=$?;[ "$rc$opt" = "0?" ]&&exit 1;[ $rc = 0 ]||{ shift $[OPTIND-1];false; }'
opts=vfdo:
# Enumerating optionswhile eval "$readopt"doecho OPT:$opt ${OPTARG+OPTARG:$OPTARG}done
# Enumerating argumentsfor argdoecho ARG:$argdone

调用示例:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFileOPT:vOPT:dOPT:o OPTARG:/fizz/someOtherFileOPT:fARG:./foo/bar/someFile

digitalpeer.com开始,稍作修改:

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

#!/bin/bashfor i in "$@"docase $i in-p=*|--prefix=*)PREFIX="${i#*=}"
;;-s=*|--searchpath=*)SEARCHPATH="${i#*=}";;-l=*|--lib=*)DIR="${i#*=}";;--default)DEFAULT=YES;;*)# unknown option;;esacdoneecho PREFIX = ${PREFIX}echo SEARCH PATH = ${SEARCHPATH}echo DIRS = ${DIR}echo DEFAULT = ${DEFAULT}

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

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

cat >/tmp/demo-space-separated.sh <<'EOF'#!/bin/bash
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; docase $1 in-e|--extension)EXTENSION="$2"shift # past argumentshift # past value;;-s|--searchpath)SEARCHPATH="$2"shift # past argumentshift # past value;;--default)DEFAULT=YESshift # past argument;;-*|--*)echo "Unknown option $1"exit 1;;*)POSITIONAL_ARGS+=("$1") # save positional argshift # past argument;;esacdone
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 ]]; thenecho "Last line of file specified as non-opt/last argument:"tail -1 "$1"fiEOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc /etc/hosts
复制粘贴上面的块的输出
FILE EXTENSION  = confSEARCH PATH     = /etcDEFAULT         =Number files in SEARCH PATH with EXTENSION: 14Last 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 Equals-分隔(例如,--option=argument

cat >/tmp/demo-equals-separated.sh <<'EOF'#!/bin/bash
for i in "$@"; docase $i in-e=*|--extension=*)EXTENSION="${i#*=}"shift # past argument=value;;-s=*|--searchpath=*)SEARCHPATH="${i#*=}"shift # past argument=value;;--default)DEFAULT=YESshift # past argument with no value;;-*|--*)echo "Unknown option $i"exit 1;;*);;esacdone
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 ]]; thenecho "Last line of file specified as non-opt/last argument:"tail -1 $1fiEOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc /etc/hosts
复制粘贴上面的块的输出
FILE EXTENSION  = confSEARCH PATH     = /etcDEFAULT         =Number files in SEARCH PATH with EXTENSION: 14Last 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#*=},在本指南中搜索“子字符串删除”。它在功能上等同于`sed 's/[^=]*=//' <<< "$i"`调用不必要的子进程或`echo "$i" | sed 's/[^=]*=//'`调用两个不必要的子进程。


使用bash和getop[s]

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

  • 不能处理空字符串的参数
  • 不能处理嵌入空格的参数

最近的getopt版本没有这些限制。有关更多信息,请参阅这些文档


POSIX getopts

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

cat >/tmp/demo-getopts.sh <<'EOF'#!/bin/sh
# A POSIX variableOPTIND=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; docase "$opt" inh|\?)show_helpexit 0;;v)  verbose=1;;f)  output_file=$OPTARG;;esacdone
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,它可能会提供信息。

使用bash模块列表中的模块“参数”

示例:

#!/bin/bash. import.sh log arguments
NAME="world"
parse_arguments "-n|--name)NAME;S" -- "$@" || {error "Cannot parse command line."exit 1}
info "Hello, $NAME!"

这是我在函数中避免破坏getopts同时在堆栈中较高位置运行的方式:

function waitForWeb () {local OPTIND=1 OPTARG OPTIONlocal host=localhost port=8080 proto=httpwhile getopts "h:p:r:" OPTION; docase "$OPTION" inh)host="$OPTARG";;p)port="$OPTARG";;r)proto="$OPTARG";;esacdone...}

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

要么:

command -x=myfilename.ext --another_switch

或:

command -x myfilename.ext --another_switch

以下是代码:

STD_IN=0
prefix=""key=""value=""for keyValue in "$@"docase "${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;;esaccase $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}=";;esacdone

这也可能是有用的:您可以设置一个值,如果有人提供输入,则使用该值覆盖默认值。

myscript.sh -f ./serverlist.txt或只是./myscript.sh(它需要默认值)

    #!/bin/bash# --- set the value, if there is inputs, override the defaults.
HOME_FOLDER="${HOME}/owned_id_checker"SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"
while [[ $# > 1 ]]dokey="$1"shift    
case $key in-i|--inputlist)SERVER_FILE_LIST="$1"shift;;esacdone
    
echo "SERVER LIST   = ${SERVER_FILE_LIST}"

我用前面的答案作为起点来整理我以前的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 terminationif [ x"$OPT" = x"--" ]; thenshiftfor OPT ; doREMAINS="$REMAINS \"$OPT\""donebreakfi# Parse current optwhile [ x"$OPT" != x"-" ] ; docase "$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 optionsNEXTOPT="${OPT#-[cfr]}" # try removing single short optif [ x"$OPT" != x"$NEXTOPT" ] ; thenOPT="-$NEXTOPT"  # multiple short opts, keep goingelsebreak  # long form, exit inner loopfidone# Done with that param. move to nextshiftdone# 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

如果#1您安装了getopts,#2您打算在同一平台上运行getopts,那么getopts效果很好。OSX和Linux(例如)在这方面的行为不同。

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

./script --arg1=value1 --arg2 value2 --shouldClean
# parse the arguments.COUNTER=0ARGS=("$@")while [ $COUNTER -lt $# ]doarg=${ARGS[$COUNTER]}let COUNTER=COUNTER+1nextArg=${ARGS[$COUNTER]}
if [[ $skipNext -eq 1 ]]; thenecho "Skipping"skipNext=0continuefi
argKey=""argVal=""if [[ "$arg" =~ ^\- ]]; then# if the format is: -key=valueif [[ "$arg" =~ \= ]]; thenargVal=$(echo "$arg" | cut -d'=' -f2)argKey=$(echo "$arg" | cut -d'=' -f1)skipNext=0
# if the format is: -key valueelif [[ ! "$nextArg" =~ ^\- ]]; thenargKey="$arg"argVal="$nextArg"skipNext=1
# if the format is: -key (a boolean flag)elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; thenargKey="$arg"argVal=""skipNext=0fi# if the format has not flag, just a value.elseargKey=""argVal="$arg"skipNext=0fi
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)showUsageexit;;esacdone

没有答案展示了增强的getop。而投票最多的答案是误导性的:它忽略-⁠vfd样式的短选项(由OP请求)或位置参数之后的选项(也由OP请求);它忽略解析错误。相反:

  • 从util-linux或以前的GNU glibc使用增强的#0.1
  • 它与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都有它)。
  • 你可以测试它的存在:getopt --test→返回值4。
  • 其他getopt或shell内置getopts的用途有限。

以下呼吁

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFilemyscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFilemyscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFilemyscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfdmyscript ./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/nullif [[ ${PIPESTATUS[0]} -ne 4 ]]; thenecho 'I’m sorry, `getopt --test` failed in this environment.'exit 1fi
# option --output/-o requires 1 argumentLONGOPTS=debug,force,output:,verboseOPTIONS=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 stdoutexit 2fi# 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; docase "$1" in-d|--debug)d=yshift;;-f|--force)f=yshift;;-v|--verbose)v=yshift;;-o|--output)outFile="$2"shift 2;;--)shiftbreak;;*)echo "Programming error"exit 3;;esacdone
# handle non-option argumentsif [[ $# -ne 1 ]]; thenecho "$0: A single input file is required."exit 4fi
echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

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

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

--参数=arg(等于分隔)

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

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

可以用一个相当简洁的方法来完成:

# process flagspointer=1while [[ $pointer -le $# ]]; doparam=${!pointer}if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointerelsecase $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)):$#};fidone
# positional remainnode_name=$1ip_address=$2

--参数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 flagspointer=1while [[ $pointer -le $# ]]; doif [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointerelseparam=${!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)):$#};fidone
# positional remainnode_name=$1ip_address=$2

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

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

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

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

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

while [[ $# > 0 ]]dokey="$1"while [[ ${key+x} ]]docase $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 optionecho Unknown option: $key #1>&2exit 10 # either this: my preferred way to handle unknown optionsbreak # 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/#-?/-}"doneshift # option(s) fully processed, proceed to next input argumentdone
while [ "$#" -gt 0 ]; docase "$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;;esacdone

此解决方案:

  • 手柄-n arg--name=arg
  • 允许在末尾进行辩论
  • 如果拼写错误,则显示正常错误
  • 兼容,不使用巴什主义
  • 可读,不需要在循环中维护状态

这是我使用变量数组对Bruno Bronosky答案的改进解决方案。

它允许您混合参数位置并为您提供一个参数数组,在没有选项的情况下保留顺序

#!/bin/bash
echo $@
PARAMS=()SOFT=0SKIP=()for i in "$@"docase $i in-n=*|--skip=*)SKIP+=("${i#*=}");;-s|--soft)SOFT=1;;*)# unknown optionPARAMS+=("$i");;esacdoneecho "SKIP            = ${SKIP[@]}"echo "SOFT            = $SOFT"echo "Parameters:"echo ${PARAMS[@]}

将输出例如:

$ ./test.sh parameter -s somefile --skip=.c --skip=.objparameter -s somefile --skip=.c --skip=.objSKIP            = .c .objSOFT            = 1Parameters:parameter somefile

另一个解决方案没有getop[s],POSIX,旧的Unix风格

Bruno Bronosky发布的解决方案类似,这里没有使用getopt(s)

我的解决方案的主要区别在于它允许将选项连接在一起,就像tar -xzf foo.tar.gz等于tar -x -z -f foo.tar.gz一样。就像在tarps等中一样,前导连字符对于短选项块是可选的(但这可以很容易地更改)。也支持长选项(但当一个块以一个开头时,需要两个前导连字符)。

带有示例选项的代码

#!/bin/sh
echoecho "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"echo
print_usage() {echo "Usage:
$0 {a|b|c} [ARG...]
Options:
--aaa-0-args-aOption without arguments.
--bbb-1-args ARG-b ARGOption with one argument.
--ccc-2-args ARG1 ARG2-c ARG1 ARG2Option with two arguments.
" >&2}
if [ $# -le 0 ]; thenprint_usageexit 1fi
opt=while :; do
if [ $# -le 0 ]; then
# no parameters remaining -> end option parsingbreak
elif [ ! "$opt" ]; then
# we are at the beginning of a fresh block# remove optional leading hyphen and strip trailing whitespacesopt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')
fi
# get the first character -> check whether long optionfirst_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')[ "$first_chr" = - ] && long_option=T || long_option=F
# note to write the options here with a leading hyphen less# also do not forget to end short options with a starcase $opt in
-)
# end of optionsshiftbreak;;
a*|-aaa-0-args)
echo "Option AAA activated!";;
b*|-bbb-1-args)
if [ "$2" ]; thenecho "Option BBB with argument '$2' activated!"shiftelseecho "BBB parameters incomplete!" >&2print_usageexit 1fi;;
c*|-ccc-2-args)
if [ "$2" ] && [ "$3" ]; thenecho "Option CCC with arguments '$2' and '$3' activated!"shift 2elseecho "CCC parameters incomplete!" >&2print_usageexit 1fi;;
h*|\?*|-help)
print_usageexit 0;;
*)
if [ "$long_option" = T ]; thenopt=$(echo "$opt" | awk '{print substr($1, 2)}')elseopt=$first_chrfiprintf 'Error: Unknown option: "%s"\n' "$opt" >&2print_usageexit 1;;
esac
if [ "$long_option" = T ]; then
# if we had a long option then we are going to get a new block nextshiftopt=
else
# if we had a short option then just move to the next characteropt=$(echo "$opt" | awk '{print substr($1, 2)}')
# if block is now empty then shift to the next one[ "$opt" ] || shift
fi
done
echo "Doing something..."
exit 0

有关示例用法,请参阅下面的示例。

带有参数的选项的位置

因此,例如在tar(至少在某些实现中)中,f选项需要放在最后,因为文件名紧随其后(tar xzf bar.tar.gz有效,但tar xfz bar.tar.gz无效)这里不是这种情况(参见后面的示例)。

带有参数的多个选项

作为另一个好处,选项参数按选项的顺序由具有所需选项的参数消耗。只需使用命令行abc X Y Z(或-abc X Y Z)查看我脚本的输出:

Option AAA activated!Option BBB with argument 'X' activated!Option CCC with arguments 'Y' and 'Z' activated!

长选项也连接在一起

此外,您还可以在选项块中拥有长选项,因为它们在块中最后出现。因此,以下命令行都是等效的(包括处理选项及其参数的顺序):

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

所有这些导致:

Option CCC with arguments 'Z' and 'Y' activated!Option BBB with argument 'X' activated!Option AAA activated!Doing something...

在这个溶液里没有

可选参数

带有可选参数的选项应该可以通过一些工作来实现,例如通过期待是否有没有连字符的块;然后用户需要在带有可选参数的块后面的每个块前面放一个连字符。也许这太复杂了,无法与用户沟通,所以在这种情况下最好只需要一个前导连字符。

有了多个可能的参数,事情会变得更加复杂。我建议不要通过确定一个参数是否支持它来使选项变得聪明(例如,一个选项只是将一个数字作为一个可选参数),因为这可能会在未来崩溃。

我个人倾向于附加选项而不是可选参数。

用等号引入的选项参数

就像可选参数一样,我不喜欢这个(顺便说一句,有没有一个讨论不同参数风格优缺点的线程?)但是如果你想要这个,你可能可以自己实现它,就像在http://mywiki.wooledge.org/BashFAQ/035#Manual_loop中用--long-with-arg=?* case语句完成的那样,然后剥离等号(顺便说一句,这个网站说,通过一些努力可以实现参数连接,但是“把[它]作为读者的练习”,这让我接受了他们的话,但我从头开始)。

其他备注

POSIX兼容,即使在我必须处理的古老Busybox设置上也能工作(例如缺少cutheadgetopts)。

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

它创建于1984年,但在1986年被埋葬,因为它不能真正使用。

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

顺便说一句:如果您对解析shell脚本中的长选项感兴趣,可能会有兴趣知道libc(Solaris)的getopt(3)实现和ksh93都添加了一个统一的长选项实现,支持长选项作为短选项的别名。这导致ksh93Bourne Shell通过getopts实现长选项的统一接口。

来自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中getop(3)的手册页:

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

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

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

deploy.sh

#!/bin/bash
while [[ "$#" -gt 0 ]]; docase $1 in-t|--target) target="$2"; shift ;;-u|--uglify) uglify=1 ;;*) echo "Unknown parameter passed: $1"; exit 1 ;;esacshiftdone
echo "Where to deploy: $target"echo "Should uglify  : $uglify"

用法:

./deploy.sh -t dev -u
# OR:
./deploy.sh --target dev --uglify

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

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

下面的脚本是一个复制粘贴工作演示。请参阅show_use函数以了解如何使用parse_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_namedlocal ARGV=() # un-named paramslocal ARGN=() # named paramslocal 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 parameternonspace="[^[:space:]]"if [[ "$1" =~ ^${nonspace}${nonspace}*=..* ]]; then# Add to named parameters arrayecho "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 spaceif [[ "$_key" == "" || "$_key" =~ " " ]]; thenecho "ARGV+=('$_escaped');"shiftcontinuefi# 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 nameif (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 neededif ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); thenecho "$_key=(\"\$$_key\");"fi# append new valueecho "$_key+=('$_val');"else# single-value named parameterecho "local $_key='$_val';"existing_named=" $_key"fi# If standalone named parameterelif [[ "$1" =~ ^\-${nonspace}+ ]]; then# remove dasheslocal _key=${1//\-}# Just add as non-named when key is empty or contains spaceif [[ "$_key" == "" || "$_key" =~ " " ]]; thenecho "ARGV+=('$_escaped');"shiftcontinuefi# Add to options arrayecho "ARGO+=('$_escaped');"echo "local $_key=\"$_key\";"# non-named parameterelse# Escape asterisk to prevent bash asterisk expansion_escaped=${1/\*/\'\"*\"\'}echo "ARGV+=('$_escaped');"fishiftdone}
#--------------------------- DEMO OF THE USAGE -------------------------------
show_use (){eval $(parse_params "$@")# --echo "${ARGV[0]}" # print first unnamed paramecho "${ARGV[1]}" # print second unnamed paramecho "${ARGN[0]}" # print first named paramecho "${ARG0[0]}" # print first option param (--force)echo "$anyparam"  # print --anyparam valueecho "$k"         # print k=5 valueecho "${multivalue[0]}" # print first value of multi-valueecho "${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

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

https://argbash.io

当我尝试时,这个问题的最高答案似乎有点错误-这是我的解决方案,我发现它更强大:

boolean_arg=""arg_with_value=""
while [[ $# -gt 0 ]]dokey="$1"case $key in-b|--boolean-arg)boolean_arg=trueshift;;-a|--arg-with-value)arg_with_value="$2"shiftshift;;-*)echo "Unknown option: $1"exit 1;;*)arg_num=$(( $arg_num + 1 ))case $arg_num in1)first_normal_arg="$1"shift;;2)second_normal_arg="$1"shift;;*)bad_args=TRUEesac;;esacdone
# Handy to have this here when adding arguments to# see if they're working. Just edit the '0' to be '1'.if [[ 0 == 1 ]]; thenecho "first_normal_arg: $first_normal_arg"echo "second_normal_arg: $second_normal_arg"echo "boolean_arg: $boolean_arg"echo "arg_with_value: $arg_with_value"exit 0fi
if [[ $bad_args == TRUE || $arg_num < 2 ]]; thenecho "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"exit 1fi

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

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

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

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

./myscript -flag flagvariable -otherflag flagvar2echo $flag $otherflagflagvariable flagvar2

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

#!/usr/bin/env bash#shebang.iovalidflags="rate time number"count=1for arg in $@domatch=0argval=$1for flag in $validflagsdosflag="-"$flagif [ "$argval" == "$sflag" ]thendeclare $flag=$2match=1fidoneif [ "$match" == "1" ]thenshift 2elseleftovers=$(echo $leftovers $argval)shiftficount=$(($count+1))done#Cleanup then restore the leftoversshift $#set -- $leftovers

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

#!/usr/bin/env bash#shebang.iorate=30time=30number=30echo "all args$@"validflags="rate time number"count=1for arg in $@domatch=0argval=$1#   argval=$(echo $@ | cut -d ' ' -f$count)for flag in $validflagsdosflag="-"$flagif [ "$argval" == "$sflag" ]thendeclare $flag=$2match=1fidoneif [ "$match" == "1" ]thenshift 2elseleftovers=$(echo $leftovers $argval)shiftficount=$(($count+1))done
#Cleanup then restore the leftoversecho "pre final clear args:$@"shift $#echo "post final clear args:$@"set -- $leftoversecho "all post set args:$@"echo arg1: $1 arg2: $2
echo leftovers: $leftoversecho rate $rate time $time number $number

最后一个,如果传递无效参数,则此错误。

#!/usr/bin/env bash#shebang.iorate=30time=30number=30validflags="rate time number"count=1for arg in $@doargval=$1match=0if [ "${argval:0:1}" == "-" ]thenfor flag in $validflagsdosflag="-"$flagif [ "$argval" == "$sflag" ]thendeclare $flag=$2match=1fidoneif [ "$match" == "0" ]thenecho "Bad argument: $argval"exit 1fishift 2elseleftovers=$(echo $leftovers $argval)shiftficount=$(($count+1))done#Cleanup then restore the leftoversshift $#set -- $leftoversecho rate $rate time $time number $numberecho leftovers: $leftovers

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

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

这个例子展示了如何使用getopteval以及HEREDOCshift来处理短参数和长参数,后面有一个必需的值和没有一个必需的值。

#!/usr/bin/env bash
# usage functionfunction 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 variablesprogname=$(basename $0)verbose=0dryrun=0num_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 parameterOPTS=$(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 ; fieval 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 ;;esacdone
if (( $verbose > 0 )); then
# print out all the parameters we read incat <<EOMnum=$num_strtime=$time_strverbose=$verbosedryrun=$dryrunEOMfi
# 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 ; fieval set -- "$OPTS"
while true; docase "$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 ;;esacdone

简明扼要,可读性强,几乎可以处理所有内容(IMHO)。

希望这有助于某人。

# As long as there is at least one more argument, keep loopingwhile [[ $# -gt 0 ]]; dokey="$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 valueOUTPUTFILE="$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 stringOUTPUTFILE="${key#*=}";;*)# Do whatever you want with extra optionsecho "Unknown option '$key'";;esac# Shift after checking all the cases to get the next optionshiftdone

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

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

./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或其他什么
  • 纯粹的bash。无需学习/使用getopts或getopts

缺点:

  • 不能合并参数

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

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

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

例子:

#!/bin/bash -ei
# load the library. bashopts.sh
# Enable backtrace dusplay on errortrap 'bashopts_exit_handle' ERR
# Initialize the librarybashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"
# Declare the optionsbashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -rbashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -rbashopts_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 numberbashopts_declare -n email_list -t string -m add -l email -d "Email adress"
# Parse argumentsbashopts_parse_args "$@"
# Process argumentbashopts_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:"")

享受:)

这是我的方法-使用regexp。

  • 没有getopts
  • 它处理短参数块-qwerty
  • 它处理短参数-q -w -e
  • 它处理长选项--qwerty
  • 您可以将属性传递给短选项或长选项(如果您使用短选项块,则属性附加到最后一个选项)
  • 您可以使用空格或=来提供属性,但属性匹配直到遇到连字符+空格“分隔符”,因此在--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, --helpdisplay this help and exit
-l, --logfile=FILENAMEfilename
-d, --debugenable debug"}
parse_options() {case $opt inh|help)help_menuexit;;l|logfile)logfile=${attr};;d|debug)debug=true;;*)echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2exit 1esac}options=$@
until [ "$options" = "" ]; doif [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; thenif [[ ${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 )); doopt=${pile:0:1}attr=""pile=${pile/${pile:0:1}/}parse_optionsdoneopt=$pileattr=${BASH_REMATCH[7]}options=${BASH_REMATCH[9]}else # leftovers that don't matchopt=${BASH_REMATCH[10]}options=""fiparse_optionsfidone

假设我们创建一个名为test_args.sh的外壳脚本,如下所示

#!/bin/shuntil [ $# -eq 0 ]doname=${1:1}; shift;if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fidoneecho "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

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

source argparser.shparse_args "$@"

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

简单,易于修改,参数可以在任何顺序。这可以被修改为采取任何形式的参数(-a,--a,a等)。

for arg in "$@"dokey=$(echo $arg | cut -f1 -d=)`value=$(echo $arg | cut -f2 -d=)`case "$key" inname|-name)      read_name=$value;;id|-id)          read_id=$value;;*)               echo "I dont know what to do with this"easedone

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

  • --longopt=val扩展为--longopt val
  • -xyz扩展为-x -y -z
  • 支持--表示标志的结束
  • 显示意外选项的错误
  • 紧凑且易于阅读的选项开关
#!/bin/bash
# Report usageusage() {echo "Usage:"echo "$(basename "$0") [options] [--] [file1, ...]"}
invalid() {echo "ERROR: Unrecognized argument: $1" >&2usageexit 1}
# Pre-process options to:# - expand -xyz into -x -y -z# - expand --longopt=arg into --longopt argARGV=()END_OF_OPT=while [[ $# -gt 0 ]]; doarg="$1"; shiftcase "${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") ;;esacdone
# Apply pre-processed optionsset -- "${ARGV[@]}"
# Parse optionsEND_OF_OPT=POSITIONAL=()while [[ $# -gt 0 ]]; docase "${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") ;;esacshiftdone
# Restore positional parametersset -- "${POSITIONAL[@]}"

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

基本上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 optionargs+=("$1"); shift;                        # Past argument only;;esacdone; set -- "${backup[@]}"                                 # Restore $@

echo "${sourceurl}"}

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

称为:

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

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

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

  • 是便携式的!
  • 零依赖,只依赖bash内置
  • 允许短期和长期选择
  • 处理空白或同时在选项和参数之间使用=分隔符
  • 支持串联短选项样式-vxf
  • 处理带有可选参数的选项(例如--color vs--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 <<EOFNAMEprogram-name.sh - Brief description 
SYNOPSISprogram-name.sh [-h|--help]program-name.sh [-f|--foo][-b|--bar <arg>][--baz[=<arg>]][--]FILE ...
REQUIRED ARGUMENTSFILE ...input files
OPTIONS-h, --helpPrints this and exits
-f, --fooA 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 optionargument starts with a hyphen
EOF}
fatal() {for i; doecho -e "${i}" >&2doneexit 1}
# For long option processingnext_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 '-' characteroptspec=":hfb:-:"args=("" "$@")  # dummy first element so $1 and $args[1] are alignedwhile getopts "$optspec" optchar; docase "$optchar" inh) usage; exit 0 ;;f) foo=1 ;;b) bar="$OPTARG" ;;-) # long option processingcase "$OPTARG" inhelp)usage; exit 0 ;;foo)foo=1 ;;bar|bar=*) next_argbar="$OPTARG" ;;baz)baz=DEFAULT ;;baz=*) next_argbaz="$OPTARG" ;;-) break ;;*) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;esac;;*) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;esacdone
shift $((OPTIND-1))
if [ "$#" -eq 0 ]; thenfatal "Expected at least one required argument FILE" \"See '${0} --help' for usage"fi
echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"

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

这支持:

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

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

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

-e不带参数
-c需要两个参数,即如何切割和为什么切割
fruit本身至少需要一个参数。
<command>是子选项,如appleorange等(类似于git,子选项commitpush等)

所以要解析它:

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 usageprint_usage 'fruit'exit 1fi

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

if [ -n "${OPTIONS[-c]}" ]thenecho "-c was passed"
# args can be accessed in a 2D-array-like formatecho "Arg1 to -c = ${ARGS[-c,0]}"echo "Arg2 to -c = ${ARGS[-c,1]}"
fi

子选项解析也可以通过将$shift_count传递给parse_options_detailed来完成,这使得它在移动参数以到达子选项的参数后开始解析。这在示例中得到了演示。

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

另一个Shell参数解析器(ASAP)

POSIX兼容,无getopt(s)

我受到相对简单的由@布朗森回答的启发,并试图改进它(不增加太多复杂性)。结果如下:

  • 使用任何-n [arg]-abn [arg]--name [arg]--name=arg样式的选项;
  • 参数可以以任何顺序出现,只有位置的留在$@在循环之后;
  • 使用--以迫使剩余的参数被视为位置;
  • 检测无效选项和缺少参数;
  • 不依赖于getopt(s)或外部工具(一个功能使用简单的sed命令);
  • 便携,紧凑,可读性强,具有独立功能
# Convenience functions.usage_error () { echo >&2 "$(basename $0):  $1"; exit 2; }assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; }
# One loop, nothing more.if [ "$#" != 0 ]; thenEOL=$(printf '\1\3\3\7')set -- "$@" "$EOL"while [ "$1" != "$EOL" ]; doopt="$1"; shiftcase "$opt" in
# Your options go here.-f|--flag) flag='true';;-n|--name) assert_argument "$1" "$opt"; name="$1"; shift;;
# Arguments processing. You may remove any unneeded line after the 1st.-|''|[!-]*) set -- "$@" "$opt";;                                          # positional argument, rotate to the end--*=*)      set -- "${opt%%=*}" "${opt#*=}" "$@";;                        # convert '--name=arg' to '--name' 'arg'-[!-]?*)    set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@";;       # convert '-abc' to '-a' '-b' '-c'--)         while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;;  # process remaining arguments as positional-*)         usage_error "unknown option: '$opt'";;                        # catch misspelled options*)          usage_error "this should NEVER happen ($opt)";;               # sanity test for previous patterns
esacdoneshift  # $EOLfi
# Do something cool with "$@"... \o/

备注:我知道…二进制模式0x01030307的参数可能会破坏逻辑。但是,如果有人在命令行中传递这样的参数,那是他们应得的。

我写了一个脚本,可以帮助轻松解析命令行参数-https://github.com/unfor19/bargs

示例

$ bash example.sh -n Willy --gender male -a 99Name:      WillyAge:       99Gender:    maleLocation:  chocolate-factory
$ bash example.sh -n Meir --gender male[ERROR] Required argument: age
Usage: bash example.sh -n Willy --gender male -a 99
--person_name  |  -n  [Willy]              What is your name?--age          |  -a  [Required]--gender       |  -g  [Required]--location     |  -l  [chocolate-factory]  insert your location
$ bash example.sh -h
Usage: bash example.sh -n Willy --gender male -a 99--person_name  |  -n  [Willy]              What is your name?--age          |  -a  [Required]--gender       |  -g  [Required]--location     |  -l  [chocolate-factory]  insert your location

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

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

getOptions是一个用符合POSIX的外壳脚本编写的新选项解析器(生成器),并于2020年8月发布。它适用于那些想要在shell脚本中支持POSIX/GNU样式选项语法的人。

支持的语法有-a+a-abc-vvv-p VALUE-pVALUE--flag--no-flag--with-flag--without-flag+a0、+a1、+a2、+a3+a4。

它支持子命令、验证、缩写选项和自动帮助生成。并适用于所有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 --helpdisp    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 ]; docase $1 in--?*=*) OPTARG=$1; shifteval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'};;--no-*|--without-*) unset OPTARG ;;-[po]?*) OPTARG=$1; shifteval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'};;-[fh]?*) OPTARG=$1; shifteval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'}OPTARG= ;;esaccase $1 in'-f'|'--flag')[ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && breakeval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''FLAG="$OPTARG";;'-p'|'--param')[ $# -le 1 ] && set "required" "$1" && breakOPTARG=$2PARAM="$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')usageexit 0 ;;'--version')echo "${VERSION}"exit 0 ;;--)shiftwhile [ $# -gt 0 ]; doREST="${REST} \"\${$(($OPTIND-$#))}\""shiftdonebreak ;;[-]?*) set "unknown" "$1"; break ;;*)REST="${REST} \"\${$(($OPTIND-$#))}\""esacshiftdone[ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }case $1 inunknown) 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" "$@"esacecho "$1" >&2exit 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--versionGETOPTIONSHERE}

我用它从末尾迭代key=>value。循环后捕获了第一个可选参数。

用法是./script.sh可选第一参数-key value-key2 value e2

#!/bin/sh
a=$(($#-1))b=$(($#))while [ $a -gt 0 ]; doeval 'key="$'$a'"; value="$'$b'"'echo "$key => $value"b=$(($b-2))a=$(($a-2))doneunset a b key value
[ $(($#%2)) -ne 0 ] && echo "first_arg = $1"

当然,你可以从左到右做一些改变。

这段代码片段显示key=>value对和第一个参数(如果存在)。

#!/bin/sh
a=$((1+$#%2))b=$((1+$a))
[ $(($#%2)) -ne 0 ] && echo "first_arg = $1"
while [ $a -lt $# ]; doeval 'key="$'$a'"; value="$'$b'"'echo "$key => $value"b=$(($b+2))a=$(($a+2))done
unset a b key value

测试了100,000个参数,速度很快。

您还可以在没有ava的情况下从左到右迭代key=>value第一可选参数

#!/bin/sh
a=$(($#%2))b=0
[ $a -eq 1 ] && echo "first_arg = $1"
for value; doif [ $b -gt $a -a $(($b%2)) -ne $a ]; thenecho "$key => $value"fikey="$value"b=$((1+$b))done
unset a b key value

我最终实现了接受的答案dash(或/bin/sh)版本,基本上没有数组使用:

while [[ $# -gt 0 ]]; docase "$1" in-v|--verbose) verbose=1; shift;;-o|--output) if [[ $# -gt 1 && "$2" != -* ]]; thenfile=$2; shift 2elseecho "-o requires file-path" 1>&2; exit 1fi ;;--)while [[ $# -gt 0 ]]; do BACKUP="$BACKUP;$1"; shift; donebreak;;*)BACKUP="$BACKUP;$1"shift;;esacdone# Restore unused arguments.while [ -n "$BACKUP" ] ; do[ ! -z "${BACKUP%%;*}" ] && set -- "$@" "${BACKUP%%;*}"[ "$BACKUP" = "${BACKUP/;/}" ] && breakBACKUP="${BACKUP#*;}"done

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

#!/bin/bashset -e
function parse() {for arg in "$@"; do # transform long options to short onesshiftcase "$arg" in"--name") set -- "$@" "-n" ;;"--verbose") set -- "$@" "-v" ;;*) set -- "$@" "$arg"esacdone
while getopts "n:v" optname  # left to ":" are flags that expect a value, right to the ":" are flags that expect nothingdocase "$optname" in"n") name=${OPTARG} ;;"v") verbose=true ;;esacdoneshift "$((OPTIND-1))" # shift out all the already processed options}

parse "$@"echo "hello $name"if [ ! -z $verbose ]; then echo 'nice to meet you!'; fi

用法:

$ ./parse.shhello$ ./parse.sh -n YOUR_NAMEhello YOUR_NAME$ ./parse.sh -n YOUR_NAME -vhello YOUR_NAMEnice to meet you!$ ./parse.sh -v -n YOUR_NAMEhello YOUR_NAMEnice to meet you!$ ./parse.sh -vhellonice to meet you!

我使用optgetoptgets的组合来解析带或不带参数的短选项和长选项,甚至是非选项(没有---的选项):

# catch wrong options and move non-options to the end of the stringargs=$(getopt -l "$opt_long" "$opt_short" "$@" 2> >(sed -e 's/^/stderr/g')) || echo -n "Error: " && echo "$args" | grep -oP "(?<=^stderr).*" && exit 1mapfile -t args < <(xargs -n1 <<< "$(echo "$args" | sed -E "s/(--[^ ]+) /\1=/g")" )set -- "${args[@]}"
# parse short and long optionswhile getopts "$opt_short-:" opt; do...done
# remove all parsed options from $@shift $((OPTIND-1)

因此,我可以使用像$opt_verbose这样的变量访问所有选项,而非选项可以通过默认变量$1$2等访问:

echo "help:$opt_help"echo "file:$opt_file"echo "verbose:$opt_verbose"echo "long_only:$opt_long_only"echo "short_only:$opt_s"echo "path:$1"echo "mail:$2"

其中一个主要功能是,我能够以完全随机的顺序传递所有选项和非选项:

#             $opt_file     $1        $2          $opt_... $opt_... $opt_...# /demo.sh --file=file.txt /dir info@example.com -V -h --long_only=yes -shelp:1file:file.txtverbose:1long_only:yesshort_only:1path:/dirmail:info@example.com

更多详情:https://stackoverflow.com/a/74275254/318765