När jag höll på och skriva mitt förra inlägg tänkte jag först använda ett lite längre skript som exempel, det scriptet skrev jag i VB.NET och grupperar alla filer baserat på extension och summera ihop storlekarna. Det skriptet ser ut som nedan:
Imports
System.IOImports
System.Collections.Generic
Module
GetFileSizesByExtension Sub Main(ByVal args() As String)
'Check arguments
If Not args.Length = 1 Then
Console.WriteLine("Syntax: getfilesizesbyextensions.exe <path>")
Return
End If
Dim dict As New SortedDictionary(Of String, Integer)
'Iterate through the files
For Each filename As String In Directory.GetFiles(args(1))
Dim fi As New FileInfo(filename)
Dim extension As String = fi.Extension.ToLower()
If dict.ContainsKey(extension) Then
dict(extension) += fi.Length
Else
dict.Add(extension, fi.Length)
End If
Next
'Print results
For Each extension As String In dict.Keys
Console.WriteLine("{0,-5} - {1}", extension, dict(extension))
Next
End Sub
End Module
När jag satt och skrev programmet slog det mig att Powershell troligen skulle passa utmärkt för att göra motsvarande, så jag tänkte att vi skulle försöka oss på detta.
Första steget är att få ut alla filerna, inga konstigheter där:
PS# dir C:\windows\system32
Det finns ett en CmdLet med namnet Group-Object ("group") och -property används för att berätta vad man skall gruppera på. Vi testar:
PS# dir \windows\system32 | group Extension
Count Name Group
----- ---- -----
49 {1025, 1028, 1031, 1033...}
5 .inf {$ncsp$.inf, $winnt$.inf, homepage.inf, ieuinit.inf...}
2 .cpx {12520437.cpx, 12520850.cpx}
1283 .dll {6to4svc.dll, aaaamon.dll, aaclient.dll, acctres.dll...}
24 .cpl {access.cpl, appwiz.cpl, bthprops.cpl, desk.cpl...}
304 .exe {accwiz.exe, actmovie.exe, ahui.exe, alg.exe...}
Kalas, vi är på rätt väg. Låt se, nu har vi en uppsättning objekt av type GroupInfo, där varje objekt innehåller namnet på extensionen, en lista med filerna som har den extensionen, samt antalet filer. Nu behöver vi ta reda på vad summan av storlekarna för filerna är.
Hmm... Alltså, för varje GroupInfo-objekt så vill vi ta listan (som finns i propertyn Group), och för varje objekt i listan vill vi ta ut storleken.
Det finns en CmdLet med namnet Measure-Object som kan summera och räkna på information i objekt:
PS# dir c:\windows\system32 | group Extension | % { ($_.Group | measure-object Length -sum).Sum }
Measure-Object : Property "Length" cannot be found in any object(s) input.
At line:1 char:75
+ dir c:\windows\system32 | group Extension | % { ($_.Group | measure-object <<<< Length -sum).Sum }
59520
4384
295688942
5656920
43885527
2947615
1588512
Okey, Det var en del på en gång, vi skall försöka dela upp det.
%-tecknet är förkortning för "ForEach-Object"; den CmdLet:en kör efterföljande scriptdel en gång för varje objekt (i det här fallet GroupInfo-objekt). I scriptet representeras det aktuella objektet med $_.
Så för varje GroupInfo-objekt så tar vi .Group och skickar in till Measure-Object. Där berättar vi att det är propertyn Length (den innehåller filstorleken) som vi vill summera. Measure-Objekt kommer att skicka tillbaka ett GenericMeasureInfo som bl a innehåller en Sum-property, vilken vi vill använda.
I ovanstående text ger Measure-Object ett felmeddelande för den första listan. Anledningen till detta är att "dir" ger tillbaka både FileInfo-objekt och DirectoryInfo-objekt. Det senare innehåller ingen Length-property. Så för att få bort felmeddelandet kan vi lägga in ett filter lite tidigare i kommandosträngen:
PS# dir c:\windows\system32 | ? { $_.GetType().Name -eq "FileInfo" }
? är förkortning för "Where-Object".
Nästa problem att lösa är att vi vill inte bara ha en lista med summerade storlekar. Om inte annat så vill vi ju veta vilken extension som genererat varje summering. Ett alternativ är att utöka GroupInfo-objektet med den summerings-informationen som Measure-Object gav or. Det vi kan göra är att använda "add-member":
PS# dir c:\windows\system32 | ? { $_.GetType().Name -eq "FileInfo" } | group Extension | % { $_ | add-member NoteProperty Size ($_.Group | measure-object Length -sum).Sum -pass}
Count Name Group
----- ---- -----
5 .inf {$ncsp$.inf, $winnt$.inf, homepage.inf, ieuinit.inf...}
2 .cpx {12520437.cpx, 12520850.cpx}
1283 .dll {6to4svc.dll, aaaamon.dll, aaclient.dll, acctres.dll...}
24 .cpl {access.cpl, appwiz.cpl, bthprops.cpl, desk.cpl...}
Så vi har använt add-member för att lägga till en NoteProperty med namnet Size och värdet av summeringen till GroupInfo-objektet. Vi har också lagt till "-passthru" för att berätta för add-member att objektet skall skickas vidare. De flesta andra CmdLets gör detta utan att man behöver säga åt dem.
Men i listan som skrivs ut så syns inte våran Size. För att se ifall den egentligen är där så testar vi att skicka vidare objekten till Get-Member (eller lite kortare "gm"). Get-Member kommer att titta på det första av de objekten som den får, och skriva ut vilka properties och sånt som objektet har.
PS# dir c:\windows\system32 | ? { $_.GetType().Name -eq "FileInfo" } | group Extension | % { $_ | add-member NotePropert
y Size ($_.Group | measure-object Length -sum).Sum -passthru} | gm
TypeName: Microsoft.PowerShell.Commands.GroupInfo
Name MemberType Definition
---- ---------- ----------
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
get_Count Method System.Int32 get_Count()
get_Group Method System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, System....
get_Name Method System.String get_Name()
get_Values Method System.Collections.ArrayList get_Values()
ToString Method System.String ToString()
Size NoteProperty System.Double Size=59520
Count Property System.Int32 Count {get;}
Group Property System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, System....
Name Property System.String Name {get;}
Values Property System.Collections.ArrayList Values {get;}
Där finns våran Size. Anledningen till att den inte syntes var att standard-output:en för GroupInfo inte innehåller Size, så vi får skicka vidare objekten till Format-Table ("ft") och berätta vad vi vill se:
PS# dir c:\windows\system32 | ? { $_.GetType().Name -eq "FileInfo" } | group Extension | % { $_ | add-member NotePropert
y Size ($_.Group | measure-object Length -sum).Sum -passthru} | ft Name, Count, Size -auto
Name Count Size
---- ----- ----
.inf 5 59520
.cpx 2 4384
.dll 1283 295688942
.cpl 24 5656920
.exe 304 43885527
Nästan klara. Det vi har kvar är att sortera informationen, och begränsa antalet rader till 10 (så att listan blir lite smidigare, texterna ovan har jag klippt för läsbarhet). Vi skickar det till Sort-Object ("sort") och sedan till Select-Object ("select") innan det skickas till Format-Table:
PS# dir c:\windows\system32 | ? { $_.GetType().Name -eq "FileInfo" } | group Extension | % { $_ | add-member NoteProperty Size ($_.Group | measure-object Length -sum).Sum -passthru} | sort Size -desc | select -first 10 | ft Name, Count, Size -auto
Name Count Size
---- ----- ----
.dll 1283 295688942
.exe 304 43885527
.bin 4 13196206
.TMP 17 7090705
.ocx 19 7074594
.cpl 24 5656920
.nls 68 5138370
.dat 13 4468086
.scr 11 2739200
Så, vilka CmdLet:s har vi stött på nu:
- Get-ChildItem (dir)
- Where-Object (?)
- Group-Object (group)
- ForEach-Object (%)
- Add-Member
- Measure-Object
- Sort-Object (sort)
- Select-Object (select)
- Format-Table (ft)
- Get-Member (gm)
Eftersom jag är ganska ny på blog-arenan så skulle jag gärna vilja ha feedback. Skriv och berätta huruvida ni finner ovanstående fullständigt ointerresant, felaktigt eller förhoppningsvis lärorikt.
Skriv gärna ifall ni har några förbättringar på den resulterande kommando-raden.
Posted
07-02-2007 2:16
by
bjorn.osterman