Verifique se a matriz está vazia no Bash

110

Eu tenho uma matriz que é preenchida com diferentes mensagens de erro enquanto meu script é executado.

Eu preciso de uma maneira de verificar se está vazio ou não no final do script e executar uma ação específica, se estiver.

Eu já tentei tratá-lo como um VAR normal e usar -z para verificá-lo, mas isso não parece funcionar. Existe uma maneira de verificar se uma matriz está vazia ou não no Bash?

Marcos Sander
fonte

Respostas:

143

Supondo que sua matriz seja $errors, basta verificar se a contagem de elementos é zero.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi
Michael Hampton
fonte
10
Observe que =é um operador de cadeia. Por acaso funciona bem neste caso, mas eu usaria o operador aritmético adequado -eq(apenas no caso de querer mudar para -geou -ltetc.).
Musiphil
6
Não funciona com set -u: "variável não acoplada" - se a matriz estiver vazia.
Igor
@ Igor: Funciona para mim no Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Se eu unset foo, então ele imprime foo: unbound variable, mas isso é diferente: a variável da matriz não existe, ao invés de existir e estar vazia.
Pedro Cordes
Também testado no Bash 3.2 (OSX) ao usar set -u- desde que você tenha declarado sua variável primeiro, isso funciona perfeitamente.
Zeroimpl 5/0818
15

Eu geralmente uso expansão aritmética neste caso:

if (( ${#a[@]} )); then
    echo not empty
fi
x-yuri
fonte
Legal e Limpo! Eu gosto disso. Também observo que, se o primeiro elemento da matriz for sempre vazio, (( ${#a} ))(o comprimento do primeiro elemento) também funcionará. No entanto, isso falhará a=(''), enquanto (( ${#a[@]} ))que a resposta dada será bem-sucedida.
precisa saber é
8

Você também pode considerar a matriz como uma variável simples. Dessa forma, apenas usando

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

ou usando o outro lado

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

O problema com essa solução é que se uma matriz é declarada como este: array=('' foo). Essas verificações reportarão a matriz como vazia, enquanto claramente não é. (obrigado @musiphil!)

Usar [ -z "$array[@]" ]não é claramente uma solução também. Não especificar colchetes tenta interpretar $arraycomo uma sequência ( [@]nesse caso, é uma sequência literal simples) e, portanto, é sempre relatada como falsa: "a sequência literal está [@]vazia?" Claramente não.

wget
fonte
7
[ -z "$array" ]ou [ -n "$array" ]não funciona. Tente array=('' foo); [ -z "$array" ] && echo emptye ele será impresso emptymesmo que arrayclaramente não esteja vazio.
Musiphil 17/08/2015
2
[[ -n "${array[*]}" ]]interpola toda a matriz como uma sequência, que você verifica se há um comprimento diferente de zero. Se você considera array=("" "")vazio, em vez de ter dois elementos vazios, isso pode ser útil.
Peter Cordes
@ PeterCordes Eu não acho que isso funcione. A expressão é avaliada como um caractere de espaço único e [[ -n " " ]]é "true", o que é uma pena. Seu comentário é exatamente o que eu quero fazer.
Michael
@ Michael: Merda, você está certo. Funciona apenas com uma matriz de 1 elemento de uma cadeia vazia, não com 2 elementos. Até chequei o bash mais antigo e ainda está errado lá; como você diz set -xmostra como se expande. Acho que não testei esse comentário antes de postar. >. <Você pode fazê-lo funcionar definindo IFS=''(salve / restaure em torno desta instrução), porque a "${array[*]}"expansão separa elementos com o primeiro caractere do IFS. (Ou espaço, se não estiver definido). Mas " Se o IFS for nulo, os parâmetros serão unidos sem separadores intermediários " (documentos para parâmetros de posição $ *, mas eu assumo o mesmo para matrizes).
Peter Cordes
@ Michael: Adicionado uma resposta que faz isso.
Peter Cordes
3

Eu verifiquei com 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

e 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

No último caso, você precisa da seguinte construção:

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

para que não falhe na matriz vazia ou não configurada. Isso se você fizer set -eucomo eu costumo fazer. Isso fornece uma verificação de erro mais rigorosa. Dos documentos :

-e

Saia imediatamente se um pipeline (consulte Pipelines), que pode consistir em um único comando simples (consulte Comandos Simples), uma lista (consulte Listas) ou um comando composto (consulte Comandos Compostos) retornar um status diferente de zero. O shell não sai se o comando que falha faz parte da lista de comandos imediatamente após uma palavra-chave while ou while, parte do teste em uma instrução if, parte de qualquer comando executado em um && ou || list exceto o comando após o final && ou ||, qualquer comando em um pipeline, exceto o último, ou se o status de retorno do comando estiver sendo invertido com! Se um comando composto que não seja um subshell retornar um status diferente de zero porque um comando falhou enquanto -e estava sendo ignorado, o shell não sai. Uma interceptação no ERR, se definida, é executada antes da saída do shell.

Essa opção se aplica ao ambiente do shell e a cada ambiente de subshell separadamente (consulte Ambiente de execução de comando) e pode causar a saída de subshells antes de executar todos os comandos no subshell.

Se um comando composto ou função shell for executado em um contexto em que -e está sendo ignorado, nenhum dos comandos executados no comando ou corpo da função composto será afetado pela configuração -e, mesmo se -e estiver definido e um comando retornar um status de falha. Se um comando composto ou função de shell definir -e durante a execução em um contexto em que -e é ignorado, essa configuração não terá nenhum efeito até que o comando composto ou o comando que contém a chamada de função seja concluído.

-você

Trate variáveis ​​e parâmetros não configurados que não sejam os parâmetros especiais '@' ou '*' como um erro ao executar a expansão de parâmetros. Uma mensagem de erro será gravada no erro padrão e um shell não interativo será encerrado.

Se você não precisar disso, fique à vontade para omitir :+${array[@]}parte.

Observe também que é essencial usar o [[operador aqui, para [obter:

$ 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
x-yuri
fonte
Com -uvocê deve realmente usar ${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski
@JakubBochenski De que versão do bash você está falando? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri
O problema no exemplo de colchetes simples é o @, certamente. Você poderia usar a *expansão de array como [ "${array[*]}" ], não? Ainda assim, [[também funciona bem. O comportamento de ambos para uma matriz com várias cadeias vazias é um pouco surpreendente. Ambos [ ${#array[*]} ]e [[ "${array[@]}" ]]são falsos array=()e array=('')verdadeiros para array=('' '')(duas ou mais cadeias vazias). Se você quiser que uma ou mais cadeias vazias sejam verdadeiras, use-a [ ${#array[@]} -gt 0 ]. Se você quisesse que todos fossem falsos, talvez //eles pudessem sair.
EISD
@eisd eu poderia usar [ "${array[*]}" ], mas se eu tivesse essa expressão, seria mais difícil para mim entender o que ela faz. Desde que [...]opera em termos de seqüências de caracteres no resultado da interpolação. Ao contrário [[...]], que pode estar ciente do que foi interpolado. Ou seja, ele pode saber que foi passado um array. [[ ${array[@]} ]]lê para mim como "verifique se a matriz não está vazia", ​​enquanto [ "${array[*]}" ]"verifique se o resultado da interpolação de todos os elementos da matriz é uma string não vazia".
x-yuri
... Quanto ao comportamento com duas cadeias vazias, não é de todo surpreendente para mim. O que é surpreendente é o comportamento com uma string vazia. Mas sem dúvida razoável. Em relação a [ ${#array[*]} ], você provavelmente quis dizer [ "${array[*]}" ], já que o primeiro é válido para qualquer número de elementos. Porque o número de elementos é sempre uma sequência não vazia. Em relação ao último com dois elementos, a expressão entre colchetes se expande para a ' 'qual é uma sequência não vazia. Quanto a [[ ${array[@]} ]]eles, eles apenas pensam (e com razão) que qualquer matriz de dois elementos não esteja vazia.
x-yuri 02/09
2

Se você deseja detectar uma matriz com elementos vazios , comoarr=("" "") vazio, igual aarr=()

Você pode colar todos os elementos juntos e verificar se o resultado tem tamanho zero. (Construir uma cópia achatada do conteúdo da matriz não é ideal para desempenho, se a matriz puder ser muito grande. Mas espero que você não esteja usando o bash para programas como esse ...)

Mas se "${arr[*]}"expande com elementos separados pelo primeiro caractere de IFS. Portanto, você precisa salvar / restaurar o IFS e fazer IFS=''isso para funcionar, ou verifique se o comprimento da string == # dos elementos da matriz - 1. (Uma matriz de nelementos possui n-1separadores). Para lidar com isso de um por um, é mais fácil diminuir a concatenação em 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 teste com 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

Infelizmente, este falhar por arr=(): [[ 1 -ne 0 ]]. Portanto, você precisa verificar as matrizes realmente vazias primeiro separadamente.


Ou comIFS='' . Provavelmente, você deseja salvar / restaurar o IFS em vez de usar um subshell, porque não é possível obter um resultado de um subshell facilmente.

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

exemplo:

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

se trabalhar com arr=()- ainda é apenas uma string vazia.

Peter Cordes
fonte
Votou, mas comecei a usar [[ "${arr[*]}" = *[![:space:]]* ]], pois posso contar com pelo menos um caractere não WS.
Michael
@ Michael: sim, essa é uma boa opção se você não precisar rejeitar arr=(" ").
Peter Cordes
0

No meu caso, a segunda resposta não foi suficiente porque poderia haver espaços em branco. Eu vim junto com:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi
Micha
fonte
echo | wcparece desnecessariamente ineficiente em comparação ao uso de shell embutidos.
Peter Cordes
Não tenho certeza se entendo o @PeterCordes, posso modificar as segundas respostas ' [ ${#errors[@]} -eq 0 ];para solucionar o problema de espaço em branco? Eu também prefiro o embutido.
22817 Micha
Como exatamente o espaço em branco causa um problema? $#expande para um número e funciona bem mesmo depois opts+=(""). por exemplo, unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"e eu entendo 2. Você pode mostrar um exemplo de algo que não funciona?
22616 Peter Cordes
Faz muito tempo. IIRC a fonte de origem sempre imprimiu pelo menos "". Assim, para opts = "" ou opts = (""), eu precisava de 0, não 1, ignorando a nova linha vazia ou a sequência vazia.
21417 Micha
Ok, então você precisa tratar opts=("")o mesmo que opts=()? Essa não é uma matriz vazia, mas você pode verificar a matriz vazia ou o primeiro elemento vazio com opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Observe que sua resposta atual diz "sem opções" para opts=("" "-foo"), o que é totalmente falso, e isso reproduz esse comportamento. Você poderia [[ -z "${opts[*]}" ]]adivinhar, para interpolar todos os elementos da matriz em uma sequência plana, que -zverifica se há um comprimento diferente de zero. Se a verificação do primeiro elemento for suficiente, -z "$opts"funciona.
Peter Cordes