Вопрос или проблема
В bash условное выражение с унарным тестом -v myvariable
проверяет, установлена ли переменная myvariable
. Обратите внимание, что myvariable
не должна расширяться с помощью префикса доллара, так что не $myvariable
. Теперь я обнаружил, что для элементов массива условное выражение -v myarray[index]
тоже хорошо работает, без полной синтаксической конструкции ${myarray[$index]}
. Попробуйте следующее:
myarray[2]=myvalue
for i in 1 2 3
do
[ -v myarray\[i] ] && echo element $i is set
done
(обратите внимание на \[ для предотвращения глоббинга, в качестве альтернативы кавычкам)
дает желаемый вывод:
element 2 is set
Вопрос
Безопасно ли это поведение, также известно как, документировано ли это поведение?
Дополнение
После чтения ответа https://unix.stackexchange.com/a/677920/376817 от Stéphane Chazelas, я расширил свой пример:
myarray[1]=val myarray[2]=val myarray[3]=val myarray[4]=val myarray[5]=val myarray[6]="" myarray[2]=""
unset myarray[3] myarray[4] myarray[5]
touch myarray4 myarrayi
myarray4=val myarrayi=val
тогда
for i in {0..7}; do [ -v myarray\[i] ] && echo element $i is set; done
дает
element 1 is set
element 2 is set
element 6 is set
Без кавычек или экранирования выражения индекса [i]
:
for i in {0..7}; do [ -v myarray[i] ] && echo element $i is set; done
дает
element 0 is set
element 1 is set
element 2 is set
element 3 is set
element 4 is set
element 5 is set
element 6 is set
element 7 is set
То же самое с переменной myarrayi
без установленных значений:
unset myarrayi
for i in {0..7}; do [ -v myarray[i] ] && echo element $i is set; done
дает
%nothing%
И наконец с расширением индекса как $i
(все еще без кавычек или экранирования скобы):
for i in {0..7}; do [ -v myarray[$i] ] && echo element $i is set; done
дает
element 1 is set
element 2 is set
element 4 is set
потому что
ls -l myarray*
показывает
-rw-rw-r-- 1 me us 0 nov 17 15:37 myarray4
-rw-rw-r-- 1 me us 0 nov 17 15:37 myarrayi
В bash
можно сделать:
[ -v 'a[2]' ]
или
[[ -v a[2] ]]
Чтобы проверить, установлен ли элемент массива с индексом 2, или элемент ассоциативного массива с ключом “2” (даже если это пустая строка), но обратите внимание:
-
Когда используется команда
[
(также известная какtest
), нужно экранировать символы[
и]
, так как они являются операторами глоббинга. Так как[
является обычной командой, она обрабатывается как любая другая обычная команда, поэтомуa[2]
в[ -v a[2] ]
будет расширено так же, как и вls -d a[2]
илиunset a[2]
. Если в текущем рабочем каталоге есть файл с именемa2
,a[2]
будет расширено до этого. А если нет, ноnullglob
илиfailglob
включены,a[2]
будет расширено до ничего или вызовет ошибку соответственно.[[ ... ]]
— это специальная конструкция с собственным синтаксисом, поэтому проблемы не возникает. -
Для ассоциативных массивов (по крайней мере в версии 5.1, на которой я это тестировал), если ключ для проверки находится в переменной
$i
, нужно использовать[ -v 'a[$i]' ]
или[[ -v 'a[$i]' ]]
и опцияassoc_expand_once
, введенная в новых версияхbash
, не должна быть включена. Использованиеили[ -v "a[$i]" ]
не будет работать для некоторых значений[[ -v a[$i] ]]
$i
, содержащих]
или обратные косые черты. Здесь необходимо экранировать$
. См. также Как безопасно использовать ассоциативные массивы внутри арифметических выражений?. -
Все еще для ассоциативных массивов, отметьте, что
bash
(в отличие отksh93
, которыйbash
попытался скопировать, илиzsh
) не поддерживает пустые ключи. Если вы используете[[ -v 'a[$i]' ]]
и$i
является пустой строкой, вы получите ошибку. Поэтому для проверки произвольных значений ключей используйте[[ -n $i && -v 'a[$i]' ]]
или[ -n "$i" ] && [ -v 'a[$i]' ]
. -
Для обычных (разреженных) массивов в
[ -v 'a[expr]' ]
или[[ -v a[expr] ]]
expr
оценивается как арифметическое выражение. Именно поэтому работаютi
и$i
. Поскольку арифметическое выражение может иметь побочные эффекты в виде присвоения переменных или выполнения произвольных команд, важно, чтобы значение$i
, использованное в[ -v 'a[i]' ]
, было проверено или в противном случае у вас будет уязвимость произвольного исполнения команды. Как видно из Импликации безопасности забывания экранирования переменной в bash/POSIX оболочках, то, чтоbash
‘s[ -v lvalue ]
работает для индексов массива, делает такие вещи, как[ -f $file ]
(где автор забыл экранировать$file
) уязвимостью ACE. -
В любом случае, всегда можно использовать метод Bourne/POSIX (
[ -n "${var+set}" ]
) примененный к элементу (ассоциативного) массива:[ -n "${a[$key]+set}" ]
, который не требует никаких обходных решений и работает со всеми версиями bash с поддержкой массивов (2.0 (1996) или выше) и поддержкой ассоциативных массивов (4.0 (2009) или выше) и переносим в оболочках с этой поддержкой. -
Обратите внимание, что
[ -v var ]
фактически то же самое, что и[ -v 'var[0]' ]
. Как в ksh88, скалярные переменные могут рассматриваться как элемент индекса 0 массива. -
Чтобы проверить, имеется ли в массиве или ассоциативном массиве какой-либо элемент (или установлена ли скалярная переменная), также можно выполнить
[ "${#a[@]}" -gt 0 ]
или[ -v 'a[@]' ]
или[ -v 'a[*]' ]
. Изменение 2023 года, как отметил @johnraff, последние два больше не работают для ассоциативных массивов с версии 5.2, если только не включить совместимость с версией 5.1. -
Чтобы перебрать индексы разреженных массивов или ключи ассоциативных массивов, можно сделать:
for key in "${!a[@]}"; do printf 'The element of key "%s" is set\n' "$key" done
(это дает
0
для скалярной переменной).
Что касается документации: если выполнить команду info bash test
или info bash '['
, вы увидите, что она отсылает к Условные выражения Bash (как используется внутри [[ ... ]]
), где в документации -v
указано:
-v VARNAME
Правда, если переменная оболочки VARNAME установлена (ей было присвоено значение).
В то время как help test
имеет:
-v VAR Истина, если переменная оболочки VAR установлена.
Что может быть VARNAME
(в частности, разрешены ли элементы массива) или что происходит, если VARNAME
ссылается на переменную, которая не является скалярной переменной, не ясно уточнено. Но, учитывая, что a[x]
в целом допустимо, где бы ни ожидалось имя переменной, и что это было так в течение десятилетий, мы можем, вероятно, безопасно предположить, что это останется так и в будущем.
Если посмотреть на другие разделы этой официальной документации, можно увидеть, что обычно подразумевается, что где бы ни ожидалось имя переменной (независимо от того, обозначается ли оно как NAME, VAR, VARNAME или более общо PARAMETER), также допустимо varname[index]
. Например, в документации unset
(info bash unset
), удаление 'array[i]'
не упомянуто, но оно упомянуто в разделе о массивах.
Файл NEWS
в источнике сообщенных данных (записки о выпуске) говорит нам, что test -v
был добавлен в bash-4.2 (2011 года), вероятно, вдохновленный ksh93, который добавил это ненадолго до этого в ksh93t+ (2009 года, с поддержкой элементов массива, упомянутой в его собственных записках о выпуске)
f.
test
/[
/[[
имеют новый унарный оператор переменной-v
, который возвращает успех, если `переменная` была установлена.
И в 4.3:
ll. Оператор
test
/[
/[[
-v variable
теперь понимает ссылки на массивы.
В 5.1:
x.
test -v N
теперь может проверить, установлен ли позиционный параметр N.
Вы обнаружите, что CWRU/changelog
в исходном дистрибутиве имеет некоторые упоминания о test -v array[@]
или [[ -v array[$key] ]]
для ассоциативных массивов, снова намекая на то, что функция является намеренной.
Нельзя исключать возможность, что в будущем что-то может быть сделано для исправления некоторых из упомянутых проблем, что может сделать недействительными обходные решения, которые я упоминаю, такие как необходимость в экранировании $
.
С bash 5.2 больше невозможно проверять, содержит ли ассоциативный массив какие-либо элементы с test -v 'arr[@]'
, который теперь тестирует на специально названный ключ ‘@’.
Я предполагаю, что test "${#arr[@]}" -gt 0
или (( ${#k[@]} > 0 ))
подойдет вместо этого.
См. compat51: https://www.gnu.org/software/bash/manual/bash.html#Shell-Compatibility-Mode
У меня эта проблема возникает в Bash 4.2, где тест -v
не работает с элементами массива, будь то индексированные или ассоциативные.
Это нормальный/объяснимый случай?
GNU bash, версия 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
( ... )
user@host1:~$ x[0]=1
user@host1:~$ echo ${x[0]}
1
user@host1:~$ [ ${x[0]} -eq 1 ] && echo Y
Y
user@host1:~$ [ -v x\[0] ] && echo Y # Почему это неверно
user@host1:~$ [[ ${x[0]} -eq 1 ]] && echo Y
Y
user@host1:~$ [[ -v x[0] ]] && echo Y # Почему это неверно
Конечно, все это работает нормально в Bash 4.3 –
user@host2:~$ bash --version
GNU bash, версия 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
( ... )
user@host2:~$ x[0]=1
user@host2:~$ echo ${x[0]}
1
user@host2:~$ [ ${x[0]} -eq 1 ] && echo Y
Y
user@host2:~$ [ -v x\[0] ] && echo Y
Y
user@host2:~$ [[ ${x[0]} -eq 1 ]] && echo Y
Y
user@host2:~$ [[ -v x[0] ]] && echo Y
Y
Ответ или решение
Вопрос, касающийся использования унарного теста -v
в Bash для проверки элемента массива, поднимает важную тему об области применения и надежности этой функциональности. Давайте разберем поведение этого оператора, его документированность и возможности применения в различных версиях Bash.
Теория
Унарный тест -v
в Bash используется для проверки того, задана ли переменная, то есть была ли ей присвоена какая-либо ценность. Это нововведение появилось в версии Bash 4.2 и было расширено в дальнейшем. Конструкция -v
, применимая к элементам массивов, позволяет определить, был ли задан элемент массива, даже если он пустой.
Когда речь идет о индексированных и ассоциативных массивах, возможность проверки с помощью -v
становится весьма полезной. Например, выражение -v array[index]
позволяет проверить, существует ли элемент в массиве под заданным индексом.
Примеры
Рассмотрим следующий пример в формате Bash-скрипта:
myarray[2]=myvalue
for i in 1 2 3
do
[ -v myarray\[i] ] && echo "Элемент $i установлен"
done
Этот код проверяет установку элементов в массиве myarray
. В данной конструкции нас интересует возможность проверки индексов массива с помощью -v
, что значительно упрощает работу с массивами в сценариях, где необходимо выяснить, существуют ли определенные элементы.
Применение
Необходимо учитывать несколько особенностей и возможных подводных камней:
-
Глобинг: Когда используется команда
[
(также известная какtest
), визуальные символы[
и]
должны быть экранированы или обернуты в кавычки, чтобы избежать их интерпретации как символов глобинга. -
Цитирование при работе с ассоциативными массивами: При работе с ассоциативными массивами важно помнить, что ключи, содержащие символы
]
или обратные слэши, могут вызывать нежелательное поведение, если они не заключены в кавычки. -
Совместимость: Несмотря на то, что унарный оператор
-v
появился в Bash 4.2, полный функционал, включая работу с массивами, стал доступен начиная с Bash 4.3. Это означает, что для более ранних версий Bash некоторая функциональность может быть недоступна. -
Аналогичные методы: В дополнение к
-v
, можно использовать более традиционный подход:[ -n "${var+set}" ]
, который совместим почти со всеми версиями Bash. -
Просмотр документации: Официальная документация может не отражать всех возможных применений оператора
-v
, поэтому понимание его поведения в разных сценариях требует тестирования и практического опыта.
Заключение
Использование унарного теста -v
для проверки элементов массива в Bash — это удобная и мощная возможность, особенно в более поздних версиях Bash. Однако, при работе с ней важно учитывать не только версию интерпретатора, но и потенциальные аспекты безопасности, связанные с интерпретацией индексов массива. Важно всегда тестировать подобные конструкции в целевом окружении и следовать современным практикам безопасного программирования.
Оператор -v
расширяет возможности управления переменными и массивами в Bash, делая скрипты более читаемыми и эффективными. Однако, чтобы использовать его в полной мере, необходим обдуманный подход, включающий в себя знание как синтаксических особенностей, так и потенциальных уязвимостей.