Dot Foreach, Dot Where

DotDotDotSądzę, że prawie każdy, kto z pracy w PowerShellu czerpie przyjemność ma swoich „faworytów” – te elementy składni, których regularnie (nad)używa… O jednym z takich „faworytów” wspomniałem Wam ostatnio – operator switch to w mojej ocenie najlepsze co zdarzyło się od pojawienia się krojonego chleba. Dziś chciałem Wam przybliżyć element składni, który wciskać próbuję regularnie, gdzie tylko się da. Głównie dlatego, że kod, który tworzę, musi działać jedynie na najnowszej wersji PowerShella. Składnia ta bowiem dodana została w wersji czwartej, więc cokolwiek wymagającego wsparcia dla „trójki” musi obejść się smakiem… To „wirtualne metody” ForEach i Where.

Ich powstanie zawdzięczamy głównie DSC: to właśnie składnia w ramach konfiguracji miała być przyczyną dodania tych elementów składni. Zasada jest prosta: każda kolekcja posiadać będzie te dwie metody, dzięki którym możemy przeprowadzać operacje na wszystkich elementach kolekcji (ForEach) lub elementy kolekcji filtrować/ segregować (Where). Oprócz dodania metod, PowerShell został rozbudowany o konstrukcję, dzięki której składnie tych dwóch wirtualnych metod możemy znacząco uprościć. Zacznijmy może od wyjaśnienia tego cudeńka.

Bez-nawiasowa metoda

W PowerShellu (podobnie jak w wielu innych językach skryptowych czy programowania) metody uruchamiamy przekazując do nich parametry w nawiasie:

PowerShell-Metody

Jeśli spróbujemy podać jedynie nazwę metody, do PowerShell spróbuje przyjść z pomocą i pokaże nam różne możliwości uruchomienia interesującej nas metody:

PowerShell-Przeciazenia-Metod

Czasami trafimy na metodę, która wśród dostępnych opcji ma uruchomienie z jednym parametrem. Gdy parametr ten w PowerShellu reprezentować będziemy przy pomocy bloku skryptu, to możemy skorzystać ze składni nieco innej niż dobrze nam znana.

PowerShell-Metoda-EventHandler

Po nazwie metody możemy od razu rozpocząć wspomniany wyżej blok skryptu:

Add-Type -AssemblyName PresentationFramework
$OK = [System.Windows.Controls.Button]::new()
$OK.Add_Click{ 'Jest dobrze...' }
view raw DotMetodaKlamra.ps1 hosted with ❤ by GitHub

Będziemy z tego korzystać w czasie naszej pracy z Where i ForEach, obie wirtualne metody mogą działać z takim pojedynczym parametrem.

Dla każdego…

O metodzie ForEach nie da się za bardzo rozpisać. W zasadzie różni się ona nieznacznie tylko od składni znanej nam z polecenia ForEach-Object. Tu również operować będziemy na zmiennej $_ zawierającej bieżący element przetwarzanej kolekcji. Sprawdzi się to zarówno dla operacji na właściwościach, jak i uruchamiania metod. Skorzystać możemy przy tym z tradycyjnej składni z nawiasami:

$zdania = @'
Zdanie
To jakieś zdanie
To zdanie jest nieco dłuższe
A w tym zdaniu lecimy już po bandzie z długością!
'@ | ConvertFrom-Csv
$zdania.ForEach(
{
$_.Zdanie.Substring(0, 12)
}
)
<#
Na wyjściu:
To jakieś zd
To zdanie je
A w tym leci
#>
view raw ForEachNawiasy.ps1 hosted with ❤ by GitHub

Ale równie skuteczna będzie wersja z samymi nawiasami klamrowymi:

$zdania = @'
Zdanie
PiSzeMy wieLKImi i MałyMi literAMI.
KRZYCZĘ! KRZYCZĘ! KRZYCZĘ!
A na Koniec Wszystko Będzie Małą Literą.
'@ | ConvertFrom-Csv
$zdania.ForEach{ $_.Zdanie.ToLower() }
<#
Na wyjściu:
piszemy wielkimi i małymi literami.
krzyczę! krzyczę! krzyczę!
a na koniec wszystko będzie małą literą.
#>
view raw ForEachKlamra.ps1 hosted with ❤ by GitHub

Dodatkowo mamy dostęp do kilku innych opcji, których zastosowanie praktyczne raczej nie będzie zbyt wielkie.

Przede wszystkim, możemy metody i właściwości wywoływać podając ich nazwę:

@(
'raz'
'dwa'
'trzy'
).ForEach('Length')
view raw ForEachProperty.ps1 hosted with ❤ by GitHub

Jeśli dodamy kolejny argument to albo wybrana przez nas właściwość zostanie nadpisana (dla właściwości) lub przekazane wartości zostaną użyte jako argumenty dla metody (jeśli przekażemy nazwę metody):

@(
'raz'
'dwa'
'trzy'
).Foreach('Replace','a','@')
<#
Na wyjściu:
r@z
dw@
trzy
#>
view raw ForEachMethod.ps1 hosted with ❤ by GitHub

Kolejna opcja to konwersja wszystkich elementów kolekcji do wybranego typu. Przydatne wszędzie tam, gdzie typ obiektu będzie miał znaczenie a sam PowerShell może przekonwertować obiekty do niewłaściwego typu. Dla przykładu: przekształcimy kolekcję liczb w serię znaków:

ForEachToChar

Przyznam, że nie zdarzyło mi się jeszcze skorzystać z tej składni w taki sposób. Z drugiej strony: warto wiedzieć, że taka możliwość istnieje. Bo a nóż, widelec…

Kto, gdzie, kiedy…

O wiele ciekawsze możliwości oferuje metoda wirtualne Where. W przypadku najprostszym znów, nie będzie się w zasadzie niczym różnić od polecenia Where-Object:

@(
1
10
100
).Where{ $_ % 10 }
view raw DotWhereKlamra.ps1 hosted with ❤ by GitHub

Metoda ta jednak ma kilka ciekawych trybów, dzięki którym możemy uzyskać nieco inne wyniki.

Pierwszy – lepszy

Przede wszystkim możemy zdecydować, że chcemy pobrać jedynie pierwszy element, który spełnia warunek zawarty w naszym filtrze. Przyda się nam to wszędzie tam, gdzie chcemy jedynie sprawdzić, czy „coś pasuje”, lub gdy pierwszy pasujący wynik jest dla nas wystarczający i nie musimy kontynuować filtrowania:

@(
1
11
121
12321
23432
232
2
).Where(
{ -not ($_ % 2) },
'First'
)
# Na wyjściu: 23432
view raw PierwszyParzysty hosted with ❤ by GitHub

Jeśli potrzebujemy kilku pierwszych elementów, to oprócz metody filtrowania podać musimy jeszcze parametr liczbowy, który poinformuje PowerShella ile pierwszych elementów będzie nas interesowało.

(1..100).Where(
{ -not ($_ % 5) },
'First',
5
)
<#
Na wyjściu:
5
10
15
20
25
#>

Dzięki temu kolekcję analizować będziemy jedynie do momentu uzyskania odpowiedniej ilości wyników.

Ostatni będą pierwszymi…

Oczywiście, jeśli jest opcja First – musi też być opcja Last. Znów, jeśli podamy tylko tryb na wyjściu uzyskamy jeden, ostatni element spełniający podany warunek:

(1..100).Where(
{ -not ($_ % 9) },
'Last'
)

I znów, możemy wybrać opcję w której pobierzemy więcej niż jeden element, poczynając od ostatniego pasującego, aż do uzyskania wymaganej przez nas liczby elementów:

(1..100).Where(
{ $_ % 2 },
'Last',
5
)
<#
Na wyjściu:
91
93
95
97
99
#>

Dopóki, odkąd…

Kolejny sposób filtrowania pozwoli nam wybrać z kolekcji elementy poprzedzające pierwszy element spełniający warunek, bądź elementy które nastąpią po nim. Tryby te to Until oraz SkipUntil. Najczęściej przyda nam się to wszędzie tam, gdzie od momentu, gdy element spełni warunek dalsze sprawdzanie jest bezcelowe (czyli na przykład w kolekcji sortowanej według jakiegoś klucza):

$wszystkiePliki = Get-ChildItem -Path C:\Windows -File | Sort-Object -Property Length
$małe = $wszystkiePliki.Where(
{ $_.Length -gt 1MB },
'Until'
)
$duże = $wszystkiePliki.Where(
{ $_.Length -gt 1MB },
'SkipUntil'
)
view raw DużeMałePliki.ps1 hosted with ❤ by GitHub

Tu również możemy przekazać parametry liczbowe, które będą miały podobny efekt: określą dokładny rozmiar wynikowej kolekcji. Zwykle jednak używać będziemy używać ich bez parametru liczbowego, by oddzielić interesującą nas część kolekcji. A co gdy chcemy kolekcję rozdzielić na dwie: tę warunek spełniającą i tę, która warunku nie spełnia? W tym celu skorzystamy z mojego ulubionego trybu.

Panowie na prawo, panie na lewo

Ostatni tryb dla metody wirtualne Where to tryb Split. Tu skorzystać będziemy musieli z tego, że w PowerShellu operator przypisania pozwala na kilka elementów po lewej stronie. Wynik z prawej strony zostanie rozdzielony pomiędzy zmienne po lewej. Jeśli liczba zmiennych po prawej będzie większa – ostatnia zmienna dostanie kolekcję. Jeśli mniejsza – „nadmiarowe” zmienne uzyskają wartość $null:

# Tyle samo elementów po obu stronach - $jeden = 1, $dwa = 2.
$jeden, $dwa = 1, 2
# Po prawej więcej elementów. Nadmiarowe elementy dodane do ostatniej zmiennej. $skalar = 1, $kolekcja = 2, 3, 4
$skalar, $kolekcja = 1, 2, 3, 4
# Po lewej więcej elementów. Nadmiarowe zmienne będą $null
$zmienna, $innaZmienna, $tuNull, $tamNull = 1, 2

Operator split zwróci zawsze dwie kolekcje, wystarczy więc przypisać jego wynik dwu zmiennym, by oddzielić ziarna od plew:

$nieparzyste, $parzyste = (1..100).Where(
{ $_ % 2 },
'Split'
)

Przydatne wszędzie tam, gdzie chcemy kolekcję rozdzielić według wskazanego klucza. Plik od folderów. Grupy od użytkowników. Zainstalowane pakiety opcjonalne od tych, które nie są zainstalowane. Jak widać – nawet wśród faworytów znaleźć potrafię ultra-faworyta… Winking smile

~ - autor: Bartek Bielawski w dniu 10 października, 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 Google

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

Zdjęcie z Twittera

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

Zdjęcie na Facebooku

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

Połączenie z %s

 
%d blogerów lubi to: