Compruebe si la matriz está vacía en Bash


110

Tengo una matriz que se llena con diferentes mensajes de error a medida que se ejecuta mi script.

Necesito una forma de verificar si está vacío o no al final del script y tomar una acción específica si es así.

Ya he intentado tratarlo como un VAR normal y usar -z para verificarlo, pero eso no parece funcionar. ¿Hay alguna manera de verificar si una matriz está vacía o no en Bash?

Respuestas:


143

Supongamos que su matriz es $errors, solo verifique si el recuento de elementos es cero.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi

10
Tenga en cuenta que =es un operador de cadena. En este caso, funciona bien, pero en su lugar usaría el operador aritmético adecuado -eq(en caso de que quisiera cambiar a -geo -lt, etc.).
musiphil

66
No funciona con set -u: "variable independiente" - si la matriz está vacía.
Igor

@ Igor: Funciona para mí en Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Si lo hago unset foo, se imprime foo: unbound variable, pero eso es diferente: la variable de matriz no existe en absoluto, en lugar de existir y estar vacía.
Peter Cordes

También se probó en Bash 3.2 (OSX) cuando se usa set -u, siempre que haya declarado su variable primero, esto funciona perfectamente.
zeroimpl

15

Generalmente uso la expansión aritmética en este caso:

if (( ${#a[@]} )); then
    echo not empty
fi

¡Bonito y limpio! Me gusta. También noto que si el primer elemento de la matriz siempre no está vacío, (( ${#a} ))(la longitud del primer elemento) también funcionará. Sin embargo, eso fallará a=(''), mientras que (( ${#a[@]} ))dado en la respuesta tendrá éxito.
cxw

8

También puede considerar la matriz como una variable simple. De esa manera, solo usando

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

o usando el otro lado

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

El problema con esta solución es que si se declara una matriz de esta manera: array=('' foo). Estas comprobaciones informarán que la matriz está vacía, mientras que claramente no lo está. (¡Gracias @musiphil!)

Usando [ -z "$array[@]" ]claramente no es una solución ni. Al no especificar llaves se intenta interpretar $arraycomo una cadena ( [@]en ese caso, es una cadena literal simple) y, por lo tanto, siempre se informa como falsa: "¿está [@]vacía la cadena literal ?" Claramente no.


77
[ -z "$array" ]o [ -n "$array" ]no funciona Intente array=('' foo); [ -z "$array" ] && echo empty, e imprimirá emptyaunque arrayclaramente no esté vacío.
musiphil

2
[[ -n "${array[*]}" ]]interpola toda la matriz como una cadena, que verifica para una longitud distinta de cero. Si considera array=("" "")que está vacío, en lugar de tener dos elementos vacíos, esto podría ser útil.
Peter Cordes

@ PeterCordes No creo que eso funcione. La expresión se evalúa como un solo carácter de espacio y [[ -n " " ]]es "verdadero", lo cual es una pena. Tu comentario es exactamente lo que quiero hacer.
Michael

@ Michael: Mierda, tienes razón. Solo funciona con una matriz de 1 elemento de una cadena vacía, no con 2 elementos. Incluso revisé bash más viejo y todavía está mal allí; como dices set -xmuestra cómo se expande. Supongo que no probé ese comentario antes de publicar. >. <Puede hacer que funcione estableciendo IFS=''(guardar / restaurar alrededor de esta instrucción), porque la "${array[*]}"expansión separa los elementos con el primer carácter de IFS. (O espacio si no está configurado). Pero " Si IFS es nulo, los parámetros se unen sin separadores intermedios " (documentos para $ * parámetros posicionales, pero supongo lo mismo para las matrices).
Peter Cordes

@Michael: se agregó una respuesta que hace esto.
Peter Cordes

3

Lo comprobé con 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

y 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

En el último caso, necesita la siguiente construcción:

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

para que no falle en una matriz vacía o desarmada. Eso si haces lo set -euque yo suelo hacer. Esto proporciona una comprobación de errores más estricta. De los documentos :

-mi

Salga de inmediato si una tubería (vea Tuberías), que puede consistir en un solo comando simple (vea Comandos simples), una lista (vea Listas) o un comando compuesto (vea Comandos compuestos) devuelve un estado distinto de cero. El shell no sale si el comando que falla es parte de la lista de comandos inmediatamente después de un tiempo o hasta la palabra clave, parte de la prueba en una declaración if, parte de cualquier comando ejecutado en un && o || lista excepto el comando que sigue al && o || final, cualquier comando en una tubería pero el último, o si el estado de retorno del comando se invierte con!. Si un comando compuesto que no sea un subshell devuelve un estado distinto de cero porque un comando falló mientras se ignoraba -e, el shell no se cierra. Una trampa en ERR, si se establece, se ejecuta antes de que salga el shell.

Esta opción se aplica al entorno de shell y a cada entorno de subshell por separado (consulte Entorno de ejecución de comandos) y puede hacer que los subshells salgan antes de ejecutar todos los comandos en el subshell.

Si un comando compuesto o una función de shell se ejecuta en un contexto donde se ignora -e, ninguno de los comandos ejecutados dentro del comando compuesto o el cuerpo de la función se verá afectado por la configuración -e, incluso si se establece -e y un comando devuelve un estado de falla Si un comando compuesto o una función de shell establece -e mientras se ejecuta en un contexto donde se ignora -e, esa configuración no tendrá ningún efecto hasta que se complete el comando compuesto o el comando que contiene la llamada a la función.

-u

Trate las variables y parámetros no establecidos que no sean los parámetros especiales '@' o '*' como un error al realizar la expansión de parámetros. Se escribirá un mensaje de error en el error estándar y se cerrará un shell no interactivo.

Si no necesita eso, no dude en omitir :+${array[@]}parte.

También tenga en cuenta que es esencial usar el [[operador aquí, con lo [que obtiene:

$ 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

Con -uusted debería usar ${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski el

@JakubBochenski ¿De qué versión de bash estás hablando? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri

El problema en el ejemplo de corchetes individuales es @, seguramente. Podrías usar la *expansión de matriz como [ "${array[*]}" ], ¿no? Aún así, [[también funciona bien. El comportamiento de ambos para una matriz con múltiples cadenas vacías es un poco sorprendente. Ambos [ ${#array[*]} ]y [[ "${array[@]}" ]]son falsos para array=()y array=('')pero verdaderos para array=('' '')(dos o más cadenas vacías). Si quisieras que una o más cadenas vacías fueran verdaderas, puedes usarlas [ ${#array[@]} -gt 0 ]. Si quisieras que todos fueran falsos, podrías //eliminarlos.
Eisd

@eisd podría usarlo [ "${array[*]}" ], pero si tuviera esa expresión, me sería más difícil entender lo que hace. Dado que [...]opera en términos de cadenas en el resultado de la interpolación. A diferencia de [[...]], que puede ser consciente de lo que se interpola. Es decir, puede saber que se le pasó una matriz. [[ ${array[@]} ]]me dice como "verificar si la matriz no está vacía", mientras que [ "${array[*]}" ]como "verificar si el resultado de la interpolación de todos los elementos de la matriz es una cadena no vacía".
x-yuri

... En cuanto al comportamiento con dos cadenas vacías, no me sorprende en absoluto. Lo sorprendente es el comportamiento con una cadena vacía. Pero posiblemente razonable. En cuanto a [ ${#array[*]} ], probablemente quiso decir [ "${array[*]}" ], ya que lo primero es cierto para cualquier número de elementos. Porque el número de elementos siempre es una cadena no vacía. Con respecto a este último con dos elementos, la expresión entre paréntesis se expande a la ' 'cual es una cadena no vacía. En cuanto a [[ ${array[@]} ]], simplemente piensan (y con razón) que cualquier conjunto de dos elementos no está vacío.
x-yuri

2

Si desea detectar una matriz con elementos vacíos ,arr=("" "") como vacío, igual quearr=()

Puede pegar todos los elementos juntos y verificar si el resultado es de longitud cero. (Construir una copia aplanada del contenido de la matriz no es ideal para el rendimiento si la matriz puede ser muy grande. Pero espero que no estés usando bash para programas como ese ...)

Pero se "${arr[*]}"expande con elementos separados por el primer carácter de IFS. Por lo tanto, debe guardar / restaurar IFS y hacer IFS=''que esto funcione, o bien comprobar que la longitud de la cadena == # de elementos de la matriz - 1. (Una matriz de nelementos tiene n-1separadores). Para lidiar con eso fuera de uno, es más fácil rellenar la concatenación en 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

caso de prueba con 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

Por desgracia, esta falla por arr=(): [[ 1 -ne 0 ]]. Por lo tanto, primero deberá verificar las matrices realmente vacías por separado.


O conIFS='' . Probablemente quiera guardar / restaurar IFS en lugar de usar una subshell, porque no puede obtener un resultado de una subshell fácilmente.

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

ejemplo:

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

no funciona con arr=()- sigue siendo sólo la cadena vacía.


He votado a favor, pero he comenzado a usarlo [[ "${arr[*]}" = *[![:space:]]* ]], ya que puedo contar con al menos un personaje que no sea WS.
Michael

@ Michael: sí, esa es una buena opción si no necesita rechazar arr=(" ").
Peter Cordes

0

En mi caso, la segunda respuesta no fue suficiente porque podría haber espacios en blanco. Llegué junto con:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi

echo | wcparece innecesariamente ineficiente en comparación con el uso de shell incorporado.
Peter Cordes

No estoy seguro si entiendo @PeterCordes, ¿puedo modificar las segundas respuestas ' [ ${#errors[@]} -eq 0 ];para evitar el problema del espacio en blanco? También preferiría el incorporado.
Micha

¿Cómo exactamente el espacio en blanco causa un problema? $#se expande a un número y funciona bien incluso después opts+=(""). Por ejemplo, unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"y lo consigo 2. ¿Puedes mostrar un ejemplo de algo que no funciona?
Peter Cordes

Hace mucho tiempo IIRC la fuente de origen siempre imprime al menos "". Por lo tanto, para opts = "" u opts = ("") necesitaba 0, no 1, ignorando la nueva línea vacía o la cadena vacía.
Micha

Ok, entonces necesitas tratar opts=("")lo mismo que opts=()? Esa no es una matriz vacía, pero puede verificar si hay una matriz vacía o un primer elemento vacío con opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Tenga en cuenta que su respuesta actual dice "no hay opciones" para opts=("" "-foo"), que es totalmente falso, y esto reproduce ese comportamiento. Podría [[ -z "${opts[*]}" ]]adivinar, para interpolar todos los elementos de la matriz en una cadena plana, que -zverifica la longitud no cero. Si verificar el primer elemento es suficiente, -z "$opts"funciona.
Peter Cordes

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.