Hey, Scripting Guy!

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

Hey, Scripting Guy!

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

詳細情報

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

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

Hey, Scripting Guy! ダウンロード

Spacer

*

タブ区切りファイル内の重複項目を削除する方法はありますか

Hey, Scripting Guy! Question

Scripting Guy さん、よろしくお願いします。タブ区切りファイル内の重複項目を削除する方法はありますか。
-- ST

SpacerHey, Scripting Guy! AnswerScript Center

ST さん、こんにちは。説明を始める前にお聞きしますが、『Not Quite What I Was Planning: Six-Word Memoirs by Writers Famous and Obscure』(英語) という本をご存じの方はいらっしゃいませんか。これは、『SMITH Magazine』(英語) の編集者に依頼された人たちが自分の人生を 6 語で言い表した言葉が載っているおもしろい本です。この本と雑誌の両方から例を挙げてみましょう。

真実の愛を見つけたが、結婚相手は別の人 (Found true love, married someone else.)。

今でも 2 人分のコーヒーを入れている (I still make coffee for two.)。

良い人間に生まれ、悪くなって、また良い人間になった (Born good. Went bad. Good again.)。

あまり踏み固められていない道だが、今はその理由がわかる (Road less traveled, now know why)。

ああ、それから、エイミー・セダリスの言葉もご紹介しましょう。

マッシュルーム。ピエロ。魔法の杖。5。かつら。わらぶき屋根 (Mushrooms. Clowns. Wands. Five. Wig. Thatched.)。

そうですね。最後の言葉については気にしないでください。

ともかく、このコラムを執筆している Scripting Guy は、この発想を Hey, Scripting Guy! コラムに適用するといいかもしれないと考えました。その日の質問にたった 6 語で答えることができたら、時間と労力を大幅に節約できますからね。しかし残念ながら、彼はすぐにその考えをあきらめなければなりませんでした。次のように、たった 6 語では自分自身 (このコラムを執筆している Scripting Guy) について言及することすらできないことがわかったからです。

1.

The

2.

Scripting

3.

Guy

4.

who

5.

writes

6.

this

7.

column

言うまでもありませんが、彼の話はときどき少し長ったらしくなる傾向があります。

実のところ、彼は 1 つだけ案を思い付きました。それは、“コードは次のとおりです。健闘を祈ります (Here is the code. Good luck.)。” というものです。でも彼は、これでスクリプトとそのしくみについての十分な説明がなされたと見なしてくれる方がいるかどうか自信がありませんでした。

そこで、彼は少し妥協して、今日のコラム内の説明の "一部" を 6 語で行うことにしました。このような 6 語の説明は角かっこで囲んであります。このコラムの残りの部分は、通常の Hey, Scripting Guy! コラムのやり方で進めます。[通常のやり方ですよ。なぜ皆さんはブーイングしているのですか (Typical fashion. Why are you booing?)。]

では始めましょう。タブ区切りファイル内の重複項目を削除するには、どうすればよいでしょうか。[さあね。この記事を読んでみてください (Beats us. Try reading the article.)。]

: どうとでも言ってください。でも、エイミー・セダリスが思い付いた言葉よりはましでしょう。しかも、彼女は本物の作家でありコメディアンでもあるんですよ。

タブ区切りファイル内の重複項目を削除できるスクリプトは次のとおりです (ただし、残念ながら、スクリプトを 6 語以内に収めることはできませんでした)。

Const ForReading = 1 
Const ForWriting = 2 
 
Set objDictionary = CreateObject("Scripting.Dictionary") 
 
Set objFSO = CreateObject("Scripting.FileSystemObject") 
Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForReading) 
 
strContents = objFile.ReadAll 
objFile.Close 
 
strContents = Replace(strContents, vbCrLf, vbTab) 
arrContents = Split(strContents, vbTab) 
 
For Each strItem in arrContents 
    If Not objDictionary.Exists(strItem) Then 
        objDictionary.Add strItem, strItem    
    End If 
Next 
 
i = 1 
 
For Each strKey in objDictionary.Keys 
    If i < 3 Then 
        strNewContents = strNewContents & strKey & vbTab 
        i = i + 1 
    Else 
        strNewContents = strNewContents & strKey & vbCrLf 
        i = 1 
    End If 
Next 
 
Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForWriting) 
objFile.Write strNewContents 
objFile.Close

