Como testar se uma variável está definida no Bash antes da versão 4.2 com a opção shell do conjunto de substantivos?

16

Para versões do Bash anteriores ao "GNU bash, versão 4.2", existem alternativas equivalentes para a -vopção do testcomando? Por exemplo:

shopt -os nounset
test -v foobar && echo foo || echo bar
# Output: bar
foobar=
test -v foobar && echo foo || echo bar
# Output: foo
Tim Friske
fonte
-vnão é uma opção test, mas um operador para expressões condicionais.
StackExchange for All
@ Tim Há três coisas, além de ser um token, uma string e parte de uma linha: um optionpara um comando test -v, um operatorpara um conditional expressione um unary test primarypara [ ]. Não misture o idioma inglês com as definições de shell.

Respostas:

36

Portátil para todos os invólucros POSIX:

if [ -n "${foobar+1}" ]; then
  echo "foobar is defined"
else
  echo "foobar is not defined"
fi

Faça isso ${foobar:+1}se quiser tratar foobarda mesma maneira, esteja vazio ou não definido. Você também pode usar ${foobar-}para obter uma sequência vazia quando foobarindefinida e o valor de foobaroutra forma (ou colocar qualquer outro valor padrão depois da -).

Em ksh, se foobarfor declarado, mas não definido, como em typeset -a foobar, será ${foobar+1}expandido para a sequência vazia.

O Zsh não possui variáveis ​​declaradas, mas não definidas: typeset -a foobarcria uma matriz vazia.

No bash, as matrizes se comportam de uma maneira diferente e surpreendente. ${a+1}somente se expande para 1se afor uma matriz não vazia, por exemplo

typeset -a a; echo ${a+1}    # prints nothing
e=(); echo ${e+1}            # prints nothing!
f=(''); echo ${f+1}          # prints 1

O mesmo princípio se aplica a matrizes associativas: variáveis ​​de matriz são tratadas como definidas se tiverem um conjunto de índices não vazio.

Uma maneira diferente e específica do bash de testar se uma variável de qualquer tipo foi definida é verificar se ela está listada . Isso relata matrizes vazias conforme definido, diferentemente , mas relata variáveis ​​declaradas mas não atribuídas ( ) como indefinidas.${!PREFIX*}${foobar+1}unset foobar; typeset -a foobar

case " ${!foobar*} " in
  *" foobar "*) echo "foobar is defined";;
  *) echo "foobar is not defined";;
esac

Isso é equivalente a testar o valor de retorno de typeset -p foobarordeclare -p foobar , exceto que typeset -p foobarfalha em variáveis ​​declaradas, mas não atribuídas.

No bash, como no ksh, set -o nounset; typeset -a foobar; echo $foobardispara um erro na tentativa de expandir a variável indefinida foobar. Ao contrário do ksh, set -o nounset; foobar=(); echo $foobar(ou echo "${foobar[@]}") também dispara um erro.

Observe que em todas as situações descritas aqui, se ${foobar+1}expande para a cadeia vazia se e somente se $foobarcausaria um erro em set -o nounset.

Gilles 'SO- parar de ser mau'
fonte
1
E as matrizes? Na versão Bash "GNU bash, versão 4.1.10 (4) -release (i686-pc-cygwin)" echo "${foobar:+1}"não é impressa 1se tiver declare -a foobarsido emitida anteriormente e, portanto, foobaré uma matriz indexada. declare -p foobarrelatórios corretamente declare -a foobar='()'. Funciona "${foobar:+1}"apenas para variáveis ​​que não são de matriz?
Tim Friske
O @TimFriske ${foobar+1}(sem o :, inverti dois exemplos na minha resposta original) está correto para matrizes no bash se sua definição de "definido" for " $foobarfuncionaria sob set -o nounset". Se sua definição for diferente, o bash é um pouco estranho. Veja minha resposta atualizada.
Gilles 'SO- stop be evil'
1
Em relação ao tópico "No bash, as matrizes se comportam de uma maneira diferente e surpreendente". o comportamento pode ser explicado nas páginas de manual do bash (1), seção "Arrays". Ele afirma que "Fazer referência a uma variável de matriz sem um subscrito é equivalente a referenciar a matriz com um subscrito de 0.". Portanto, se nem um 0índice nem uma chave são definidos como é verdade a=(), ${a+1}retornam corretamente nada.
Tim Friske
1
@ TimFriske Eu sei que a implementação do bash está em conformidade com sua documentação. Mas tratar uma matriz vazia como uma variável indefinida é um design realmente estranho.
Gilles 'SO- stop be evil'
Prefiro o resultado de: Como verificar se uma variável está definida no Bash? -> O Caminho Certo ... Eu acordei como eu acho que isso deveria funcionar. Ouso dizer que o Windows tem um definedoperador. Test poderia fazer isso; não pode ser difícil ( hum ....)
será
5

Para resumir a resposta de Gilles, criei as seguintes regras:

  1. Use [[ -v foobar ]]para variáveis ​​na versão Bash> = 4.2.
  2. Use declare -p foobar &>/dev/nullpara variáveis ​​de matriz na versão Bash <4.2.
  3. Use (( ${foo[0]+1} ))ou (( ${bar[foo]+1} ))para subscritos de matrizes indexadas ( -a) e com chave ( -A) ( declare), respectivamente. As opções 1 e 2 não funcionam aqui.
Tim Friske
fonte
3

Eu uso a mesma técnica para todas as variáveis ​​no bash e funciona, por exemplo:

[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

saídas:

foobar is unset

enquanto

foobar=( "val" "val2" )
[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

saídas:

foobar is set
Stein Inge Morisbak
fonte
Teve que remover [@] se a matriz tiver mais de um valor.
Stein Inge Morisbak
1
Isso funciona muito bem, desde que você queira testar se ele tem um valor, não se foi definido. Ou seja, foobar=""relatará isso foobar is unset. Não, espere, eu retiro isso. Realmente apenas testa se o primeiro elemento está vazio ou não, então parece ser apenas uma boa idéia se você souber que a variável NÃO é uma matriz e se preocupa apenas com o vazio, não com a definição.
Ron Burk 15/06
só funciona se seus scripts estão funcionando com variáveis indefinidas permitidos (sem set-u)
Florian Heigl