Como saber se uma sequência de caracteres não está definida em um script de shell do Bash

151

Se eu quiser verificar a seqüência nula, eu faria

[ -z $mystr ]

mas e se eu quiser verificar se a variável foi definida? Ou não há distinção nos scripts do Bash?

Setjmp
fonte
27
por favor, tenha o hábito de usar [-z "$ mystr"] em vez de [-z $ mystr]
Charles Duffy
como fazer a coisa inversa? Quero dizer, quando a string não é nula
Abra o caminho
1
@flow: What about[ -n "${VAR+x}"] && echo not null
Lekensteyn
1
@CharlesDuffy Eu já li isso em vários recursos online. Por que isso é preferido?
ffledgling 03/03
6
@ Ayos, porque se você não tiver aspas, o conteúdo da variável é dividido em sequências e globbed; se for uma string vazia, torna-se em [ -z ]vez de [ -z "" ]; se tem espaços, torna-se em [ -z "my" "test" ]vez de [ -z my test ]; e se for [ -z * ], então *é substituído pelos nomes dos arquivos em seu diretório.
Charles Duffy

Respostas:

138

Acho que a resposta que você está depois está implícito (se não for indicado) por Vinko 's resposta , embora não esteja escrito de forma simples. Para distinguir se o VAR está definido, mas vazio ou não, você pode usar:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Você provavelmente pode combinar os dois testes na segunda linha em um com:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

No entanto, se você ler a documentação do Autoconf, descobrirá que eles não recomendam combinar termos com ' -a' e recomendam o uso de testes simples separados, combinados com &&. Eu não encontrei um sistema em que haja um problema; isso não significa que eles não existiam (mas são provavelmente extremamente raros hoje em dia, mesmo que não fossem tão raros no passado distante).

