web-dev-qa-db-ja.com

Bashでgetoptsを使用して単一オプションの複数の引数を取得する

getoptsのヘルプが必要です。

実行すると次のようなBashスクリプトを作成しました。

$ foo.sh -i env -dディレクトリ-sサブディレクトリ-fファイル

各フラグから1つの引数を処理するときに正しく機能します。しかし、各フラグから複数の引数を呼び出すと、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

何か案は?

47
vegasbrianc

同じオプションを複数回使用し、すべての値を配列に追加

ここで非常に具体的な元の質問については、ライアンの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
66
mivk

私はこの質問が古いことを知っていますが、誰かが答えを探しに来た場合に備えて、ここにこの答えを投げたいと思いました。

BASHのようなシェルは、すでにこのようにディレクトリを再帰的に作成することをサポートしているため、スクリプトは実際には必要ありません。たとえば、元のポスターは次のようなものを望んでいました。

$ 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’
24
Ryan

getoptsオプションは、0個または1個の引数のみを取ることができます。インターフェイスを変更して-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
19
glenn jackman

私はあなたがこのように持っていた同じ問題を修正しました:

の代わりに:

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
10
David Berkan

オプションに任意の数の値を指定する場合は、単純なループを使用してそれらを見つけ、配列に詰め込むことができます。たとえば、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に入れます。その後、別の破線のパラメーターにヒットするまで、残りのパラメーターの検索を続けますOR評価する引数がなくなる。破線のパラメーターではないパラメーターがさらに見つかった場合、それを追加します$ 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
5
KoZm0kNoT

実際には、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
5
Achal Dave

元の質問は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!
2
Brendon Dugan
#!/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
0
nverkland

リストの作成方法を示していないため

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

続行する方法は少しわかりませんが、基本的には適切な変数に新しい値を追加し続ける必要があります。

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

最初のパスではdirが空になり、${dirList}の最終値のfromから始まるスペースで終わることに注意してください。 (前面または背面に余分なスペースを含まないコードが本当に必要な場合は、表示できるコマンドがありますが、理解するのは難しく、ここでは必要ないようです、教えてください)

その後、リスト変数を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 

これがお役に立てば幸いです。

0
shellter