如何在Bash中获得带有标志的参数

我知道我可以很容易地在bash中获得这样的定位参数:

$0$1

我希望能够使用这样的标志选项来指定每个参数的使用:

mysql -u user -h host

通过标志而不是位置来获取-u param值和-h param值的最佳方法是什么?

382652 次浏览

这是我常用的成语:

while test $# -gt 0; do
case "$1" in
-h|--help)
echo "$package - attempt to capture frames"
echo " "
echo "$package [options] application [arguments]"
echo " "
echo "options:"
echo "-h, --help                show brief help"
echo "-a, --action=ACTION       specify an action to use"
echo "-o, --output-dir=DIR      specify a directory to store output in"
exit 0
;;
-a)
shift
if test $# -gt 0; then
export PROCESS=$1
else
echo "no process specified"
exit 1
fi
shift
;;
--action*)
export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
shift
;;
-o)
shift
if test $# -gt 0; then
export OUTPUT=$1
else
echo "no output dir specified"
exit 1
fi
shift
;;
--output-dir*)
export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
shift
;;
*)
break
;;
esac
done

要点如下:

  • $#是参数的个数
  • While循环查看提供的所有参数,在case语句中匹配它们的值
  • Shift移走了第一个。您可以在case语句中多次移位以获取多个值。

Getopt是你的朋友。举个简单的例子:

function f () {
TEMP=`getopt --long -o "u:h:" "$@"`
eval set -- "$TEMP"
while true ; do
case "$1" in
-u )
user=$2
shift 2
;;
-h )
host=$2
shift 2
;;
*)
break
;;
esac
done;


echo "user = $user, host = $host"
}


f -u myself -h some_host

在您的/usr/bin目录中应该有各种示例。

这个例子使用了Bash内置的getopts命令,来自谷歌Shell样式指南:

a_flag=''
b_flag=''
files=''
verbose='false'


print_usage() {
printf "Usage: ..."
}


while getopts 'abf:v' flag; do
case "${flag}" in
a) a_flag='true' ;;
b) b_flag='true' ;;
f) files="${OPTARG}" ;;
v) verbose='true' ;;
*) print_usage
exit 1 ;;
esac
done

注意:如果一个字符后面跟着一个冒号(例如f:),该选项应该有一个参数。

示例用法:./script -v -a -b -f filename

使用getopts比接受的答案有几个优点:

  • while条件可读性更强,并显示了接受的选项是什么
  • 清洁代码;不计算参数个数和移位
  • 你可以连接选项(例如-a -b -c-abc)

然而,一个很大的缺点是它不支持长选项,只支持单字符选项。

另一种替代方法是使用类似下面例子的东西,它允许你使用长——图片或短-我标记,也允许编译后的我= " example.jpg "或单独的我example.jpg方法传递参数。

# declaring a couple of associative arrays
declare -A arguments=();
declare -A variables=();


# declaring an index integer
declare -i index=1;


# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value)
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";
variables["--git-user"]="git_user";
variables["-gb"]="git_branch";
variables["--git-branch"]="git_branch";
variables["-dbr"]="db_fqdn";
variables["--db-redirect"]="db_fqdn";
variables["-e"]="environment";
variables["--environment"]="environment";


# $@ here represents all arguments passed in
for i in "$@"
do
arguments[$index]=$i;
prev_index="$(expr $index - 1)";


# this if block does something akin to "where $i contains ="
# "%=*" here strips out everything from the = to the end of the argument leaving only the label
if [[ $i == *"="* ]]
then argument_label=${i%=*}
else argument_label=${arguments[$prev_index]}
fi


# this if block only evaluates to true if the argument label exists in the variables array
if [[ -n ${variables[$argument_label]} ]]
then
# dynamically creating variables names using declare
# "#$argument_label=" here strips out the label leaving only the value
if [[ $i == *"="* ]]
then declare ${variables[$argument_label]}=${i#$argument_label=}
else declare ${variables[$argument_label]}=${arguments[$index]}
fi
fi


index=index+1;
done;


# then you could simply use the variables like so:
echo "$git_user";

我认为这可以作为一个简单的例子来说明你想要达到的目标。不需要使用外部工具。Bash内置的工具可以为您完成这项工作。

function DOSOMETHING {


while test $# -gt 0; do
case "$1" in
-first)
shift
first_argument=$1
shift
;;
-last)
shift
last_argument=$1
shift
;;
*)
echo "$1 is not a recognized flag!"
return 1;
;;
esac
done


echo "First argument : $first_argument";
echo "Last argument : $last_argument";
}

这将允许您使用标志,因此无论您以何种顺序传递参数,都将获得正确的行为。

例子:

 DOSOMETHING -last "Adios" -first "Hola"

输出:

 First argument : Hola
Last argument : Adios

您可以将此函数添加到概要文件中,也可以将其放入脚本中。

谢谢!

< p >编辑: 将其保存为一个文件,然后以yourfile.sh -last "Adios" -first "Hola"

