Проверьте, пуст ли массив в Bash

110

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

Мне нужен способ проверить, не пусто ли оно в конце скрипта, и выполнить определенное действие, если оно есть.

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

Маркос Сандер
источник

Ответы:

143

Предположим, ваш массив равен $errors, просто проверьте, равно ли количество элементов нулю.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi
Майкл Хэмптон
источник
10
Обратите внимание, что =это строковый оператор. В этом случае это работает нормально, но -eqвместо этого я бы использовал правильный арифметический оператор (на всякий случай, если бы я хотел переключиться на -geили -ltи т. Д.).
Musiphil
6
Не работает с set -u: "unbound variable" - если массив пуст.
Игорь
@Igor: у меня работает в Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty, Если я unset foo, то это печатает foo: unbound variable, но это отличается: переменная массива вообще не существует, а не существует и является пустой.
Питер Кордес
Также проверяется в Bash 3.2 (OSX) при использовании set -u- если вы сначала объявили свою переменную, это работает отлично.
0
15

Я обычно использую арифметическое расширение в этом случае:

if (( ${#a[@]} )); then
    echo not empty
fi
х-юри
источник
Красиво и чисто! Мне это нравится. Также отмечу, что если первый элемент массива всегда непустой, (( ${#a} ))(длина первого элемента) также будет работать. Однако, это не удастся a=(''), тогда как (( ${#a[@]} ))приведенный в ответе будет успешным.
CXW
8

Вы также можете рассматривать массив как простую переменную. Таким образом, просто используя

if [ -z "$array" ]; then
    echo "Array empty"
else
    echo "Array non empty"
fi

или используя другую сторону

if [ -n "$array" ]; then
    echo "Array non empty"
else
    echo "Array empty"
fi

Проблема с этим решением является то , что если массив объявлен как это: array=('' foo). Эти проверки будут сообщать массив как пустой, в то время как он явно нет. (спасибо @musiphil!)

Использование [ -z "$array[@]" ]явно не является решением. Не указывая фигурные скобки пытается интерпретировать $arrayкак строка ( [@]в этом случае простая текстовая строка) , и поэтому всегда считаются ложными: «это символьная строка [@]пустой?» Очевидно, нет.

Wget
источник
7
[ -z "$array" ]или [ -n "$array" ]не работает. Попробуйте array=('' foo); [ -z "$array" ] && echo empty, и он напечатает, emptyхотя arrayявно не пусто.
Musiphil
2
[[ -n "${array[*]}" ]]интерполирует весь массив как строку, которую вы проверяете на ненулевую длину. Если вы считаете array=("" "")пустым, а не два пустых элемента, это может быть полезно.
Питер Кордес
@PeterCordes Я не думаю, что это работает. Выражение оценивается одним пробелом и [[ -n " " ]]является «истиной», что очень жаль. Ваш комментарий - именно то, что я хочу сделать.
Майкл
@ Майкл: Дерьмо, ты прав. Он работает только с 1-элементным массивом пустой строки, а не с 2 элементами. Я даже проверил старый Bash, и он все еще там не так; как вы говорите, set -xпоказывает, как он расширяется. Я думаю, что я не проверял этот комментарий перед публикацией. >. <Вы можете заставить его работать, установив IFS=''(сохраните / восстановите его вокруг этого оператора), потому что "${array[*]}"расширение разделяет элементы с первым символом IFS. (Или пробел, если не установлен). Но « Если IFS имеет значение null, параметры объединяются без промежуточных разделителей. » (Документы для позиционных параметров $ *, но я предполагаю, что то же самое для массивов).
Питер Кордес
@Michael: добавлен ответ, который делает это.
Питер Кордес
3

Я проверил это с bash-4.4.0:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]} ]]; then
        echo not empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

и bash-4.1.5:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]:+${array[@]}} ]]; then
        echo non-empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

В последнем случае вам понадобится следующая конструкция:

${array[@]:+${array[@]}}

для того, чтобы он не потерпел неудачу на пустом или неустановленном массиве. Это если ты делаешь так, set -euкак я обычно делаю. Это обеспечивает более строгую проверку ошибок. Из документов :

-e

Немедленно завершите работу, если конвейер (см. Конвейеры), который может состоять из одной простой команды (см. Простые команды), списка (см. Списки) или составной команды (см. Составные команды), возвращает ненулевое состояние. Оболочка не завершает работу, если сбойная команда является частью списка команд, следующих сразу за ключевым словом некоторое время или до, часть теста в операторе if, часть любой команды, выполняемой в && или || список, за исключением команды, следующей за последним символом && или ||, любой команды в конвейере, кроме последней, или если статус возврата команды инвертируется с!. Если составная команда, отличная от подоболочки, возвращает ненулевое состояние из-за сбоя команды, когда -e игнорировалась, оболочка не завершается. Ловушка ERR, если установлена, выполняется до выхода из оболочки.

Этот параметр применяется к среде оболочки и к каждой среде подоболочки отдельно (см. Среду выполнения команд) и может привести к выходу подоболочек перед выполнением всех команд в подоболочке.

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

-u

Обрабатывайте неустановленные переменные и параметры, кроме специальных параметров '@' или '*', как ошибку при выполнении расширения параметра. Сообщение об ошибке будет записано в стандартную ошибку, и неинтерактивная оболочка завершится.

Если вам это не нужно, не стесняйтесь пропустить :+${array[@]}часть.

