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 


内
```
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201341/1650f277-e8a9-b570-b4a0-19201f20a3e6.png)

#一連の処理を関数化
###実例
```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
```
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201341/e07ea883-5438-8f2f-6543-71dc87224f33.png)

###備考
- `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