Centrum Skryptów - Systemy operacyjne

Jak przesortować plik CSV przy pomocy programu Windows PowerShell?

Udostępnij na: Facebook

Skrypciarze odpowiadają na Wasze pytania

Cześć Skrypciarze!

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! Pytanie

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ść Skrypciarze! Pytanie

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 Do początku strony

Centrum Skryptów - Systemy operacyjne