Nota
Estou dando uma resposta fortemente focada no Bash por causa da bash
tag.
Resposta curta
Desde que você lide apenas com variáveis nomeadas no Bash, essa função sempre deve informar se a variável foi definida, mesmo que seja uma matriz vazia.
variable-is-set() {
declare -p "$1" &>/dev/null
}
Por que isso funciona
No Bash (pelo menos em 3.0), se var
for uma variável declarada / definida, declare -p var
emite um declare
comando que definiria var
a variável para qualquer que seja seu tipo e valor atuais e retorna o código de status 0
(êxito). Se var
não for declarado, declare -p var
emite uma mensagem de erro stderr
e retorna o código de status 1
. Usando &>/dev/null
, redireciona a saída normal stdout
e a stderr
saída para /dev/null
, para nunca mais ser vista, e sem alterar o código de status. Portanto, a função retorna apenas o código de status.
Por que outros métodos (às vezes) falham no Bash
[ -n "$var" ]
: Isso verifica apenas se ${var[0]}
não está vazio. (No Bash, $var
é o mesmo que ${var[0]}
.)
[ -n "${var+x}" ]
: Apenas verifica se ${var[0]}
está definido.
[ "${#var[@]}" != 0 ]
: Isso verifica apenas se pelo menos um índice de $var
está definido.
Quando este método falha no Bash
Isso só funciona para variáveis nomeadas (incluindo $_
), e não determinadas variáveis especiais ( $!
, $@
, $#
, $$
, $*
, $?
, $-
, $0
, $1
, $2
, ..., e qualquer outro que eu possa ter esquecido). Como nenhuma dessas são matrizes, o estilo POSIX [ -n "${var+x}" ]
funciona para todas essas variáveis especiais. Mas tome cuidado para envolvê-lo em uma função, pois muitas variáveis especiais alteram valores / existência quando funções são chamadas.
Nota de compatibilidade do shell
Se o seu script tiver matrizes e você estiver tentando torná-lo compatível com o maior número possível de shells, considere usar em typeset -p
vez de declare -p
. Eu li que o ksh suporta apenas o primeiro, mas não pude testar isso. Eu sei que o Bash 3.0+ e o Zsh 5.5.1 suportam ambos typeset -p
e declare -p
, diferindo apenas em qual é uma alternativa para o outro. Mas não testei diferenças além dessas duas palavras-chave e não testei outras conchas.
Se você precisa que seu script seja compatível com POSIX sh, não poderá usar matrizes. Sem matrizes, [ -n "{$var+x}" ]
funciona.
Código de comparação para diferentes métodos no Bash
Essa função desativa a variável var
, eval
é o código passado, executa testes para determinar se var
é definido pelo eval
código d e, finalmente, mostra os códigos de status resultantes para os diferentes testes.
Estou pulando test -v var
, [ -v var ]
e [[ -v var ]]
porque eles produzem resultados idênticos ao padrão POSIX [ -n "${var+x}" ]
, exigindo o Bash 4.2+. Também estou pulando typeset -p
porque é o mesmo que declare -p
nas conchas que testei (Bash 3.0 a 5.0 e Zsh 5.5.1).
is-var-set-after() {
# Set var by passed expression.
unset var
eval "$1"
# Run the tests, in increasing order of accuracy.
[ -n "$var" ] # (index 0 of) var is nonempty
nonempty=$?
[ -n "${var+x}" ] # (index 0 of) var is set, maybe empty
plus=$?
[ "${#var[@]}" != 0 ] # var has at least one index set, maybe empty
count=$?
declare -p var &>/dev/null # var has been declared (any type)
declared=$?
# Show test results.
printf '%30s: %2s %2s %2s %2s\n' "$1" $nonempty $plus $count $declared
}
Código do caso de teste
Observe que os resultados do teste podem ser inesperados devido ao Bash tratar os índices não numéricos da matriz como "0" se a variável não tiver sido declarada como uma matriz associativa. Além disso, matrizes associativas são válidas apenas no Bash 4.0+.
# Header.
printf '%30s: %2s %2s %2s %2s\n' "test" '-n' '+x' '#@' '-p'
# First 5 tests: Equivalent to setting 'var=foo' because index 0 of an
# indexed array is also the nonindexed value, and non-numerical
# indices in an array not declared as associative are the same as
# index 0.
is-var-set-after "var=foo" # 0 0 0 0
is-var-set-after "var=(foo)" # 0 0 0 0
is-var-set-after "var=([0]=foo)" # 0 0 0 0
is-var-set-after "var=([x]=foo)" # 0 0 0 0
is-var-set-after "var=([y]=bar [x]=foo)" # 0 0 0 0
# '[ -n "$var" ]' fails when var is empty.
is-var-set-after "var=''" # 1 0 0 0
is-var-set-after "var=([0]='')" # 1 0 0 0
# Indices other than 0 are not detected by '[ -n "$var" ]' or by
# '[ -n "${var+x}" ]'.
is-var-set-after "var=([1]='')" # 1 1 0 0
is-var-set-after "var=([1]=foo)" # 1 1 0 0
is-var-set-after "declare -A var; var=([x]=foo)" # 1 1 0 0
# Empty arrays are only detected by 'declare -p'.
is-var-set-after "var=()" # 1 1 1 0
is-var-set-after "declare -a var" # 1 1 1 0
is-var-set-after "declare -A var" # 1 1 1 0
# If 'var' is unset, then it even fails the 'declare -p var' test.
is-var-set-after "unset var" # 1 1 1 1
Saída de teste
As mnemónicas teste na linha de cabeçalho correspondem a [ -n "$var" ]
, [ -n "${var+x}" ]
, [ "${#var[@]}" != 0 ]
, e declare -p var
, respectivamente.
test: -n +x #@ -p
var=foo: 0 0 0 0
var=(foo): 0 0 0 0
var=([0]=foo): 0 0 0 0
var=([x]=foo): 0 0 0 0
var=([y]=bar [x]=foo): 0 0 0 0
var='': 1 0 0 0
var=([0]=''): 1 0 0 0
var=([1]=''): 1 1 0 0
var=([1]=foo): 1 1 0 0
declare -A var; var=([x]=foo): 1 1 0 0
var=(): 1 1 1 0
declare -a var: 1 1 1 0
declare -A var: 1 1 1 0
unset var: 1 1 1 1
Sumário
declare -p var &>/dev/null
é (100%?) confiável para testar variáveis nomeadas no Bash desde pelo menos 3,0.
[ -n "${var+x}" ]
é confiável em situações compatíveis com POSIX, mas não pode manipular matrizes.
- Existem outros testes para verificar se uma variável não é vazia e para verificar variáveis declaradas em outros shells. Mas esses testes não são adequados para scripts Bash nem POSIX.
if test $# -gt 0; then printf 'arg <%s>\n' "$@"; fi
.