Como posso testar se uma variável está vazia ou contém apenas espaços?

240

A seguinte sintaxe do bash verifica se paramnão está vazia:

 [[ !  -z  $param  ]]

Por exemplo:

param=""
[[ !  -z  $param  ]] && echo "I am not zero"

Sem saída e tudo bem.

Mas quando paramestá vazio, exceto por um (ou mais) caracteres de espaço, o caso é diferente:

param=" " # one space
[[ !  -z  $param  ]] && echo "I am not zero"

"Eu não sou zero" é emitido.

Como posso alterar o teste para considerar variáveis ​​que contêm apenas caracteres de espaço como vazias?

maihabunash
fonte
4
De man test: -z STRING - the length of STRING is zero. Se você deseja remover todos os espaços $param, use${param// /}
dchirikov
6
WOW não existe uma trim()função simples embutida em qualquer * nix? Tantos hacks diferentes para conseguir algo tão simples ...
ADTC
6
@ADTC $ (sed 's / ^ \ s + | \ s + $ // g' <<< $ string) parece bastante simples para mim. Por que reinventar a roda?
21136 jbowman
3
Porque poderia ser mais brilhante
Inversus
1
Apenas observando que [[ ! -z $param ]]é equivalente a test ! -z $param.
Noah Sussman

Respostas:

292

Primeiro, observe que o -zteste é explicitamente para:

o comprimento da corda é zero

Ou seja, uma string contendo apenas espaços não deve ser verdadeira abaixo -z, porque tem um comprimento diferente de zero.

O que você deseja é remover os espaços da variável usando a expansão do parâmetro de substituição de padrão :

[[ -z "${param// }" ]]

Isso expande a paramvariável e substitui todas as correspondências do padrão (um único espaço) por nada; portanto, uma sequência que possui apenas espaços será expandida para uma sequência vazia.


O âmago da questão de como isso funciona é que ${var/pattern/string}substitui a primeira partida mais longa de patterncom string. Quando patterncomeça com /(como acima), substitui todas as correspondências. Como a substituição está vazia, podemos omitir o final /e o stringvalor:

$ {parâmetro / padrão / string}

O padrão é expandido para produzir um padrão, assim como na expansão do nome do arquivo. O parâmetro é expandido e a correspondência mais longa do padrão em relação ao seu valor é substituída por string . Se o padrão começar com '/', todas as correspondências do padrão serão substituídas por uma sequência . Normalmente, apenas a primeira partida é substituída. ... Se a sequência for nula, as correspondências do padrão serão excluídas e o padrão / a seguir poderá ser omitido.

Depois de tudo isso, acabamos ${param// }excluindo todos os espaços.

Observe que, embora esteja presente em ksh(onde ele se originou) zshe bash, essa sintaxe não é POSIX e não deve ser usada em shscripts.

Michael Homer
fonte
use sedeles disseram ... apenas echoa variável que eles disseram ... #
mikezter 15/05
1
Hmm. Se é ${var/pattern/string}assim, não deveria ser [[ -z ${param/ /} ]]o espaço entre as barras para ser o padrão e nada depois para ser a corda?
Jesse Chisholm
@JesseChisholm "Como a substituição está vazia, podemos omitir o valor final / e a string"; "Se a sequência for nula, as correspondências do padrão serão excluídas e o / padrão a seguir poderá ser omitido."
Michael Homer
6
@MichaelHomer A resposta com [[ -z "${param// }" ]]certeza parece patternvazia e o replacement stringespaço é único. Ah! Agora vejo que if pattern begins with a slashperdi essa parte. então o padrão é / e a string é deixada de lado e, portanto, vazia. Obrigado.
Jesse Chisholm
nota bash: se estiver sendo executado no conjunto -o nounset (set -u) e o parâmetro estiver desativado (em vez de nulo ou preenchido com espaço), isso gerará um erro variável não vinculado. (set + u; .....) isentaria este teste.
precisa
31

A maneira mais fácil de verificar se uma string contém apenas caracteres em um conjunto autorizado é testar a presença de caracteres não autorizados. Portanto, em vez de testar se a cadeia contém apenas espaços, teste se a cadeia contém algum caractere que não seja o espaço. No bash, ksh ou zsh:

if [[ $param = *[!\ ]* ]]; then
  echo "\$param contains characters other than space"
else
  echo "\$param consists of spaces only"
fi

"Consiste apenas em espaços" inclui o caso de uma variável vazia (ou não definida).

Você pode testar qualquer caractere de espaço em branco. Use [[ $param = *[^[:space:]]* ]]para usar configurações de localidade ou qualquer lista explícita de caracteres de espaço em branco que você deseja testar, por exemplo, [[ $param = *[$' \t\n']* ]]para testar espaço, tabulação ou nova linha.

Combinar uma string com um padrão com =inside [[ … ]]é uma extensão ksh (também presente no bash e no zsh). Em qualquer estilo Bourne / POSIX, você pode usar a caseconstrução para combinar uma sequência com um padrão. Observe que os padrões de shell padrão são usados !para negar um conjunto de caracteres, e não ^como na maioria das sintaxes de expressão regular.

case "$param" in
  *[!\ ]*) echo "\$param contains characters other than space";;
  *) echo "\$param consists of spaces only";;
