bash getopts, только короткие опции, все требуемые значения, собственная проверка


8

Я пытаюсь создать сценарий оболочки, который принимает различные параметры и getoptsкажется хорошим решением, так как он может обрабатывать переменное упорядочение параметров и аргументов (я думаю!).

Я буду использовать только короткие опции, и каждому короткому варианту потребуется соответствующее значение, например: ./command.sh -a arga -g argg -b argbно я бы хотел, чтобы параметры вводились в неконкретном порядке, так как большинство людей привыкли работать с командами оболочки ,

Другой момент заключается в том, что я хотел бы сделать свою собственную проверку значений аргумента option, в идеале внутри caseоператоров. Причина этого в том, что мое тестирование :)в моем caseутверждении дало противоречивые результаты (вероятно, из-за отсутствия понимания с моей стороны).
Например:

#!/bin/bash
OPTIND=1 # Reset if getopts used previously
if (($# == 0)); then
        echo "Usage"
        exit 2
fi
while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                :)
                       echo "Option -$OPTARG requires an argument" >&2
                       exit 2;;
        esac
done
shift $((OPTIND-1))
echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

Не удалось для подобных случаев ... ./command.sh -d -h
Когда я хочу, чтобы он пометил -d как требующий аргумента, но я получаю значение, -d=-hкоторое не то, что мне нужно.

Поэтому я подумал, что было бы проще выполнить мою собственную проверку в операторах case, чтобы убедиться, что каждая опция установлена ​​и установлена ​​только один раз.

Я пытаюсь сделать следующее, но мои if [ ! "$MYSQL_HOST" ]; thenблоки не срабатывают.

OPTIND=1 # Reset if getopts used previously

if (($# == 0)); then
        echo "Usage"
        exit 2
fi

while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        if [ ! "$MYSQL_HOST" ]; then
                                echo "host not set"
                                exit 2
                        fi
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        if [ ! "$MYSQL_USER" ]; then
                                echo "username not set"
                                exit 2
                        fi
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        if [ ! "$MYSQL_PASS" ]; then
                                echo "password not set"
                                exit 2
                        fi
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        if [ ! "$BACKUP_DIR" ]; then
                                echo "backup dir not set"
                                exit 2
                        fi
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                #:)
                #       echo "Option -$opt requires an argument" >&2
                #       exit 2;;
        esac
done
shift $((OPTIND-1))

echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

Есть ли причина, по которой я не могу проверить, OPTARGимеет ли an нулевую длину изнутри getopts ... while ... case?

Какой лучший способ запустить мою собственную проверку аргументов getoptsв случае, когда я не хочу полагаться на :). Выполнить проверку моего аргумента за пределами while ... case ... esac?
Тогда я мог бы получить значения аргументов и -dт. Д. И не уловить пропущенную опцию.

Ответы:


4

Когда вы вызываете ваш второй скрипт (я сохранил его как getoptit) с:

getoptit -d -h

Это напечатает:

MYSQL_HOST=''  MYSQL_USER=''  MYSQL_PASS=''  BACKUP_DIR='-h' Additionals: 

Итак, BACKUP_DIR установлен, и вы проверяете, if [ ! "$BACKUP_DIR" ]; thenне установлен ли он, поэтому вполне нормально, что код внутри него не запускается.

Если вы хотите проверить, задан ли каждый параметр один раз, вы должны сделать это, прежде чем выполнять присвоение из значения $ OPTARG. И вам, вероятно, также следует проверить, чтобы $ OPTARG начинался с '-'(для -d -hошибки), прежде чем присваивать:

...
            d)
                    if [ ! -z "$BACKUP_DIR" ]; then
                            echo "backup dir already set"
                            exit 2
                    fi
                    if [ z"${OPTARG:0:1}" == "z-" ]; then
                            echo "backup dir starts with option string"
                            exit 2
                    fi
                    BACKUP_DIR=$OPTARG
                    ;;
...

Отличное объяснение. Имеет смысл, когда я подумал о том, что на самом деле есть whileцикл, работающий над опциями. Это метод, с которым я столкнулся, хотя он многословен по сравнению с другими, очень ясно, что происходит в сценарии. И ремонтопригодность других важна в этом случае.
батфастад

3

Поскольку другие ответы на самом деле не так много отвечают на ваш вопрос: это ожидаемое поведение, основанное на интерпретации POSIX :

Когда для опции требуется аргумент-опция, утилита getopts помещает его в переменную оболочки OPTARG. Если ни одна опция не была найдена, или если найденная опция не имеет аргумента-опции, OPTARG не будет установлен.)

