Подтвердить что ты не робот

Получение нескольких аргументов для одной опции с помощью getopts в Bash

Мне нужна помощь с getopts.

Я создал Bash script, который выглядит следующим образом:

$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

Любые идеи?

4b9b3361

Ответ 1

Вы можете использовать одну и ту же опцию несколько раз и добавить все значения в массив.

В самом конкретном исходном вопросе решение 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

Ответ 2

Я знаю, что этот вопрос старый, но я хотел отбросить этот ответ здесь, если кто-то найдет ответ.

Оболочки вроде BASH поддерживают каталоги рекурсивно, как это уже, поэтому script на самом деле не нужен. Например, оригинальный плакат хотел что-то вроде:

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

Ответ 3

Параметры 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 "[email protected]"; do
  touch "$path/$file"
done

Ответ 4

Я исправил ту же проблему, что и у вас, вот так:

Вместо:

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

Ответ 5

Фактически существует способ получить несколько аргументов с помощью getopts, но для этого требуется ручное рушительство с переменной getopts 'OPTIND.

См. следующий script (воспроизведенный ниже): 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.

            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)) > $# ]] || 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

Ответ 6

Исходный вопрос касается 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!

Ответ 7

Если вы хотите указать любое количество значений для параметра, вы можете использовать простой цикл, чтобы найти их и ввести в массив. Например, позвольте модифицировать пример 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

Если мы добавили эти строки в script, чтобы продемонстрировать:

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

Вывод будет:

subdirectory1 subdirectory2
subdirectory2
file1

Ответ 8

Поскольку вы не показываете, как вы надеетесь построить свой список

/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 

Надеюсь, это поможет.

Ответ 9

#!/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 [email protected])"
           ;;
        "-i") INSTS=$(get_opts [email protected])
           ;;
        "-c") CLUSTERS=$(get_opts [email protected])
           ;;
        "-h") help
           ;;
        ?) echo "sorry, I dont do $1"
           exit
           ;;
    esac
    shift
done