的形式执行
#!/bin/bash
while test $# -gt 0; do
case "$1" in
-first)
shift
first_argument=$1
shift
;;
-last)
shift
last_argument=$1
shift
;;
*)
echo "$1 is not a recognized flag!"
return 1;
;;
esac
done


echo "First argument : $first_argument";
echo "Last argument : $last_argument";

我最喜欢Robert McMahan的答案,因为它似乎是最容易让任何脚本使用的可共享包含文件。但它似乎有一个缺陷,与行if [[ -n ${variables[$argument_label]} ]]抛出消息,“变量:坏数组下标”。我没有代表发表评论,我怀疑这是适当的“修复”,但包装ifif [[ -n $argument_label ]] ; then清理它。

这是我最终得到的代码,如果你知道更好的方法,请在Robert的答案上添加注释。

包含文件"flags- declarations .sh"

# declaring a couple of associative arrays
declare -A arguments=();
declare -A variables=();


# declaring an index integer
declare -i index=1;

包含文件"flags-arguments.sh"

# $@ here represents all arguments passed in
for i in "$@"
do
arguments[$index]=$i;
prev_index="$(expr $index - 1)";


# this if block does something akin to "where $i contains ="
# "%=*" here strips out everything from the = to the end of the argument leaving only the label
if [[ $i == *"="* ]]
then argument_label=${i%=*}
else argument_label=${arguments[$prev_index]}
fi


if [[ -n $argument_label ]] ; then
# this if block only evaluates to true if the argument label exists in the variables array
if [[ -n ${variables[$argument_label]} ]] ; then
# dynamically creating variables names using declare
# "#$argument_label=" here strips out the label leaving only the value
if [[ $i == *"="* ]]
then declare ${variables[$argument_label]}=${i#$argument_label=}
else declare ${variables[$argument_label]}=${arguments[$index]}
fi
fi
fi


index=index+1;
done;

你的“script.sh”

. bin/includes/flags-declares.sh


# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value)
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";
variables["--git-user"]="git_user";
variables["-gb"]="git_branch";
variables["--git-branch"]="git_branch";
variables["-dbr"]="db_fqdn";
variables["--db-redirect"]="db_fqdn";
variables["-e"]="environment";
variables["--environment"]="environment";


. bin/includes/flags-arguments.sh


# then you could simply use the variables like so:
echo "$git_user";
echo "$git_branch";
echo "$db_fqdn";
echo "$environment";
如果你熟悉Python argparse,并且不介意调用Python来解析bash参数,我发现有一段代码非常有用,而且超级容易使用,叫做argparse-bash https://github.com/nhoffman/argparse-bash < / p >

示例取自他们的Example .sh脚本:

#!/bin/bash


source $(dirname $0)/argparse.bash || exit 1
argparse "$@" <<EOF || exit 1
parser.add_argument('infile')
parser.add_argument('outfile')
parser.add_argument('-a', '--the-answer', default=42, type=int,
help='Pick a number [default %(default)s]')
parser.add_argument('-d', '--do-the-thing', action='store_true',
default=False, help='store a boolean [default %(default)s]')
parser.add_argument('-m', '--multiple', nargs='+',
help='multiple values allowed')
EOF


echo required infile: "$INFILE"
echo required outfile: "$OUTFILE"
echo the answer: "$THE_ANSWER"
echo -n do the thing?
if [[ $DO_THE_THING ]]; then
echo " yes, do it"
else
echo " no, do not do it"
fi
echo -n "arg with multiple values: "
for a in "${MULTIPLE[@]}"; do
echo -n "[$a] "
done
echo

我建议一个简单的TLDR:;非初始化的例子。

创建一个名为greeting .sh的bash脚本

#!/bin/bash


while getopts "n:" arg; do
case $arg in
n) Name=$OPTARG;;
esac
done


echo "Hello $Name!"

然后你可以在执行脚本时传递一个可选参数-n

执行脚本如下:

$ bash greeter.sh -n 'Bob'

输出

$ Hello Bob!

笔记

如果你想使用多个参数:

  1. 扩展while getops "n:" arg: do,包含更多参数,例如 李while getops "n:o:p:" arg: do < / >
  2. 用额外的变量赋值扩展大小写开关。例如o) Option=$OPTARGp) Parameter=$OPTARG

要使脚本可执行:

chmod u+x greeter.sh
#!/bin/bash


if getopts "n:" arg; then
echo "Welcome $OPTARG"
fi

保存为sample.sh 并尝试运行

sh sample.sh -n John

在你的终端。

我在使用带有多个标志的getopts时遇到了麻烦,所以我编写了这段代码。它使用一个模态变量来检测标志,并使用这些标志为变量分配参数。

注意,如果一个标志不应该有参数,除了设置CURRENTFLAG之外,还可以执行其他操作。

    for MYFIELD in "$@"; do


CHECKFIRST=`echo $MYFIELD | cut -c1`


if [ "$CHECKFIRST" == "-" ]; then
mode="flag"
else
mode="arg"
fi


