bash скрипт с поддержкой длинных (gnu-style) опций

    Предстала на первый взгляд тривиальная задача: написать скрипт с различными опциями при запуске. Допустим нужно обработать всего две опции: name и dir. И задача действительно тривиальна при условии, что опции у нас короткие. Но если есть жгучее желание использовать длинные опции, то пиши пропало: getopts, который планировалось использовать изначально, в bash совсем на это не годится.

    Хотя в ksh всё работает на ура:
    #!/bin/ksh
    while getopts "f(file):s(server):" flag
        do
            echo "$flag" $OPTIND $OPTARG
        done

    Но у нас bash, поэтому грустно вздыхаем и пытаемся выбраться из сложившейся ситуации.
    Вриант парсить самостоятельно с одной стороны привлекателен, но уж слишком скучен и неинтересен: каждый раз нужно думать об обработке ошибок, исключений и многих других вещах. Да и не хочется каждый раз изобретать велосипед, при написании подобного скрипта.
    #!/bin/bash
    while true; 
        do
            case "$1" in
                -n | --name ) echo NAME="$2"; shift 2;;
                -d | --dir ) echo DIR="$2"; shift 2;;
            esac
        done

    Можно попробовать использовать getopts с небольшим хаком для поддержки длинных имён:
    #!/bin/bash
    while getopts ":n:d:-:" OPTION; do
        case "$OPTION" in
            -)  case "$OPTARG" in
                    name) echo LONG_NAME="${!OPTIND}";;
                    dir)  echo LONG_DIR="${!OPTIND}" ;;
                esac;;
            n)  echo SHORT_NAME="$OPTARG" ;;
            d)  echo SHORT_DIR="$OPTARG" ;;
        esac
    done

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

    Сетуем, что в bash до сих пор не впилили getopts_long, но гугл подсказывает, что это можно сделать самостоятельно: скачиваем функцию getopts_long и включаем её в наш скрипт:
    #!/bin/bash
    . getopts_long
    while getopts_long :d:n::vh opt \
        name required_argument \
        dir required_argument \
        help 0 "" "$@"
        do
            case "$opt" in
                n|name) echo NAME="$OPTLARG";;
                d|dir) echo DIR="$OPTLARG";;
                help 0 "" "$@"
            esac
    done

    Казалось бы вот оно, счастье, но нашлось ещё более элегантное решение: более продвинутая библиотека shflags, которая помимо парсинга опций умеет так же и проверять значения. Различаются строки, логические переменные, целые числа, нецелые числа (по сути это строки, т.к. в шелле нет понятия нецелых чисел, но проверка на правильность формата есть. Определяется это при указании переменных через DEFINE_string|_boolean|_float|_integer и даже самостоятельно обзывает переменные для опций согласно длинному имени опции, так же поддерживает кучу различных шеллов (sh, bash, dash, ksh, zsh). Красота да и только. Более подробно о плюшках можно посмотреть в самой библотеке. Там достаточно подробная справка. Пример использования:
    #!/bin/bash
    . ./shflags
    DEFINE_string 'name' 'world' 'comment for name' 'n'
    DEFINE_string 'dir' 'dir' 'comment for dir' 'd'
    FLAGS "$@" || exit 1
    eval set -- "${FLAGS_ARGV}"
    echo "Name is ${FLAGS_name} and dir is ${FLAGS_dir}"

    Однако и в этом решении есть ложка дёгтя: библиотека использует getopt для парсинга опций, а негнутый getopt, как пишут в интернетах, не поддерживает длинные опции. Так что возможны проблемы с совместимостью.
    • +26
    • 12,3k
    • 9
    Поделиться публикацией

    Похожие публикации

    Комментарии 9

      –3
      Есть решение проще: остаться на ksh :)
        +12
        Может я чего-то не понимаю, но в GNU-системах есть работающий внешний getopt, который вполне себе умеет --longoptions. Зачем весь этот огород городить?
        0
        Гм. Интересно. Шутку некоторые восприняли всерьёз. Но у минусующих хотелось бы спросить: чем так плох ksh?
        +3
        С другой стороны, длинные опции — не всегда хорошо… Но за shflags спасибо.
          +2
          Никто не мешает дублировать длинные опции короткими. С длинными опциями гораздо нагляднее всё получается, а если прикрутить bash_completion, так вообще замечательно.
            0
            А вот статья про прикручивание bash_completion к скрипту была бы еще полезнее :)
        0
        > негнутый getopt, как пишут в интернетах, не поддерживает длинные опции

        Ванильный getopt, хуже того, даже значений с пробелами не поддерживает:

        -T, --test
        Test if your getopt(1) is this enhanced version or an old version. This generates no output, and sets
        the error status to 4. Other implementations of getopt(1), and this version if the environment vari‐
        able GETOPT_COMPATIBLE is set, will return `--' and error status 0.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.