ご覧のように、まず、テキスト ファイルを開くときに必要になる ForReading と ForWriting という 2 つの定数を定義します。[定数が 2 つあるということは、ファイルを 2 回開く必要があるということです (Two constants. Must open file twice.)。] 定数を定義したら、Scripting.Dictionary オブジェクトと Scripting.FileSystemObject オブジェクトのインスタンスを作成し、次のコード行を使用して C:\Scripts\Test.txt ファイルを読み取り用として開きます。

Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForReading)

ファイルを開いたら、ReadAll メソッドを使用してファイルの内容全体を読み取って、strContents という名前の変数に格納し、一旦ファイルを閉じます。[ファイルを取得したので、次は何をするか、おわかりですよね (We’ve got the file. Now what?)。]

次の処理に進む前に、先ほど開いたファイルの内容を見てみましょう (偶然ではありませんが、これは strContents 変数の値でもあります)。

Apple    Apple    Apple 
Banana   Cherry   Cherry 
Cherry   Date     Fig 
Fig      Lemon    Orange 
Orange   Peach    Peach 
Pear     Pear     Pear

ご覧のとおり、これはさまざまなフルーツ名の一覧で、3 列で構成されています。また、こうしたフルーツ名の多くは複数回出てきます。たとえば、Apple という単語は 3 回、Peach という単語は 2 回出てきます。[デートは 1 回だなんて、私たちの人生と同じです (One date. Story of our lives.] 次に必要な作業は、重複項目をすべて削除することです。[必要なのは最初の項目だけで、重複項目は不要です (Originals only. Duplicates need not apply.)。] つまり、3 列で構成された次のような一覧にする必要があります。

Apple    Banana    Cherry 
Date     Fig       Lemon 
Orange   Peach     Pear

良い質問です。一体どのようにすればこれを実現できるのかお答えしましょう。まず、もう少し処理しやすい状態の一覧を用意する必要があります。実際、本当に必要なのは、このような 3 列なんていう凝った形式の単語一覧ではなく、1 列で構成された単語一覧です。[1 列はすばらしく、3 列はごちゃごちゃしています (One column’s great; three’s a crowd.)。] この 3 列で構成された一覧を 1 列で構成された一覧にするには、まず、次のように、すべての復帰改行文字 (vbCrLf) をタブ文字 (vbTab) に変更する必要があります。

strContents = Replace(strContents, vbCrLf, vbTab)

このようにすると、1 行で構成された単語一覧 (各単語はタブ文字で区切られている) が作成されます。つまり、次のようなものが作成されます (ただし、この 1 行のデータにはまだまだ続きがあります)。

Apple    Apple    Apple    Banana  Cherry   Cherry

[バナナは 1 つだけです。人生とは不公平なものですね (One lone banana. Life isn’t fair.)。]

確かに、この状態では、それほど大きな進歩が見られないと思われるかもしれません。信じられないかもしれませんが、進歩しているのです。[Scripting Guys が進歩するなんて、驚きですね (Holy smokes: Scripting Guys make progress!)。] これで、すべての単語がタブ文字で区切られたので、Split 関数を使用して、strContents 変数の値をタブ文字の箇所で分割します。

arrContents = Split(strContents, vbTab)

なぜ、このような処理を行う必要があるのか、ですって。この処理を行うと、次の要素で構成された arrContents という名前の配列を作成できるからです。

Apple  
Apple  
Apple 
Banana 
Cherry 
Cherry 
Cherry 
Date   
Fig 
Fig    
Lemon  
Orange 
Orange 
Peach  
Peach 
Pear   
Pear   
Pear

このようなすてきな配列を作成したので、重複項目は非常に簡単に削除できます。この処理を行うために、まずは、次のように、arrContents 配列のすべての項目をループ処理する For Each ループを設定します。[For Each ループということは、すべての項目を処理するということです (For Each: everyone gets a turn!)。]

For Each strItem in arrContents

このループ内では、次のように、Exists メソッドを使用して、配列の 1 つ目の項目が Dictionary オブジェクトに存在するかどうかを確認します。[ほらね。私たちが Dictionary オブジェクトの存在を忘れることはありません (See? We never forget the Dictionary.)。]

If Not objDictionary.Exists(strItem) Then

この項目が Dictionary オブジェクトに存在するということは、その項目が重複項目だということです。その場合は、ループ処理の先頭に戻り、配列の次の項目について同じ処理を繰り返します。項目が Dictionary オブジェクトに存在しない場合は、次のように、その項目を Dictionary オブジェクトのキーおよび項目として、Dictionary オブジェクトに追加します。

objDictionary.Add strItem, strItem

配列の全項目の処理が完了すると、Dictionary オブジェクトには、次のキー (および項目) が格納されています。

Apple  
Banana 
Cherry 
Date   
Fig 
Lemon  
Orange 
Peach  
Pear

すばらしいですね。ただし、この 1 列で構成された一覧を 3 列で構成された一覧に戻す必要があります。[3 の方が 1 より良い場合もあるのです (Sometimes three is better than one.)。] それには、まず、i という名前のカウンター変数に値 1 を代入します。

i = 1

続いて、次のように、Dictionary オブジェクトの全キーを処理する別の For Each ループを設定します。[スクリプトでは、いくつのループ処理を使うのかわかりませんね (Can never have too many loops.)。]

For Each strKey in objDictionary.Keys

3 列で構成された一覧が必要なので、このループ内ではまず、カウンター変数 i の値が 3 未満であるかどうかを確認します。3 未満の場合は、次のコード ブロックを実行します。

strNewContents = strNewContents & strKey & vbTab 
i = i + 1

ご覧のとおり、ここでは、それほど複雑なことはしていません。[Scripting Guys のポリシーは、"考え方もスクリプトも単純なのが一番" です (Scripting Guys: Simple minds, simple scripts.)。] 1 行目では、strNewContents という名前の変数に値を代入しています。もう少し具体的に説明すると、この変数には、strContents 変数の既存の値に Dictionary キーの値とタブ文字を追加したものを代入しています。続いて、2 行目では、カウンター変数の値を 1 増加します。この一連の処理が何を意味しているのかというと、ループ処理を 3 回実行した時点では strContents 変数には次のような値が格納されているということです。

Apple    Banana  Cherry

4 回目のループ処理では何が起こるのか、ですって。4 回目のループ処理では、カウンター変数 i の値が 3 になります。ですから、先ほどのコード ブロックの代わりに、次の 2 行のコードを実行します。

strNewContents = strNewContents & strKey & vbCrLf 
i = 1

違いがわかりますか。このコード ブロックでは、文字列の末尾に、タブ文字ではなく復帰改行文字を追加しています。さらに、カウンター変数 i の値を 1 にリセットしています。この 2 つの処理により、一覧の次の項目 (4 つ目の項目) が 1 行目の 4 列目ではなく 2 行目の 1 列目に現れるようになります。

strNewContents 変数の値の形式を変更し終えたら、今度は書き込み用として Test.txt ファイルを再度開きます。

Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForWriting)

