gag*_*eet 390 bash getopt getopts command-line-arguments
我希望使用我的shell脚本调用多种形式的命令行选项.
我知道getopts
可以使用,但就像在Perl中一样,我无法对shell做同样的事情.
关于如何做到这一点的任何想法,以便我可以使用如下选项:
./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/
Run Code Online (Sandbox Code Playgroud)
在上面,两个命令对我的shell意味着相同的东西,但使用getopts
,我还没有能够实现这些?
Urb*_*ond 290
getopt
并且getopts
是不同的野兽,人们似乎对他们所做的事情有一些误解. getopts
是一个内置命令,bash
用于处理循环中的命令行选项,并将每个找到的选项和值依次分配给内置变量,以便您可以进一步处理它们. getopt
但是,它是一个外部实用程序,它实际上并不像 bash getopts
,Perl Getopt
模块或Python optparse
/ argparse
模块那样为您处理选项.所有这一切getopt
确实是规范化所传递的选项-即它们转换为更规范的形式,因此,它是一个shell脚本来处理它们更容易.例如,应用程序getopt
可能会转换以下内容:
myscript -ab infile.txt -ooutfile.txt
Run Code Online (Sandbox Code Playgroud)
进入这个:
myscript -a -b -o outfile.txt infile.txt
Run Code Online (Sandbox Code Playgroud)
你必须自己做实际的处理.getopt
如果对指定选项的方式进行各种限制,则根本不必使用:
-o
上面),该值必须作为单独的参数(在空格之后).为什么用getopt
而不是getopts
?基本原因是只有GNU getopt
才能支持长命名的命令行选项.1(GNU getopt
是Linux上的默认设置.Mac OS X和FreeBSD有一个基本的,非常有用getopt
,但可以安装GNU版本;见下文.)
例如,这是一个使用GNU的例子getopt
,从我的脚本调用javawrap
:
# NOTE: This requires GNU getopt. On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
-n 'javawrap' -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"
VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
case "$1" in
-v | --verbose ) VERBOSE=true; shift ;;
-d | --debug ) DEBUG=true; shift ;;
-m | --memory ) MEMORY="$2"; shift 2 ;;
--debugfile ) DEBUGFILE="$2"; shift 2 ;;
--minheap )
JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
--maxheap )
JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
Run Code Online (Sandbox Code Playgroud)
这允许您指定类似--verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"
或类似的选项.调用的效果getopt
是规范化选项,--verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"
以便您可以更轻松地处理它们.引用"$1"
并且"$2"
很重要,因为它确保正确处理带有空格的参数.
如果删除前9行(通过该eval set
行的所有内容),代码仍然有效!但是,您的代码在它接受的各种选项中会更加挑剔:特别是,您必须以上述"规范"形式指定所有选项.getopt
但是,通过使用,您可以对单字母选项进行分组,使用较短的非模糊形式的长选项,使用--file foo.txt
或--file=foo.txt
样式,使用-m 4096
或-m4096
样式,混合选项和任何顺序的非选项等. getopt
如果找到无法识别或不明确的选项,也会输出错误消息.
注意:实际上有两个完全不同的版本getopt
,基本getopt
和GNU getopt
,具有不同的功能和不同的调用约定.2 Basic getopt
非常破碎:它不仅不处理长选项,它甚至不能处理参数或空参数内的嵌入空格,而是getopts
这样做.上面的代码在基本代码中不起作用getopt
.GNU getopt
默认安装在Linux上,但在Mac OS X和FreeBSD上需要单独安装.在Mac OS X上,安装MacPorts(http://www.macports.org),然后sudo port install getopt
安装GNU getopt
(通常是/opt/local/bin
),并确保它/opt/local/bin
在你的shell路径之前/usr/bin
.在FreeBSD上,安装misc/getopt
.
修改自己程序的示例代码的快速指南:在前几行中,除了调用的行之外,所有都是"样板文件"应该保持不变getopt
.您应该在之后更改程序名称,在之后-n
指定短选项,在之后指定-o
长选项--long
.在带有值的选项后放置冒号.
最后,如果您看到set
代替的代码eval set
,那么它就是为BSD编写的getopt
.您应该将其更改为使用eval set
样式,该样式适用于两个版本getopt
,而普通版本set
无法正常使用GNU getopt
.
1实际上,getopts
在ksh93
支持长命名的选项中,但是这个shell并不经常使用bash
.在zsh
,用于zparseopts
获得此功能.
2从技术上讲,"GNU getopt
"是用词不当; 这个版本实际上是为Linux而不是GNU项目编写的.但是,它遵循所有GNU约定,并且getopt
通常使用术语"GNU "(例如在FreeBSD上).
Bil*_*win 286
可以考虑三种实现:
Bash内置getopts
.这不支持带有双短划线前缀的长选项名称.它仅支持单字符选项.
BSD UNIX实现独立getopt
命令(这是MacOS使用的).这也不支持长选项.
其他一些答案显示了使用bash内置getopts
模拟长选项的解决方案.该解决方案实际上是一个短的选项,其字符是" - ".所以你得到" - "作为旗帜.然后,随后的任何内容都将成为OPTARG,并使用嵌套测试OPTARG case
.
这很聪明,但它带有警告:
getopts
无法强制执行opt规范.如果用户提供无效选项,则无法返回错误.在解析OPTARG时,您必须自己进行错误检查.因此,尽管可以编写更多代码来解决缺乏对长选项的支持的问题,但这需要做更多的工作,并且部分地违背了使用getopt解析器来简化代码的目的.
小智 196
Bash builtin getopts函数可用于通过在optspec中放入一个破折号字符后跟冒号来解析长选项:
#!/usr/bin/env bash
optspec=":hv-:"
while getopts "$optspec" optchar; do
case "${optchar}" in
-)
case "${OPTARG}" in
loglevel)
val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
;;
loglevel=*)
val=${OPTARG#*=}
opt=${OPTARG%=$val}
echo "Parsing option: '--${opt}', value: '${val}'" >&2
;;
*)
if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
echo "Unknown option --${OPTARG}" >&2
fi
;;
esac;;
h)
echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
exit 2
;;
v)
echo "Parsing option: '-${optchar}'" >&2
;;
*)
if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
echo "Non-option argument: '-${OPTARG}'" >&2
fi
;;
esac
done
Run Code Online (Sandbox Code Playgroud)
复制到当前工作目录getopts_test.sh
中的可执行文件名= 后,可以生成类似的输出
$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'
Run Code Online (Sandbox Code Playgroud)
显然,getopts既不OPTERR
对long选项执行检查也不执行option-argument解析.上面的脚本片段显示了如何手动完成此操作.基本原理也适用于Debian Almquist shell("dash").请注意特殊情况:
getopts -- "-:" ## without the option terminator "-- " bash complains about "-:"
getopts "-:" ## this works in the Debian Almquist shell ("dash")
Run Code Online (Sandbox Code Playgroud)
请注意,正如来自http://mywiki.wooledge.org/BashFAQ的 GreyCat所指出的,这个技巧利用了shell的非标准行为,允许使用option-argument(即"-f filename"中的文件名)连接到选项(如"-ffilename").该POSIX标准说必须有它们之间的空间,这在的情况下" - longoption"将终止选项的解析,并把所有longoptions成非选项参数.
Jon*_*ler 148
内置getopts
命令仍为AFAIK,仅限于单字符选项.
有(或曾经是)一个外部程序getopt
,它会重新组织一组选项,以便更容易解析.您可以调整该设计以处理长选项.用法示例:
aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
case "$1" in
(-a) aflag=yes;;
(-b) bflag=yes;;
(-f) flist="$flist $2"; shift;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done
# Process remaining non-option arguments
...
Run Code Online (Sandbox Code Playgroud)
您可以使用与getoptlong
命令类似的方案.
请注意,外部getopt
程序的根本弱点是难以处理带有空格的参数,并且难以准确地保留这些空间.这就是内置getopts
优越的原因,尽管它只处理单字母选项.
sme*_*sme 76
这是一个实际使用带有长选项的getopt的示例:
aflag=no
bflag=no
cargument=none
# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
# something went wrong, getopt will put out an error message for us
exit 1
fi
set -- $options
while [ $# -gt 0 ]
do
case $1 in
-a|--along) aflag="yes" ;;
-b|--blong) bflag="yes" ;;
# for options with required arguments, an additional shift is required
-c|--clong) cargument="$2" ; shift;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done
Run Code Online (Sandbox Code Playgroud)
Ada*_*atz 49
可以通过标准getopts
内置解析长选项作为-
"选项"的"参数"
这是可移植的本机POSIX shell - 不需要外部程序或基本原理.
本指南执行长选项作为参数传递给-
选项,因此--alpha
被看作getopts
是-
与参数alpha
和--bravo=foo
被视为-
与参数bravo=foo
.真正的论点可以用简单的替代品来收获:${OPTARG#*=}
.
在此示例中,-b
(及其长形式--bravo
)具有强制选项(请注意对长格式强制执行的手动重建).长参数的非布尔选项在等号之后出现,例如--bravo=foo
(长选项的空格分隔符很难实现).
因为这样使用getopts
,这个解决方案支持使用类似cmd -ac --bravo=foo -d FILE
(它具有组合选项-a
和 - c
并将长选项与标准选项交错),而大多数其他答案在这里要么努力要么不能做到.
while getopts ab:c-: arg; do
case $arg in
a ) ARG_A=true ;;
b ) ARG_B="$OPTARG" ;;
c ) ARG_C=true ;;
- ) LONG_OPTARG="${OPTARG#*=}"
case $OPTARG in
alpha ) ARG_A=true ;;
bravo=?* ) ARG_B="$LONG_OPTARG" ;;
bravo* ) echo "No arg for --$OPTARG option" >&2; exit 2 ;;
charlie ) ARG_C=true ;;
alpha* | charlie* )
echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;;
'' ) break ;; # "--" terminates argument processing
* ) echo "Illegal option --$OPTARG" >&2; exit 2 ;;
esac ;;
\? ) exit 2 ;; # getopts already reported the illegal option
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
Run Code Online (Sandbox Code Playgroud)
当参数是一个破折号(-
)时,它还有两个组件:标志名称和(可选)其参数.我将这些标准方式划分为任何命令,第一个等号(=
). $LONG_OPTARG
因此,仅仅是$OPTARG
没有国旗名称或等号的内容.
内部case
手动实现长选项,因此需要一些内务处理:
bravo=?
匹配--bravo=foo
但不匹配--bravo=
(注意:case
第一场比赛后停止)bravo*
如下,并指出在缺少必需的参数--bravo
和--bravo=
alpha* | charlie*
捕获给不支持它们的选项的参数''
用于支持碰巧以破折号开头的非选项*
捕获所有其他长选项并重新创建getopts为无效选项抛出的错误您不一定需要所有这些家政用品.例如,您可能希望--bravo
拥有一个可选参数(-b
由于其中的限制而无法支持getopts
).只需删除=?
相关的故障情况,然后${ARG_B:=$DEFAULT_ARG_B}
在第一次使用时拨打电话$ARG_B
.
小智 33
看看shFlags是一个可移植的shell库(意思是:Linux,Solaris等上的sh,bash,dash,ksh,zsh).
它使添加新标志就像在脚本中添加一行一样简单,它提供了一个自动生成的使用功能.
这是一个简单的Hello, world!
使用shFlag:
#!/bin/sh
# source shflags from current directory
. ./shflags
# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'
# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
# say hello
echo "Hello, ${FLAGS_name}!"
Run Code Online (Sandbox Code Playgroud)
对于具有支持长选项的增强型getopt(例如Linux)的操作系统,您可以执行以下操作:
$ ./hello_world.sh --name Kate
Hello, Kate!
Run Code Online (Sandbox Code Playgroud)
其余的,你必须使用短选项:
$ ./hello_world.sh -n Kate
Hello, Kate!
Run Code Online (Sandbox Code Playgroud)
添加新标志就像添加新标志一样简单DEFINE_ call
.
小智 30
getopts
短/长选项和参数Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty
Run Code Online (Sandbox Code Playgroud)
function _usage()
{
###### U S A G E : Help and ERROR ######
cat <<EOF
foobar $Options
$*
Usage: foobar <[options]>
Options:
-b --bar Set bar to yes ($foo)
-f --foo Set foo to yes ($bart)
-h --help Show this message
-A --arguments=... Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
-B --barfoo Set barfoo to yes ($barfoo)
-F --foobar Set foobar to yes ($foobar)
EOF
}
[ $# = 0 ] && _usage " >>>>>>>> no options given "
Run Code Online (Sandbox Code Playgroud)
getops
有长/短标志以及长参数while getopts ':bfh-A:BF' OPTION ; do
case "$OPTION" in
b ) sbar=yes ;;
f ) sfoo=yes ;;
h ) _usage ;;
A ) sarguments=yes;sARG="$OPTARG" ;;
B ) sbarfoo=yes ;;
F ) sfoobar=yes ;;
- ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
eval OPTION="\$$optind"
OPTARG=$(echo $OPTION | cut -d'=' -f2)
OPTION=$(echo $OPTION | cut -d'=' -f1)
case $OPTION in
--foo ) lfoo=yes ;;
--bar ) lbar=yes ;;
--foobar ) lfoobar=yes ;;
--barfoo ) lbarfoo=yes ;;
--help ) _usage ;;
--arguments ) larguments=yes;lARG="$OPTARG" ;;
* ) _usage " Long: >>>>>>>> invalid options (long) " ;;
esac
OPTIND=1
shift
;;
? ) _usage "Short: >>>>>>>> invalid options (short) " ;;
esac
done
Run Code Online (Sandbox Code Playgroud)
##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo : $sfoo long-foo : $lfoo"
echo "RESULT short-bar : $sbar long-bar : $lbar"
echo "RESULT short-foobar : $sfoobar long-foobar : $lfoobar"
echo "RESULT short-barfoo : $sbarfoo long-barfoo : $lbarfoo"
echo "RESULT short-arguments: $sarguments with Argument = \"$sARG\" long-arguments: $larguments and $lARG"
Run Code Online (Sandbox Code Playgroud)
#!/bin/bash
# foobar: getopts with short and long options AND arguments
function _cleanup ()
{
unset -f _usage _cleanup ; return 0
}
## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN
###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty
function _usage()
{
###### U S A G E : Help and ERROR ######
cat <<EOF
foobar $Options
$*
Usage: foobar <[options]>
Options:
-b --bar Set bar to yes ($foo)
-f --foo Set foo to yes ($bart)
-h --help Show this message
-A --arguments=... Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
-B --barfoo Set barfoo to yes ($barfoo)
-F --foobar Set foobar to yes ($foobar)
EOF
}
[ $# = 0 ] && _usage " >>>>>>>> no options given "
##################################################################
####### "getopts" with: short options AND long options #######
####### AND short/long arguments #######
while getopts ':bfh-A:BF' OPTION ; do
case "$OPTION" in
b ) sbar=yes ;;
f ) sfoo=yes ;;
h ) _usage ;;
A ) sarguments=yes;sARG="$OPTARG" ;;
B ) sbarfoo=yes ;;
F ) sfoobar=yes ;;
- ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
eval OPTION="\$$optind"
OPTARG=$(echo $OPTION | cut -d'=' -f2)
OPTION=$(echo $OPTION | cut -d'=' -f1)
case $OPTION in
--foo ) lfoo=yes ;;
--bar ) lbar=yes ;;
--foobar ) lfoobar=yes ;;
--barfoo ) lbarfoo=yes ;;
--help ) _usage ;;
--arguments ) larguments=yes;lARG="$OPTARG" ;;
* ) _usage " Long: >>>>>>>> invalid options (long) " ;;
esac
OPTIND=1
shift
;;
? ) _usage "Short: >>>>>>>> invalid options (short) " ;;
esac
done
Run Code Online (Sandbox Code Playgroud)
mtv*_*vee 23
其他方式...
# translate long options to short
for arg
do
delim=""
case "$arg" in
--help) args="${args}-h ";;
--verbose) args="${args}-v ";;
--config) args="${args}-c ";;
# pass through anything else
*) [[ "${arg:0:1}" == "-" ]] || delim="\""
args="${args}${delim}${arg}${delim} ";;
esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
case $opt in
h) usage ;;
v) VERBOSE=true ;;
c) source $OPTARG ;;
\?) usage ;;
:)
echo "option -$OPTARG requires an argument"
usage
;;
esac
done
Run Code Online (Sandbox Code Playgroud)
小智 18
我有点解决这个问题:
# A string with command options
options=$@
# An array with all the arguments
arguments=($options)
# Loop index
index=0
for argument in $options
do
# Incrementing index
index=`expr $index + 1`
# The conditions
case $argument in
-a) echo "key $argument value ${arguments[index]}" ;;
-abc) echo "key $argument value ${arguments[index]}" ;;
esac
done
exit;
Run Code Online (Sandbox Code Playgroud)
我愚蠢还是什么?getopt
而且getopts
很混乱.
小智 14
如果您不想要getopt
依赖项,可以这样做:
while test $# -gt 0
do
case $1 in
# Normal option processing
-h | --help)
# usage and help
;;
-v | --version)
# version info
;;
# ...
# Special cases
--)
break
;;
--*)
# error unknown (long) option $1
;;
-?)
# error unknown (short) option $1
;;
# FUN STUFF HERE:
# Split apart combined short options
-*)
split=$1
shift
set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
continue
;;
# Done with options
*)
break
;;
esac
# for testing purposes:
echo "$1"
shift
done
Run Code Online (Sandbox Code Playgroud)
当然,那么你不能在一个短划线上使用长样式选项.如果你想添加缩短的版本(例如--verbos而不是--verbose),那么你需要手动添加它们.
但是,如果您希望获得getopts
功能以及长选项,这是一种简单的方法.
我还把这个片段放在一个要点上.
Nie*_*jou 11
内置getopts
无法做到这一点.有一个外部的getopt(1)程序可以做到这一点,但你只能在Linux上从util-linux包中获得它.它附带一个示例脚本getopt-parse.bash.
还有一个getopts_long
写为shell函数.
#!/bin/bash
while getopts "abc:d:" flag
do
case $flag in
a) echo "[getopts:$OPTIND]==> -$flag";;
b) echo "[getopts:$OPTIND]==> -$flag";;
c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
esac
done
shift $((OPTIND-1))
echo "[otheropts]==> $@"
exit
Run Code Online (Sandbox Code Playgroud)
.
#!/bin/bash
until [ -z "$1" ]; do
case $1 in
"--dlong")
shift
if [ "${1:1:0}" != "-" ]
then
echo "==> dlong $1"
shift
fi;;
*) echo "==> other $1"; shift;;
esac
done
exit
Run Code Online (Sandbox Code Playgroud)
小智 7
在ksh93
,getopts
支持长名称...
while getopts "f(file):s(server):" flag
do
echo "$flag" $OPTIND $OPTARG
done
Run Code Online (Sandbox Code Playgroud)
或者我发现的教程已经说过了.试试看吧.
发明轮子的另一个版本......
这个函数是(希望)POSIX兼容的普通bourne shell替代GNU getopt.它支持短/长选项,可以接受强制/可选/无参数,并且指定选项的方式几乎与GNU getopt相同,因此转换是微不足道的.
当然,这仍然是放入脚本的相当大的代码块,但它大约是众所周知的getopt_long shell函数的一半,并且在您只想替换现有GNU getopt用途的情况下可能更好.
这是相当新的代码,所以YMMV(绝对请让我知道如果这实际上不是以任何理由POSIX兼容 - 便携性从一开始的打算,但我没有一个有用的POSIX测试环境).
代码和示例用法如下:
#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105
# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
local param
for param; do
printf %s\\n "$param" \
| sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
done
printf %s\\n " "
}
# Exit with status $1 after displaying error message $2.
exiterr () {
printf %s\\n "$2" >&2
exit $1
}
# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
local shortopts longopts \
arg argtype getopt nonopt opt optchar optword suffix
shortopts="$1"
longopts="$2"
shift 2
getopt=
nonopt=
while [ $# -gt 0 ]; do
opt=
arg=
argtype=
case "$1" in
# '--' means don't parse the remaining options
( -- ) {
getopt="${getopt}$(save "$@")"
shift $#
break
};;
# process short option
( -[!-]* ) { # -x[foo]
suffix=${1#-?} # foo
opt=${1%$suffix} # -x
optchar=${opt#-} # x
case "${shortopts}" in
( *${optchar}::* ) { # optional argument
argtype=optional
arg="${suffix}"
shift
};;
( *${optchar}:* ) { # required argument
argtype=required
if [ -n "${suffix}" ]; then
arg="${suffix}"
shift
else
case "$2" in
( -* ) exiterr 1 "$1 requires an argument";;
( ?* ) arg="$2"; shift 2;;
( * ) exiterr 1 "$1 requires an argument";;
esac
fi
};;
( *${optchar}* ) { # no argument
argtype=none
arg=
shift
# Handle multiple no-argument parameters combined as
# -xyz instead of -x -y -z. If we have just shifted
# parameter -xyz, we now replace it with -yz (which
# will be processed in the next iteration).
if [ -n "${suffix}" ]; then
eval "set -- $(save "-${suffix}")$(save "$@")"
fi
};;
( * ) exiterr 1 "Unknown option $1";;
esac
};;
# process long option
( --?* ) { # --xarg[=foo]
suffix=${1#*=} # foo (unless there was no =)
if [ "${suffix}" = "$1" ]; then
suffix=
fi
opt=${1%=$suffix} # --xarg
optword=${opt#--} # xarg
case ",${longopts}," in
( *,${optword}::,* ) { # optional argument
argtype=optional
arg="${suffix}"
shift
};;
( *,${optword}:,* ) { # required argument
argtype=required
if [ -n "${suffix}" ]; then
arg="${suffix}"
shift
else
case "$2" in
( -* ) exiterr 1 \
"--${optword} requires an argument";;
( ?* ) arg="$2"; shift 2;;
( * ) exiterr 1 \
"--${optword} requires an argument";;
esac
fi
};;
( *,${optword},* ) { # no argument
if [ -n "${suffix}" ]; then
exiterr 1 "--${optword} does not take an argument"
fi
argtype=none
arg=
shift
};;
( * ) exiterr 1 "Unknown option $1";;
esac
};;
# any other parameters starting with -
( -* ) exiterr 1 "Unknown option $1";;
# remember non-option parameters
( * ) nonopt="${nonopt}$(save "$1")"; shift;;
esac
if [ -n "${opt}" ]; then
getopt="${getopt}$(save "$opt")"
case "${argtype}" in
( optional|required ) {
getopt="${getopt}$(save "$arg")"
};;
esac
fi
done
# Generate function output, suitable for:
# eval "set -- $(posix_getopt ...)"
printf %s "${getopt}"
if [ -n "${nonopt}" ]; then
printf %s "$(save "--")${nonopt}"
fi
}
Run Code Online (Sandbox Code Playgroud)
用法示例:
# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
#eval set -- ${opts}
eval "set -- ${opts}"
while [ $# -gt 0 ]; do
case "$1" in
( -- ) shift; break;;
( -h|--help ) help=1; shift; break;;
( -v|--version ) version_help=1; shift; break;;
( -d|--directory ) dir=$2; shift 2;;
( -c|--client ) useclient=1; client=$2; shift 2;;
( -s|--server ) startserver=1; server_name=$2; shift 2;;
( -L|--load ) load=$2; shift 2;;
( -D|--delete ) delete=1; shift;;
esac
done
else
shorthelp=1 # getopt returned (and reported) an error.
fi
Run Code Online (Sandbox Code Playgroud)
我不时只写shell脚本,但并没有实践,因此,感谢您提供任何反馈。
使用@Arvid Requate提出的策略,我们注意到了一些用户错误。忘记包含值的用户将不小心将下一个选项的名称视为值:
./getopts_test.sh --loglevel= --toc=TRUE
Run Code Online (Sandbox Code Playgroud)
将导致“ loglevel”的值被视为“ --toc = TRUE”。这是可以避免的。
我从http://mwiki.wooledge.org/BashFAQ/035关于手动解析的讨论中采纳了一些有关检查CLI的用户错误的想法 。我将错误检查插入到处理“-”和“-”参数中。
然后,我开始摆弄语法,所以这里的任何错误都是我的错,而不是原始作者。
我的方法可以帮助希望长时间输入带有或不带有等号的用户。也就是说,它对“ --loglevel 9”的响应应该与“ --loglevel = 9”相同。在-/ space方法中,无法确定用户是否忘记了参数,因此需要进行一些猜测。
如果您刚开始,“-opt = value”和“ --opt value”格式之间会有一个有趣的区别。带有等号的命令行参数被视为“ opt = value”,而要处理的工作是字符串解析,以“ =”分隔。相反,使用“ --opt值”时,参数的名称为“ opt”,因此我们面临着在命令行中获取下一个值的挑战。这就是@Arvid Requate使用$ {!OPTIND}(间接引用)的地方。我还是完全不了解,BashFAQ中的评论似乎警告了这种风格(http://mywiki.wooledge.org/BashFAQ/006)。顺便说一句,我不认为以前的海报有关OPTIND = $((($ OPTIND + 1))的重要性的评论是正确的。我是说
在此脚本的最新版本中,标志-v表示VERBOSE打印输出。
将其保存在名为“ cli-5.sh”的文件中,使其成为可执行文件,这些文件中的任何一个都将起作用,或者以所需的方式失败
./cli-5.sh -v --loglevel=44 --toc TRUE
./cli-5.sh -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9
./cli-5.sh --toc FALSE --loglevel=77
./cli-5.sh --toc=FALSE --loglevel=77
./cli-5.sh -l99 -t yyy
./cli-5.sh -l 99 -t yyy
Run Code Online (Sandbox Code Playgroud)
这是对intpu用户进行错误检查的示例输出
$ ./cli-5.sh --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh --toc= --loglevel=77
ERROR: value for toc undefined
Run Code Online (Sandbox Code Playgroud)
您应该考虑打开-v,因为它会打印出OPTIND和OPTARG的内部信息
#/usr/bin/env bash
## Paul Johnson
## 20171016
##
## Combines ideas from
## /sf/ask/28166421/
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035
# What I don't understand yet:
# In @Arvid REquate's answer, we have
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!
die() {
printf '%s\n' "$1" >&2
exit 1
}
printparse(){
if [ ${VERBOSE} -gt 0 ]; then
printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
fi
}
showme(){
if [ ${VERBOSE} -gt 0 ]; then
printf 'VERBOSE: %s\n' "$1" >&2;
fi
}
VERBOSE=0
loglevel=0
toc="TRUE"
optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do
showme "OPTARG: ${OPTARG[*]}"
showme "OPTIND: ${OPTIND[*]}"
case "${OPTCHAR}" in
-)
case "${OPTARG}" in
loglevel) #argument has no equal sign
opt=${OPTARG}
val="${!OPTIND}"
## check value. If negative, assume user forgot value
showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
if [[ "$val" == -* ]]; then
die "ERROR: $opt value must not have dash at beginning"
fi
## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
printparse "--${OPTARG}" " " "${val}"
loglevel="${val}"
shift
;;
loglevel=*) #argument has equal sign
opt=${OPTARG%=*}
val=${OPTARG#*=}
if [ "${OPTARG#*=}" ]; then
printparse "--${opt}" "=" "${val}"
loglevel="${val}"
## shift CAUTION don't shift this, fails othewise
else
die "ERROR: $opt value must be supplied"
fi
;;
toc) #argument has no equal sign
opt=${OPTARG}
val="${!OPTIND}"
## check value. If negative, assume user forgot value
showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
if [[ "$val" == -* ]]; then
die "ERROR: $opt value must not have dash at beginning"
fi
## OPTIND=$(( $OPTIND + 1 )) #??
printparse "--${opt}" " " "${val}"
toc="${val}"
shift
;;
toc=*) #argument has equal sign
opt=${OPTARG%=*}
val=${OPTARG#*=}
if [ "${OPTARG#*=}" ]; then
toc=${val}
printparse "--$opt" " -> " "$toc"
##shift ## NO! dont shift this
else
die "ERROR: value for $opt undefined"
fi
;;
help)
echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
exit 2
;;
*)
if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
echo "Unknown option --${OPTARG}" >&2
fi
;;
esac;;
h|-\?|--help)
## must rewrite this for all of the arguments
echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
exit 2
;;
l)
loglevel=${OPTARG}
printparse "-l" " " "${loglevel}"
;;
t)
toc=${OPTARG}
;;
v)
VERBOSE=1
;;
*)
if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
echo "Non-option argument: '-${OPTARG}'" >&2
fi
;;
esac
done
echo "
After Parsing values
"
echo "loglevel $loglevel"
echo "toc $toc"
Run Code Online (Sandbox Code Playgroud)
接受的答案很好地指出了 bash 内置的所有缺点getopts
。答案结束于:
因此,虽然可以编写更多代码来解决对长选项缺乏支持的问题,但这需要做更多的工作,并且在一定程度上违背了使用 getopt 解析器来简化代码的目的。
尽管我原则上同意该声明,但我认为我们在各种脚本中实现此功能的次数证明了为创建“标准化”、经过良好测试的解决方案付出一些努力是合理的。
因此,我getopts
通过getopts_long
在纯 bash 中实现来“升级”内置的bash,没有外部依赖。该函数的使用与内置的 100% 兼容getopts
。
通过在脚本中包含getopts_long
(托管在 GitHub 上),原始问题的答案可以简单地实现为:
source "${PATH_TO}/getopts_long.bash"
while getopts_long ':c: copyfile:' OPTKEY; do
case ${OPTKEY} in
'c'|'copyfile')
echo 'file supplied -- ${OPTARG}'
;;
'?')
echo "INVALID OPTION -- ${OPTARG}" >&2
exit 1
;;
':')
echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
exit 1
;;
*)
echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
exit 1
;;
esac
done
shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
385368 次 |
最近记录: |