Než začneme s dnešním tématem, uděláme si malou exkurzi do novinek ze světa PowerShellu. V dubnu se v Las Vegas konal další ročník konference TEC (The Experts Conference), její součástí byla i první PowerShell Deep Dive konference. Na této konferenci byla oznámena pěkná novinka - specifikace PowerShellu byla uvolněna pod licencí Microsoft Community Promise. Na první pohled nic zajímavého, ale pokud se do specifikace podíváte, najdete v ní mnoho zajímavých informací o fungování PowerShellu.

Základní porovnávání textu

V posledním dílu jsem slíbil, že se dnes podíváme na regulární výrazy. Musím se ale předem omluvit. Zamýšlený úvod se rozrostl více než jsem předpokládal a proto si regulární výrazy necháme až do dalšího pokračování.

Nejdřív se tedy podíváme na jednoduché a rychlé porovnávání textu. S tímto typem porovnávání si vystačíme ve většině případů.

Poznámka: V tomto článku budu mluvit o porovnání textů, ale vše samozřejmě platí i pro jiné datové typy.

PowerShell nám nabízí základní operátory porovnání, jsou to např. –eq, -gt, -like, -contains. Nebudu je vyjmenovávat všechy, jejich seznam najdete v tématické nápovědě about_Comparison_Operators. Než si ukážeme jednoduchý příklad, je potřeba zmínit ještě jednu věc. Pro většinu operátorů platí, že pokud je na levé straně porovnání (vstup) jediná hodnota, výstupem je true nebo false. Pokud je na vstupu více hodnot, výstupem jsou všechny vstupy splňující podmínku.

PS C:\> 'PowerShell' -eq 'powershell'
True
PS C:\> 'PowerShell','VBS','Perl','bash' -eq 'powershell'
PowerShell
PS C:\> 'PowerShell','VBS','Perl','bash' -ne 'powershell'
VBS
Perl
Bash

PS C:\> 'PowerShell' -ceq 'powershell'
False
PS C:\> 'PowerShell','VBS','Perl','bash' -cne 'powershell'
PowerShell
VBS
Perl
Bash
PS C:\> 'PowerShell','VBS','Perl','bash' -ceq 'powershell'

PS C:\> 1..10 -gt 5
6
7
8
9
10

PS C:\> 1..10 -le 5
1
2
3
4
5

První tři příkazy jsou jednoduché, zjišťujeme, jestli je v zadaném vstupu text „PowerShell“. Další tři příkazy již porovnávají text včetně kontroly velkých/malých písmen (c v názvu od case-sensitive) a proto nám první z nich vrátí hodnotu false. Poslední dva příkazy ukazují porovnání pomocí operátorů větší než/menší rovno.

Poznámka: Velká diskuse se strhla kvůli pojmenování operátorů písmeny a nepoužití „zažitých“ operátorů jako je například ‘>‘. Níže uvádím citaci z knihy PowerShell in Action od Bruce Payetta, který je jedním z designérů jazyka PowerShellu:

Let’s talk about the most contentious design decision in the PowerShell language. And the winner is: why the heck did we not use the conventional symbols for comparison like “>”, “>=”, “<”, “<=”, “==”, and “!=” ? My, this was a touchy issue. The answer is that the “>” and “<” characters are used for output redirection. Since PowerShell is a shell and all shell languages in the last 30 years have used “>” and “<” for I/O redirection, people expected that PowerShell should do the same. During the first public beta of PowerShell, this topic generated discussions that went on for months. We looked at a variety of alternatives, such as modal parsing where sometimes “>” meant greater-than and sometimes it meant redirection. We looked at alternative character sequences for the operators like “:>” or “->”, either for redirection or comparison. We did usability tests and held focus groups, and in the end, settled on what we had started with. The redirection operators are “>” and “<”, and the comparison operators are taken from the UNIX test(1) command. We expect that, since these operators have a 30-year pedigree, they are adequate and appropriate to use in PowerShell. (We also expect that people will continue to complain about this decision, though hopefully not for 30 more years.)

