なぜPowerShellはテキストではなくオブジェクトを出力するのか

## はじめに
PowerShellはWindowsの伝統的なシェルであるコマンドプロンプトに代わる次世代シェルです。

PowerShellはUNIXシェルと同様にパイプラインがあります。コマンドプロンプトにもパイプはありましたが、あまり多くのコマンドの組み合わせには耐えられません。

一方でUNIXシェルとは違うところがあります。コマンドが入出力するのが**テキスト**ではなく**オブジェクト**であるという点です。

PowerShellの入門書を読むと多くの場合はじめにこの特徴について書いてあり、**オブジェクトを入出力することによりパイプがより強力になっている**ことが分かります。

とりわけ、パイプと組み合わせる以下のコマンドが分かりやすいです。

| UNIX    |   PowerShell     |
| ------- | -----------------|
| `awk  ` | `Select-Object ` |
| `grep ` | `Where-Object  ` |
| `xargs` | `ForEach-Object` |

## UNIXとPowerShellのパイプラインを比較する

### UNIXのパイプラインの例（psとgrep）

UNIXのシェルはパイプラインによってコマンドを組み合わせることができます。たとえば、実行中のプロセス一覧を出力する `ps` というコマンドがあります。

```bash:bash
$ ps
  PID TTY          TIME CMD
    8 tty1     00:00:00 bash
   24 tty1     00:00:00 ps
```
また `grep` というコマンドがあります。入力したテキストのうち指定したパターンに当てはまる行のみを出力するコマンドです。

`ps` の出力を `grep bash` の入力にパイプ `|` で与えると、

```bash:bash
$ ps | grep bash
    8 tty1     00:00:00 bash
```

となります。`ps` の出力行のうち `bash` というパターンに当てはまる行が出力されています。

### UNIXはテキストを入出力する
UNIXのシェルで入出力されるのはテキストです。たとえば、`ps` の出力を `grep PID` の入力にパイプ `|` で与えると、

```bash:bash
$ ps | grep PID
  PID TTY          TIME CMD
```

となります。`ps` の出力の見出し行（`PID`というパターンに当てはまった）のみ出力されました。つまり、`ps` の出力の見出し行と一覧行にはフォーマット上の区別はない、ということです。

### PowerShellのパイプラインの場合（psとWhere-Object）
PowerShellにも `ps` というコマンドがあります。

```powershell:powershell
PS D:\> ps

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    101       7     1348       1484              4264   0 ApsInsSvc
    124       8     1564        884              4296   0 armsvc
...
    785      32    68884      82104       4.16   9764   1 powershell
...
```

UNIXと同様にパイプで他のコマンドの入力に出力を与えられます。たとえば、`Where-Object ProcessName -match powershell` の入力に `ps` の出力を与えると次のよう出力されます。

```powershell:powershell
PS D:\> ps | Where-Object ProcessName -match powershell

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    656      32    64224      78016       6.06   9764   1 powershell
```

`ps` の出力オブジェクトのうちプロパティ `ProcessName` が `powershell` というパターンに当てはまるオブジェクトのみ出力されました。

### PowerShellはオブジェクトを入出力する
出力されるのはテキストではなくオブジェクトです。だからこそプロパティ `ProcessName` で絞り込むことができたわけです。

オブジェクトなのでクラスやメンバが定められています。`Get-Member`の入力に与えることでクラスやメンバを調べられます。

```powershell:powershell
PS D:\> ps | Where-Object ProcessName -match powershell | Get-Member

   TypeName: System.Diagnostics.Process

Name                       MemberType     Definition
----                       ----------     ----------
...
Kill                       Method         void Kill()
...
Id                         Property       int Id {get;}
...
ProcessName                Property       string ProcessName {get;}
...
```

以上は　`ps | Where-Object ...`　の出力オブジェクトが「`System.Diagnostics.Process`」という**クラス**で「`Kill`」という**メソッド**や「`Id`」「`ProcessName`」という**プロパティ**をメンバとして持つことを表しています。

## 入力をもとにプロセスを終了する
### PowerShellの場合（ForEach-Objectで入力オブジェクトのメソッドを呼び出す）
オブジェクトなのでメソッドを呼び出すことができます。各入力オブジェクトについてメソッドを呼ぶときは、`ForEach-Object` の入力に与えることでできます。たとえば、`ps | Where-Object ...` の出力オブジェクトについてそれぞれ `Kill` というメソッドを呼ぶときは、

```powershell:powershell
PS D:\> ps | Where-Object ProcessName -match powershell | ForEach-Object Kill
```

となります。以上でPowerShellがプロセス終了になります。

### UNIXシェルの場合（awkでテキストを加工する）
以上のことをUNIXのシェルで再現してみます。

```bash:bash
$ ps | grep bash
    8 tty1     00:00:00 bash
```

`kill -9` のパラメータにプロセスIDを与えればよいです。UNIXシェルの `ps` の出力はテキストなのでプロパティを持ちません。そのため`PID`と指定をするのではなく、テキスト行を区切ることで解決します。

`ps` の出力は連続空白を区切りとして4つの列から成り立ちます。そのうちの1列目がプロセスIDです。入力の1列目のみを出力するために`awk`を使います。

