V nedávno skončených Scripting Games se často vyskytoval ve výsledných skriptem jeden „nešvar“ – výstup byl často podáván ve formě textu pomocí cmdletu Write-Host. Obecně proti Write-Host nic nemám, ale v pravidlech Scripting Games (a v diskusích) bylo často zmiňováno, že výstupem by měly být objekty. My dnes ale půjdeme opačnou cestou a podíváme se, jak vytvořit textový výstup pomocí operátoru formátování (-f).

Pozor: PowerShell je nad objekty postaven a práce s objekty je u něm přirozená. Ovšem občas se nám ale hodí na výstupu textová informace nebo prostě jenom chceme generovat velké množství textu.

Operátor formátování nám říká, jak chceme formátovat a co chceme formátovat. Vezměme si následující příklad:

PS C:\> $proc = Get-Process powershell
PS C:\> $proc

Handles  NPM(K)  PM(K)   WS(K)   VM(M)   CPU(s)   Id  ProcessName
-------  ------  -----   -----   -----   ------   --  -----------
    254       7  57780   57672     162     2.55 3856  powershell

PS C:\> "Process {0} ma Id {1}" -f $proc.Name, $proc.Id
Process powershell ma Id 3856

PS C:\> Write-Host "Process $proc.Name ma Id $proc.Id"
Process System.Diagnostics.Process (powershell).Name ma Id System.Diagnostics.Process (powershell).Id

PS C:\> Write-Host "Process $($proc.Name) ma Id $($proc.Id)"
Process powershell ma Id 3856

Do proměnné $proc jsme si uložili aktuální proces PowerShellu a na následujícím řádku jsme vypsali jeho jméno a Id pomocí operátoru formátování (k zápisu se hned vrátím). Vidíte, že na následujících dvou řádkách jsem zkusil vypsat tu samou informaci pomocí cmdletu Write-Host. V prvním jsem získal z mého pohledu naprosto neužitečnou informaci a teprve ve druhém správná data. Musel jsem totiž použít tzv. subexpression, což je příkaz PowerShellu uzavřený v kulatých závorkách uvozených znakem dolaru: $(<sem_patří_nějaký_příkaz>)

Pokud PowerShell vidí subexpression v textovém řetězci, provede nejdříve příkaz v závorce a teprve poté poskládá celý řetězec. V našem příkladu se tedy nejprve převedly $proc.Name a $proc.Id na správné texty a pak se celý řetězec složil do očekávané podoby.

A nyní zpátky k operátoru formátování. Na levé straně máme uvedeno, jak chceme formátovat:

"Process {0} ma Id {1}"

Čísla ve složených závorkách mi určují místa, kam přijdou proměnné z pravé strany operátoru formátování. Číslování je stejně jako u polí, uváděno o nuly, čili první proměnná je zobrazena symbolem {0}. Na pravé straně pak máme ony zmiňované proměnné:

$proc.Name, $proc.Id

které se nám ve výsledném řetězci přeloží na požadované hodnoty. Celý zápis

"Process {0} ma Id {1}" -f $proc.Name, $proc.Id

bychom tedy mohli přečíst jako: Vypiš text „Process“, dále vlož jméno procesu, text „ma Id“a následně Id zmiňovaného procesu. Pokud bychom například chtěli vypsat stejnou informaci pro více procesů, můžeme to provést následujícím způsobem:

PS C:\> Get-Process | Select -First 3 | ForEach { "Process {0} ma Id {1}" -f $_.Name, $_.Id }
Process AESTFltr ma Id 3104
Process ApMsgFwd ma Id 3904
Process ApntEx ma Id 924

Pro první tři procesy na mém počítači jsem vypsal požadovanou informaci. Pořadí vypisovaných informací můžeme samozřejmě libovolně měnit, můj oblíbený příklad je tento:

PS C:\> "{4}{4}{1}{5}{0}{3}{0}{5}{2}" -f 'e','ka','l','p','po','t'

Dáte dohromady výsledek? Ze zápisu je vidět, že jsem některé proměnné (zde na pravé straně zastoupené daným textem) použil vícekrát a rozhodně jsem nedodržoval dané pořadí na levé straně. Nicméně v případě takovéhoto zápisu už bych trochu pochyboval o čitelnosti J Mimochodem, výsledný text je popokatepetl.

