Bash getopts: чтение $OPTARG для необязательных флагов?

Я хотел бы иметь возможность принимать как обязательные, так и необязательные флаги в моем script. Вот что я до сих пор.

#!bin/bash

while getopts ":a:b:cdef" opt; do
      case $opt in
        a ) APPLE="$OPTARG";;
        b ) BANANA="$OPTARG";;
        c ) CHERRY="$OPTARG";;
        d ) DFRUIT="$OPTARG";;
        e ) EGGPLANT="$OPTARG";;
        f ) FIG="$OPTARG";;
        \?) echo "Invalid option: -"$OPTARG"" >&2
            exit 1;;
        : ) echo "Option -"$OPTARG" requires an argument." >&2
            exit 1;;
      esac
    done
echo "Apple is "$APPLE""
echo "Banana is "$BANANA""
echo "Cherry is "$CHERRY""
echo "Dfruit is "$DFRUIT""
echo "Eggplant is "$EGGPLANT""
echo "Fig is "$FIG""

Однако вывод для следующего:

bash script.sh -a apple -b banana -c cherry -d dfruit -e eggplant -f fig

... выводит это:

Apple is apple
Banana is banana
Cherry is 
Dfruit is 
Eggplant is 
Fig is

Как вы можете видеть, необязательные флаги не вытягивают аргументы с помощью $OPTARG, как в случае с обязательными флагами. Есть ли способ читать $OPTARG на необязательных флагах, не избавляясь от аккуратной обработки ошибок ":)"?

=======================================

РЕДАКТИРОВАТЬ: Я закончил, следуя совету Гилберта ниже. Вот что я сделал:

#!/bin/bash

  if [[ "$1" =~ ^((-{1,2})([Hh]$|[Hh][Ee][Ll][Pp])|)$ ]]; then
    print_usage; exit 1
  else
    while [[ $# -gt 0 ]]; do
      opt="$1"
      shift;
      current_arg="$1"
      if [[ "$current_arg" =~ ^-{1,2}.* ]]; then
        echo "WARNING: You may have left an argument blank. Double check your command." 
      fi
      case "$opt" in
        "-a"|"--apple"      ) APPLE="$1"; shift;;
        "-b"|"--banana"     ) BANANA="$1"; shift;;
        "-c"|"--cherry"     ) CHERRY="$1"; shift;;
        "-d"|"--dfruit"     ) DFRUIT="$1"; shift;;
        "-e"|"--eggplant"   ) EGGPLANT="$1"; shift;;
        "-f"|"--fig"        ) FIG="$1"; shift;;
        *                   ) echo "ERROR: Invalid option: \""$opt"\"" >&2
                              exit 1;;
      esac
    done
  fi

  if [[ "$APPLE" == "" || "$BANANA" == "" ]]; then
    echo "ERROR: Options -a and -b require arguments." >&2
    exit 1
  fi

Большое спасибо, всем. До сих пор это прекрасно работает.

Ответы

Ответ 1

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

Но если вы готовы использовать аргументы стиля "--posix", посетите аргумент аргумента bash аргументов для аргументов в [email protected]

Ответ 2

: означает "принимает аргумент", а не "обязательный аргумент". То есть символ опции, за которым не следует :, означает параметр стиля флага (без аргумента), тогда как символ опции, за которым следует :, означает параметр с аргументом.

Таким образом, вы, вероятно, хотите

getopts "a:b:c:d:e:f:" opt

Если вы хотите "обязательные" параметры (немного оксюморона), вы можете проверить после разбора аргумента, что все обязательные значения параметров были установлены.

Ответ 3

Это непросто... Любые опциональные аргументы опции должны быть необходимы, насколько известно getopts. Конечно, необязательный аргумент должен быть частью того же аргумента в script как опция, с которой он идет. В противном случае опция -f с необязательным аргументом и опция -a с требуемым аргументом могут запутаться:

# Is -a an option or an argument?
./script.sh -f -a foo

# -a is definitely an argument
./script.sh -f-a foo

Единственный способ сделать это - проверить, является ли параметр и его аргумент одним аргументом в script. Если это так, OPTARG является аргументом опции. В противном случае OPTIND должен быть уменьшен на единицу. Конечно, теперь у опции есть аргумент, означающий, что символ будет найден, когда в опции отсутствует аргумент. Просто используйте другой case, чтобы определить, нужны ли какие-либо параметры:

