Як визначити, чи не визначено рядок у сценарії оболонки Bash


151

Якщо я хочу перевірити нульовий рядок, який би я зробив

[ -z $mystr ]

але що робити, якщо я хочу перевірити, чи вказана змінна взагалі? Або немає відмінності в сценарії Баша?


27
будь ласка, звичайте використовувати [-z "$ mystr"] на відміну від [-z $ mystr]
Чарльз Даффі

як зробити обернену річ? Я маю на увазі, коли рядок не є нульовим
відкрийте шлях

1
@flow: А як же[ -n "${VAR+x}"] && echo not null
Лекенштейн

1
@CharlesDuffy Я читав це раніше на багатьох інтернет-ресурсах. Чому це віддається перевазі?
ffledgling

6
@Ayos, тому що якщо у вас немає лапок, вміст змінної є розділеним на рядки та глобальним; якщо це порожній рядок, він стає [ -z ]замість [ -z "" ]; якщо він має пробіли, він стає [ -z "my" "test" ]замість [ -z my test ]; а якщо це так [ -z * ], то *замінюється назви файлів у вашому каталозі.
Чарльз Даффі

Відповіді:


138

Я думаю , що відповідь ви після Мається на увазі (якщо не вказано інше) на Вінко «s відповідь , хоча це не прописано просто. Щоб визначити, чи встановлено VAR, але порожній чи не встановлений, ви можете використовувати:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Напевно, ви можете поєднати два тести на другому рядку в один із:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Однак якщо ви прочитаєте документацію для Autoconf, то виявите, що вони не рекомендують поєднувати терміни з ' -a' і рекомендують використовувати окремі прості тести в поєднанні з &&. Я не стикався із системою, де є проблеми; це не означає, що вони раніше не існували (але вони, мабуть, надзвичайно рідкісні в наші дні, навіть якщо в далекому минулому вони не були такими рідкісними).

