Mrówkowe szaleństwo

Natrafiłem niedawno w Wikipedii na Mrówkę Langdona. Pomysł jest w sumie prosty: na nieskończenie dużej planszy składającej się kwadratowych pól stoi sobie mrówka. I teraz tak: jeżeli stoi na białym polu, obraca się w lewo, a jeżeli na czarnym – w prawo. Następnie zmienia kolor pola na przeciwny (a więc z białego na czarny, lub z czarnego na biały) i przesuwa się o jedno pole do przodu. I tak do usranej śmierci.

Trochę się to kojarzy z Game of Life Johna Conwaya.

Pomyślałem sobie, że mogłoby to być całkiem niezłe ćwiczenie ogólne na posługiwanie się Pythonem.

No i się zaczęło…

Najpierw całkiem niewinna wersja minimalna:

import matplotlib.pyplot as plt
import numpy as np

dirs = {"N": [0, 1], "S": [0, -1], "W": [-1, 0], "E": [1, 0]}
L = {"N": "W", "W": "S", "S": "E", "E": "N"}
R = {"N": "E", "E": "S", "S": "W", "W": "N"}

xsize, ysize = 500,500
steps = 1000000
x, y = xsize//2, ysize//2
board = np.zeros(shape=(xsize, ysize), dtype=int)

turns = [L,R]
dir = "N"
for X in range(steps):
    dir = turns[(board[x, y])][dir]
    board[x, y] = 1-board[x, y]
    dx, dy = dirs[dir]
    x, y = (x + dx) % xsize, (y + dy) % ysize
plt.imshow(board)
plt.xticks([])
plt.yticks([])
plt.show()

Powyższy kod generuje trasę spaceru Mrówki Langdona z jedną różnicą względem oryginału: zamiast nieskończonej płaszczyzny używamy prostokąta “zawiniętego” wzdłuż i w poprzek (czyli mrówka wychodząc poza lewą krawędź pojawia się po prawej stronie i tak dalej).

Wynik:

A potem spróbowałem dodać więcej kolorów, więcej opcji zakręcania, czyli do “w lewo” i “w prawo” dołożyłem opcję “zawróć” oraz “idź dalej prosto” i na koniec zrobił mi się z tego generator losowych spacerów Mrówek Langdona, którego kod wygląda teraz tak:

import matplotlib.pyplot as plt
import numpy as np
from random import choice, randrange as rr

dirs = {"N": [0, 1], "S": [0, -1], "W": [-1, 0], "E": [1, 0]}

L = {"N": "W", "W": "S", "S": "E", "E": "N"} # left
R = {"N": "E", "E": "S", "S": "W", "W": "N"} # right
N = {"N": "N", "W": "W", "S": "S", "E": "E"} # none
B = {"N": "S", "W": "E", "S": "N", "E": "W"} # back

colormaps = plt.colormaps()

for mainloop in range(1000):

    xsize, ysize = 200 + rr(300), 200+rr(300)
    steps, colours = 1000000 + rr(4000000), 20+rr(65515)
    x, y = xsize//2, ysize//2
    board = np.zeros(shape=(xsize, ysize), dtype=int)

    turns = []
    tt = ""
    trnum = 2+rr(20)
    for i in range(trnum):
        t = choice(("L", "R", "N", "B"))
        tt += t
        turns.append({"L": L, "R": R, "N": N, "B": B}[t])

    dir = "N"
    for X in range(steps):
        dir = turns[(board[x, y]) % len(turns)][dir]
        board[x, y] = (board[x, y] + 1) % colours
        dx, dy = dirs[dir]
        x, y = (x + dx) % xsize, (y + dy) % ysize
    plt.imshow(board, cmap=choice(colormaps))
    plt.xticks([])
    plt.yticks([])
    xlab = "{}x{}|{}|C:{}|S:{}".format(xsize, ysize, tt, colours, steps)
    plt.xlabel(xlab)
    filename = "ants/langton-{}-{}-{}-{}-{}.png".format(
        xsize, ysize, tt, colours, steps)
    plt.savefig(filename)
    print(".", end="")
print()

