Inverter variável booleana

26

Eu quero tentar um script simples

flag=false
while !$flag
do
   read x
   if [ "$x" -eq "true" ]
   then
     flag=true
   fi
   echo "${x} : ${flag}"
done

Mas quando eu o executar, se eu digitar true, verei isso x="true"e flag="true", mas o ciclo não termina. O que há de errado com o script? Como posso inverter adequadamente uma variável booleana?

criador
fonte
THX! eu tenho agora
breedish

Respostas:

21

Existem dois erros no seu script. A primeira é que você precisa de um espaço entre !e $flag, caso contrário, o shell procura um comando chamado !$flag. O segundo erro é -eqpara comparações de números inteiros, mas você o está usando em uma string. Dependendo do seu shell, você verá uma mensagem de erro e o loop continuará para sempre, porque a condição [ "$x" -eq "true" ]não pode ser verdadeira, ou todo valor não inteiro será tratado como 0 e o loop será encerrado se você digitar qualquer string (incluindo false) diferente de um número diferente de 0.

Enquanto ! $flagestiver correto, é uma má idéia tratar uma string como um comando. Funcionaria, mas seria muito sensível a alterações em seu script, pois você precisa garantir que isso $flagnunca pode ser outra coisa senão trueou false. Seria melhor usar uma comparação de cadeias aqui, como no teste abaixo.

flag=false
while [ "$flag" != "true" ]
do
   read x
   if [ "$x" = "true" ]
   then
     flag=true
   fi
   echo "${x} : ${flag}"
done

Provavelmente existe uma maneira melhor de expressar a lógica que você procura. Por exemplo, você pode fazer um loop infinito e interrompê-lo quando detectar a condição de finalização.

while true; do
  read -r x
  if [ "$x" = "true" ]; then break; fi
  echo "$x: false"
done
Gilles 'SO- parar de ser mau'
fonte
Com o [builtin de ksh, [ x -eq y ]retorne true se a xexpressão aritmética resolver para o mesmo número que a yexpressão aritmética, por exemplo x=2 true=1+1 ksh -c '[ x -eq true ] && echo yes', produzirá sim.
Stéphane Chazelas
10

Embora não haja variáveis ​​booleanas no Bash, é muito fácil imitá-las usando a avaliação aritmética.

flag= # False
flag=0 # False
flag=1 # True (actually any integer number != 0 will do, but see remark below about toggling)
flag="some string" # Maybe False (make sure that the string isn't interpreted as a number)

if ((flag)) # Test for True
then
  : # do something
fi

if ! ((flag)) # Test for False
then
  : # do something
fi

flag=$((1-flag)) # Toggle flag (only works when flag is either empty or unset, 0, 1, or a string which doesn't represent a number)

Isso também funciona no ksh. Eu não ficaria surpreso se ele funcionasse em todos os shells compatíveis com POSIX, mas não tivesse verificado o padrão.

ack
fonte
11
Funciona ! ! ((val))no Bash, como em C ou C ++? Por exemplo, se valfor 0, permanecerá 0 depois ! !. Se valfor 1, permanece 1 depois ! !. Se valfor 8, é reduzido para 1 depois ! !. Ou preciso fazer outra pergunta?
11
-1 No POSIX sh, autônomo ((..)) é indefinido; remova a referência a ele
LinuxSecurityFreak
Vlastimil a pergunta especificamente é sobre bash - não posix shell. Por que votar quando a pergunta não é sobre esse tópico?
Nick Touro
6

Se você tiver certeza absoluta de que a variável conterá 0 ou 1, use o operador igual a XOR bit a bit para alternar entre os dois valores:

$ foo=0
$ echo $foo
0
$ ((foo ^= 1))
$ echo $foo
1
$ ((foo ^= 1))
$echo $foo
0
user971217
fonte
2

Isso funciona para mim:

flag=false
while [[ "$flag" == "false" ]]
do
   read x
   if [[ "$x" == "true" ]]
   then
     flag=true
   fi
   echo "${x} : ${flag}"
done

Mas, na verdade, o que você deve fazer é substituir o seu whilepor:

while ! $flag
ДМИТРИЙ МАЛИКОВ
fonte
0

Não há conceito de uma variável booleana no shell.
Variáveis do shell só poderia ser text(uma string), e, em alguns casos, esse texto pode ser interpretado como um inteiro ( 1, 0xa, 010, etc.).

Portanto, a flag=truenão implica em veracidade ou falsidade para a concha.

Corda

O que poderia ser feito é uma comparação de cadeias [ "$flag" == "true" ]ou usar o conteúdo da variável em algum comando e verificar suas conseqüências , como execute true(porque há um executável chamado truee um chamado false) como um comando e verifique se o código de saída desse comando é zero (bem sucedido).

$flag; if [ "$?" -eq 0 ]; then ... fi

Ou mais curto:

if "$flag"; then ... fi

Se o conteúdo de uma variável for usado como um comando, a !poderá ser usado para negar o status de saída do comando, se existir um espaço entre ambos ( ! cmd), como em:

if ! "$flag"; then ... fi

O script deve mudar para:

flag=false
while ! "$flag" 
do
   read x
   if [ "$x" == "true" ]
   then
     flag=true
   fi
   echo "${x} : ${flag}"
done

Inteiro

Use valores numéricos e expansões aritméticas .

Nesse caso, o código de saída de $((0))é 1e o código de saída de $((1))é 0.
No bash, ksh e zsh, a aritmética pode ser realizada dentro de um ((..))(observe que a partida $está faltando).

flag=0; if ((flag)); then ... fi

Uma versão portátil deste código é mais complicada:

flag=0; if [ "$((flag))" -eq 0 ]; then ... fi      # test for a number
flag=0; if [ "$((flag))"  == 0 ]; then ... fi      # test for the string "0"

No bash / ksh / zsh, você pode fazer:

flag=0    
while ((!flag)) 
do
   read x
   [ "$x" == "true" ] && flag=1
   echo "${x} : ${flag}"
done

alternativamente

Você pode "Inverter uma variável booleana" (desde que contenha um valor numérico) como:

((flag=!flag))

Isso mudará o valor de flagpara ou 0ou 1.


Nota : Verifique se há erros em https://www.shellcheck.net/ antes de postar seu código como uma pergunta, muitas vezes o suficiente para encontrar o problema.

Isaac
fonte
0

untilé a abreviação de while !(e until, ao contrário de !Bourne), então você pode fazer:

flag=false
until "$flag"
do
   IFS= read -r x
   if [ "$x" = true ]
   then
     flag=true
   fi
   printf '%s\n' "$x : $flag"
done

Qual deve ser o mesmo que:

flag=false
while ! "$flag"
do
   IFS= read -r x
   if [ "$x" = true ]
   then
     flag=true
   fi
   printf '%s\n' "$x : $flag"
done

Você também pode escrever como:

until
   IFS= read -r x
   printf '%s\n' "$x"
   [ "$x" = true ]
do
   continue
done

A parte entre until/ whilee donão precisa ser um único comando.

!é uma palavra-chave na sintaxe do shell POSIX. Ele precisa ser delimitado, um token separado do que segue e ser o primeiro token que precedeu um pipeline ( ! foo | barnega o pipeline e foo | ! baré inválido, embora alguns shells o aceitem como uma extensão).

!trueé interpretado como o !truecomando que é improvável que exista. ! trueDeveria trabalhar. !(true)deve funcionar em shells compatíveis com POSIX, pois !é delimitado por um (token, mas, na prática, não funciona em ksh/ bash -O extglobonde conflita com o !(pattern)operador glob estendido nem o zsh (exceto na emulação sh) onde conflita glob(qualifiers)com bareglobquale glob(group)sem.

Stéphane Chazelas
fonte
-1
[ ${FOO:-0} == 0 ] && FOO=1 || FOO=0

ou até:

[ ${FOO:-false} == false ] && FOO=true || FOO=false

deve fazer o trabalho

slaxor
fonte