このページの本文へ

Windows Info 第415回

ConvertFrom-Stringを使って、テキストデータをテンプレートで読み込む

2024年01月21日 10時00分更新

文● 塩田紳二 編集● ASCII

  • この記事をはてなブックマークに追加
  • 本文印刷

 世の中には、単純なテキストで表現されているデータが結構ある。CSVやJSONのようにちゃんとした定義があって、それに準じているテキストは簡単に読み込むことができるが、基本的には規則的だが、例外とか複数のパターンがあるような場合、手作業で形式を揃えるなどの前処理が必要になる。しかし、対象の数や量が増えるとそれも簡単ではない。

 こうした場合、PowerShellのConvertFrom-Stringコマンドの「自動生成されたサンプル ドリブン解析」機能を使うと、何とかなることがある。簡単にこの機能を説明すると、元のデータから値を抜き出すための「例」(テンプレートと呼ばれる)を作り、これを元に値を抽出する。完璧ではないが、コツを覚えると使える場面もある。

 ただしこの機能、内部エラーが発生すると、「情報をマイクロソフトに送ってほしい」といったメッセージが出る。まだ、改良の余地があるということだろう。

ConvertFrom-String

ConvertFrom-Stringコマンドではテンプレートを使った解析で内部エラーが出ることがある。そのような場合、情報をMicrosoftに送れというメッセージが表示される。このことなどから、まだ、完成の域には達していないと思われる

 また、Windows PowerShell(Ver.5.1)でも、PowerShell(Ver.7.x)でも利用できるが、挙動が若干異なる。片方で利用できるテンプレートが他方でエラーになることもあれば、同じデータであっても結果が異なる場合がある。

そもそもConvertFrom-Stringとは?

 PowerShellで「ConvertFrom」で始まるコマンドは、対象データからPowerShellのオブジェクト(PSCustomObject)を作るコマンドだ。たとえばConvertFrom-Csvならば、CSV(カンマ区切りファイル)からPowerShellのPSCustomObjectを作る。一回PSCustomObjectになってしまえば、あとは、PowerShellで処理ができる。

 ConverFrom-Stringコマンド(https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/convertfrom-string?view=powershell-5.1&viewFallbackFrom=powershell-7.3)には、大きく2つの機能がある。1つは、区切り文字を使ったテキスト行の変換である。このとき区切り記号には、正規表現を指定できる。これは、多くの言語にある正規表現クラスのsplitメソッドと同じもの。イメージ的には、Excelの「区切り記号」機能のようなものである。

 具体的には、-Delimiterオプションに正規表現で区切りとなる文字を指定する。

ConvertFrom-String

