比如,我有一个脚本,用这行代码调用:
./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?
当前回答
我使用optget和optgets的组合来解析带或不带参数的短选项和长选项,甚至是非选项(不带-或-的选项):
# catch wrong options and move non-options to the end of the string
args=$(getopt -l "$opt_long" "$opt_short" "$@" 2> >(sed -e 's/^/stderr/g')) || echo -n "Error: " && echo "$args" | grep -oP "(?<=^stderr).*" && exit 1
mapfile -t args < <(xargs -n1 <<< "$(echo "$args" | sed -E "s/(--[^ ]+) /\1=/g")" )
set -- "${args[@]}"
# parse short and long options
while getopts "$opt_short-:" opt; do
...
done
# remove all parsed options from $@
shift $((OPTIND-1)
这样,我就可以使用$opt_verbose这样的变量访问所有选项,而非选项可以通过默认变量$1、$2等访问:
echo "help:$opt_help"
echo "file:$opt_file"
echo "verbose:$opt_verbose"
echo "long_only:$opt_long_only"
echo "short_only:$opt_s"
echo "path:$1"
echo "mail:$2"
其中一个主要特点是,我能够以完全随机的顺序传递所有选项和非选项:
# $opt_file $1 $2 $opt_... $opt_... $opt_...
# /demo.sh --file=file.txt /dir info@example.com -V -h --long_only=yes -s
help:1
file:file.txt
verbose:1
long_only:yes
short_only:1
path:/dir
mail:info@example.com
更多详情:https://stackoverflow.com/a/74275254/318765
其他回答
有几种方法可以解析cmdline参数(例如GNU getopt(不可移植)vs BSD(MacOS)getopt vs getopts)-所有这些都有问题。此解决方案
是便携式的!没有依赖关系,仅依赖于bash内置允许短期和长期选项处理空格或同时在选项和参数之间使用=分隔符支持串联短选项样式-vxfhandles选项和可选参数(例如--color vs--color=always),正确检测和报告未知选项支持--表示选项结束,以及与相同功能集的替代方案相比,不需要代码膨胀。即简洁,因此易于维护
示例:任何
# flag
-f
--foo
# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"
# option with optional argument
--baz
--baz="Optional Hello"
#!/usr/bin/env bash
usage() {
cat - >&2 <<EOF
NAME
program-name.sh - Brief description
SYNOPSIS
program-name.sh [-h|--help]
program-name.sh [-f|--foo]
[-b|--bar <arg>]
[--baz[=<arg>]]
[--]
FILE ...
REQUIRED ARGUMENTS
FILE ...
input files
OPTIONS
-h, --help
Prints this and exits
-f, --foo
A flag option
-b, --bar <arg>
Option requiring an argument <arg>
--baz[=<arg>]
Option that has an optional argument <arg>. If <arg>
is not specified, defaults to 'DEFAULT'
--
Specify end of options; useful if the first non option
argument starts with a hyphen
EOF
}
fatal() {
for i; do
echo -e "${i}" >&2
done
exit 1
}
# For long option processing
next_arg() {
if [[ $OPTARG == *=* ]]; then
# for cases like '--opt=arg'
OPTARG="${OPTARG#*=}"
else
# for cases like '--opt arg'
OPTARG="${args[$OPTIND]}"
OPTIND=$((OPTIND + 1))
fi
}
# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "$@") # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
case "$optchar" in
h) usage; exit 0 ;;
f) foo=1 ;;
b) bar="$OPTARG" ;;
-) # long option processing
case "$OPTARG" in
help)
usage; exit 0 ;;
foo)
foo=1 ;;
bar|bar=*) next_arg
bar="$OPTARG" ;;
baz)
baz=DEFAULT ;;
baz=*) next_arg
baz="$OPTARG" ;;
-) break ;;
*) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
esac
;;
*) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
esac
done
shift $((OPTIND-1))
if [ "$#" -eq 0 ]; then
fatal "Expected at least one required argument FILE" \
"See '${0} --help' for usage"
fi
echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"
当我尝试这个问题时,上面的答案似乎有点bug——这是我发现更强大的解决方案:
boolean_arg=""
arg_with_value=""
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-b|--boolean-arg)
boolean_arg=true
shift
;;
-a|--arg-with-value)
arg_with_value="$2"
shift
shift
;;
-*)
echo "Unknown option: $1"
exit 1
;;
*)
arg_num=$(( $arg_num + 1 ))
case $arg_num in
1)
first_normal_arg="$1"
shift
;;
2)
second_normal_arg="$1"
shift
;;
*)
bad_args=TRUE
esac
;;
esac
done
# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
echo "first_normal_arg: $first_normal_arg"
echo "second_normal_arg: $second_normal_arg"
echo "boolean_arg: $boolean_arg"
echo "arg_with_value: $arg_with_value"
exit 0
fi
if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
exit 1
fi
另一个没有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)。
getopt()/getopts()是一个很好的选项。从此处复制:
“getopt”的简单用法如下小脚本所示:
#!/bin/bash
echo "Before getopt"
for i
do
echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
echo "-->$i"
done
我们所说的是,-b、 -c或-d将被允许,但-c后面跟着一个参数(“c:”表示)。如果我们称之为“g”并尝试:
bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
我们从两个参数开始“getopt”将选项和每个人都有自己的论点。它也是添加了“--”。
在@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[@]}"