我希望使用shell脚本调用长形式和短形式的命令行选项。
我知道可以使用getopts,但是像在Perl中一样,我不能用shell做同样的事情。
关于如何做到这一点的任何想法,这样我就可以使用如下选项:
./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/
在上面,这两个命令对我的shell意味着同样的事情,但使用getopts,我还不能实现这些?
我希望使用shell脚本调用长形式和短形式的命令行选项。
我知道可以使用getopts,但是像在Perl中一样,我不能用shell做同样的事情。
关于如何做到这一点的任何想法,这样我就可以使用如下选项:
./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/
在上面,这两个命令对我的shell意味着同样的事情,但使用getopts,我还不能实现这些?
当前回答
另一种方式…
# 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
其他回答
hm.
对纯bash选项不太满意。为什么不使用perl来得到你想要的。直接解析$*数组,并自动命名您的选项。
简单的帮助脚本:
#!/usr/bin/perl
use Getopt::Long;
my $optstring = shift;
my @opts = split(m#,#, $optstring);
my %opt;
GetOptions(\%opt, @opts);
print "set -- " . join(' ', map("'$_'", @ARGV)) . ";";
my $xx;
my $key;
foreach $key (keys(%opt))
{
print "export $key='$opt{$key}'; ";
}
然后你可以在你的脚本中使用一行,例如:
#!/bin/bash
eval `getopts.pl reuse:s,long_opt:s,hello $*`;
echo "HELLO: $hello"
echo "LONG_OPT: $long_opt"
echo "REUSE: $reuse"
echo $*
/tmp/script.sh hello——reuse me——long_opt whatever_you_want_except_spaces——hello 1 2 3
你好:1 LONG_OPT: whatever_you_want_except空格 重用:我
1, 2, 3
这里唯一需要注意的是空格不适用。但它避免了bash相当复杂的循环语法,适用于长参数,自动将它们命名为变量并自动调整$*的大小,因此99%的时间都是有效的。
可以考虑以下三种实现方式:
Bash内置的getopts。这不支持带有双破折号前缀的长选项名。它只支持单字符选项。 BSD UNIX实现的独立getopt命令(这是MacOS使用的)。这也不支持长选项。 独立getopt的GNU实现。GNU getopt(3)(由Linux上的命令行getopt(1)使用)支持解析长选项。
其他一些答案给出了使用bash内置getopts模拟长选项的解决方案。该解决方案实际上生成了一个字符为“-”的短选项。所以你得到“——”作为标志。然后,后面的所有内容都变成OPTARG,并使用嵌套的case测试OPTARG。
这很聪明,但也需要注意:
getopts不能强制执行opt规范。如果用户提供了无效的选项,它不能返回错误。在解析OPTARG时,您必须自己进行错误检查。 OPTARG用于长选项名称,当长选项本身有参数时,这会使使用复杂化。你最终不得不自己编写代码作为一个额外的案例。
因此,虽然可以编写更多的代码来解决长选项支持不足的问题,但工作量要大得多,并且在一定程度上违背了使用getopt解析器来简化代码的目的。
这需要一些时间,但我想要全部:
短选项 长选项 不管有没有论点 非选项参数(没有“-”或“——”的参数) 顺序不重要(script.sh /file -V或script.sh -V /file) 抓住错误用法 在不同的脚本中使用它作为模块,而不需要更改多行代码
最后,我提出了下面的解决方案,它使用getopt来捕获错误并将非选项移动到列表的末尾,然后再使用getopts来解析短选项和长选项。
所有的选项都会自动解析,将它们的长选项名作为变量名(请看例子):
# create string of short options
opt_short=$(printf "%s" "${!options[@]}")
# create string of long options
opt_long="$(printf ",%s" "${options[@]}")"
# 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
# create new array of options
mapfile -t args < <(xargs -n1 <<< "$(echo "$args" | sed -E "s/(--[^ ]+) '/\1='/g")" )
# overwrite $@ (options)
set -- "${args[@]}"
# parse options ([h]=help sets the variable "$opt_help" and [V]="" sets the variable "$opt_V")
while getopts "$opt_short-:" opt; do
echo "$opt:$OPTARG"
# long option
if [[ "$opt" == "-" ]]; then
# extract long option name
opt="${OPTARG%%=*}"
# extract long option argument (may be empty)
OPTARG="${OPTARG#"$opt"}"
# remove "=" from long option argument
OPTARG="${OPTARG#=}"
# set variable name
opt=opt_$opt
# short option without argument uses long option name as variable name
elif [[ "${options[$opt]+x}" ]] && [[ "${options[$opt]}" ]]; then
opt=opt_${options[$opt]}
# short option with argument uses long option name as variable name
elif [[ "${options[$opt:]+x}" ]] && [[ "${options[$opt:]}" ]]; then
opt=opt_${options[$opt:]}
# short option without long option name uses short option name as variable name
else
opt=opt_$opt
fi
# remove double colon
opt="${opt%:}"
# options without arguments are set to 1
[[ ! $OPTARG ]] && OPTARG=1
# replace hyphen against underscore
opt="${opt//-/_}"
# set variable variables (replaces hyphen against underscore)
printf -v "$opt" '%s' "$OPTARG"
done
现在,我只需要定义所需的选项名称和源代码脚本:
# import options module
declare -A options=( [h]=help [f:]=file: [V]=verbose [0]=long_only: [s]="" )
source "/usr/local/bin/inc/options.sh";
# display help text
if [[ $opt_help ]]; then
echo "help text"
exit
fi
# output
echo "opt_help:$opt_help"
echo "opt_file:$opt_file"
echo "opt_verbose:$opt_verbose"
echo "opt_long_only:$opt_long_only"
echo "opt_short_only:$opt_s"
echo "opt_path:$1"
echo "opt_mail:$2"
在调用脚本时,可以完全随机地传递所有选项和非选项:
# $opt_file $1 $2 $opt_V $opt_long_only $opt_s
# /demo.sh --file=file.txt /dir info@example.com -V --long_only=yes -s
opt_help:1
opt_file:file.txt
opt_verbose:1
opt_long_only:yes
opt_short_only:1
opt_path=/dir
opt_mail:info@example.com
笔记
在选项数组中,在选项名称后添加:以启用参数。 如果没有给出较长的选项名,则变量名将是$opt_X,其中X是较短的选项名。 如果您希望使用较长的选项名而不定义较短的选项名,则将数组索引设置为一个数字,如上面示例中使用[0]=long_only所做的那样。当然,每个数组下标必须是唯一的。
使用的技术
不使用临时文件捕获stderr 将字符串转换为数组 使用:来解析getopt参数 使用getopts解析长选项名
内置的getopts不能做到这一点。有一个外部的getopt(1)程序可以做到这一点,但是您只能在Linux上从util-linux包中获得它。它附带一个示例脚本getop -parse.bash。
还有一个getopts_long被写成shell函数。
内置的getopts只解析短选项(ksh93除外), 但是您仍然可以添加几行脚本来使getopts处理长选项。
以下是在http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts中找到的部分代码
#== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
#== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
[foo]=f
[bar]=b
[foobar]=F
[barfoo]=B
[help]=h
[man]=h
)
#== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
#== translate long options to short ==#
if [[ "x$OPTION" == "x-" ]]; then
LONG_OPTION=$OPTARG
LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
LONG_OPTIND=-1
[[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
[[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
OPTION=${ARRAY_OPTS[$LONG_OPTION]}
[[ "x$OPTION" = "x" ]] && OPTION="?" OPTARG="-$LONG_OPTION"
if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then
OPTION=":" OPTARG="-$LONG_OPTION"
else
OPTARG="$LONG_OPTARG";
if [[ $LONG_OPTIND -ne -1 ]]; then
[[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
shift $OPTIND
OPTIND=1
fi
fi
fi
fi
#== options follow by another option instead of argument ==#
if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then
OPTARG="$OPTION" OPTION=":"
fi
#== manage options ==#
case "$OPTION" in
f ) foo=1 bar=0 ;;
b ) foo=0 bar=1 ;;
B ) barfoo=${OPTARG} ;;
F ) foobar=1 && foobar_name=${OPTARG} ;;
h ) usagefull && exit 0 ;;
: ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
esac
done
shift $((${OPTIND} - 1))
下面是一个测试:
# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2
# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2
另外,在最近的Korn Shell ksh93中,getopts可以自然地解析长选项,甚至显示类似的手册页。(参见http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options)