Przed uruchomieniem skryptu należy się upewnić, że mamy lokalny folder o nazwie “ants”, do którego będą trafiać wygenerowane obrazki. Skrypt wygeneruje 1000 losowych spacerów i zapisze każdy z nich w osobnym pliku png. Obrazki mają losową rozdzielczość (od 200×200 do 500×500 pikseli), losową mapę i liczbę kolorów, losowe reguły zakręcania oraz liczbę iteracji (między 1M a 5M).

Mała próbka:

Większa próbka (kliknięcie w obrazek wyświetli go w powiększeniu):

Co dalej?

Jak czas i zdrowie pozwolą, spróbujemy pobawić się planszą heksagonalną…

Pasjansowy zegar czy zegarowy pasjans?

Solitaire znają wszyscy[citation needed]. Wykładały go nasze babcie i prababcie, był też dołączany przez Microsoft do większości wersji Windows.

Jest jednak wiele innych pasjansów. Jeden z nich – którego nauczyła mnie babcia – to Zegar.

Zegar jest – w odróżnieniu od Solitaire – pasjansem stuprocentowo deterministycznym. Innymi słowy wynik zależy wyłącznie od początkowego potasowania talii. Nie ma tu żadnego miejsca na myślenie, na decydowanie; po prostu robimy krok po kroku to, co każą nam karty. Mało twórcze, ale czasem człowiek potrzebuje[citation needed] mechanicznego, bezmyślnego przekładania kart. Taki reset.

Szczegóły samego pasjansa dobrze objaśnia Łukasz z portalu Przystanek Planszówka (KLIK), skąd zresztą ukradłem obrazek do dzisiejszego wpisu. Jeżeli ktoś nie zna Zegara, może sobie tam poczytać. Tutaj natomiast pokażę jak zrobić symulację Zegara w Pythonie aby sprawdzić jak często wychodzi, a jak często – nie wychodzi.

Zanim zacznę, powiem jeszcze, że za dzieciaka wykładałem Zegar dość często. Nie pamiętam ile razy mi się udało “wygrać”, ale były to przypadki bardzo rzadkie.

OK, lecimy:

from random import sample
wygrane, ilosc_prob = 0, 100000

for proba in range(ilosc_prob):
    stos = sample(range(52), 52)  # tasujemy talię

    # zegar to lista list:
    # zewnętrzna lista ma 13 elementów (12 godzin plus stosik na środku)
    # każdy z 13 elementów jest listą 4-elementową (stos 4 kart)
    # przykładowo zegar[11][0] to pierwsza karta godziny 11
    zegar = []

    for i in range(13):  # rozkładamy zegar
        zegar.append(stos[i::13])

    krole = 0  # licznik króli (królów?)

    karta = 0  # startujemy od środka zegara
    while 1:  # gramy!
        karta = zegar[karta % 13].pop()  # bierzemy kolejną kartę
        if karta % 13 == 0:  # jeżeli trafiliśmy na króla
            krole += 1  # liczymy go
            if krole == 4:  # jeżeli to ostatni król
                break  # koniec pasjansa

    # jeżeli odkryliśmy wszystkie 12 godzin zegara, udało się
    if(sum(map(lambda x: x == [], zegar)) == 12):
        wygrane += 1

print(wygrane/ilosc_prob)

Przy liczbie prób 1M (lub więcej) wynik prawie zawsze wychodzi w okolicach 0.077. A więc 77 razy na 1000 pasjans wychodzi. Pozostałe 923 razy – nie wychodzi.

Najbardziej frustrujące jest, kiedy już prawie, prawie wyszedł, odkryliśmy 11 godzin, brakuje tylko jednej – i ostatni król pojawia się odrobinę za wcześnie.

No dobra. Skoro pasjans nie wychodzi za często, może spróbujmy policzyć ile średnio godzin uda się odkryć zanim pojawi się czwarty król?

from random import sample
wygrane, ilosc_prob, odkryte = 0, 100000, 0

