Czy jest jakaś istotna różnica przy porównywaniu zmiennej jako łańcucha lub jako wartości int

22

Z ciekawości, porównując zmienną bash (jej wartość jest an integer), można przetestować ją na pewnej predefiniowanej wartości zadeklarowanej jako intlub string.

Przykładowy skrypt :

#!/bin/bash
f1()
{
        [ "$1" == "1" ] && echo "$FUNCNAME: \"1\" compared as string"
}

f2()
{
        [[ "$1" -eq 1 ]] && echo "$FUNCNAME: \"1\" compared as int"
}

f1 $1
f2 $1

Wyjście :

$  ./param.sh 1
f1: "1" compared as string
f2: "1" compared as int

i

$  ./param.sh blah
$

Obie funkcje zachowują się w ten sam sposób, więc zastanawiam się, czy istnieje preferowany sposób sprawdzania zmiennej całkowitej? Wybrałbym sprawdzanie intkontra intbardziej rygorystyczne, ale zastanawiam się, czy robią to jakieś wady string?

W tym przypadku f2()jest również bardziej rygorystyczny w porównaniu, tzn. Przekazanie wartości dziesiętnej spowoduje jej uszkodzenie f1(), ale nie spowoduje problemu.

fduff
źródło
Zauważ, że Bash tak naprawdę nie ma typu danych Integer. Możesz po prostu zasugerować Bashowi, aby traktował String jako liczbę całkowitą.
helpermethod

Odpowiedzi:

18

Tak, wiele różnic. Na przykład =sprawdza dokładność równości łańcucha, ale -eqocenia arytmetycznie oba wyrażenia przed sprawdzeniem równości:

$ [ " 1 " -eq 1 ] && echo equal || echo not
equal
$ [ " 1 " = 1 ] && echo equal || echo not
not

$ [ +1 -eq 1 ] && echo equal || echo not
equal
$ [ +1 = 1 ] && echo equal || echo not
not

$ [ "0+1" -eq 1 ] && echo equal || echo not
equal
$ [ "0+1" = 1 ] && echo equal || echo not
not

Ponadto pusty ciąg znaków jest liczbowo równy zero:

$ [ "" -eq 0 ] && echo equal || echo not
equal
$ [ "" = 0 ] && echo equal || echo not
not

Zupełnie inna klasa różnic pojawia się, gdy weźmiesz operatory porównania - biorąc pod uwagę <vs -lt, na przykład:

$ [[ 2 -lt 10 ]] && echo less || echo not
less
$ [[ 2 < 10 ]] && echo less || echo not
not

Wynika to z faktu, że ciąg „2” jest alfabetycznie po ciągu „10” (ponieważ 1 występuje przed 2), ale liczba „2” jest liczbowo mniejsza niż liczba „10”.

godlygeek
źródło
2
Nie zapominaj, że są też (( ... ))operacje numeryczne. (( " 1 " == 1 )) && echo yes || echo nowyniki wyes
Patrick
7

Porównanie liczb całkowitych i ciągów staje się najbardziej znaczące, gdy porównujesz większe lub mniejsze niż:

#!/bin/bash

eleven=11
nine=9

[[ $nine < $eleven ]] && echo string   # fail

[[ "$nine" -lt "$eleven" ]] && echo integer # pass

Pierwszy nie powiedzie się, ponieważ 9 pojawia się po 11, gdy jest posortowane leksykograficznie.

Zauważ, że użycie cudzysłowów nie określa, czy porównujesz ciągi, czy liczby, operator tak. Możesz dodać lub usunąć powyższe cytaty, nie robi to żadnej różnicy. Bash przechwytuje niezdefiniowane zmienne w podwójnych nawiasach, więc cudzysłowy nie są konieczne. Używanie cudzysłowów z pojedynczymi nawiasami do testów numerycznych nie uratuje cię, ponieważ:

[ "" -lt 11 ]

i tak jest błędem („wymagane wyrażenie całkowite”). Cytaty są skutecznym zabezpieczeniem przy porównywaniu ciągów w nawiasach pojedynczych:

[ "" \< 11 ]

Uwaga w podwójnych nawiasach, ale nie ""będzie .-eq 0== 0

Złotowłosa
źródło
1
W bash nie jest absolutnie konieczne cytowanie zmiennych w podwójnych nawiasach: wbudowany [[jest wystarczająco inteligentny, aby zapamiętać, gdzie są zmienne, i nie da się oszukać pustymi zmiennymi. Pojedyncze nawiasy kwadratowe ( [) nie mają tej funkcji i wymagają cudzysłowów.
glenn jackman
@glennjackman Nie zauważył tego. [[ -lt 11 ]]jest błędem, ale nothing=; [[ $nothing -lt 11 ]]nie jest. Trochę przerobiłem ostatni akapit.
goldilocks,
2

Oprócz tego, co zostało powiedziane.
Porównywanie pod kątem równości jest szybsze z liczbami, chociaż w skryptach powłoki rzadko zdarza się potrzeba szybkich obliczeń.

$ b=234
$ time for ((a=1;a<1000000;a++)); do [[ $b = "234" ]]; done

real    0m13.008s
user    0m12.677s
sys 0m0.312s

$ time for ((a=1;a<1000000;a++)); do [[ $b -eq 234 ]]; done

real    0m10.266s
user    0m9.657s
sys 0m0.572s
Emmanuel
źródło
Biorąc pod uwagę, że robią różne rzeczy, powiedziałbym, że wydajność jest nieistotna - musisz użyć tego, który robi to, co chcesz.
godlygeek
@godlygeek Porównywanie równości zmiennej można osiągnąć w obie strony. „-eq” jest szybsze.
Emmanuel,
Testują różne definicje równości. Jeśli chcesz odpowiedzieć na pytanie „Czy ta zmienna zawiera dokładny ciąg 123”, możesz tylko użyć =, ponieważ użycie -eqpasowałoby również do „+123”. Jeśli chcesz wiedzieć „Czy ta zmienna, gdy jest oceniana jako wyrażenie arytmetyczne, porównaj równe 123”, możesz tylko użyć -eq. Jedyny raz widzę, gdzie programista nie dbałby o to, która definicja równości została zastosowana, kiedy wie, że zawartość zmiennej jest ograniczona do określonego wzorca wcześniej.
godlygeek
@godlygeek ciekawe, pytanie dotyczyło porównania równości liczb tak, jakby były łańcuchami, czy pasuje to w przypadku zmiennych ograniczonych przed czasem do określonego wzorca?
Emmanuel,
Twój przykład ( b=234) pasuje do tego wzorca - wiesz, że nie jest to +234 lub „234” lub „233 + 1”, ponieważ sam go przypisałeś, więc wiesz, że porównywanie go jako łańcucha i liczby jest jednakowo ważne. Ale skrypt OP, ponieważ pobiera dane wejściowe jako argument wiersza poleceń, nie ma tego ograniczenia - rozważ nazwanie go jako ./param.sh 0+1lub./param.sh " 1"
godlygeek