Hey, Scripting Guy!正規表現で間違いをなくす

Microsoft Scripting Guys

この記事で使用しているコードのダウンロード: HeyScriptingGuy2008_01.exe (150KB)

だれでも 間違えることはあります (えっ、Scripting Guys チームが結成されたのは間違いではなかったとお思いですか)。しかし、間違えることにプラスの面があるとすれば、それは、えーと、なんでしょう。間違いには、常にそこから学ぶことができるというプラスの面があります、なんて言うと思いましたか。まさか、とんでもありません。間違いから学ぶことができるのは、そもそも間違えない方がはるかに良かったということだけです。

注 : すみません、今日は Microsoft Bob に関する冗談はありません。以前、Microsoft Bob をからかうという間違いを犯しましたが、この件については、確かに間違いから学んだことがありました。Microsoft Bob のことになると、私たちは口を閉ざすようになりました。

正直なところ、Scripting Guys は、間違えることに関するプラスの面が思い当たりません。だからこそ、できるだけ間違いを犯さないことに専心しているのです (実は、間違いを最小限にとどめる唯一の方法は、実際に行う作業の数を最小限にとどめることです。それはまた別問題ですが)。

自分たちが、自らの間違いを最小限に抑えようとする点には何の問題もありません。しかし、共同作業者の間違いが減らなければ、あまり意味がありません (既に述べましたが、Microsoft Bob に関する冗談ではありません)。たとえば、データベース プログラムや Active Directory® に対するフロントエンド アプリケーションを作成したことがあれば (多くの方が経験していると思いますが)、ここで言わんとしていることはおわかりでしょう。フロントエンドのデータ入力プログラムの機能は、ユーザーがデータを入力することです。

たとえば、名前を入力するときに、Ken のように最初の文字を大文字に、残りすべての文字を小文字にする必要があるとします。この場合、ユーザーが名前を「KEN」と入力するとどうなるでしょうか。また、日付を 01/23/2007 という形式で入力する必要があるとします。この場合、ユーザーが日付を「January 23, 2007」と入力するとどうなるでしょうか。だんだん、わかってきていただけたと思います。既に述べたように、フロントエンドのデータ入力プログラムの機能は、ユーザーがデータを入力することです。そして、いやが応でも、データ入力を行うユーザーは間違いを犯します。もちろん、ユーザーが間違いを犯さないようにサポートする手段を講じていれば話は別ですが。

データが有効であることを確認する

競技会の閉幕に向けて

Scripting Guys が 1 年で最も楽しみにしているイベントが何だかご存知でしょうか。昨年か一昨年の 2 月にスクリプト センターにアクセスしたことがある方は、そのイベントが Winter Scripting Games であると推測されるかもしれません。でも、違います。Scripting Guys が楽しみにしているのは、2 週間にわたるノンストップの感動と興奮の後、その年の Scripting Games 競技会が成功に終わったことの満悦感に浸る瞬間とその後 1 か月にわたる活動休止期間です。

というわけで、1 年で最もお気に入りのイベントである Scripting Games の閉幕に向けて、2008 Winter Scripting Games を開催します。2008 年 2 月 15 日から 3 月 3 日までスクリプト センターで開催される、現存する最も優れたスクリプト競技会にご参加ください。2 週間以上にわたり、スクリプトのおもしろさを体感できます。

Scripting Games に参加すると、スクリプト作成スキルをテストして、さらに向上させることができます。Scripting Guys は、今年の競技会閉幕後の活動休止期間をさらに数日間増やすため、競技会を昨年よりも一層大きくし、より良いものにしています。今年も、競技会では、初心者と上級者という 2 つの部門を用意しています。ですから、まったくのスクリプト初心者でも、ベテラン (年齢不問) でも、またはその中間に位置するユーザーでも、だれでもこの競技会にご参加いただけます。

さて、今年の競技会で変わったことはというと、スクリプト言語をもう 1 つ追加しました。参加できる競技は、VBScript、Windows PowerShell、または新登場の Perl です (Perl 自体は新しい言語ではなく、この競技会に初めて追加されるということです)。おそらくさらにおもしろくなると思いますが、この補足記事は、競技会の何か月も前にまとめる必要があるので、実際どうなるかわかりません。最新情報については、スクリプト センターにアクセスしてください。