for proba in range(ilosc_prob):
    stos = sample(range(52), 52)  # tasujemy talię

    # zegar to lista list:
    # zewnętrzna lista ma 13 elementów (12 godzin plus stosik na środku)
    # każdy z 13 elementów jest listą 4-elementową (stos 4 kart)
    # przykładowo zegar[11][0] to pierwsza karta godziny 11
    zegar = []

    for i in range(13):  # rozkładamy zegar
        zegar.append(stos[i::13])

    krole = 0  # licznik króli (królów?)

    karta = 0  # startujemy od środka zegara
    while 1:  # gramy!
        karta = zegar[karta % 13].pop()  # bierzemy kolejną kartę
        if karta % 13 == 0:  # jeżeli trafiliśmy na króla
            krole += 1  # liczymy go
            if krole == 4:  # jeżeli to ostatni król
                break  # koniec pasjansa

    # zliczamy ilość odkrytych godzin
    odkryte += sum(map(lambda x: x == [], zegar))

print(odkryte/ilosc_prob)

Modyfikacja jest niewielka: zamiast zliczać wygrane pasjanse, po każdym pasjansie zliczamy ilość odkrytych godzin.

Ile razy bym tego nie uruchamiał, zawsze wychodzi mi około 6.99. Czyli wykładając Zegar możemy się spodziewać, że odkryjemy siedem godzin zanim trafimy na ostatniego króla.

Wyjaśnienie sposobu liczenia odkrytych godzin:

Operator map działa tak, że wywołuje zadaną funkcję dla każdego elementu zadanej listy i zwraca listę wyników. W naszym przypadku listą jest zegar po zakończeniu pasjansa, a funkcją jest sprawdzenie, czy element listy jest sam w sobie pustą listą (x == []). Na końcu sumujemy wynik. Operator porównania zwraca True / False, które są traktowane przez sum jako 1 / 0.

Pchełki Python: plotka w totka

Natrafiłem gdzieś niedawno na informację, że w jednym z krajów w Afryce podobno doszło do przekrętu, bo w loterii padły liczby 5, 6, 7, 8, 9, 10.

Pomyślałem sobie, jakie jest prawdopodobieństwo wylosowania pięciu sąsiednich liczb spośród… no właśnie, spośród ilu? Przyjąłem roboczo, że spośród 49.

Pierwszą liczbę możemy wybrać na 49 sposobów. Drugą na 48, trzecią na 47 i tak dalej, aż do szóstej. Razem około dziesięciu miliardów kombinacji:

44 * 45 * 46 * 47 * 48 * 49 = 10 068 347 520

Ile spośród nich składa się z sześciu sąsiadujących liczb?

Sąsiadujące liczby ustawione w kolejności od najmniejszej do największej można uzyskać na 44 sposoby: począwszy od (1,2,3,4,5,6) aż do (44,45,46,47,48,49).

Każdy z tych 44 sposobów ma dodatkowo 6! = 720 permutacji (przestawień). A więc łącznie mamy 44 * 720 = 31 680 układów liczb sąsiadujących ze sobą. Szansa trafienia na jeden z nich wynosi:

31 680 / 10 068 347 520 = 0.000000314649449

Mniej więcej trzy dziesięciomilionowe. Jeżeli zagramy w totka dziesięć milionów razy, natrafimy na układ sześciu kolejnych liczb mniej więcej trzy razy.

Jak długo trzeba grać, żeby trafić chociaż raz?

Oczywiście, nie wiadomo. Szansa za każdym razem jest taka sama: około trzy na dziesięć milionów. Wiadomo jednak, że przy odpowiednio długim graniu prędzej czy później trafimy na jeden z tych 31680 układów.

Ile to jest “odpowiednio długo”?

Nie da się uzyskać gwarancji, że po N próbach trafimy. Ale można spróbować policzyć ile tych prób powinno być, żeby szansa trafienia była, dajmy na to, 99%. Jak to zrobić? Jeżeli szansa nie trafienia w jednej próbie wynosi (1 – 31680/10068347520) czyli około 0.99999685350550951185, to przy dwóch próbach będzie to (1 – 31680/10068347520)2 czyli około 0.99999370702091945128. Różnica niewielka. Ale nie wolno nie doceniać potęgowania: przy odpowiednio dużej liczbie prób uzyskamy wreszcie prawdopodobieństwo nie trafienia mniejsze niż 0.01.

Metodą prób i błędów (chociaż pewnie dałoby się prościej, logarytmem) sprawdziłem, że:

(1 – 31680/10068347520)1463586 = 0.009999976555193245

natomiast

(1 – 31680/10068347520)1463585 = 0.010000008020163384

Tak więc grając 1463586 razy mamy 99% szans na to, że gdzieś tam trafimy pięć sąsiednich liczb.

Na koniec spróbujemy sobie to zasymulować w Pythonie:

from random import sample
data = [x for x in range(1, 50)]
while 1 == 1:
    c = 0
    minr = 48
    while minr > 5:
        c += 1
        s = sample(data, 6)
        mr = max(s) - min(s)
        if mr < minr:
            minr = mr
    print(c, end=" ")

Uruchomienie powyższego kodu na kilkanaście godzin (ponad 32000 wykonań dużej pętli) dało mi na wyjściu trochę liczb, podstawowe statystyki:

  • W ekstremalnych przypadkach udało się uzyskać trafienie po 35 losowaniach (minimum) oraz po 3 999 695 losowaniach (maksimum).
  • Średnio trafienie następowało po 318K losowań
  • Mediana w okolicy 221K losowań

Wnioskuję stąd, że gdyby w owym afrykańskim kraju loteria odbywałaby się codziennie, to trzeba by czekać na uzyskanie takiego wyniku gdzieś między 650 a 1000 lat. Zakładając rzecz jasna, że losujemy spośród 49 liczb, a nie, dajmy na to, 20 albo 100.

Wniosek?

Brak 😉 Może był tam jakiś przekręt, może nie… Z prawdopodobieństwem tak już jest.

Powershell 7: eksperymentujemy #5

Wpis kończący serię o nowym Powershell.

Obsługa błędów w poprzednich wersjach PS była całkiem niezła. Jest try-catch, jest -ErrorAction, wreszcie jest zmienna $Error ze wszystkimi błędami, które do tej pory udało nam się wygenerować (zarówno krytycznymi jak i niekrytycznymi).

W wersji 7 pojawiła się jeszcze jedna komenda: Get-Error.

Proszę sobie spróbować podzielić przez zero albo wykonać Test-Connection do nieistniejącego adresu dns a potem uruchomić Get-Error:

Mnóstwo interesujących informacji na temat błędu; jest nawet konkretna lokalizacja (numer linii, pozycja w linii), w której błąd się pojawił.

Dla mnie osobiście najwygodniejsze jest tu to, że Get-Error w szybki sposób zwraca szczegóły *ostatniego* błędu w ładny, czytelny sposób. Co prawda wszystkie te informacje dało się wyciągnąć w poprzednich wersjach, ale wymagały nieco wygibasów. A tu – wszystko jak na tacy.

Na zakończenie dodam jeszcze, że tych pięć wpisów o Powershell 7 bynajmniej nie wyczerpuje wszystkich nowości w tej wersji. Opisałem tylko te, które społeczność zgromadzona wokół PS uznała za najbardziej przydatne.

Powershell 7: eksperymentujemy #4

Powershell 7 wprowadził dwa nowe operatory umożliwiające warunkowe uruchamianie poleceń w łańcuchu.

Wyobraźmy sobie, że chcemy najpierw uruchomić polecenie A a potem polecenie B, ale tylko jeżeli A zakończyło się sukcesem.

Czemu tak? Na przykład A może tworzyć nowy folder a B kopiować coś do niego – nie ma sensu uruchamiać B jeżeli A się nie udało.

Do tego celu służy operator &&:

New-Item -Type Directory -Name "TestFolder01" -ErrorAction SilentlyContinue | Out-Null && Write-Host "Udało się"

Powyższy kod spróbuje utworzyć nowy folder “TestFolder01”, a następnie wyświetli komunikat “Udało się” jeżeli wszystko poszło OK.

Można też w drugą stronę: czasami chcemy uruchomić jakąś komendę tylko wtedy, gdy jakaś inna komenda poległa z błędem. Wówczas użyjemy operatora ||:

New-Item -Type Directory -Name "TestFolder01" -ErrorAction SilentlyContinue | Out-Null || Write-Host "Nie udało się"

