O que é uma maneira concisa de verificar se as variáveis ​​de ambiente estão definidas em um script de shell Unix?

500

Eu tenho alguns scripts shell do Unix nos quais preciso verificar se certas variáveis ​​de ambiente estão definidas antes de começar a fazer as coisas, então faço esse tipo de coisa:

if [ -z "$STATE" ]; then
    echo "Need to set STATE"
    exit 1
fi  

if [ -z "$DEST" ]; then
    echo "Need to set DEST"
    exit 1
fi

que é muita digitação. Existe um idioma mais elegante para verificar se um conjunto de variáveis ​​de ambiente está definido?

Edição: devo mencionar que essas variáveis ​​não têm valor padrão significativo - o script deve errar se houver algum não definido.

AndrewR
fonte
2
Muitas das respostas a essa pergunta parecem algo que você veria no Code Golf Stack Exchange .
Daniel Schilling

Respostas:

587

Expansão de parâmetros

A resposta óbvia é usar uma das formas especiais de expansão de parâmetros:

: ${STATE?"Need to set STATE"}
: ${DEST:?"Need to set DEST non-empty"}

Ou melhor (consulte a seção 'Posição das aspas duplas' abaixo):

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

A primeira variante (usando just ?) requer que STATE seja definido, mas STATE = "" (uma sequência vazia) está OK - não exatamente o que você deseja, mas a notação alternativa e antiga.

A segunda variante (usando :?) requer que DEST seja definido e não esteja vazio.

Se você não fornecer nenhuma mensagem, o shell fornecerá uma mensagem padrão.

A ${var?}construção é portátil de volta à Versão 7 UNIX e ao Bourne Shell (1978 ou nos próximos anos). A ${var:?}construção é um pouco mais recente: acho que estava no System III UNIX por volta de 1981, mas pode ter sido no PWB UNIX antes disso. Está, portanto, no Korn Shell e nos shells POSIX, incluindo especificamente o Bash.

Geralmente é documentado na página de manual do shell em uma seção chamada Expansão de parâmetros . Por exemplo, o bashmanual diz:

${parameter:?word}

Exibir erro se nulo ou não definido. Se o parâmetro for nulo ou não definido, a expansão da palavra (ou uma mensagem nesse sentido se a palavra não estiver presente) será gravada no erro padrão e o shell, se não for interativo, será encerrado. Caso contrário, o valor do parâmetro é substituído.

O comando do cólon

