我想设计一个shell脚本作为几个脚本的包装器。我想使用myshell.sh
为getopts
指定参数,并将相同顺序的其余参数传递给指定的脚本。
如果myshell.sh
执行如下:
myshell.sh -h hostname -s test.sh -d waittime param1 param2 param3
myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
myshell.sh param1 -h hostname -d waittime -s test.sh param2 param3
以上所有内容都应该能够作为
test.sh param1 param2 param3
是否可以使用 myshell.sh
中的选项参数并将剩余参数发布到基础脚本?
答案 0 :(得分:79)
很抱歉评论了一个旧帖子,但我想发帖给那些正在搜索如何做的人...
我想做一些与OP类似的事情,我找到了我需要的相关信息here和here
基本上,如果你想做类似的事情:
script.sh [options] ARG1 ARG2
然后得到这样的选项:
while getopts "h:u:p:d:" flag; do
case "$flag" in
h) HOSTNAME=$OPTARG;;
u) USERNAME=$OPTARG;;
p) PASSWORD=$OPTARG;;
d) DATABASE=$OPTARG;;
esac
done
然后你可以得到这样的位置论证:
ARG1=${@:$OPTIND:1}
ARG2=${@:$OPTIND+1:1}
更多信息和详细信息可通过上面的链接获得。
希望有所帮助!!
答案 1 :(得分:8)
混合opts和args:
ARGS=""
echo "options :"
while [ $# -gt 0 ]
do
unset OPTIND
unset OPTARG
while getopts as:c: options
do
case $options in
a) echo "option a no optarg"
;;
s) serveur="$OPTARG"
echo "option s = $serveur"
;;
c) cible="$OPTARG"
echo "option c = $cible"
;;
esac
done
shift $((OPTIND-1))
ARGS="${ARGS} $1 "
shift
done
echo "ARGS : $ARGS"
exit 1
结果:
bash test.sh -a arg1 arg2 -s serveur -c cible arg3
options :
option a no optarg
option s = serveur
option c = cible
ARGS : arg1 arg2 arg3
答案 2 :(得分:8)
#!/bin/bash
script_args=()
while [ $OPTIND -le "$#" ]
do
if getopts h:d:s: option
then
case $option
in
h) host_name="$OPTARG";;
d) wait_time="$OPTARG";;
s) script="$OPTARG";;
esac
else
script_args+=("${!OPTIND}")
((OPTIND++))
fi
done
"$script" "${script_args[@]}"
#!/bin/bash
echo "$0 $@"
$ PATH+=:. # Use the cases as written without prepending ./ to the scripts
$ myshell.sh -h hostname -s test.sh -d waittime param1 param2 param3
./test.sh param1 param2 param3
$ myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
./test.sh param1 param2 param3
$ myshell.sh param1 -h hostname -d waittime -s test.sh param2 param3
./test.sh param1 param2 param3
getopts
如果遇到位置参数,将失败。如果将其用作循环条件,则只要位置参数出现在选项之前,循环就会过早中断,就像在两个测试用例中一样。
因此,仅在处理完所有参数后,此循环才会中断。如果getopts
无法识别某些内容,我们仅假设它是一个位置参数,然后将其填充到数组中,同时手动递增getopts
的计数器。
按照书面规定,子脚本不能接受选项(仅位置参数),因为包装器脚本中的getopts
将吃掉它们并打印一条错误消息,同时将任何参数视为位置参数:>
$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./myshell.sh: illegal option -- a
./test.sh param1 param2 opt1 param3
如果我们知道子脚本只能接受位置参数,那么myshell.sh
可能应该在无法识别的选项上暂停。就像在case
块的末尾添加默认的最后一种情况一样简单:
\?) exit 1;;
$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./myshell.sh: illegal option -- a
如果子脚本需要接受选项(只要它们不与myshell.sh
中的选项发生冲突),我们可以通过在冒号之前加一个冒号来将getopts
切换为静默错误报告字符串:
if getopts :h:d:s: option
然后,我们将使用默认的后一种情况将所有无法识别的选项填充到script_args
中:
\?) script_args+=("-$OPTARG");;
$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./test.sh param1 param2 -a opt1 param3
答案 3 :(得分:2)
getopts
无法解析param1
和-n
选项的混合。
将param1-3置于其他选项中要好得多。
此外,您可以使用现有的库,例如shflags。它非常智能,易于使用。
最后一种方法是编写自己的函数来解析没有getopts的params,只需通过case
构造迭代所有参数。这是最难的方式,但它是完全符合您的期望的唯一方式。
答案 4 :(得分:0)
我想到了getopts
可以扩展到真正混合选项和位置参数的一种方式。我们的想法是在调用getopts
并将找到的任何位置参数分配给n1
,n2
,n3
等之间切换:
parse_args() {
_parse_args 1 "$@"
}
_parse_args() {
local n="$1"
shift
local options_func="$1"
shift
local OPTIND
"$options_func" "$@"
shift $(( OPTIND - 1 ))
if [ $# -gt 0 ]; then
eval test -n \${n$n+x}
if [ $? -eq 0 ]; then
eval n$n="\$1"
fi
shift
_parse_args $(( n + 1 )) "$options_func" "$@"
fi
}
然后在OP的案例中,您可以像以下一样使用它:
main() {
local n1='' n2='' n3=''
local duration hostname script
parse_args parse_main_options "$@"
echo "n1 = $n1"
echo "n2 = $n2"
echo "n3 = $n3"
echo "duration = $duration"
echo "hostname = $hostname"
echo "script = $script"
}
parse_main_options() {
while getopts d:h:s: opt; do
case "$opt" in
d) duration="$OPTARG" ;;
h) hostname="$OPTARG" ;;
s) script="$OPTARG" ;;
esac
done
}
main "$@"
运行它会显示输出:
$ myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
n1 = param1
n2 = param2
n3 = param3
duration = waittime
hostname = hostname
script = test.sh
只是一个概念证明,但也许对某人有用。
注意:如果使用parse_args
的一个函数调用使用parse_args
和的外部函数声明的另一个函数,则会出现问题例如local n4=''
,但内部的和 4个或更多位置参数传递给内部函数
答案 5 :(得分:0)
简单地掌握了一个快捷方式,它可以轻松处理选项和位置参数的混合(只留下$ @中的位置参数):
#!/bin/bash
while [ ${#} -gt 0 ];do OPTERR=0;OPTIND=1;getopts "p:o:hvu" arg;case "$arg" in
p) echo "Path: [$OPTARG]" ;;
o) echo "Output: [$OPTARG]" ;;
h) echo "Help" ;;
v) echo "Version" ;;
\?) SET+=("$1") ;;
*) echo "Coding error: '-$arg' is not handled by case">&2 ;;
esac;shift;[ "" != "$OPTARG" ] && shift;done
[ ${#SET[@]} -gt 0 ] && set "" "${SET[@]}" && shift
echo -e "=========\nLeftover (positional) parameters (count=$#) are:"
for i in `seq $#`;do echo -e "\t$i> [${!i}]";done
示例输出:
[root@hots:~]$ ./test.sh 'aa bb' -h -v -u -q 'cc dd' -p 'ee ff' 'gg hh' -o ooo
Help
Version
Coding error: '-u' is not handled by case
Path: [ee ff]
Output: [ooo]
=========
Leftover (positional) parameters (count=4) are:
1> [aa bb]
2> [-q]
3> [cc dd]
4> [gg hh]
[root@hots:~]$
答案 6 :(得分:0)
您可以直接实现自己的bash参数解析器,而不必使用getopts
。以此为工作示例。它可以同时处理名称和位置参数。
#!/bin/bash
function parse_command_line() {
local named_options;
local parsed_positional_arguments;
yes_to_all_questions="";
parsed_positional_arguments=0;
named_options=(
"-y" "--yes"
"-n" "--no"
"-h" "--help"
"-s" "--skip"
"-v" "--version"
);
function validateduplicateoptions() {
local item;
local variabletoset;
local namedargument;
local argumentvalue;
variabletoset="${1}";
namedargument="${2}";
argumentvalue="${3}";
if [[ -z "${namedargument}" ]]; then
printf "Error: Missing command line option for named argument '%s', got '%s'...\\n" "${variabletoset}" "${argumentvalue}";
exit 1;
fi;
for item in "${named_options[@]}";
do
if [[ "${item}" == "${argumentvalue}" ]]; then
printf "Warning: Named argument '%s' got possible invalid option '%s'...\\n" "${namedargument}" "${argumentvalue}";
exit 1;
fi;
done;
if [[ -n "${!variabletoset}" ]]; then
printf "Warning: Overriding the named argument '%s=%s' with '%s'...\\n" "${namedargument}" "${!variabletoset}" "${argumentvalue}";
else
printf "Setting '%s' named argument '%s=%s'...\\n" "${thing_name}" "${namedargument}" "${argumentvalue}";
fi;
eval "${variabletoset}='${argumentvalue}'";
}
# https://stackoverflow.com/questions/2210349/test-whether-string-is-a-valid-integer
function validateintegeroption() {
local namedargument;
local argumentvalue;
namedargument="${1}";
argumentvalue="${2}";
if [[ -z "${2}" ]];
then
argumentvalue="${1}";
fi;
if [[ -n "$(printf "%s" "${argumentvalue}" | sed s/[0-9]//g)" ]];
then
if [[ -z "${2}" ]];
then
printf "Error: The %s positional argument requires a integer, but it got '%s'...\\n" "${parsed_positional_arguments}" "${argumentvalue}";
else
printf "Error: The named argument '%s' requires a integer, but it got '%s'...\\n" "${namedargument}" "${argumentvalue}";
fi;
exit 1;
fi;
}
function validateposisionaloption() {
local variabletoset;
local argumentvalue;
variabletoset="${1}";
argumentvalue="${2}";
if [[ -n "${!variabletoset}" ]]; then
printf "Warning: Overriding the %s positional argument '%s=%s' with '%s'...\\n" "${parsed_positional_arguments}" "${variabletoset}" "${!variabletoset}" "${argumentvalue}";
else
printf "Setting the %s positional argument '%s=%s'...\\n" "${parsed_positional_arguments}" "${variabletoset}" "${argumentvalue}";
fi;
eval "${variabletoset}='${argumentvalue}'";
}
while [[ "${#}" -gt 0 ]];
do
case ${1} in
-y|--yes)
yes_to_all_questions="${1}";
printf "Named argument '%s' for yes to all questions was triggered.\\n" "${1}";
;;
-n|--no)
yes_to_all_questions="${1}";
printf "Named argument '%s' for no to all questions was triggered.\\n" "${1}";
;;
-h|--help)
printf "Print help here\\n";
exit 0;
;;
-s|--skip)
validateintegeroption "${1}" "${2}";
validateduplicateoptions g_installation_model_skip_commands "${1}" "${2}";
shift;
;;
-v|--version)
validateduplicateoptions branch_or_tag "${1}" "${2}";
shift;
;;
*)
parsed_positional_arguments=$((parsed_positional_arguments+1));
case ${parsed_positional_arguments} in
1)
validateposisionaloption branch_or_tag "${1}";
;;
2)
validateintegeroption "${1}";
validateposisionaloption g_installation_model_skip_commands "${1}";
;;
*)
printf "ERROR: Extra positional command line argument '%s' found.\\n" "${1}";
exit 1;
;;
esac;
;;
esac;
shift;
done;
if [[ -z "${g_installation_model_skip_commands}" ]];
then
g_installation_model_skip_commands="0";
fi;
}
您可以将此函数称为:
#!/bin/bash
source ./function_file.sh;
parse_command_line "${@}";
用法示例:
./test.sh as 22 -s 3
Setting the 1 positional argument 'branch_or_tag=as'...
Setting the 2 positional argument 'skip_commands=22'...
Warning: Overriding the named argument '-s=22' with '3'...
参考文献:
答案 7 :(得分:-1)
unix选项处理有一些标准,在shell编程中,getopts
是执行它们的最佳方式。几乎所有现代语言(perl,python)都在getopts
上有变体。
这只是一个简单的例子:
command [ options ] [--] [ words ]
每个选项必须以短划线-
开头,并且必须包含单个字符。
GNU项目引入了Long Options,从两个短划线--
开始,
然后是一个完整的词,--long_option
。 AST KSH项目有一个getopts,它还支持长选项,和长选项,以单个短划线-
开头,与find(1)
一样。
选项可能会或可能不会产生争论。
任何不以短划线-
开头的单词都将结束选项处理。
必须跳过字符串--
并结束选项处理。
任何剩余的参数都保留为位置参数。
Open Group有一个关于Utility Argument Syntax
答案 8 :(得分:-1)
你可以尝试这个技巧:在使用optargs进行while循环之后,只需使用此代码段
#shift away all the options so that only positional agruments
#remain in $@
for (( i=0; i<OPTIND-1; i++)); do
shift
done
POSITIONAL="$@"
然而,这种方法有一个错误:
也许它有更多的错误......
看看整个例子:
while getopts :abc opt; do
case $opt in
a)
echo found: -a
;;
b)
echo found: -b
;;
c)
echo found: -c
;;
\?) echo found bad option: -$OPTARG
;;
esac
done
#OPTIND-1 now points to the first arguments not beginning with -
#shift away all the options so that only positional agruments
#remain in $@
for (( i=0; i<OPTIND-1; i++)); do
shift
done
POSITIONAL="$@"
echo "positional: $POSITIONAL"
输出:
[root@host ~]# ./abc.sh -abc -de -fgh -bca haha blabla -m -c
found: -a
found: -b
found: -c
found bad option: -d
found bad option: -e
found bad option: -f
found bad option: -g
found bad option: -h
found: -b
found: -c
found: -a
positional: haha blabla -m -c