Как определить, не определена ли строка в сценарии оболочки Bash

151

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

[ -z $mystr ]

но что, если я хочу проверить, была ли переменная определена вообще? Или нет никакого различия в скриптах Bash?

Setjmp
источник
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}, и так далее.


Как указывает в своем ответе Гили , если вы запускаете с опцией, то основной ответ выше не с . Это легко исправить:bashset -o nounsetunbound 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, поищите раздел «Расширение параметров», а затем следующий текст: «Если не выполняется раскрытие подстроки, используя формы, описанные ниже, тесты bash для параметр, который не установлен или имеет значение null ['null', означающее пустую строку]. Пропуск двоеточия приводит к проверке только для параметра, который не установлен (...) $ {параметр: + слово}: использовать альтернативное значение. Если параметр имеет значение null или unset, ничего не подставляется, в противном случае подставляется расширение слова. "
Дэвид Тонхофер
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
Но я хочу различать, является ли строка "" или не была определена когда-либо. Это возможно?
Setjmp
1
этот ответ действительно сказать , как различать эти два случая; перейдите по ссылке bash faq для дальнейшего обсуждения.
Чарльз Даффи
1
Я добавил, что после его комментария Чарльз
Винко Врсалович
2
Посмотрите "Расширение параметров" на странице руководства bash для всех этих "уловок". Например, $ {foo: -default} для использования значения по умолчанию, $ {foo: = default} для назначения значения по умолчанию, $ {foo:? Message error} для отображения сообщения об ошибке, если foo не установлен и т. Д.
Jouni K Сеппанен
18

Расширенное руководство по написанию сценариев, 10.2. Подстановка параметров:

  • $ {var + blahblah}: если определено var, вместо выражения подставляется «blahblah», иначе подставляется нуль
  • $ {var-blahblah}: если переменная определена, она сама подставляется, иначе подставляется «blahblah»
  • $ {var? blahblah}: если переменная определена, она подставляется, иначе функция существует с '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которая делает то же самое намного проще.
ceving
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
источник
Хорошая практика в Bash. Иногда пути к файлам имеют пробелы или для защиты от внедрения команд
k107
1
Я думаю, ${var+x}что в этом нет необходимости, за исключением случаев, когда имена переменных могут содержать пробелы.
Энн ван Россум
Не просто хорошая практика; это требуется с , [чтобы получить правильные результаты. Если varне установлено или пусто, [ -n $var ]сводится к тому, [ -n ]что имеет выходной статус 0, а [ -n "$var" ]ожидаемый (и правильный) выходной статус
равен
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 или test), но это работает для меня .. Есть идеи, какие версии это было добавлено?
Эш Берлин-Тейлор
1
Он задокументирован в условных выражениях , на которые ссылается testоператор в хвостовой части раздела Bourne Shell Builtins . Я не знаю, когда он был добавлен, но его нет в версии 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
Спасибо за проверку, когда она стала доступной. Я использовал его раньше в разных системах, но потом вдруг это не сработало. Я пришел сюда из любопытства и да, конечно, bash в этой системе - 3.x bash.
клик
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.

Sacrilicious
источник
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 могут захотеть разные окончания строк.

funroll
источник
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"
nfleury
источник
-3

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

Адитья Мукерджи
источник
2
Я не голосовал против этого, но это что-то вроде кувалды, чтобы сломать довольно маленький орех.
Джонатан Леффлер
Мне действительно нравится этот ответ лучше, чем принятый ответ. Понятно, что делается в этом ответе. Принятый ответ ужасен как ад.
Швейцарии
Кажется, что этот ответ является скорее побочным эффектом реализации «множества», чем желательным.
Джонатан Морган