はじめに
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 { ... }」は各入力オブジェクトについてブロック内のスクリプトを実行するという意味になります。