Write メソッドを使用して、Text.txt の既存の内容を strNewContents 変数の値で上書きします。次に、Close メソッドを使用して、もう一度ファイルを閉じます。今度は永遠に閉じます。[ファイルを閉じたということは、コラムも終わりということですね (File closed? Column must be over.)。]

SS さん、これでうまくいくでしょう ("That should do it, SS."。しまった。今のは 5 語にしかなっていません (Shoot, that’s only five words.)。あ、またやってしまいました。今のも 5 語にしかなっていませんね (Double shoot: that’s only five words, too.)。何てこった。最後の文は 7 文字でした)。質問がある場合はお知らせください。スクリプト センターや Scripting Guys を説明する 6 語の言葉を思い付いた場合もお知らせください。今後のコラムに掲載します。たとえば、私たちがこのコラムを表現する言葉として思い付いたものをご紹介しましょう。

問題ありません…。でも、なぜスクリプトに関する情報がないのでしょうか (OK …. But why no scripting information?)。

それから、愛すべき編集者を端的に表現した言葉をご紹介しましょう。

人が大好きで、朝食に人を 1 人食べました (Loves people. Had one for breakfast.)。

また明日お会いしましょう (See you all tomorrow.)。

待ってください。前言は撤回します。これならどうでしょう。

明日新しいコラムを発表します。では、また明日 (New column tomorrow; see you then.)。

こちらの方が良いですね (That’s better.)。

待ってください。前言は撤回します。これならどうでしょう。

こちらの方が良いですね。6 語使用しなくてはならないので (That’s better; must use six words.)。


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