Tu znów próbujemy utworzyć folder TestFolder01, ale ponieważ taki folder już jest, operacja New-Item nie powiedzie się i dostaniemy na wyjściu komunikat “Nie udało się”.

Operatory && i || można oczywiście łączyć:

New-Item -Type Directory -Name "TestFolder02" -ErrorAction SilentlyContinue | Out-Null && Write-Host "Udało się" || Write-Host "Nie udało się"

Powyższa linijka za pierwszym razem da komunikat “Udało się”, a za drugim – “Nie udało się”. Od biedy można myśleć o parze && – || jak o operatorze trójwartościowym: wykonaj A, jeżeli się uda to wykonaj B, w przeciwnym razie C:

A && B || C

Powershell 7: eksperymentujemy #3

Po operatorze trójwartościowym i współbieżnej pętli ForEach przyszedł czas na Null-e.

Null to takie bydlę, które z jednej strony jest całkiem wygodnym konceptem, a z drugiej przysparza programistom (i twórcom języków programowania) mnóstwo kłopotów.

Null z definicji oznacza brak wartości. Nie: zero. Nie: pusty ciąg znaków. Nie. BRAK wartości.

Najwięcej problemów z NULL-em pojawia się przy operacjach boole-owskich: czy null AND true da w wyniku false? A może null?

Czy null OR true da w wyniku true? false? null?

Hmmm…

Niektóre języki próbują to rozwiązać poprzez wprowadzenie specjalnej wartości UNKNOWN: null AND true da w wyniku UNKNOWN. 3 + NULL – również. Ale tu pojawia się problem wyższego rzędu: skoro UNKNOWN samo w sobie jest wartością (specjalną, ale jednak), trzeba dorobić logikę, która ją uwzględnia… czy UNKNOWN and NULL dają na wyjściu UNKNOWN? A może NULL? A może 42? Hm.

Inne języki zakładają po prostu, że jakakolwiek operacja z NULL-em da w wyniku NULL.

Jeszcze inne konwertują niejawnie NULL na 0.

Różnych algorytmów postępowania z NULL-ami jest całkiem sporo (niektóre się wzajemnie wykluczają!), ale ja dziś nie o tym.

Ja dziś o tym, jak rozpoznać NULL-a w Powershell oraz co z tego wynika.

Zacznę od tego, że implementacja NULL w Powershell nie jest byt elegancka. W wersji 5.x na przykład można było zrobić coś takiego:

$zmienna = $null
$zmienna -eq $null

Wynik powyższego dawał na wyjściu True. Ale już:

[string] $innazmienna = $null
$innazmienna -eq $null

… generował na wyjściu False. Wystarczy więc dodać deklarację typu, żeby się rozjechało. Jeżeli chcieliśmy sprawdzić “null-owatość” zmiennej typu string, musieliśmy się łapać “brudnych” chwytów typu [String]::Empty lub im podobnych. Groch z kapustą.

Jeszcze gorzej było z NULL-ami zwracanymi przez zapytania SQL. Żeby sprawdzić, czy dostaliśmy NULL, trzeba było używać [DBNull]::Value, co prowadziło do jeszcze większego zamieszania.

Powershell 7.0 próbuje to wszystko uporządkować – czy robi to prawidłowo czy nie, chyba nie da się jednoznacznie określić, krzaków po drodze jest mnóstwo. Ale jest postęp.

Ci z Czytelników, którzy liznęli w życiu nieco SQL-a wiedzą o istnieniu operatora COALESCE, który zwraca pierwszą napotkaną wartość parametru niebędącego NULL-em. Innymi słowy jeżeli a i b są NULL, a c i d nie są, wówczas COALESCE(a, b, c, d) zwróci wartość zmiennej c, bo jest to pierwszy parametr niebędący NULL. I tak dalej.

W Powershell 7 pojawił się operator ??, który robi mniej więcej to samo, tylko trochę inaczej:

$x = "iks"
$y = "igrek"
$z = "zet"
$a ?? $b ?? $c ?? $x ?? $y ?? $z

Powyższy kod da na wyjściu “iks”, ponieważ zarówno $a jak też $b i $c są NULL, a $x jest pierwszą zmienną niebędącą NULL.