if [ "$mode" == "flag" ]; then
case $MYFIELD in
-a)
CURRENTFLAG="VARIABLE_A"
;;
-b)
CURRENTFLAG="VARIABLE_B"
;;
-c)
CURRENTFLAG="VARIABLE_C"
;;
esac
elif [ "$mode" == "arg" ]; then
case $CURRENTFLAG in
VARIABLE_A)
VARIABLE_A="$MYFIELD"
;;
VARIABLE_B)
VARIABLE_B="$MYFIELD"
;;
VARIABLE_C)
VARIABLE_C="$MYFIELD"
;;
esac
fi
done

这就是我的解。我希望能够处理布尔标志没有连字符,一个连字符,和两个连字符以及参数/值分配一个和两个连字符。

# Handle multiple types of arguments and prints some variables
#
# Boolean flags
# 1) No hyphen
#    create   Assigns `true` to the variable `CREATE`.
#             Default is `CREATE_DEFAULT`.
#    delete   Assigns true to the variable `DELETE`.
#             Default is `DELETE_DEFAULT`.
# 2) One hyphen
#      a      Assigns `true` to a. Default is `false`.
#      b      Assigns `true` to b. Default is `false`.
# 3) Two hyphens
#    cats     Assigns `true` to `cats`. By default is not set.
#    dogs     Assigns `true` to `cats`. By default is not set.
#
# Parameter - Value
# 1) One hyphen
#      c      Assign any value you want
#      d      Assign any value you want
#
# 2) Two hyphens
#   ... Anything really, whatever two-hyphen argument is given that is not
#       defined as flag, will be defined with the next argument after it.
#
# Example:
# ./parser_example.sh delete -a -c VA_1 --cats --dir /path/to/dir
parser() {
# Define arguments with one hyphen that are boolean flags
HYPHEN_FLAGS="a b"
# Define arguments with two hyphens that are boolean flags
DHYPHEN_FLAGS="cats dogs"


# Iterate over all the arguments
while [ $# -gt 0 ]; do
# Handle the arguments with no hyphen
if [[ $1 != "-"* ]]; then
echo "Argument with no hyphen!"
echo $1
# Assign true to argument $1
declare $1=true
# Shift arguments by one to the left
shift
# Handle the arguments with one hyphen
elif [[ $1 == "-"[A-Za-z0-9]* ]]; then
# Handle the flags
if [[ $HYPHEN_FLAGS == *"${1/-/}"* ]]; then
echo "Argument with one hyphen flag!"
echo $1
# Remove the hyphen from $1
local param="${1/-/}"
# Assign true to $param
declare $param=true
# Shift by one
shift
# Handle the parameter-value cases
else
echo "Argument with one hyphen value!"
echo $1 $2
# Remove the hyphen from $1
local param="${1/-/}"
# Assign argument $2 to $param
declare $param="$2"
# Shift by two
shift 2
fi
# Handle the arguments with two hyphens
elif [[ $1 == "--"[A-Za-z0-9]* ]]; then
# NOTE: For double hyphen I am using `declare -g $param`.
#   This is the case because I am assuming that's going to be
#   the final name of the variable
echo "Argument with two hypens!"
# Handle the flags
if [[ $DHYPHEN_FLAGS == *"${1/--/}"* ]]; then
echo $1 true
# Remove the hyphens from $1
local param="${1/--/}"
# Assign argument $2 to $param
declare -g $param=true
# Shift by two
shift
# Handle the parameter-value cases
else
echo $1 $2
# Remove the hyphens from $1
local param="${1/--/}"
# Assign argument $2 to $param
declare -g $param="$2"
# Shift by two
shift 2
fi
fi


done
# Default value for arguments with no hypheb
CREATE=${create:-'CREATE_DEFAULT'}
DELETE=${delete:-'DELETE_DEFAULT'}
# Default value for arguments with one hypen flag
VAR1=${a:-false}
VAR2=${b:-false}
# Default value for arguments with value
# NOTE1: This is just for illustration in one line. We can well create
#   another function to handle this. Here I am handling the cases where
#   we have a full named argument and a contraction of it.
#   For example `--arg1` can be also set with `-c`.
# NOTE2: What we are doing here is to check if $arg is defined. If not,
#   check if $c was defined. If not, assign the default value "VD_"
VAR3=$(if [[ $arg1 ]]; then echo $arg1; else echo ${c:-"VD_1"}; fi)
VAR4=$(if [[ $arg2 ]]; then echo $arg2; else echo ${d:-"VD_2"}; fi)
}




# Pass all the arguments given to the script to the parser function
parser "$@"




echo $CREATE $DELETE $VAR1 $VAR2 $VAR3 $VAR4 $cats $dir

一些参考

  • 主程序被找到在这里
  • 更多关于将所有参数传递给函数在这里
  • 关于默认值在这里的更多信息。
  • 关于declare的更多信息做$ bash -c "help declare"
  • 关于shift的更多信息做$ bash -c "help shift"