LoginSignup
7
5

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-07-04

はじめに

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
$ ps
  PID TTY          TIME CMD
    8 tty1     00:00:00 bash
   24 tty1     00:00:00 ps

また grep というコマンドがあります。入力したテキストのうち指定したパターンに当てはまる行のみを出力するコマンドです。

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

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

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

UNIXはテキストを入出力する

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

bash
$ ps | grep PID
  PID TTY          TIME CMD

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

PowerShellのパイプラインの場合(psとWhere-Object)

PowerShellにも ps というコマンドがあります。

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
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 の出力オブジェクトのうちプロパティ ProcessNamepowershell というパターンに当てはまるオブジェクトのみ出力されました。

PowerShellはオブジェクトを入出力する

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

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

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
PS D:\> ps | Where-Object ProcessName -match powershell | ForEach-Object Kill

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

UNIXシェルの場合(awkでテキストを加工する)

以上のことをUNIXのシェルで再現してみます。

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

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

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

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

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

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

以上で bashkill されます。

補足

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 の別名(エイリアス)で、killStop-Process の別名です。Get あるいは Stop が動詞句で Process が名詞句です。

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

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

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

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

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

grep と Where-Object

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

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
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
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
PS D:\> ps | Where-Object ProcessName -Match powershell | Select-Object id | kill

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

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

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

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5