Switch: szwajcarski scyzoryk

PowerShell_Swiss_KnifePowerShell na przestrzeni lat ewoluował: zarówno jako platforma automatyzacji jak i język skryptowy. Dla mnie jednak, mimo licznych zmian, jedna konstrukcja w tym języku pozostaje „faworytem”, łącząc w sobie możliwości wyrażeń logicznych i pętli, oferując pracę z wieloma źródłami danych w sposób nie spotykany w innych, dostępnych w PowerShellu konstrukcji. Ta konstrukcja to switch: szwajcarski scyzoryk w PowerShellu.

Niemal każdy język, czy to programowania, czy to skryptowy, ma podobną konstrukcję. W jednym przypadku będzie to switch, w innym case. Zasada jest prosta: w oparciu o wartość zmiennej (bądź wyrażenia) wybieramy jedną z możliwych ścieżek. Możemy też podążyć kilkoma ścieżkami jednocześnie – wrócimy do tego nieco później. Możemy też zdefiniować ścieżkę „domyślną”, z której korzystać będziemy wtedy, gdy żadna ze zdefiniowanych nie będzie pasować do przekazanej zmiennej. Zacznijmy więc od tego, najbardziej oczywistego zastosowania switcha.

Ostrze pierwsze

Najprostszy przypadek to oczywiście przekazanie pojedynczej zmiennej do konstrukcji i analiza jej zawartości. Możemy analizować zarówno liczby jak i ciągi znaków przy tym podając te drugie możemy pominąć cudzysłów w przypadku ciągów znaków niezawierających elementów składni (takich jak klamry, cudzysłów, przecinek czy spacja):

switch ($prostaZmienna) {
ciągZnaków {
'string'
}
0 {
'zero'
}
default {
'coś innego'
}
}

Jeśli nasza prosta zmienna zawierać będzie `ciągZnaków` – na wyjściu uzyskamy `string`. Dla wartości 0 – `zero`. Dla każdej innej wartości na wyjściu uzyskamy `coś innego`.

Ostrze drugie

Co jednak, gdy zwykłe porównywanie ciągów znaków/ liczb nie wystarczy? Switch w PowerShellu pozwala nam definiować ścieżki w oparciu o wynik bloku skryptu. I jak to ma zwykle miejsce w przypadku tego rodzaju bloków skryptu, możemy skorzystać ze zmiennej „podłoga” a wynik przetworzony na wartość logiczną rozstrzygnie, czy podążymy daną „ścieżką”. Oczywiście, proste wyrażenia i bloki skryptu możemy mieszać:

switch ($prostaZmienna) {
{ $_ -gt 10 } {
'Większa niż 10'
}
{ $_ -lt 0 } {
'♪ Mniej niż zero-o, mniej niz zero-o. O-o-o! ♪'
}
5 {
'Piąteczka!'
}
}
<#
Wynik dla różnych wartości zmiennej $prostaZmienna:
$prostaZmienna = 11
Większa niż 10
$prostaZmienna = 5
Piąteczka!
$prostaZmienna = -5
♪ Mniej niż zero-o, mniej niz zero-o. O-o-o! ♪
#>

Bloki skryptu mogą – choć nie muszą – korzystać z przekazanej do switcha wartości. Zmienną tę wykorzystać możemy nie tylko w czasie rozstrzygania, czy chcemy skorzystać z danej ścieżki. Blok skryptu, który opisuje poszczególne ścieżki również udostępnia nam tę zmienną. Oprócz niej możemy też skorzystać w obu przypadkach ze zmiennej ‚switch’. Ta zmienna oferuje nam jednak nieco więcej, niż tylko wartość przekazanego do switcha obiektu:

