foreach i ForEach-Object

Tym razem bedzie o programowaniu. Za to o programowaniu w PowerShellu, czyli tak bardziej dla administratorów. W ramach jednego z projektów dlubie ostatnio skrypt wczytujacy z pliku dane i na ich podstawie modyfikujacy w srodowisku sporo róznych atrybutów. Ot, klient zazyczyl sobie, zeby konfiguracja byla zgodna z korporacyjnymi regulami i konwencjami i chwala mu za to.

Oprogramowanie tego przez prosty plik EXE wywolujacy wlasciwe funkcje API byloby wzglednie latwe, ale od pewnego czasu tlumacze wszystkim "uczcie sie PowerShella i próbujcie w nim rozwiazywac wasze zadania administracyjne" i sam tez sie do tego próbuje zmuszac. Ma to swój sens, ale to zupelnie inny temat.

W kazdym razie mój skrypt sobie rosnie (glównie ze wzgledu na odczytywanie i ustawianie ACLi) i ma na razie kilkaset linii. Rozmiar wynika równiez w sporym stopniu z tego, ze zalozylem sobie, ze w któryms momencie klient zostanie ze skryptem sam i dobrze byloby, gdyby na ekranie jasno widzial, dlaczego cos nie dziala i co ma zmienic w pliku wejsciowym. Ze wzgledu na obsluge bledów, skrypt wczytuje sobie plik wejsciowy, analizuje go i weryfikuje jego wykonalnosc, po czym albo stwierdza, ze wszystko gra i przystepuje do pracy, albo grzecznie tlumaczy uzytkownikowi, co jest nie tak i konczy dzialanie.

Do analizy danych wejsciowych linia po linii uzylem prostego polecenia

$DaneZPliku | ForEach-Object {$dana=$_.costam; costam; costam}

gdzie $DaneZPliku zostaly chwile wczesniej wczytane. Okazalo sie bardzo szybko, ze pusta linia w takim pliku wejsciowym sprawia, ze $dana ma wartosc $NULL i moja procedura sprawdzajaca mówi (slusznie!), ze to niezbyt dobrze.

Dodalem wiec prosty warunek na poczatku bloku:

$DaneZPliku | ForEach-Object {$dana=$_.costam; if ($dana –eq $NULL) {Continue}; costam; costam}

I tu okazalo sie, ze mam problem. Zamiast (zgodnie z moim naiwnym oczekiwaniem) zaczac znecac sie nad kolejnym obiektem, mój skrypt konczyl dzialanie. Oczywiscie Continue mozna prosto ominac uzywajac If, ale tworzy to dlugie bloki, które potem trudniej wylapuje sie na ekranie. Continue jest milsze. Dlugo myslalem zanim doszedlem do prostego wniosku: tu nie ma petli! Wiec jak Continue ma dzialac poprawnie...? Petle robi sie uzywajac foreach. Zmylil mnie tak naprawde "przyjazny" alias zdefiniowany w PowerShellu...  polecenie "alias foreach" pokaze o co chodzi. Otóz wbrew temu aliasowi, polecenia te nie sa tozsame. Foreach to równoczesnie alias i slowo kluczowe i moze to prowadzic do klopotów.

Jezeli chce uzyc prawdziwej petli, to moje sprawdzanie danych wygladac powinno tak:

foreach ($i in $DaneZPliku) {$dana=$i.costam; if ($dana –eq $NULL) {Continue}; costam; costam}

I to wreszcie dziala jak powinno.

Jako test polecam dwa proste polecenia:

foreach ($i in 'kot','pies','but','krowa') {if ($i -eq 'but') {continue}; write-host $i} – tu mamy petle i wszystko dziala jak powinno, omijajac $i, które nie jest zwierzeciem i kontynuujac dzialanie.

Jezeli jednak napiszemy:

('kot','pies','but','krowa') | ForEach-Object {$i=$_; if ($i -eq 'but') {continue}; write-host $i} – to petli tu nie ma! I Continue bez slowa konczy dzialanie polecenia.

Ten jezyk tak ma i wiem, ze to ja powinienem sie nauczyc a nie mówic, jaki to jest glupi. Choc chetnie zobaczylbym po prostu komunikat o bledzie w sytuacji, gdy uzyje Continue poza petla...

Do zapamietania pozostaje jedno: foreach to zupelnie co innego niz ForEach-Object, a fakt, ze ForEach-Object mozna w dowolnym miejscu zastapic przez foreach, wcale nie upraszcza sytuacji. Najlepiej odzwyczaic sie od uzywania foreach jako aliasu i dzieki temu lepiej czuc gdzie robimy petle, a gdzie wcale nie.

Powershellowo sie ostatnio na blogu zrobilo. Postaram sie napisac cos z innej beczki, ale jak widac takie czasy, ze tak zwany "ITPro" bez PowerShella sie nie obejdzie...

Autor: Grzegorz Tworek [MVP]