Правильный способ проверить флаг командной строки в bash

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

if echo $* | grep -e "--flag" -q
then
  echo ">>>> Running with flag"
else
  echo ">>>> Running without flag"
fi

Есть ли лучший способ?

Примечание. Я явно не хочу перечислять все флаги в switch/getopt. (В этом случае любые такие вещи станут половиной или более полного скрипта. Также телом if просто установите набор переменных)


person BCS    schedule 20.05.2010    source источник


Ответы (7)


Альтернатива тому, что вы делаете:

if [[ $* == *--flag* ]]

См. также BashFAQ/035.

Примечание. Это также будет соответствовать --flags-off, так как это простая проверка подстроки.

person Dennis Williamson    schedule 20.05.2010
comment
Не будет ли это также собирать ложные срабатывания? например если вы ищете *-f*, оно будет соответствовать --force, -fish, --update-files, -w-t-fи т. д. - person nathanchere; 30.05.2017
comment
@nathanchere: важен тщательный выбор строки совпадения. Обратите внимание, что ваш пример допускает все совпадения, которые вы перечисляете, но мой оставляет гораздо меньше возможностей ложных срабатываний. Учитывая требование OP к простому решению, мое вполне подходит. В противном случае используйте getopt или getopts. - person Dennis Williamson; 30.05.2017
comment
Точно моя точка зрения. Мой пример был намеренно упрощенным, но все же демонстрирует ту же проблему, что и ваша. например если вы используете *--flag*, вы будете соответствовать --flag, --flags-off и т. д. т. е. это неаккуратное решение - person nathanchere; 31.05.2017
comment
@nathanchere Вы можете опубликовать лучшее решение, отвечающее требованиям OP. - person Dennis Williamson; 31.05.2017
comment
Не могли бы вы рассказать, как получить порядковый номер этого $*? - person Optimus Prime; 14.07.2017
comment
@OptimusPrime: Зачем тебе порядковый номер? - person Dennis Williamson; 14.07.2017
comment
Допустим, флаг находится на 5-й позиции, тогда 6 долларов дадут мне его значение. - person Optimus Prime; 18.07.2017
comment
@OptimusPrime: ОП хотел быстрый и грязный способ проверить наличие флага без использования getopt. Для полной обработки флагов с (и/или без) аргументами вы должны использовать методы, описанные в FAQ, на который я ссылался. - person Dennis Williamson; 18.07.2017
comment
"$*" не нужно? - person Randoms; 05.08.2017
comment
@Randoms: Если вы спрашиваете, нужно ли цитировать - это не обязательно в двойных квадратных скобках. - person Dennis Williamson; 23.08.2017
comment
Моя небольшая модификация, которая решает упомянутую проблему ложных срабатываний - person Nick Veld; 20.01.2021

Я обычно вижу, что это делается с помощью оператора case. Вот выдержка из скрипта git-repack. :

while test $# != 0
do
    case "$1" in
    -n) no_update_info=t ;;
    -a) all_into_one=t ;;
    -A) all_into_one=t
        unpack_unreachable=--unpack-unreachable ;;
    -d) remove_redundant=t ;;
    -q) GIT_QUIET=t ;;
    -f) no_reuse=--no-reuse-object ;;
    -l) local=--local ;;
    --max-pack-size|--window|--window-memory|--depth)
        extra="$extra $1=$2"; shift ;;
    --) shift; break;;
    *)  usage ;;
    esac
    shift
done

Обратите внимание, что это позволяет вам проверять как короткие, так и длинные флаги. В этом случае другие параметры строятся с использованием переменной extra.

person Kaleb Pederson    schedule 20.05.2010
comment
Я думаю, вам нужно удалить последний оператор «shift» сразу после «esac». По крайней мере, на моей машине это не работает, если оно присутствует. - person Thomas Krille; 30.07.2015
comment
@ThomasKrille Сдвиг в конце необходим для перехода к следующему аргументу. Если вы удалите его, он будет работать бесконечно (если только вы не используете один из вариантов со сдвигом в случае). - person scorgn; 04.12.2020

вы можете использовать простой подход и перебирать аргументы, чтобы проверить каждый из них на равенство с заданным параметром (например, -t).

поместите его в функцию:

has_param() {
    local term="$1"
    shift
    for arg; do
        if [[ $arg == "$term" ]]; then
            return 0
        fi
    done
    return 1
}

