Який лаконічний спосіб перевірити, чи змінні середовища встановлені в скрипті оболонки 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

що багато друкує. Чи є більш елегантна ідіома для перевірки встановлення набору змінних середовища?

EDIT: Я повинен зазначити, що ці змінні не мають значущого значення за замовчуванням - сценарій повинен помилитися, якщо такі не встановлені.


2
Багато відповідей на це запитання схожі на те, що ви побачили на Code Exchange 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}"

Перший варіант (використовуючи просто ?) вимагає встановити STATE, але STATE = "" (порожня рядок) добре - не саме те, що ви хочете, але альтернативні та старі позначення.

Другий варіант (за допомогою :?) вимагає, щоб DEST був встановлений і не порожній.

Якщо ви не вводите повідомлення, оболонка надає повідомлення за замовчуванням.

${var?}Конструкція переносить назад Version 7 UNIX і Bourne Shell (1978 або близько цього). ${var:?}Конструкція трохи більш пізні: Я думаю , що це було в System III UNIX близько 1981, але це , можливо, були в ПРБ UNIX до цього. Тому він знаходиться в оболонці Корна і в оболонках POSIX, включаючи спеціально Bash.

Зазвичай це задокументовано на сторінці чоловічої оболонки в розділі під назвою Розширення параметрів . Наприклад, bashпосібник говорить:

${parameter:?word}

Помилка відображення, якщо нульова або не встановлена. Якщо параметр недійсний або не встановлений, розширення слова (або повідомлення для цього ефекту, якщо слова немає), записується на стандартну помилку, а оболонка, якщо вона не є інтерактивною, залишається. В іншому випадку значення параметра замінено.

Колонна команда

Я, мабуть, слід додати, що команда двокрапки просто перевіряє свої аргументи, а потім успішно. Це оригінальне позначення коментаря до оболонки (до ' #' до кінця рядка). Довгий час сценарії оболонок Борна мали двокрапку як перший символ. Оболонка C читає сценарій і використовує перший символ, щоб визначити, чи це для оболонки C ( #хеш) або оболонки Борна ( :двокрапка). Тоді ядро ​​вступило в акт і додало підтримку ' #!/path/to/program', а оболонка Борна отримала ' #' коментарів, і конвенція про двокрапку пішла в сторону. Але якщо ви натрапите на сценарій, який починається з двокрапки, тепер ви дізнаєтесь чому.


Позиція подвійних лапок

Блонг запитав у коментарі :

Будь-які думки щодо цієї дискусії? https://github.com/koalaman/shellcheck/isissue/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 року і ніколи не бачив цього синтаксису - просто показує ...
AndrewR

Як це працює і де це документально зафіксовано? Мені цікаво, чи можна її модифікувати для перевірки існування змінної та встановлене на певне значення.
jhabbott

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

Як використовувати цей синтаксис із змінною рядка з пробілами в ньому. Під час встановлення змінної, яку я хочу встановити, "Це тест", я отримую помилку із записом "Це: команда не знайдена". Будь-які ідеї?
користувач2294382

1
Будь-які думки щодо цієї дискусії? github.com/koalaman/shellcheck/isissue/…
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
Це, однак, не розрізняє невстановлену змінну та набір змінних у порожній рядок.
чепнер

57

Ваше запитання залежить від оболонки, яку ви використовуєте.

Шкаралупа Борна залишає дуже мало на шляху до того, що ви хочете.

АЛЕ ...

Це працює, майже всюди.

Просто спробуйте триматися подалі від csh. Це було добре для дзвіночків, які він додав, порівняв оболонку Борна, але вона справді скрипить. Якщо ви мені не вірите, просто спробуйте розділити STDERR в csh! (-:

Тут є дві можливості. Приклад вище, а саме:

${MyVariable:=SomeDefault}

вперше вам потрібно звернутися до $ MyVariable. Це сприймає заздрість. var MyVariable і, якщо він наразі не встановлений, призначає зміну SomeDefault змінній для подальшого використання.

Ви також маєте можливість:

${MyVariable:-SomeDefault}

яка просто замінює SomeDefault для змінної, де ви використовуєте цю конструкцію. Він не призначає змінній SomeDefault змінній, і значення MyVariable все одно буде нульовим після появи цього твердження.


3
CSH: (foo> foo.out)> & foo.err
Mr.Ree

Оболонка Борна робить все, що потрібно.
Джонатан Леффлер

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), і я не зміг фактично встановити var на порожню рядок, без того, щоб він також був знятий. Як би ти це зробив?
Sz.

@ Sz Я не впевнений, що ти маєш на увазі. "Скинутий" означає, що ім'я не має жодного значення, призначеного йому. Якщо значення fooне встановлено, "$foo"усе ще розширюється до порожнього рядка.
чепнер

17

bash4.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 у вас є $? ДЕРЖАВ.


2
Це перевіряє, чи STATEвін порожній чи невідомий, а не просто скинутий.
чепнер

7

Для таких майбутніх людей, як я, я хотів зробити крок вперед і параметризувати ім'я 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

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 - інша справа.
nafdef

-7

$?Синтаксис дуже акуратно:

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

Це працює, але хтось тут знає, де я можу знайти документи в цьому синтаксисі? $? як правило, попереднє повернене значення.
Danny Staple

Моє погано - це, здається, не працює. Спробуйте його у простому сценарії, з набором і без цього.
Danny Staple

11
У оболонках Bourne, Korn, Bash та POSIX $?- статус виходу попередньої команди; $?BLAH- рядок, що складається з статусу виходу попередньої команди, об'єднаної з "BLAH". Це зовсім не перевіряє змінну $BLAH. (Ходять чутки, що це може зробити більш-менш те, що потрібно csh, але морські раковини краще залишити на морському березі.)
Джонатан Леффлер

Це абсолютно неправильна відповідь, що стосується Баша.
codeforester
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.