気になる方のために言っておくと、賞品が用意されます。それが何かというと、おっと、ここではびっくりする楽しみを奪わないでおきましょう。スクリプト センターにアクセスして調べてください。microsoft.com/technet/scriptcenter/funzone/games/default.mspx です。

おそらく、多くの方は、既にデータ入力の基本的な検証は行っているでしょう。そして、多くの場合、この単純なデータ入力の検証で問題ありません。文字列値 strName に含まれる文字が 20 文字以下であることを確認する必要がある場合には、次の簡単なコード ブロックを使用します。

If Len(strName) > 20 Then
    Wscript.Echo "Invalid name; a " & _
    "name must have 20 or fewer characters."
End If

strName の値が新しい部品番号で、部品番号は、4 桁の数字の後に 2 つの大文字のという指定のパターン (1234AB など) と一致する必要があるとします。シンプルな VBScript を使用して、strName の値が部品番号の指定のパターンに従っていることを検証できるでしょうか。はい、できます。ただし、まじめな話、その方法はお勧めしません。代わりに、正規表現の使用をお勧めします。

値に数字のみが含まれることを確認する

正規表現は 1950 年代に開発されたもので、当初は、数学的表記の形式として説明されました。1960 年代に入ると、この数学的モデルは、テキスト ファイル内の文字パターンの検索方法として QED テキスト エディタに組み込まれました。その後しばらくして...。何か言いましたか。おや、どうやら、正規表現の歴史にだれもが興味をそそられるわけではないようですね。わかりました。そうであれば、具体例をお見せしましょう。

変数 (ここでは strSearchString 変数) に数字のみが含まれるようにする必要がある場合には、図 1 に示すようなスクリプトを使用します。

Figure 1 数字だけが入力されるようにするスクリプト

Set objRegEx = _
  CreateObject("VBScript.RegExp")

objRegEx.Global = True   
objRegEx.Pattern = "\D"

strSearchString = "353627"

Set colMatches = _
  objRegEx.Execute(strSearchString)  

If colMatches.Count > 0 Then
    Wscript.Echo "Invalid: a " & _
      "non-numeric character was found."
Else
    Wscript.Echo "This is a valid entry."
End If

ご覧のとおり、このスクリプトでは、まず VBScript.RegExp オブジェクトのインスタンスを作成します。このオブジェクトにより、ご想像のとおり、スクリプトで正規表現を使用できるようになります (本当は、その説明をしたかったのですが、仕方がないですね)。その後、Global および Pattern という、正規表現オブジェクトの 2 つの主なプロパティに値を代入します。

Global プロパティは、スクリプトで、パターンに一致する文字列をすべて照合するのか、それとも単純に初出の文字列を検出したら照合を停止するのかを決定します。一般に、この値は True に設定し、パターンに一致するすべての文字列を検索することをお勧めします (既定値は False です)。多くの場合、パターンに一致するすべての文字列を検出する必要があります。その必要がなくても、最初の 1 つだけを検出することの利点は、途中で検索を停止した場合、最後まで検索を続けるよりもスクリプトの実行速度が速いということだけです。理論的には、良さそうに聞こえます。しかし、通常、検索は非常に短時間で完了するため、実際には大きな違いを感じません。

良いところは、Pattern プロパティでは目的の文字 (および文字の詳細) を指定します。サンプル スクリプトでは、0 から 9 の数字以外の文字を検索しています。そのような文字 (アルファベット、カンマ、ピリオドなど) が検出されると、strSearchString 変数に代入された値が有効ではないことがわかります。正規表現では、数字以外の文字を検索するのに \D という構文を使用します。そのため、Pattern プロパティに値 \D を代入します。

注 : \D が数字以外の文字に一致するということが、どうしてわかったのでしょう。実は、その裏には長い歴史があります。というのも、数年前...。ああ、そうでした。これについては、MSDN® の VBScript ランゲージ リファレンス (msdn2.microsoft.com/1400241x) で調べました。

Global プロパティと Pattern プロパティに値を代入したら、今度は strSearchString 変数に値を代入します。

strSearchString = "353627"

では、この文字列に数字以外の文字があるかどうか、どうしたらわかるのでしょう。答えは簡単です。単純に Execute メソッドを呼び出し、数字以外の文字があるかどうか変数を調査します。

Set colMatches = _
objRegEx.Execute(strSearchString)

