Pull to refresh

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

Reading time 2 min
Views 15K
Предстала на первый взгляд тривиальная задача: написать скрипт с различными опциями при запуске. Допустим нужно обработать всего две опции: 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, как пишут в интернетах, не поддерживает длинные опции. Так что возможны проблемы с совместимостью.
Tags:
Hubs:
+26
Comments 9
Comments Comments 9

Articles