Provavelmente devo acrescentar que o comando dois pontos simplesmente tem seus argumentos avaliados e depois é bem-sucedido. É a notação de comentário do shell original (antes de ' #' até o final da linha). Por um longo tempo, os scripts de shell Bourne tiveram dois pontos como o primeiro caractere. O Shell C leria um script e usaria o primeiro caractere para determinar se era para o Shell C (um #hash ' ') ou o shell Bourne ( :dois pontos ' '). Então o kernel entrou em ação e adicionou suporte aos comentários ' #!/path/to/program' e o shell Bourne recebeu ' #', e a convenção do cólon foi esquecida. Mas se você encontrar um script que comece com dois pontos, agora saberá o porquê.


Posição de aspas duplas

blong perguntou em um comentário :

Alguma opinião sobre essa discussão? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

A essência da discussão é:

… No entanto, quando eu shellcheck(com a versão 0.4.1), recebo esta mensagem:

In script.sh line 13:
: ${FOO:?"The environment variable 'FOO' must be set and non-empty"}
  ^-- SC2086: Double quote to prevent globbing and word splitting.

Algum conselho sobre o que devo fazer neste caso?

A resposta curta é "faça o que shellchecksugere":

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Para ilustrar o motivo, estude o seguinte. Observe que o :comando não ecoa seus argumentos (mas o shell avalia os argumentos). Queremos ver os argumentos, portanto, o código abaixo usa printf "%s\n"no lugar de :.

$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$ 
$ x="*"
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
bash: x: You must set x
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
*
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ 

Observe como o valor em $xé expandido para primeiro *e depois para uma lista de nomes de arquivos quando a expressão geral não está entre aspas duplas. Isto é o que shellcheckestá recomendando que deve ser corrigido. Não verifiquei que ela não se opõe ao formulário em que a expressão está entre aspas duplas, mas é uma suposição razoável de que estaria tudo bem.

Jonathan Leffler
fonte
2
É disso que preciso. Eu tenho usado várias versões do Unix desde 1987 e nunca vi essa sintaxe - só serve para mostrar ...
AndrewR
Como isso funciona e onde está documentado? Gostaria de saber se ele pode ser modificado para verificar se a variável existe e está definida com um valor específico.
jhabbott
6
Está documentado na página do manual do shell ou no manual do Bash, geralmente no cabeçalho 'Expansão de parâmetros'. A forma padrão para verificar se a variável existe e está definido para um valor específico (dizer 'abc') é simplesmente: if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi.
Jonathan Leffler
Como uso essa sintaxe com uma variável de cadeia de caracteres com espaços. Eu recebo um erro dizendo "Este: comando não encontrado" ao definir a variável que desejo marcar como "Este é um teste". Alguma ideia?
user2294382
1
Alguma opinião sobre essa discussão? github.com/koalaman/shellcheck/issues/...
BLONG
138

Tente o seguinte:

[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;
David Schlosnagle
fonte
8
Isso é bem detalhado. O ponto e vírgula é redundante.
Jonathan Leffler
3
Ou ainda menor, [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } veja esta publicação no blog
zipizap 18/11/2012
36
O que é isso, é legível. Sou a favor deste. Os scripts Bash precisam de toda a ajuda possível nesse departamento!
Iain Duncan
a resposta original é mais consistente em sua sintaxe.
dente rápido
4
No entanto, isso não distingue entre uma variável não definida e uma variável definida como a sequência vazia.
chepner 21/09/16
56

Sua pergunta depende do shell que você está usando.

A casca de Bourne deixa muito pouco no que você procura.

MAS...

Funciona, em quase todos os lugares.

Apenas tente ficar longe do csh. Foi bom para os sinos e assobios que adicionou, comparou a concha Bourne, mas está realmente rangendo agora. Se você não acredita em mim, tente separar o STDERR no csh! (-:

Existem duas possibilidades aqui. O exemplo acima, ou seja, usando:

${MyVariable:=SomeDefault}

pela primeira vez, você precisa se referir a $ MyVariable. Isso leva o env. var MyVariable e, se não estiver definido no momento, atribui o valor de SomeDefault à variável para uso posterior.

Você também tem a possibilidade de:

${MyVariable:-SomeDefault}

que apenas substitui SomeDefault pela variável em que você está usando essa construção. Ele não atribui o valor SomeDefault à variável, e o valor de MyVariable ainda será nulo depois que essa instrução for encontrada.

Rob Wells
fonte
3
CSH: (foo> foo.out)> & foo.err
Sr.Ree
O shell Bourne faz o que é necessário.
Jonathan Leffler
51

Certamente, a abordagem mais simples é adicionar a -uopção ao shebang (a linha na parte superior do seu script), supondo que você esteja usando bash:

#!/bin/sh -u

Isso fará com que o script saia se houver alguma variável não acoplada oculta.

Paul Makkar
fonte
40
${MyVariable:=SomeDefault}

Se MyVariablefor definido e não nulo, redefinirá o valor da variável (= nada acontece).
Senão, MyVariableestá definido como SomeDefault.

O acima tentará executar ${MyVariable}, portanto, se você quiser apenas definir a variável, faça:

MyVariable=${MyVariable:=SomeDefault}
Vincent Van Den Berghe
fonte
Isso não gera a mensagem - e o Q diz que não há padrão (embora não tenha dito isso quando você digitou sua resposta).
Jonathan Leffler
10
Versões corretas: (1): $ {MyVariable: = SomeDefault} ou (2): MyVariable = $ {MyVariable: -SomeDefault}
Jonathan Leffler
23

Na minha opinião, a verificação mais simples e mais compatível para #! / Bin / sh é:

if [ "$MYVAR" = "" ]
then
   echo "Does not exist"
else
   echo "Exists"
fi

Novamente, isso é para / bin / sh e também é compatível com sistemas Solaris antigos.

Adriano
fonte
Este 'funciona', mas até mesmo sistemas antigos Solaris tem um /bin/shque apoiar a ${var:?}notação, etc.
Jonathan Leffler
A melhor resposta até agora.
Ole
4
Ser definido como a sequência vazia é diferente de não ser definido. Este teste imprimiria "Não existe" depois de ambos unset MYVARe MYVAR=.
chepner
@chepner, votei seu comentário como positivo no heads-up presumivelmente valioso, mas depois continuei testando-o (com bash) e não consegui definir um var para uma string vazia sem que ele também não estivesse definido. Como você faria isso?
Sz.
@ Sz Não sei ao certo o que você quer dizer. "Desativar" significa que o nome não tem nenhum valor atribuído a ele. Se foonão estiver definido, "$foo"ainda será expandido para a sequência vazia.
chepner
17

bash4.2 introduziu o -voperador que testa se um nome está definido com algum valor, mesmo a string vazia.

$ unset a
$ b=
$ c=
$ [[ -v a ]] && echo "a is set"
$ [[ -v b ]] && echo "b is set"
b is set
$ [[ -v c ]] && echo "c is set"
c is set
chepner
fonte
16

Eu sempre usei:

if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi

Receio que não seja muito mais conciso.

Sob CSH, você tem $? STATE.

Mr.Ree
fonte
2
Isso verifica se STATEestá vazio ou não definido, não apenas não definido.
chepner 21/09/16
7

Para futuras pessoas como eu, eu queria dar um passo adiante e parametrizar o nome do var, para que eu possa percorrer uma lista de nomes de variáveis ​​com tamanho variável:

#!/bin/bash
declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN)

for var_name in "${vars[@]}"
do
  if [ -z "$(eval "echo \$$var_name")" ]; then
    echo "Missing environment variable $var_name"
    exit 1
  fi
done
nicooga
fonte
3

Podemos escrever uma boa asserção para verificar várias variáveis ​​de uma só vez:

#
# assert if variables are set (to a non-empty string)
# if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise
#
# Usage: assert_var_not_null [-f] variable ...
#
function assert_var_not_null() {
  local fatal var num_null=0
  [[ "$1" = "-f" ]] && { shift; fatal=1; }
  for var in "$@"; do
    [[ -z "${!var}" ]] &&
      printf '%s\n' "Variable '$var' not set" >&2 &&
      ((num_null++))
  done

  if ((num_null > 0)); then
    [[ "$fatal" ]] && exit 1
    return 1
  fi
  return 0
}

Chamada de amostra:

one=1 two=2
assert_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_not_null -f one two three
echo test 3: return_code=$? # this code shouldn't execute

Resultado:

test 1: return_code=0
Variable 'three' not set
test 2: return_code=1
Variable 'three' not set

Mais afirmações aqui: https://github.com/codeforester/base/blob/master/lib/assertions.sh

codeforester
fonte
1

Nenhuma das soluções acima funcionou para meus propósitos, em parte porque verifiquei o ambiente em busca de uma lista aberta de variáveis ​​que precisam ser definidas antes de iniciar um processo demorado. Eu acabei com isso:

mapfile -t arr < variables.txt

EXITCODE=0

for i in "${arr[@]}"
do
   ISSET=$(env | grep ^${i}= | wc -l)
   if [ "${ISSET}" = "0" ];
   then
      EXITCODE=-1
      echo "ENV variable $i is required."
   fi
done

exit ${EXITCODE}
Gudlaugur Egilsson
fonte
-1

Em vez de usar scripts de shell externos, costumo carregar funções no meu shell de login. Eu uso algo como isso como uma função auxiliar para verificar as variáveis ​​de ambiente em vez de qualquer variável definida:

is_this_an_env_variable ()
    local var="$1"
    if env |grep -q "^$var"; then
       return 0
    else
       return 1
    fi
 }
nafdef
fonte
1
Isto está errado. is_this_an_env_variable Pretornará sucesso porque PATHexiste.
22418 Eric Eric
Existe porque é uma variável de ambiente. Se foi definido eqaul uma cadeia não nula é outra questão.
nafdef 24/03
-7

A $?sintaxe é bem organizada:

if [ $?BLAH == 1 ]; then 
    echo "Exists"; 
else 
    echo "Does not exist"; 
fi
Graeme
fonte
Isso funciona, mas alguém aqui sabe onde posso encontrar documentos sobre essa sintaxe? $? normalmente é o valor de retorno anterior.
Danny Staple
Meu mal - isso não parece funcionar. Experimente em um script simples, com e sem esse conjunto.
Danny Staple
11
Nos shells Bourne, Korn, Bash e POSIX, $?é o status de saída do comando anterior; $?BLAHé a sequência que consiste no status de saída do comando anterior concatenado com 'BLAH'. Isso não testa a variável $BLAH. (Há um rumor que poderia fazer mais ou menos o que é necessário no csh, mas conchas são deixadas à beira do mar.)
Jonathan Leffler
Essa é uma resposta completamente errada no que diz respeito ao Bash.
codeforester