```bash:bash
$ ps | grep bash | awk '{print $1}'
8
```

この出力を `kill -9` のパラメータに与えます。パラメータに出力を与えるために `xargs` の入力に与えます。

```bash:bash
$ ps | grep bash | awk '{print $1}' | xargs kill -9
```

以上で `bash` が `kill` されます。

## 補足

### PowerShellについて

『Windows PowerShell ポケットリファレンス』Part 1「PowerShellの基礎」にこう書かれています。

>コマンドレットは従来のコマンドのようにテキストデータを出力するのではなく、.NET Framework の「オブジェクト」を出力します。〔…〕たとえば `Get-Process` というプロセス一覧を取得するコマンドレットを実行すると、.Net Frameworkの `Process` クラスをインスタンス化した `Process` オブジェクトが、存在するプロセス数だけ得られます。`Process` クラスには `ProcessName` プロパティ（プロセス名）や `Id` プロパティ（プロセスID）などが定義され
ているため、得られた `Process` オブジェクトにはそれぞれ別々のプロパティ値が格納されています。得られたオブジェクトは、プロパティ情報を保ったまま次々にパイプライン（記号「`|`」）を通じて後続のコマンドレットの入力とできるので、プロパティの値によって出力にフィルタをかけたり（`Where-Object`コマンドレット）、必要なプロパティのみを選択表示したり（`Select-Object` コマンドレット）、〔…〕といったことができます。

今回の記事は以上の引用を参考に書かれました。

### コマンドレットとエイリアス

UNIXのシェルはコマンドと呼びますが、PowerShellではコマンドレットと呼びます。このことは、PowerShellのコマンドレットは動詞句と名詞句に分かれていることを表します。たとえば、`ps` というコマンドレットは `Get-Process` の別名（エイリアス）で、`kill` も `Stop-Process` の別名です。`Get` あるいは `Stop` が動詞句で `Process` が名詞句です。

UNIXのコマンドは短いという特徴があります。他方、PowerShellは比較的に長いです。とはいえ、PowerShellはTAB補完によりいちいち長く書く必要はほとんどありません。たとえば、`Get-Process` も`get-pro[TAB]` と入力すれば補完されます。

また、別名が用意されているのでUNIX的に短く書くことも多くの場合可能です。たとえば、`Set-Location` というコマンドレットにはUNIX的な `ls` という別名とコマンドプロンプト的な `dir` という別名が用意されています。

また、以下で補足する代表的なコマンドレットにも特別な別名があります。

| コマンドレット    | エイリアス | エイリアス（1文字）|
| -----------------|-----------|-----|
| `Select-Object ` |`select`   | なし|
| `Where-Object  ` |`where`    | `?` |
| `ForEach-Object` |`foreach`  | `%` |

たとえば、`Where-Object ProcessName -match powershell` は `where ProcessName -match powershell` あるいは `? ProcessName -match powershell` と書いても同じ意味になります。

### grep と Where-Object

UNIXシェルの `grep` に対応するのが `Where-Object` です。

```powershell:powershell
PS D:\> ps | Where-Object ProcessName -match powershell

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    656      32    64224      78016       6.06   9764   1 powershell

```

`Where-Object {...}` というスクリプトブロック「`{...}`」による構文の方がより一般的です。たとえば「`Where-Object ProcessName -match powershell`」は「`Where-Object {$_.ProcessName -match powershell}`」と書き換えられます。「`$_.ProcessName`」は入力オブジェクト「`$_`」のプロパティ「`ProcessName`」という意味です。


### awk と Select-Object
UNIXシェルの `awk` に対応するのが `Select-Object` です。

```powershell:powershell
PS D:\> ps | Where-Object ProcessName -match powershell

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    656      32    64224      78016       6.06   9764   1 powershell
```

の出力オブジェクトのうちプロパティ `Id` を選択したい場合は、

```powershell:powershell
PS D:\> ps | Where-Object ProcessName -Match powershell | Select-Object id

   Id
   --
12400

```

というように `Select-Object id` の入力に与えればよいです。

出力したいプロパティ名を列挙することで出力を定めることができます。またワイルドカード「`*`」によって `Select-Object *` と書けばすべてのプロパティが出力されます。

### xargs と ForEach-Object
UNIXシェルの `xargs` に対応するのが `ForEach-Object` です。

先ほどは `Process` クラスに定められているメソッド `Kill` でプロセス終了しましたが、プロセス終了するコマンド `kill` のパラメータにプロセスIDを与えるという方法でもプロセス終了できます。

```powershell:powershell
PS D:\> ps | Where-Object ProcessName -Match powershell | Select-Object id | kill

```

あえて列選択をする必要もありません。以下のコマンドの組み合わせでも可能です。

```powershell:powershell
PS D:\> ps | Where-Object ProcessName -Match powershell | ForEach-Object {kill $_.Id}
```

「`ForEach-Object {kill $_.Id}`」というコマンドの「`$_.Id`」は入力オブジェクト「`$_`」のプロパティ「`Id`」という意味です。「`ForEach-Object { ... }`」は各入力オブジェクトについてブロック内のスクリプトを実行するという意味になります。