-Delimiterオプションで区切りパターンを指定することで、行単位のテキストを分解できる。逆クオートは、PowerShellでエスケープ文字を表現するためのもの。たとえば、「`t」はタブとなり、Unix/Linuxなどでの「\t」に相当する

 何も指定しないと空白文字(スペース、タブ、復帰+改行、改行、復帰、フォームフィード)の1回以上の繰り返しが区切りとなる。ただし、ファイルから読み込む場合と文字列を直接処理した場合では、改行の扱いが異なる。

 具体的には、ファイルから読み込む場合には、CR+LFが行区切りとなり、ConvertFrom-Stringは行単位に処理する。しかし、文字列をパイプラインで与えた場合、CR+LFは単なる文字となり、デフォルトの区切り文字にはなるが、行単位での処理にはならない。文字列を与える場合には、カンマで区切って文字列の配列として渡す必要がある。これはConvertFrom-Stringの問題ではなく、PowerShellの挙動である。

"this`t`tis a pen.TEST`nNext`r`nLine","test1`ttest2" | ConvertFrom-String -Delimiter '[.\s]+'

 区切り文字として指定した正規表現「[.\s]+」では、ピリオド「.」または空白文字「\s」(スペース、タブ、復帰+改行、改行、復帰、フォームフィード)の1回以上の繰り返し「+」を表す。

 区切り文字が明確にわかっている場合には、-Delimiterオプションを使い、空白文字ならオプションなしでConvertFrom-Stringで処理すればよい。

例示によるデータの抽出

 しかし、世の中には、データが都合良く記述されていないこともある。こうした場合、元データからデータを取り出すための「例示」を作り、これをテンプレートファイルとしてConvertFrom-Stringに与えることで、データが抽出できることがある。

 この場合、区切り文字による分割とは異なり、1件(1レコード)のデータがテキストの1行になっている必要もない。1つのデータが複数行になっていても、データの抽出が可能だ。これを「自動生成されたサンプル ドリブン解析」という。Microsoftの研究所で作られたExcelのフラッシュフィルと同じ基本技術である。

 今回は、こうした形式でデータを出力するnetshコマンドを使って、無線LANアクセスポイントを列挙し、信号強度などを取り出してみる。

ConvertFrom-String

netshコマンドを使うと、受信可能なアクセスポイントの一覧を表示でき、その中には信号強度(シグナル)のようなパラメーターがある。ただし、各SSID内の情報は一定の形式ではなく、受信状態や相手先の機能、設定などにより複数のパターンが存在する

ConvertFrom-String

netshコマンドの出力を下で掲載しているリストのテンプレートを使ってConvertFrom-Stringコマンドで解析できる。コマンド出力は複数のパターンを持つが、ある程度までなら、自動でこれに対応できる

 Windowsの多くの機能がPowerShellで扱えるが、無線LANアクセスポイントを扱う処理は、PowerShellの標準コマンドにはないため、netshコマンドを使わざるを得ない場合がある。netshに関しては、以前からPowerShellに置き換わるような話があったものの、代替が可能なのは一部の機能に留まり、いまだにnetshコマンドも残ったままだ。

 例示によるデータの抽出には、テンプレートを作成する必要がある。1件のデータ(例)の形式は完璧に同一である必要はなく、レコードにより存在しない行やデータがあってもかまわない。

 テンプレートは、元のテキストから抜き出したい値の部分を波括弧で括り、値の前にプロパティ名とコロンをおく(以後説明のためこの記述をプロパティと呼ぶ)。オプションでプロパティ名の前に型キャストを置くこともできる。以下のような形式を使う

{[型キャスト]<プロパティ名>*:<値の例>}  繰り返しの先頭プロパティ
{[型キャスト]<プロパティ名>:<値の例>}  その他のプロパティ

 前記のnetshコマンドなら以下のリストのようなテンプレートを作成する。リスト中「SSID」の次にある「{[int]id*:1}」の部分が値を抜き出す部分だ。これは、処理対象データから一部を抜き出してエディタなどで作成する。途中の空白なども処理対象テキストと一致させる。

ConvertFrom-String

 この項目にだけプロパティ名(id)の後ろに「*」があるが、これがデータ1件(1レコード)の最初の部分であることを示す。以後、繰り返しの先頭部分は、同じように「{[int]id*:」で始める。コロンの後ろの数値などは例示であるので何でもよい。

 ただし、記号や大文字小文字の区別などで文字列パターンによる照合を学習しようとするので、記号の種類が異なるようにパターンを定義する。複数件のパターンを定義するが、すべての値を同じように処理しなくてもいい。

 たとえば、2つ目のパターンでは、1つ目と同じく「{netname:Buffalo-G-DA80_EXT}」を定義しているが、これは、文字列に1つ目の「{netname:HUMAX-17141}」と異なるアンダーバー文字が含まれているからだ。1行目だけだと、ハイフンで区切られた電話番号のようなパターンとして学習してしまうことがある。

 また、抽出するプロパティは、最低限とし、テンプレートに入れるサンプルの数(レコード数)も最低限とすることが望ましい。というのは、プロパティの数が増えるほど動作が不安定になるからだ。netshの出力では、1件のデータにときどき「Bss ロード」という項目が入ることがある。また、BSSIDの項目が複数入ることがある。その中にある「接続されているステーション」や2つ目のBSSIDのシグナルなどを取り出そうとすると、正しく解析されない、あるいは内部エラーが発生する。

 元データのパターンを選び、テンプレートファイルに入れて、段階的に値取得のプロパティを追加していくとうまく行きやすい。

 なお、初回の実行では、テンプレートを学習して、内部で動作するコードを生成するため、少し時間がかかる。テンプレートに問題がない場合、ConvertFrom-Stringに「-UpdateTemplate」を付けると、テンプレートファイルに学習結果を保存するため、実行が早くなる。

 このことや繰り返し編集の必要などから、テンプレートは外部ファイルに保存して実行させたほうがよく、コマンドラインでテンプレートを指定する「-TemplateContent <テンプレート文字列>」は、プロパティが2~3個までの場合に使うほうがいいだろう。

 なお、機能を解説したマイクロソフトのブログ「ConvertFrom-String: Example-based text parsing(英語)」(https://devblogs.microsoft.com/powershell/convertfrom-string-example-based-text-parsing/)によれば、プロパティ名後ろに付ける「*」は、サブ構造の繰り返しに利用でき、プロパティ名の前に「!」を付けることで、その部分をプロパティと認識させないなどの機能があるとされているが、文法的なエラーにならないものの、内部エラーになることが多い(うまく行くこともある)。また、ブログにある「-Debug」オプションは現行バージョンでは廃止されている。

 netshコマンドの結果取得は、プログラムを書いて抽出していくのはかなり面倒で、テンプレートを作るだけで何とかなるというのはありがたい。とはいえ、頑強な機能ではなく、テンプレートで指定されていないパターンに対応しきれないことがある。

 今回の例で言えば、無線LANの暗号化がされていないアクセスポイントでは、情報が空欄になる、あるいは日本語文の表記「フリー」などが混ざることがあって、こうした自体に対応できないことがある。しかし、これに対応させようとテンプレートに例を追加すると、逆に不安定になる、またはプロパティ値を間違える可能性もある。まあ、そこそこ使える未完成の機能として、欲張らずに「うまく行ったら儲けもの」的に使うほうがいいだろう。

カテゴリートップへ

この連載の記事

注目ニュース

ASCII倶楽部

プレミアムPC試用レポート

ピックアップ

ASCII.jp RSS2.0 配信中

ASCII.jpメール デジタルMac/iPodマガジン