How do I parse command line arguments in Bash?
比如,我有一个脚本可以用这行来调用:
1 | ./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile |
或者这个:
1 | ./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile |
在每种情况下(或两种情况的某种组合)
方法1:使用不带getopt▼显示的bash
传递键值对参数的两种常见方法是:
bash空格分隔(例如,用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #!/bin/bash POSITIONAL=() while [[ $# -gt 0 ]] do key="$1" case $key in -e|--extension) EXTENSION="$2" shift # past argument shift # past value ;; -s|--searchpath) SEARCHPATH="$2" shift # past argument shift # past value ;; -l|--lib) LIBPATH="$2" shift # past argument shift # past value ;; --default) DEFAULT=YES shift # past argument ;; *) # unknown option POSITIONAL+=("$1") # save it in an array for later shift # past argument ;; esac done set --"${POSITIONAL[@]}" # restore positional parameters echo FILE EXTENSION ="${EXTENSION}" echo SEARCH PATH ="${SEARCHPATH}" echo LIBRARY PATH ="${LIBPATH}" echo DEFAULT ="${DEFAULT}" echo"Number files in SEARCH PATH with EXTENSION:" $(ls -1"${SEARCHPATH}"/*."${EXTENSION}" | wc -l) if [[ -n $1 ]]; then echo"Last line of file specified as non-opt/last argument:" tail -1"$1" fi |
bash等于separated(例如,
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #!/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"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 |
为了更好地理解
发件人:http://mywiki.wooledge.org/bashfaq/035 getopts
getopt(1)限制(较旧、相对较新的
- 无法处理空字符串的参数
- 无法处理嵌入空白的参数
较新的
另外,posix shell(和其他)提供的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/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: $@" # End of file |
有一个getopts教程解释了所有语法和变量的含义。在bash中,还有
没有答案提到增强型getopt。最热门的答案是误导性的:它忽略了
- 从Util Linux或以前的GNU glibc中使用增强的
getopt 。1 - 它与gnu glibc的c函数
getopt_long() 一起工作。 - 具有所有有用的区别特征(其他的没有):
- 处理参数2中的空格、引用字符甚至二进制
- 最后可以处理选项:
script.sh -o outFile file1 file2 -v 。 - 允许
= 样式的长选项:script.sh --outfile=fileOut --infile fileIn 。
- 已经很老了,没有GNU系统缺少这个(例如,任何Linux都有)。
- 您可以使用:
getopt --test →返回值4来测试它的存在性。 - 其他
getopt 或外壳内置的getopts 的用途有限。
以下电话
1 2 3 4 5 | 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 |
全部返回
1 | verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile |
以下为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | #!/bin/bash # saner programming env: these switches turn some bugs into errors set -o errexit -o pipefail -o noclobber -o nounset ! 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 # -use ! and PIPESTATUS to get exit code with errexit set # -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" |
1enhanced getopt可用于大多数"bash系统",包括cygwin;on os x try brew install gnu getopt or
发件人:digitalpeer.com,稍作修改
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #!/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} |
为了更好地理解
The simple use of"getopt" is shown in this mini-script:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/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 |
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:
1 2 3 4 5 6 7 8 9 10 | bash-2.05a$ ./g -abc foo Before getopt -abc foo After getopt -->-a -->-b -->-c -->foo -->-- |
We start with two arguments, and
"getopt" breaks apart the options and
puts each in its own argument. It also
added"--".
冒着添加另一个可以忽略的示例的风险,这里是我的方案。
- 处理
-n arg 和--name=arg 。 - 允许在结尾使用参数
- 如果有拼写错误,则显示正常错误
- 兼容,不使用bashims
- 可读,不需要在循环中保持状态
希望对某人有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
更简洁的方式
脚本
1 2 3 4 5 6 7 8 9 10 | #!/bin/bash while [["$#" -gt 0 ]]; do case $1 in -d|--deploy) deploy="$2"; shift;; -u|--uglify) uglify=1;; *) echo"Unknown parameter passed: $1"; exit 1;; esac; shift; done echo"Should deploy? $deploy" echo"Should uglify? $uglify" |
用途:
1 2 3 4 5 | ./script.sh -d dev -u # OR: ./script.sh --deploy dev --uglify |
这个问题我晚了4年,但我想还钱。我把前面的答案作为一个起点来整理我以前的特殊参数解析。然后我重构出以下模板代码。它使用=或空格分隔的参数处理长参数和短参数,以及组合在一起的多个短参数。最后,它将任何非参数参数重新插入$1,$2..变量。希望它有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | #!/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: configfile='$CONFIGFILE' force='$FORCE' retry='$RETRY' remains='$REMAINS'" for i ; do echo - $i ; done |
我的答案很大程度上是基于BrunoBronosky的答案,但是我把他的两个纯bash实现混搭成了一个我经常使用的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # 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 |
这允许您同时具有空格分隔的选项/值以及相等的定义值。
因此,可以使用以下命令运行脚本:
1 | ./myscript --foo -b -o /fizz/file.txt |
以及:
1 | ./myscript -f --bar -o=/fizz/file.txt |
两者的最终结果应该相同。
赞成的意见:
同时允许-arg=value和-arg值
使用任何可以在bash中使用的arg名称
- 意思是-a或-a r g或-a r g或-a-r-g或其他
纯粹的狂欢。无需学习/使用getopt或getopts
欺骗:
无法组合参数
- 意思是不-ABC。你必须做-A-B-C
这些是我能想到的唯一的利弊
我发现在脚本中编写可移植解析的问题非常令人沮丧,以至于我编写了argbash——一个可以为脚本生成参数解析代码的FOSS代码生成器,它还有一些好的特性:
https://argbash.io网站
我觉得这个很简单,可以使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #!/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 |
调用示例:
1 2 3 4 5 6 | ./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile OPT:v OPT:d OPT:o OPTARG:/fizz/someOtherFile OPT:f ARG:./foo/bar/someFile |
扩展@guneysus的优秀答案,这里有一个调整,允许用户使用他们喜欢的任何语法,例如
1 | command -x=myfilename.ext --another_switch |
VS
1 | command -x myfilename.ext --another_switch |
也就是说,等号可以用空格代替。
这种"模糊解释"可能不符合您的喜好,但是如果您正在制作可与其他实用程序互换的脚本(就像我的脚本一样,它必须与ffmpeg一起工作),那么灵活性是有用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 |
如果您安装了getopts,并且打算在同一平台上运行它,那么getopts工作得很好。在这方面,OSX和Linux(例如)表现不同。
这里有一个(非getopts)解决方案,它支持equals、non equals和boolean标志。例如,您可以这样运行脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | ./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 |
我给您提供了函数
下面的脚本是一个复制粘贴工作演示。参见
局限性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #!/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//"/\\\ <div class="suo-content">[collapse title=""]<ul><li>要使用演示来解析bash脚本中的参数,只需执行<wyn>show_use"$@"</wyn>。</li><li>基本上,我发现github.com/renatosilva/easyoptions以相同的方式执行相同的操作,但比这个函数要大一些。</li></ul>[/collapse]</div><hr> <p> EasyOptions does not require any parsing: </p> [cc lang="bash"]## Options: ## --verbose, -v Verbose mode ## --output=FILE Output filename source easyoptions || exit if test -n"${verbose}"; then echo"output file is ${output}" echo"${arguments[@]}" fi |
这是我在函数中的操作方式,以避免在堆栈的较高位置同时中断getopts的运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 ... } |
请注意,EDOCX1[0]是AT&T的一个短暂的错误。
Getopt创建于1984年,但由于它实际上不可用,已于1986年埋葬。
证明
顺便说一句:如果您对分析shell脚本中的长选项感兴趣,那么您可能会感兴趣的是,libc(solaris)和
Bourne Shell手册页上的长选项示例:
显示BourneShell和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网站
我想提供我的选项解析版本,它允许以下内容:
1 2 3 4 5 6 | -s p1 --stage p1 -w somefolder --workfolder somefolder -sw p1 somefolder -e=hello |
还允许这样做(可能是不需要的):
1 2 3 | -s--workfolder p1 somefolder -se=hello p1 -swe=hello p1 somefolder |
在使用之前,您必须决定是否在选项上使用if=这是为了保持代码的整洁(ish)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 2 3 4 5 6 7 | #!/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" |
运行以下命令后:
1 | sh test_args.sh -year 2017 -flag -month 12 -day 22 |
输出将是:
1 | year=2017 month=12 day=22 flag=true |
保留未处理参数的解决方案。包括演示。
这是我的解决方案。它非常灵活,不像其他软件包,不应该需要外部包,并且干净地处理剩余的参数。
用法为:
你所要做的就是编辑validFlags行。它加上连字符并搜索所有参数。然后它将下一个参数定义为标志名,例如
1 2 3 | ./myscript -flag flagvariable -otherflag flagvar2 echo $flag $otherflag flagvariable flagvar2 |
主代码(简短的版本、详细的示例以及出错的版本):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/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演示的详细版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #!/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 |
最后一个,如果传递了一个无效参数,这个参数就会出错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #!/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将作为单个参数处理。不过,您可以很容易地在我的代码中编写额外的代码来添加这个功能。
我已经编写了一个bash助手来编写一个好的bash工具
项目主页:https://gitlab.mbedsys.org/mbedsys/bashopts
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #!/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 |
将给予帮助:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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:"") |
享受:
混合基于位置和标志的参数--参数=参数(等于分隔符)
在位置参数之间自由混合标志:
1 2 | ./script.sh dumbo 127.0.0.1 --environment=production -q -d ./script.sh dumbo --environment=production 127.0.0.1 --quiet -d |
可以用相当简洁的方法完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | # 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 |
--参数参数(空格分隔)
通常情况下,不混合
1 | ./script.sh dumbo 127.0.0.1 --environment production -q -d |
这有点冒险,但仍然有效
1 | ./script.sh dumbo --environment production 127.0.0.1 --quiet -d |
来源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # 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 |
这是我的方法-使用regexp。
- 没有胜利者
- 它处理短参数
-qwerty 块。 - 它处理短参数
-q -w -e 。 - 它处理长期权
--qwerty 。 - 可以将属性传递给短选项或长选项(如果使用短选项块,则属性将附加到最后一个选项)
- 您可以使用空格或
= 提供属性,但属性匹配直到遇到连字符+空格"分隔符",因此在--q=qwe ty qwe ty 中是一个属性。 - 它处理以上所有的混合,所以
-o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute 是有效的
脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | #!/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} Run ${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 |
我想提交我的项目:https://github.com/flyingangel/argparser
1 2 | source argparser.sh parse_args"$@" |
就这么简单。将使用与参数同名的变量填充环境。
使用bash模块中的模块"参数"
例子:
1 2 3 4 5 6 7 8 9 10 11 | #!/bin/bash . import.sh log arguments NAME="world" parse_arguments"-n|--name)NAME;S" --"$@" || { error"Cannot parse command line." exit 1 } info"Hello, $NAME!" |
这也可能有助于了解,您可以设置一个值,如果有人提供输入,则使用该值覆盖默认值。
myscript.sh-f./serverlist.txt或仅/myscript.sh(采用默认值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #!/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 ]] do key="$1" shift case $key in -i|--inputlist) SERVER_FILE_LIST="$1" shift ;; esac done echo"SERVER LIST = ${SERVER_FILE_LIST}" |
另一个没有getopt▼显示、posix和旧的unix风格的解决方案
与布鲁诺·布朗斯基发布的解决方案类似,这里没有使用
我的解决方案的主要区别在于,它允许选项串联在一起,就像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | #!/bin/sh echo echo"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 -a Option without arguments. --bbb-1-args ARG -b ARG Option with one argument. --ccc-2-args ARG1 ARG2 -c ARG1 ARG2 Option with two arguments. ">&2 } if [ $# -le 0 ]; then print_usage exit 1 fi opt= while :; do if [ $# -le 0 ]; then # no parameters remaining -> end option parsing break elif [ !"$opt" ]; then # we are at the beginning of a fresh block # remove optional leading hyphen and strip trailing whitespaces opt=$(echo"$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/') fi # get the first character -> check whether long option first_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 star case $opt in -) # end of options shift break ;; a*|-aaa-0-args) echo"Option AAA activated!" ;; b*|-bbb-1-args) if ["$2" ]; then echo"Option BBB with argument '$2' activated!" shift else echo"BBB parameters incomplete!">&2 print_usage exit 1 fi ;; c*|-ccc-2-args) if ["$2" ] && ["$3" ]; then echo"Option CCC with arguments '$2' and '$3' activated!" shift 2 else echo"CCC parameters incomplete!">&2 print_usage exit 1 fi ;; h*|\?*|-help) print_usage exit 0 ;; *) if ["$long_option" = T ]; then opt=$(echo"$opt" | awk '{print substr($1, 2)}') else opt=$first_chr fi printf 'Error: Unknown option:"%s" '"$opt">&2 print_usage exit 1 ;; esac if ["$long_option" = T ]; then # if we had a long option then we are going to get a new block next shift opt= else # if we had a short option then just move to the next character opt=$(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 |
有关示例用法,请参阅下面的示例。
带参数的选项位置就其价值而言,带有参数的选项不是最后一个(只需要长选项)。因此,尽管例如在EDOCX1(至少在某些实现中)中,
作为另一个奖励,选项参数按选项的顺序由具有所需选项的参数使用。只需查看命令行
1 2 3 | 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
所有这些都会导致:
1 2 3 4 | 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循环中使用
与posix兼容,即使是在我必须处理的古代busybox设置上(例如,
扩展@bruno bronosky的答案,我添加了一个"预处理器"来处理一些常见的格式:
- 将
--longopt=val 扩展为--longopt val 。 - 将
-xyz 扩展为-x -y -z 。 - 支持
-- 表示标志结束 - 显示意外选项的错误
- 紧凑易读的选项开关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #!/bin/bash # Report usage usage() { echo"Usage:" echo"$(basename $0) [options] [--] [file1, ...]" # Optionally exit with a status code if [ -n"$1" ]; then exit"$1" fi } invalid() { echo"ERROR: Unrecognized argument: $1">&2 usage 1 } # Pre-process options to: # - expand -xyz into -x -y -z # - expand --longopt=arg into --longopt arg ARGV=() END_OF_OPT= while [[ $# -gt 0 ]]; do arg="$1"; shift case"${END_OF_OPT}${arg}" in --) ARGV+=("$arg"); END_OF_OPT=1 ;; --*=*)ARGV+=("${arg%%=*}""${arg#*=}") ;; --*) ARGV+=("$arg"); END_OF_OPT=1 ;; -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;; *) ARGV+=("$arg") ;; esac done # Apply pre-processed options set --"${ARGV[@]}" # Parse options END_OF_OPT= POSITIONAL=() while [[ $# -gt 0 ]]; do case"${END_OF_OPT}${1}" in -h|--help) usage 0 ;; -p|--password) shift; PASSWORD="$1" ;; -u|--username) shift; USERNAME="$1" ;; -n|--name) shift; names+=("$1") ;; -q|--quiet) QUIET=1 ;; -C|--copy) COPY=1 ;; -N|--notify) NOTIFY=1 ;; --stdin) READ_STDIN=1 ;; --) END_OF_OPT=1 ;; -*) invalid"$1" ;; *) POSITIONAL+=("$1") ;; esac shift done # Restore positional parameters set --"${POSITIONAL[@]}" |
当我试着回答这个问题的时候,它的首要答案似乎有点笨拙——这是我的解决方案,我发现它更强大:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | boolean_arg="" arg_with_value="" while [[ $# -gt 0 ]] do key="$1" case $key in -b|--boolean-arg) boolean_arg=true shift ;; -a|--arg-with-value) arg_with_value="$2" shift shift ;; -*) echo"Unknown option: $1" exit 1 ;; *) arg_num=$(( $arg_num + 1 )) case $arg_num in 1) first_normal_arg="$1" shift ;; 2) second_normal_arg="$1" shift ;; *) bad_args=TRUE esac ;; esac done # Handy to have this here when adding arguments to # see if they're working. Just edit the '0' to be '1'. if [[ 0 == 1 ]]; then echo"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 0 fi if [[ $bad_args == TRUE || $arg_num < 2 ]]; then echo"Usage: $(basename"$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]" exit 1 fi |
这个例子演示了如何使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #!/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, don't 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 |
上面脚本中最重要的几行是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
简明扼要,可读性强,处理几乎所有事情(imho)。
希望能帮助别人。
这是我改进的布鲁诺·布朗斯基用变量数组的答案的解决方案。
它允许您混合参数位置,并为您提供一个参数数组,保留没有选项的顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #!/bin/bash echo $@ PARAMS=() SOFT=0 SKIP=() for i in"$@" do case $i in -n=*|--skip=*) SKIP+=("${i#*=}") ;; -s|--soft) SOFT=1 ;; *) # unknown option PARAMS+=("$i") ;; esac done echo"SKIP = ${SKIP[@]}" echo"SOFT = $SOFT" echo"Parameters:" echo ${PARAMS[@]} |
将输出,例如:
1 2 3 4 5 6 | $ ./test.sh parameter -s somefile --skip=.c --skip=.obj parameter -s somefile --skip=.c --skip=.obj SKIP = .c .obj SOFT = 1 Parameters: parameter somefile |
简单且易于修改,参数可以按任意顺序排列。这可以修改为采用任何形式的参数(-a、-a、a等)。
1 2 3 4 5 6 7 8 9 10 | for arg in"$@" do key=$(echo $arg | cut -f1 -d=)` value=$(echo $arg | cut -f2 -d=)` case"$key" in name|-name) read_name=$value;; id|-id) read_id=$value;; *) echo"I dont know what to do with this" ease done |