Hey, Scripting Guy!

Scripting Guys が皆さんの質問にお答えします

Hey, Scripting Guy!

TechNet コラムへようこそ。このコラムでは、よく寄せられるシステム管理スクリプトに関する質問に Scripting Guys がお答えします。システム管理スクリプトについて質問がある場合は、scripter@microsoft.com (英語のみ) までお送りください。すべての質問に回答することはできないかもしれませんが、可能な限り対応いたします。

詳細情報

Hey, Scripting Guy! カテゴリ別アーカイブ

Hey, Scripting Guy! 日付別アーカイブ

Hey, Scripting Guy! ダウンロード

Spacer

*

Windows PowerShell を使用して CSV ファイルのデータを並べ替える方法はありますか

Hey, Scripting Guy! Question

Scripting Guy さん、よろしくお願いします。いくつかのフィールドが含まれているコンマ区切りファイルがあります。Windows PowerShell からこのファイルを読み取り、1 つ (または複数) のフィールドに基づいて情報を並べ替えたいと思っています。でも、うまくいきません。何かアドバイスをいただけますか。

-- DW

SpacerHey, Scripting Guy! AnswerScript Center

DW さん、こんにちは。質問に答える前に、ご報告しておかなければならないことがあります。先週の土曜日、このコラムを執筆している Scripting Guy はドーナツを買うために車を走らせていました (彼の生活のどれほど多くの部分がドーナツを中心に回っているかには、本当に驚きますよね)。ドミノ・ピザの前を通り過ぎたとき、彼は店の前に次のような大きな垂れ幕がかかっているのに気付きました。

"ドミノのピザ現在販売中"

ええ、その読み方で合っています。信じられないかもしれませんが、ドミノのピザを現在販売しているドミノ・ピザの店がシアトル地域に少なくとも 1 軒はあるということです。この店のスタッフは次は一体何を考え出すことでしょう。

このコラムを執筆している Scripting Guy は、車を止めて垂れ幕について聞きたいという衝動に駆られましたが、運転を続けました (「まずドーナツを食べなさい。質問は後でしなさい。」という古いことわざがありますからね)。でも彼は、店主がそもそもなぜあんな垂れ幕を掛けようと思ったのかが不思議でなりませんでした。そこで、彼は次のような会話を想像しました。

「ねえ Bob、この店でドミノのピザの販売を開始しようかと思うんだけど」

「何を言ってるんだ。気は確かか。ドミノ・ピザでドミノのピザの販売なんてできないよ」

「いや、Bob、それはどうかな。ドミノのピザの販売を開始したらこの店にとってためになると心底思うんだ」

「ああ、そうかもしれないな。でも、そもそも、世間の人は、この店でドミノのピザの販売を開始したことにどうやって気付くっていうんだい。だって、通りすがりに『あ、ドミノ・ピザの店がある。あの店ではきっとドミノのピザを買えるんだろうな。』なんて思うドライバーはいないだろう」

「うーん、それは考えてもみなかったな…。そうだ、いい考えがある。"ドミノのピザ現在販売中" という大きな垂れ幕を掛けよう」

「それは突拍子もなくて、うまくいきそうなアイデアだな」

聞かれる前にお答えしますが、私たちは、ドミノ・ピザの店でドミノのピザの販売が開始される前に何が販売されていたかは知りません。これについては、お調べしておく必要がありますね。

でも、CSV の内容を並べ替えられる Windows PowerShell コマンドを販売していなかったことだけは確かです (いや、これについても確かではないかもしれませんが、きっとそうだと思います)。Windows PowerShell を使用してコンマ区切りファイルのデータを並べ替える方法についての情報をお探しなら、入手できる場所は 1 か所しかありません。

いや、ピザハットではありません。この Hey, Scripting Guy! コラムです。

実際、今日のコラムでは、CSV ファイルのデータを並べ替える方法をご紹介する予定です。でも今すぐにではありません。昼食後にご紹介します。ピザやドーナツについていろいろ話していたら、お腹がすいてきましたからね。

わかりました、先にご紹介した方がいいですね。まずは、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

