Centrum Skryptów

Windows PowerShell: Jak nie należy skryptować Udostępnij na: Facebook

Autor: Don Jones

Opublikowano: 27 listopada 2009

Zawartość strony
Nie nadużywajmy zmiennych  Nie nadużywajmy zmiennych
Znaczące nazwy zmiennych  Znaczące nazwy zmiennych
Upraszczanie porównań  Upraszczanie porównań
Nie przerywajmy skryptu  Nie przerywajmy skryptu
Pętle, pętle...  Pętle, pętle...
Czemu tak cicho?  Czemu tak cicho?
Pokażcie swoje skrypty  Pokażcie swoje skrypty
Windows PowerShell - Pytania i odpowiedzi  Windows PowerShell - Pytania i odpowiedzi

 

Jakiś czas temu złożyłem Czytelnikom mojego blogu na ConcentratedTech.com (nadal aktualną) ofertę : przyślijcie mi swoje skrypty Windows PowerShell, a ja je przejrzę i zaproponuję, jak można je ulepszyć. Sam pomysł pochodzi ze spostrzeżeń moich kolegów-blogerów i moich własnych, zaś jeden z Czytelników zasugerował, że coś takiego mogłoby dać ludziom prawdziwą, praktyczną metodę podniesienia umiejętności. W ciągu paru ostatnich miesięcy zauważyłem kilka trendów, gdy zorientowałem się, że pewne sugestie dla autorów skryptów ciągle się powtarzają. Tak więc zestawiłem listę takich powtarzalnych uwag, aby się nimi podzielić w tym miejscu.

Na początek jednak pozwolę sobie zauważyć, że każdy skrypt, który sprawia, że robota zostanie wykonana (z powodzeniem i bez strat ubocznych – oczywiście) jest dobrym skryptem. Niektóre z moich sugestii mogą wydawać się dzieleniem włosa na czworo i część z nich pewnie taka jest. Tym niemniej każda moja propozycja niesie ze sobą jakieś korzyści, które postaram się pokazać w trakcie omawiania.

Nie należy też się przejmować, jeśli ktoś sam robi te „złe rzeczy”, które tu wypunktowałem. Zdaję sobie sprawę, że wielu z tych, którzy obecnie tworzą skrypty Windows PowerShell, zaczynało od VBScript i niektóre „złe przyzwyczajenia” są w istocie właściwą drogą postępowania w świecie VBScript. Jest to jednak okazja, by się nauczyć, czym Windows PowerShell i VBScript się różnią i jak pisać skrypty wykorzystujące te korzyści, które Windows PowerShell ma do zaoferowania.

Oto kilka moich typowych sugestii, które zgłosiłem w ciągu ostatnich kilku miesięcy.

Nie nadużywajmy zmiennych

Naturalnie zmienne są użyteczne, ale nie dajmy się sprowadzić na manowce. Widziałem wiele przykładów takich konstrukcji:

$varfile = "c:\names.txt"

        $varexists = Test-Path $varfile

        if ($varexists –eq $false) {

        Write-Output "Plik $varfile nie istnieje"

        break

        }

        $varnames = Get-Content $varfile

Jest kilka rzeczy, które wolałbym zmienić w takim przykładzie. Po pierwsze i przede wszystkim: nie ma potrzeby „wciskania” wszystkich informacji w zmienne. Można przepisać ten kod przy użyciu mniejszej ich liczby. Dlaczego? No cóż, dla powłoki nie ma znaczenia, jak wielu zmiennych użyjemy, ale jestem wielkim (naprawdę WIELKIM) fanem tworzenia skryptów, które będą jak najbardziej czytelne – by ktoś, kto będzie czytał skrypt, był w stanie wyobrazić sobie, co on robi. Im więcej zmiennych włączymy, tym więcej ten ktoś będzie musiał „przetwarzać” w pamięci – i tym mniejsza szansa, że uda mu się odtworzyć działanie skryptu. Jeśli nie potrafię zrozumieć, co robi skrypt, patrząc na jego wydruk, tym mniejsze są moje szanse jego zdebugowania w razie wystąpienia problemów. Zatem zacznę od przepisania tego przykładu w następujący sposób:

