Какой краткий способ проверить, что переменные среды установлены в сценарии оболочки Unix?


500

У меня есть несколько сценариев оболочки Unix, где мне нужно проверить, установлены ли определенные переменные среды, прежде чем я начну что-то делать, поэтому я делаю такие вещи:

if [ -z "$STATE" ]; then
    echo "Need to set STATE"
    exit 1
fi  

if [ -z "$DEST" ]; then
    echo "Need to set DEST"
    exit 1
fi

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

РЕДАКТИРОВАТЬ: я должен отметить, что эти переменные не имеют значащего значения по умолчанию - скрипт должен выдавать ошибку, если какие-либо не установлены.


2
Многие ответы на этот вопрос похожи на то, что вы увидите на Code Golf Stack Exchange .
— Даниэль Шиллинг

Ответы:


587

Расширение параметра

Очевидный ответ - использовать одну из специальных форм расширения параметров:

: ${STATE?"Need to set STATE"}
: ${DEST:?"Need to set DEST non-empty"}

Или лучше (см. Раздел «Положение двойных кавычек» ниже):

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Первый вариант (с использованием just ?) требует установки STATE, но STATE = "" (пустая строка) в порядке - не совсем то, что вы хотите, но альтернативная и более старая нотация.

Второй вариант (использование :?) требует, чтобы DEST был установлен и не был пустым.

Если вы не укажете сообщение, оболочка предоставит сообщение по умолчанию.

${var?}Конструкция переносит обратно Version 7 UNIX и Bourne Shell (1978 или около этого). Эта ${var:?}конструкция немного новее: я думаю, что она была в System III UNIX примерно в 1981 году, но до этого она могла быть в PWB UNIX. Поэтому он находится в оболочке Korn и в оболочках POSIX, включая, в частности, Bash.

Обычно это документируется на справочной странице оболочки в разделе под названием « Расширение параметров» . Например, bashруководство говорит:

${parameter:?word}

Ошибка дисплея, если ноль или не установлен. Если параметр равен нулю или не задан, расширение слова (или сообщение на этот счет, если слово отсутствует) записывается в стандартную ошибку, и оболочка, если она не является интерактивной, завершается. В противном случае значение параметра подставляется.

Колонское командование