ご覧のとおり、これは比較的単純な CSV (コンマ区切り) ファイルで、FirstName、LastName、Department、Score という 4 つのフィールドが含まれています。DW さんは、ファイルを読み取り、この 4 つのフィールドの 1 つに基づいてデータを並べ替えることができるスクリプト (またはコマンド) をお望みとのことでしたね。残念ながら、この問題に対する彼の最初の試みはあまりうまくいきませんでした。Get-Content C:\Scripts\Test.txt | Sort-Object というコマンドを実行したところ、次のような出力結果が返されたそうです。

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

興味深い結果ではありますが、DW さんが思い描いていたものとは異なります。

何がいけなかったのでしょうか。問題点の 1 つは、Get-Content コマンドレットは通常、CSV ファイルを処理する際に使用するコマンドレットではないということです。その理由は、Get-Content コマンドレットは、CSV ファイルが個々のフィールドで構成されているということを認識しておらず、テキスト ファイル内の各行を単純に 1 つの大きなエンティティとして認識するからです。DW さんが実行したコマンドの出力結果を見ると、Get-Content コマンドレットによるテキスト ファイルの読み取りも、Sort-Object コマンドレットによるそのファイルの内容の並べ替えも実際に行われていることがわかります。問題は、彼のコマンドでは、行内の最初の文字に基づいて行 (見出し行を含む) が並べ替えられただけであるということです。これは、彼が思い描いていたものとは明らかに異なります。

いえ、特定のフィールドに基づいて並べ替えを行うように Sort-Object コマンドレットに指示するという方法もうまくいきません。Get-Content C:\Scripts\Test.txt | Sort-Object Department というコマンドを実行してみたところ、出力結果は次のようになりました。

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

ここでは、データの並べ替えは何に基づいて行われているのか、ですって。正直なところ、私たちにはわかりません。でも、Department フィールドに基づいてデータが並べ替えられているのではないことは確かです。というのも、Get-Content コマンドレットでは Sort-Object コマンドレットにフィールド情報をまったく渡していないからです (出力を Get-Member コマンドレットに渡してみれば、私たちの言いたいことがおわかりいただけると思います)。

では、Get-Content コマンドレットが適切でないなら、一体何を使用すればいいのでしょうか。結局のところ、CSV ファイルを処理する際はいつも Import-CSV コマンドレットを使用する必要があります。たとえば、次のコード行をご覧ください。ここでは、C:\Scripts\Test.txt ファイルをインポートしています。

Import-CSV C:\Scripts\Test.txt

今度は、このコマンドを実行した場合の出力結果をご覧ください。

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

見出し行がデータ行から切り離されており、また、個々のフィールドがそれぞれ別々の列に配置されていることがわかります。これはどういうことかというと、Import-CSV コマンドレットはコンマ区切りファイルのしくみを認識しており、個々のフィールドをテキスト ファイルの別々のプロパティとして扱う必要があることも認識しているということです。これをよりわかりやすく示すために、次のコマンドをご紹介します。このコマンドを実行すると、Test.txt の内容がインポートされ、そのデータは Get-Member コマンドレットにパイプされます。

Import-CSV C:\Scripts\Test.txt | Get-Member

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

出力結果の最後の 4 行をご覧ください。すると、4 つのフィールド (Department、FirstName、LastName、および Score) がテキスト ファイルのプロパティとして扱われていることがわかります (もっと正確に言うと、NoteProperty として扱われています。PowerShell では、NoteProperty は FirstName = Alice のような名前と値の組み合わせです)。Import-CSV コマンドレットでは、CSV ファイルの特性を正しく認識しています。

Import-CSV コマンドレットが CSV のフィールドをプロパティとして扱うというのは重要なことなのか、ですって。もちろんです。何しろ、そのおかげで、ファイルの内容を Sort-Object コマンドレットにパイプする際に、次のようにフィールド名 (つまり、プロパティ名) を指定できますからね。

Import-CSV C:\Scripts\Test.txt | Sort-Object Department

このコマンドを実行した場合の出力結果は、次のようになります。

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

これこそ、DW さんがずっと思い描いていたものです。

