ワンライナー内でのセミコロンの使い方【Powershell】

# はじめに

最近業務の効率化の一環としてPowershellを覚えている最中のため、
覚えておくと少し便利なTipsを共有しておきます。

今回は自分なりに調べたワンライナー内でのセミコロンの使い方です。

## 目的

例えば、次のようなコマンドと戻り値を持つようなファイル群があって、
各ファイルで使われているimportをファイルごとに表示したくなったとします。

```
PS D:\eclipse-workspace\janken> Get-ChildItem *.java -Recurse | Select-Object -Property FullName -First 5

FullName
--------
D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\JankenHands.java
D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\JankenModel.java
D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\JankenResult.java
D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\MontyJanken.java
D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\MontyJankenBattle.java
```

### 失敗例

単に上の入力をそのままパイプラインに流してしまうと、
下の出力のようにどのファイルがどのimportと対応しているかがわからなくなります。

```powershell
Get-ChildItem *.java -Recurse | Select-Object -Property FullName -First 5 | % { Get-Content $_.FullName | Where-Object {$_ -match 'import'}}
```

```
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Random;
```

### 成功例

ここで利用するのが、文の切れ目を表すことができるセミコロンです。
処理を適切に区切ることで、下記のような出力を得ることができます。

```powershell
Get-ChildItem *.java -Recurse | Select-Object -Property FullName -First 5 | % { Write-Host "> $($_.FullName)" ; Get-Content $_.FullName | Where-Object {$_ -match 'import'} | Write-Host ; Write-Host '' }
```

```
> D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\JankenHands.java
import java.util.ArrayList;
import java.util.List;

> D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\JankenModel.java
import java.util.Random;

> D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\JankenResult.java

> D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\MontyJanken.java
import java.util.Random;

> D:\eclipse-workspace\janken\src\main\java\personal\mickie\janken\Model\MontyJankenBattle.java

```

## 仕組み

パイプライン内で```Get-ChildItem *.java -Recurse | Select-Object -Property FullName -First 5```を実行すると、目的で示された5つのファイルがパイプラインに流れてきます。
このパイプラインのオブジェクトに対して、

* ファイルのフルパスを表示(```Write-Host "> $($_.FullName)"```)
* import文のフィルタリング(```Get-Content $_.FullName | Where-Object {$_ -match 'import'} | Write-Host```)
* 空白行の挿入(```Write-Host ''```)

上記を順番に行いたいため、%(Foreach-Object文)の中括弧内で3行の処理をセミコロンでまとめました。

## 余談

下記のMicrosoftのリファレンス内で、次のような記述ができるとありました。

https://docs.microsoft.com/ja-jp/previous-versions/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-6


```
1..2 | ForEach-Object { 'begin' } { 'process A' }  { 'process B' }  { 'end' }

begin
process A
process B
process A
process B
end
```

つまり、下の2つの文は等価なようです。

```powershell

Get-ChildItem *.java -Recurse | Select-Object -Property FullName -First 5 | % { Write-Host "> $($_.FullName)" ; Get-Content $_.FullName | Where-Object {$_ -match 'import'} | Write-Host ; Write-Host '' }
Get-ChildItem *.java -Recurse | Select-Object -Property FullName -First 5 | % {} { Write-Host "> $($_.FullName)" } {Get-Content $_.FullName | Where-Object {$_ -match 'import'} | Write-Host}{Write-Host '' } {}

```

よっぽどのことがないと最初の方が可読性は高いと思います。