Posted at

PowerShell で awk の /pattern/,/pattern/ みたいなことをする

More than 1 year has passed since last update.

相当するコマンドレットが思い当たらなかったので、以下のような関数を作りました。

同等のことができるコマンドレットがあれば教えてください。

あと関数の命名センスをください。


Select-Range.ps1

Function Select-Range {

Param(
[string]$Property,
[string]$Pattern1,
[string]$Pattern2
)
Begin {
$flag = $False
}
Process {
$Target = $_
if ($Property) { $Target = $_.$Property }
if ($Target -EQ $Null -OR $Target.GetType().FullName -NE "System.String") {
Write-Error "Target Property is not System.String!"
return
}

if(!$flag) { $flag = $Target -Match $Pattern1 }
if($flag) { $_ } # output
if($flag) { $flag = $Target -NotMatch $Pattern2 }
}
}


使い方は以下のような感じで


使い方

PS> # 例えば以下のようなファイルがあったとして(Linux の sar -A です)

PS> Get-Content C:\Temp\20170611.txt

Linux 4.11.3-200.fc25.x86_64 (fedora25) 06/11/17 _x86_64_ (1 CPU)

13:26:39 LINUX RESTART (1 CPU)

13:30:05 CPU %usr %nice %sys %iowait %steal %irq %soft %guest %gnice %idle
13:40:36 all 0.05 0.00 0.07 0.02 0.00 0.11 0.03 0.00 0.00 99.72
13:40:36 0 0.05 0.00 0.07 0.02 0.00 0.11 0.03 0.00 0.00 99.72
13:50:36 all 0.02 0.00 0.03 0.01 0.00 0.09 0.03 0.00 0.00 99.83
13:50:36 0 0.02 0.00 0.03 0.01 0.00 0.09 0.03 0.00 0.00 99.83
14:00:36 all 0.06 0.00 0.07 0.02 0.00 0.10 0.04 0.00 0.00 99.72
14:00:36 0 0.06 0.00 0.07 0.02 0.00 0.10 0.04 0.00 0.00 99.72
14:10:36 all 0.01 0.00 0.02 0.00 0.00 0.08 0.02 0.00 0.00 99.88
14:10:36 0 0.01 0.00 0.02 0.00 0.00 0.08 0.02 0.00 0.00 99.88
Average: all 0.03 0.00 0.04 0.01 0.00 0.10 0.03 0.00 0.00 99.79
Average: 0 0.03 0.00 0.04 0.01 0.00 0.10 0.03 0.00 0.00 99.79

13:30:05 proc/s cswch/s
13:40:36 0.14 46.97
13:50:36 0.04 35.14
14:00:36 0.07 42.10
14:10:36 0.03 29.48
Average: 0.07 38.53

13:30:05 pswpin/s pswpout/s
13:40:36 0.00 0.00
13:50:36 0.00 0.00
14:00:36 0.00 0.00
14:10:36 0.00 0.00
Average: 0.00 0.00

13:30:05 pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff
13:40:36 2.32 2.30 24.35 0.02 17.19 0.00 0.00 0.00 0.00
13:50:36 0.25 0.77 5.80 0.00 3.87 0.00 0.00 0.00 0.00
14:00:36 0.09 0.82 12.98 0.00 9.70 0.00 0.00 0.00 0.00
14:10:36 0.00 0.91 4.11 0.00 3.28 0.00 0.00 0.00 0.00
Average: 0.69 1.21 11.97 0.00 8.62 0.00 0.00 0.00 0.00

13:30:05 tps rtps wtps bread/s bwrtn/s
13:40:36 0.34 0.03 0.30 4.64 4.60
13:50:36 0.15 0.01 0.15 0.51 1.54
14:00:36 0.17 0.00 0.17 0.17 1.64
14:10:36 0.18 0.00 0.18 0.00 1.83
Average: 0.21 0.01 0.20 1.37 2.43

13:30:05 frmpg/s bufpg/s campg/s
13:40:36 -1.04 0.00 0.58
13:50:36 -0.09 0.00 0.07
14:00:36 -0.06 0.00 0.03
14:10:36 -0.03 0.00 0.00
Average: -0.31 0.00 0.17

PS>
PS>
PS>
PS>
PS> # 以下のような感じで部分抽出ができます。
PS> # ひとつのファイルに複数のデータを混在させるの行儀悪いからやめて欲しい。
PS> Get-Content C:\Temp\20170611.txt | Select-Range -Pattern1 pgpgin -Pattern2 Average
13:30:05 pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff
13:40:36 2.32 2.30 24.35 0.02 17.19 0.00 0.00 0.00 0.00
13:50:36 0.25 0.77 5.80 0.00 3.87 0.00 0.00 0.00 0.00
14:00:36 0.09 0.82 12.98 0.00 9.70 0.00 0.00 0.00 0.00
14:10:36 0.00 0.91 4.11 0.00 3.28 0.00 0.00 0.00 0.00
Average: 0.69 1.21 11.97 0.00 8.62 0.00 0.00 0.00 0.00
PS>
PS>
PS>
PS>
PS> # ここまでできるとテキストの解析までやりたくなるので以下のようにしてデータ化します。
PS> Get-Content C:\Temp\20170611.txt |
Select-Range -Pattern1 pgpgin -Pattern2 Average |
% { $_ -replace "\s+","," } |
% {$index=1} { if($index -eq 1) { $_ = $_ -replace "^.{8}","Time" } $index++; $_ } |
select-string -NotMatch Average |
select -expand line |
convertfrom-csv |
% { $_.time = [datetime]$_.Time; $_ } |
ft

Time pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff
---- -------- --------- ------- -------- -------- --------- --------- --------- ------
2017/06/11 13:40:36 2.32 2.30 24.35 0.02 17.19 0.00 0.00 0.00 0.00
2017/06/11 13:50:36 0.25 0.77 5.80 0.00 3.87 0.00 0.00 0.00 0.00
2017/06/11 14:00:36 0.09 0.82 12.98 0.00 9.70 0.00 0.00 0.00 0.00
2017/06/11 14:10:36 0.00 0.91 4.11 0.00 3.28 0.00 0.00 0.00 0.00

PS>
PS>
PS>
PS>
PS> # このままだと [datetime] した時に、
PS> # 必ず作業当日になってしまうので、ファイル名から日付を取るようにしたい。
PS> $file = Get-Item "C:\Temp\20170611.txt"
PS> $date = $file.BaseName -replace "(....)(..)(..)","`$1/`$2/`$3 "
PS> Get-Content $file |
Select-Range -Pattern1 pgpgin -Pattern2 Average |
% { $_ -replace "\s+","," } |
% {$index=1} { if($index -eq 1) { $_ = $_ -replace "^.{8}","Time" } $index++; $_ } |
select-string -NotMatch Average |
select -expand line |
convertfrom-csv |
% { $_.time = [datetime]($date + $_.Time); $_ } |
ft

PS> # 出力は変化ないので省略


そういえば PowerShell は n 番目(大抵の場合 n=1 )のオブジェクトだけストリームの中で処理するという処理が手間ですね。

例えば、先ほど使用した以下のコマンドは、

% {$index=1} { if($index -eq 1) { $_ = $_ -replace "^.{8}","Time" } $index++; $_ }

Linux だと以下のように書けます。

sed -r '1s/^.{8}/Time/'

一回変数に入れれば良いという主張はその通りなのですが、シェル芸人的にはパイプの中で片付けたいところ。。。


誰かこんな感じの作ってくれないかなぁ

PS> get-content $file | Edit-Stream -Index 1 -Replace "^.{8}","Time"