Drugim ważnym operatorem, który pojawił się w PS7, jest operator “podstaw jeżeli NULL”, który najpierw sprawdza, czy po lewej stronie jest NULL i jeżeli tak, podstawia do lewej strony to, co jest po prawej, a jeżeli nie, to nic nie robi. Przykład:

$a ??= "literka a"
$a ??= "literka b"
$a ??= "literka c"

Tutaj na dzień dobry $a jest NULL (bo nie ma takiej zmiennej, a nieistniejące zmienne w PS są zawsze NULL), więc najpierw do $a zostanie podstawiony tekst “literka a”. Potem próbujemy tam podstawić “literka b”, ale operator ??= “widzi”, że po lewej stronie ma już jakąś wartość (“literka a”) i zignoruje podstawienie. To samo dzieje się w kolejnej linii, tej z literką c.

Wniosek końcowy? Operatory ?? oraz ??= mogą zaoszczędzić nam zbędnego klepania if-ów, ale trzeba ich używać z głową. Jak wszystkiego zresztą.

Powershell 7: eksperymentujemy #2

Dziś drugi wpis z serii nowinek w nowym Powershell. Tym razem weźmiemy na tapet operator trójargumentowy zwany czasem operatorem warunkowym (po naszemu: ternary operator). Czyli pytajnik z dwukropkiem.

Operator ów to nic innego jak sprawdzenie jakiegoś warunku, a następnie zwrócenie wartości X jeżeli warunek ów jest prawdziwy bądź też Y jeżeli nie jest.

Przykład:

$c = $a -eq $b ? 3 : 4

Powyższy kod należy rozumieć tak: sprawdź czy $a równa się $b i jeżeli tak, to podstaw za $c trójkę, a jeżeli nie – czwórkę.

Inny przykład:

1..10 | Foreach-Object {$dzien = Get-Random "Poniedzialek","Wtorek","Sroda","Czwartek","Piatek","Sobota","Niedziela"; $ma_r = $dzien -like "*r*" ? "$dzien ma R" : "$dzien nie ma R"; $ma_r}

Wynik:

Czwartek ma R
Niedziela nie ma R
Sobota nie ma R
Sobota nie ma R
Poniedziałek nie ma R
Czwartek ma R
Niedziela nie ma R
Środa ma R
Niedziela nie ma R
Wtorek ma R

Tym razem wylosowaliśmy sobie losowo dziesięć razy nazwę dnia tygodnia i sprawdziliśmy każdorazowo czy nazwa ma w środku literę R czy nie (dlaczego? Bo “Niedzierla”, jak mówi stary kawał).

Implementacja operatora trójwartościowego w Powershell 7.0 jest dość “goła”:

Po pierwsze, nie da się użyć wyniku operatora w charakterze zmiennej (innymi słowy nie da się podstawić do wyniku operatora żadnej wartości). W C jest to jak najbardziej możliwe:

( x > y ? a : b) = 7

Powyższy kod w C podstawi siódemkę do zmiennej a jeżeli x>y, w przeciwnym razie do b. W Powershell podobny zabieg skończy się komunikatem o błędzie składni

Po drugie zaś powershellowa wersja nie obsługuje składni “skróconej”; w C możemy napisać:

a = b ?: c

Tu z kolei jeżeli b jest prawdą, wartość zmiennej a nie zmieni się, a jeżeli nie, podstawimy tam wartość zmiennej c. Innymi słowy powyższa linijka jest równoważna:

a = b ? a : c

W Powershell użycie składni “skróconej” nie przejdzie – również zostaniemy opluci komunikatem błędu.

Wniosek końcowy: pomimo niepełnej implementacji operatora trójwartościowego mamy od teraz okazję skrócenia tego:

if($a -ne $b) {$x = 7} else {$x = 9}

Do tego:

$x = $a -ne $b ? 7 : 9

Prawda, że prościej?

Pchełki Powershell: proste logowanie z cherbatką

Ja wiem, że jestem z dolnych okolic ujemnego IQ, ale żeby “herbatka” napisać przez ce-ha?

Zaraz się wyjaśni.