Formátování ve formátování

V příkladu s výpisem více procesů jste si všimli, že výsledek není úplně oku lahodící. Někdy by se nám hodilo, aby se jednotlivé položky výstupu zarovnávaly pod sebe. Naštěstí operátor formátování disponuje možností dalšího formátování (proto ten divný nadpis). Zmiňovaný příklad můžeme tedy přepsat například takto:

PS C:\> Get-Process | Select -First 3 | ForEach { "Process '{0,8}' ma Id: {1,4}" -f $_.Name, $_.Id }
Process 'AESTFltr' ma Id: 3104
Process 'ApMsgFwd' ma Id: 3904
Process ' ApntEx' ma Id: 924

Pro lepší představu jsem do výstupu přidal jednoduché uvozovky. Zápis {0,8} říká, že nahrazovaný text bude dlouhý osm znaků a v případě, že bude kratší, bude doplněn zleva mezerami. To samé pravidlo se uplatní i v případě Id daného procesu ({1,4}), kde šířka textu bude čtyři znaky. Pokud bych použil větší číslo, výsledek bude následující:

PS C:\> "Process '{0,15}' ma Id: {1,8}" -f $proc.Name, $proc.Id
Process ' powershell' ma Id: 3856

Možná je ještě o trochu lepší způsob zarovnat text (jméno procesu) ve vytvořeném bloku na levou stranu. V tom případě použijeme šířku textu se zápornou hodnotou:

PS C:\> Get-Process | Select -First 3 | ForEach { "Process {0,-10} ma Id: {1,4}" -f $_.Name, $_.Id }
Process AESTFltr ma Id: 3104
Process ApMsgFwd ma Id: 3904
Process ApntEx ma Id: 924

Další možností úpravy je formátování pomocí formátovacího řetězce. Podíváme se na nejjednodušší formátování, například měny a poté se vrátíme k formátování čísel.

PS C:\> $mame=50
PS C:\> $nakup=63.5

PS C:\> "Kdyz budeme mit {0} a utratime {1}, zustane nam {2}." -f $mame, $nakup, ($mame-$nakup)
Kdyz budeme mit 50 a utratime 63.5, zustane nam -13.5.

PS C:\> "Kdyz budeme mit {0:C} a utratime {1:C}, zustane nam {2:C}." -f $mame, $nakup, ($mame-$nakup)
Kdyz budeme mit 50,00 Kč a utratime 63,50 Kč, zustane nam -13,50 Kč.

Do proměnných si uložíme částku, kterou máme na účtu a kterou pak utratíme. V prvním případě výsledný text nijak neformátujeme a dostáváme větu jak z automatického překladače. Ve druhém případě doplníme za znak dvojtečky formátovací řetězec pro zápis měny (Currency). Vidíte, že výsledek se bez jakéhokoli dalšího přispění změnil na vcelku srozumitelnou podobu mých výdajů a příjmů. Formátování hodnot je dáno nastavením prostředí Windows, takže v mém případě takto:

clip_image001

V závěrečném příkladu si ukážeme možnost formátování čísel. Příklad bude trošku složitější, nejdříve si ukážeme výsledek:

ProcessName            cpu    ws
---------------------------------
TOTALCMD             465.0  11.89
System               260.3   1.26
WINWORD              213.8  77.27
procexp              184.5  26.39
svchost              125.1  99.32
powershell_ise       101.0 164.04
explorer              98.9  30.96
svchost               75.8  29.45
aenrjpro              69.9   8.82
chrome                68.0  23.44

Výsledkem je TOP10 procesů podle využití CPU.

Začneme tedy získáním procesů.

PS C:\> $procesy = Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
PS C:\> $procesy

Handles  NPM(K)   PM(K)     WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------   -----     ----- -----   ------     -- -----------
   
196      7    6152      12160    62   519.73   4508 TOTALCMD
  
1104      0       0       1288     3   264.09      4 System
  
1150     21   65180      79224   274   222.66   3512 WINWORD
   
513     13   20812      27036   112   189.61   3556 procexp
   
706     50   71340     101704   542   125.13   2548 svchost
   
432     18  173456     168744   463   108.33   2952 powershell_ise
   
518     18   22552      31700   131   100.33   3372 explorer
  
2470    146   17576      29688   154    76.73    856 svchost
   
199      8   20864      24012   122    70.36   3388 chrome
   
276      9    7532       9036    74    69.94   1180 aenrjpro

Nyní si dáme dohromady hlavičku, kterou použijeme ve výpisu.

PS C:\> $header = "{0,-20}{1,6}{2,7}" -f 'ProcessName','cpu','ws'
PS C:\> $header = "{0}`n{1}" -f $header, ('-'*$header.Length)
PS C:\> $header
ProcessName          cpu       ws
---------------------------------

Již dopředu jsme si rozmysleli, jména a šířku jednotlivých sloupců. První řádka určuje jména sloupců a druhá řádka vytváří „čáru“ pro oddělení hlavičky od vlastních hodnot.

Poznámka: Všimněte si jedné zajímavosti – pokud násobíte dvě proměnné a první z nich je typu string (řetězec), bude výsledkem opakování tohoto řetězce, tedy

PS C:\> $a='xxxyyy'
PS C:\> $a*3
xxxyyyxxxyyyxxxyyy

PS C:\> $b='123'
PS C:\> $b*3
123123123
PS C:\> 3*$b
369

V posledním příkladu jsem jako první uvedl číslo a proto se celý výraz provádí jako násobení dvou čísel. Proto jsem v mé definované hlavičce mohl využít zápisu ('-'*$header.Length) pro opakované vypsání znaku -. Tím bychom měli definovanou hlavičku našeho výpisu. Nyní budeme formátovat vlastní data, podívejme se na následující zápis:

PS C:\> $c = 1.12345
PS C:\> "{0:###.0}`n{0:###.00}" -f $c
1.1
1.12

Počet desetinných míst dané proměnné je zobrazen v závislosti na počtu desetinných míst ve formátu. Toho můžeme využít při zarovnávání ve výsledné tabulce. Připravíme si proto jednotlivé části a vyzkoušíme je na naší proměnné z našeho dnešního prvního příkladu.

PS C:\> $proc

Handles  NPM(K)    PM(K)     WS(K) VM(M)   CPU(s)      Id ProcessName
-------  ------    -----     ----- -----   ------      -- -----------
   
470       7    56152     57788   163     4.95    3856 powershell

PS C:\> $p = "{0,-20}" -f $proc.ProcessName
PS C:\> $cpu = "{0,6:###.0}" -f $proc.CPU
PS C:\> $ws = "{0,7:###.00}" -f ($proc.WS/1MB)
PS C:\> "{0}{1}{2}" -f $p, $cpu, $ws
powershell 4.8 56.43

První formátování je nám již známé – určujeme šířku sloupce pro jméno procesu. Ve druhém a třetím kombinujeme šířku sloupce se zobrazením na určitý počet desetinných míst. Pro ověření můžeme samozřejmě vyzkoušet toto:

PS C:\> $cpu
  
4.8
PS C:\> $ws
  
56.43
PS C:\> $proc
powershell
PS C:\> $proc.Length
20

A vidíte, že jednotlivé položky jsou opravdu „předformátované“ podle naší potřeby. Nyní vše spojíme dohromady.

$procesy = Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
$header = "{0,-20}{1,6}{2,7}" -f 'ProcessName','cpu','ws'
$header = "{0}`n{1}" -f $header, ('-'*$header.Length)
$header
foreach ($p in $procesy) {
   
$proc = "{0,-20}" -f $p.ProcessName
   
$cpu = "{0,6:###.0}" -f $p.CPU
   
$ws = "{0,7:###.00}" -f ($p.WS/1MB)
   
"{0}{1}{2}" -f $proc, $cpu, $ws
}

A výsledkem bude náš očekávaný výstup:

ProcessName            cpu     ws
---------------------------------
TOTALCMD             465.0  11.89
System               260.3   1.26
WINWORD              213.8  77.27
procexp              184.5  26.39
svchost              125.1  99.32
powershell_ise       101.0 164.04
explorer              98.9  30.96
svchost               75.8  29.45
aenrjpro              69.9   8.82
chrome                68.0  23.44

Formátovací operátor se dá využít například pro generování textových souborů v určitém formátu. Připravíte si jednotlivé „šablony“ jako v předchozím případě a pak výsledný text pouze přesměrujete na potřebný výstup.

- David Moravec