はじめに
PowerShellはWindowsの伝統的なシェルであるコマンドプロンプトに代わる次世代シェルです。
PowerShellはUNIXシェルと同様にパイプラインがあります。コマンドプロンプトにもパイプはありましたが、あまり多くのコマンドの組み合わせには耐えられません。
一方でUNIXシェルとは違うところがあります。コマンドが入出力するのがテキストではなくオブジェクトであるという点です。
PowerShellの入門書を読むと多くの場合はじめにこの特徴について書いてあり、オブジェクトを入出力することによりパイプがより強力になっていることが分かります。
とりわけ、パイプと組み合わせる以下のコマンドが分かりやすいです。
UNIX | PowerShell |
---|---|
awk |
Select-Object |
grep |
Where-Object |
xargs |
ForEach-Object |
UNIXとPowerShellのパイプラインを比較する
UNIXのパイプラインの例(psとgrep)
UNIXのシェルはパイプラインによってコマンドを組み合わせることができます。たとえば、実行中のプロセス一覧を出力する ps
というコマンドがあります。
$ ps
PID TTY TIME CMD
8 tty1 00:00:00 bash
24 tty1 00:00:00 ps
また grep
というコマンドがあります。入力したテキストのうち指定したパターンに当てはまる行のみを出力するコマンドです。
ps
の出力を grep bash
の入力にパイプ |
で与えると、
$ ps | grep bash
8 tty1 00:00:00 bash
となります。ps
の出力行のうち bash
というパターンに当てはまる行が出力されています。
UNIXはテキストを入出力する
UNIXのシェルで入出力されるのはテキストです。たとえば、ps
の出力を grep PID
の入力にパイプ |
で与えると、
$ ps | grep PID
PID TTY TIME CMD
となります。ps
の出力の見出し行(PID
というパターンに当てはまった)のみ出力されました。つまり、ps
の出力の見出し行と一覧行にはフォーマット上の区別はない、ということです。
PowerShellのパイプラインの場合(psとWhere-Object)
PowerShellにも ps
というコマンドがあります。
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
の出力を与えると次のよう出力されます。
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
の入力に与えることでクラスやメンバを調べられます。
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
というメソッドを呼ぶときは、
PS D:\> ps | Where-Object ProcessName -match powershell | ForEach-Object Kill
となります。以上でPowerShellがプロセス終了になります。
UNIXシェルの場合(awkでテキストを加工する)
以上のことをUNIXのシェルで再現してみます。
$ ps | grep bash
8 tty1 00:00:00 bash
kill -9
のパラメータにプロセスIDを与えればよいです。UNIXシェルの ps
の出力はテキストなのでプロパティを持ちません。そのためPID
と指定をするのではなく、テキスト行を区切ることで解決します。
ps
の出力は連続空白を区切りとして4つの列から成り立ちます。そのうちの1列目がプロセスIDです。入力の1列目のみを出力するためにawk
を使います。
$ ps | grep bash | awk '{print $1}'
8
この出力を kill -9
のパラメータに与えます。パラメータに出力を与えるために xargs
の入力に与えます。
$ 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
です。
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
です。
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
を選択したい場合は、
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を与えるという方法でもプロセス終了できます。
PS D:\> ps | Where-Object ProcessName -Match powershell | Select-Object id | kill
あえて列選択をする必要もありません。以下のコマンドの組み合わせでも可能です。
PS D:\> ps | Where-Object ProcessName -Match powershell | ForEach-Object {kill $_.Id}
「ForEach-Object {kill $_.Id}
」というコマンドの「$_.Id
」は入力オブジェクト「$_
」のプロパティ「Id
」という意味です。「ForEach-Object { ... }
」は各入力オブジェクトについてブロック内のスクリプトを実行するという意味になります。