比如,我有一个脚本,用这行代码调用:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
或者这个:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
什么是公认的解析方式,以便在每种情况下(或两者的某种组合)$v、$f和$d都将设置为true,而$outFile将等于/fiz/someOtherFile?
比如,我有一个脚本,用这行代码调用:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
或者这个:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
什么是公认的解析方式,以便在每种情况下(或两者的某种组合)$v、$f和$d都将设置为true,而$outFile将等于/fiz/someOtherFile?
当前回答
我想提供我的选项解析版本,它允许以下内容:
-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello
也允许这样做(可能不需要):
-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder
您必须在使用前决定是否在选项上使用=。这是为了保持代码干净。
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
其他回答
另一个选项解析器(生成器)
用于shell脚本的优雅选项解析器(完全支持所有POSIX shell)https://github.com/ko1nksm/getoptions(更新:v3.3.0于2021-05-02发布)
getoptions是一个新的选项解析器(生成器),用POSIX兼容的shell脚本编写,于2020年8月发布。它适用于那些希望在shell脚本中支持POSIX/GNU样式选项语法的人。
支持的语法有-a、+a、-abc、-vvv、-p VALUE、-pVALUE、--flag、--no flag、--带flag、---不带flag,--param VALUE,--param=VALUE,--option[=VALUE],--no option--。
它支持子命令、验证、缩写选项和自动帮助生成。并且适用于所有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 --help
disp 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 ]; do
case $1 in
--?*=*) OPTARG=$1; shift
eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
;;
--no-*|--without-*) unset OPTARG ;;
-[po]?*) OPTARG=$1; shift
eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
;;
-[fh]?*) OPTARG=$1; shift
eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'}
OPTARG= ;;
esac
case $1 in
'-f'|'--flag')
[ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break
eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
FLAG="$OPTARG"
;;
'-p'|'--param')
[ $# -le 1 ] && set "required" "$1" && break
OPTARG=$2
PARAM="$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')
usage
exit 0 ;;
'--version')
echo "${VERSION}"
exit 0 ;;
--)
shift
while [ $# -gt 0 ]; do
REST="${REST} \"\${$(($OPTIND-$#))}\""
shift
done
break ;;
[-]?*) set "unknown" "$1"; break ;;
*)
REST="${REST} \"\${$(($OPTIND-$#))}\""
esac
shift
done
[ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
case $1 in
unknown) 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" "$@"
esac
echo "$1" >&2
exit 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
--version
GETOPTIONSHERE
}
我认为这个很简单:
#!/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
Bash空格分隔(例如--option参数)
cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
-e|--extension)
EXTENSION="$2"
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH="$2"
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
-*|--*)
echo "Unknown option $1"
exit 1
;;
*)
POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument
;;
esac
done
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 "$1"
fi
EOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc /etc/hosts
复制粘贴上面的块的输出
FILE EXTENSION = conf
SEARCH PATH = /etc
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
用法
demo-space-separated.sh -e conf -s /etc /etc/hosts
Bash等于分隔符(例如,--option=argument)
cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash
for i in "$@"; do
case $i in
-e=*|--extension=*)
EXTENSION="${i#*=}"
shift # past argument=value
;;
-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
shift # past argument=value
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
-*|--*)
echo "Unknown option $i"
exit 1
;;
*)
;;
esac
done
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 $1
fi
EOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc /etc/hosts
复制粘贴上面的块的输出
FILE EXTENSION = conf
SEARCH PATH = /etc
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
用法
demo-equals-separated.sh -e=conf -s=/etc /etc/hosts
要更好地理解${i#*=},请在本指南中搜索“Substring Removal”。它在功能上等同于调用一个不需要的子流程的‘sed’s/[^=]*=//'<<<“$i”`或调用两个不需要子流程的“echo”$i”|sed’s/[^=]*=//'`。
将bash与getopt[s]一起使用
getopt(1)限制(较旧、较新的getopt版本):
无法处理空字符串的参数无法处理带有嵌入空格的参数
最近的getopt版本没有这些限制。有关更多信息,请参阅这些文档。
POSIX获取选项
此外,POSIX外壳和其他提供的getopts没有这些限制。我包含了一个简单的getopts示例。
cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh
# A POSIX variable
OPTIND=1 # Reset in case getopts has been used previously in the shell.
# Initialize our own variables:
output_file=""
verbose=0
while getopts "h?vf:" opt; do
case "$opt" in
h|\?)
show_help
exit 0
;;
v) verbose=1
;;
f) output_file=$OPTARG
;;
esac
done
shift $((OPTIND-1))
[ "${1:-}" = "--" ] && shift
echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF
chmod +x /tmp/demo-getopts.sh
/tmp/demo-getopts.sh -vf /etc/hosts foo bar
复制粘贴上面的块的输出
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
用法
demo-getopts.sh -vf /etc/hosts foo bar
getopts的优点是:
它更便于携带,可以在其他外壳中使用,如dash。它可以以典型的Unix方式自动处理多个单一选项,如-vf文件名。
getopts的缺点是它只能处理短选项(-h,而不是-help),而无需额外代码。
有一个getopts教程,它解释了所有语法和变量的含义。在bash中,还有帮助getopts,这可能会提供信息。
无答案展示了增强的getopt。最热门的答案是误导性的:要么忽略-vfd样式的短选项(OP请求)或位置参数后的选项(OP也请求);并且它忽略解析错误。相反:
使用util-linux或以前的GNUglibc.1中的增强getopt它与GNUglibc的C函数getopt_long()一起工作。本页上没有其他解决方案可以做到这一切:在arguments 2中处理空格、引用字符甚至二进制(非增强型getopt无法做到这一点)它可以在结尾处处理选项:script.sh-o outFile file1 file2-v(getopts不执行此操作)allows=-style long选项:script.sh--outfile=fileOut--infile fileIn(如果自解析,则允许两者都很长)允许组合的短选项,例如-vfd(如果是自解析,则实际工作)允许触摸选项参数,例如-oOutfile或-vfdoOutfile它太旧了,以至于没有GNU系统缺少它(例如,任何Linux都有它)。您可以使用:getopt--test测试它的存在→ 返回值4。其他getopt或shell内置getopt用途有限。
以下通话
myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile
全部返回
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
使用以下myscript
#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# ${PIPESTATUS[0]} with a simple $?, but I prefer safety.
set -o errexit -o pipefail -o noclobber -o nounset
# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
echo 'I’m sorry, `getopt --test` failed in this environment.'
exit 1
fi
# option --output/-o requires 1 argument
LONGOPTS=debug,force,output:,verbose
OPTIONS=dfo:v
# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via -- "$@" to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
# e.g. return value is 1
# then getopt has complained about wrong arguments to stdout
exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"
d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
case "$1" in
-d|--debug)
d=y
shift
;;
-f|--force)
f=y
shift
;;
-v|--verbose)
v=y
shift
;;
-o|--output)
outFile="$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
;;
esac
done
# handle non-option arguments
if [[ $# -ne 1 ]]; then
echo "$0: A single input file is required."
exit 4
fi
echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"
1增强的getopt可用于大多数“bash系统”,包括Cygwin;在OS X上,尝试brew安装gnu getopt或sudo端口安装getopt2 POSIX exec()约定没有可靠的方法在命令行参数中传递二进制NULL;这些字节过早地结束了参数3 1997年或之前发布的第一个版本(我只追溯到1997年)
保留未处理参数的解决方案。包括演示。
这是我的解决方案。它非常灵活,不像其他软件包,不需要外部包,并且可以干净地处理遗留的参数。
用法为:/myscript-flag flag变量-otherflag flagvar2
您所要做的就是编辑有效标记行。它在前面加上连字符并搜索所有参数。然后它将下一个参数定义为标志名,例如。
./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2
主要代码(简短的版本,详细的示例,以及错误输出的版本):
#!/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演示的详细版本:
#!/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
最后一个,如果传递了一个无效的参数,这一个会出错。
#!/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
优点:不管怎么说,它处理得很好。它保留了许多其他解决方案所没有的未使用的参数。它还允许调用变量,而无需在脚本中手动定义。如果没有给出相应的参数,它还允许预填充变量。(请参阅详细示例)。
缺点:无法解析单个复杂的arg字符串,例如-xcvf将作为单个参数处理。不过,您可以很容易地在我的代码中编写额外的代码,以添加此功能。