Odwróć zmienną logiczną

26

Chcę wypróbować prosty skrypt

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

Ale kiedy go uruchomię, jeśli napiszę true, zobaczę x="true"i flag="true", ale cykl się nie kończy. Co jest nie tak ze skryptem? Jak poprawnie odwrócić zmienną boolowską?

rozmnażać się
źródło
dzięki! mam to teraz
rozmnażaj

Odpowiedzi:

21

W twoim skrypcie są dwa błędy. Pierwszym jest to, że potrzebujesz odstępu między !i $flag, w przeciwnym razie powłoka szuka polecenia o nazwie !$flag. Drugi błąd dotyczy -eqporównań liczb całkowitych, ale używasz go w ciągu znaków. W zależności od powłoki albo zobaczysz komunikat o błędzie, a pętla będzie trwała wiecznie, ponieważ warunek [ "$x" -eq "true" ]nie może być spełniony, lub każda wartość nie będąca liczbą całkowitą będzie traktowana jako 0, a pętla zakończy się, jeśli wpiszesz dowolny ciąg (w tym false) inna niż liczba różna od 0.

Chociaż ! $flagjest poprawne, złym pomysłem jest traktowanie łańcucha jako polecenia. To by działało, ale byłoby bardzo wrażliwe na zmiany w twoim skrypcie, ponieważ musisz upewnić się, że $flagnigdy nie będzie to nic innego jak truelub false. Lepiej byłoby użyć porównania ciągów tutaj, jak w poniższym teście.

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

Prawdopodobnie istnieje lepszy sposób na wyrażenie logiki, której szukasz. Na przykład możesz utworzyć nieskończoną pętlę i przerwać ją, gdy wykryjesz warunek zakończenia.

while true; do
  read -r x
  if [ "$x" = "true" ]; then break; fi
  echo "$x: false"
done
Gilles „SO- przestań być zły”
źródło
Z [wbudowanego polecenia z ksh, [ x -eq y ]return true jeśli xarytmetycznych ustąpieniu ekspresyjnych do takiej samej liczby jako ywyrażenia arytmetycznego tak na przykład x=2 true=1+1 ksh -c '[ x -eq true ] && echo yes'would Tak Wyjście.
Stéphane Chazelas
10

Chociaż w Bash nie ma zmiennych boolowskich, bardzo łatwo jest je emulować za pomocą obliczeń arytmetycznych.

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)

Działa to również w ksh. Nie zdziwiłbym się, gdyby działał we wszystkich powłokach zgodnych z POSIX, ale nie sprawdził standardu.

ack
źródło
1
Czy ! ! ((val))działa w Bash, podobnie jak w C lub C ++? Na przykład, jeśli valjest 0, pozostaje 0 po ! !. Jeśli valjest 1, pozostaje 1 po ! !. Jeśli valma wartość 8, jest zmniejszany do 1 po ! !. Czy muszę zadać kolejne pytanie?
1
-1 | W POSIX sh samodzielny ((..)) jest niezdefiniowany; usuń odniesienie do niego
LinuxSecurityFreak
Pytanie Vlastimil dotyczy konkretnie bash - nie powłoki POSIX. Po co głosować w dół, gdy pytanie nie dotyczy tego tematu?
Nick Bull
6

Jeśli możesz być absolutnie pewien, że zmienna będzie zawierać 0 lub 1, możesz użyć bitowego operatora XOR do przełączania między dwiema wartościami:

$ foo=0
$ echo $foo
0
$ ((foo ^= 1))
$ echo $foo
1
$ ((foo ^= 1))
$echo $foo
0
użytkownik971217
źródło
2

To działa dla mnie:

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

Ale tak naprawdę powinieneś zrobić to while:

while ! $flag
ДМИТРИЙ МАЛИКОВ
źródło
0

W powłoce nie ma koncepcji zmiennej boolowskiej.
Zmienne powłoki mogą być tylko text(ciągiem), a w niektórych przypadkach tekst ten może być interpretowany jako liczba całkowita ( 1,0xa , 010, itd.).

Dlatego a flag=true nie sugeruje żadnej prawdziwości ani fałszu skorupie.

Strunowy

To, co można zrobić, to albo porównać ciąg znaków, [ "$flag" == "true" ]albo użyć zawartości zmiennej w niektórych poleceniach i sprawdzić ich konsekwencje , na przykład wykonać true(ponieważ istnieje zarówno wywołany plik wykonywalny, jak truei jeden wywołany false) jako polecenie i sprawdzić, czy kod wyjścia tego polecenia wynosi zero (odnoszący sukcesy).

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

Lub krócej:

if "$flag"; then ... fi

Jeśli zawartość zmiennej jest używana jako polecenie, !można użyć do negowania statusu wyjścia polecenia, jeśli między nimi ( ! cmd) istnieje spacja , jak w:

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

Skrypt powinien zmienić się na:

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

Liczba całkowita

Użyj wartości liczbowych i rozszerzeń arytmetycznych .

W takim przypadku kod wyjścia $((0))to 1i kod wyjścia $((1))to 0.
W bash, ksh i zsh arytmetyka może być przeprowadzona wewnątrz ((..))(zauważ, że $brakuje początku).

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

Przenośna wersja tego kodu jest bardziej skomplikowana:

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

W bash / ksh / zsh możesz zrobić:

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

Alternatywnie

Możesz „Odwrócić zmienną logiczną” (pod warunkiem, że zawiera wartość liczbową) jako:

((flag=!flag))

To zmieni wartość flagna jedną 0lub 1.


Uwaga : przed opublikowaniem kodu jako pytania sprawdź błędy w https://www.shellcheck.net/ , wiele razy to wystarczy, aby znaleźć problem.

Izaak
źródło
0

untiljest skrótem od while !(i until, w przeciwieństwie do !Bourne'a), więc możesz zrobić:

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

Który powinien być taki sam jak:

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

Możesz również napisać to jako:

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

Część pomiędzy until/ whilei donie musi być pojedynczym poleceniem.

!jest słowem kluczowym w składni powłoki POSIX. Należy go oddzielić, token oddzielić od tego, co następuje, i być pierwszym tokenem poprzedzającym potok ( ! foo | barneguje potok i foo | ! barjest nieprawidłowy, chociaż niektóre powłoki akceptują go jako rozszerzenie).

!truejest interpretowane jako !truepolecenie, które prawdopodobnie nie istnieje. ! truepowinno działać. !(true)powinien działać w powłokach zgodnych z POSIX, ponieważ !jest on ograniczony, ponieważ następuje po nim (token, ale w praktyce nie działa w ksh/ bash -O extglobgdzie jest w konflikcie z !(pattern)rozszerzonym operatorem glob ani zsh (z wyjątkiem emulacji sh), gdzie jest w konflikcie glob(qualifiers)z bareglobquali glob(group)bez.

Stéphane Chazelas
źródło
-1
[ ${FOO:-0} == 0 ] && FOO=1 || FOO=0

lub nawet:

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

powinien wykonać pracę

slaxor
źródło