如何在 Bash 中获取带有标志的参数
Posted
技术标签:
【中文标题】如何在 Bash 中获取带有标志的参数【英文标题】:How to get arguments with flags in Bash 【发布时间】:2011-10-27 12:49:46 【问题描述】:我知道我可以在 bash 中轻松获取这样的定位参数:
$0
或 $1
我希望能够使用这样的标志选项来指定每个参数的用途:
mysql -u user -h host
通过标志而不是位置获取-u param
值和-h param
值的最佳方法是什么?
【问题讨论】:
在unix.stackexchange.com 询问/检查可能是个好主意 google for "bash getopts" -- 很多教程。 @glenn-jackman:既然我知道了这个名字,我肯定会用谷歌搜索它。关于谷歌的事情是 - 问一个问题 - 你应该已经知道 50% 的答案。 看看BashFAQ#035 【参考方案1】:这是我常用的成语:
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 语句中多次移动以获取多个值。
【讨论】:
--action*
和 --output-dir*
案例有什么作用?
他们只是将获得的值保存到环境中。
@Lucio 超级旧评论,但添加它以防其他人访问此页面。 *(通配符)适用于有人键入--action=[ACTION]
的情况以及有人键入--action [ACTION]
的情况
为什么*)
你会在那里中断,你不应该退出或忽略错误的选项吗?换句话说,-bad -o dir
-o dir
部分永远不会被处理。
您可以将export PROCESS='echo $1 | sed -e 's/^[^=]*=//g'
替换为export PROCESS="$1/*"="/"
【参考方案2】:
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 目录下应该有各种各样的例子。
【讨论】:
可以在目录/usr/share/doc/util-linux/examples
中找到更广泛的示例,至少在 Ubuntu 机器上是这样。
@Shizzmo 你能解释一下set --
的语法吗?这个成语我已经看过几次了。将感激不尽。非常感谢。【参考方案3】:
本示例使用 Bash 内置的getopts
命令,来自Google Shell Style Guide:
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
)
但是,一个很大的缺点是它不支持长选项,只支持单字符选项。
【讨论】:
有人想知道为什么这个使用 bash 内置函数的答案不是最佳答案 为了后代:'abf:v' 中的冒号表示 -f 需要一个额外的参数(在这种情况下是文件名)。 我不得不将错误行改为:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
您能添加关于冒号的注释吗?在每个字母之后,没有冒号表示没有 arg,一个冒号表示一个 arg,两个冒号表示可选 arg?
@WillBarnwell 应该注意,它是在提出问题 3 年后添加的,而最佳答案是在同一天添加的。【参考方案4】:
另一种选择是使用类似于以下示例的内容,这将允许您使用长 --image 或短 -i 标签并允许编译 -i="example.jpg" 或单独的 -i 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";
【讨论】:
【参考方案5】:我认为这可以作为您想要实现的目标的更简单示例。无需使用外部工具。 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
您可以将此功能添加到您的个人资料或将其放入脚本中。
谢谢!
编辑:
将其保存为文件,然后以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";
【讨论】:
我使用上面的代码&运行时它没有打印任何东西。./hello.sh DOSOMETHING -last "Adios" -first "Hola"
@dinu0101 这是一个函数。不是脚本。您应该将其用作 DOSOMETHING -last "Adios" -first "Hola"
谢谢@Matias。明白了。如何在脚本中运行。
非常感谢@Matias
在 macOS 上使用 return 1;
和最后一个示例输出 can only 'return' from a function or sourced script
。切换到exit 1;
可以正常工作。【参考方案6】:
我喜欢 Robert McMahan 的最佳答案,因为它似乎最容易制作成可共享的包含文件以供您的任何脚本使用。但是,if [[ -n $variables[$argument_label] ]]
抛出消息“变量:错误的数组下标”似乎存在缺陷。我没有代表发表评论,我怀疑这是正确的“修复”,但是将 if
包装在 if [[ -n $argument_label ]] ; then
中可以清理它。
这是我最终得到的代码,如果您知道更好的方法,请在罗伯特的回答中添加评论。
包含文件“flags-declares.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";
【讨论】:
【参考方案7】:如果你熟悉 Python argparse,并且不介意调用 python 来解析 bash 参数,我发现有一段代码非常有用且超级好用,称为 argparse-bash https://github.com/nhoffman/argparse-bash
示例来自他们的 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
【讨论】:
【参考方案8】:我提出一个简单的 TLDR:;未启动的示例。
创建一个名为 greeter.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!
备注
如果您想使用多个参数:
-
使用更多参数扩展
while getops "n:" arg: do
,例如
while getops "n:o:p:" arg: do
使用额外的变量分配扩展 case 开关。如o) Option=$OPTARG
和p) Parameter=$OPTARG
使脚本可执行:
chmod u+x greeter.sh
【讨论】:
【参考方案9】:#!/bin/bash
if getopts "n:" arg; then
echo "Welcome $OPTARG"
fi
另存为 sample.sh 并尝试运行
sh sample.sh -n John
在您的终端中。
【讨论】:
【参考方案10】:我在使用带有多个标志的 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
【讨论】:
【参考方案11】:所以这是我的解决方案。我希望能够处理不带连字符、带一个连字符和带两个连字符的布尔标志,以及带一个和两个连字符的参数/值分配。
# 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
一些参考资料
发现主程序here。 更多关于将所有参数传递给函数here。 更多关于默认值的信息here。 更多关于declare
做$ bash -c "help declare"
的信息。
更多关于shift
的信息$ bash -c "help shift"
。
【讨论】:
以上是关于如何在 Bash 中获取带有标志的参数的主要内容,如果未能解决你的问题,请参考以下文章