switch ($prostaZmienna) {
{ 'dowolny ciąg znaków -> logiczne $true' } {
, $switch | Get-Member
}
}
<#
TypeName: System.Array+SZArrayEnumerator
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone(), System.Object ICloneable.Clone()
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
MoveNext Method bool MoveNext(), bool IEnumerator.MoveNext()
Reset Method void Reset(), void IEnumerator.Reset()
ToString Method string ToString()
Current Property System.Object Current {get;}
#>

W gruncie rzeczy nie różni się ona zbytnio od podobnych zmiennych oferowanych w pętlach. Dlaczego? Odpowiedź jest prosta: switch w PowerShellu pozwala na operowanie zbliżone do tego, jakie zyskujemy dzięki pętlom takim jak for, czy foreach. Zanim jednak zaczniemy się bawić kolekcjami, wspomnimy o jeszcze jednej możliwości przy analizie zawartości przekazywanego do switcha obiektu.

Nożyczki

PowerShell tworzony był przez ludzi zakochanych w wyrażeniach regularnych i wieloznacznych. Możemy korzystać z nich na wiele sposobów. Ten sentyment do tego rodzaju konstrukcji widać również w switchu. Jako jedyna konstrukcja (przynajmniej w starszych wersjach PowerShella) oferuje on dwa parametry, dzięki którym zmienić możemy domyślne zachowanie w przypadku analizy ciągów znaków. Domyślnie ścieżka zostanie wykonana tylko wtedy, gdy ciąg znaków przekazany pokrywa się z wymaganym. Co jednak gdy chcemy przejść do danej ścieżki wtedy, gdy ciąg znaków pokrywa się częściowo? Lub pasuje do wspomnianych uprzednio wyrażeń regularnych? W takiej sytuacji skorzystamy z jednego z dwóch parametrów: Wildcard bądź Regex:

switch -Wildcard ('cośtam') {
*tam {
'Pasuje'
}
coo* {
'Nie pasuje'
}
}
# Na wyjściu: "Pasuje"
switch -Regex ('dwa słowa') {
^d {
'zaczyna sie od "d"'
}
a$ {
'kończy się na "a"'
}
\d {
'zawiera cyfry'
}
}
# Na wyjsciu: "zaczyna się od d" oraz "kończy się na a"

Daje nam to dużo więcej swobody i często sprawia, że nasz switch możemy znacznie uprościć. Oczywiście, w takiej sytuacji dostęp do zmiennej $_ jest kluczowy: Tu nie mamy pewności, jaki obiekt u nas „wylądował”, musimy więc skorzystać z tej zmiennej jeśli chcemy go zwrócić, bądź przeprowadzić na nim pewne operacje. Alternatywnie, skorzystać możemy z $switch.Current:

switch -Regex ('pasujące słowo') {
pas {
"Pasuje: $_"
}
s.o.o {
$switch.Current * 2
}
}
<#
Na wyjściu:
Pasuje: pasujące słowo
pasujące słowopasujące słowo
#>

Tyle odnośnie wartości logicznych. Przyjrzyjmy się temu, jak switch wykorzystać jako pętlę.

Korkociąg

Możliwości mamy dwie. Przede wszystkim do konstrukcji switch przekazać możemy dowolną kolekcję. Konstrukcja analizować będzie poszczególne elementy kolekcji i podążać jedną (lub kilkoma) pasującymi ścieżkami:

$użycieCpu = 50, 80, 90
switch ($użycieCpu) {
50 {
"Połówka"
}
{ $_ -gt 30 } {
"Ciepło... ($_)"
}
{ $_ -gt 70 } {
"Gorąco! ($_)"
}
}
<#
Na wyjściu:
Połówka
Ciepło... (50)
Ciepło... (80)
Gorąco! (80)
Ciepło... (90)
Gorąco! (90)
#>

Tu przyjrzymy się naszym możliwościom, gdy chcemy pozwolić na jedną jedyną ścieżkę. Dla przykładu: chcemy określić „zdrowie” jakiegoś systemu w oparciu o jego parametry. W przypadku pojedynczej wartości przekazanej do switcha możemy bez problemu skorzystać ze słowa kluczowego break: Jeśli jakakolwiek aktywowana ścieżka zakończy się tym słowem kluczowym, PowerShell „wyjdzie” ze switcha i przejdzie do następnego wyrażenia w naszym kodzie:

$procentWolnejPamięci = 30
switch ($procentWolnejPamięci) {
{ $_ -gt 70 } {
'OK'
break
}
{ $_ -gt 50 } {
'Tak sobie...'
break
}
{ $_ -gt 20 } {
'Kiepsko'
break
}
{ $_ -gt 10 } {
'Oops?'
break
}
default {
'Pożar!'
}
}
# Na wyjściu tylko "Kiepsko", mimo że blok skryptu przy 'Oops' też zwróciłby wartość logiczną $true

W przypadku kolekcji musimy być nieco ostrożniejsi. Korzystając z break spowodujemy, że analiza zakończy się w chwili, gdy pierwszy obiekt w tej kolekcji pasować będzie do wzorca. Jeśli tego właśnie chcemy: break będzie naszym rozwiązaniem. W przeciwnym razie skorzystamy z instrukcji continue:

$użycieCpu = 40, 50, 60, 80, 90
switch ($użycieCpu) {
50 {
"Połówka"
continue
}
{ $_ -gt 70 } {
"Gorąco! ($_)"
break
}
{ $_ -gt 50 } {
"Ciepło... ($_)"
continue
}
default {
"Ujdzie... ($_)"
}
}
<#
Na wyjściu brakuje 90 (break dla 'Gorąco' i 80):
Ujdzie... (40)
Połówka
Ciepło... (60)
Gorąco! (80)
#>

Jako żywo przypomina to zachowanie wszystkich innych pętli w PowerShellu. Break by czmychnąć na dobre, continue by przejść do następnego elementu w pętli.

Śrubokręt

Kolekcje przekazane do switcha to jedno rozwiązanie. Innym rozwiązaniem będzie skorzystanie ze ścieżki do pliku (przekazywanej do switcha przez parametr File). Taka konstrukcja pozwoli nam analizę jego zawartości linia po linii. Oczywiście, możemy ten parametr połączyć z Regex bądź Wildcard, sprawiając, że switch staje się nieco rozbudowaną implementacją grepa:

switch -Regex -File C:\Windows\system32\drivers\etc\hosts {
^\s*# {
"Komentarz: [$_]"
}
^\s*$ {
"Pusta linia: [$_]"
}
default {
"Coś tu mamy: [$_]"
}
}

Oprócz analizy możemy też wykonać pewne ściśle określone operacje, dzięki czemu switch pozwoli przekształcić prosty plik w swoisty zestaw instrukcji dla naszego skryptu. Są oczywiście lepsze rozwiązania umożliwiające podobne „karmienie” naszego skryptu wartościami poszczególnych parametrów wejściowych, ale switch – jak widać – ma swoje ewidentne zalety.

I to tyle. Nie będę ukrywać: uwielbiam korzystać ze switcha. To jedna z tych rzeczy w PowerShellu, które prawdopodobnie zdarza mi się nadużywać. Warto jednak pamiętać o nim zawsze wtedy, gdy potrzebujemy połączyć analityczne możliwości wyrażeń logicznych z pracą „en masse” pętli a przy tym w prosty sposób skorzystać z wyrażeń regularnych. Wszystko na raz, lub jedno z powyższych. Winking smile

~ - autor: Bartek Bielawski w dniu 24 czerwca, 2020.

Jedna odpowiedź to “Switch: szwajcarski scyzoryk”

  1. Super, z tym file to nawet nie widzialem ze tak mozna. Jeszcze warto chyba wspomniec o switch ($null) ze mozna generalnie nawet nic mu nie podac zeby jakies tam sciezki sobie porobic.uzylem raz czy dwa do tej pory, ale sie przydalo. Pozatym, super wpis.

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: