如何在bash中使用getopts的示例

我想这样调用myscript文件:

$ ./myscript -s 45 -p any_string

$ ./myscript -h  #should display help
$ ./myscript     #should display help

我的要求是:

  • getopt来获取输入参数
  • 检查-s是否存在,如果不存在则返回错误
  • 检查-s之后的值是否为45或90
  • 检查-p是否存在,并且后面有一个输入字符串
  • 如果用户输入./myscript -h或只是./myscript,则显示帮助

我尝试了到目前为止这段代码:

#!/bin/bash
while getopts "h:s:" arg; do
case $arg in
h)
echo "usage"
;;
s)
strength=$OPTARG
echo $strength
;;
esac
done

但是使用这些代码,我会得到错误。如何与Bash和getopt?

775853 次浏览

getopt打包的例子(我的发行版把它放在/usr/share/getopt/getopt-parse.bash中)看起来它涵盖了你所有的情况:

#!/bin/bash


# A small example program for using the new getopt(1) program.
# This program will only work with bash(1)
# An similar program using the tcsh(1) script language can be found
# as parse.tcsh


# Example input and output (from the bash prompt):
# ./parse.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long "
# Option a
# Option c, no argument
# Option c, argument 'more'
# Option b, argument ' very long '
# Remaining arguments:
# --> 'par1'
# --> 'another arg'
# --> 'wow!*\?'


# Note that we use `"$@"' to let each command-line parameter expand to a
# separate word. The quotes around '$@' are essential!
# We need TEMP as the `eval set --' would nuke the return value of getopt.
TEMP=$(getopt -o ab:c:: --long a-long,b-long:,c-long:: \
-n 'example.bash' -- "$@")


if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi


# Note the quotes around '$TEMP': they are essential!
eval set -- "$TEMP"


while true ; do
case "$1" in
-a|--a-long) echo "Option a" ; shift ;;
-b|--b-long) echo "Option b, argument '$2'" ; shift 2 ;;
-c|--c-long)
# c has an optional argument. As we are in quoted mode,
# an empty parameter will be generated if its optional
# argument is not found.
case "$2" in
"") echo "Option c, no argument"; shift 2 ;;
*)  echo "Option c, argument '$2'" ; shift 2 ;;
esac ;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
echo "Remaining arguments:"
for arg do echo '--> '"'$arg'" ; done
#!/bin/bash


usage() { echo "Usage: $0 [-s <45|90>] [-p <string>]" 1>&2; exit 1; }


while getopts ":s:p:" o; do
case "${o}" in
s)
s=${OPTARG}
((s == 45 || s == 90)) || usage
;;
p)
p=${OPTARG}
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))


if [ -z "${s}" ] || [ -z "${p}" ]; then
usage
fi


echo "s = ${s}"
echo "p = ${p}"

示例运行:

$ ./myscript.sh
Usage: ./myscript.sh [-s <45|90>] [-p <string>]


$ ./myscript.sh -h
Usage: ./myscript.sh [-s <45|90>] [-p <string>]


$ ./myscript.sh -s "" -p ""
Usage: ./myscript.sh [-s <45|90>] [-p <string>]


$ ./myscript.sh -s 10 -p foo
Usage: ./myscript.sh [-s <45|90>] [-p <string>]


$ ./myscript.sh -s 45 -p foo
s = 45
p = foo


$ ./myscript.sh -s 90 -p bar
s = 90
p = bar

我知道这个问题已经有答案了,但是为了记录,为了任何和我有同样要求的人,我决定张贴这个相关的答案。代码中充满了解释代码的注释。

答:更新

将文件保存为getopt.sh:

#!/bin/bash


function get_variable_name_for_option {
local OPT_DESC=${1}
local OPTION=${2}
local VAR=$(echo ${OPT_DESC} | sed -e "s/.*\[\?-${OPTION} \([A-Z_]\+\).*/\1/g" -e "s/.*\[\?-\(${OPTION}\).*/\1FLAG/g")


if [[ "${VAR}" == "${1}" ]]; then
echo ""
else
echo ${VAR}
fi
}


function parse_options {
local OPT_DESC=${1}
local INPUT=$(get_input_for_getopts "${OPT_DESC}")


shift
while getopts ${INPUT} OPTION ${@};
do
[ ${OPTION} == "?" ] && usage
VARNAME=$(get_variable_name_for_option "${OPT_DESC}" "${OPTION}")
[ "${VARNAME}" != "" ] && eval "${VARNAME}=${OPTARG:-true}" # && printf "\t%s\n" "* Declaring ${VARNAME}=${!VARNAME} -- OPTIONS='$OPTION'"
done


check_for_required "${OPT_DESC}"


}


function check_for_required {
local OPT_DESC=${1}
local REQUIRED=$(get_required "${OPT_DESC}" | sed -e "s/\://g")
while test -n "${REQUIRED}"; do
OPTION=${REQUIRED:0:1}
VARNAME=$(get_variable_name_for_option "${OPT_DESC}" "${OPTION}")
[ -z "${!VARNAME}" ] && printf "ERROR: %s\n" "Option -${OPTION} must been set." && usage
REQUIRED=${REQUIRED:1}
done
}


function get_input_for_getopts {
local OPT_DESC=${1}
echo ${OPT_DESC} | sed -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/[][ -]//g"
}


function get_optional {
local OPT_DESC=${1}
echo ${OPT_DESC} | sed -e "s/[^[]*\(\[[^]]*\]\)[^[]*/\1/g" -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/[][ -]//g"
}


function get_required {
local OPT_DESC=${1}
echo ${OPT_DESC} | sed -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/\[[^[]*\]//g" -e "s/[][ -]//g"
}


function usage {
printf "Usage:\n\t%s\n" "${0} ${OPT_DESC}"
exit 10
}

然后你可以这样使用它:

#!/bin/bash
#
# [ and ] defines optional arguments
#


# location to getopts.sh file
source ./getopt.sh
USAGE="-u USER -d DATABASE -p PASS -s SID [ -a START_DATE_TIME ]"
parse_options "${USAGE}" ${@}


echo ${USER}
echo ${START_DATE_TIME}

旧的回答:

最近我需要使用一种通用方法。我想到了这个解决方案:

#!/bin/bash
# Option Description:
# -------------------
#
# Option description is based on getopts bash builtin. The description adds a variable name feature to be used
# on future checks for required or optional values.
# The option description adds "=>VARIABLE_NAME" string. Variable name should be UPPERCASE. Valid characters
# are [A-Z_]*.
#
# A option description example:
#   OPT_DESC="a:=>A_VARIABLE|b:=>B_VARIABLE|c=>C_VARIABLE"
#
# -a option will require a value (the colon means that) and should be saved in variable A_VARIABLE.
# "|" is used to separate options description.
# -b option rule applies the same as -a.
# -c option doesn't require a value (the colon absense means that) and its existence should be set in C_VARIABLE
#
#   ~$ echo get_options ${OPT_DESC}
#   a:b:c
#   ~$
#




# Required options
REQUIRED_DESC="a:=>REQ_A_VAR_VALUE|B:=>REQ_B_VAR_VALUE|c=>REQ_C_VAR_FLAG"


# Optional options (duh)
OPTIONAL_DESC="P:=>OPT_P_VAR_VALUE|r=>OPT_R_VAR_FLAG"


function usage {
IFS="|"
printf "%s" ${0}
for i in ${REQUIRED_DESC};
do
VARNAME=$(echo $i | sed -e "s/.*=>//g")
printf " %s" "-${i:0:1} $VARNAME"
done


for i in ${OPTIONAL_DESC};
do
VARNAME=$(echo $i | sed -e "s/.*=>//g")
printf " %s" "[-${i:0:1} $VARNAME]"
done
printf "\n"
unset IFS
exit
}


# Auxiliary function that returns options characters to be passed
# into 'getopts' from a option description.
# Arguments:
#   $1: The options description (SEE TOP)
#
# Example:
#   OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR"
#   OPTIONS=$(get_options ${OPT_DESC})
#   echo "${OPTIONS}"
#
# Output:
#   "h:f:PW"
function get_options {
echo ${1} | sed -e "s/\([a-zA-Z]\:\?\)=>[A-Z_]*|\?/\1/g"
}


# Auxiliary function that returns all variable names separated by '|'
# Arguments:
#       $1: The options description (SEE TOP)
#
# Example:
#       OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR"
#       VARNAMES=$(get_values ${OPT_DESC})
#       echo "${VARNAMES}"
#
# Output:
#       "H_VAR|F_VAR|P_VAR|W_VAR"
function get_variables {
echo ${1} | sed -e "s/[a-zA-Z]\:\?=>\([^|]*\)/\1/g"
}


# Auxiliary function that returns the variable name based on the
# option passed by.
# Arguments:
#   $1: The options description (SEE TOP)
#   $2: The option which the variable name wants to be retrieved
#
# Example:
#   OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR"
#   H_VAR=$(get_variable_name ${OPT_DESC} "h")
#   echo "${H_VAR}"
#
# Output:
#   "H_VAR"
function get_variable_name {
VAR=$(echo ${1} | sed -e "s/.*${2}\:\?=>\([^|]*\).*/\1/g")
if [[ ${VAR} == ${1} ]]; then
echo ""
else
echo ${VAR}
fi
}


# Gets the required options from the required description
REQUIRED=$(get_options ${REQUIRED_DESC})


# Gets the optional options (duh) from the optional description
OPTIONAL=$(get_options ${OPTIONAL_DESC})


# or... $(get_options "${OPTIONAL_DESC}|${REQUIRED_DESC}")


# The colon at starts instructs getopts to remain silent
while getopts ":${REQUIRED}${OPTIONAL}" OPTION
do
[[ ${OPTION} == ":" ]] && usage
VAR=$(get_variable_name "${REQUIRED_DESC}|${OPTIONAL_DESC}" ${OPTION})
[[ -n ${VAR} ]] && eval "$VAR=${OPTARG}"
done


shift $(($OPTIND - 1))


# Checks for required options. Report an error and exits if
# required options are missing.


# Using function version ...
VARS=$(get_variables ${REQUIRED_DESC})
IFS="|"
for VARNAME in $VARS;
do
[[ -v ${VARNAME} ]] || usage
done
unset IFS


# ... or using IFS Version (no function)
OLDIFS=${IFS}
IFS="|"
for i in ${REQUIRED_DESC};
do
VARNAME=$(echo $i | sed -e "s/.*=>//g")
[[ -v ${VARNAME} ]] || usage
printf "%s %s %s\n" "-${i:0:1}" "${!VARNAME:=present}" "${VARNAME}"
done
IFS=${OLDIFS}

我没有粗略地测试这个,所以我可能会有一些bug。

原始代码的问题是:

  • h:在不应该的地方期望参数,因此将其更改为h(不带冒号)
  • 要想得到-p any_string,你需要将p:添加到参数列表中

基本上,选项后面的:意味着它需要参数。


getopts的基本语法是(参见:man bash):

getopts OPTSTRING VARNAME [ARGS...]

地点:

  • OPTSTRING是包含预期参数列表的字符串,

    • h -检查选项-h 没有参数;在不支持的选项上给出错误;
    • h: -检查选项-h 参数;在不支持的选项上给出错误;
    • abc -检查选项-a-b-c;不支持选项的给了错误;
    • :abc -检查选项-a-b-c;不支持选项的沉默错误;

      注意:换句话说,在options前面加冒号可以处理代码中的错误。在不支持选项的情况下,变量将包含__ABC0,在缺少值的情况下包含: < / p >

    • 李< / ul > < / >
    • OPTARG -被设置为当前参数值,

    • OPTERR -指示Bash是否应该显示错误消息。

    所以代码可以是:

    #!/usr/bin/env bash
    usage() { echo "$0 usage:" && grep " .)\ #" $0; exit 0; }
    [ $# -eq 0 ] && usage
    while getopts ":hs:p:" arg; do
    case $arg in
    p) # Specify p value.
    echo "p is ${OPTARG}"
    ;;
    s) # Specify strength, either 45 or 90.
    strength=${OPTARG}
    [ $strength -eq 45 -o $strength -eq 90 ] \
    && echo "Strength is $strength." \
    || echo "Strength needs to be either 45 or 90, $strength found instead."
    ;;
    h | *) # Display help.
    usage
    exit 0
    ;;
    esac
    done
    

    使用示例:

    $ ./foo.sh
    ./foo.sh usage:
    p) # Specify p value.
    s) # Specify strength, either 45 or 90.
    h | *) # Display help.
    $ ./foo.sh -s 123 -p any_string
    Strength needs to be either 45 or 90, 123 found instead.
    p is any_string
    $ ./foo.sh -s 90 -p any_string
    Strength is 90.
    p is any_string
    

    参见:小的getopts教程在Bash黑客维基

POSIX 7示例

同样值得检查标准:http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html中的示例

aflag=
bflag=
while getopts ab: name
do
case $name in
a)    aflag=1;;
b)    bflag=1
bval="$OPTARG";;
?)   printf "Usage: %s: [-a] [-b value] args\n" $0
exit 2;;
esac
done
if [ ! -z "$aflag" ]; then
printf "Option -a specified\n"
fi
if [ ! -z "$bflag" ]; then
printf 'Option -b "%s" specified\n' "$bval"
fi
shift $(($OPTIND - 1))
printf "Remaining arguments are: %s\n" "$*"

然后我们可以试一下:

$ sh a.sh
Remaining arguments are:
$ sh a.sh -a
Option -a specified
Remaining arguments are:
$ sh a.sh -b
No arg for -b option
Usage: a.sh: [-a] [-b value] args
$ sh a.sh -b myval
Option -b "myval" specified
Remaining arguments are:
$ sh a.sh -a -b myval
Option -a specified
Option -b "myval" specified
Remaining arguments are:
$ sh a.sh remain
Remaining arguments are: remain
$ sh a.sh -- -a remain
Remaining arguments are: -a remain

在Ubuntu 17.10中测试,sh是dash 0.5.8。

使用getopt

为什么getopt ?

解析详细的命令行参数,以避免混淆,并澄清我们正在解析的选项,以便命令的读者能够理解发生了什么。

什么是getopt?

getopt用于分解(解析)命令行中的选项,以便shell过程进行解析,并检查合法选项。它使用GNU getopt(3)例程来做到这一点。

getopt可以有以下类型的选项。

  1. 没有价值的选择
  2. 键值对选项

注意:在本文档中,在解释语法时:

  • 在语法/示例中,[]内的任何内容都是可选参数。
  • 是一个占位符,这意味着它应该用实际值代替。

如何使用__abc0 ?

语法:First Form

getopt optstring parameters

例子:

# This is correct
getopt "hv:t::" -v 123 -t123
getopt "hv:t::" -v123 -t123  # -v and 123 doesn't have whitespace


# -h takes no value.
getopt "hv:t::" -h -v123




# This is wrong. after -t can't have whitespace.
# Only optional params cannot have whitespace between key and value
getopt "hv:t::" -v 123 -t 123


# Multiple arguments that takes value.
getopt "h:v:t::g::" -h abc -v 123 -t21


# Multiple arguments without value
# All of these are correct
getopt "hvt" -htv
getopt "hvt" -h -t -v
getopt "hvt" -tv -h

这里h,v,t是选项,-h -v -t是命令行中应该给出的选项。

  1. 'h'是一个无值选项。
  2. 'v:'表示选项-v具有值和 是强制性选项。':'表示有值
  3. 't::'表示 Option -t有值,但是可选的。
  4. . '::'表示可选

在可选参数中,value不能与选项有空格分隔。所以,在"-t123"例如,-t是选项123是值。

语法:第二形式

getopt [getopt_options] [--] optstring parameters

在getopt被分成五个部分之后

  • 命令本身,即getopt
  • getopt_options,它描述了如何解析参数。单短线长选项,双短线选项。
  • ——,将getopt_options与您想要解析的选项和允许的短选项分开
  • 短期权,是在发现后立即采取的。就像表单优先语法一样。
  • 参数,这些是你传递到程序中的选项。您想要解析的选项并在它们上设置实际值。

例子

getopt -l "name:,version::,verbose" -- "n:v::V" --name=Karthik -version=5.2 -verbose

语法:第三形式

getopt [getopt_options] -o|--options optstring [getopt_options] [--] [parameters]

在getopt被分成五个部分之后

  • 命令本身,即getopt
  • getopt_options,它描述了如何解析参数。单短线长选项,双短线选项。
  • 短选项,即-o或——options。就像Form first语法一样,但是带有选项"-o"在"——"(双破折号)。
  • ——,将getopt_options与您想要解析的选项和允许的短选项分开
  • 参数,这些是你传递到程序中的选项。您想要解析的选项并在它们上设置实际值。

例子

getopt -l "name:,version::,verbose" -a -o "n:v::V" -- -name=Karthik -version=5.2 -verbose

GETOPT_OPTIONS

Getopt_options改变命令行参数的解析方式。

下面是一些getopt_选项

选项:-l或——longoptions

表示getopt命令应该允许多字符选项 认可。

.

.

.

.

例如,--name=Karthik是一个在命令行发送的长选项。在getopt中,长选项的使用类似于

getopt -l "name:,version" -- "" --name=Karthik

由于指定了name:,该选项应该包含一个值

选项:-a或——alternative

表示getopt命令应该允许长选项有一个破折号 '-'而不是双破折号'——'.

例如,你可以用-name=Karthik代替--name=Karthik

getopt -a -l "name:,version" -- "" -name=Karthik

包含代码的完整脚本示例:

#!/bin/bash


# filename: commandLine.sh
# author: @theBuzzyCoder


showHelp() {
# `cat << EOF` This means that cat should stop reading when EOF is detected
cat << EOF
Usage: ./installer -v <espo-version> [-hrV]
Install Pre-requisites for EspoCRM with docker in Development mode


-h, -help,          --help                  Display help


-v, -espo-version,  --espo-version          Set and Download specific version of EspoCRM


-r, -rebuild,       --rebuild               Rebuild php vendor directory using composer and compiled css using grunt


-V, -verbose,       --verbose               Run script in verbose mode. Will print out each step of execution.


EOF
# EOF is found above and hence cat command stops reading. This is equivalent to echo but much neater when printing out.
}




