我希望使用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,我还不能实现这些?
当前回答
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%的时间都是有效的。
其他回答
长选项可以被内置的标准getopts作为- " option "的"参数"解析。
这是可移植的本地POSIX shell -不需要外部程序或bashisms。
本指南将长选项作为-选项的参数实现,因此——alpha被getopts视为参数alpha为-,而——bravo=foo被参数bravo=foo视为-。true实参通过shell参数展开获取,更新$OPT和$OPTARG。
在本例中,-b和-c(以及它们的长形式——bravo和——charlie)具有强制实参。长选项的参数出现在等号之后,例如——bravo=foo(长选项的空格分隔符很难实现,参见下文)。
因为它使用了内置的getopts,所以这个解决方案支持像cmd——bravo=foo -ac FILE这样的用法(它组合了选项-a和-c,并将长选项与标准选项交织在一起),而这里的大多数其他答案要么很难做到,要么无法做到这一点。
die() { echo "$*" >&2; exit 2; } # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }
while getopts ab:c:-: OPT; do
# support long options: https://stackoverflow.com/a/28466267/519360
if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
fi
case "$OPT" in
a | alpha ) alpha=true ;;
b | bravo ) needs_arg; bravo="$OPTARG" ;;
c | charlie ) needs_arg; charlie="$OPTARG" ;;
??* ) die "Illegal option --$OPT" ;; # bad long option
? ) exit 2 ;; # bad short option (error reported via getopts)
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
When the option is a dash (-), it is a long option. getopts will have parsed the actual long option into $OPTARG, e.g. --bravo=foo originally sets OPT='-' and OPTARG='bravo=foo'. The if stanza sets $OPT to the contents of $OPTARG before the first equals sign (bravo in our example) and then removes that from the beginning of $OPTARG (yielding =foo in this step, or an empty string if there is no =). Finally, we strip the argument's leading =. At this point, $OPT is either a short option (one character) or a long option (2+ characters).
The case then matches either short or long options (the pipe, |, indicates that "or" operation. A long-only option like delta ) delta=true ;; doesn't need a pipe). For short options, getopts automatically complains about options and missing arguments, so we have to replicate those manually using the needs_arg function, which fatally exits when $OPTARG is empty. The ??* condition will match any remaining long option (? matches a single character and * matches zero or more, so ??* matches 2+ characters), allowing us to issue the "Illegal option" error before exiting.
与正常的gnu风格的长选项一样,提供——将停止解析,因此- -- --bravo=4将把$alpha设置为true,但$bravo将保持不变,$1将-bravo=4。我不能说我建议用前导破折号来命名文件,但这是表示它们不是选项的方法。
小错误:如果有人给出了一个无效的单字符长选项(它也不是一个短选项),这将退出一个错误,但没有消息(这个实现假设它是一个短选项)。您可以在case之前的条件中使用一个额外的变量来跟踪它,然后在最后的case条件中测试它,但我认为这是一个太麻烦的角落情况。
大写变量名:一般情况下,建议保留全大写变量供系统使用。我将$OPT保留为全大写,以与$OPTARG保持一致,但这确实打破了这种约定。我认为这很合适,因为这是系统应该做的事情,它应该是安全的;我还没有听说过任何使用这个变量名的标准。
要抱怨长选项的非预期实参:用翻转测试模拟needs_argg来抱怨一个非预期实参:
no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }
要接受带空格分隔参数的长选项:您可以用eval "ARG_B=\"\$$OPTIND\""(或使用bash的间接展开,ARG_B="${!OPTIND}")拉入下一个参数,然后增加$OPTIND,正如这个答案的旧版本所指出的那样,但它不可靠;getopts可以过早地终止,假设参数超出了它的作用域,并且一些实现不太适合手动操作$OPTIND。
内置的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优越的原因,尽管它只能处理单字母选项。
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解析器来简化代码的目的。
看一下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_调用一样简单。