在 Bash 中使用 getopts 检索单个选项的多个参数

我需要 getopts的帮助。

我创建了一个 Bash 脚本,在运行时如下所示:

$foo.sh-i env-d 目录-s 子目录-f 文件

当处理来自每个标志的一个参数时,它可以正确工作。但是当我从每个标志中调用几个参数时,我不确定如何从 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

在获取选项之后,我想从变量中构建目录结构

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

有什么想法吗?

125554 次浏览

因为你没有表明你希望如何构建你的列表

/test/directory/subdirectory/file1
. . .
test/directory/subdirectory2/file3

有点不清楚如何继续,但基本上您需要不断地向适当的变量添加任何新值,即。

 case $opt in
d ) dirList="${dirList} $OPTARG" ;;
esac

请注意,在第一次传递时,dir 将为空,并且您将在 ${dirList}的最终值的起始位置得到一个空格。(如果你真的需要不包含任何额外空格的代码,不管是前面还是后面,我可以给你看一个命令,但是它很难理解,看起来你在这里不需要它,但是请让我知道)

然后可以将列表变量包装到 for 循环中,以发出所有值,即。

for dir in ${dirList} do
for f in ${fileList} ; do
echo $dir/$f
done
done

最后,在 case 语句中“捕获”任何未知的输入被认为是一种好的实践,即。

 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

希望这个能帮上忙。

Getopts 选项只能接受零个或一个参数。您可能需要更改接口以删除-f 选项,并只迭代剩余的非选项参数

usage: foo.sh -i end -d dir -s subdir file [...]

那么,

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

我解决了和你一样的问题:

而不是:

foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3

这样做:

foo.sh -i test -d directory -s "subdirectory subdirectory2" -f "file1 file2 file3"

使用空格分隔符,你可以用一个基本的循环遍历它。 密码是这样的:

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

下面是一个输出示例:

$ ./getopts.sh -s "testdir1 testdir2" -f "file1 file2 file3"
testdir1/file1
testdir1/file2
testdir1/file3
testdir2/file1
testdir2/file2
testdir2/file3

我知道这个问题很老套,但我想把这个答案放在这里,以防有人来寻求答案。

像 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

使用这个命令行很容易做到这一点:

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’

或者更短一点:

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’

或者更短,更顺从:

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’

或者最后,使用序列:

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’

你可以 多次使用相同的选项并将所有值添加到一个数组中

对于这里非常具体的原始问题,Ryan 的 mkdir -p解决方案显然是最好的。

然而,对于 使用 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

产出将是:

$ /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

实际上有一种使用 getopts检索多个参数的方法,但是需要使用 getoptsOPTIND变量进行一些手动修改。

请参阅以下脚本(转载如下) : https://gist.github.com/achalddave/290f7fcad89a0d7c3719。也许有更简单的方法,但这是我能找到的最快的方法。

#!/bin/sh


usage() {
cat << EOF
$0 -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 $1 is a flag; e.g. "-b"
[[ "$1" =~ -.* ]] && 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., ${2}. Use eval to get the value in ${2}.
# 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

最初的问题涉及 getopts,但是还有一个解决方案提供了更灵活的功能,而没有使用 getopts (这可能有点冗长,但是提供了更灵活的命令行接口)。这里有一个例子:

while [[ $# > 0 ]]
do
key="$1"
case $key in
-f|--foo)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
case $nextArg in
bar)
echo "--foo bar found!"
;;
baz)
echo "--foo baz found!"
;;
*)
echo "$key $nextArg found!"
;;
esac
if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
-b|--bar)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
case $nextArg in
foo)
echo "--bar foo found!"
;;
baz)
echo "--bar baz found!"
;;
*)
echo "$key $nextArg found!"
;;
esac
if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
-z|--baz)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do


echo "Doing some random task with $key $nextArg"


if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
*)
echo "Unknown flag $key"
;;
esac
shift
done

在本例中,我们遍历所有命令行选项,寻找与我们接受的命令行标志(例如-f 或—— foo)匹配的参数。一旦找到一个标志,我们就循环遍历每个参数,直到用完参数或遇到另一个标志。这使我们重新进入只处理标志的外部循环。

通过这种设置,下面的命令是等效的:

script -f foo bar baz
script -f foo -f bar -f baz
script --foo foo -f bar baz
script --foo foo bar -f baz

您还可以解析令人难以置信的混乱参数集,例如:

script -f baz derp --baz herp -z derp -b foo --foo bar -q llama --bar fight

要获得输出:

--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!

如果您想为一个选项指定任意数量的值,您可以使用一个简单的循环来查找它们并将它们填充到一个数组中。例如,让我们修改 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

这将获取第一个参数($OPTARG)并将其放入数组 $sub 中。然后,它将继续搜索其余的参数,直到它遇到另一个虚线参数或者没有更多的参数要求值。如果它找到更多不是虚线参数的参数,它会将其添加到 $sub 数组中,并增加 $OPTIND 变量。

因此在 OP 的示例中,可以运行以下命令:

foo.sh -i test -d directory -s subdirectory1 subdirectory2 -f file1

如果我们将这些行添加到脚本中来演示:

echo ${sub[@]}
echo ${sub[1]}
echo $files

产出将是:

subdirectory1 subdirectory2
subdirectory2
file1
#!/bin/bash
myname=$(basename "$0")


# 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
[[ "$1" =~ -.* ]] && break ||  rs="$rs $1" && rc=$((rc + 1))
done
echo "$rs"
}


#parse entire command-line
while [[ $# -gt 0 ]]; do
case $1 in
"-a") ACTS="$(get_opts $@)"
;;
"-i") INSTS=$(get_opts $@)
;;
"-c") CLUSTERS=$(get_opts $@)
;;
"-h") help
;;
?) echo "sorry, I dont do $1"
exit
;;
esac
shift
done

下面的链接应该是此需求的通用解决方案。

它很容易注入,并且足够清晰,可以理解,同时最大限度地减少对原始代码的影响。

使用 getopts (bash)的多个选项参数

function getopts-extra () {
declare i=1
# if the next argument is not an option, then append it to array OPTARG
while [[ ${OPTIND} -le $# && ${!OPTIND:0:1} != '-' ]]; do
OPTARG[i]=${!OPTIND}
let i++ OPTIND++
done
}


# Use it within the context of `getopts`:
while getopts s: opt; do
case $opt in
s) getopts-extra "$@"
args=( "${OPTARG[@]}" )
esac
done

这是为单个选项传递多个参数的简单方法。

#!/bin/bash


#test.sh -i 'input1 input2'
#OR
#test.sh -i 'input*'


while getopts "i:" opt; do
case $opt in
i ) input=$OPTARG;;
esac
done
inputs=( $input )


echo "First input is "$inputs""
echo "Second input is "${inputs[1]}""
echo "All inputs: "${inputs[@]}""