これまでに提示されたすべてのソリューションは にコードを挿入しましcase ... in ... esac
たが、私の意見では、変更されたコマンドを使用する方がはるかに自然であるため、次のgetopts
関数を記述しました。
編集:
オプションの引数の型を指定できるようになりました (使用法を参照してください)。
$nextArg
さらに、オプション引数に「似ている」かどうかをテストする代わりに、関数は$nextArg
からの文字が含まれているかどうかをチェックするようになりました$optstring
。このように、 に含まれていないオプション文字は、 ' 必須の引数$optstring
と同様に、オプションの引数として使用できます。getopts
最新の変更:
$nextArg
オプション引数であるかどうかのテストを修正しました: ダッシュで始まる
かどうかをテストします。
このテストがないと、文字 from を含むオプションの引数は認識されません。$nextArg
$optstring
- regexp 型指定子が追加されました (使用法に関する情報を参照してください)。
- 修正: 0 は、int として指定されたオプションの引数として認識されません。
$nextArg
が int であるかどうかの単純化されたテスト。
- 型指定子正規表現と一致するかどうかをテストするために
::/.../:
使用します。
このようにして、(ほぼ (*)) Perl 正規表現の全機能を利用できます。
(*): 使用情報の最後の段落を参照してください。perl
$nextArg
- 修正済み: 複数の regexp 型指定子では機能しません:非貪欲な一致が必要なため、 /コンストラクトの代わりに
使用します。perl
grep
sed
使用法:
呼び出し:getopts-plus optstring name "$@"
optstring
: 通常の と同様ですが、オプション文字に::getopts
を追加することにより、オプションの引数でオプションを指定できます。
ただし、スクリプトが唯一のオプション引数としてオプションの引数を持つオプションを使用した呼び出しをサポートし、その後に非オプションの引数が続く場合、非オプションの引数はオプションの引数と見なされます。
運が良く、オプションの引数が整数であることが予想され、オプション以外の引数が文字列である場合、またはその逆の場合、整数の場合は:::iを、または:::s の場合は :::sを追加して型を指定できます。その問題を解決するための文字列。
それが当てはまらない場合は、オプション文字に::/.../を追加することで、オプションの引数に Perl 正規表現を指定できます。
Perl 正規表現の紹介については、こちらを参照してください: https://perldoc.perl.org/perlretut
注意: ATM、/.../
の後にのみ正規表現として認識されます::
。つまり、他の区切り文字も修飾子も使用m#...#a
できません。認識された。
オプションの引数を持つオプションの後に非オプションの引数がある場合、正規表現に一致する場合にのみ、オプションの引数と見なされます。
明確にするために:::/.../
引数の検証を目的としたものではなく、オプションの引数を持つオプションの引数と非オプションの引数を区別することだけを目的としています。
#!/bin/bash
# Invocation: getopts-plus optstring name "$@"\
# \
# optstring: Like normal getopts, but you may specify options with optional argument
# by appending :: to the option letter.\
# \
# However, if your script supports an invocation with an option with optional
# argument as the only option argument, followed by a non-option argument,
# the non-option argument will be considered to be the argument for the option.\
# \
# If you're lucky and the optional argument is expected to be an integer, whereas
# the non-option argument is a string or vice versa, you may specify the type by
# appending :::i for an integer or :::s for a string to solve that issue.\
# \
# If that doesn't apply, you may specify a Perl regexp for the optional arg by appending
# ::/.../ to the option letter.\
# See here for an introduction to Perl regexps: https://perldoc.perl.org/perlretut
# Please note: ATM, only /.../ will be recognised as a regexp after ::,\
# i. e. neither other delimiters, nor modifiers may be used, so e. g. m#...#a will
# not be recognised.\
# If there is a non-option argument after the option with optional argument, it will
# be considered to be the optional argument only if it matches the regexp.\
# To be clear: ::/.../ is not meant for argument validation but solely to discriminate
# between arguments for options with optional argument and non-option arguments.
function getopts-plus
{
local optstring=$1
local -n name=$2
shift 2
local optionalArgSuffixRE='::(?::[si]|/.*?/)?'
local optionalArgTypeCaptureRE=':::([si])|::(/.*?/)'
# If we pass 'opt' for 'name' (as I always do when using getopts) and there is
# also a local variable 'opt', the "outer" 'opt' will always be empty.
# I don't understand why a local variable interferes with caller's variable with
# same name in this case; however, we can easily circumvent this.
local opt_
# Extract options with optional arg
local -A isOptWithOptionalArg
while read opt_; do
# Using an associative array as set
isOptWithOptionalArg[$opt_]=1
done <<<$(perlGetCaptures "$optstring" "([a-zA-Z])$optionalArgSuffixRE")
# Extract all option letters (used to weed out possible optional args that are option args)
local optLetters=$(perlGetCaptures "$optstring" "([a-zA-Z])(?:$optionalArgSuffixRE|:)?")
# Save original optstring, then remove our suffix(es)
local optstringOrg=$optstring
optstring=$(perl -pe "s#$optionalArgSuffixRE##g" <<<$optstring)
getopts $optstring name "$@" || return # Return value is getopts' exit value.
# If current option is an option with optional arg and if an arg has been provided,
# check if that arg is not an option and if it isn't, check if that arg matches(*)
# the specified type, if any, and if it does or no type has been specified,
# assign it to OPTARG and inc OPTIND.
#
# (*) We detect an int because it's easy, but we assume a string if it's not an int
# because detecting a string would be complicated.
# So it sounds strange to call it a match if we know that the optional arg is specified
# to be a string, but merely that the provided arg is not an int, but in this context,
# "not an int" is equivalent to "string". At least I think so, but I might be wrong.
if ((isOptWithOptionalArg[$name])) && [[ ${!OPTIND} ]]; then
local nextArg=${!OPTIND} foundOpt=0
# Test if $nextArg is an option arg
if [[ $nextArg == -* ]]; then
# Check if $nextArg contains a letter from $optLetters.
# This way, an option not contained in $optstring can be
# used as optional arg, as with getopts' mandatory args.
local i
# Start at char 1 to skip the leading dash
for ((i = 1; i < ${#nextArg}; i++)); do
while read opt_; do
[[ ${nextArg:i:1} == $opt_ ]] && foundOpt=1 && break 2
done <<<$optLetters
done
((foundOpt)) && return
fi
# Extract type of optional arg if specified
local optArgType=$(perlGetCaptures "$optstringOrg" "$name(?:$optionalArgTypeCaptureRE)" '$1$2')
local nextArgIsOptArg=0
case $optArgType in
/*/) # Check if $nextArg matches regexp
perlMatch "$nextArg" "$optArgType" && nextArgIsOptArg=1
;;
[si]) # Check if $nextArg is an int
local nextArgIsInt=0
[[ $nextArg =~ ^[0-9]+$ ]] && nextArgIsInt=1
# Test if specified type and arg type match (see (*) above).
# N.B.: We need command groups since && and || between commands have same precedence.
{ [[ $optArgType == i ]] && ((nextArgIsInt)) || { [[ $optArgType == s ]] && ((! nextArgIsInt)); }; } && nextArgIsOptArg=1
;;
'') # No type or regexp specified => Assume $nextArg is optional arg.
nextArgIsOptArg=1
;;
esac
if ((nextArgIsOptArg)); then
OPTARG=$nextArg && ((OPTIND++))
fi
fi
}
# Uses perl to match \<string\> against \<regexp\>.\
# Returns with code 0 on a match and 1 otherwise.
function perlMatch # Args: <string> <regexp>
{
perl -e 'q('"$1"') =~ '"$2"' and exit 0; exit 1;'
}
# Uses perl to match \<string\> against \<regexp\>
# and prints each capture on a separate line.\
# If \<regexp\> contains more than one capture group,
# you must specify the \<line format\> which is an
# arbitrary Perl string containing your desired backrefs.\
# By default, merely $1 will be printed.
function perlGetCaptures # Args: <string> <regexp> [<line format>]
{
local lineFmt=${3:-\$1}
# Matching repeatedly with g option gives one set of captures at a time.
perl -e 'while (q('"$1"') =~ m#'"$2"'#g) { print(qq('"$lineFmt"') . "\n"); }'
}
必要がない場合に備えて、関数本体内にコメントがない同じスクリプト:
#!/bin/bash
# Invocation: getopts-plus optstring name "$@"\
# \
# optstring: Like normal getopts, but you may specify options with optional argument
# by appending :: to the option letter.\
# \
# However, if your script supports an invocation with an option with optional
# argument as the only option argument, followed by a non-option argument,
# the non-option argument will be considered to be the argument for the option.\
# \
# If you're lucky and the optional argument is expected to be an integer, whereas
# the non-option argument is a string or vice versa, you may specify the type by
# appending :::i for an integer or :::s for a string to solve that issue.\
# \
# If that doesn't apply, you may specify a Perl regexp for the optional arg by appending
# ::/.../ to the option letter.\
# See here for an introduction to Perl regexps: https://perldoc.perl.org/perlretut
# Please note: ATM, only /.../ will be recognised as a regexp after ::,\
# i. e. neither other delimiters, nor modifiers may be used, so e. g. m#...#a will
# not be recognised.\
# If there is a non-option argument after the option with optional argument, it will
# be considered to be the optional argument only if it matches the regexp.\
# To be clear: ::/.../ is not meant for argument validation but solely to discriminate
# between arguments for options with optional argument and non-option arguments.
function getopts-plus
{
local optstring=$1
local -n name=$2
shift 2
local optionalArgSuffixRE='::(?::[si]|/.*?/)?'
local optionalArgTypeCaptureRE=':::([si])|::(/.*?/)'
local opt_
local -A isOptWithOptionalArg
while read opt_; do
isOptWithOptionalArg[$opt_]=1
done <<<$(perlGetCaptures "$optstring" "([a-zA-Z])$optionalArgSuffixRE")
local optLetters=$(perlGetCaptures "$optstring" "([a-zA-Z])(?:$optionalArgSuffixRE|:)?")
local optstringOrg=$optstring
optstring=$(perl -pe "s#$optionalArgSuffixRE##g" <<<$optstring)
getopts $optstring name "$@" || return
if ((isOptWithOptionalArg[$name])) && [[ ${!OPTIND} ]]; then
local nextArg=${!OPTIND} foundOpt=0
if [[ $nextArg == -* ]]; then
local i
for ((i = 1; i < ${#nextArg}; i++)); do
while read opt_; do
[[ ${nextArg:i:1} == $opt_ ]] && foundOpt=1 && break 2
done <<<$optLetters
done
((foundOpt)) && return
fi
local optArgType=$(perlGetCaptures "$optstringOrg" "$name(?:$optionalArgTypeCaptureRE)" '$1$2')
local nextArgIsOptArg=0
case $optArgType in
/*/)
perlMatch "$nextArg" "$optArgType" && nextArgIsOptArg=1
;;
[si])
local nextArgIsInt=0
[[ $nextArg =~ ^[0-9]+$ ]] && nextArgIsInt=1
{ [[ $optArgType == i ]] && ((nextArgIsInt)) || { [[ $optArgType == s ]] && ((! nextArgIsInt)); }; } && nextArgIsOptArg=1
;;
'')
nextArgIsOptArg=1
;;
esac
if ((nextArgIsOptArg)); then
OPTARG=$nextArg && ((OPTIND++))
fi
fi
}
# Uses perl to match \<string\> against \<regexp\>.\
# Returns with code 0 on a match and 1 otherwise.
function perlMatch # Args: <string> <regexp>
{
perl -e 'q('"$1"') =~ '"$2"' and exit 0; exit 1;'
}
# Uses perl to match \<string\> against \<regexp\>
# and prints each capture on a separate line.\
# If \<regexp\> contains more than one capture group,
# you must specify the \<line format\> which is an
# arbitrary Perl string containing your desired backrefs.\
# By default, merely $1 will be printed.
function perlGetCaptures # Args: <string> <regexp> [<line format>]
{
local lineFmt=${3:-\$1}
perl -e 'while (q('"$1"') =~ m#'"$2"'#g) { print(qq('"$lineFmt"') . "\n"); }'
}
最新バージョンを使用したいくつかのテスト:
整数として指定されたオプションの arg 型。int-g
は渡されませんが、その後に非オプション文字列 arg が続きます。
$ . ./getopts-plus.sh
$ while getopts-plus 'b:c::de::f::g:::ia' opt -ab 99 -c 11 -def 55 -g "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == '' <-- Empty because "hello you" is not an int
上記と同様ですが、int arg を使用します。
$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ia' opt -ab 99 -c 11 -def 55 -g 7 "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == '7' <-- The passed int
-h
regexpのオプション オプションが追加されまし/^(a|b|ab|ba)$/
た。引数は渡されませんでした。
$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/a' opt -ab 99 -c 11 -def 55 -gh "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == ''
-------------------------
opt == 'h'
OPTARG == '' <-- Empty because "hello you" does not match the regexp
上記と同様ですが、正規表現に一致する引数を使用します。
$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/a' opt -ab 99 -c 11 -def 55 -gh ab "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == ''
-------------------------
opt == 'h'
OPTARG == 'ab' <-- The arg that matches the regexp
-i
regexp (英数字またはアンダースコアを意味する/^\w+$/
Perl トークンを使用) を使用して、別の regexp 型のオプション オプションを追加しました。引数は渡されませんでした。\w
$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/ai::/^\w+$/' opt -ab 99 -c 11 -def 55 -gh ab -i "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
[23:10:49]
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == ''
-------------------------
opt == 'h'
OPTARG == 'ab'
-------------------------
opt == 'i'
OPTARG == '' <-- Empty because "hello you" contains a space.
上記と同様ですが、正規表現に一致する引数を使用します。
$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/ai::/^\w+$/' opt -ab 99 -c 11 -def 55 -gh ab -i foo_Bar_1 "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
[23:15:23]
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == ''
-------------------------
opt == 'h'
OPTARG == 'ab'
-------------------------
opt == 'i'
OPTARG == 'foo_Bar_1' <-- Matched because it contains only alphanumeric chars and underscores.