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

387

У меня есть несколько сценариев оболочки 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

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

EDIT: я должен упомянуть, что эти переменные не имеют значимого значения по умолчанию - script должен выходить из системы, если они не установлены.

  • 2
    Многие ответы на этот вопрос похожи на то, что вы увидите на Code Golf Stack Exchange .
Теги:

14 ответов

460
Лучший ответ

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

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

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

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

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

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

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

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

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

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

${parameter:?word}

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

Команда Colon

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


Позиция двойных кавычек

blong в comment:

Любые мысли об этом обсуждении? 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.

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

Короткий ответ: "do as 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
$ 

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

  • 1
    Это то, что мне нужно. Я использую различные версии Unix с 1987 года, и я никогда не видел этот синтаксис - просто хочу показать ...
  • 0
    Как это работает и где это задокументировано? Мне интересно, если это можно изменить, чтобы проверить, существует ли переменная и задано ли конкретное значение.
Показать ещё 13 комментариев
83

Попробуйте следующее:

[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;
  • 7
    Это довольно многословно. Точка с запятой избыточна.
  • 2
    Или даже короче [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } см. этот блог
Показать ещё 7 комментариев
51

Ваш вопрос зависит от используемой оболочки.

Раковина Bourne оставляет очень мало на пути того, что вы после.

НО...

Он работает почти везде.

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

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

${MyVariable:=SomeDefault}

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

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

${MyVariable:-SomeDefault}

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

  • 3
    CSH: (foo> foo.out)> & foo.err
  • 0
    Оболочка Борна делает то, что требуется.
36

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

#!/bin/sh -u

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

  • 20
    или используйте команду set из вашего скрипта: set -u
26
${MyVariable:=SomeDefault}

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

  • 0
    Это не генерирует сообщение - и Q говорит, что по умолчанию нет (хотя это не говорило, когда вы печатали свой ответ).
  • 9
    Правильные версии: (1): $ {MyVariable: = SomeDefault} или (2): MyVariable = $ {MyVariable: -SomeDefault}
17

По-моему, самая простая и совместимая проверка для #!/bin/sh:

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

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

  • 0
    Это «работает», но даже старые системы Solaris имеют /bin/sh который поддерживает нотацию ${var:?} и т. Д.
  • 6
    Мне это нравится, потому что читать IMO очень просто.
Показать ещё 5 комментариев
13

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

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

Не намного яснее, боюсь.

В CSH у вас есть $? STATE.

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

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
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}
0

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

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 существует.
  • 0
    Он существует, потому что это переменная окружения. Было ли это установлено, eqaul ненулевая строка - другой вопрос.
0

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

#!/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
0

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

#
# 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_not_null [-f] variable ...
#
function assert_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_not_null one two
echo test 1: return_code=$?
assert_not_null one two three
echo test 2: return_code=$?
assert_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
-3

Это тоже может быть так:

if (set -u; : $HOME) 2> /dev/null
...
...

http://unstableme.blogspot.com/2007/02/checks-whether-envvar-is-set-or-not.html

  • 0
    Примечание: выход редир. заключается в отключении сообщения об ошибке, когда переменная не установлена.
-8

Синтаксис $? довольно опрятен:

if [ $?BLAH == 1 ]; then 
    echo "Exists"; 
else 
    echo "Does not exist"; 
fi
  • 0
    Это работает, но кто-нибудь здесь знает, где я могу найти документы по этому синтаксису? $? обычно это предыдущее возвращаемое значение.
  • 0
    Мой плохой - это не похоже на работу. Попробуйте это простым сценарием, с этим набором и без него.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню