比如,我有一个脚本,用这行代码调用:
./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?
当前回答
另一个没有getopt[s]、POSIX、旧Unix风格的解决方案
与Bruno Bronosky发布的解决方案类似,这里没有使用getopt。
我的解决方案的主要区别在于,它允许将选项连接在一起,就像tar-xzf foo.tar.gz等于tar-x-z-f foo.tar.gif一样。就像在tar、ps等中一样,前导连字符对于短选项块是可选的(但这可以很容易地更改)。也支持长选项(但当块以一个开始时,则需要两个前导连字符)。
带有示例选项的代码
#!/bin/sh
echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"
echo
print_usage() {
echo "Usage:
$0 {a|b|c} [ARG...]
Options:
--aaa-0-args
-a
Option without arguments.
--bbb-1-args ARG
-b ARG
Option with one argument.
--ccc-2-args ARG1 ARG2
-c ARG1 ARG2
Option with two arguments.
" >&2
}
if [ $# -le 0 ]; then
print_usage
exit 1
fi
opt=
while :; do
if [ $# -le 0 ]; then
# no parameters remaining -> end option parsing
break
elif [ ! "$opt" ]; then
# we are at the beginning of a fresh block
# remove optional leading hyphen and strip trailing whitespaces
opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')
fi
# get the first character -> check whether long option
first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
[ "$first_chr" = - ] && long_option=T || long_option=F
# note to write the options here with a leading hyphen less
# also do not forget to end short options with a star
case $opt in
-)
# end of options
shift
break
;;
a*|-aaa-0-args)
echo "Option AAA activated!"
;;
b*|-bbb-1-args)
if [ "$2" ]; then
echo "Option BBB with argument '$2' activated!"
shift
else
echo "BBB parameters incomplete!" >&2
print_usage
exit 1
fi
;;
c*|-ccc-2-args)
if [ "$2" ] && [ "$3" ]; then
echo "Option CCC with arguments '$2' and '$3' activated!"
shift 2
else
echo "CCC parameters incomplete!" >&2
print_usage
exit 1
fi
;;
h*|\?*|-help)
print_usage
exit 0
;;
*)
if [ "$long_option" = T ]; then
opt=$(echo "$opt" | awk '{print substr($1, 2)}')
else
opt=$first_chr
fi
printf 'Error: Unknown option: "%s"\n' "$opt" >&2
print_usage
exit 1
;;
esac
if [ "$long_option" = T ]; then
# if we had a long option then we are going to get a new block next
shift
opt=
else
# if we had a short option then just move to the next character
opt=$(echo "$opt" | awk '{print substr($1, 2)}')
# if block is now empty then shift to the next one
[ "$opt" ] || shift
fi
done
echo "Doing something..."
exit 0
有关示例用法,请参阅下面的示例。
带参数选项的位置
不管有什么价值,带参数的选项并不是最后一个(只需要长选项)。因此,虽然在tar(至少在某些实现中)中,f选项需要是最后一个,因为文件名在后面(tar xzf bar.tar.gz有效,但tar xfz bar.tar.gif无效),但这里的情况并非如此(请参阅后面的示例)。
带参数的多个选项
作为另一个奖励,选项参数按选项的顺序由具有所需选项的参数消耗。只需使用命令行abc X Y Z(或-abc X Y Z)查看脚本的输出即可:
Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!
长选项也连接在一起
此外,您也可以在选项块中使用长选项,因为它们出现在选项块的最后。因此,以下命令行都是等效的(包括处理选项及其参数的顺序):
-cba Z Y Xcba Z Y X-cb-aaa-0-args Z Y X-c-bbb-1-args Z Y X-a--ccc-2-args Z Y-ba Xc Z Y b X a-c Z Y-b X-a--ccc-2-args Z Y--bbb-1-args X--aaa-0-args
所有这些都会导致:
Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...
不在此解决方案中
可选参数
带有可选参数的选项应该可以通过一些工作来实现,例如,通过查看是否有不带连字符的块;然后,用户需要在带有可选参数的块后面的每个块前面加上连字符。也许这太复杂了,无法与用户进行通信,所以在这种情况下,只需要一个前导连字符就可以了。
有了多个可能的参数,事情变得更加复杂。我建议不要让选项试图通过确定某个参数是否适合它来变得聪明(例如,选项只是将数字作为可选参数),因为这可能会在未来中断。
我个人更喜欢附加选项,而不是可选参数。
带等号的选项参数
就像可选参数一样,我不喜欢这个(顺便问一下,是否有讨论不同参数样式利弊的线程?)但如果你想这样做,你可能可以像在http://mywiki.wooledge.org/BashFAQ/035#Manual_loop带--long,arg=?*case语句,然后去掉等号(这是BTW网站,该网站说通过一些努力可以进行参数连接,但“将其作为练习留给读者”,这让我相信他们的话,但我从头开始)。
其他注意事项
POSIX兼容,即使在我必须处理的古老Busybox设置上也能工作(例如,缺少切割、头部和getopts)。
其他回答
在@brunobronosky的回答基础上,我添加了一个“预处理器”来处理一些常见的格式:
将--longopt=val展开为--longopt-val将-xyz展开为-x-y-z支持--表示标志的结束显示意外选项的错误小巧易读的选项开关
#!/bin/bash
# Report usage
usage() {
echo "Usage:"
echo "$(basename "$0") [options] [--] [file1, ...]"
}
invalid() {
echo "ERROR: Unrecognized argument: $1" >&2
usage
exit 1
}
# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
arg="$1"; shift
case "${END_OF_OPT}${arg}" in
--) ARGV+=("$arg"); END_OF_OPT=1 ;;
--*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
--*) ARGV+=("$arg") ;;
-*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
*) ARGV+=("$arg") ;;
esac
done
# Apply pre-processed options
set -- "${ARGV[@]}"
# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "${END_OF_OPT}${1}" in
-h|--help) usage; exit 0 ;;
-p|--password) shift; PASSWORD="$1" ;;
-u|--username) shift; USERNAME="$1" ;;
-n|--name) shift; names+=("$1") ;;
-q|--quiet) QUIET=1 ;;
-C|--copy) COPY=1 ;;
-N|--notify) NOTIFY=1 ;;
--stdin) READ_STDIN=1 ;;
--) END_OF_OPT=1 ;;
-*) invalid "$1" ;;
*) POSITIONAL+=("$1") ;;
esac
shift
done
# Restore positional parameters
set -- "${POSITIONAL[@]}"
我想提交我的项目:https://github.com/flyingangel/argparser
source argparser.sh
parse_args "$@"
就这么简单。环境将填充与参数同名的变量
保留未处理参数的解决方案。包括演示。
这是我的解决方案。它非常灵活,不像其他软件包,不需要外部包,并且可以干净地处理遗留的参数。
用法为:/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将作为单个参数处理。不过,您可以很容易地在我的代码中编写额外的代码,以添加此功能。
这是我在函数中如何避免在堆栈的较高位置同时中断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
...
}
根据这里的其他答案,这是我的版本:
#!/bin/bash
set -e
function parse() {
for arg in "$@"; do # transform long options to short ones
shift
case "$arg" in
"--name") set -- "$@" "-n" ;;
"--verbose") set -- "$@" "-v" ;;
*) set -- "$@" "$arg"
esac
done
while getopts "n:v" optname # left to ":" are flags that expect a value, right to the ":" are flags that expect nothing
do
case "$optname" in
"n") name=${OPTARG} ;;
"v") verbose=true ;;
esac
done
shift "$((OPTIND-1))" # shift out all the already processed options
}
parse "$@"
echo "hello $name"
if [ ! -z $verbose ]; then echo 'nice to meet you!'; fi
用法:
$ ./parse.sh
hello
$ ./parse.sh -n YOUR_NAME
hello YOUR_NAME
$ ./parse.sh -n YOUR_NAME -v
hello YOUR_NAME
nice to meet you!
$ ./parse.sh -v -n YOUR_NAME
hello YOUR_NAME
nice to meet you!
$ ./parse.sh -v
hello
nice to meet you!