$varfile = "c:\names.txt"

        If ((Test-Path $varfile) –eq $false) {

        Write-Output " Plik $varfile nie istnieje"

        break

        }

        $varnames = Get-Content $varfile

Wszystko, co zrobiłem, to wyeliminowanie zmiennej $varexists i bezpośrednie wykorzystanie wyjścia z polecenia Test-Path. Tą metodą nie musiałem sięgać po konstrukt If, sprawdzać $varexists, po czym wracać i odkrywać, czym jest $varexists. Zmniejsza to liczbę informacji, o których muszę pamiętać. Zwróćmy uwagę, że pozostawiłem zmienną $varfile; zrobiłem tak, gdyż informacja ta jest używana trzy razy – jeśli korzystamy z czegoś kilka razy, wówczas warto to umieścić w zmiennej.

 Do początku strony Do początku strony

Znaczące nazwy zmiennych

Pozostając przy tym samym przykładzie, należy pamiętać, aby nazwy zmiennych pomagały w ustaleniu, co te zmienne przechowują. Nie ma potrzeby używania przedrostków, takich jak „var”, gdyż znak $ na początku informuje, że to zmienna. W istocie prefiksy takiej „str” dla łańcuchów (strings) czy „int” dla liczb całkowitych (integers) można uznać za przestarzałe („jak w 1993”, jaka mawiam podczas moich wykładów). Użyjmy po prostu czytelnych nazw. Oto ponownie przepisany przykład:

$filename = "c:\names.txt"

       If ((Test-Path $filename) –eq $false) {

       Write-Output "Plik $filename nie istnieje"

       break

       }

       $computernames = Get-Content $filename

 Do początku strony Do początku strony

Upraszczanie porównań

Nie jestem fanem operatorów porównania w Windows PowerShell – tych wszystkich gt, lt, eq i tak dalej. Mój brak zamiłowania zapewne wynika z długich doświadczeń z VBScript, gdzie mogłem używać bardziej przyjaznych operatorów, jak >, < czy =. Większość z nas pamięta te operatory z lekcji matematyki w podstawówce i wie, że wymagają one znacznie mniejszego wysiłku podczas czytania. O ile jest to więc możliwe, staram się unikać stosowania operatorów. Zaś każdy warunek, w którym sprawdzamy, czy coś jest prawdą, czy też fałszem, jest okazją do usunięcia operatora:

$filename = "c:\names.txt"

       If ((Test-Path $filename) –eq $false) {

       Write-Output "Plik $filename nie istnieje"

       break

       }

       $computernames = Get-Content $filename

Ponieważ cmdlet Test-Path zwraca wartość logiczną True (prawda) albo False (fałsz), nie ma potrzeba porównywania tego wyniku z True lub False. Wszystko wewnątrz nawiasów konstruktu If musi ostatecznie sprowadzić się do True albo False. W innej sytuacji trzeba by użyć porównania, aby to osiągnąć, ale jeśli jakiś element wewnątrz już generuje logiczne True lub False, możemy po prostu użyć tej informacji bezpośrednio.

Muszę tu zauważyć, ze operator not jest tym jedynym, który lubię. Jest znacznie bliższy zwykłego angielskiego, niż jego odpowiedniki (na przykład !=, ~ czy ^) używane w wielu językach programowania.

Ponadto powinienem jeszcze wspomnieć o nawiasach, które umieściłem wokół cmdlet Test Path i jego parametru wejściowego $filename. Zacząłem umieszczać polecenia w nawiasach, gdyż pozwala to wizualnie wyróżnić je jako pojedynczy byt. Windows PowerShell używa głównie spacji, a nie innych znaków, do oddzielania takich elementów jak polecenia, nazwy parametrów czy ich wartości. Obramowanie całego ciągu polecenia nawiasami pozwala zaznaczyć całość, bez konieczności wpatrywania się, która spacja oznacza początek, a która koniec polecenia.

 Do początku strony Do początku strony

Nie przerywajmy skryptu