Mě osobně použité řešení vyhovuje. Přemýšlení, jestli ‘=‘ je porovnání nebo přiřazení a jestli ‘<>’ je to same jako ‘!=’ mě v jiných programovacích jazycích trošku vadí (i když chápu, že vývojář, který píše každý den v C# nemusí mou radost/starost sdílet).

Dalšími operátory, které se hodí pro porovnání textu, jsou like a match. Like funfuje tak, jak je většina z nás zvyklá z operačního systému. Využívá znaků * a ? – použití je stejné již od dob MS-DOSu. Všichni už určitě někdy psali *.txt. Méně známá je možnost použití hranatých závorek pro výčet, např. [a-d] znamená jakýkoli ze znaků a, b, c, d. Ukažme si vše opět na příkladu.

PS C:\> 'PowerShell' -like 'powershell'
True
PS C:\> 'PowerShell' -clike 'powershell'
False
PS C:\> 'PowerShell' -like 'shell'
False
PS C:\> 'PowerShell' -like '*shell'
True
PS C:\> 'PowerShell' -like 'P*shell'
True
PS C:\> 'PowerShell' -like 'P[o-q]*shell'
True
PS C:\> 'PowerShell' -like 'P[p-q]*shell'
False
PS C:\> 'PowerShell' -like 'P[awpo]*shell'
True
PS C:\> 'PowerShell' -like 'P[awp]*shell'
False

Nejprve porovnáváme like a case-sensitive like. Třetí ař pátý příklad ukazují (očekávané) použití hvězdičky. Další příklady už používají zmiňovaný operátor rozsahu. Pokud bychom trvali na velikosti písmen, mohli bychom slovo PowerShell porovnat například takto.

PS C:\> 'PowerShell' -clike '[A-Z]??[aeiouy][rs][RS]hell*'
True

Z předchozího jsou vidět dvě věci – otazník opravdu zastupuje jeden libovolný znak a hvězdička (na konci) znamená ve skutečnosti i nulový výskyt znaku. Vše si ještě potvrdíme závěrečnou ukázkou.

PS C:\> '' -like '*'
True
PS C:\> '' -like '?'
False

Operátor match porovnává vstupní texty pomocí regulárních výrazů. K nim se dostaneme příště, nyní si ještě ukážeme jeden užitečný cmdlet.

Select-String

Pokud chcete ve vstupním textu (nebo souboru) vyhledávat vzory textu, je Select-String ideálním pomocníkem. Zkusme se podívat, jestli se v log souborech objevují hlášení o chybách.

PS C:\temp> ls kb25????2.log | Select-String 'error' -SimpleMatch

KB2506212.log:6:2.500: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2
KB2506212.log:8:3.078: In Function GetReleaseSet, line 1240,
RegQueryValueEx failed with error 0x2
KB2506212.log:34:3.484: KB2506212 Setup encountered an error: The update.ver file is not correct.
KB2506212.log:48:3.828: Update.exe extended error code = 0xf200
KB2506212.log:53:1.875: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2
KB2506212.log:55:2.234: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2
KB2506212.log:61:3.765: Update.exe extended error code = 0xf201
KB2506212.log:67:1.922: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2
KB2506212.log:86:2.328: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2
KB2506212.log:112:2.687: KB2506212 Setup encountered an error: The update.ver file is not correct.
KB2506212.log:113:2.687: KB2506212 Setup encountered an error: The update.ver file is not correct.
KB2506212.log:114:2.687: KB2506212 Setup encountered an error: The update.ver file is not correct.
KB2506212.log:115:2.687: KB2506212 Setup encountered an error: The update.ver file is not correct.
KB2508272.log:7:1.906: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2
KB2508272.log:38:2.313: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2
KB2508272.log:53:2.406: KB2508272 Setup encountered an error: The update.ver file is not correct.

Pokud vstupní soubory obsahují hledaný text, je vypsána každá řádka, kde se vyskytuje, včetně čísla této řádky. Čistě pro kontrolu, vstupní soubor vypadá takto:

clip_image002

Častokrát nám ovšem stačí pouze vědět, že v souboru je nějaká chyba – soubor můžeme pak zkopírovat či poslat mailem na další kontrolu. Zkusme hledat čas, kdy začal zápis do logu z předchozího obrázku (23:20:44)

PS C:\temp> ls kb*.log | Select-String '23:20:44' -SimpleMatch -Quiet

False
False
False
False
True
False
False
False
False
False
False
False

J Parádní výsledek, kdy jsme se dozvěděli, že ve vstupních souborech je jeden, který vyhovuje našemu zadání. Abychom se dozvěděli i jméno tohoto souboru, musíme náš příkaz trochu upravit:

PS C:\temp> ls kb*.log | Where-Object { Select-String '23:20:44' -Input $_ -Simple -Quiet } | Select Name

Name
----
KB2506212.log

Seznam vstupních souborů jsme filtrovali pomocí Where-Object, pokud soubor obsahoval hledaný text, tzn. cmdlet Select-String vrátil hodnotu true, vypsali jsme jméno souboru.

Pokud jsme nalezli potřebný soubor, můžeme se dále podívat, kde se hledaný text vyskytuje. Velice užitečným parametrem je Context. Udáváme jím, kolik řádek před/za hledaným, chceme zobrazit ve výpisu.

PS C:\temp> Select-String -Path kb2506212.log -Pattern '23:20:44' -SimpleMatch -Context 2

  kb2506212.log:1:[KB2506212.log]
 
kb2506212.log:2:2.500: ================================================================================
> kb2506212.log:3:2.500: 2011/04/14 23:20:44.390 (local)
  kb2506212.log:4:2.500: C:\WINDOWS\SoftwareDistribution\update\update.exe (version 6.3.13.0)

  kb2506212.log:5:2.500: Hotfix started with following command line: /si

PS C:\temp> Select-String -Path kb2506212.log -Pattern '23:20:44' -SimpleMatch -Context 1, 5

  kb2506212.log:2:2.500: ================================================================================
> kb2506212.log:3:2.500: 2011/04/14 23:20:44.390 (local)
  kb2506212.log:4:2.500: C:\WINDOWS\SoftwareDistribution\update\update.exe (version 6.3.13.0)

  kb2506212.log:5:2.500: Hotfix started with following command line: /si
 
kb2506212.log:6:2.500: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2
 
kb2506212.log:7:3.078: DoInstallation: CleanPFR failed: 0x2
 
kb2506212.log:8:3.078: In Function GetReleaseSet, line 1240, RegQueryValueEx failed with error 0x2

Jako hodnoty parametru můžeme uvést jedno nebo dvě čísla. Pokud uvedeme jedno, PowerShell zobrazí daný počet řádek před a za hledaným textem. V případě, že uvedeme čísla dvě, platí první pro počet řádek před hledaným textem a druhé pro počet řádek za ním.

Select-String může porovnávat vstupní text i pomocí regulárních výrazů. Jistě jste si všimli, že jsem u všech příkladů použil parametr SimpleMatch – ten říká, že nechci pro porovnání regulární výrazy použít. Na konci příštího dílu si vyzkoušíme použití Select-String bez parametru SimpleMatch.

Závěrem

Než úplně skončíme, vrátíme se ještě na chvilku k porovnávání pomocí operátorů. Zajímavý je operátor is. Tím testujeme jakého typu je vstupní parametr.

PS C:\temp> 'PowerShell' -is 'System.String'
True

Toho můžeme využít pro pěkný trik. Pokud žádáme po uživateli jméno heslo, můžeme v PowerShellu použít cmdlet Get-Credentials. Pokud máme funkci a nevíme, jestli je vstupem pouze uživatelské jméno nebo již credentials, lze použít následující trik (v PowerShellu v2 bychom mohli situaci řešit pomocí advanced funkcí elegantněji, teď mi jde čistě o ukázku operátoru is):

function Get-Cred
{
    param ($cred)
    Write-Host "Before: $($cred.GetType().FullName)"
    if ($cred -is 'System.String')
    {
        $cred = Get-Credential -Credential $cred
    }
    Write-Host "After: $($cred.GetType().FullName)"
}

V prvním případě zadáváme jako vstup text:

PS C:\> Get-Cred -cred aaa
Before: System.String
After: System.Management.Automation.PSCredential

Ve druhém už předáváme credentials

PS C:\> $c = Get-credential -credential bbb
PS C:\> Get-Cred -cred $c
Before: System.Management.Automation.PSCredential
After: System.Management.Automation.PSCredential

- David Moravec