说,我有一个用这行调用的脚本:
./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
?
传递键 - 值对参数的两种常用方法是:
--option argument
)(没有getopt [s])用法 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="$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
EOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
--option=argument
)(没有getopt [s])用法 demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
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
为了更好地理解本指南中的${i#*=}
"子串删除"搜索.它在功能上等同于调用不必要的子进程或调用两个不必要的子进程.`sed 's/[^=]*=//' <<< "$i"`
`echo "$i" | sed 's/[^=]*=//'`
来自:http://mywiki.wooledge.org/BashFAQ/035#getopts
getopt(1)限制(较旧的,相对较新的getopt
版本):
无法处理空字符串的参数
无法处理嵌入空格的参数
更新的getopt
版本没有这些限制.
此外,POSIX shell(和其他)提供getopts
没有这些限制.这是一个简单的getopts
例子:
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 $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 $1
fi
EOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
优点demo-getopts.sh -vf /etc/hosts foo bar
是:
它更便携,并且可以在其他外壳中工作getopts
.
它可以自动处理多种单一选项,如dash
典型的Unix方式.
缺点-vf filename
是它只能处理短选项(getopts
而不是-h
没有附加代码).
有一个getopts教程,解释了所有语法和变量的含义.在bash中,也有--help
可能提供信息.
没有回答提到增强的getopt.而得票最多的答案是误导性的:它忽略-?vfd
风格期权短仓(由OP请求),选择定位参数后(也由OP的要求)而忽略解析-错误.代替:
使用getopt
util-linux或以前的GNU glibc 增强版.1
它适用于getopt_long()
GNU glibc的C函数.
有所有有用的区别特征(其他没有它们):
处理空格,在参数中引用字符甚至二进制2
它可以在最后处理选项: getopt
允许script.sh -o outFile file1 file2 -v
式长选项:getopts
太旧了已经3没有GNU系统缺少这个(例如,任何Linux有它).
您可以使用以下方法测试其存在:=
→返回值4.
其他script.sh --outfile=fileOut --infile fileIn
或壳内置物-vfd
的用途有限.
以下电话
myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile
一切都归来
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
以下内容 -oOutfile
#!/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 "$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"
大多数"bash系统"都有 1个增强的getopt,包括Cygwin; 在OS X上尝试 brew install gnu-getopt或-vfdoOutfile
2 POSIXgetopt --test
约定没有可靠的方法在命令行参数中传递二进制NULL; 这些字节过早结束参数
3发布第一个版本在1997年或以前(I仅跟踪回1997)
来自:digitalpeer.com稍作修改
用法 myscript.sh -p=my_prefix -s=dirname -l=libname
#!/bin/bash for i in "$@" do case $i in -p=*|--prefix=*) PREFIX="${i#*=}" ;; -s=*|--searchpath=*) SEARCHPATH="${i#*=}" ;; -l=*|--lib=*) DIR="${i#*=}" ;; --default) DEFAULT=YES ;; *) # unknown option ;; esac done echo PREFIX = ${PREFIX} echo SEARCH PATH = ${SEARCHPATH} echo DIRS = ${DIR} echo DEFAULT = ${DEFAULT}
为了更好地理解本指南中的${i#*=}
"子串删除"搜索.它在功能上等同于调用不必要的子进程或调用两个不必要的子进程.`sed 's/[^=]*=//' <<< "$i"`
`echo "$i" | sed 's/[^=]*=//'`
script.sh
#!/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"
用法:
./script.sh -d dev -u
# OR:
./script.sh --deploy dev --uglify
getopt()
/ getopts()
是一个不错的选择.从这里偷来的:
这个迷你脚本中显示了"getopt"的简单使用:
#!/bin/bash echo "Before getopt" for i do echo $i done args=`getopt abc:d $*` set -- $args echo "After getopt" for i do echo "-->$i" done
我们所说的是允许使用-a,-b,-c或-d中的任何一个,但是-c后跟一个参数("c:"表示).
如果我们称之为"g"并试一试:
bash-2.05a$ ./g -abc foo Before getopt -abc foo After getopt -->-a -->-b -->-c -->foo -->--
我们从两个参数开始,"getopt"拆分选项并将每个选项放在自己的参数中.它还添加了" - ".
冒着添加另一个要忽略的例子的风险,这是我的方案.
处理-n arg
和--name=arg
最后允许参数
如果有任何拼写错误,则会显示正确的错误
兼容,不使用bashisms
可读,不需要在循环中维护状态
希望它对某人有用.
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
我对这个问题迟到了4年,但是想要回馈.我使用前面的答案作为整理我的旧adhoc param解析的起点.然后我重构了以下模板代码.它使用=或空格分隔的参数处理长和短参数,以及组合在一起的多个短参数.最后,它将任何非参数参数重新插入到$ 1,$ 2 ..变量中.我希望它有用.
#!/usr/bin/env bash # NOTICE: Uncomment if your script depends on bashisms. #if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi echo "Before" for i ; do echo - $i ; done # Code template for parsing command line parameters using only portable shell # code, while handling both long and short params, handling '-f file' and # '-f=file' style param data and also capturing non-parameters to be inserted # back into the shell positional parameters. while [ -n "$1" ]; do # Copy so we can modify it (can't modify $1) OPT="$1" # Detect argument termination if [ x"$OPT" = x"--" ]; then shift for OPT ; do REMAINS="$REMAINS \"$OPT\"" done break fi # Parse current opt while [ x"$OPT" != x"-" ] ; do case "$OPT" in # Handle --flag=value opts like this -c=* | --config=* ) CONFIGFILE="${OPT#*=}" shift ;; # and --flag value opts like this -c* | --config ) CONFIGFILE="$2" shift ;; -f* | --force ) FORCE=true ;; -r* | --retry ) RETRY=true ;; # Anything unknown is recorded for later * ) REMAINS="$REMAINS \"$OPT\"" break ;; esac # Check for multiple short options # NOTICE: be sure to update this pattern to match valid options NEXTOPT="${OPT#-[cfr]}" # try removing single short opt if [ x"$OPT" != x"$NEXTOPT" ] ; then OPT="-$NEXTOPT" # multiple short opts, keep going else break # long form, exit inner loop fi done # Done with that param. move to next shift done # Set the non-parameters back into the positional parameters ($1 $2 ..) eval set -- $REMAINS echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'" for i ; do echo - $i ; done
我发现在脚本中编写可移植解析非常令人沮丧,因为我编写了Argbash - 一个FOSS代码生成器,可以为脚本生成参数解析代码,还有一些很好的特性:
https://argbash.io
我的答案很大程度上是基于Bruno Bronosky的答案,但我将他的两个纯粹的bash实现混合成了一个我经常使用的实现.
# As long as there is at least one more argument, keep looping while [[ $# -gt 0 ]]; do key="$1" case "$key" in # This is a flag type option. Will catch either -f or --foo -f|--foo) FOO=1 ;; # Also a flag type option. Will catch either -b or --bar -b|--bar) BAR=1 ;; # This is an arg value type option. Will catch -o value or --output-file value -o|--output-file) shift # past the key and to the value OUTPUTFILE="$1" ;; # This is an arg=value type option. Will catch -o=value or --output-file=value -o=*|--output-file=*) # No need to shift here since the value is part of the same string OUTPUTFILE="${key#*=}" ;; *) # Do whatever you want with extra options echo "Unknown option '$key'" ;; esac # Shift after checking all the cases to get the next option shift done
这允许您同时具有空格分隔的选项/值以及相等的定义值.
所以你可以运行你的脚本:
./myscript --foo -b -o /fizz/file.txt
以及:
./myscript -f --bar -o=/fizz/file.txt
两者都应该有相同的最终结果.
优点:
允许-arg = value和-arg值
适用于您可以在bash中使用的任何arg名称
含义-a或-arg或--arg或-arg或其他
纯粹的bash.无需学习/使用getopt或getopts
缺点:
无法组合args
意思是没有-abc.你必须做-a -b -c
这些是我能想到的唯一优点/缺点
我认为这个很简单,可以使用:
#!/bin/bash # readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }' opts=vfdo: # Enumerating options while eval $readopt do echo OPT:$opt ${OPTARG+OPTARG:$OPTARG} done # Enumerating arguments for arg do echo ARG:$arg done
调用示例:
./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile OPT:v OPT:d OPT:o OPTARG:/fizz/someOtherFile OPT:f ARG:./foo/bar/someFile
扩展@guneysus的优秀答案,这是一个调整,让用户可以使用他们喜欢的任何语法,例如
command -x=myfilename.ext --another_switch
VS
command -x myfilename.ext --another_switch
也就是说,equals可以用空格替换.
这种"模糊解释"可能不符合您的喜好,但如果您制作的脚本可以与其他实用程序互换(就像我的情况一样,必须与ffmpeg一起使用),灵活性很有用.
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
我给你的函数parse_params
将从命令行解析params.
它是纯粹的Bash解决方案,没有其他实用程序.
不污染全球范围.
毫不费力地返回简单易用的变量,您可以构建更多逻辑.
参数之前的破折号无关紧要(--all
等于-all
等于all=all
)
下面的脚本是一个复制粘贴工作演示.查看show_use
功能以了解如何使用parse_params
.
限制:
不支持空格分隔的params(-d 1
)
帕拉姆名称将失去破折号所以--any-param
和-anyparam
等价
eval $(parse_params "$@")
必须在bash 函数中使用(它不能在全局范围内工作)
#!/bin/bash # Universal Bash parameter parsing # Parse equal sign separated params into named local variables # Standalone named parameter value will equal its param name (--force creates variable $force=="force") # Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array) # Puts un-named params as-is into ${ARGV[*]} array # Additionally puts all named params as-is into ${ARGN[*]} array # Additionally puts all standalone "option" params as-is into ${ARGO[*]} array # @author Oleksii Chekulaiev # @version v1.4.1 (Jul-27-2018) parse_params () { local existing_named local ARGV=() # un-named params local ARGN=() # named params local ARGO=() # options (--params) echo "local ARGV=(); local ARGN=(); local ARGO=();" while [[ "$1" != "" ]]; do # Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage _escaped=${1/\*/\'\"*\"\'} _escaped=${_escaped//\'/\\\'} _escaped=${_escaped//\"/\\\"} # If equals delimited named parameter nonspace="[^[:space:]]" if [[ "$1" =~ ^${nonspace}${nonspace}*=..* ]]; then # Add to named parameters array echo "ARGN+=('$_escaped');" # key is part before first = local _key=$(echo "$1" | cut -d = -f 1) # Just add as non-named when key is empty or contains space if [[ "$_key" == "" || "$_key" =~ " " ]]; then echo "ARGV+=('$_escaped');" shift continue fi # val is everything after key and = (protect from param==value error) local _val="${1/$_key=}" # remove dashes from key name _key=${_key//\-} # skip when key is empty # search for existing parameter name if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then # if name already exists then it's a multi-value named parameter # re-declare it as an array if needed if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then echo "$_key=(\"\$$_key\");" fi # append new value echo "$_key+=('$_val');" else # single-value named parameter echo "local $_key='$_val';" existing_named=" $_key" fi # If standalone named parameter elif [[ "$1" =~ ^\-${nonspace}+ ]]; then # remove dashes local _key=${1//\-} # Just add as non-named when key is empty or contains space if [[ "$_key" == "" || "$_key" =~ " " ]]; then echo "ARGV+=('$_escaped');" shift continue fi # Add to options array echo "ARGO+=('$_escaped');" echo "local $_key=\"$_key\";" # non-named parameter else # Escape asterisk to prevent bash asterisk expansion _escaped=${1/\*/\'\"*\"\'} echo "ARGV+=('$_escaped');" fi shift done } #--------------------------- DEMO OF THE USAGE ------------------------------- show_use () { eval $(parse_params "$@") # -- echo "${ARGV[0]}" # print first unnamed param echo "${ARGV[1]}" # print second unnamed param echo "${ARGN[0]}" # print first named param echo "${ARG0[0]}" # print first option param (--force) echo "$anyparam" # print --anyparam value echo "$k" # print k=5 value echo "${multivalue[0]}" # print first value of multi-value echo "${multivalue[1]}" # print second value of multi-value [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you" } show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
EasyOptions不需要任何解析:
## 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
如果安装了#1,那么getopts工作得很好;#2你打算在同一平台上运行它.OSX和Linux(例如)在这方面表现不同.
这是一个支持equals,non-equals和boolean标志的(非getopts)解决方案.例如,您可以通过以下方式运行脚本:
./script --arg1=value1 --arg2 value2 --shouldClean # parse the arguments. COUNTER=0 ARGS=("$@") while [ $COUNTER -lt $# ] do arg=${ARGS[$COUNTER]} let COUNTER=COUNTER+1 nextArg=${ARGS[$COUNTER]} if [[ $skipNext -eq 1 ]]; then echo "Skipping" skipNext=0 continue fi argKey="" argVal="" if [[ "$arg" =~ ^\- ]]; then # if the format is: -key=value if [[ "$arg" =~ \= ]]; then argVal=$(echo "$arg" | cut -d'=' -f2) argKey=$(echo "$arg" | cut -d'=' -f1) skipNext=0 # if the format is: -key value elif [[ ! "$nextArg" =~ ^\- ]]; then argKey="$arg" argVal="$nextArg" skipNext=1 # if the format is: -key (a boolean flag) elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then argKey="$arg" argVal="" skipNext=0 fi # if the format has not flag, just a value. else argKey="" argVal="$arg" skipNext=0 fi case "$argKey" in --source-scmurl) SOURCE_URL="$argVal" ;; --dest-scmurl) DEST_URL="$argVal" ;; --version-num) VERSION_NUM="$argVal" ;; -c|--clean) CLEAN_BEFORE_START="1" ;; -h|--help|-help|--h) showUsage exit ;; esac done
这是我在一个函数中做的工作,以避免在堆栈中更高的位置同时运行getopts:
function waitForWeb () { local OPTIND=1 OPTARG OPTION local host=localhost port=8080 proto=http while getopts "h:p:r:" OPTION; do case "$OPTION" in h) host="$OPTARG" ;; p) port="$OPTARG" ;; r) proto="$OPTARG" ;; esac done ... }
本示例说明如何使用getopt
and eval
和HEREDOC
and shift
来处理带有和不带有必需值的短和长参数。同样,switch / case语句简洁明了,易于遵循。
#!/usr/bin/env bash # usage function function usage() { cat << HEREDOC Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run] optional arguments: -h, --help show this help message and exit -n, --num NUM pass in a number -t, --time TIME_STR pass in a time string -v, --verbose increase the verbosity of the bash script --dry-run do a dry run, dont change any files HEREDOC } # initialize variables progname=$(basename $0) verbose=0 dryrun=0 num_str= time_str= # use getopt and store the output into $OPTS # note the use of -o for the short options, --long for the long name options # and a : for any option that takes a parameter OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@") if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi eval set -- "$OPTS" while true; do # uncomment the next line to see how shift is working # echo "\$1:\"$1\" \$2:\"$2\"" case "$1" in -h | --help ) usage; exit; ;; -n | --num ) num_str="$2"; shift 2 ;; -t | --time ) time_str="$2"; shift 2 ;; --dry-run ) dryrun=1; shift ;; -v | --verbose ) verbose=$((verbose + 1)); shift ;; -- ) shift; break ;; * ) break ;; esac done if (( $verbose > 0 )); then # print out all the parameters we read in cat <<-EOM num=$num_str time=$time_str verbose=$verbose dryrun=$dryrun EOM fi # The rest of your script below
上面脚本中最重要的几行是:
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@") if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi eval set -- "$OPTS" while true; do case "$1" in -h | --help ) usage; exit; ;; -n | --num ) num_str="$2"; shift 2 ;; -t | --time ) time_str="$2"; shift 2 ;; --dry-run ) dryrun=1; shift ;; -v | --verbose ) verbose=$((verbose + 1)); shift ;; -- ) shift; break ;; * ) break ;; esac done
简而言之,可读性强,几乎可以处理所有内容(IMHO)。
希望能对某人有所帮助。