Prawy do lewego?

prawy-do-lewegoPowerShell stara się być pomocny. Zrobi wszystko co w jego mocy, by kod, który w nim „wklepiemy” zadziałał. To właśnie dlatego możemy w nim porównywać rzeczy z pozoru nieporównywalne. Czy to będzie data porównywana z ciągiem znaków, ciąg znaków porównywany z liczbą czy liczba porównywana z wartością logiczną – wszystko „przejdzie”, tak długo jak konwersja (nie koniecznie zgodna z naszą intuicją) jest możliwa. Na ogół PowerShell stosować będzie w takiej sytuacji zasadę „prawy do lewego”. Ale jest od tej zasady wyjątek.

Zanim przejdziemy do konkretów winienem Wam kilka słów wstępu. To będzie kolejna (która to już, czwarta?) próba „reaktywacji” tego bloga. Tym razem jednak podszedłem do sprawy metodycznie i z wyprzedzeniem zaplanowałem kilka(naście) postów, mieszając tematy bardziej skomplikowane i te nieco prostsze. Ten w mojej ocenie zdecydowanie zalicza się do tych drugich.

Zasada ogólna: prawy do lewego

W PowerShellu często porównywać będziemy obiekty różnych typów: po jednej stronie ciąg znaków, po drugiej zwykła liczba. Z jeden strony obiekt typu DateTime, z drugiej ciąg znaków. Przy każdym takim porównaniu PowerShell stosuje prostą zasadę: typ zmiennej po lewej stronie decyduje, do jakiego typu „równane” będą obie strony:

(Get-Date) -gt '2020-02-20'

Ciąg znaków po lewej zostanie przekonwertowany do obiektu DateTime. Na wyjściu uzyskamy więc $true – jest kwiecień, luty pozostaje jedynie odległym wspomnieniem.

Zasada ta oznacza, że czasem musimy odwrócić operację porównania. Dla przykładu: wynik polecenia Import-Csv zawierać będzie jedynie ciągi znaków. Jeśli więc chcemy wybrać elementy z pliku, dla których właściwość jest większa/ mniejsza niż założona liczba, nie możemy porównywać wprost. Musimy porównanie odwrócić, by obie strony zostały przekonwertowane do liczb:

$plikCsv = @'
Nazwa,Liczba
Jedenaście,11
Dwa,2
Siedem,7
Osiem,8
'@ | ConvertFrom-Csv
$plikCsv | Where-Object { $_.Liczba -lt 5}
# Zwróci jedenaście - porównujemy ciągi znaków...
$plikCsv | Where-Object { 5 -gt $_.Liczba }
# Zwróci jedynie 2 - porównujemy liczby

Inny sposób to jawne przekonwertowanie do interesującego typu: jeśli wymusimy typ po lewej stronie, to siłą rzeczy – prawa zostanie do niego przekonwertowana.

Wyjątek od reguły

Sytuacja wygląda nieco inaczej, gdy skorzystamy z grupy operatorów przeznaczonych do pracy na ciągach znaków: match, like i wszystkie operatory pokrewne (takie jak cmatch, czy notmatch).

Przyczyna jest dość oczywista: operator służący do porównywania ciągów znaków sprawdza się jedynie wtedy, gdy porównujemy ciągi znaków. W przypadku tego operatora nie ma znaczenia, czy po lewej stronie znajdzie się obiekt typu DateTime, liczba, wartość logiczna:

# Wszystkie wyrażenia są prawdziwe...
(Get-Date -Day 27) -match '.*27.*'
0x30 -like '*8'
@{} -like '*hashtable*'

Problem w tym, że znając zasadę ogólną możemy zyskać dziwne wyniki. Dla przykładu: operator like wykorzystywany bez znaków wieloznacznych teoretycznie nie różni się niczym od operatora eq. W praktyce jednak niemal identyczne wyrażenie może dać zupełnie inny wynik, szczególnie gdy typ zmiennej jest nam nieznany (a w wyniku konwersji uzyskamy nieoczekiwane wyniki):

$obiekt = [PSCustomObject]@{
Foo = [PSCustomOBject]@{
Bar = $false
}
}
# wymuszamy porównanie ciągów znaków - dostajemy $true
$obiekt.Foo.Bar -like 'False'
# pozostawiamy sprawę konwersji "prawy do lewego" - dostajemy $false
$obiekt.Foo.Bar -eq 'False'

Warto o tym pamiętać, gdy trafimy na wyrażenie, które z nieznanego nam powodu działania przeciwnie do tego, czego byśmy oczekiwali!

~ - autor: Bartek Bielawski w dniu 6 kwietnia, 2020.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s

 
%d blogerów lubi to: