Při práci v PowerShellu, stejně jako v jakémkoliv jiném programovacím či skriptovacím jazyku, narazíte čas od času na nějakou zajímavost, nebo obecný vzorec, který vám může usnadnit práci, zpřehlednit ji, nebo vám konečně pomůže pochopit nějaký obecný princip. V tomto článku vám předložím některé z těchto tipů a zajímavostí. Věřte ale, že je to jen malá část.
Navíc se snažím nerozebírat pokročilá témata, ale ukázat především tipy základní. Doufám, že alespoň některé z nich pro vás budou nové.
PowerShell se používá dvěma různými způsoby: většinu kódu máme ve svých skriptech a tyto pouštíme podle potřeby. Anebo máme otevřenou konzoli a píšeme ps kód a spouštíme bez ukládání.
V prvním případě je velmi důležitá přehlednost kódu, jeho srozumitelnost a pochopitelnost. Proto se doporučuje používat plná jména cmdletů, ne jen gci ale Get-ChildItem (nehledě na to, že na jiném systému může gci být aliasem pro něco úplně jiného).
gci
Get-ChildItem
Ve druhém případě ovšem používáme hojně aliasů (gci|?{!$_.PSIsContainer}|select -exp Length) a co nejkratších konstrukcí. Důležité je dosáhnout cíle, ale téměř nezáleží na tom jak. Díky tomu, že PowerShell je velmi benevolentní, umožní nám pojmenovat funkce a aliasy velmi zajímavými jmény. Podívejte se na příklady.
gci|?{!$_.PSIsContainer}|select -exp Length
PS> # dobře známé funkce ?? a ?: PS> function ?? { if ($args[0]) { $args[0] } else { $args[1] } } PS> function ?: { if (&$args[0]) { $args[1] } else { $args[2] } } PS> ?? $null 'default value' default value PS> ?: {1} 'is 1' 'is not 1' is 1 PS> ?: {get-process nonexisting -ea 0} 'process exists' 'process doesn''t exist' process doesn't exist PS> function \ { 'this is slash' } PS> function * { '*'*10 } PS> # definuje funkci, jejíž jméno je CTRL+D PS> New-Item -Path "function:$([char][int]4)" -ItemType function –Value { write-host 'CTRL+D!' }
I když PowerShell nechává na nás, jak se budou naše funkce jmenovat, dělejme tak s rozmyslem.
Nejlépe a okamžitě vše bude vidět na příkladu
PS> 1kb, 1mb, 1gb, 1tb, 1pb 1024 1048576 1073741824 1099511627776 1125899906842624
Na konec číselné hodnoty můžete přidat jednotku (v bytech). PowerShell toto automaticky vyhodnotí za vás. V důsledku pak zjednodušíte kód např. takto: gci | ? { !$_.PSIsContainer -and $_.Length -gt 15mb }
gci | ? { !$_.PSIsContainer -and $_.Length -gt 15mb }
Tato technika není příliš často používána, ale v mnoha případech dokáže nahradit použití cmdletů a pipeline. Pro demonstraci předpokládejme, že náš adresář obsahuje tyto soubory:
ch01-2010-03-01.txt ch01-2010-03-02.txt ch02-2010-03-01.txt ch02-2010-03-02.txt ch03-2010-03-01.txt ch04-2010-03-01.txt
My z nich chceme vyfiltrovat jen ty, které začínají ch01. Z nich pak chceme získat pouze střední část zkonvertovatelnou na [datetime]. Tradičně bychom toto mohli provést přibližně takto:
[datetime]
Get-ChildItem c:\temp\aa\ | select -exp Name | ? { $_ -like 'ch01*'} | % { $_ -replace 'ch01-|\.txt','' }
Ale stejně tak můžeme použít i následující způsob. Všimněte si, že část select -exp Name v tomto případě není nutná, protože dojde ke konverzi [FileInfo] na string, při níž se bere jméno souboru.
select -exp Name
[FileInfo]
(Get-ChildItem c:\temp\aa\ | select -exp Name) -like 'ch01*' -replace 'ch01-|\.txt',''
Zde jsme použili dvou vlastností: Zaprvé – operátory se dají řetězit. Tedy i operátor replace by někdo kvůli čitelnosti mohli rozdělit na dva. Výsledek by pak vypadal takto: ...-replace 'ch01-','' -replace '\.txt',''. Výsledek předchozího operátoru se uplatní při vyhodnocování následujícího operátoru. Toto jsem neviděl pořádně zdokumentované, tedy se odvolávám pouze na své dosavadní zkušenosti. Zadruhé – některé operátory pracují nejen nad skalárními hodnotami, ale také nad poli. Proto jsme mohli operátorům like a replace jako levý operand předat pole a vrátila se nám korektní hodnota. V případě, že bychom ale uvedený příklad chtěli dovést až do konce a hodnoty 2010-03-02 a podobné chtěli převést na datetime, pak toto je špatně: ... -replace 'ch01-|\.txt','' -as [datetime]. V tomto případě se operátor snaží převést vstupní objekt (pole) na čas a toto pochopitelně nedopadne dobře. Stačí ale drobná změna a máme funkční kód.
replace
...-replace 'ch01-','' -replace '\.txt',''
like
datetime
... -replace 'ch01-|\.txt','' -as [datetime]
Srovnejte:
Get-ChildItem c:\temp\aa\ | select -exp Name | ? { $_ -like 'ch01*'} | % { $_ -replace 'ch01-|\.txt','' } | % { $_ -as [datetime] } | ? { $_ -le '2010-03-01' }
(Get-ChildItem c:\temp\aa\) ` -like 'ch01*' ` -replace 'ch01-|\.txt','' ` -as [datetime[]] ` -le '2010-03-01'
$$, $^
$^ a $$ jsou automatické proměnné, které mají smysl hlavně při interaktivní práci v shellu. Ne vždy je použijete, ale hodí se je znát. O co jde, je nejlépe vidět na příkladu:
$^
$$
PS> Get-ChildItem -rec c:\temp\powershelltest\version1 ...výpis souborů PS> $^ Get-ChildItem PS> $$ c:\temp\powershelltest\version1
Proměnné obsahují první a poslední token na předchozím řádku. Přesněji: Contains the first token in the last line received by the session a Contains the last token in the last line received by the session. Mohou vám tak ušetři hlavně psaní dlouhých cest do příkazů jako je Get-ChildItem, Get-Item atd. Otázka na StackOverflow dokládá, že se najdou tací, kteří tuto proměnnou skutečně používají.
Get-Item
Pro mě nejužitečnější tip k práci s objekty a typy je přetypování. Nemám na mysli operátory, ale jednodušší způsob, jak využít jednoparametrický konstruktor. Příklady napoví.
PS> Add-Type @" using System; using System.Net; public class Test1 { public Test1(string s) { Console.WriteLine("Test1 - string ctor: {0}", s); } public Test1(int i) { Console.WriteLine("Test1 - int ctor: {0}", i); } public Test1(WebClient c) { Console.WriteLine("Test1 – webclient ctor: {0}", c); } } public class Test2 { public Test2(string s1, string s2) { Console.WriteLine("Test1 - string ctor: {0}, {1}", s1, s2); } } "@ PS> [Test1] 'a' > $null # pouzije 1. konstruktor PS> [Test1] 1 > $null # pouzije 2. konstruktor PS> [Test1] (New-Object Net.WebClient) > $null # pouzije 3. konstruktor PS> [Test1] (date) > $null # chyba - Multiple ambiguous overloads... PS> [Test2] @('a','b') > $null # bohuzel nejdePS> [system.net.ipaddress[]]'127.0.0.1','192.168.45.1','192.168.45.2','192.168.45.3'
Je tedy možné, pokud máme k dispozici jednoparametrický konstruktor, vyhnout se cmdletu New-Object a pouze použít přetypování.
New-Object
Při vytváření instancí nějakého .NET typu je potřeba vždy uvádět plnou cestu. Například New-Object System.Text.StringBuilder. Co nám může pomoct?
New-Object System.Text.StringBuilder
Předně není nutné psát System. Kratší zápis vypadá takto:
PS> $sb = new-object Text.StringBuilder
Pokud máme vytvářet více instancí z jednoho namespace a nechceme pořád daný namespace opisovat, můžeme si jej uložit do proměnné:
PS> $g = 'System.Collections.Generic.' PS> $list = New-Object ($g + 'List[int]')
Skládání stringů možná nikoho nepřekvapilo a možná to každému přišlo naprosto přirozené. Méně přirozeně může vypadat poslední tip k typům. Odkaz na třídu si můžeme uložit do proměnné a později použít hlavně při volání statických metod.
PS> Add-Type –assembly system.windows.forms #nacteme windows.forms PS> $forms = [System.Windows.Forms.MessageBox] PS> $forms::Show('Hello')
PSObject
Za časů PowerShell V1 se k instancím obecného objektu přídávaly property pomocí cmdletu Add-Member.
Add-Member
$info = new-object PSObject | Add-Member Noteproperty App 'ap' -pass | Add-Member Noteproperty Account 'account' -pass | Add-Member Noteproperty StatusId 1000 -pass
Někteří si život usnadňovali trikem pomocí Select-Object.
Select-Object
$info = '' | Select-Object App,Account,StatusId $info.App,$info.Account,$info.StatusId = 'app','account',1000
V PowerShellu V2 ale máme k dispozici nový parametr -property, který zápis velmi usnadní a zpřehlední. Už nikdy víc Add-Member a Select-Object jen kvůli přidání property!
-property
$info = new-object PSObject -property @{App='app'; Account='account'; StatusId=1000 }
Pro nejjednodušší případy, kdy i vyrábění PSObjectu je pro nás zdlouhavé, můžeme použít hashtable. Přistupovat do ní totiž můžeme nejen pomocí hranatých závorek, ale i pomocí tečkové notace.
hashtable
PS> $h = @{Height=180; Weigh=80; Name='El'; Surname='Hombre' } PS> $h.Name El
Někdy může být zajímavé i použití více hodnot do indexu:
PS> $h['Height', 'Name'] 180 El
Tento přístup má ale i své nevýhody: Pří přístupu přes tečkovou notaci nefunguje doplňování pomocí [TAB]. Navíc se tyto "objekty" nedají třídit podle dané "property".
1..10 | % { New-Object PSObject -property @{Num=$_} } | Sort Num -desc #funguje 1..10 | % { @{Num=$_} | Sort Num -desc #nedava spravne vysledky
V PowerShellu V2 máme k dispozici splatting operátor @. Používá se při práci s parametry funkcí a cmdletů. Za běhu si můžeme určit, které parametry budeme chtít předat.
@
PS> function Write-Parameters { param([string]$p1, [int]$p2, [switch]$p3, [string]$p4) Write-Host P1: $p1 Write-Host P2: $p2 Write-Host P3: $p3 Write-Host P4: $p4 } PS> $parameters1 = @('2000') # pole PS> $parameters2 = @('2. polozka v poli') # pole PS> $switch = @{p3=$true; p1='P1' } # hashtable PS> Write-Parameters @parameters1 @parameters2 @switch PS> Write-Parameters 2000 '2.polozka v poli' -p3 -p1 P1
Všimněte si, že výstup z volání Write-Parameters je stejný v obou případech. Za jméno funkce/cmdletu můžeme předat pole, nebo hashtable, které určují vstupní parametry. Pokud předáme pole, účinek je podobný, jako bychom zapisovali pouze argumenty a spoléhali se na navázání na parametry pomocí pozice. Pokud předáme hashtable, argumenty jsou navázány stejně, jakobychom před každým z nich specifikovali jméno parametru.
Write-Parameters
Pozn.: vyhodnocování parametrů je relativně složitý proces. Více je popsáno v knize Windows PowerShell in Action. Rád bych jen upozornil, že v našem případě se nejdříve navážou argumenty určené explicitně, tj. před nimi je jméno parametru (např. -p1 P1). A až teprve poté se navazují zbylé argumenty pomocí pozice na dosud nenavázané parametry. V našem případě se tedy nejdříve naváže parametr p1 a p3. Teprve potom se začíná navazovat argument 2000. Parametr p1 už je navázaný, tedy je přeskočen. První volný je parametr p2, proto se hodnota naváže na něj. To stejné platí pro hodnotu 2. polozka v poli.
-p1 P1
Splatting použijeme tehdy, pokud chceme využít už existujících cmdletů, jako je např. Get-ChildItem.
PS> function Get-MyItems { param([switch]$all, [string]$extension, [string]$directory) $p = @{} if ($all) { $p['recurse'] = $true } if ($extension) { $p['filter'] = "*.$extension" } if ($directory) { $p['literalPath'] = $directory } Get-ChildItem @p } PS> Get-MyItems -extension txt PS> Get-MyItems -all -directory G:\temp\blog
Alias
Get-Alias
Narazili jste někdy na použití např. alias gm a divili se, co je to vlastně ten alias zač? V seznamu funkcí se nenachází, stejně tak v seznamu aliasů a cmdletů. Podobně funguje date, job, childitem, item, verb, service, atd.
alias gm
alias
date
job
childitem
item
verb
service
Pokud PowerShell zjistí, že neexistuje daný příkaz (např. můžeme mít definovanou funkci service), zjistí, zda existuje alias Get-…. Pokud alias existuje, zavolá jej. Pokud neexistuje, zkouší podobně existenci funkce a cmdletu a případně existující příkaz zavolá.
Get-…
To, že se proměnné v řetězci vyhodnocují, ví asi každý, kdo s PowerShellem pracuje.
Pokud jde o proměnnou, která obsahuje pole hodnot, pak tyto hodnoty jsou spojeny pomocí speciální proměnné $ofs. Pokud bychom chtěli hodnoty oddělit pomocí svislítek, můžeme použít toto:
$ofs
PS> $ofs = "|"; $pole = 1,2,3,4; "$pole" 1|2|3|4
Mnohem pěknější je ovšem v tomto případě použít operátor -join než nějakou magickou proměnnou.
-join
PS> $pole = 1,2,3,4; $pole -join "|" 1|2|3|4
Pokud chceme přistupovat k property objektu uložené v proměnné, musíme už použít závorek:
PS> $pole = 1,2,3,4; "velikost: $($pole.Length)" velikost: 4
Při vyhodnocování výrazu nejsme omezeni, tj. můžeme použít např. indexaci, můžeme vytvořit objekt, atd. Tato praktika se ovšem nedá obecně doporučit. Výraz lze téměř jistě vždy vyhodnotit dříve než až při použití v řetězci:
PS> "$((new-object net.webclient).DownloadString('http://google.com').Substring(0, 50))" <!doctype html><html><head><meta http-equiv="conte
Pokud se ovšem seznam vnořených property dozvíme až za běhu, můžeme použít chytré metody ExpandString:
PS> $x=[xml]'<a><b><c>some value</c><c>some second value</c></b></a>' PS> $prop='a.b.c[1]' #tento řetězec zjistime až za běhu PS> $stringToExpand = "`$(`$x.$prop)" PS> $ExecutionContext.InvokeCommand.ExpandString($stringToExpand)
-OutputVariable
-OutputVariable je parametr společný všem cmdletům. Používá se v situacích, kdy cmdlet vrací nějaké objekty (tj. u Write-Host nemá smysl) a chceme zachytit výstup z daného cmdletu do proměnné. Může se nám např. hodit při debugování nebo na zjednodušení skriptů. V příkladech budu používat alias OV.
Write-Host
OV
PS> Get-Process svchost -OV svchosts | ? { $_.Handles -gt 500 } -OV svchosts2 PS> $svchosts.Count 15 PS> $svchosts2.Count 4
Tento parametr funguje podobně jako cmdlet Tee-Object, který má mimojiné také možnost uložit obsah do proměnné, ale jednak je použití parametru jednodušší, druhak je možné do proměnné přidávat předřazením znaménka +.
Tee-Object
PS> Get-Process svchost -OV procs; Get-Process firefox -OV +procs; Get-Process powershell -OV +procs PS> $procs | select -exp ProcessName -unique svchost firefox powershell
Start-Transcript
Stop-Transcript
ii adresář
ii soubor
gci env:
Stop-Pipeline
ArrayList
$pole = 1..10; while($pole) { $p,$pole = $pole; write-host $p }
#
#<řetězec obsažený v nějakém předchozím příkazu>[TAB]
c:\temp\log??iis*
Možná je to všem programátorům zřejmé, možná ne, ale – v PowerShellu můžete pomocí Add-Type používat i Windows API. Joel Bennet má na PoshCode.org krásnou ukázku. Jeho modul umožní skrýt a zobrazit okna, nebo nastavit průhlednost oka. Použití je velmi jednoduché.
Add-Type
PS> $n = Select-Window | ? {$_.title -match 'untitled' } #vybere instanci Notepadu PS> $n | Set-GhostWindow -Percent 50 #nastav průhlednost na 50% PS> $n | Remove-GhostWindow # odstraň průhlednost
Pokud máte své vlastní tipy, které vám přijdou zajímavé, neváhejte a využijte komentářů. Ostatní čtenáři je jistě ocení!
- Josef Štefan
Další díly seriálu: Seriál: Windows PowerShell – PS a Active Directory (část 7.) Seriál: Windows Powershell – PS pro programátory (část 6.) Seriál: Windows Powershell - souborový systém a registry (část 5.) Seriál: Windows Powershell – dolujeme data aneb jak na WMI (část 4.) Seriál: Windows Powershell – roury a aliasy (část 3.) Seriál: Windows Powershell – objekty a roury (část 2.) Seriál: Windows Powershell – úvod (část 1.)