Какой краткий способ проверить, что переменные среды установлены в сценарии оболочки 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.