Перевірте, чи масив порожній у 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 змінною" - якщо масив порожній.
Ігор

@Igor: Для мене працює в Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Якщо я unset foo, то він друкує foo: unbound variable, але це інакше: змінна масиву взагалі не існує, а існує та є порожньою.
Пітер Кордес

Також перевірено на Bash 3.2 (OSX) при використанні set -u- доки ви спочатку оголосили свою змінну, це працює чудово.
zeroimpl

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як рядок ( [@]у такому випадку це проста літеральна рядок) і тому завжди повідомляється як помилковий: "чи буквальна рядок [@]порожня?" Ясно, що ні.


7
[ -z "$array" ]або [ -n "$array" ]не працює. Спробуйте array=('' foo); [ -z "$array" ] && echo empty, і він надрукується, emptyхоча arrayявно не порожній.
musiphil

2
[[ -n "${array[*]}" ]]інтерполює весь масив у вигляді рядка, який ви перевіряєте на нульову довжину. Якщо ви вважаєте array=("" "")порожнім, а не двома порожніми елементами, це може бути корисним.
Пітер Кордес

@PeterCordes Я не думаю, що це працює. Вираз оцінює один простір символу і [[ -n " " ]]є "правдою", що шкода. Ваш коментар - це саме те, що я хочу зробити.
Майкл

@Michael: Лайно, ти маєш рацію. Він працює лише з 1-елементним масивом порожнього рядка, а не з 2 елементами. Я навіть перевірив старший баш, і все ще там неправильно; як ви говорите, set -xпоказує, як вона розширюється. Напевно, я не перевіряв цей коментар перед публікацією. >. <Ви можете змусити його працювати, встановивши IFS=''(збережіть / відновіть його навколо цього оператора), оскільки "${array[*]}"розширення розділяє елементи з першим символом IFS. (Або пробіл, якщо його не встановлено). Але " Якщо IFS недійсний, параметри з'єднуються без втручання роздільників. " (Документи для $ * позиційних парам. Але я вважаю те саме для масивів).
Пітер Кордес

@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як я зазвичай роблю. Це передбачає більш чітку перевірку помилок. З документів :

Вийдіть негайно, якщо трубопровід (див. Трубопроводи), який може складатися з однієї простої команди (див. Прості команди), списку (див. Списки) або складної команди (див. Складені команди) повертає ненульовий статус. Оболонка не виходить, якщо команда, яка не вдається, є частиною списку команд відразу після деякого часу або до появи ключового слова, частина тесту в операторі if, частина будь-якої команди, виконаної в && або || список, за винятком команди після остаточного && або ||, будь-якої команди в конвеєрі, але останньої, або якщо стан повернення команди інвертується !. Якщо складна команда, яка не є допоміжною оболонкою, повертає ненульовий статус, оскільки команда не вдалася, коли -e ігнорувався, оболонка не виходить. Папка на ERR, якщо вона встановлена, виконується до виходу оболонки.

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

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

Розгляньте невстановлені змінні та параметри, відмінні від спеціальних параметрів "@" або "*", як помилку при виконанні розширення параметра. Повідомлення про помилку буде записано на стандартну помилку, і неінтерактивна оболонка вийде.

Якщо вам це не потрібно, сміливо пропустіть :+${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-yuri

Проблема в прикладі одинарних дужок - це @, безумовно. Ви можете використовувати *розширення масиву як [ "${array[*]}" ], чи не так? Все-таки [[теж чудово працює. Поведінка обох для масиву з кількома порожніми рядками трохи дивує. Обидва [ ${#array[*]} ]і [[ "${array[@]}" ]]помилкові для array=()і , array=('')але вірно і для array=('' '')(два або більше порожніх рядків). Якщо ви хотіли, щоб одна чи кілька порожніх рядків всім істинним, ви можете використовувати [ ${#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-символ.
Майкл

@Michael: так, це хороший варіант, якщо вам не потрібно відкидати 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 ];способу, щоб вирішити проблему пробілу? Я також вважаю за краще вбудований.
Micha

Як саме пробіл викликає проблему? $#розширюється до числа і працює чудово навіть після opts+=(""). наприклад, unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"і я отримую 2. Чи можете ви показати приклад того, що не працює?
Пітер Кордес

Це вже давно. IIRC вихідне джерело завжди надруковано принаймні "". Таким чином, для opts = "" або opts = ("") мені знадобилось 0, а не 1, ігноруючи порожній новий рядок або порожній рядок.
Micha

Гаразд, значить, потрібно ставитися так opts=("")само, як opts=()? Це не порожній масив, але ви можете перевірити наявність порожнього масиву або порожній перший елемент за допомогою opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Зауважте, що у вашій нинішній відповіді сказано, що "немає варіантів" opts=("" "-foo"), що є абсолютно хибним, і це відтворює таку поведінку. Можна було б [[ -z "${opts[*]}" ]]припустити, щоб інтерполювати всі елементи масиву в рівний рядок, який -zперевіряє наявність ненульової довжини. Якщо перевірка першого елемента достатня, -z "$opts"працює.
Пітер Кордес

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