Ostatnia sugestia, którą mam odnośnie omawianego przykładu, dotyczy budowy jego logiki. Jeśli plik nie istnieje, przerywamy działanie skryptu. Jeśli jednak istnieje, nie powinniśmy wykonywać wyrażenia Break, ale kontynuować wykonywanie skryptu. Gdy tylko jest to możliwe, wolę unikać używania Break w celu kończenia działania skryptu. Przepiszę ten przykład jeszcze raz:

$filename = "c:\names.txt"

        If (Test-Path $filename) {

        $computernames = Get-Content $filename

        # zrób cokolwiek, co zamierzałeś

        } else {

        Write-Output "Plik $filename nie istnieje"

        }

W ten sposób uprościłem logikę konstruktu If. Wyeliminowałem też potrzebę użycia wyrażenia Break, co zmniejsza wątpliwości na temat tego, co skrypt ma robić. Jestem skłonny przyznać, że ta sugestia to wspomniane wcześniej dzielenie włosa na czworo, ale myślę, że dzięki dobrym praktykom kodowania będziemy tworzyć lepsze skrypty, a to jest na pewno właściwsze podejście niż wyskakiwanie ze skryptu w połowie drogi. Taka konstrukcja jest łatwiejsza do debugowania i znacznie prościej jest ją analizować na wydruku, gdy patrzymy na skrypt i próbujemy ustalić, co on robi.

 Do początku strony Do początku strony

Pętle, pętle...

Wiele skryptów, które przeglądałem, musiało odczytywać informacje z pliku tekstowego i wykonywać te same czynności w pętli dla każdego wiersza tego pliku. Przyjmijmy, że chcemy skontaktować się z kilkoma komputerami, zatem przygotujemy listę ich nazw, po jednej w wierszu, w pliku tekstowym. Oto typowy przykład konstrukcji skryptu, jakie oglądałem:

$line = 0

        $names = get-content c:\names.txt

        while ($line –lt $names.length) {

          # zrób coś z $names[$line]

        $line++

        }

Ten fragment ładuje plik do zmiennej $names, po czym ręcznie wylicza cały zbiór, tak jakby był on macierzą. Zmienna $line przechowuje informację, nad którym wierszem właśnie pracujemy. Skrypt ten działa – ale robi nieco więcej, niż naprawdę potrzeba, zaś kodu tego nie można nazwać łatwym do odczytania przez kogoś innego. Konstrukt ForEach dostępny w Windows PowerShell jest znacznie właściwszą metodą wyliczania zbiorów (technicznie rzecz biorąc, Get-Content zwraca zbiór, a nie macierz). Spróbujmy więc tak:

Foreach ($name in (Get-Content c:\names.txt))

        {

          # zrób coś z $name

        }

Mamy tu kilka korzyści. Trzymając się wcześniejszej sugestii, umieściłem wywołanie Get-Content bezpośrednio wewnątrz konstruktu, gdyż moim zdaniem sprawia to, że lepiej widać, co naprawdę się dzieje. ForEach wyliczy wszystkie elementy zbioru, bez względu na to, jak duży jest ten zbiór. Za każdym obiegiem pętli pobierze kolejny element zbioru i umieści go w zmiennej $name. Tak więc nazwa zmiennej reprezentuje jej zawartość – nazwę komputera, dzięki czemu skrypt staje się łatwiejszy do czytania i edycji.

Nie musimy martwić się o to, w którym wierszu pliku wejściowego skrypt się znajduje ani stosować w tym celu dodatkowej zmiennej $line – konstrukt ForEach robi to za nas. Wyeliminowanie trzech wierszy kodu sprawia, że jest on nieco bardziej zwięzły i wymaga śledzenia mniejszej liczby rzeczy, gdy próbujemy ustalić, co dany skrypt robi.

 Do początku strony Do początku strony

Czemu tak cicho?

Jedną praktyką, którą chciałbym naprawdę napiętnować, jest wyłączanie komunikatów o błędach w całym skrypcie. Powiedzmy, że przeglądamy skrypt, który zaczyna się od wiersza:

$ErrorActionPreference = "SilentlyContinue"

Zatrzymać to natychmiast!

