我希望使用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,我还不能实现这些?
当前回答
这需要一些时间,但我想要全部:
短选项 长选项 不管有没有论点 非选项参数(没有“-”或“——”的参数) 顺序不重要(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,它可以重新组织一组选项,使其更容易解析。你也可以调整这种设计来处理长选项。使用示例:
aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
case "$1" in
(-a) aflag=yes;;
(-b) bflag=yes;;
(-f) flist="$flist $2"; shift;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done
# Process remaining non-option arguments
...
您可以对getoptlong命令使用类似的方案。
请注意,外部getopt程序的基本弱点是难以处理其中包含空格的参数,以及难以准确地保留这些空格。这就是内置getopts优越的原因,尽管它只能处理单字母选项。
看一下shFlags,它是一个可移植的shell库(意思是:sh, bash, dash, ksh, zsh在Linux, Solaris等上)。
它使得添加新标志就像在脚本中添加一行一样简单,并且它提供了一个自动生成的用法函数。
这是一个简单的Hello, world!使用shFlag:
#!/bin/sh
# source shflags from current directory
. ./shflags
# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'
# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
# say hello
echo "Hello, ${FLAGS_name}!"
对于具有支持长选项的增强getopt的操作系统(例如Linux),您可以这样做:
$ ./hello_world.sh --name Kate
Hello, Kate!
对于其他情况,您必须使用短选项:
$ ./hello_world.sh -n Kate
Hello, Kate!
添加一个新标志就像添加一个新的DEFINE_调用一样简单。
下面你可以找到bash中复杂选项解析的几种不同方法: http://mywiki.wooledge.org/ComplexOptionParsing
我确实创建了下面的一个,而且我认为它很好,因为它的代码最少 多头和空头选择都有效。使用这种方法,长选项也可以有多个参数。
#!/bin/bash
# Uses bash extensions. Not portable as written.
declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
case "${opt}" in
-) #OPTARG is name-of-long-option or name-of-long-option=value
if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
then
opt=${OPTARG/=*/}
OPTARG=${OPTARG#*=}
((OPTIND--))
else #with this --key value1 value2 format multiple arguments are possible
opt="$OPTARG"
OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
fi
((OPTIND+=longoptspec[$opt]))
continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
;;
loglevel)
loglevel=$OPTARG
;;
h|help)
echo "usage: $0 [--loglevel[=]<value>]" >&2
exit 2
;;
esac
break; done
done
# End of file
在ksh93中,getopts支持长名字…
while getopts "f(file):s(server):" flag
do
echo "$flag" $OPTIND $OPTARG
done
至少我找到的教程是这么说的。试试看。
下面是一个实际使用长选项的getopt的示例:
aflag=no
bflag=no
cargument=none
# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
# something went wrong, getopt will put out an error message for us
exit 1
fi
set -- $options
while [ $# -gt 0 ]
do
case $1 in
-a|--along) aflag="yes" ;;
-b|--blong) bflag="yes" ;;
# for options with required arguments, an additional shift is required
-c|--clong) cargument="$2" ; shift;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done