Czasem mamy do napisania skrypt w Powershell. I nie jednolinijkowy, który kopiuje plik z folderu A do folderu B, tylko taki bardziejszy. Taki, co to i do bazy danych się podłączy, i do jakiegoś webserwisu na ten przykład, a to w rejestrze grzebnie, a to jakiegoś CSS-a czy HTML-a wygeneruje, czy co tam jeszcze kto potrzebuje. No taki bardziejszy.

A jak powszechnie wiadomo[citation needed] czym bardziej bardziejszy skrypt tym większe szanse na popełnienie błędu. A skoro będą błędy to już na dzień dobry warto pomyśleć o jakimś systemie logowania różnych informacji do pliku. Albo chociaż na ekran. A najlepiej jedno i drugie.

Do tego jeszcze żeby było prosto i szybko.

Powershell ma filtry.

A cebula ma warstwy i co z tego? Jakie, kurna, znów filtry? O co temu gościowi znów qu’est-ce que c’est?

Filtr w Powershellu to nic innego jak funkcja bez argumentów, którą można “karmić” danymi wyłącznie przez rurkę.

Czyli po naszemu pipe.

O, tak wygląda rurka: |

A tak wyglądają dwie rurki obok siebie: ||

Użytkownicy linuksów są z rurką obeznani, bo tam bez rurki się w zasadzie nie da. Windowsiarze przebrzydli może też wiedzą, co to rurka. A może nie…

Rurka działa o, tak:

jakaś-komenda | jakaś-inna-komenda

Działa to w ten sposób, że dane wygenerowane przez pierwszą komendę (tę po lewej stronie rurki) są wysyłane do drugiej komendy (tej po prawej stronie).

Na przykład:

dir | sort

Powyższe wykona najpierw polecenie dir (czyli wylistowanie wszystkich plików i katalogów w bieżącym miejscu) a następnie zamiast wywalić to na ekran, wyśle do polecenia sort, które – niespodzianka – posortuje te nasze pliki i katalogi alfabetycznie i dopiero wtedy wyświetli na ekranie, posortowane. Chyba że za sort umieścimy kolejną rurkę, wtedy ta posortowana list plików i folderów trafi do kolejnego polecenia. I tak dalej. Można w ten sposób budować całkiem interesujące i zaawansowane mechanizmy przetwarzania danych.

Ale do rzeczy, moiściewi, do rzeczy, miało być o logowaniu i o debilach ortograficznych!

No dobra. Logowanie. Filtry. Lecimy:

Filter DoLog {
    $LogName = "$PSScriptRoot\log\$(Get-Date -Format 'yyyyMMdd').log"
    (Get-Date -Format "yyyyMMddhhmmss.fff ") + $_ | Tee-Object $LogName -Append
}

Od tej pory możemy w naszym skrypcie przepuszczać różne rzeczy przez DoLog, za pomocą rurki. Na przykład o tak:

"Bardzo Ważny Komunikat" | DoLog

Powyższa komenda wypisze na ekranie “Bardzo Ważny Komunikat”, poprzedzony znacznikiem czasowym, a także wyśle ten sam komunikat do pliku 20191018.log w podfolderze [log] w lokalizacji naszego skryptu.

Fajne?

Fajne, tylko że nie zadziała, bo nie mamy folderu [log].

Ja z lenistwa zawsze tworzę ten folder ręcznie, ale można to zautomatyzować jak się komuś mocno chce. Mi się nie chce.

A co z tą nieszczęsną cherbatką?

Jeżeli przyjrzymy się treści naszego filtra DoLog zobaczymy tam polecenie Tee-Object, które działa analogicznie do linuksowego tee. Czyli kopiujemy (w locie) dane, jedna kopia ląduje na ekranie a druga – w pliku o podanej nazwie. Opcja -Append zaś zagwarantuje, że kolejne komunikaty będą dopisywane na koniec pliku czyli dokładnie tak, jak powinien działać log. “Tee” wygląda trochę jak “Tea” z błędem, stąd cherbatka.

Aha, $_ reprezentuje w Powershell “to”, czyli w tym przypadku zawartość wylewającą się z rurki. A w innym przypadku – na przykład bieżący element iterowanej właśnie kolekcji, albo jeszcze coś innego. Ale to już może kiedy indziej.