while getopts ":a:b:c:d:e:f:" opt; do
    case $opt in
        a) APPLE="$OPTARG";;
        b) BANANA="$OPTARG";;
        c|d|e|f)
            if test "$OPTARG" = "$(eval echo '$'$((OPTIND - 1)))"; then
                OPTIND=$((OPTIND - 1))
            else
                 case $opt in
                     c) CHERRY="$OPTARG";;
                     d) DFRUIT="$OPTARG";;
                     ...
                esac
            fi ;;
        \?) ... ;;
        :)
             case "$OPTARG" in
                 c|d|e|f) ;; # Ignore missing arguments
                 *) echo "option requires an argument -- $OPTARG" >&2 ;;
            esac ;;
        esac
    done

Это сработало для меня до сих пор.

Ответ 4

Для bash это мой любимый способ разбора/поддержки cli args. Я использовал getopts, и было слишком сложно, что он не поддерживал бы длинные варианты. Мне нравится, как это работает в противном случае - особенно для встроенных функций.

usage()
{
    echo "usage: $0 -OPT1 <opt1_arg> -OPT2"
}
while [ "`echo $1 | cut -c1`" = "-" ]
do
    case "$1" in
        -OPT1)
                OPT1_ARGV=$2
                OPT1_BOOL=1
                shift 2
            ;;
        -OPT2)
                OPT2_BOOL=1
                shift 1
            ;;
        *)
                usage
                exit 1
            ;;
esac
done

Короткий, простой. Лучший друг инженера!

Я думаю, что это может быть изменено для поддержки параметров "-", а также...

Приветствия =)

Ответ 5

Понимание bash getopts

На странице руководства bash (со ссылкой на руководство версии 4.1) для getopts указано:

getopts optstring name [args]

getopts используется скриптами оболочки для анализа позиционных параметров. optstring содержит символы опций, подлежащие распознаванию; если за персонажем следует двоеточие, ожидается, что параметр будет иметь аргумент, который должен быть отделен от него по белому пространству. Двоеточие (':) и вопросительный знак (??) Могут не быть используется как символ опции. Каждый раз, когда он вызывается, getopts помещает следующий параметр в имени переменной оболочки, инициализируя имя, если оно не существует, и индекс следующего аргумента, который будет обработан в переменной OPTIND. OPTINDинициализируется до 1 каждый раз, когда вызывается оболочка или оболочка script. Когда опция требует аргумента, getopts помещает этот аргумент в переменную OPTARG. Оболочка не reset OPTIND автоматически; он должен быть вручную reset между несколькими вызовами getopts в пределах одного вызова оболочки, если необходимо использовать новый набор параметров.

Когда встречается конец опций, getopts выходит с возвращаемым значением больше нуля. OPTIND устанавливается в индекс первого аргумента без опции, и имя установлено на '?.

getopts обычно анализирует позиционные параметры, но если больше аргументов заданный в args, getopts вместо этого анализирует их.

getopts может сообщать об ошибках двумя способами. Если первый символ optstring является двоеточие, тихая отчетность об ошибках. В диагностических сообщениях нормальной работы печатаются, когда встречаются недопустимые параметры или отсутствующие аргументы параметра. Если для переменной OPTERR установлено значение 0, сообщения об ошибках не будут отображаться, даже если первый символ optstring не является двоеточием.

Если отображается недопустимая опция, getopts places '? на имя и, если не молчать, печатает сообщение об ошибке и unsets OPTARG. Если getopts не работает, параметр найденный символ помещается в OPTARG, и диагностическое сообщение не печатается. Если требуемый аргумент не найден, а getopts не является тихим, знак вопроса ('?) Помещается в имя, OPTARG не отображается, и выводится диагностическое сообщение. Если getopts молчат, тогда двоеточие (':) помещается в имя и OPTARG устанавливается на найден символ опции.

Обратите внимание, что:

  • Ведущий двоеточие в строке опций помещает getopts в тихий режим; он не генерирует никаких сообщений об ошибках.

  • В описании ничего не говорится о необязательных аргументах опций.

Я предполагаю, что вы после функциональности сродни:

script -ffilename
script -f

где флаг f (-f) необязательно принимает аргумент. Это не поддерживается командой bash getopts. Функция POSIX getopt() едва поддерживает эту нотацию. Фактически, только последний параметр в командной строке может иметь необязательный аргумент в POSIX.

Каковы альтернативы?

В части проконсультируйтесь с с помощью getopts в bash оболочке script, чтобы получить длинные и короткие параметры командной строки.

Программа GNU getopt (единственная!) - это сложное beastie, которое поддерживает длинные и короткие опции и поддерживает необязательные аргументы для длинных опций (и использует GNU getopt(3). Отслеживание его источника интересно, ссылка на странице на die.net неверна, вы найдете это в подкаталоге в ftp://ftp.kernel.org/pub/linux/utils/util-linux (без -ng). Я не отслеживал местоположение в http://www.gnu.org/ или http://www.fsf.org/, который содержит его.