これで、問題はすべて解決したことになるでしょうか。ほとんどは解決したと思います。でも、対処しなければならない小さな問題がまだ 1 つ残っています。CSV ファイルのデータを Score フィールドに基づいて並べ替えるように設計された次のコマンドを発行するとしましょう。

Import-CSV C:\Scripts\Test.txt | Sort-Object Score

すると、このコマンドの出力結果は、次のようになります。

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

おや、一体いつから 151 は 17 よりも前に来るようになったのでしょう。それに、なぜ 514 が 67 よりも前に来るのでしょう。

皆さんが「なるほど、きっと PowerShell はこうした値を数値ではなく文字列として扱っているんだな」と思っていらっしゃるとしたら、そのとおりです。ここでは、PowerShell は確かにこうした値を文字列として扱っています。そして、文字列の並べ替えという向こう見ずな世界では、15 で始まるもの (151 など) は 17 で始まるものよりも前に来ます。そういうしくみになっているのです。

では、この問題を解決するにはどうすればよいのかというと、その 1 つの方法をご紹介します。

Import-CSV C:\Scripts\Test.txt | Sort-Object {[int] $_.Score}

このコマンドでは、特定のプロパティ名に基づいてではなく、スクリプトブロックから取得した値に基づいて並べ替えを行っています (中かっこで囲まれたコマンドは、事実上、他のスクリプトやコマンドに埋め込まれたちょっとしたスクリプトです)。PowerShell では、$_ 変数は、現在パイプライン内にあるオブジェクトを表します。つまり、$_.Score のような構文を使用してそのオブジェクトの特定のプロパティを表すことができるということです。もちろん、Score プロパティに基づいて並べ替えを行いたくはありません。前述のとおり、望みどおりの出力結果が得られないからです。代わりに、[int] を使用して Score プロパティの値を整数に変換します。これにより、PowerShell で点数が数値として並べ替えられるようになります。そして、出力結果は次のようになります。

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

こちらの方がはるかにいいですね。

ちなみに、CSV ファイルのデータを並べ替える際は、Sort-Object コマンドレットの他のパラメーターもすべて使用できます。点数に基づいて、ただし降順でデータを並べ替えたいとお考えですか。その場合に必要な作業は、次のように、ただ –descending パラメーターを追加するだけです。

Import-CSV C:\Scripts\Test.txt | Sort-Object {[int] $_.Score} -descending

または、Department (部署) に基づいて並べ替えて、さらに、各部署内では LastName に基づいて並べ替えたいとお考えかもしれませんね。次のコマンドを使用すれば、それも可能です。

Import-CSV C:\Scripts\Test.txt | Sort-Object Department, LastName

その他にもいろいろな条件で並べ替えを行うことができます。

DW さん、これでうまくいくでしょう。これでもう、CSV ファイルのデータを好きなように並べ替えることができるようになったはずです。今日のコラムに費やすことができる時間はこれでほぼ使い果たしましたが、少し時間をとって、2008 年冬季スクリプト競技大会が今週の金曜日 (2 月 15 日) に始まることを皆さんにお伝えしておきます。既にお伝えしましたが、今年のスクリプト競技大会は、これまでよりも大規模ですてきなものになる予定です。なぜでしょうか。理由の 1 つは、ドミノ・ピザの優秀なスタッフからヒントを得て、今年の冬季スクリプト競技大会ではなんと、冬季スクリプト競技大会の種目を目玉にすることにしたからです。そうです、今年の冬季スクリプト競技大会では、冬季スクリプト競技大会の種目が目白押しになる予定です。

確かに、これはちょっとした賭けですが、私たちは、冬季スクリプト競技大会に冬季スクリプト競技大会の種目を取り入れることが、突拍子もなくて、うまくいきそうなアイデアだと信じたいと思います (それに、これはピザ屋でピザを販売することよりも突拍子もないことではないと思います)。Scripting Guys はついに大胆な賭けに出すぎたのでしょうか。その答えは金曜日に判明することでしょう。では、金曜日にまたお会いしましょう。


ページのトップへページのトップへ