Powershellでcsvデータからgrep的ななにか


概説

CSVをデータベースにして検索システムを作った際にUnix系のgrepのように検索語に強調をつけたかった。

注意点

職場のがお粗末だったのでもっといい方法はないかと模索したPowerShell初心者の備忘録です。あんまりPowerShellでの開発経験がなかったため備忘録もかねてかなり冗長に説明しています。適宜読み飛ばしてもらったほうがいいかもしれません。

実行環境

  • Windows 10
  • PowerShell

環境面の挫折ポイント

  • どうやらWindows PowerShellではANSIIエスケープが使えるらしい(参考URL参照)
  • 職場の環境が対応していなかったため断念
  • Write-Host-Foreground-Colorオプションで無理やり対応させた

元データ

phsdata.csv
PS > $phs | ft

氏名   所属   PHS番号
--   --   -----
白鵬   内科   5678 
鶴竜   内科   4839 
正代   会計課  1823 
朝乃山  外科   4852 
貴景勝  放射線科 1800 
照ノ富士 精神科  8493 
隆の勝  内科   2939 
高安   内科   3884 
御獄海  外科   2858 
大栄翔  精神科  1923 

前処理

Where-Objectについて

PowerShellには便利なWhere-Objectというフィルタ関数があります。

PS > $phs | Where-Object {$_.所属 -match '内'} | ft

氏名  所属 PHS番号
--  -- -----
白鵬  内科 5678 
鶴竜  内科 4839 
隆の勝 内科 2939 
高安  内科 3884 

この例だと所属に対して「内」でフィルタをかけています。もちろんデータ型はSystem.Array型です。
(実際はSystem.ArrayにネストされたPSCustomObject型の集合)

PS > $match = $phs | Where-Object {$_.所属 -match '内'}
PS> $match.GetType()

IsPublic IsSerial Name                                     BaseType                                                     
-------- -------- ----                                     --------                                                      
True     True     Object[]                                 System.Array 
PS > $match | ForEach-Object -Process { $_.GetType()}

IsPublic IsSerial Name                                     BaseType                                                                  
-------- -------- ----                                     --------                                                                  
True     False    PSCustomObject                           System.Object                                                             
True     False    PSCustomObject                           System.Object                                                             
True     False    PSCustomObject                           System.Object                                                             
True     False    PSCustomObject                           System.Object   

特に標準でハイライトにしてくれる機能は用意されていないようです。
標準出力に出力してくれるWrite-Hostにも特定文字列に対して文字色を変化させるようなオプションは用意されていません。
ここから検索語に対してハイライト化する処理を無理やり追加したいとおもいます。

Out-String

PowerShellには出力を文字列にしてくれるOut-Stringという便利な関数があるそうです。
使わない手はないですね。

PS > $result = $phs | Where-Object {$_.所属 -match '内'} | Out-String
PS > $result

氏名  所属 PHS番号
--  -- -----
白鵬  内科 5678 
鶴竜  内科 4839 
隆の勝 内科 2939 
高安  内科 3884 

見た目は変わりませんが、ちゃんと文字列型になっているはずです。
あくまでも文字列ですのでFormat-Tableなどは有効ではありません。もしそのような処理をしたい場合は文字列化以前に行うべきです。

PS > $result.GetType()

IsPublic IsSerial Name                                     BaseType                                                                  
-------- -------- ----                                     --------                                                                  
True     True     String                                   System.Object     

-split演算子

PowerShellには-split演算子という形で文字列の分割メソッドが提供されているようです。さっきの検索文字列で区切ってみたいと思います。

PS > $result = $result -split '内'
PS > $result

氏名  所属 PHS番号
--  -- -----
白鵬  
科 5678 
鶴竜  
科 4839 
隆の勝 
科 2939 
高安  
科 3884 

検索文字列を除いた文字列が配列に格納にされている状態です。
もちろん型はSystem.Array型に変化しています。

PS > $result.GetType()

IsPublic IsSerial Name                                     BaseType                                                                  
-------- -------- ----                                     --------                                                                  
True     True     String[]                                 System.Array    

本題

ここで配列となった文字列に対してWrite-Hostを用いて装飾を加えていきたいと思います。
繰り返し処理に関してはForeach-Objectを用いてパイプでつなぎました。
Write-Host-NoNewlineは最後の改行をキャンセルする仕様です。

PS > $result | ForEach-Object -Process {
Write-Host $_ -NoNewline
Write-Host '内' -NoNewline -ForegroundColor Yellow
}

氏名  所属 PHS番号
--  -- -----
白鵬  内科 5678 
鶴竜  内科 4839 
隆の勝 内科 2939 
高安  内科 3884 


内

一連の処理を関数化

実例

test.ps1
$phs = Import-Csv .\phsdata.txt
function Search-Data-By-Value($value,$column,$data){
$result = $data | Where-Object {$_.$column -match $value} | Out-String
$result -split $value | ForEach-Object -Process {
    Write-Host $_ -NoNewline
    Write-Host $value -NoNewline -ForegroundColor Yellow
    } -End {
    Write-Host 'を検索しました。'
    }
}

Search-Data-By-Value '内' '所属' $phs

備考

  • Foreach-Object-Endを用いて無理やり最後の「内」をそれっぽく見せました。
  • PSCustomObjectのカラム指定は変数でも可能なのでカラム指定は引数にしました。

考察

  • PowerShellは標準でImport-Csvのようなパーサを用意してくれているのでちょっとしたデータベースlikeな処理には向いてるかも。
  • 今回はWrite-Hostに収束したが、Write-Output等を活用していろんなことができそう。
  • 最後の余計な検索語の表示に関しては消去する方法が思いつかなかった。誰か頭のいいひと教えてください。

参考URL

https://forsenergy.com/ja-jp/windowspowershellhelp/html/d704165a-2007-4c7c-bab9-6e2d2c8c5ca5.htm
https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_special_characters?view=powershell-7.1