esac

Para testar caracteres em branco, a $'…'sintaxe é específica para ksh / bash / zsh; você pode inserir esses caracteres no seu script literalmente (observe que uma nova linha terá que estar entre aspas, pois a barra invertida + a nova linha se expandirá para nada) ou gerá-las, por exemplo

whitespace=$(printf '\n\t ')
case "$param" in
  *[!$whitespace]*) echo "\$param contains non-whitespace characters";;
  *) echo "\$param consists of whitespace only";;
esac
Gilles
fonte
Isso é quase excelente - mas, até o momento, não responde à pergunta conforme solicitado. Atualmente, ele pode dizer a diferença entre uma variável de shell não configurada ou vazia e uma que contém apenas espaços. Se você aceitar minha sugestão, adicionará o formulário de expansão param ainda não convertido por qualquer outra resposta que testa explicitamente uma variável shell para ser definida - quero dizer ${var?not set}- passar nesse teste e sobreviver garantiria que uma variável correspondida por você *) caseefetuaria uma definição definitiva. resposta, por exemplo.
mikeserv
Correção - este, na verdade, seria responder à pergunta pedido inicialmente, mas desde então tem sido editado de tal forma que muda fundamentalmente o significado da pergunta e, portanto, invalida a sua resposta.
mikeserv
Você precisa [[ $param = *[!\ ]* ]]entrar mksh.
Stéphane Chazelas
20

POSIXly:

case $var in
  (*[![:blank:]]*) echo '$var contains non blank';;
  (*) echo '$var contains only blanks or is empty or unset'
esac

Para diferenciar entre em branco , não em branco , vazio e não definido :

case ${var+x$var} in
  (x) echo empty;;
  ("") echo unset;;
  (x*[![:blank:]]*) echo non-blank;;
  (*) echo blank
esac

[:blank:]é para caracteres de espaçamento horizontal (espaço e tabulação em ASCII, mas provavelmente há mais alguns no seu código do idioma; alguns sistemas incluirão o espaço sem quebra (quando disponível), outros não). Se você quiser caracteres de espaçamento vertical também (como nova linha ou feed de formulário), substitua [:blank:]por [:space:].

Stéphane Chazelas
fonte
O POSIXly é ideal porque funcionará com o traço (sem dúvida melhor).
Ken afiada
zshparece ter problema com [![:blank:]], aumento :bé modificador inválido. In she kshemular, evento de aumento não encontrado para[
cuonglm
@cuonglm, o que você tentou? var=foo zsh -c 'case ${var+x$var} in (x*[![:blank:]]*) echo x; esac'está bem para mim. O evento não encontrado seria apenas para shells interativos.
Stéphane Chazelas
@ StéphaneChazelas: Ah, certo, tentei com conchas interativas. O :bmodificador inválido é um pouco estranho.
cuonglm
1
@FranklinYu, no OS / X shé bashconstruído com --enable-xpg-echo-defaulte de --enable-strict-posix-defaultmodo a ser compatível com Unix (que envolve a echo '\t'saída de uma guia).
Stéphane Chazelas
4

O único motivo restante para escrever um script de shell, em vez de um script em uma boa linguagem de script, é se a portabilidade extrema é uma preocupação primordial. O legado /bin/shé a única coisa que você pode ter certeza, mas o Perl, por exemplo, tem mais probabilidade de estar disponível em várias plataformas do que o Bash. Portanto, nunca escreva scripts de shell que usem recursos que não sejam verdadeiramente universais - e lembre-se de que vários fornecedores proprietários de Unix congelaram seu ambiente de shell antes do POSIX.1-2001 .

Existe uma maneira portátil de fazer esse teste, mas você precisa usar tr:

[ "x`printf '%s' "$var" | tr -d "$IFS"`" = x ]

(O valor padrão de $IFS, convenientemente, é um espaço, uma guia e uma nova linha.)

(O printfpróprio built-in é muito portátil, mas confiar nele é muito menos complicado do que descobrir qual a sua variante echo).

zwol
fonte
1
Isso é um pouco complicado. A maneira portátil usual de testar se uma string contém determinados caracteres está comcase .
Gilles
@ Gilles Eu não acho que é possível escrever um caseglobo para detectar uma string que está vazia ou que contém apenas caracteres em branco. (Seria fácil com uma expressão regular, mas casenão faz regexps A construção em sua resposta não vai funcionar se você se preocupa com novas linhas..)
Zwol
1
Dei espaços como exemplo na minha resposta, porque era isso que a pergunta fazia. É igualmente fácil para testar caracteres em branco: case $param in *[![:space:]]*) …. Se você deseja testar os IFScaracteres, pode usar o padrão *[$IFS]*.
Gilles
1
@mikeserv Em um script realmente muito defensivo, você define explicitamente o IFS <space><tab><newline>no início e nunca mais o toca. Eu pensei que seria uma distração.
zwol 29/07
1
Em relação às variáveis ​​nos padrões, acho que funciona em todas as versões Bourne e em todos os shells POSIX; exigiria código extra para detectar padrões de caso e desativar a expansão de variáveis ​​e não consigo pensar em um motivo para fazê-lo. Eu tenho alguns exemplos disso .profileque foram executados por muitas conchas Bourne. É claro [$IFS]que não fará o que foi planejado se IFStiver certos valores não padrão (vazio (ou não definido), contendo ], começando com !ou ^).
Gilles
4
if [[ -n "${variable_name/[ ]*\n/}" ]]
then
    #execute if the the variable is not empty and contains non space characters
