Як написати сценарій, який працює з аргументами чи без них?


13

У мене є bash-сценарій, який виглядає приблизно так:

#!/bin/bash

if [ $1 = "--test" ] || [ $1 = "-t" ]; then
    echo "Testing..."
    testing="y"

else
    testing="n"
    echo "Not testing."

fi

Так що я хочу , щоб бути в змозі зробити це не тільки запустити його ./script --testабо ./script -t, але і без будь - яких аргументів (тільки ./script), але , здавалося б , якби я зробити це з поточним кодом виходу просто:

./script: line 3: [: =: unary operator expected
./script: line 3: [: =: unary operator expected
Not testing.

Отже, як я програмую його так, що запуск його без аргументів буде просто робити, elseне викидаючи помилку? Що я роблю неправильно?


1
Вам потрібні подвійні дужки навколо чеків. [[]]
Терранс


@Теранс вони вам не потрібні , хоча ви повинні використовувати їх, якщо націлені на баш.
Руслан

Відповіді:


1

"Правильний спосіб" використання довгих варіантів сценаріїв оболонок - це утиліта getopt GNU . Існує також getopts, який є вбудованим в башти , але він дозволяє лише короткі варіанти, наприклад -t. Кілька прикладів getoptвикористання можна знайти тут .

Ось сценарій, який демонструє, як я підійшов до вашого питання. Пояснення більшості кроків додаються як коментарі в межах самого сценарію.

#!/bin/bash

# GNU getopt allows long options. Letters in -o correspond to
# comma-separated list given in --long.

opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened

set -- $opts # some would prefer using eval set -- "$opts"
# if theres -- as first argument, the script is called without
# option flags
if [ "$1" = "--"  ]; then
    echo "Not testing"
    testing="n"
    # Here we exit, and avoid ever getting to argument parsing loop
    # A more practical case would be to call a function here
    # that performs for no options case
    exit 1
fi

# Although this question asks only for one 
# option flag, the proper use of getopt is with while loop
# That's why it's done so - to show proper form.
while true; do
    case "$1" in
        # spaces are important in the case definition
        -t | --test ) testing="y"; echo "Testing" ;;
    esac
    # Loop will terminate if there's no more  
    # positional parameters to shift
    shift  || break
done

echo "Out of loop"

З кількома спрощеннями та видаленими коментарями це може бути скорочено до:

#!/bin/bash
opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened
set -- $opts    
case "$1" in
    -t | --test ) testing="y"; echo "Testing";;
    --) testing="n"; echo "Not testing";;
esac

16

Кілька способів; Два найбільш очевидних:

  • Поставте $1подвійні лапки:if [ "$1" = "--test" ]
  • Перевірте кількість аргументів, використовуючи $ #

А ще краще, використовуйте getopts.


2
+1 для використання getopts. Це найкращий шлях.
Сет

3
Ще одна поширена ідіома - [ "x$1" = "x--test" ]захищати знову аргументи командного рядка, які є дійсними операторами для [команди. (Це ще одна причина, чому [[ ]]віддається перевага: це частина синтаксису оболонки, а не просто вбудована команда.)
Пітер Кордес

7

Вам потрібно цитувати свої змінні всередині умови if. Замінити:

if [ $1 = "--test" ] || [ $1 = "-t" ]; then

з:

if [ "$1" = '--test' ] || [ "$1" = '-t' ]; then  

Тоді це спрацює:

➜ ~ ./test.sh
Не тестує.

Завжди подвійно цитуйте свої змінні!


Чи є однозначні лапки або вони можуть замість цього використовувати подвійні лапки?

Точна відповідь залежить від того, яку оболонку ви використовуєте, але з вашого питання я припускаю, що ви використовуєте нащадка Bourne-shell. Основна відмінність одинарних та подвійних лапок полягає в тому, що змінне розширення відбувається всередині подвійних лапок, але не всередині одинарних лапок: $ FOO = бар $ ехо "$ FOO" бар $ echo '$ FOO' $ FOO (хм .... може ' t формат коду у коментарях ...)
JayEye

@ParanoidPanda Не потрібно використовувати одиничні лапки, ні, але добре використовувати їх на строкових літералах. Таким чином, ви не здивуєтесь згодом, якщо у ваших рядкових даних є символ, який баш хоче розширити.
Сет

@ParanoidPanda Що сказав @JayEye: З одинарними котируваннями немає змінних foo=bar echo '$foo'роздруків розширень $fooна вихід, а foo=bar echo "$foo"друкує barзамість цього.
EKons

4

Ви використовуєте bash. Bash має хорошу альтернативу [: [[. З цим [[вам не доведеться турбуватися про котирування, і ви отримуєте набагато більше операторів, ніж [, включаючи належну підтримку ||:

if [[ $1 = "--test" || $1 = "-t" ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Або ви можете використовувати регулярні вирази:

if [[ $1 =~ ^(--test|-t) ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Звичайно, завжди слід робити правильне цитування, але немає причин дотримуватися цього [при використанні bash.


3

Щоб перевірити наявність аргументів (загалом, і виконайте дії відповідно). Це повинно працювати:

#!/bin/bash

check=$1

if [ -z "$check" ]; then
  echo "no arguments"
else echo "there was an argument"
fi

Чому ні [ -z "$1" ]?
EKons

@ ΈρικΚωνσταντόπουλος У цьому випадку, звичайно, однак, як правило, в сценаріях я називаю змінну одноразово, посилаюся на неї в процедурах, що зазвичай більше одного разу. Вирішили зберегти протокол у вибірці.
Яків Влійм

У цьому зразку ви, здається, використовуєте $checkодин раз, перед будь-яким shifts, тому було б краще використовувати $1замість цього.
EKons

@ ΈρικΚωνσταντόπουλος Звичайно, як не було сказано, це зразок , який слід використовувати в сценаріях. У сценаріях неправильно повторювати те ж саме знову і знову.
Яків Влійм

Я розумію, що ви маєте на увазі, але краще практикувати не використовувати шебангів #!при наданні зразків (ви повинні описати оболонку поза кодом).
EKons
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.