Также обратите внимание, что здесь важно использовать [[оператор, так как [вы получаете:

$ cat 1.sh
#!/usr/bin/env bash
set -eu
array=(a b c d)
if [ "${array[@]}" ]; then
    echo non-empty
else
    echo empty
fi

$ ./1.sh
_/1.sh: line 4: [: too many arguments
empty
х-юри
источник
С -uвами на самом деле стоит использовать ${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Якуб Боченски
@JakubBochenski О какой версии bash вы говорите? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-
Проблема в примере с одиночными скобками @, конечно же. Вы могли бы использовать *расширение массива, как [ "${array[*]}" ], не так ли? Тем не менее, [[также отлично работает. Поведение обоих из них для массива с несколькими пустыми строками немного удивительно. Оба [ ${#array[*]} ]и [[ "${array[@]}" ]]ложны для array=()и array=('')но верны для array=('' '')(две или более пустых строки). Если вы хотите, чтобы одна или несколько пустых строк дали всем true, вы можете использовать [ ${#array[@]} -gt 0 ]. Если бы вы хотели, чтобы все они были ложными, вы могли бы //их исключить.
eisd
@eisd Я мог бы использовать [ "${array[*]}" ], но если бы я столкнулся с таким выражением, мне было бы труднее понять, что оно делает. Так как [...]действует в терминах строк по результату интерполяции. В отличие от того [[...]], который может быть в курсе того, что было интерполировано. То есть он может знать, что ему был передан массив. [[ ${array[@]} ]]читается как «проверить, не является ли массив непустым», а [ "${array[*]}" ]как «проверить, является ли результат интерполяции всех элементов массива непустой строкой».
x-yuri
... Что касается поведения с двумя пустыми строками, меня это совсем не удивляет. Что удивительно, так это поведение с одной пустой строкой. Но возможно разумно. Что касается [ ${#array[*]} ], вы, вероятно, имели в виду [ "${array[*]}" ], так как первое верно для любого количества элементов. Потому что количество элементов всегда непустая строка. Что касается последнего с двумя элементами, то выражение в скобках расширяется до ' 'непустой строки. Что касается [[ ${array[@]} ]], они просто думают (и это правильно), что любой массив из двух элементов не является пустым.
x-yuri
2

Если вы хотите обнаружить массив с пустыми элементами ,arr=("" "") как пустой, так же, какarr=()

Вы можете вставить все элементы вместе и проверить, будет ли результат нулевой длины. (Построение плоской копии содержимого массива не идеально для производительности, если массив может быть очень большим. Но, надеюсь, вы не используете bash для таких программ ...)

Но "${arr[*]}"расширяется элементами, разделенными первым символом IFS. Поэтому вам нужно сохранить / восстановить IFS и выполнить IFS=''эту операцию, или же проверить, что длина строки == # элементов массива - 1. (Массив nэлементов имеет n-1разделители). Чтобы справиться с этим один за другим, проще всего дополнить конкатенацию 1

arr=("" "")

## Assuming default non-empty IFS
## TODO: also check for ${#arr[@]} -eq 0
concat="${arr[*]} "      # n-1 separators + 1 space + array elements
[[ "${#concat}" -ne "${#arr[@]}" ]]  && echo not empty array || echo empty array

контрольный пример с set -x

### a non-empty element
$ arr=("" "x")
  + arr=("" "x")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat=' x '
  + [[ 3 -ne 2 ]]
  + echo not empty array
not empty array

### 2 empty elements
$ arr=("" "")
  + arr=("" "")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat='  '
  + [[ 2 -ne 2 ]]
  + echo empty array
empty array

К сожалению , это не выполняется для arr=(): [[ 1 -ne 0 ]]. Таким образом, вам нужно сначала проверить наличие фактически пустых массивов.


Или сIFS='' . Возможно, вы захотите сохранить / восстановить IFS вместо использования подоболочки, потому что вы не можете легко получить результат из подоболочки.

# inside a () subshell so we don't modify our own IFS
(IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)

пример:

$ arr=("" "")
$ (IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)
   + IFS=
   + [[ -n '' ]]
   + echo empty array
empty array

делает работу с arr=()- это все-таки просто пустая строка.

Питер Кордес
источник
Я проголосовал, но я начал использовать [[ "${arr[*]}" = *[![:space:]]* ]], так как могу рассчитывать как минимум на одного не-WS персонажа.
Майкл
@ Майкл: да, это хороший вариант, если вам не нужно отказываться arr=(" ").
Питер Кордес
0

В моем случае второго ответа было недостаточно, потому что могли быть пробелы. Я пришел вместе с:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi
Миха
источник
echo | wcкажется излишне неэффективным по сравнению с использованием встроенных в оболочку.
Питер Кордес
Не уверен, что понимаю @PeterCordes, могу ли я изменить вторые ответы, [ ${#errors[@]} -eq 0 ];чтобы обойти проблему с пробелами? Я также предпочел бы встроенный.
Мика,
Как именно пробелы вызывают проблемы? $#расширяется до числа, и прекрасно работает даже после opts+=(""). например unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"и я получаю 2. Можете ли вы показать пример чего-то, что не работает?
Питер Кордес
Это давно. IIRC исходный источник всегда печатается как минимум "". Таким образом, для opts = "" или opts = ("") мне нужно 0, а не 1, игнорируя пустую строку или пустую строку.
Миха
Итак, вы должны относиться так opts=("")же, как opts=()? Это не пустой массив, но вы можете проверить наличие пустого массива или пустого первого элемента с помощью opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Обратите внимание, что в вашем текущем ответе написано «без вариантов» opts=("" "-foo"), что полностью обманчиво, и это воспроизводит это поведение. Можно было бы [[ -z "${opts[*]}" ]]догадаться, чтобы интерполировать все элементы массива в плоскую строку, которая -zпроверяет ненулевую длину. Если проверки первого элемента достаточно, -z "$opts"работает.
Питер Кордес