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

veg*_*anc 50 bash getopts command-line-arguments

我需要帮助getopts.

我创建了一个Bash脚本,在运行时看起来像这样:

$ foo.sh -i env -d directory -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
Run Code Online (Sandbox Code Playgroud)

抓住选项后,我想从变量构建目录结构

foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Run Code Online (Sandbox Code Playgroud)

那么目录结构就是

/test/directory/subdirectory/file1
/test/directory/subdirectory/file2
/test/directory/subdirectory/file3
/test/directory/subdirectory2/file1
/test/directory/subdirectory2/file2
/test/directory/subdirectory2/file3
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

miv*_*ivk 70

您可以多次使用相同的选项,并将所有值添加到数组中.

对于这里非常具体的原始问题,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
Run Code Online (Sandbox Code Playgroud)

输出将是:

$ /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
Run Code Online (Sandbox Code Playgroud)


小智 24

我知道这个问题已经过时了,但我想在这里提出这个答案,以防有人来寻找答案.

像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
Run Code Online (Sandbox Code Playgroud)

使用此命令行可以轻松完成此操作:

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’
Run Code Online (Sandbox Code Playgroud)

甚至更短一些:

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’
Run Code Online (Sandbox Code Playgroud)

或更短,更符合:

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’
Run Code Online (Sandbox Code Playgroud)

或者最后,使用序列:

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’
Run Code Online (Sandbox Code Playgroud)


gle*_*man 19

getopts选项只能采用零个或一个参数.您可能希望更改接口以删除-f选项,并迭代其余的非选项参数

usage: foo.sh -i end -d dir -s subdir file [...]
Run Code Online (Sandbox Code Playgroud)

所以,

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
Run Code Online (Sandbox Code Playgroud)

  • 我同意Glenn,这通常是我所使用的。但是,另一种选择是只使用另一个定界符(例如逗号)来分隔多个参数而不是空格,然后在逗号上分割$ OPTARG。例如-f file1,file2,file3。我倾向于只对我打算保留的命令执行此操作,因为我不相信其他人会意识到他们不得在逗号后加空格 (2认同)

Dav*_*kan 10

我修复了你喜欢的相同问题:

代替:

foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Run Code Online (Sandbox Code Playgroud)

做这个:

foo.sh -i test -d directory -s "subdirectory subdirectory2" -f "file1 file2 file3"
Run Code Online (Sandbox Code Playgroud)

使用空间分隔符,您可以使用基本循环运行它.这是代码:

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
Run Code Online (Sandbox Code Playgroud)

这是一个示例输出:

$ ./getopts.sh -s "testdir1 testdir2" -f "file1 file2 file3"
testdir1/file1
testdir1/file2
testdir1/file3
testdir2/file1
testdir2/file2
testdir2/file3
Run Code Online (Sandbox Code Playgroud)


Ach*_*ave 5

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

请参阅以下脚本(如下所示):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
Run Code Online (Sandbox Code Playgroud)


KoZ*_*NoT 5

如果要为选项指定任意数量的值,则可以使用简单的循环来查找它们并将它们填充到数组中。例如,让我们修改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
Run Code Online (Sandbox Code Playgroud)

这将采用第一个参数($ OPTARG)并将其放入数组$ sub中。然后它将继续搜索剩余的参数,直到命中另一个破折号的参数或没有其他要评估的参数为止。如果发现更多不是虚线参数的参数,则将其添加到$ sub数组中并增加$ OPTIND变量。

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

foo.sh -i test -d directory -s subdirectory1 subdirectory2 -f file1
Run Code Online (Sandbox Code Playgroud)

如果我们将以下行添加到脚本中以演示:

echo ${sub[@]}
echo ${sub[1]}
echo $files
Run Code Online (Sandbox Code Playgroud)

输出为:

subdirectory1 subdirectory2
subdirectory2
file1
Run Code Online (Sandbox Code Playgroud)


she*_*ter 0

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

/test/directory/subdirectory/file1
. . .
test/directory/subdirectory2/file3
Run Code Online (Sandbox Code Playgroud)

有点不清楚如何继续,但基本上你需要不断地将任何新值附加到适当的变量,即

 case $opt in
    d ) dirList="${dirList} $OPTARG" ;;
 esac
Run Code Online (Sandbox Code Playgroud)

请注意,在第一次传递时 dir 将为空,并且您最终会在 的最终值的起始处出现一个空格${dirList}。(如果您确实需要不包含任何额外空格的代码,无论是前面还是后面,我可以向您展示一个命令,但它很难理解,而且您似乎在这里不需要它,但请告诉我)

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

for dir in ${dirList} do
   for f in ${fileList} ; do
      echo $dir/$f
   done
done
Run Code Online (Sandbox Code Playgroud)

最后,将任何未知输入“捕获”到案例陈述中被认为是良好的做法,即

 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 
Run Code Online (Sandbox Code Playgroud)

我希望这有帮助。