在 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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 21:04:19  来源:igfitidea点击:

Retrieving multiple arguments for a single option using getopts in Bash

bashcommand-line-argumentsgetopts

提问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 -psolution 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' OPTINDvariable.

实际上有一种方法可以使用 检索多个参数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.

我希望这有帮助。