Это все , что в настоящее время говорится, и поставить (не так долго) историю короткий: getoptsне волшебно знать , что вполне веский аргумент он видит для опции , которые вам потребуется , чтобы иметь аргумент на самом деле не вариант аргумент , но другой вариант , Ничто не мешает вам иметь -hв качестве действительного аргумента -dпараметр, который на практике означает, что он getoptsвыдаст ошибку только в том случае, если ваш параметр, требующий аргумента, будет последним в командной строке, например:

test.sh:

#!/bin/sh

while getopts ":xy:" o; do
    case "${o}" in
        :) echo "${OPTARG} requires an argument"; exit 1;
    esac
done

Пример:

$ ./test.sh -y -x
$

$ ./test.sh -x -y
y requires an argument

Причина, по которой ваш второй подход не работает, заключается в том, что синтаксический анализ getoptsостается тем же, поэтому, как только вы попали в цикл, «повреждение» уже сделано.

Если вы абсолютно хотите запретить это, то, как указал Benubird, вам придется самостоятельно проверить аргументы опций и выдать ошибку, если они равны действительной опции.


2

Я бы продолжил использовать, getoptsно сделаю дополнительную проверку после: (не проверено)

errs=0
declare -A option=(
    [MYSQL_HOST]="-h"
    [MYSQL_USER]="-u"
    [MYSQL_PASS]="-p"
    [BACKUP_DIR]="-d" 
)
for var in "${!option[@]}"; do
    if [[ -z "${!var}" ]]; then
        echo "error: specify a value for $var with ${option[var]}"
        ((errs++))
    fi
done
((errs > 0)) && exit 1

требуется bash версии 4


1

Встроенный в Gnu необолочечный getopt делает для вас меньше работы, но обеспечивает большую гибкость, позволяя вам определять, что происходит после обнаружения флага, в том числе самостоятельно отключая аргументы.

Я нашел статью, сравнивающую getopts и getopt, которая, вероятно, будет полезна, так как руководство довольно сложно понять (по крайней мере, перед кофе).


1

Во-первых, вы должны использовать if [ -z "$MYSQL_USER" ].

Во-вторых, нет необходимости в назначении - if [ -z "$OPTARG" ]будет работать нормально.

В-третьих, я подозреваю, что вы на самом деле хотите if [ ${#OPTARG} = 0 ]. $ {# x} - это элемент bash, возвращающий длину строки $ x (подробнее здесь ).

В-четвертых, если вы делаете свою собственную проверку, я бы порекомендовал getopt вместо getopts, поскольку он обеспечивает гораздо большую гибкость.

Наконец, чтобы ответить на ваш первый вопрос, вы можете определить, когда флаг передается в качестве аргумента, поместив список флагов вверху, например так:

args=( -h -u -p -d )

И затем наличие опции в опции, где вы хотите проверить, является ли предоставленный аргумент опцией, что-то вроде этого:

d)
    BACKUP_DIR=$OPTARG
    if [ "$(echo ${args[@]/$OPTARG/})" = "$(echo ${args[@]})" ]
    then
        echo "Argument is an option! Error!"
    fi

Не идеальный ответ, но это работает! Ваша проблема в том, что «-h» является совершенно допустимым аргументом, и вы не даете оболочке понять, что она ищет только те параметры, которые также не являются допустимыми флагами.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.