Як я можу аналізувати необов'язкові аргументи в bash-скрипті, якщо не наводиться порядок?


13

Мене бентежить як включити необов'язкові аргументи / прапорці під час написання bash-скрипту для наступної програми:

Програма вимагає двох аргументів:

run_program --flag1 <value> --flag2 <value>

Однак є кілька необов’язкових прапорів:

run_program --flag1 <value> --flag2 <value> --optflag1 <value> --optflag2 <value> --optflag3 <value> --optflag4 <value> --optflag5 <value> 

Я хотів би запустити скрипт bash таким чином, що він потребує аргументів користувача. Якщо користувачі вводять лише два аргументи для того, це було б:

#!/bin/sh

run_program --flag1 $1 --flag2 $2

Але що робити, якщо включений будь-який з необов’язкових аргументів? Я б подумав, що це буде

if [ --optflag1 "$3" ]; then
    run_program --flag1 $1 --flag2 $2 --optflag1 $3
fi

Але що робити, якщо $ 4, але не $ 3?


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


@orion З getopts, мені потрібно вказати кожну комбінацію аргументів? 3 & 4, 3 & 5, 3 & 4 & 5 тощо?
ShanZhengYang

Ні, ви просто встановите їх, якщо отримаєте їх, інакше він повідомляє, що його не знайдено, тому в основному ви просто "отримуєте" кожен варіант у будь-якому порядку і вказуєте їх у будь-якому порядку, якщо він взагалі є. Але просто прочитайте сторінку bash man, все там є.
Оріон

@orion Вибачте, але я все ще не зовсім розумію getopts. Скажімо, я змушую користувачів запускати сценарій з усіма аргументами: run_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSEщо запускає програму як program --flag1 VAL --flag2 VAL. Якщо ви бігли run_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE, програма працюватиме як program --flag1 VAL --flag2 VAL --optflag2 10. Як можна отримати таку поведінку getopts?
ShanZhengYang

Відповіді:


25

У цій статті показано два різні способи - shiftі getopts(та обговорюються переваги та недоліки двох підходів).

Переглядаючи shiftсценарій $1, вирішує, яку дію вжити, а потім виконує shift, переходячи $2до $1, $3до $2тощо.

Наприклад:

while :; do
    case $1 in
        -a|--flag1) flag1="SET"            
        ;;
        -b|--flag2) flag2="SET"            
        ;;
        -c|--optflag1) optflag1="SET"            
        ;;
        -d|--optflag2) optflag2="SET"            
        ;;
        -e|--optflag3) optflag3="SET"            
        ;;
        *) break
    esac
    shift
done

За допомогою цього getoptsви визначаєте (короткі) параметри у whileвиразі:

while getopts abcde opt; do
    case $opt in
        a) flag1="SET"
        ;;
        b) flag2="SET"
        ;;
        c) optflag1="SET"
        ;;
        d) optflag2="SET"
        ;;
        e) optflag3="SET"
        ;;
    esac
done

Очевидно, що це просто фрагменти коду, і я пропустив перевірку - перевіряючи, чи встановлені обов'язкові аргументи flag1 та flag2 тощо.

Який підхід ви використовуєте, певною мірою є питанням смаку - наскільки портативний ви хочете, щоб був ваш сценарій, чи можете ви жити лише з короткими (POSIX) параметрами чи хочете довгі варіанти (GNU) тощо.


Зазвичай я роблю while (( $# ))замість цього while :;і часто кидаюсь із помилкою у *справі
Ясен

це відповідає назви, але не на питанні.
Ясен

@Jasen Я переглянув це вчора ввечері і не міг за все життя зрозуміти, чому. Сьогодні вранці це набагато зрозуміліше (і я вже бачив відповідь Оріона). Цю відповідь я видалю пізніше сьогодні (я хотів спочатку підтвердити ваш коментар, і це здавалося найпростішим способом).
Джон N

немає проблем, це все-таки хороша відповідь, просто питання ухилився від неї.
Ясен

1
Примітка: У випадку set -o nounsetпершого рішення помилка буде відсутня, якщо не вказано жодного параметра. Виправлення:case ${1:-} in
Рафаель

3

використовувати масив.

#!/bin/bash

args=( --flag1 "$1" --flag2 "$2" )
[  "x$3" = xFALSE ] ||  args+=( --optflag1 "$3" )
[  "x$4" = xFALSE ] ||  args+=( --optflag2 "$4" )
[  "x$5" = xFALSE ] ||  args+=( --optflag3 "$5" )
[  "x$6" = xFALSE ] ||  args+=( --optflag4 "$6" )
[  "x$7" = xFALSE ] ||  args+=( --optflag5 "$7" )

program_name "${args[@]}"

це буде правильно обробляти аргументи з пробілами в них.

[ред.] Я використовував приблизно еквівалентний синтаксис, args=( "${args[@]}" --optflag1 "$3" )але G-Man запропонував кращий спосіб.


1
Ви можете трохи впорядкувати це, сказавши args+=( --optflag1 "$3" ). Можливо, ви захочете побачити мою відповідь на нашу довідкову роботу, наслідки для безпеки, якщо не вдалося процитувати змінну в оболонках bash / POSIX , де я обговорюю цю методику (побудови командного рядка в масиві шляхом умовного додавання необов'язкових аргументів).
G-Man каже: "Відновіть Моніку"

@ G-Man Спасибі, я не бачив, щоб у документації, розуміючи, [@]мав достатній інструмент для вирішення моєї проблеми.
Ясен

1

У сценарії оболонки аргументами є "$ 1", "$ 2", "$ 3" і т. Д. Кількість аргументів - $ #.
Якщо ваш скрипт не розпізнає варіанти, ви можете залишити виявлення параметрів і розглянути всі аргументи як операнди.
Для розпізнавання варіантів використовуйте вбудований getopts


0

Якщо ваші параметри введення є позиційними (ви знаєте, в яких місцях вони знаходяться), а не вказані прапорами, то вам потрібно лише побудувати командний рядок. Просто підготуйте аргументи команд для всіх:

FLAG1="--flag1 $1"
FLAG2="--flag2 $2"
OPTFLAG1=""
OPTFLAG2=""
OPTFLAG3=""
if [ xFALSE != x"$3" ]; then
   OPTFLAG1="--optflag1 $3"
fi
if [ xFALSE != x"$4" ]; then
   OPTFLAG2="--optflag2 $4"
fi
#the same for other flags

#now just run the program
runprogram $FLAG1 $FLAG2 $OPTFLAG1 $OPTFLAG2 $OPTFLAG3

Якщо параметри не вказані, то відповідні рядки порожні і розгортаються в ніщо. Зауважте, що в останньому рядку немає лапок. Це тому, що ви хочете, щоб оболонка розділила параметри на слова (щоб надати --flag1і $1окремі аргументи вашій програмі). Звичайно, це піде не так, якщо ваші вихідні параметри містять пробіли. Якщо ви працюєте з цим, ви можете залишити його, але якщо це загальний сценарій, він може мати несподівану поведінку, якщо користувач вводить щось із пробілами. Щоб вирішити це, вам доведеться зробити код трохи потворнішим.

xПрефікс в []тесті є в разі , $3чи $4порожньо. У цьому випадку bash [ FALSE != $3 ]перетвориться [ FALSE != ]на синтаксичну помилку, тому для захисту від цього є ще один довільний символ. Це дуже поширений спосіб, ви побачите це в багатьох сценаріях.

Я налаштував OPTFLAG1і решту з них ""на початку просто для того, щоб бути впевненим (на випадок, якщо вони були налаштовані на щось раніше), але якщо вони насправді не були декларовані в оточенні, то вам цього строго не потрібно робити.

Кілька додаткових зауважень:

  • Насправді ви могли просто отримувати параметри так само, як і runprogramце: з прапорами. Ось про що John Nйдеться. Ось де getoptsстає корисним.
  • Використання для цього FALSE трохи нестандартно і досить довго. Зазвичай можна використовувати один символ (можливо -), щоб подати порожнє значення або просто пропустити порожню рядок, наприклад "", якщо це не є дійсним значенням параметра. Порожня рядок також робить тест коротшим, просто використовуйте if [ -n "$3" ].
  • Якщо є лише один необов'язковий прапор або вони залежні, тому ви ніколи не можете мати OPTFLAG2, але не OPTFLAG1, ви можете просто пропустити ті, які ви не хочете встановлювати. Якщо ви використовуєте ""для порожніх параметрів, як це було запропоновано вище, ви все одно можете пропустити всі порожні порожні місця.

Цей метод є майже таким чином, як варіанти передаються компіляторам в Makefiles.

І знову - якщо входи можуть містити пробіли, це стає некрасивим.


Ні, ви не хочете, щоб оболонка розділяла параметри на слова. Ви завжди повинні цитувати всі посилання на змінні оболонки, якщо у вас немає вагомих причин цього не робити, і ви впевнені, що знаєте, що робите. Див. Наслідки безпеки, коли не можна процитувати змінну в оболонках bash / POSIX , якщо ви її не знайомі, і прокрутіть униз до моєї відповіді , де я вирішую саме цю проблему (з тією ж методикою, яку використовує Jasen ).
G-Man каже: "Відновіть Моніку"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.