Я, вероятно, должен добавить, что команда двоеточия просто оценивает свои аргументы и затем успешно. Это оригинальная запись комментария оболочки (перед ' #' до конца строки). Долгое время сценарии оболочки Bourne имели двоеточие в качестве первого символа. Оболочка C будет читать сценарий и использовать первый символ, чтобы определить, относится ли он к оболочке C ( #хэш ') или к оболочке Борна ( :двоеточие'). Затем ядро ​​приняло участие и добавило поддержку комментариев ' #!/path/to/program', а оболочка Bourne получила ' #', и соглашение о двоеточии ушло на второй план. Но если вы столкнетесь со сценарием, который начинается с двоеточия, теперь вы будете знать, почему.


Положение двойных кавычек

blong спросил в комментарии :

Есть мысли по поводу этой дискуссии? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

Суть обсуждения такова:

… Однако, когда я shellcheckэто (с версией 0.4.1), я получаю это сообщение:

In script.sh line 13:
: ${FOO:?"The environment variable 'FOO' must be set and non-empty"}
  ^-- SC2086: Double quote to prevent globbing and word splitting.

Любой совет, что мне делать в этом случае?

Краткий ответ «делай как shellcheckподсказывает»:

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Чтобы проиллюстрировать почему, изучите следующее. Обратите внимание, что :команда не отображает свои аргументы (но оболочка оценивает аргументы). Мы хотим видеть аргументы, поэтому код ниже использует printf "%s\n"вместо :.

$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$ 
$ x="*"
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
bash: x: You must set x
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
*
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ 

Обратите внимание, что значение in $xрасширяется до первого, *а затем списка имен файлов, когда общее выражение не в двойных кавычках. Это то, что shellcheckрекомендуется исправить. Я не проверял, что он не возражает против формы, в которой выражение заключено в двойные кавычки, но разумно предположить, что все будет в порядке.


2
Это то, что мне нужно. Я использую различные версии Unix с 1987 года, и я никогда не видел этот синтаксис - просто
— хочу

Как это работает и где это задокументировано? Мне интересно, если он может быть изменен, чтобы проверить, существует ли переменная и задано ли конкретное значение.
— Джабботт

6
Это задокументировано на странице руководства по оболочке или в руководстве по Bash, обычно под заголовком «Расширение параметров». Стандартный способ проверить , существует ли переменная и устанавливается на значение конкретного (скажем , «ABC») просто: if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi.
— Джонатан Леффлер

Как использовать этот синтаксис со строковой переменной с пробелами в нем. Я получаю сообщение об ошибке «This: команда не найдена», когда я устанавливаю переменную, которую я хочу проверить, «Это тест». Любые идеи?
— user2294382

1
Есть мысли по поводу этой дискуссии? github.com/koalaman/shellcheck/issues/…
— blong

138

Попробуй это:

[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;

8
Это довольно многословно. Точка с запятой избыточна.
— Джонатан Леффлер

3
Или еще короче [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } смотрите этот пост в блоге
— zipizap

36
Что это такое, читаемо. Я все за этого. Скриптам Bash нужна вся помощь, которую они могут получить в этом отделе!
— Иан Дункан

оригинальный ответ более последовательн в своем синтаксисе.
— быстрый зуб

4
Это, однако, не различает неустановленную переменную и переменную, установленную в пустую строку.
— Чепнер

56

Ваш вопрос зависит от оболочки, которую вы используете.

Оболочка Борна оставляет очень мало того, что вы ищете.

НО...

Это работает, почти везде.

Просто попробуйте и держитесь подальше от csh. Это было хорошо для добавленных прибамбасов, по сравнению с панцирем Борна, но сейчас он действительно скрипит. Если вы мне не верите, просто попытайтесь выделить STDERR в csh! (-:

Здесь есть две возможности. Пример выше, а именно с использованием:

${MyVariable:=SomeDefault}

в первый раз вам нужно обратиться к $ MyVariable. Это занимает env. var MyVariable и, если он в данный момент не задан, присваивает значение SomeDefault переменной для последующего использования.

У вас также есть возможность:

${MyVariable:-SomeDefault}

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


3
CSH: (foo> foo.out)> & foo.err
— Мистер Ри

Оболочка Борна делает то, что требуется.
— Джонатан Леффлер

51

Конечно, самый простой подход - добавить -uпереключатель к шебангу (строка вверху вашего скрипта), предполагая, что вы используете bash:

#!/bin/sh -u

Это приведет к завершению работы скрипта, если в нем скрываются какие-либо несвязанные переменные.


40
${MyVariable:=SomeDefault}

Если MyVariableустановлено и не равно нулю, будет сброшено значение переменной (= ничего не происходит).
Остальное MyVariableустановлено SomeDefault.

Вышеприведенное попытается выполнить ${MyVariable}, так что если вы просто хотите установить переменную do:

MyVariable=${MyVariable:=SomeDefault}

Это не генерирует сообщение - и Q говорит, что по умолчанию нет (хотя это не говорило, когда вы печатали свой ответ).
— Джонатан Леффлер

10
Правильные версии: (1): $ {MyVariable: = SomeDefault} или (2): MyVariable = $ {MyVariable: -SomeDefault}
— Джонатан Леффлер

23

На мой взгляд, самая простая и совместимая проверка для #! / Bin / sh :

if [ "$MYVAR" = "" ]
then
   echo "Does not exist"
else
   echo "Exists"
fi

Опять же, это для / bin / sh и совместимо также со старыми системами Solaris.


Это «работает», но даже старые системы Solaris имеют систему /bin/shподдержки ${var:?}обозначений и т. Д.
— Джонатан Леффлер

Лучший ответ на данный момент.
— Оле

4
Быть установленным в пустую строку отличается от того, чтобы быть установленным вообще. Этот тест вывел бы «Не существует» после обоих unset MYVARи MYVAR=.
— Чепнер

@chepner, я одобрил твой комментарий за предположительно ценный хедз-ап, но потом я продолжил тестировать его (с помощью bash) и не смог установить переменную в пустую строку без ее отключения. Как бы Вы это сделали?
— СЗ

@Sz Я не уверен, что ты имеешь в виду. «Unset» означает, что имя не имеет никакого значения, присвоенного ему. Если fooне установлено, "$foo"все равно расширяется до пустой строки.
— chepner

17

bashВ 4.2 введен -vоператор, который проверяет, задано ли для имени какое-либо значение, даже пустую строку.

$ unset a
$ b=
$ c=
$ [[ -v a ]] && echo "a is set"
$ [[ -v b ]] && echo "b is set"
b is set
$ [[ -v c ]] && echo "c is set"
c is set

16

Я всегда использовал:

if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi

Боюсь, не так уж и кратко.

Под CSH у вас есть $? STATE.


2
Это проверяет, STATEявляется ли пустым или не установленным, а не просто не установленным.
— Чепнер

7

Для будущих людей, таких как я, я хотел сделать шаг вперед и параметризовать имя переменной, чтобы я мог перебрать список имен переменных переменного размера:

#!/bin/bash
declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN)

for var_name in "${vars[@]}"
do
  if [ -z "$(eval "echo \$$var_name")" ]; then
    echo "Missing environment variable $var_name"
    exit 1
  fi
done

3

Мы можем написать хорошее утверждение, чтобы проверить сразу несколько переменных:

#
# assert if variables are set (to a non-empty string)
# if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise
#
# Usage: assert_var_not_null [-f] variable ...
#
function assert_var_not_null() {
  local fatal var num_null=0
  [[ "$1" = "-f" ]] && { shift; fatal=1; }
  for var in "$@"; do
    [[ -z "${!var}" ]] &&
      printf '%s\n' "Variable '$var' not set" >&2 &&
      ((num_null++))
  done

  if ((num_null > 0)); then
    [[ "$fatal" ]] && exit 1
    return 1
  fi
  return 0
}

Пример вызова:

one=1 two=2
assert_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_not_null -f one two three
echo test 3: return_code=$? # this code shouldn't execute

Вывод:

test 1: return_code=0
Variable 'three' not set
test 2: return_code=1
Variable 'three' not set

Больше таких утверждений здесь: https://github.com/codeforester/base/blob/master/lib/assertions.sh



1

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

mapfile -t arr < variables.txt

EXITCODE=0

for i in "${arr[@]}"
do
   ISSET=$(env | grep ^${i}= | wc -l)
   if [ "${ISSET}" = "0" ];
   then
      EXITCODE=-1
      echo "ENV variable $i is required."
   fi
done

exit ${EXITCODE}

-1

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

is_this_an_env_variable ()
    local var="$1"
    if env |grep -q "^$var"; then
       return 0
    else
       return 1
    fi
 }

1
Это не верно. is_this_an_env_variable Pвернет успех, потому что PATHсуществует.
— Эрик

Он существует, потому что это переменная окружения. Было ли это установлено, eqaul ненулевая строка - другой вопрос.
— Нафдеф

-7

$?Синтаксис очень аккуратно:

if [ $?BLAH == 1 ]; then 
    echo "Exists"; 
else 
    echo "Does not exist"; 
fi

Это работает, но кто-нибудь здесь знает, где я могу найти документы по этому синтаксису? $? обычно это предыдущее возвращаемое значение.
— Дэнни Стейпл

Мой плохой - это не похоже на работу. Попробуйте это простым сценарием, с этим набором и без него.
— Дэнни Стейпл

11
В оболочках Bourne, Korn, Bash и POSIX $?- состояние выхода предыдущей команды; $?BLAHявляется строкой, состоящей из состояния выхода предыдущей команды, объединенной с BLAH. Это не проверяет переменную $BLAHвообще. (Ходят слухи, что он может сделать более или менее то, что требуется csh, но морские раковины лучше всего оставить на берегу моря.)
— Джонатан Леффлер

Это совершенно неправильный ответ, если речь идет о Bash.
— codeforester
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.