Ви можете знайти деталі цих та інших пов’язаних розширень параметрів оболонки , testабо[ командні та умовні вирази в посібнику Bash.


Мене нещодавно по електронній пошті запитали про цю відповідь із запитанням:

Ви використовуєте два тести, і я добре розумію другий, але не перший. Точніше, я не розумію необхідності змінного розширення

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

Не вдалося б цього досягти?

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

Справедливе запитання - відповідь "Ні, ваша простіша альтернатива не робить те саме".

Припустимо, я напишу це перед вашим тестом:

VAR=

Ваш тест скаже, що "VAR взагалі не встановлений", але мій скаже (через те, що нічого не перегукується) "VAR встановлений, але його значення може бути порожнім". Спробуйте цей сценарій:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

Вихід:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

У другій парі тестів змінна встановлюється, але вона встановлюється в порожнє значення. Це відмінність ${VAR=value}та ${VAR:=value}позначення. Дітто для ${VAR-value}і ${VAR:-value}, і ${VAR+value}і ${VAR:+value}, і так далі.


Як вказує Гілі у своїй відповіді , якщо ви біжите bashз set -o nounsetваріантом, то основна відповідь вище не вдається unbound variable. Це легко виправити:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Або ви можете скасувати set -o nounsetопцію за допомогою set +u( set -uщо еквівалентно set -o nounset).


7
Єдина проблема, з якою я маю цю відповідь, полягає в тому, що вона виконує своє завдання досить опосередковано і неясно.
Швейцарія

3
Для тих, хто хоче шукати опис того, що зазначено вище, на сторінці bash man, шукайте розділ "Розширення параметрів", а потім цей текст: "Не виконуючи розширення підрядків, використовуючи форми, задокументовані нижче, тести bash для параметр, який не встановлено, або null ['null', що означає порожній рядок]. Опущення двокрапки призводить до тесту лише для параметра, який не встановлено (...) $ {параметр: + слово}: Використовуйте альтернативне значення. Якщо параметр є недійсним або не встановленим, нічого не замінено, інакше розширення слова замінено ".
Девід Тонхофер

2
@Swiss Це не є ні незрозумілим, ні непрямим, але ідіоматичним. Можливо, незнайомому програмісту, ${+}і ${-}це незрозуміло, але ознайомлення з цими конструкціями має важливе значення, якщо потрібно бути компетентним користувачем оболонки.
Вільям Перселл

Дивіться stackoverflow.com/a/20003892/14731, якщо ви хочете реалізувати це з set -o nounsetувімкненим.
Гілі

Я багато тестував на цьому; тому що результати в моїх сценаріях були непостійними. Я пропоную людям заглянути " [ -v VAR ]" підхід з bash -s v4.2 і далі .
буде

38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z також працює для невизначених змінних. Щоб розрізнити невизначений і визначений, ви використовуєте речі, перелічені тут, або, з чіткішими поясненнями, тут .

Найпростіший спосіб - це розширення, як у цих прикладах. Щоб отримати всі свої параметри, перегляньте розділ Розширення параметрів у посібнику.

Інше слово:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Значення за замовчуванням:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

Звичайно, ви використовуєте один із них по-різному, вводячи потрібне значення замість "значення за замовчуванням" та використовуючи розширення безпосередньо, якщо це доречно.


1
Але я хочу розрізнити, чи є рядок "" чи не був визначений ніколи. Це можливо?
Седжмп

1
ця відповідь говорить про те, як розрізняти ці два випадки; перейдіть за посиланням bash faq, яке передбачає більше обговорення.
Чарльз Даффі

1
Я додав, що після його коментаря Чарльз
Вінко Врсалович

2
Знайдіть "Розширення параметрів" на сторінці bash man для всіх цих "хитрощів". Наприклад, $ {foo: -default} використовувати значення за замовчуванням, $ {foo: = за замовчуванням} призначити значення за замовчуванням, $ {foo:? Повідомлення про помилку} для відображення повідомлення про помилку, якщо foo не встановлено тощо.
Jouni K .Сеппанен

18

Розширений посібник з сценаріїв баш, 10.2. Параметр Заміна:

  • $ {var + blahblah}: якщо var визначено, 'blahblah' замінено виразом, інше null заміщене
  • $ {var-blahblah}: якщо var визначено, він сам заміщений, інакше "blahblah" заміщений
  • $ {var? blahblah}: якщо var визначений, він заміщений, інакше функція існує з "blahblah" як повідомлення про помилку.


щоб базувати свою логіку програми на тому, визначена чи ні змінна $ mystr, ви можете зробити наступне:

isdefined=0
${mystr+ export isdefined=1}

тепер, якщо isdefined = 0, то змінна була невизначена, якщо isdefined = 1, змінна була визначена

Цей спосіб перевірки змінних кращий, ніж наведений вище відповідь, тому що він більш елегантний, читабельний, і якщо ваша оболонка bash була налаштована на помилку при використанні невизначених змінних (set -u), сценарій припиняється передчасно.


Інші корисні речі:

мати значення за замовчуванням 7, присвоєне $ mystr, якщо воно не визначене, і залишити його неушкодженим інакше:

mystr=${mystr- 7}

надрукувати повідомлення про помилку та вийти з функції, якщо змінна не визначена:

: ${mystr? not defined}

Остерігайтеся тут, що я використав ':', щоб не мати вмісту $ mystr як команди, якщо він визначений.


Я не знав про те? синтаксис змінних BASH. Це приємний улов.
Швейцарія

Це також працює з непрямими змінними посиланнями. Це проходить: var= ; varname=var ; : ${!varname?not defined}і це закінчується: varname=var ; : ${!varname?not defined}. Але корисно використовувати звичку, set -uяка робить те саме набагато простіше.
припинення

11

Підсумок тестів.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"

Гарна практика в баші. Іноді шляхи до файлів мають пробіли, або для захисту від введення команд
k107

1
Я думаю, що з ${var+x}цим не потрібно, за винятком випадків, коли імена змінних дозволяють мати пробіли в них.
Енн ван Россум

Не просто хороша практика; це потрібно з , [щоб отримати правильні результати. Якщо varвона не встановлена ​​або порожня, [ -n $var ]зменшується до [ -n ]статусу виходу 0, тоді [ -n "$var" ]як очікуваний (і правильний) статус виходу становить 1
chepner

5

https://stackoverflow.com/a/9824943/14731 містить кращу відповідь (той, який можна читати і працює з set -o nounsetувімкненим). Це працює приблизно так:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi

4

Явним способом перевірки визначеної змінної буде:

[ -v mystr ]

1
Неможливо знайти його на будь-якій із сторінок man (для bash або тесту), але це працює для мене. Будь-яка ідея, до яких версій це додано?
Еш Берлін-Тейлор

1
Це задокументовано в Умовних виразах , на які посилається testоператор у хвостовому кінці розділу Буллін Буллінс . Я не знаю, коли він був доданий, але його немає у версії Apple bash(на основі bash3.2.51), тому, ймовірно, є функцією 4.x.
Джонатан Леффлер

1
Це не працює для мене GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). Помилка є -bash: [: -v: unary operator expected. За коментарем тут , для роботи потрібно як мінімум баш 4.2.
Acumenus

Дякуємо за перевірку, коли вона стала доступною. Раніше я використовував його в різних системах, але потім раптом це не спрацювало. Я прийшов сюди з цікавості і так, звичайно, баш на цій системі - 3.x баш.
clacke

1

інший варіант: розширення "індекси списку масивів" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

єдиний раз, коли ця функція розширюється на порожню рядок, це коли її fooне встановлено, тому ви можете перевірити її за допомогою рядка умовно:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

має бути доступний у будь-якій версії bash> = 3.0


1

щоб не пролити цей велосипед ще далі, але хотів додати

shopt -s -o nounset

це те, що ви можете додати у верхній частині скрипту, що стане помилкою, якщо змінні не декларуються ніде в сценарії. Повідомлення, яке ви бачите, є unbound variable, але як згадують інші, воно не набере порожнього рядка або нульового значення. Щоб переконатися, що будь-яке окреме значення не порожнє, ми можемо протестувати змінну, оскільки вона розширена ${mystr:?}, також відомий як розширення знаку долара, що може помилитися з parameter null or not set.


1

Bash Reference Manual є авторитетним джерелом інформації про БАШЕЄВ.

Ось приклад тестування змінної, щоб побачити, чи існує вона:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

розділу 6.3.2 .)

Зауважте, що пробіл після відкритого [та перед цим не] є обов'язковим .


Поради для користувачів Vim

У мене був сценарій, який мав декілька декларацій наступним чином:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

Але я хотів, щоб вони відклали будь-які існуючі значення. Тому я переписав їх, щоб виглядати так:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

Мені вдалося автоматизувати це в програмі vim за допомогою швидкого регулярного вираження:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

Це можна застосувати, обравши відповідні рядки візуально, а потім набравши текст :. Командний рядок попередньо заповнюється с :'<,'>. Вставте вищевказану команду і натисніть клавішу Enter.


Тестовано на цій версії Vim:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Користувачі Windows можуть хотіти різних закінчень рядків.


0

Ось, на мій погляд, набагато зрозуміліший спосіб перевірити, чи визначена змінна:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Використовуйте його наступним чином:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi

1
Написання функції - не погана ідея; викликати grep- це жахливо. Зауважте, що bashпідтримка ${!var_name}як спосіб отримання значення змінної, ім'я якої вказано в $var_name, тому name=value; value=1; echo ${name} ${!name}дає результат value 1.
Джонатан Леффлер

0

Коротша версія для тестування невизначеної змінної може бути просто:

test -z ${mystr} && echo "mystr is not defined"

-3

виклик без будь-яких аргументів .. він виводить усі визначені значення ..
останніми у списку будуть ті, що визначені у вашому сценарії ..
так що ви можете передати його вихід на щось, що могло б зрозуміти, що визначено, а що ні


2
Я не проголосував це, але це щось із кувалди, щоб зламати досить маленький горіх.
Джонатан Леффлер

Мені справді подобається ця відповідь краще, ніж прийнята відповідь. Зрозуміло, що робиться в цій відповіді. Прийнята відповідь примхлива як пекло.
Швейцарія

Схоже, ця відповідь є скоріше побічним ефектом від реалізації «набору», ніж бажаного.
Джонатан Морган
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.