Execute メソッドを呼び出すと、検出された一致する文字列 (Pattern のインスタンス) は自動的に Matches コレクションに保存されます (サンプル スクリプトでは、このコレクションに colMatches という名前を付けました)。(ここでは数字以外の文字が含まれていますが) 変数に数字以外の文字が含まれるかどうかを確認するのに必要な作業は、コレクションの Count プロパティ (コレクションの項目数を示す) の値を確認するだけです。

If colMatches.Count > 0 Then
    Wscript.Echo "Invalid: a " & _
      "non-numeric character was found."
Else
    Wscript.Echo "This is a valid entry."
End If

Count プロパティの値が 0 より大きい場合は、変数の値に数字以外の文字が検出されたということになります (変数の値に含まれるすべての文字が数字の場合、コレクションは空となり、Count プロパティの値は 0 となります)。サンプル スクリプトでは、その後、数字以外の文字が検出されたという事実をエコー バックします。実際のスクリプトやデータベースのフロントエンド アプリケーションでは、おそらく同様のメッセージを表示し、ユーザーが再試行できるようにループ処理の先頭に戻るでしょう。この処理については、皆さんにお任せします。何しろ Scripting Guys には、他に気にかけなければならないことがたくさんありますから。

たとえばどんなことがあるかというと、いつか、TechNet Magazine チームのメンバが、私たちが毎月送っている記事を実際に読み始めるかもしれないという心配です。私たちは、むしろ彼らには間違いから学んでほしくありません。

さて、良い質問ですね。IsNumeric 関数を使用して、strSearchString 変数に数字が含まれるかどうかを確認することはできないのでしょうか。もちろんできます。ただし、strSearchString 変数に含まれる数字が任意の数字であることが前提になります。strSearchString 変数の値が正の整数であることが必要な場合には、この関数を使用すると問題になる可能性があります。というのも、IsNumeric 関数では、次のどちらの数字も有効な数字として認識されるからです。

-45
672.69

なぜこれらが有効な数字として認識されるのでしょうか。その理由は、これらは単に正の整数ではないというだけで、有効な数字だからです。しかし、正規表現では、数字以外の文字の負符号 (-) やピリオド (.) を理解するため、これらの数字は無効なエントリとしてマークされます。このコラムを読みながら「このコラムを読む必要があるのだろうか」と思っていた場合は、まさにここにもっともな理由があります。

何か言いましたか。これでは不十分ですって。うわ、今月は手厳しいですね。わかりました。では、正規表現を使用して実行できる別の種類のデータ入力の検証についてご説明しましょう。

値に数字が含まれないことを確認する

変数の値に数字のみが含まれるようにする方法については、既に説明しました。では、反対に、変数の値に数字が含まれないようにするにはどうすれば良いでしょうか。その場合は、次の検索パターンを使用できます。

objRegEx.Pattern = "[0-9]"

上記のコードではどのような処理が行われているのでしょうか。おもしろく不思議な正規表現の世界では、角かっこ記号 ([ と ]) を使用して、一連の文字や (この例のように) 文字の範囲を指定できます。Pattern プロパティに設定されている [0-9] は何を意味するのでしょうか。これは、0 から 9 の範囲内のすべての文字、つまり数値を検索しているという意味になります。偶数のみが必要な場合 (つまり、値に 1、3、5、7、9 が含まれないようにする場合) には、次のパターンを使用します。

objRegEx.Pattern = "[13579]"

ハイフンを使用していないため、検索しているのは文字の範囲ではなく 1、3、5、7、9 という実際の文字を検索しています。

もちろん、パターン [0-9] では数字のみが検索されますが、カンマやピリオドなど、アルファベットでも数字でもない他の文字は検出されません。アルファベット以外の文字を検索するパターンを作成することはできるでしょうか。もちろんできます。正規表現を使用すれば、実質、何でも実行できます。

objRegEx.Pattern = "[^A-Z][^a-z]"

実は、このパターンでは 2 つの条件を組み合わせています。検索しているのは、[^A-Z] と [^a-z] です。おそらくおわかりのように、A-Z は、大文字の A から大文字の Z までという文字の範囲を表しています。では、^ 文字は何を意味するのでしょうか。^ が 1 組の角かっこ内に含まれるとき、この文字は "文字セット内にない文字を検索する" ことを意味します。したがって、[^A-Z] は、"大文字以外の文字を検索する" ことを意味します。一方、[^a-z] は、"小文字以外の文字を検索する" ことを意味します。つまり、このパターンは、大文字でも小文字でもない文字を検索していることを意味します。それには、数字、カンマやピリオド、キーボードに表示されているその他の見慣れない文字などが含まれます。

または、IgnoreCase プロパティを True に設定することもできます。

objRegEx.IgnoreCase = True

これにより、検索では大文字と小文字が区別されなくなります。すると、次のように Pattern プロパティを使用し、(IgnoreCase プロパティと組み合わせることで) 大文字でも小文字でもない文字を検索できます。

objRegEx.Pattern = "[^A-Z]"

部品番号が有効であることを確認する

では、少し高度な処理を実行してみましょう (正規表現を使用すると本当に高度な処理を実行できます。例については、Web サイト regexlib.com を参照してください)。この記事の前半で、次のように述べました。「strName の値が新しい部品番号で、部品番号は、4 桁の数字の後に 2 つの大文字のという指定のパターン (1234AB など) と一致する必要があるとします」と。変数の値がこのパターンと一致することを検証するにはどうすれば良いでしょうか。もちろん、次のように正規表現を使用します。

objRegEx.Pattern = "^\d{4}[A-Z]{2}"

このパターンは何を意味するのでしょうか。偶然の一致ですね。このパターンの意味をちょうど説明しようと思っていたところです。この場合、3 つの検索条件があります。

^\d{4} 

\d は、数字 (0 ~ 9 の範囲の文字) を検索していることを意味します。{4} は何を意味するのでしょうか。これは、ちょうど 4 文字 (つまり 4 桁) の文字と一致する必要があることを意味します。したがって、4 つの連続する数字を検索していることになります。

では、^ 文字はどうでしょうか。^ が 1 組の角かっこで囲まれていない場合、この文字は、値が後続のパターンで始まる必要があることを意味します。つまり、AA1234AB は一致する文字列としてマークされません (その理由は、この値は 4 つの連続する数字ではなく AA で始まっているためです)。必要に応じて、$ 文字を使用し、文字列の最後に一致するパターンが発生する必要があることを示すこともできます。ただし、それは必要ありません (少なくともこの例では、という話ですが)。

[A-Z]{2} 

既におわかりでしょうが、[A-Z] コンポーネントは大文字を意味します。では、{2} はどうでしょうか。そのとおりです。4 桁の数字の後に 2 つの大文字があることが必要です。

簡単ですね。ところで、ここでは、Count プロパティが 0 の場合は無効な値が検出されたことになります。その理由は、今回検索しているのは、文字列を無効にする 1 つの文字ではなく、パターンと完全に一致する文字列を検索しているからです。一致する文字列が検出されない場合、無効な値があることを意味します。また、コレクションの Count プロパティが 0 であることも意味します。

注 : その他に役立つのは、{3,} と {3,7} という 2 つの構文です。{3,} は、少なくとも 3 つの連続する文字 (または式) が必要であることを意味します。ただし、最小値は 3 ですが、最大値はありません。\d{3,} は、123、1234、123456789 に一致します。{3,7} は、3 つ以上、かつ 7 つ以下の連続する文字 (または式) が必要であることを意味します。したがって 123456 は一致する文字列と見なされますが、(7 つを超える連続する数字が含まれる) 123456789 という文字列は不一致と見なされます。

もう 1 つ役に立つパターンをご紹介しましょう。4 桁の数字、US というアルファベットの文字、最後の 2 文字 (アルファベットや数字などの任意の文字) と続く部品番号があるとします。このパターンに一致する文字列を検出する 1 つの方法を次に示します。

objRegEx.Pattern = "^\d{4}US.."

ご覧のとおり、このパターンは 4 桁の数字 (\d) で始まり、その後に US という文字が続く必要があります (US という文字のみで、それ以外の文字は一致する文字列とは見なされません)。US という文字列の後に、なんらかの文字が 2 文字必要です。正規表現では任意の文字をどのように示すのでしょうか。ちょっと待ってください、この方法はわかります。待ってください、わかりました。任意の 1 つの文字は、ピリオド (.) によって示されます。必然的に、2 つの文字は 2 つのピリオドによって示すことができます。

..

