Обработка аргументов в bash-скриптах без getopt и getopts

Этот пост был опубликован мной более года назад. Информация, описанная ниже, уже могла потерять актуальность, но всё ещё может быть полезна.

Привет. Я в свободное время для рабочих целей пишу один проект, который состоит из кучи bash-скриптов. Есть одна точка входа, которая из cli принимает аргументы. Этот скрипт подключает остальные куски функционала через source. В этих кусках описаны функции, которые должны обрабатывать разные аргументы, нужные только для чего-то конкретного.

В первой версии проекта я просто явно передавал $1, $2, $N сверху вниз, в другие функции. Это было топорным решением, и оно работало, потому что возможные аргументы и их порядок был известен и прост. Но эстетически это было отвратительно. Чтобы мне было приятно смотреть на код, нужно было сильно усложнить вообще всё, чтобы всё стало намного проще.

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

  • длинные аргументы без значений:
    ./script.sh --foo --bar
  • длинные аргументы со значениями:
    ./script.sh --foo=bar
  • короткие аргументы без значений:
    ./script.sh -a -b -c
  • короткие аргументы со значениями:
    ./script.sh -a avalue -b bvalue
  • комбинировать короткие любые короткие аргументы в одно слово:
    ./script.sh -abc bvalue
  • всё перечисленное выше — одновременно!
  • порядок аргументов не должен иметь значения
  • иметь доступ к любому из них откуда угодно в любой момент времени.

getopt мог бы решить эту проблему. Но внутри каждой отдельной функции невозможно парсить разные аргументы:

#!/bin/bash

# все возможные аргументы надо перечислять здесь
getopt -o a -- $@

func1() {
    getopt -o b -- $@ # вызовет ошибку
}

func2() {
    getopt -o с -- $@ # вызовет ошибку
}

getopts просто не поддерживает длинные аргументы.

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

Любой скрипт, запускаемый из cli, даёт тебе доступ к массиву аргументов, с которыми он был вызван, через $@. Моё дело только грамотно его распарсить.

В двух словах, я сделал так:

  • для коротких аргументов: бегу в цикле по $@ и проверяю ожидаемый ли это аргумент (-b) или находится ли он среди комбинации других коротких (-abc). Если это он и ожидается факт его наличия, то возвращаю 1. Если это он и ожидается его значение, то возвращаю следующий аргумент;
  • для длинных аргументов то же самое, но проще, потому как не приходится проверять комбинации: если мы нашли аргумент и требуем только его существование, то возвращаем 1, а если нашли аргумент со значением — возвращаем все от = до конца аргумента.

Ещё написал функцию для получения аргумента по его индексу — тоже может быть полезно, если заведомо известен порядок аргументов и тебе нужно взять какой-то конкретный.

Ниже — готовый код, с примерами и более подробными комментариями на английском пошагово. Ты можешь сохранить этот код в файл args.sh, а там, где тебе нужно, дописать . args.sh или source args.sh и использовать функции arg, argl и argn как показано на примерах.

Опубликовано
В рубрике blog Отмечено

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *