Powershell入門 - 09. パイプライン

これまでの内容は他の言語を習得済みの人に向けて、if文やfor文などを使った"一般的な"プログラミングの書き方を学習してきました。  
Powershellでは`パイプライン`という仕組みを使うことで、これまでの内容をより簡潔に書くことが出来ます。  

今回はこのパイプラインの仕組みと、パイプラインと一緒によく使われる標準的なコマンドレットを紹介します。

# 1. パイプラインとは
パイプラインは、コマンドレットの処理結果や変数を処理した結果を、次の処理(コマンドレット)に渡すための仕組みです。  
例えば、以下のforeach文があったとします。
```powershell
$files = Get-ChildItem -Path D:\powershell\data\sampleFolder1 -File
foreach($file in $files){
    Get-Content $file.PSPath
}
```

この例ではGet-ChildItemでsampleFolder1直下にあるファイルのみのリストをfilesという変数に保存します。
このfilesからforeach文を使って、1件ずつファイルを取得し、その中身を順に表示しています。
この例から分かる通り、filesという変数が二つのコマンドをつなぐ役割を担っています。

一方で、この変数filesを使わずに、2つの処理をつなぐのがパイプラインになります。  
上記の処理をパイプラインで書くと、以下の通りになります。

```powershell
Get-ChildItem -Path D:\powershell\data\sampleFolder1 -File | Get-Content
```

パイプラインは記号`|`で書きます。
このパイプラインを使うことで、`|`の左側の処理の結果を`|`の右側の処理に渡すことが出来ます。
右側のGet-Contentには引数を渡していないように見えますが、パイプラインを使って左側の処理の結果が渡ってきています。  

他のプログラミング言語から入った人は、このパイプラインの考え方に最初は戸惑うかもしれません。  
最初なので丁寧に例題を使って学習していきましょう。

---
#### 例題1-1.
現在のカレントディレクトリを取得するコマンドレットを実行しなさい。

#### 例題1-2.
以下の二つのパスを接続した新しいパスを作成するコマンドレットを実行しなさい。
- D:\powershell\data
- sampleFolder2

※結果が`D:\powershell\data\sampleFolder2` になればOK

##### ヒント
新しいパスを作るには、Join-Pathコマンドレットを使います。

#### 例題1-3.
例題1-1,1-2を参考にして、`(現在のカレントディレクトリ)\test`となる新しいパスを作成する処理を実行しなさい。

#### 例題2.
以下の変数path_listには3つのフォルダのフォルダパスが格納されている。  
それぞれのフォルダについて、フォルダに格納されている子要素(ファイル、フォルダ)を表示しなさい。  
※変数をパイプラインでつないで使用すること

```powershell
$path_list = "D:\", "D:\powershell", "D:\powershell\data"
```

##### ヒント
`$path_list | xxxx`と書くことで、xxxxの処理に$path_listの中身を1件ずつ渡せます。  
xxxxに何が入るのか考えてみましょう。

---

# 2. パイプラインで使用するコマンドレット
最初に見せたパイプラインの例を改めて確認します。

```powershell
Get-ChildItem -Path D:\powershell\data\sampleFolder1 -File | Get-Content
```

上記の例では左側の処理の結果は複数のファイルとなります。  
この場合、右側のGet-Contentには複数のファイルが渡され、その1件ずつを順番に処理してくれます。  

この例では右側の処理が「ファイルの中身を見る」という簡単なものでしたが、より複雑な処理を行いたい場合があります。  
そのような場合に使える便利なコマンドレットを紹介します。

## 2.1. リストの各要素に対する処理を記述する(ForEach-Object)
`ForEach-Object`はリストなどの複数要素がある場合に、それぞれの要素にどのような処理をしたいかを記述するコマンドです。  
以下の例を見てみましょう。

```powershell
$count = 0
Get-ChildItem | ForEach-Object {
    Write-Host $_.FullName
    $count += 1
}
Write-Host ("ファイル数:" + $count)
```  

上記の例ではGet-ChileItemの結果がパイプライン`|`を通って、ForEach-Objectに渡されます。  
Foreach-Objectでは、渡された要素1件ずつに対してどのような処理を行うかを記述します。  
今回は、①ファイル名をフルパスで表示、②ファイル数をカウント の2つを行うことにしました。  
このようにForEach-Objectを使うことで、1つのコマンドレットでは実現できない複雑な処理を定義できます。  

#### 重要
ForEach-Objectで処理する各要素は`$_`という変数に格納されています。  
これは**自動変数**といって、powershell側が勝手に用意してくれる変数です。

---
#### 例題3.
以下の変数の値の合計値をパイプラインとForEach-Objectを使って求めなさい。

```powershell
$array = 1..10
```

#### 例題4.
以下のコマンドを実行しなさい。
その後、ForEach-Objectを使って、以下のコマンドのヘルプを取得しつつ、コマンドの数が何件あるかカウントしなさい。

```powershell
Get-Command -Name Get-Loca*
```

## 2.2. リストから要素を絞り込む
例えば、Get-ChildItemにはファイルのみ取得、フォルダのみ取得などの絞り込みを行うための引数が定義されています。  
しかしながら、ファイルサイズが10MB以下のファイルのみ取得などの細かな条件で絞り込むなど、標準のコマンドレットのオプションでは定義できないもの or 書きづらいものがあります。  

この場合の解決策の1つはForEach-Object内でif文を使って絞り込むことですが、`Where-Object`を使うことでより見通しが良く記述できる場合があります。
まずは以下の処理と処理結果を確認しましょう。

```powershell
PS D:\powershell\data\sampleFolder1> Get-ChildItem D:\powershell\data\sampleFolder1

    ディレクトリ: D:\powershell\data\sampleFolder1

Mode                 LastWriteTime         Length Name       
----                 -------------         ------ ----       
-a----        2022/08/11      7:24           9180 sample2.txt
-a----        2022/08/10     14:45             24 sample3.txt
-a----        2022/08/12      7:32            838 sample4.log
-a----        2022/08/12      7:33             14 sample5.py
```

上記はsampleFolder1の中身を確認するためのコマンドです。  
Lengthはファイルサイズ(バイト)になります。  

ファイルサイズが100バイト以上のものを絞り込む場合、Where-Obectを使って以下のように書きます。

```powershell
Get-ChildItem D:\powershell\data\sampleFolder1 | Where-Object {$_.Length -ge 100}
```

Where-Objectの中身にはtrue か falseで判断できる条件式を書きます。  
つまり、if文の条件式に書く内容を書くと、パイプラインで渡されたデータから、条件に合致するものを絞り込む仕組みです。

また、Where-Objectで絞り込んだ結果は、**さらにパイプラインで渡すことが出来ます。**
```powershell
Get-ChildItem D:\powershell\data\sampleFolder1 | 
    Where-Object {$_.Length -ge 100} |
        ForEach-Object { Write-Host $_.FullName }
```

処理が長くなる場合は、無理に1行で書かず、`|`の後で改行を入れましょう。

---
#### 例題5.
以下の処理結果から、ファイルの拡張子が.txtであるものを絞り込みなさい。
```powershell
Get-ChildItem D:\powershell\data\sampleFolder1
```

**ヒント**  
- ファイル名は、-Nameで取得可能。
- 文字列 -Like 検索文字列 でワイルドカードを使ったあいまい検索が可能

#### 例題6.
配列Pathsには、ファイルのパスが格納されている。  
この中から、実際に存在するパスのみを抜き出しなさい。

```powershell
$Paths = "D:\powershell\data\sample1.log", "D:\powershell\data\sample1.txt", "D:\powershell\data\sample11.txt"
```

##### ヒント
パスが存在するかどうかは、Test-Pathコマンドレットを使って調べることが出来ます。  
Where-Objectの中でTest-Pathを使いましょう。

---

## 2.3. リストを並び替える
取得したデータを並び替えるコマンドレットは`Sort-Object`になります。

```powershell
Get-ChildItem -Path D:\powershell\data\sampleFolder1 | Sort-Object
```

出力結果が複数ある場合、Sort-Objectによって順番を並び替えることが出来ます。  
デフォルトでは、並び順はName属性(ファイル名)の昇順です。  

別の属性を使って並び替えたい場合、`-Property`を指定します。
以下は、Length属性(=ファイルサイズ)で並び替えた例です。

```powershell
Get-ChildItem -Path D:\powershell\data\sampleFolder1 | Sort-Object -Property Length
```

また、降順に並び替えたい場合は、`-Descending`を付けます。

```powershell
Get-ChildItem -Path D:\powershell\data\sampleFolder1 | Sort-Object -Property Length -Descending
```

---
#### 例題7.
以下のコマンドレットの出力結果を更新日時(LastWriteTime)の降順に並び替えなさい。
```powershell
Get-ChildItem -Path D:\powershell\data\sampleFolder1
```
---

# 3. 最後に
パイプラインはPowershellの中でも特に重要な、奥が深い機能です。  
この資料ですべてを説明することはできないので、是非とも公式のマニュアルを読むことをお勧めします。
[パイプラインについて - Microsoft](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_pipelines?view=powershell-7.2)

Prev : [08. 文字列、パス文字列の操作](./334a6b2d37c0edf11be6)
Next : [10.関数](./42c823687bc116f0b7ce)

[目次へ戻る](./926b8053a031539437a3)