Home > Windows にまつわる e.t.c.

PowerShell で目的の文字列を抽出する


PowerShell コマンドレットであれば、戻り値がオブジェクトなので必要なプロパティを簡単に取り出すことができますが、、コマンドラインツールの場合は戻り値が文字列なので、必要な部分を切り出す必要があります。

例えば、ntp.nict.jp との時間のズレを求める場合は、w32tm /monitor /computers:ntp.nict.jp を実行します。

PS C:\> w32tm /monitor /computers:ntp.nict.jp

分析中 ntp.nict.jp (1 of 1)...


ntp.nict.jp[[2001:df0:232:eea0::fff4]:123]:
    ICMP: 6ms 遅延
    NTP: +0.0002190s ローカル コンピューターの時刻からのオフセット
        RefID: 'NICT' [0x5443494E]
        階層: 1

 

人が見れば、「+0.0002190」がズレであることが一目でわかりますが、これを値として取り出すにはどうすれば良いでしょう。

僕が良く使うては、AWK 方式と $Matches を使う2通りの方法です。

それぞれの方法を紹介しましょう。

 

AWK 方式

AWK とは、UNIX でよく使われていた(過去形が正しいかは不明)空白文字等で行をセパレートし、目的の文字列をハンドリングするツールです。
AWK は DOS 版もあり、インストール不要で exe を実行するだけだったので僕はよく使っていました。

PowerShell も Split で特定文字で行分解が出来るので、同様な事が可能です。

まずは foreach で w32tm の出力結果を回して、「NTP: 」の行を特定します。

目的の行が出てきたら、まずは Splitを使って「s」で分割すると、以下のようになります

PS C:\> ("     NTP: +0.0002190s ローカル コンピューターの時刻からのオフセット").Split("s")
     NTP: +0.0002190
 ローカル コンピューターの時刻からのオフセット

 

すると、セパレートした1つめ文字列に目的のズレが含まれます。更にこいつを「:」で分割すると、以下のようになります。

PS C:\> ("     NTP: +0.0002190").Split(":")
     NTP
 +0.0002190

 

今度は2つ目の文字列に目的のズレが入っています。よく見ると先頭に余分な空白が入っているので、Trim で余分な空白を取り除けば目的のズレを値して取り出すことができます。

PS C:\> (" +0.0002190").Trim()
+0.0002190

これを一連のスクリプトで書くとこんな感じになります。

# w32tm の結果を格納
$Results = w32tm /monitor /computers:ntp.nict.jp

# 1行づつ処理する
foreach( $Line in $Results ){
    # 「NTP: 」の行を特定
    if( $Line -match "NTP: "){

        # 「s」で分割
        $Buffers = $Line.Split("s")

        # 1つ目の文字列
        $TergetBuffer = $Buffers[0]

        # 「:」で分割
        $Buffers = $TergetBuffer.Split(":")

        # 2つ目の文字列
        $TergetBuffer = $Buffers[1]

        # Trim で余分な空白を取り除く
        $DifferentTime = $TergetBuffer.Trim()
    
        # Loop から抜ける
        break
    }
}

echo $DifferentTime

 

$Matches を使う

$Matches とは -match で合致した内容を格納する自動変数です。

百聞は一見に如かずなので、どのような動作をするのか見てみましょう。

PS C:\> "     NTP: +0.0002190s ローカル コンピューターの時刻からのオフセット" -match "NTP: .*s"
True
PS C:\> $Matches

Name                           Value
----                           -----
0                              NTP: +0.0002190s

 

Value にマッチした文字列が格納されていますね。

この値から余分な文字列を消しても良いのですが、$Matches に必要な文字列だけを格納することができます。

(?<プロパティ名>正規表現?)

 

比較正規表現にこの呪文を埋め込むと、目的の文字列だけを取り出すことができます。

先ほどの「NTP: .*s」を「NTP: (?<DifferentTime>.*?)s」に書き換えるとどうなるかを試してみます。

PS C:\> "     NTP: +0.0002190s ローカル コンピューターの時刻からのオフセット" -match "NTP: (?<DifferentTime>.*?)s"
True
PS C:\> $Matches

Name                           Value
----                           -----
DifferentTime                  +0.0002190
0                              NTP: +0.0002190s

 

DifferentTime に目的のズレのみが格納されました。
つまり、カッコの中に書かれた範囲のみを取り出すことが出来るのです。

これを一連のスクリプトで書くとこんな感じになりますね。

# w32tm の結果を格納
$Results = w32tm /monitor /computers:ntp.nict.jp

# 1行づつ処理する
foreach( $Line in $Results ){
    # 「NTP: .*s」の行を特定
    if( $Line -match "NTP: (?<DifferentTime>.*?)s"){

        # Loop から抜ける
        break
    }
}
echo $Matches.DifferentTime

 

注意点は、行末とマッチする場合、行末正規表現の「$」を付けないと値が取れないです。

応用編で、(?<プロパティ名>正規表現) と書くと、マッチした全体をプロパティに格納することができます。

(?<プロパティ名>正規表現?) は  -match "^(?<date>.*?) (?<time>.*?) (?<timetaken>.*?)$" みたいな感じで複数書くことが出来るので、ログ解析にも応用できます。

ただし、-match はコストが大きいので、大量データーをハンドリングする場合 AWK 方式の方が短時間で処理出来るので、ケースバイケースで使い分けてください。

 

関連情報

標準出力/テキストファイルをオブジェクトにする PowerShell フィルタ
http://www.vwnet.jp/Windows/PowerShell/2016092402/StdOut2Object.htm

 

 

back.gif (1980 バイト)

home.gif (1907 バイト)

Copyright © MURA All rights reserved.