Teste se várias variáveis ​​estão definidas


19

Eu gostaria de garantir que, em um determinado ponto de um script, após sourceum arquivo de configuração, várias variáveis ​​sejam definidas e, se não estiverem, parem a execução, informando o usuário sobre a variável ausente. eu tentei

for var in $one $two $three ; do
    ...

mas se, por exemplo, $twonão estiver definido, o loop nunca será executado $two. A próxima coisa que tentei foi

for var in one two three ; do
    if [ -n ${!var} ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

Mas se dois não estiverem definidos, ainda recebo "dois está definido como" em vez de "dois não está definido".

Como posso ter certeza de que todas as variáveis ​​necessárias estão definidas?

Atualização / solução: Eu sei que há uma diferença entre "conjunto" e "conjunto, mas vazio". Agora uso (graças a /programming//a/16753536/3456281 e as respostas a esta pergunta) o seguinte:

if [ -n "${!var:-}" ] ; then

portanto, se varestiver definido, mas vazio, ainda será considerado inválido.


1
Você também pode adicionar set -uao início do seu script para finalizá-lo imediatamente quando uma variável não definida for usada.
N

Respostas:


8

Erro de citação.

if [ -n "${!var}" ] ; then

Para o futuro: Configuração

set -x

antes de executar o código teria mostrado o problema. Em vez de adicionar isso ao código, você pode chamar seu script com

bash -vx ./my/script.sh

Isso funciona, mas o que está acontecendo com / sem aspas? Minha abordagem geral está correta em primeiro lugar?
Jasper

Sim, isso é premonitório: Eu respondi a pergunta antes de ser perguntado ... 8-)
Hauke Laging

4
@ Jasper Você deve sempre citar variáveis. Isso não custa mais tempo do que pensar se isso é necessário todas as vezes. Mas evita os erros.
Hauke ​​Laging

Pelo contrário, citar apenas protege contra expansão. Se você está interessado na expansão, certamente não é o caminho a citar. Por exemplo: cs = 673.290.765; set - $ (IFS =,; eco $ cs); eco $ #; saída: 3
mikeserv

2
@mikeserv Somente aspas simples protegem contra a expansão o que eu obviamente não sugiro. As aspas duplas protegem contra a divisão de palavras. Não neguei que possa haver casos em que a citação deva ser omitida. Mas isso não é um argumento contra minha regra geral. Que tipo de pessoa vai usar algo assim set -- $(IFS=, ; echo $cs)? O tipo de pessoa que precisa perguntar aqui por if [ -n ${!var} ] ; thenque não funciona? Provavelmente não.
Hauke ​​Laging 26/03/14

5

A única coisa que você precisa são aspas no seu teste:

for var in one two three ; do
    if [ -n "${!var}" ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

Funciona para mim.


4

Se você deseja que o programa seja parado:

N= 
${one?var 1 is unset} 
${two:?var 2 is unset or null}
${three:+${N:?var 3 is set and not null}}

Isso fará o truque. Cada uma das mensagens após o ponto de interrogação é impressa stderre o shell pai morre. Bem, ok, então nem todas as mensagens - apenas uma - apenas a primeira que falha imprime uma mensagem, porque o shell morre. Eu gosto de usar esses testes assim:

( for v in "$one" "$two" "$three" ; do
    i=$((i+1)) ; : ${v:?var $i is unset or null...} 
done ) || _handle_it

Eu tinha muito mais a dizer sobre isso aqui .


2

Você pode adicionar

set -u

até o início do seu script para finalizá-lo quando tentar usar uma variável não definida.

Um script como

#!/bin/sh
set -u
echo $foo

vai resultar em

script.sh: 3: script.sh: foo: parâmetro não definido

Se você estiver usando bash, o erro será semelhante a este:

script.sh: linha 3: foo: variável não acoplada


E, na sua opinião, isso faz sentido para as verificações em tempo de execução?
Hauke ​​Laging

2
@HaukeLaging Eu não te sigo bem - set -uevita exatamente o tipo de erro que o OP está tentando evitar e (ao contrário de todas as outras soluções) não se limita a um conjunto específico de variáveis. Na verdade, é uma precaução útil para quase todos os scripts de shell fazer com que falhem com segurança, em vez de fazer coisas inesperadas quando uma variável está desabilitada. Este artigo é uma referência útil sobre o tópico.
N

@ n.st eu discordo - null pode ser um valor tão útil quanto não null se você planeja isso.
mikeserv

1
@ n.st Isso não faz sentido para o OP, pois ele não deseja uma proteção contra o acesso a variáveis ​​não definidas (BTW: uma variável definida mas vazia causaria o mesmo erro, mas não reagiria à sua "proteção"). Ele deseja que o tempo de execução verifique se uma variável está desativada / vazia. Sua sugestão pode ser útil como uma ajuda geral ao desenvolvimento, mas não resolve o problema do OP.
Hauke ​​Laging 26/03/14

set -uEu acho que poderia ser usado também, mas não é tão flexível quanto a expansão adequada dos parâmetros (veja minha pergunta atualizada). E com set -uisso eu teria que fazer um acesso simulado a todas as variáveis ​​de destino se eu quiser verificar a presença delas em um só lugar.
Jasper

2

Uma solução que é maximamente amigável testa todos os requisitos e os reporta em conjunto, em vez de falhar no primeiro e exigir que o processo funcione corretamente:

#!/bin/bash

required_vars=(one two three)

missing_vars=()
for i in "${required_vars[@]}"
do
    test -n "${!i:+y}" || missing_vars+=("$i")
done
if [ ${#missing_vars[@]} -ne 0 ]
then
    echo "The following variables are not set, but should be:" >&2
    printf ' %q\n' "${missing_vars[@]}" >&2
    exit 1
fi

Uso uma variável de matriz para rastrear quais variáveis ​​não foram definidas e uso o resultado para compor uma mensagem voltada para o usuário.

Notas:

  • Eu citei ${required_vars[@]}no forloop principalmente por hábito - eu não recomendaria incluir nenhum metacaractere de shell nos seus nomes de variáveis!
  • Não citei ${#missing_vars[@]}, porque sempre é um número inteiro, mesmo que você seja perverso o suficiente para desconsiderar o conselho anterior.
  • Eu usei %qao imprimir; %snormalmente seria suficiente.
  • A saída de erro sempre vai para o fluxo de erros com >&2, portanto, não é canalizada para os comandos downstream
  • O silêncio é de ouro - não imprima informações de progresso ou informações de depuração, a menos que solicitado especificamente. Isso torna os erros mais óbvios.

1

bash4.2 permite testar se uma variável está definida com o -voperador; uma variável não configurada e uma variável configurada para a sequência vazia são duas condições diferentes:

$ unset foo
$ [[ -v foo ]] && echo foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty
$ foo=
$ [[ -v foo ]] && echo foo is set
foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty

Eu estava perguntando sobre "múltiplas" variáveis, então eu estava procurando algo como engano ...
Jasper

Você não precisa de engano, uma vez que -vleva o nome de uma variável, por isso for var in one two three; [[ -v $var ]] && ...; doneiria verificar se cada um one, twoe threesão definidos em sequência.
chepner

1

Acho que, se você quer dizer not set, a variável nunca deve ser inicializada. Se você usar [ -n "${!var}" ], a variável vazia like two=""falhará enquanto estiver definida . Você pode tentar isso:

one=1
three=3

for var in one two three; do
  declare -p $var > /dev/null 2>&1 \
  && printf '%s is set to %s\n' "$var" "${!var}" \
  || printf '%s is not set\n' "$var"
done

0

Uma solução concisa (embora um pouco hacky), para lidar com o caso em que o sourcescript d pode definir um valor nulo para as variáveis, é inicializar as variáveis ​​para um valor especial antes de buscar o script e depois verificar esse valor posteriormente , por exemplo

one=#UNSET#
two=#UNSET#
three=#UNSET#

. set_vars_script

if [[ $one$two$three == *#UNSET#* ]] ; then
  echo "One or more essential variables are unset" >&2
  exit 1
fi

1
outra solução legal, mas eu gostaria de dar uma mensagem de erro mais específica, em seguida, "uma ou mais variáveis unset" ...
Jasper

0

Estendi a resposta do @ mikeserv .

Esta versão leva uma lista de variáveis ​​para verificar a presença, imprimindo o nome da variável ausente e acionando uma chamada para uso () por erro.

REQD_VALUES=("VARIABLE" "FOO" "BAR" "OTHER_VARIABLE")
( i=0; for var_name in ${REQD_VALUES[@]}; do
    VALUE=${!var_name} ;
    i=$((i+1)) ; : ${VALUE:?$var_name is missing}
done ) || usage

Exemplo de saída quando um valor está ausente:

./myscript.sh: line 42: VALUE: OTHER_VARIABLE is missing

Observe que você pode alterar a variável chamada VALUE para um nome alternativo que melhor se adapte à saída desejada.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.