Jak przesortować plik CSV przy pomocy programu Windows PowerShell?
Skrypciarze odpowiadają na Wasze pytania
Witamy w rubryce TechNet, w której Skrypciarze z firmy Microsoft odpowiadają na częste pytania dotyczące używania skryptów w administracji systemu. Jeśli macie jakieś pytania z tej dziedziny, zachęcamy do wysłania e-maila na adres: scripter@microsoft.com. Nie możemy zagwarantować odpowiedzi na każde otrzymane pytanie, ale staramy się jak możemy. |
CONTENT
Jak przesortować plik CSV przy pomocy programu Windows PowerShell?
Cześć, Skrypciarze! Mam plik z wartościami oddzielonymi przecinkami, który zawiera różne pola. Chciałbym odczytać ten plik przy pomocy programu Windows PowerShell, a następnie przesortować na podstawie jednego z pól. Jak to zrobić?
-- DW
Cześć, DW! Nie wiem czy jest Ci znany ten fakt, ale ostatnia sobota była Międzynarodowym Dniem Pizzy. Nie powiem, żeby nie spodobał mi się ten pomysł. Oprócz pączków, to właśnie pizza jest moim ulubionym daniem. Jestem więc niebywałym szczęściarzem, mając w tak krótkim odstępie czasu zarówno pączkowy czwartek (zresztą jak każdy dzień roku), a niedługo potem Światowy Dzień Pizzy. Zastanawia mnie tylko fakt, co na to inni, którzy nie mają takiego szczęścia, jak ja i lubią na przykład… jajecznicę, albo żabie udka. Czy nie czują się trochę poszkodowani? Światowy Dzień Żabich Udek? Jajecznicy? Gdyby tak każdy dzień roku zadedykować jednemu daniu, to wiele gospodyń nie miałoby problemu „co dziś ugotować?” Co Wy na to?
To tylko takie przemyślenia, jestem bowiem fanem egalitaryzmu. Uważam więc, że każdy obywatel, oprócz prawa do międzynarodowego święta swojej potrawy, ma również prawo do przesortowania pliku CSV wedle uznania. Przyjrzyjmy się więc plikowi DW:
FirstName,LastName,Department,Score
Alice,Ciccu,Human Resources,222
Ken,Myer,Finance,151
Pilar,Ackerman,Finance,514
Jonathan,Haas,Administration,17
Syed,Abbas,Human Resources,67
Terri,Chudzik,Finance,188
Jest to dość prosty plik CSV (z wartościami oddzielonymi przecinkami), zawierający cztery pola: FirstName; LastName; Department; oraz Score. DW chciałby napisać skrypt (lub polecenie), który po odczytaniu pliku przesortowałby go pod kątem jednego z tych pól. Jego pomysł (czyli polecenie Get-Content C:\Scripts\Test.txt | Sort-Object) zakończyło się następującym fiaskiem:
Alice,Ciccu,Human Resources,222
FirstName,LastName,Department,Score
Jonathan,Haas,Administration,17
Ken,Myer,Finance,151
Pilar,Ackerman,Finance,514
Syed,Abbas,Human Resources,67
Terri,Chudzik,Finance,188
Ciekawe, aczkolwiek nie to, o co chodziło DW.
W czym problem? Otóż cmdlet Get-Content nie jest właściwym cmdletem do pracy z plikami CSV. Dlaczego? Ponieważ cmdlet Get-Content nie zauważa, że plik CSV jest utworzony z poszczególnych pól. Dlatego też pojmuje cały plik jako jedną wielką całość, co oczywiście nie jest prawdą. Rzut oka na dane wynikowe, uzyskane przez DW, pozwoli zauważyć, że cmdlet Get-Content odczytał plik tekstowy, a cmdlet Sort-Object przesortował jego zawartość. Problem w tym, że przesortował plik wierszami pod kątem pierwszej litery wiersza (żeby było śmieszniej, to łącznie z nagłówkiem). Chyba nie o to chodziło DW.
Po wypróbowaniu polecenia Get-Content C:\Scripts\Test.txt | Sort-Object Department otrzymaliśmy następujący wynik:
Jonathan,Haas,Administration,17
Syed,Abbas,Human Resources,67
Terri,Chudzik,Finance,188
Pilar,Ackerman,Finance,514
FirstName,LastName,Department,Score
Alice,Ciccu,Human Resources,222
Ken,Myer,Finance,151
Pod kątem czego przesortowaliśmy teraz nasze dane? Tak w sumie to nie jesteśmy w stanie odpowiedzieć na to pytanie – nie mamy pojęcia. Na pewno jednak nie jest to sortowanie na podstawie nazwy departamentu. Get-Content nie przekazuje tu żadnej informacji o polu Sort-Object (spróbujcie nadać plikowi cmdlet Get-Member, a zobaczycie co mam na myśli).
Jeżeli zatem Get-Content nie spełnia naszych oczekiwań, to co je spełnia? Odpowiedź jest dość krótka: cmdlet Import-CSV. Przyjrzyjmy się teraz poniższemu wierszowi kodu, który importuje plik C:\Scripts\Test.txt:
Import-CSV C:\Scripts\Test.txt
Teraz spójrzmy na rezultaty:
FirstName LastName Department Score
--------- -------- ---------- -----
Alice Ciccu Human Resources 222
Ken Myer Finance 151
Pilar Ackerman Finance 514
Jonathan Haas Administration 17
Syed Abbas Human Resources 67
Terri Chudzik Finance 188
Pierwszą rzeczą, która rzuca się w oczy, jest fakt, że nagłówek oddzielony jest od pozostałych rzędów, a poszczególne pola zostały umieszczone w kolumnach. Co to oznacza? Oznacza to ni mniej, ni więcej, tylko fakt, że cmdlet Import-CSV zdaje sobie sprawę z tego, jak działa taki plik z wartościami oddzielonymi przecinkami i wie, że poszczególne pola powinny być traktowane jako osobne właściwości danego pliku. Poniższe polecenie importuje zawartość pliku C:\Scripts\Test.txt i przesyła ją w potoku do cmdletu Get-Member:
Import-CSV C:\Scripts\Test.txt | Get-Member
To zwrotna informacja od Get-Member:
Name MemberType Definition
---- ---------- ----------
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
ToString Method System.String ToString()
Department NoteProperty System.String Department=Human Resources
FirstName NoteProperty System.String FirstName=Alice
LastName NoteProperty System.String LastName=Ciccu
Score NoteProperty System.String Score=222
Spójrzmy teraz na ostatnie cztery wiersze. Nasze cztery pola – Department, FirstName, LastName oraz Score są traktowane jako właściwości pliku tekstowego, a dokładniej jako właściwości typu NoteProperties. W programie PowerShell właściwość NoteProperty jest parą nazwa-wartość, tak jak FirstName = Alice. Import-CSV rozumie po prostu całą istotę plików CSV.
Czy to takie ważne, że Import-CSV traktuje pola CVS jako właściwości? W sumie nawet bardzo ważne. Teraz możemy przecież określić nazwę pola (to jest nazwę właściwości) podczas przesyłania potokiem zawartości pliku do cmdletu Sort-Object:
Import-CSV C:\Scripts\Test.txt | Sort-Object Department
Da nam to następujący wynik:
FirstName LastName Department Score
--------- -------- ---------- -----
Jonathan Haas Administration 17
Terri Chudzik Finance 188
Pilar Ackerman Finance 514
Ken Myer Finance 151
Alice Ciccu Human Resources 222
Syed Abbas Human Resources 67
Raczej o coś takiego chodziło DW.
Czy oznacza to dla nas rozwiązanie wszystkich problemów? No prawie wszystkich, pozostaje jeszcze jedna ważna kwestia. Załóżmy, że napiszemy polecenie, które będzie miało przesortować plik CSV pod kątem pola Score:
Import-CSV C:\Scripts\Test.txt | Sort-Object Score
Wynik będzie teraz następujący:
FirstName LastName Department Score
--------- -------- ---------- -----
Ken Myer Finance 151
Jonathan Haas Administration 17
Terri Chudzik Finance 188
Alice Ciccu Human Resources 222
Pilar Ackerman Finance 514
Syed Abbas Human Resources 67
Tak, tu właśnie trzeba uważać, bo jakimś trafem 151 umieszczone jest przed 17, a 514 przed 67. Domyślacie się pewnie dlaczego właśnie tak, ale mimo to – muszę to napisać – program PowerShell traktuje te wartości jako ciągi nie liczby, więc wszystko, co zaczyna się od 15 (np. 151) musi znaleźć się przed 17. Tak to już działa.
Zapytacie się teraz pewnie: „czy da się to jakoś obejść?”. Niestety nie.
No dobra, żartowałem, da się:
Import-CSV C:\Scripts\Test.txt | Sort-Object {[int] $_.Score}
W powyższym poleceniu nie sortujemy pod kątem jakiejś nazwy właściwości, zamiast tego sortujemy na podstawie wartości pochodzącej z bloku skryptu (polecenia ujęte w klamry są niczym małe skrypty osadzone w innych skryptach lub poleceniach). W PowerShell zmienna $_ oznacza obiekt znajdujący się obecnie w potoku. Oznacza to, że możemy użyć zmiennej w rodzaju $_.Score do oznaczenia wybranej właściwości takiego obiektu. Oczywiście nie chcemy sortować tu niczego pod kątem wyniku, chcieliśmy jednak zwrócić Waszą uwagę na fakt, że zrobienie tego nie przyniesie nam zamierzonych rezultatów. Zamiast tego użyjemy [int] przekonwertujemy wartości właściwości Score do licznika. To spowoduje, że program PowerShell będzie sortował te wyniki jako liczby, co powinno nam dać następujący wynik:
FirstName LastName Department Score
--------- -------- ---------- -----
Jonathan Haas Administration 17
Syed Abbas Human Resources 67
Ken Myer Finance 151
Terri Chudzik Finance 188
Alice Ciccu Human Resources 222
Pilar Ackerman Finance 514
O niebo lepiej!
Oczywiście pliki CSV można sortować pod kątem różnych parametrów Sort-Object. Chcecie przesortować je pod katem wyników w porządku malejącym? Proszę bardzo, trzeba tylko dodać parametr –descending:
Import-CSV C:\Scripts\Test.txt | Sort-Object {[int] $_.Score} -descending
Można także przesortować je pod względem departamentu, a potem, w obrębie każdego departamentu pod kątem LastName (czyli nazwiska):
Import-CSV C:\Scripts\Test.txt | Sort-Object Department, LastName
Itd.
Powinno zadziałać DW, możesz teraz sortować swój plik CSV, jak Ci się żywnie podoba. Przypominamy tylko, że oprócz Światowego Dnia Skrypciarza oraz Światowego Dnia Pizzy, należy umieścić w kalendarzu jeszcze jedną ważną datę - 15 lutego 2008, czyli dzień w którym rozpoczynamy tegoroczną edycję Skrypciarskiej Olimpiady Zimowej 2008 Winter Scripting Games (j.ang.). W tym roku szykuje się wiele emocji! Nie będziecie mogli oderwać się od komputerów, gwarantujemy. Dlatego też radzimy, żeby zawczasu uzbroić się we wszystkie numery pizzerii i innych gastronomicznych miejsc, gdzie mają dowóz do domu, bądź w gotujących krewnych, tudzież dziewczynę/chłopaka/męża/żonę/psa/kota…
Do początku strony |