我正在寻找比 getopt/getopts 更干净、更“自记录”的处理 shell 脚本参数的方法。
它需要提供...
- 完全支持“=”或“ ”(空格)后带或不带值的长选项。
- 正确处理连字符选项名称(即 --ignore-case)
- 正确处理带引号的选项值(即--text“文本字符串”)
我想消除 getopt/getopts 所需的嵌入 case 语句的大循环的开销,并将选项处理减少到类似......
option=argumentparse "$@"
[[ option == "" ]] && helpShow
[[ option =~ -h|--help ]] && helpShow
[[ option =~ -v|--version ]] && versionShow
[[ option =~ -G|--GUI ]] && GUI=$TRUE
[[ option =~ --title ]] && TITLE=${option["--title"]}
在这里,argumentparse() 函数将各种语法可能性解析为一致的格式,可能是关联数组。
一定有什么东西被编码在某个地方。有任何想法吗?
(更新并重新命名)
答案1
由于这个问题已经被浏览了很多(至少对我来说)但没有提交答案,所以传递所采用的解决方案......
笔记
某些函数(例如多接口输出函数ifHelpShow()
)uiShow()
已被使用,但未包含在此处,因为它们的调用包含相关信息,但它们的实现不包含相关信息。
###############################################################################
# FUNCTIONS (bash 4.1.0)
###############################################################################
function isOption () {
# isOption "$@"
# Return true (0) if argument has 1 or more leading hyphens.
# Example:
# isOption "$@" && ...
# Note:
# Cannot use ifHelpShow() here since cannot distinguish 'isOption --help'
# from 'isOption "$@"' where first argument in "$@" is '--help'
# Revised:
# 20140117 docsalvage
#
# support both short and long options
[[ "${1:0:1}" == "-" ]] && return 0
return 1
}
function optionArg () {
ifHelpShow "$1" 'optionArg --option "$@"
Echo argument to option if any. Within "$@", option and argument may be separated
by space or "=". Quoted strings are preserved. If no argument, nothing echoed.
Return true (0) if option is in argument list, whether an option-argument supplied
or not. Return false (1) if option not in argument list. See also option().
Examples:
FILE=$(optionArg --file "$1")
if $(optionArg -f "$@"); then ...
optionArg --file "$@" && ...
Revised:
20140117 docsalvage' && return
#
# --option to find (without '=argument' if any)
local FINDOPT="$1"; shift
local OPTION=""
local ARG=
local o=
local re="^$FINDOPT="
#
# echo "option start: FINDOPT=$FINDOPT, o=$o, OPTION=$OPTION, ARG=$ARG, @=$@" >&2
#
# let "$@" split commandline, respecting quoted strings
for o in "$@"
do
# echo "FINDOPT=$FINDOPT, o=$o, OPTION=$OPTION, ARG=$ARG" >&2
# echo " o=$o" >&2
# echo "re=$re" >&2
#
# detect --option and handle --option=argument
[[ $o =~ $re ]] && { OPTION=$FINDOPT; ARG="${o/$FINDOPT=/}"; break; }
#
# $OPTION will be non-null if --option was detected in last pass through loop
[[ ! $OPTION ]] && [[ "$o" != $FINDOPT ]] && { continue; } # is a positional arg (no previous --option)
[[ ! $OPTION ]] && [[ "$o" == $FINDOPT ]] && { OPTION="$o"; continue; } # is the arg to last --option
[[ $OPTION ]] && isOption "$o" && { break; } # no more arguments
[[ $OPTION ]] && ! isOption "$o" && { ARG="$o"; break; } # only allow 1 argument
done
#
# echo "option final: FINDOPT=$FINDOPT, o=$o, OPTION=$OPTION, ARG=$ARG, @=$@" >&2
#
# use '-n' to remove any blank lines
echo -n "$ARG"
[[ "$OPTION" == "$FINDOPT" ]] && return 0
return 1
}
###############################################################################
# MAIN (bash 4.1.0) (excerpt of relevant lines)
###############################################################################
# options
[[ "$@" == "" ]] && { zimdialog --help ; exit 0; }
[[ "$1" == "--help" ]] && { zimdialog --help ; exit 0; }
[[ "$1" == "--version" ]] && { uiShow "version $VERSION\n"; exit 0; }
# options with arguments
TITLE="$(optionArg --title "$@")"
TIP="$( optionArg --tip "$@")"
FILE="$( optionArg --file "$@")"
答案2
这里有一个很好的答案,但OP明确要求更简单处理命令行选项。我认为没有比使用getopt
from更简单的解析复杂 shell 选项的方法了实用程序Linux:
$ cat opts.sh
#!/bin/bash
# Print usage and exit
usage() {
exec >&2 # Write everything to STDERR (consistent with getopt errors)
(($#)) && echo "$@"
echo "Usage: $0 [opts...] [args...]"
exit 1
}
# Use getopt to validate options and reorder them. On error print usage
OPTS=$(getopt -s bash -o 'ab:c' -l 'longopt,longopt2,longwitharg:' -- "$@") || usage
# Replace our arguments with the reordered version
eval set -- "$OPTS"
# At this point everything up to "--" is options
declare opt_a opt_c longopt longopt2 longwitharg
declare -a opt_b # Array to accumulate -b arguments
while (($#))
do
case $1 in
-a) opt_a=1;;
-b) opt_b+=("$2"); shift;;
-c) ((++opt_c));;
--longopt) longopt=1;;
--longopt2) ((++longopt2));;
--longwitharg) longwitharg=$2; shift;;
--) shift; break;; # We're done with options, shift over "--" and move on...
*) usage "Unknown argument: $1" # Should not happen unless getopt errors are ignored.
esac
# Always shift once (for options with arguments we already shifted in the case so it's the 2nd shift)
shift
done
echo "Remaining arguments after parsing options: $#"
# Now you can work directly with "$@" or slurp it in an array
args=("$@")
# Here's what we're left with:
declare -p opt_a opt_b opt_c longopt longopt2 longwitharg
# This is how you iterate over an array which may contain spaces and other field separators
for file in "${args[@]}"
do
echo "File arg: $file"
done
getopt
将进行验证并返回错误(除非您告诉它不要这样做,否则您可以自己捕获它们)。例如:
$ ./opts.sh --badopt
getopt: unrecognized option '--badopt'
其他所有内容均已正确订购和引用:
$ ./opts.sh -ab3 -b8 file1 -ccc --longopt --longwitharg=abc --longwitharg "abc def" "file with spaces"
Remaining arguments after parsing options: 2
declare -- opt_a="1"
declare -a opt_b=([0]="3" [1]="8")
declare -- opt_c="3"
declare -- longopt="1"
declare -- longopt2
declare -- longwitharg="abc def"
File arg: file1
File arg: file with spaces
这里的论点:
-a
是一个标志 - 如果存在则设置为 1-b
并重复,每个实例都被添加到opt_b
数组中-c
是一个计数器,计算 3 次出现的次数--longopt
是一个像这样的标志-a
--longopt2
是一个类似的计数器-c
(本例中未使用,因此未设置;在算术扩展)。--longwitharg
是一个带参数的正常选项。在此示例中,我们稍后在命令行上覆盖其值file1
和是剩余的参数,尽管它们是在随机位置指定的,但在getopt 对命令行重新排序file with spaces
后,它们都会出现在末尾。--
请注意,引用变量对于正确处理带空格的参数非常重要,尽管我自愿避免在不需要的地方使用引号,例如:
- 直接变量赋值:
longwitharg=$2
- 开关盒:
case $1 in
如果不确定,额外的引号通常不会有什么坏处。