在 Bash 中使用 getopts 检索单个选项的多个参数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7529856/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Retrieving multiple arguments for a single option using getopts in Bash
提问by vegasbrianc
I need help with getopts
.
我需要帮助getopts
。
I created a Bash script which looks like this when run:
我创建了一个 Bash 脚本,运行时如下所示:
$ foo.sh -i env -d directory -s subdirectory -f file
$ foo.sh -i env -d 目录 -s 子目录 -f 文件
It works correctly when handling one argument from each flag. But when I invoke several arguments from each flag I am not sure how to pull the multiple variable information out of the variables in getopts
.
处理来自每个标志的一个参数时,它可以正常工作。但是当我从每个标志调用几个参数时,我不确定如何从getopts
.
while getopts ":i:d:s:f:" opt
do
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=$OPTARG;;
f ) files=$OPTARG;;
esac
done
After grabbing the options I then want to build directory structures from the variables
获取选项后,我想从变量构建目录结构
foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Then the directory structure would be
那么目录结构将是
/test/directory/subdirectory/file1
/test/directory/subdirectory/file2
/test/directory/subdirectory/file3
/test/directory/subdirectory2/file1
/test/directory/subdirectory2/file2
/test/directory/subdirectory2/file3
Any ideas?
有任何想法吗?
回答by mivk
You can use the same option multiple times and add all values to an array.
您可以多次使用相同的选项并将所有值添加到数组中。
For the very specific original question here, Ryan's mkdir -p
solution is obviously the best.
对于这里非常具体的原始问题,Ryan 的mkdir -p
解决方案显然是最好的。
However, for the more general question of getting multiple values from the same option with getopts, here it is:
但是,对于使用 getopts 从同一个选项中获取多个值的更普遍的问题,这里是:
#!/bin/bash
while getopts "m:" opt; do
case $opt in
m) multi+=("$OPTARG");;
#...
esac
done
shift $((OPTIND -1))
echo "The first value of the array 'multi' is '$multi'"
echo "The whole list of values is '${multi[@]}'"
echo "Or:"
for val in "${multi[@]}"; do
echo " - $val"
done
The output would be:
输出将是:
$ /tmp/t
The first value of the array 'multi' is ''
The whole list of values is ''
Or:
$ /tmp/t -m "one arg with spaces"
The first value of the array 'multi' is 'one arg with spaces'
The whole list of values is 'one arg with spaces'
Or:
- one arg with spaces
$ /tmp/t -m one -m "second argument" -m three
The first value of the array 'multi' is 'one'
The whole list of values is 'one second argument three'
Or:
- one
- second argument
- three
回答by Ryan
I know this question is old, but I wanted to throw this answer on here in case someone comes looking for an answer.
我知道这个问题很老,但我想把这个答案放在这里,以防有人来寻找答案。
Shells like BASH support making directories recursively like this already, so a script isn't really needed. For instance, the original poster wanted something like:
像 BASH 这样的 Shell 已经支持像这样递归地创建目录,因此实际上并不需要脚本。例如,原始海报想要这样的东西:
$ foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
/test/directory/subdirectory/file1
/test/directory/subdirectory/file2
/test/directory/subdirectory/file3
/test/directory/subdirectory2/file1
/test/directory/subdirectory2/file2
/test/directory/subdirectory2/file3
This is easily done with this command line:
使用此命令行可以轻松完成此操作:
pong:~/tmp
[10] rmclean$ mkdir -pv test/directory/{subdirectory,subdirectory2}/{file1,file2,file3}
mkdir: created directory ‘test'
mkdir: created directory ‘test/directory'
mkdir: created directory ‘test/directory/subdirectory'
mkdir: created directory ‘test/directory/subdirectory/file1'
mkdir: created directory ‘test/directory/subdirectory/file2'
mkdir: created directory ‘test/directory/subdirectory/file3'
mkdir: created directory ‘test/directory/subdirectory2'
mkdir: created directory ‘test/directory/subdirectory2/file1'
mkdir: created directory ‘test/directory/subdirectory2/file2'
mkdir: created directory ‘test/directory/subdirectory2/file3'
Or even a bit shorter:
或者甚至更短一点:
pong:~/tmp
[12] rmclean$ mkdir -pv test/directory/{subdirectory,subdirectory2}/file{1,2,3}
mkdir: created directory ‘test'
mkdir: created directory ‘test/directory'
mkdir: created directory ‘test/directory/subdirectory'
mkdir: created directory ‘test/directory/subdirectory/file1'
mkdir: created directory ‘test/directory/subdirectory/file2'
mkdir: created directory ‘test/directory/subdirectory/file3'
mkdir: created directory ‘test/directory/subdirectory2'
mkdir: created directory ‘test/directory/subdirectory2/file1'
mkdir: created directory ‘test/directory/subdirectory2/file2'
mkdir: created directory ‘test/directory/subdirectory2/file3'
Or shorter, with more conformity:
或者更短,更符合:
pong:~/tmp
[14] rmclean$ mkdir -pv test/directory/subdirectory{1,2}/file{1,2,3}
mkdir: created directory ‘test'
mkdir: created directory ‘test/directory'
mkdir: created directory ‘test/directory/subdirectory1'
mkdir: created directory ‘test/directory/subdirectory1/file1'
mkdir: created directory ‘test/directory/subdirectory1/file2'
mkdir: created directory ‘test/directory/subdirectory1/file3'
mkdir: created directory ‘test/directory/subdirectory2'
mkdir: created directory ‘test/directory/subdirectory2/file1'
mkdir: created directory ‘test/directory/subdirectory2/file2'
mkdir: created directory ‘test/directory/subdirectory2/file3'
Or lastly, using sequences:
或者最后,使用序列:
pong:~/tmp
[16] rmclean$ mkdir -pv test/directory/subdirectory{1..2}/file{1..3}
mkdir: created directory ‘test'
mkdir: created directory ‘test/directory'
mkdir: created directory ‘test/directory/subdirectory1'
mkdir: created directory ‘test/directory/subdirectory1/file1'
mkdir: created directory ‘test/directory/subdirectory1/file2'
mkdir: created directory ‘test/directory/subdirectory1/file3'
mkdir: created directory ‘test/directory/subdirectory2'
mkdir: created directory ‘test/directory/subdirectory2/file1'
mkdir: created directory ‘test/directory/subdirectory2/file2'
mkdir: created directory ‘test/directory/subdirectory2/file3'
回答by glenn Hymanman
getopts options can only take zero or one argument. You might want to change your interface to remove the -f option, and just iterate over the remaining non-option arguments
getopts 选项只能采用零个或一个参数。您可能想要更改您的界面以删除 -f 选项,并且只需迭代剩余的非选项参数
usage: foo.sh -i end -d dir -s subdir file [...]
So,
所以,
while getopts ":i:d:s:" opt; do
case "$opt" in
i) initial=$OPTARG ;;
d) dir=$OPTARG ;;
s) sub=$OPTARG ;;
esac
done
shift $(( OPTIND - 1 ))
path="/$initial/$dir/$sub"
mkdir -p "$path"
for file in "$@"; do
touch "$path/$file"
done
回答by David Berkan
I fixed the same problem you had like this:
我解决了你遇到的同样问题:
Instead of:
代替:
foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Do this:
做这个:
foo.sh -i test -d directory -s "subdirectory subdirectory2" -f "file1 file2 file3"
With the space separator you can just run through it with a basic loop. Here's the code:
使用空格分隔符,您可以使用基本循环来运行它。这是代码:
while getopts ":i:d:s:f:" opt
do
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=$OPTARG;;
f ) files=$OPTARG;;
esac
done
for subdir in $sub;do
for file in $files;do
echo $subdir/$file
done
done
Here's a sample output:
这是一个示例输出:
$ ./getopts.sh -s "testdir1 testdir2" -f "file1 file2 file3"
testdir1/file1
testdir1/file2
testdir1/file3
testdir2/file1
testdir2/file2
testdir2/file3
回答by KoZm0kNoT
If you want to specify any number of values for an option, you can use a simple loop to find them and stuff them into an array. For example, let's modify the OP's example to allow any number of -s parameters:
如果你想为一个选项指定任意数量的值,你可以使用一个简单的循环来查找它们并将它们填充到一个数组中。例如,让我们修改 OP 的示例以允许任意数量的 -s 参数:
unset -v sub
while getopts ":i:d:s:f:" opt
do
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=("$OPTARG")
until [[ $(eval "echo ${$OPTIND}") =~ ^-.* ]] || [ -z $(eval "echo ${$OPTIND}") ]; do
sub+=($(eval "echo ${$OPTIND}"))
OPTIND=$((OPTIND + 1))
done
;;
f ) files=$OPTARG;;
esac
done
This takes the first argument ($OPTARG) and puts it into the array $sub. Then it will continue searching through the remaining parameters until it either hits another dashed parameter OR there are no more arguments to evaluate. If it finds more parameters that aren't a dashed parameter, it adds it to the $sub array and bumps up the $OPTIND variable.
这需要第一个参数 ($OPTARG) 并将其放入数组 $sub 中。然后它将继续搜索剩余的参数,直到它遇到另一个虚线参数或没有更多参数可供评估。如果它发现更多不是虚线参数的参数,它会将其添加到 $sub 数组并增加 $OPTIND 变量。
So in the OP's example, the following could be run:
因此,在 OP 的示例中,可以运行以下命令:
foo.sh -i test -d directory -s subdirectory1 subdirectory2 -f file1
If we added these lines to the script to demonstrate:
如果我们将这些行添加到脚本中以进行演示:
echo ${sub[@]}
echo ${sub[1]}
echo $files
The output would be:
输出将是:
subdirectory1 subdirectory2
subdirectory2
file1
回答by Achal Dave
There actually is a way to retrieve multiple arguments using getopts
, but it requires some manual hacking with getopts
' OPTIND
variable.
实际上有一种方法可以使用 检索多个参数getopts
,但它需要使用getopts
'OPTIND
变量进行一些手动修改。
See the following script (reproduced below): https://gist.github.com/achalddave/290f7fcad89a0d7c3719. There's probably an easier way, but this was the quickest way I could find.
请参阅以下脚本(转载如下):https: //gist.github.com/achalddave/290f7fcad89a0d7c3719。可能有一种更简单的方法,但这是我能找到的最快的方法。
#!/bin/sh
usage() {
cat << EOF
while [[ $# > 0 ]]
do
key=""
case $key in
-f|--foo)
nextArg=""
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
case $nextArg in
bar)
echo "--foo bar found!"
;;
baz)
echo "--foo baz found!"
;;
*)
echo "$key $nextArg found!"
;;
esac
if ! [[ "" =~ -.* ]]; then
shift
nextArg=""
else
shift
break
fi
done
;;
-b|--bar)
nextArg=""
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
case $nextArg in
foo)
echo "--bar foo found!"
;;
baz)
echo "--bar baz found!"
;;
*)
echo "$key $nextArg found!"
;;
esac
if ! [[ "" =~ -.* ]]; then
shift
nextArg=""
else
shift
break
fi
done
;;
-z|--baz)
nextArg=""
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
echo "Doing some random task with $key $nextArg"
if ! [[ "" =~ -.* ]]; then
shift
nextArg=""
else
shift
break
fi
done
;;
*)
echo "Unknown flag $key"
;;
esac
shift
done
-a <a1> <a2> <a3> [-b] <b1> [-c]
-a First flag; takes in 3 arguments
-b Second flag; takes in 1 argument
-c Third flag; takes in no arguments
EOF
}
is_flag() {
# Check if is a flag; e.g. "-b"
[[ "" =~ -.* ]] && return 0 || return 1
}
# Note:
# For a, we fool getopts into thinking a doesn't take in an argument
# For b, we can just use getopts normal behavior to take in an argument
while getopts "ab:c" opt ; do
case "${opt}" in
a)
# This is the tricky part.
# $OPTIND has the index of the _next_ parameter; so "${$((OPTIND))}"
# will give us, e.g., . Use eval to get the value in .
# The {} are needed in general for the possible case of multiple digits.
eval "a1=${$((OPTIND))}"
eval "a2=${$((OPTIND+1))}"
eval "a3=${$((OPTIND+2))}"
# Note: We need to check that we're still in bounds, and that
# a1,a2,a3 aren't flags. e.g.
# ./getopts-multiple.sh -a 1 2 -b
# should error, and not set a3 to be -b.
if [ $((OPTIND+2)) -gt $# ] || is_flag "$a1" || is_flag "$a2" || is_flag "$a3"
then
usage
echo
echo "-a requires 3 arguments!"
exit
fi
echo "-a has arguments $a1, $a2, $a3"
# "shift" getopts' index
OPTIND=$((OPTIND+3))
;;
b)
# Can get the argument from getopts directly
echo "-b has argument $OPTARG"
;;
c)
# No arguments, life goes on
echo "-c"
;;
esac
done
回答by Brendon Dugan
The original question deals with getopts, but there is another solution that provides more flexible functionality without getopts (this is perhaps a bit more verbose, but provides a far more flexible command line interface). Here is an example:
最初的问题涉及 getopts,但还有另一种解决方案可以在没有 getopts 的情况下提供更灵活的功能(这可能有点冗长,但提供了更灵活的命令行界面)。下面是一个例子:
script -f foo bar baz
script -f foo -f bar -f baz
script --foo foo -f bar baz
script --foo foo bar -f baz
In this example we are looping through all of the command line options looking for parameters that match our accepted command line flags (such as -f or --foo). Once we find a flag, we loop through every parameter until we run out of parameters or encounter another flag. This breaks us back out into our outer loop which only processes flags.
在此示例中,我们将遍历所有命令行选项,以查找与我们接受的命令行标志(例如 -f 或 --foo)匹配的参数。一旦我们找到一个标志,我们就会遍历每个参数,直到我们用完参数或遇到另一个标志。这让我们回到只处理标志的外循环。
With this setup, the following commands are equivalent:
使用此设置,以下命令是等效的:
script -f baz derp --baz herp -z derp -b foo --foo bar -q llama --bar fight
You can also parse incredibly disorganized parameter sets such as:
您还可以解析非常混乱的参数集,例如:
--foo baz found!
-f derp found!
Doing some random task with --baz herp
Doing some random task with -z derp
--bar foo found!
--foo bar found!
Unknown flag -q
Unknown flag llama
--bar fight found!
To get the output:
要获得输出:
#!/bin/bash
myname=$(basename "/test/directory/subdirectory/file1
. . .
test/directory/subdirectory2/file3
")
# help function
help () { cat <<EOP
$myname: -c cluster [...] -a action [...] -i instance [...]
EOP
}
# parse sub options
get_opts () {
rs='' && rc=0 # return string and return code
while [[ $# -gt 0 ]]; do
shift
[[ "" =~ -.* ]] && break || rs="$rs " && rc=$((rc + 1))
done
echo "$rs"
}
#parse entire command-line
while [[ $# -gt 0 ]]; do
case in
"-a") ACTS="$(get_opts $@)"
;;
"-i") INSTS=$(get_opts $@)
;;
"-c") CLUSTERS=$(get_opts $@)
;;
"-h") help
;;
?) echo "sorry, I dont do "
exit
;;
esac
shift
done
回答by nverkland
case $opt in
d ) dirList="${dirList} $OPTARG" ;;
esac
回答by shellter
As you don't show how you hope to construct your list
因为你没有展示你希望如何构建你的列表
for dir in ${dirList} do
for f in ${fileList} ; do
echo $dir/$f
done
done
it's a little unclear how to proceed, but basically you need to keep appending any new values to the appropriate variable, i.e.
有点不清楚如何继续,但基本上你需要继续将任何新值附加到适当的变量,即
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=$OPTARG;;
f ) files=$OPTARG;;
* )
printf "unknown flag supplied "${OPTARG}\nUsageMessageGoesHere\n"
exit 1
;;
esac
Note that on the first pass dir will be empty, and you'll wind up with a space leading at the from of your final value for ${dirList}
. (If you really need code that doesn't include any extra spaces, front or back, there is a command I can show you, but it will be hard to understand, and it doesn't seem that you'll need it here, but let me know)
请注意,在第一遍 dir 将是空的,并且您将在${dirList}
. (如果你真的需要不包含任何额外空格的代码,前面或后面,有一个命令我可以给你看,但会很难理解,而且你在这里似乎不需要它,但让我知道)
You can then wrap your list variables in for loops to emit all the values, i.e.
然后,您可以将列表变量包装在 for 循环中以发出所有值,即
##代码##Finally, it is considered good practice to 'trap' any unknown inputs to your case statement, i.e.
最后,将任何未知输入“捕获”到 case 语句中被认为是一种很好的做法,即
##代码##I hope this helps.
我希望这有帮助。