Polecenie to nie sprawia, że błędy znikną – one zaledwie zostaną ukryte. Tak, istnieją sytuacje, gdy możemy przewidzieć i zechcieć zignorować określony błąd, na przykład gdy korzystamy z Windows Management Instrumentation (WMI) do połączenia się ze zdalnymi maszynami, które potencjalnie mogą być niedostępne. Nigdy jednak nie ma powodu, by ignorować błędy globalnie. Komunikaty o błędach są przydatne i pozwalają dostrzec problemy, które – niekiedy – możemy naprawić.

Jeśli chcemy pominąć komunikaty błędów dla konkretnego polecenia, możemy to zrobić, dodając do niego parametr EA "SilentlyContinue". Jeśli chcemy zignorować błędy kilku poleceń w jednym wierszu, możemy albo dodać parametr EA do każdego z nich albo dołączyć wiersz $ErrorActionPreference (taki jak pokazany wcześniej) bezpośrednio przed tym blokiem poleceń, a po nim dodać kolejny wiersz:

$ErrorActionPreference = "Continue"

Spowoduje on przywrócenie normalnej obsługi błędów.

Osobiście nigdy nie wyłączam komunikatów błędów, chyba że używam własnego mechanizmu ich obsługi, na przykład w taki sposób:

Trap {

              # przekaż komunikat błędu do pliku dziennika albo coś w tym rodzaju

             }

        Get-WmiObject Win32_Service –computerName $remote –EA Stop

Ten przykład ukazuje, jak parametr –EA "Stop" pozwala wygenerować prawdziwy wyjątek zamiast po prostu komunikatu o błędzie, dając możliwość jego przechwycenia i obsłużenia własną metodą. Komunikaty można zapisać do pliku, wyświetlić bardziej przyjazny komunikat błędu albo zrobić cokolwiek innego, co wydaje się odpowiednie w takim wypadku.

 Do początku strony Do początku strony

Pokażcie swoje skrypty

Jak powiedziałem, niektóre z tych porad są bardziej kosmetyczne, ale wszystkie oferują konkretne korzyści, które warto rozważyć. Zapraszam wszystkich do dzielenia się własnymi skryptami Windows PowerShell. Kilka z nich analizuję co miesiąc i publikuję sugestie na ConcentratedTech.com. Zapraszam do przejrzenia moich wcześniejszych wskazówek i przysyłania własnych skryptów do przejrzenia.

 Do początku strony Do początku strony

Windows PowerShell - Pytania i odpowiedzi

Pytanie: Czy istnieje sposób zdefiniowania stałego, niestandardowego aliasu w Windows PowerShell?

Każdy, kto kiedykolwiek utworzył nowy alias, zauważył zapewne, że istnieje on tylko do momentu zamknięcia powłoki. Poniższy przykład tworzy nowy alias d, będący skrótem do polecenia Get-ChildItem:

New-Alias d Get-ChildItem

Jednak po zamknięciu powłoki alias zostanie utracony. Rozwiązaniem jest utworzenie profilu Windows PowerShell. W folderze Documents należy utworzyć nowy folder o nazwie WindowsPowerShell. W nim umieszczamy plik tekstowy o nazwie profile.ps1, zawierający dowolne polecenia, które mają być wykonane automatycznie przy każdym uruchomieniu powłoki – w tym nowe definicje aliasów. Profil gwarantuje, że polecenia będą wykonywane za każdym razem, tworząc „trwałe” aliasy, które zawsze będą dostępne w Windows PowerShell.

$var1 = 'Windows'

$var2 = "Microsoft $var1"

Po wykonaniu tych dwóch poleceń zmienna $var2 będzie zawierała łańcuch "Microsoft Windows" (zmienna $var1 zostanie zastąpiona przez jej zawartość). W celu uniknięcia nieporozumień zalecane jest stosowanie wyłącznie pojedynczych cudzysłowów, o ile nie zachodzi potrzeba jawnego wykorzystania opisanej funkcjonalności podstawiania zmiennych.

O autorze

Don Jones jest współzałożycielem firmy (i witryny) Concentrated Technology, zawierającej blogi na temat Windows PowerShell, SQL Server, App-V i inne.

 Do początku strony Do początku strony

Centrum Skryptów