export version=0
export verbose=0
export rebuilt=0


# $@ is all command line parameters passed to the script.
# -o is for short options like -v
# -l is for long options with double dash like --version
# the comma separates different long options
# -a is for long options with single dash like -version
options=$(getopt -l "help,version:,verbose,rebuild,dryrun" -o "hv:Vrd" -a -- "$@")


# set --:
# If no arguments follow this option, then the positional parameters are unset. Otherwise, the positional parameters
# are set to the arguments, even if some of them begin with a ‘-’.
eval set -- "$options"


while true
do
case "$1" in
-h|--help)
showHelp
exit 0
;;
-v|--version)
shift
export version="$1"
;;
-V|--verbose)
export verbose=1
set -xv  # Set xtrace and verbose mode.
;;
-r|--rebuild)
export rebuild=1
;;
--)
shift
break;;
esac
shift
done

运行脚本文件:

# With short options grouped together and long option
# With double dash '--version'


bash commandLine.sh --version=1.0 -rV
# With short options grouped together and long option
# With single dash '-version'


bash commandLine.sh -version=1.0 -rV


# OR with short option that takes value, value separated by whitespace
# by key


bash commandLine.sh -v 1.0 -rV


# OR with short option that takes value, value without whitespace
# separation from key.


bash commandLine.sh -v1.0 -rV


# OR Separating individual short options


bash commandLine.sh -v1.0 -r -V

getoptsgetopt是非常有限的。虽然getopt建议完全不要使用,但它确实提供了长选项。而getopts只允许单字符选项,如-a -b。使用这两种方法都有一些缺点。

所以我写了一个小脚本来替换getoptsgetopt。 这是一个开始,它可能会得到很大的改进

< >强更新08-04-2020 < / >强:我已经添加了对连字符的支持,例如--package-name

用法:./script.sh package install——package“name with space”; ——构建archive" < / p >

# Example:
# parseArguments "${@}"
# echo "${ARG_0}" -> package
# echo "${ARG_1}" -> install
# echo "${ARG_PACKAGE}" -> "name with space"
# echo "${ARG_BUILD}" -> 1 (true)
# echo "${ARG_ARCHIVE}" -> 1 (true)
function parseArguments() {
PREVIOUS_ITEM=''
COUNT=0
for CURRENT_ITEM in "${@}"
do
if [[ ${CURRENT_ITEM} == "--"* ]]; then
printf -v "ARG_$(formatArgument "${CURRENT_ITEM}")" "%s" "1" # could set this to empty string and check with [ -z "${ARG_ITEM-x}" ] if it's set, but empty.
else
if [[ $PREVIOUS_ITEM == "--"* ]]; then
printf -v "ARG_$(formatArgument "${PREVIOUS_ITEM}")" "%s" "${CURRENT_ITEM}"
else
printf -v "ARG_${COUNT}" "%s" "${CURRENT_ITEM}"
fi
fi


PREVIOUS_ITEM="${CURRENT_ITEM}"
(( COUNT++ ))
done
}


# Format argument.
function formatArgument() {
ARGUMENT="${1^^}" # Capitalize.
ARGUMENT="${ARGUMENT/--/}" # Remove "--".
ARGUMENT="${ARGUMENT//-/_}" # Replace "-" with "_".
echo "${ARGUMENT}"
}

马克·G。的评论(在Adrian Frühwirth的答案之下)中的巨大一行代码转换为更可读的答案——这展示了如何避免使用getopts来获得可选参数:

usage() {
printf "Usage: %s <req> [<-s|--sopt> <45|90>] [<-p|--popt> <string>]\n" "$0";
return 1;
};


main() {
req="${1:?$(usage)}";
shift;
s="";
p="";
while [ "$#" -ge 1 ]; do
case "$1" in
-s|--sopt)
shift;
s="${1:?$(usage)}";
[ "$s" -eq 45 ] || [ "$s" -eq 90 ] || {
usage;
return 1;
}
;;
-p|--popt)
shift;
p="${1:?$(usage)}"
;;
*)
usage;
return 1
;;
esac;
shift;
done;
printf "req = %s\ns = %s\np = %s\n" "$req" "$s" "$p";
};


main "$@"

n.caillou的评论所述:

如果选项和参数之间没有空格,它就会失败。

然而,为了使它更符合POSIX(来自马克·G。其他评论):

        case "$1" in
-s*)
s=${1#-s};
if [ -z "$s" ];
shift;
s=$1;
fi