и используйте его как предикат в тестовых выражениях:

if has_param '-t' "$@"; then
    echo "yay!"
fi

if ! has_param '-t' "$1" "$2" "$wat"; then
    echo "nay..."
fi

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

for arg; do
    if [[ -z "$arg" ]]; then
        return 2
    fi
    # ...

это очень читабельно и не даст вам ложных срабатываний, как сопоставление с образцом или сопоставление с регулярным выражением.
это также позволит размещать флаги в произвольных позициях, например, вы можете поставить -h в конец командной строки (не вникая, хорошо это или плохо).


но чем больше я об этом думал, тем больше меня что-то беспокоило.

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

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

person Eliran Malka    schedule 03.06.2019
comment
Это был лучший ответ! - person Vegard Stikbakke; 14.08.2019
comment
отличный ответ. Обратите внимание, что это также можно использовать для нескольких параметров. Я нашел этот очиститель, а затем getopts. - person Matt Pengelly; 06.09.2019

Вы можете использовать ключевое слово getopt в bash.

Из http://aplawrence.com/Unix/getopts.html:

получитьопт

Это автономный исполняемый файл, который существует уже давно. В более старых версиях отсутствует возможность обрабатывать аргументы в кавычках (foo a "это не сработает" c), а версии, которые могут, делают это неуклюже. Если вы используете последнюю версию Linux, ваш «getopt» может это сделать; SCO OSR5, Mac OS X 10.2.6 и FreeBSD 4.4 имеют более старую версию, которой нет.

Простое использование «getopt» показано в этом мини-скрипте:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done
person WhirlWind    schedule 20.05.2010
comment
Лучше использовать встроенный getopts, чем внешний getopt. - person Dennis Williamson; 20.05.2010
comment
@Dennis: getopts поддерживает длинные имена параметров, такие как --flag? - person indiv; 20.05.2010
comment
@indiv: О, извините, я пропустил это требование. Я бы использовал оператор case, прежде чем использовать getopt. См. это для сравнения getopt и getopts. - person Dennis Williamson; 20.05.2010
comment
Из часто задаваемых вопросов в приведенном выше ответе: getopts: Если это не версия от util-linux, и вы не используете расширенный режим, никогда не используйте getopt(1). Традиционные версии getopt не могут обрабатывать пустые строки аргументов или аргументы со встроенными пробелами. - person Ajax; 29.12.2015

Я внес небольшие изменения в ответ Eliran Malka:

Эта функция может оценивать различные синонимы параметров, такие как -q и --quick. Кроме того, он не использует возврат 0/1, а возвращает ненулевое значение, когда параметр найден:

function has_param() {
    local terms="$1"
    shift

    for term in $terms; do
        for arg; do
            if [[ $arg == "$term" ]]; then
                echo "yes"
            fi
        done
    done
}

# Same usage:

# Assign result to a variable.
FLAG_QUICK=$(has_param "-q --quick" "$@")  # "yes" or ""

# Test in a condition using the nonzero-length-test to detect "yes" response.
if [[ -n $(has_param "-h --help" "$@") ]]; then; 
    echo "Need help?"
fi

# Check, is a flag is NOT set by using the zero-length test.
if [[ -z $(has_param "-f --flag" "$@") ]]; then
    echo "FLAG NOT SET"
fi
person Philipp    schedule 16.09.2020

Но не альтернатива, а улучшение.

if echo $* | grep -e "\b--flag\b" -q

При поиске границы слов действительно будет выбран вариант --flag, а не --flagstaff или --not-really--flag.

person snaeqe    schedule 19.11.2020

Модификация Денниса Уильямсона ответ с дополнительным примером аргумента в краткой форме.

if [[ \ $*\  == *\ --flag\ * ]] || [[ \ $*\  == *\ -f\ * ]]

Он решает проблему ложноположительного совпадения --flags-off и даже --another--flag (более популярный такой случай для аргументов с одним пунктиром: --one-more-flag вместо *-f*).

\ (обратная косая черта + пробел) означает место для выражений внутри [[ ]]. Пробелы вокруг $* позволяют быть уверенными, что аргументы не касаются ни начала, ни конца строки, они касаются только пробелов. И теперь целевой флаг, окруженный пробелами, можно искать в строке с аргументами.

person Nick Veld    schedule 20.01.2021