特集
» 2007年09月20日 00時00分 公開

Windows PowerShellコマンド&スクリプティング入門:PowerShellスクリプティングの第一歩(後編) (3/5)

[山田祥寛,著]

 ここまでは、PowerShellのコマンドレットと演算子のみを利用したコードを紹介してきたが、より複雑なスクリプトを記述するには、条件分岐やループなど、コードの流れを制御するための「制御構文」も必要である。

 PowerShellでは、条件分岐構文としてif 〜 elseif 〜 else、switch 〜 case命令、そしてループ構文としてfor、foreach、while、do 〜 while命令と、ほかのスクリプト言語でもおなじみの制御構文が一通り提供されている。以下では、これら制御構文の概要と、注意するべきポイントについて解説する。

単純分岐と多岐分岐

 まずは、条件分岐構文である。条件を満たした場合にのみ何らかの処理を行いたい、あるいは、A、Bいずれかの処理を選択的に行いたいというケースで使用するのが、if 〜 elseif 〜 else命令の役割だ。

 例えば以下は、変数$xが1であるかどうかを判定し、判定結果によって異なるメッセージを表示するごく単純なスクリプトである。

$x = 1
if($x -eq 1){
  Write-Output '変数$xは1です。'
}else{
  Write-Output '変数$xは1ではありません。'
}

 このコードを実行すると、確かに「変数$xは1です。」というメッセージが表示されることが確認できる。このように、if命令では与えられた条件式(ここでは「$x -eq 1」*)を判定し、真の場合にはif直後のブロックを、偽の場合にはelseブロックを、それぞれ実行する。ちなみに、ブロック内の命令が1文しかない場合にも、PowerShellではスクリプト・ブロックを表す中カッコ({ 〜 })を省略することはできない。

* 「-eq」はPowerShellで提供される比較演算子の一種である。そのほか、比較演算子については前編でも紹介しているので、参照してほしい。


 またif命令では、elseifブロックを使って多岐分岐を記述できる。

$x = 100
if($x -ge 80){
  Write-Output '変数$xは80以上です。'
}elseif($x -ge 40){
  Write-Output '変数$xは40以上80未満です。'
}else{
  Write-Output '変数$xは40未満です。'
}

 ここでは変数$xの値が、80以上、40以上80未満、40未満いずれであるかによって、処理を分岐している。elseifブロックは必要に応じて複数個記述することもできる。注意していただきたいのは、if 〜 elseif 〜 else命令では複数の条件に合致した場合にも、「最初に合致したブロックのみが実行される」という点である。ここでは、変数$xが100であるので、最初の条件式「$x -ge 80」と、次の条件式「$x -ge 40」のいずれにも合致するが、実際に実行されるのは最初に合致したifブロックだけである。

 このように、if命令を利用すればちょっとした多岐分岐までを記述することができるが、等価比較による多岐分岐ならば、switch 〜 case命令を利用した方がシンプルに記述できる。例えば以下にswitch命令を利用したコードを挙げておく。

$x = 1
switch($x){
  1 {Write-Output '変数$xは1です。'}
  2 {Write-Output '変数$xは2です。'}
  3 {Write-Output '変数$xは3です。'}
  default {Write-Output '変数$xは1、2、3ではありません。'}
}

 このコードを実行すると、確かに「変数$xは1です。」というメッセージが表示されることが確認できる。このように、switch命令では先頭で与えられた式(ここでは「$x」)と、それぞれの照合パターン(ここでは1、2、3)とを比較し、合致したブロックを実行する。ただしここで注意していただきたいのは、(if命令と異なり)switch命令では複数のブロックに合致する場合に、「条件に合致したすべてのブロック」が実行されるという点である。次の例は、複数のブロックがマッチするように先ほどのコードを書き換えたものである。

$x = 1
switch($x){
  1 {Write-Output '変数$xは1です。'}
  1 {Write-Output '変数$xは1です。(2)'}
  3 {Write-Output '変数$xは3です。'}
  default {Write-Output '変数$xは1、2、3ではありません。'}
}

 この例では確かに、

変数$xは1です。
変数$xは1です。(2)

のような結果が返されることが確認できる。もしもif命令同様、最初に合致したブロックのみを実行したければ、ブロックの末尾にbreak命令を記述すればよい。

$x = 1
switch($x){
  1 {
      Write-Output '変数$xは1です。'
      break
    }
  1 {
      Write-Output '変数$xは1です。(2)'
      break
    }
  3 {
      Write-Output '変数$xは3です。'
      break
    }
  default {Write-Output '変数$xは1、2、3ではありません。'}
}

 break命令により、各ブロックの末尾で制御がswitchブロック全体から脱出することになる。複数のブロックがマッチしては困る場合、それぞれの分岐ブロックの末尾にはbreak命令を記述しておこう。すると意図しないブロックが実行されることを防ぐことができる。

[コラム]switch命令のオプション

 本文で紹介した内容は、ほかのスクリプティング言語でもほぼ共通の内容であるが、PowerShellのswitch命令ではこれに加えて、やや面白い機能が提供されている。switch命令の後方にオプションを指定すれば、式評価にワイルドカードや正規表現が利用できるのだ。次の例ではワイルドカードを使用している。

$x = "XYZ"
switch -wildcard ($x){
  "X*"  {Write-Output '変数$xは"X"で始まります。'}
  "X_"  {Write-Output '変数$xは"X"+1文字です。'}
  "XA*" {Write-Output '変数$xは"XA"で始まります。'}
}

変数$xは"X"で始まります。

 「-wildcard」オプションは、式の内容をワイルドカードで評価することを表す。そのほか、値の大文字/小文字を区別しない「-case」オプション、式の内容を正規表現パターンとして評価する「-regex」オプションなども利用できる


ループ構文(1) ――for命令とforeach命令――

 for命令は、指定されたカウンタ変数によってループを制御する、最も基本的なループ構文である。for命令を利用した具体的なコード例を次に挙げる。

$ary=@(100,120,300)
for($i=0; $i -lt 3; $i++){
  $sum += $ary[$i];
}
Write-Output $sum

配列内の数値をすべて合計するためのコード(for命令を利用した場合)

 このコードを実行すると、配列内に含まれる数値(100、120、300)の合計である520が得られる。for命令ではループの回数を、

for(初期化式; 終了条件; 増分式)

の形式で指定する。「初期化式」はループの始まりで評価される式で、ここではカウンタ変数(ループの回数を管理するための変数)である$iを0で初期化している。「増分式」はループの1回ごとに実行される式を、「終了条件」はループを継続するための条件式をそれぞれ表す。つまりこのコードでは、カウンタ変数$iを、ループを1回実行するごとに1ずつインクリメント(増加)していき、これを$iが3(配列サイズ)未満である間だけ繰り返すことになる。

 結果、スクリプト・ブロック({〜})の中で定義された命令が、$iが0〜2で変化する間だけ繰り返され、$ary[0]〜[2]の内容が順番に変数$sumに加算される。

 もっとも、このように配列やコレクションの内容を順番に処理するようなケースでは、foreach命令を利用した方がより直感的にコードを記述できる。以下は先ほどのコードをforeach命令で書き換えたものだ。

$ary=@(100,120,300)
foreach($dat in $ary){
  $sum += $dat;
}
Write-Output $sum

配列内の数値をすべて合計するためのコード(foreach命令を利用した場合)

 foreach命令では、

foreach(一時変数 in 配列、コレクション){

のように指定することで、配列から順に要素を取り出し、読み出す要素がなくなるまでループを繰り返す。取り出された要素は一時変数に格納され、スクリプト・ブロック内で参照できる。

 このように、foreach命令はごく直感的に利用できる制御構文の1つであるが、ここで1つ疑問に思われる方もいるかもしれない。というのも、PowerShellにはコマンドレットとしてよく似た機能を持つForEach-Objectが提供されている。これを利用すれば、foreach命令と同様の処理を行うことが可能である。例えば先ほどの例はForEach-Objectを利用して、次のように書き換えることができる。

@(100,120,300) | ForEach-Object {$sum += $_}
Write-Output $sum

 多くの場合、この2つはプログラマの好みによって使い分けて構わないが、内部的な挙動としては微妙な違いもあるので要注意だ。というのも、foreach命令が入力元となる要素(オブジェクト)をすべて取得してからループ処理を行うのに対して、ForEach-Objectコマンドレットはオブジェクトを1つずつ読み込みながら処理する。このため、前者の方が処理に際してより多くのメモリを必要とすることになる。一方、一括してオブジェクトを取得するため、処理の内容によっては前者の方が高速に動作する可能性がある。要は、消費リソースとパフォーマンスとをてんびんにかけて、両者を使い分ける必要があるということだ。

ループ構文(2) ――while/do 〜 while命令――

 while/do 〜 while命令は、いずれも与えられた条件式が真と評価される間だけループを繰り返す。例えば、先ほどfor/foreach命令で示したコードをwhile/do 〜 while命令で記述すると、それぞれ次のようになる。

※while命令を利用した例
$i=0;
$sum=0;
$ary=@(100,120,300)
while($i -lt 3){  ……変数$iが3未満の場合の間、ループを継続
  $sum += $ary[$i];
  $i++;
}
Write-Output $sum


※do 〜 while命令を利用した例
$i=0;
$sum=0;
$ary=@(100,120,300)
do{
  $sum += $ary[$i];
  $i++;
}while($i -lt 3)  ……変数$iが3未満の場合の間、ループを継続
Write-Output $sum

while/do 〜 while命令を利用した例

 コードを実行すると、どちらも配列要素の合計値である520が得られる。条件式の記述がスクリプト・ブロックの前方/後方いずれにあるかという点を除いては非常によく似た両者であるが、1点だけ大きく異なる点があるので、要注意だ。

 というのも、while命令は条件式をブロックの前方で判定(前置判断)するのに対して、do 〜 while命令は条件式をブロックの後方で判定(後置判断)するのである。つまり、do 〜 while命令では条件の真偽にかかわらず、「最低1回は必ず」ループが実行されるということである。一方、while命令ではループの開始時点で条件式が偽と評価される場合にはループは「1度も実行されない」。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。