これは簡単でしたね。では、少し高度な処理を見てみましょう。部品番号の真ん中にある 2 つの文字が、部品が製造された国を示すとします。製造工場が米国、英国、スペインにある場合、US、UK、ES という 2 文字のコードはいずれも有効であることになります。正規表現で複数の選択肢を指定するには、一体どうすれば良いでしょうか。

次のスクリプトはどうでしょう。

objRegEx.Pattern = "^\d{4}(US|UK|ES).."

ここで重要なのは、(US|UK|ES) という部分です。3 つの許容される値 (US、UK、および ES) をかっこ内に配置し、各値をパイプ (|) 文字で区切っています。これが、正規表現で複数の選択肢を指定する方法です。

でもちょっと待ってください。角かっこ内には複数の選択肢を配置できると言いませんでしたか。それが [13579] で説明していた目的ではありませんでしたか。はい、そうです。ただし、ここで選択肢となるのは 1 つの文字です。[13579] という構文は常に 1、3、5、7、および 9 と解釈されます。135 や 79 を選択肢として使用するには、(135|79) という構文を使用する必要があります。また、次のように、かっこを使用して、検索する 1 つの単語を指定することもできます。

objRegEx.Pattern = "(scripting)"

また、かっこは省略することもできます。

objRegEx.Pattern = "scripting"

注 : そうですね、これは便利ですね。ですが、検索用語の一部としてかっこを含める必要がある場合にはどうすれば良いでしょうか。つまり、(scripting) という文字列を検索する場合にはどうでしょう。右かっこも左かっこも正規表現では予約文字であるため、各文字の前に \ を付ける必要があります。つまり、次のようになります。

objRegEx.Pattern = "\(scripting\)"

表現の自由

今月は、これで終わりです。正規表現についてさらに詳しく知りたい場合は、Web キャスト「システム管理者の文字列理論 : 正規表現の概要」(microsoft.com/technet/scriptcenter/webcasts/archive.mspx) を参照してください。そして、正規表現を習得および使用し始めるときには、次の Sophia Loren の不朽の言葉を肝に銘じておいてください。「間違いとは、豊かな人生を送るために支払わなければいけない税金のようなものである。」

すばらしい。私たち Scripting Guys は、思ったよりもずっと豊かな人生を送ってきたんですね。

Dr. Scripto のスクリプト パズル

パズルを解くスキルだけでなく、スクリプト作成スキルもテストする月に一度の課題です。

2008 年 1 月 : 名詞を探し出す

Dr. Scripto は、今月は、Windows PowerShell を使用することにしました。もちろん、コマンドレットなしで Windows PowerShell を使用することはできません。とはいえ、正しく使用していればの話ですが (Dr. Scripto はどんなことでも常に正しく行います)。Dr. Scripto は、このパズルのグリッドにいくつかのコマンドレットを配置しました。皆さんの課題は、それらを見つけることです。

さて、正直に言うと、正式なコマンドレットの名称を使用したら、このパズルの難易度はもう少し高くなるでしょう。コマンドレットは動詞と名詞を組み合わせた形式であるので、Get- や Set- などで始まるものが何十種類もあります。そのため、コマンドレットの動詞部分 (およびハイフン) を省き、名詞部分のみを残しました (ただ、ヒントをお教えすると、動詞部分はすべて Get- でした)。

今月の課題は、コマンドレットの名詞を探し出すことです。パズルに隠されている名詞の一覧は以下のとおりです (Scripting Guys は本当に寛大ですよね)。各語は、右から左へ、上から下へ、またその逆方向と縦横無尽に配置されていますが、斜めには配置されていません。例として最初の単語 (Location) をお知らせしておきます。では、頑張ってください。

名詞の一覧

Alias
ChildItem
Credential
Date
EventLog
ExecutionPolicy
Location
Member
PSSnapin
TraceSource
Unique
Variable
WMIObject

ANSWER:

Dr. Scripto のスクリプト パズル

解答 : 名詞を探し出す (2008 年 1 月)

Microsoft Scripting Guys は、マイクロソフトの仕事をしています、というよりもマイクロソフトにより雇われています。野球をプレイしたり監督したり観戦したり (または他のさまざまな活動を) しているのでない限り、彼らは TechNet スクリプト センターを運営しています。詳細については、www.scriptingguys.com を参照してください。

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; 許可なしに一部または全体を複製することは禁止されています.