Как написать скрипт, который работает с аргументами или без них?

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
Вам нужны двойные скобки вокруг чеков. [[]]
Терренс
3
Посмотрите на ловушку Bash # 4
Steeldriver
@ Терренс, они вам не нужны , хотя вы должны их использовать, если вы нацелены на bash.
Руслан

Ответы:

1

«Правильный способ» использования длинных опций в сценариях оболочки - использование утилиты getopt GNU . Также есть getopts, встроенный в bash , но он допускает только короткие варианты, такие как -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.

JayEye
источник
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 = bar $ echo "$ FOO" bar $ echo '$ FOO' $ FOO (hmm .... can ' код в формате t в комментариях ...)
JayEye
@ParanoidPanda Вы не должны использовать одинарные кавычки, нет, но это хорошая практика, чтобы использовать их на строковых литералах. Таким образом, вы не удивитесь позже, если ваши строковые данные будут иметь символ bash, который вы хотите расширить.
Сет
@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