Você pode encontrar os detalhes dessas e de outras expansões de parâmetros de shell relacionadas , o comando testou [expressões condicionais no manual do Bash.


Recentemente, fui perguntado por e-mail sobre esta resposta com a pergunta:

Você usa dois testes e eu entendo bem o segundo, mas não o primeiro. Mais precisamente, não entendo a necessidade de expansão variável

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

Isso não faria o mesmo?

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

Pergunta justa - a resposta é 'Não, sua alternativa mais simples não faz a mesma coisa'.

Suponha que eu escreva isso antes do seu teste:

VAR=

Seu teste dirá "VAR não está definido", mas o meu dirá (por implicação, porque não ecoa nada) "VAR está definido, mas seu valor pode estar vazio". Experimente este script:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

A saída é:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

No segundo par de testes, a variável está definida, mas está definida como o valor vazio. Essa é a distinção que as notações ${VAR=value}e ${VAR:=value}fazem. O mesmo vale para ${VAR-value}e ${VAR:-value}, e ${VAR+value}e ${VAR:+value}, e assim por diante.


Como Gili aponta em sua resposta , se você executar bashcom a set -o nounsetopção, a resposta básica acima falhará unbound variable. É facilmente remediado:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Ou você pode cancelar a set -o nounsetopção com set +u( set -usendo equivalente a set -o nounset).

Jonathan Leffler
fonte
7
O único problema que tenho com esta resposta é que ela realiza sua tarefa de maneira bastante indireta e pouco clara.
Swiss,
3
Para aqueles que desejam procurar a descrição do que significa acima na página de manual do bash, procure a seção "Expansão de parâmetros" e, em seguida, este texto: "Quando não estiver executando a expansão de substring, usando os formulários documentados abaixo, o bash testa um parâmetro não definido ou nulo ['null' significa a sequência vazia]. A omissão dos dois pontos resulta em um teste apenas para um parâmetro não definido (...) $ {parâmetro: + palavra}: Use Valor Alternativo. é nulo ou não definido, nada é substituído; caso contrário, a expansão da palavra é substituída ".
David Tonhofer
2
@ Swiss Isso não é claro nem indireto, mas é idiomático. Talvez para um programador não familiarizado ${+}e ${-}isso não esteja claro, mas a familiaridade com essas construções é essencial para que um usuário seja competente no shell.
22612 William Pursell
Consulte stackoverflow.com/a/20003892/14731 se desejar implementar isso com set -o nounsetativado.
Gili
Eu fiz muitos testes nisso; porque os resultados nos meus scripts eram inconsistentes. Eu sugiro que as pessoas " [ -v VAR ]" estudem a abordagem com o bash -s v4.2 e posterior .
will
38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z também funciona para variáveis ​​indefinidas. Para distinguir entre um indefinido e um definido, use as coisas listadas aqui ou, com explicações mais claras, aqui .

A maneira mais limpa é usar a expansão, como nesses exemplos. Para obter todas as suas opções, consulte a seção Expansão de parâmetros do manual.

Palavra alternativa:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Valor padrão:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

É claro que você usaria um desses diferentemente, colocando o valor desejado em vez de 'valor padrão' e usando a expansão diretamente, se apropriado.

Vinko Vrsalovic
fonte
1
Mas quero distinguir se a string é "" ou não foi definida nunca. Isso é possível?
Setjmp 23/10/08
1
esta resposta não dizer como distinguir entre esses dois casos; siga o link bash faq que fornece mais discussões.
Charles Duffy
1
Eu adicionei que, após seu comentário, Charles
Vinko Vrsalovic 23/10/08
2
Consulte "Expansão de parâmetros" na página de manual do bash para todos esses "truques". Por exemplo, $ {foo: -default} para usar um valor padrão, $ {foo: = default} para atribuir o valor padrão, $ {foo :? message} para exibir uma mensagem de erro se foo não estiver definido, etc.
Jouni K Seppänen
18

Guia avançado de script do bash, 10.2. Substituição de Parâmetros:

  • $ {var + blahblah}: se var for definido, 'blahblah' será substituído pela expressão, caso contrário, nulo será substituído
  • $ {var-blahblah}: se var for definido, ele próprio será substituído; caso contrário, 'blahblah' será substituído
  • $ {var? blahblah}: se var for definido, ele será substituído; caso contrário, a função existe com 'blahblah' como uma mensagem de erro.


para basear a lógica do programa em se a variável $ mystr está definida ou não, você pode fazer o seguinte:

isdefined=0
${mystr+ export isdefined=1}

agora, se isdefined = 0, a variável foi indefinida, se isdefined = 1, a variável foi definida

Essa maneira de verificar as variáveis ​​é melhor que a resposta acima, pois é mais elegante, legível e, se o seu bash shell foi configurado para erro no uso de variáveis ​​indefinidas (set -u), o script será encerrado prematuramente.


Outras coisas úteis:

ter um valor padrão de 7 atribuído a $ mystr se não tiver sido definido e deixá-lo intacto de outra forma:

mystr=${mystr- 7}

para imprimir uma mensagem de erro e sair da função se a variável não estiver definida:

: ${mystr? not defined}

Cuidado aqui que usei ':' para não ter o conteúdo de $ mystr executado como um comando, caso esteja definido.


fonte
Eu não sabia sobre o? sintaxe para variáveis ​​BASH. Essa é uma boa pegadinha.
Swiss,
Isso funciona também com referências variáveis ​​indiretas. Este passa: var= ; varname=var ; : ${!varname?not defined}e isso termina: varname=var ; : ${!varname?not defined}. Mas é um bom hábito de usar set -uque faz o mesmo muito mais fácil.
ceving
11

Um resumo dos testes.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"
k107
fonte
Boas práticas no bash. Às vezes, caminhos de arquivo ter espaços, ou para proteger contra injeção de comando
K107
1
Eu acho ${var+x}que não é necessário, exceto se os nomes das variáveis tiverem espaços.
Anne van Rossum
Não apenas boas práticas; é necessário com [para obter resultados corretos. Se varé desactivado ou vazio, [ -n $var ]reduz-se a [ -n ]que tem estado de saída 0, enquanto que [ -n "$var" ]tem o esperado (e correcta) estado de saída de 1.
chepner
5

https://stackoverflow.com/a/9824943/14731 contém uma resposta melhor (que é mais legível e funciona com set -o nounsetativado). Funciona aproximadamente assim:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi
Gili
fonte
4

A maneira explícita de verificar se uma variável está sendo definida seria:

[ -v mystr ]
Felix Leipold
fonte
1
Não é possível encontrar isso em nenhuma das páginas de manual (para bash ou teste), mas funciona para mim. Alguma idéia de quais versões foram adicionadas?
Ash Berlin-Taylor
1
Está documentado em Expressões condicionais , referenciadas pelo testoperador no final da seção Bourne Shell Builtins . Não sei quando foi adicionado, mas não está na versão da Apple bash(com base em bash3.2.51), por isso é provavelmente um recurso 4.x.
Jonathan Leffler
1
Isso não funciona para mim GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). O erro é -bash: [: -v: unary operator expected. Por um comentário aqui , é necessário no mínimo bash 4.2 para funcionar.
Acumenus 02/04
Obrigado por verificar quando ficou disponível. Eu já o havia usado em vários sistemas, mas de repente não funcionou. Eu vim aqui por curiosidade e sim, é claro que a festança neste sistema é uma festança 3.x.
clacke
1

outra opção: a expansão "list array index" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

o único momento em que isso se expande para a cadeia vazia é quando fooestá desativado, para que você possa verificá-la com a cadeia condicional:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

deve estar disponível em qualquer versão do bash> = 3.0

Aaron Davies
fonte
1

não derramar ainda mais esta bicicleta, mas queria acrescentar

shopt -s -o nounset

é algo que você pode adicionar à parte superior de um script, que causará erro se as variáveis ​​não forem declaradas em nenhum lugar do script. A mensagem que você vê é unbound variable, mas como outros mencionam, ela não captura uma sequência vazia ou um valor nulo. Para garantir que qualquer valor individual não esteja vazio, podemos testar uma variável à medida que ela é expandida ${mystr:?}, também conhecida como expansão de cifrão, o que causaria um erro parameter null or not set.

Sacrilicious
fonte
1

O Manual de Referência do Bash é uma fonte autorizada de informações sobre o bash.

Aqui está um exemplo de teste de uma variável para ver se ela existe:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(Da seção 6.3.2 .)

Observe que o espaço em branco após a abertura [e antes da não] é opcional .


Dicas para usuários do Vim

Eu tinha um script que tinha várias declarações da seguinte maneira:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

Mas eu queria que eles se submetessem a quaisquer valores existentes. Então, eu os reescrevi para ficar assim:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

Consegui automatizar isso no vim usando um regex rápido:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

Isso pode ser aplicado selecionando as linhas relevantes visualmente e digitando :. A barra de comandos é preenchida previamente com :'<,'>. Cole o comando acima e pressione Enter.


Testado nesta versão do Vim:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Os usuários do Windows podem querer finais de linha diferentes.

funroll
fonte
0

Aqui está o que eu acho que é uma maneira muito mais clara de verificar se uma variável está definida:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Use-o da seguinte maneira:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi
suíço
fonte
1
Escrever uma função não é uma má idéia; invocar grepé horrível. Observe que bashsuporta ${!var_name}como uma maneira de obter o valor de uma variável cujo nome é especificado em $var_name, portanto, name=value; value=1; echo ${name} ${!name}produz value 1.
Jonathan Leffler
0

Uma versão mais curta para testar variável indefinida pode ser simplesmente:

test -z ${mystr} && echo "mystr is not defined"
nfleury
fonte
-3

conjunto de chamadas sem argumentos .. ele gera todos os vars definidos ..
os últimos na lista seriam os definidos em seu script ..
para que você possa canalizar sua saída para algo que possa descobrir o que está definido e o que não está

Aditya Mukherji
fonte
2
Eu não votei contra isso, mas é uma espécie de marreta quebrar uma noz pequena.
Jonathan Leffler
Na verdade, eu gosto mais desta resposta do que a resposta aceita. Está claro o que está sendo feito nesta resposta. A resposta aceita é vacilante como o inferno.
Swiss
Parece que esta resposta é mais um efeito colateral da implementação de "conjunto" do que algo que é desejável.
Jonathan Morgan