else
    #execute if the variable is empty or contains only spaces
fi
Guru
fonte
isso elimina qualquer caractere de espaço em branco, não apenas um espaço, certo? obrigado
Alexander Mills
0

Para testar se a variável está vazia ou contém espaços, você também pode usar este código:

${name:?variable is empty}
pratik
fonte
2
Acho que sua resposta foi negada porque o "ou conter espaços" não é verdadeiro.
Bernhard
tenha cuidado - isso mata o shell atual. Melhor colocá-lo em um (: $ {subshell?}). Além disso - que escreverá no stderr - você provavelmente deseja redirecionar. E o mais importante que avalia o valor real da variável, se não estiver definido ou nulo. Se houver um comando lá, você acabou de executá-lo. É melhor executar esse teste :.
mikeserv
como isso vai matar a concha. e u também pode testar isso, primeiro definir name=aaaa, em seguida, executar este comando echo ${name:?variable is empty}imprimirá valor do nome da variável e agora executar este comando echo ${name1:?variable is empty}ele irá imprimir SH: name1: variável está vazia
pratik
Sim - echo ${name:?variable is empty}avaliará o $namevalor não nulo ou matará o shell. Portanto, pela mesma razão que você não faz prompt > $randomvarnem deveria ${var?}- se houver um comando lá, provavelmente será executado - e você não sabe o que é. Um shell interativo não precisa sair - e é por isso que o seu não. Ainda assim, se você quiser ver alguns testes, existem muitos aqui. . Este não é tão longa ...
mikeserv
0

Para testar se uma variável está desmarcada, vazia (tem um valor nulo) ou contém apenas espaços:

 [ -z "${param#"${param%%[! ]*}"}" ] && echo "empty"

Explicação:

  • A expansão de ${param%%[! ]*}remove do primeiro não espaço até o fim.
  • Isso deixa apenas os espaços principais.
  • A próxima expansão remove os espaços iniciais da variável.
  • Se o resultado estiver vazio, a variável estava vazia para começar.
  • Ou, se a variável não estava vazia, a remoção dos espaços iniciais a deixava vazia.
  • Se o resultado estiver vazio -z, faça eco empty.

O acima é compatível com POSIX e é executado em qualquer descendente de Bourne.

Se você deseja estender isso para também as guias, inclua uma guia explícita:

 [ -z "${param#"${param%%[!     ]*}"}" ] && echo "empty"

ou use uma variável com os caracteres que você precisa remover:

 var=$' \t'   # in ksh, bash, zsh
 [ -z "${param#"${param%%[!$var]*}"}" ] && echo "empty"

ou você pode usar alguma "expressão de classe de caractere" POSIX (em branco significa espaço e guia):

 [ -z "${param#"${param%%[![:blank:]]*}"}" ] && echo "empty"

Mas isso falhará em mksh, lksh e nobre.

Isaac
fonte
-1

Tente o seguinte:

$ [[ -z \`echo $n\` ]] && echo zero

zero
Hugo
fonte