try-catch制御構文

PowerShell Coreは、シェルとして開発されているが、オブジェクト指向のプログラミング言語としての側面を備えており、制御構文としてtry-catchを使用することができる。

try-catchは、C#やJavaといったプログラミング言語を使ったことのある方ならおなじみの構文だ。シェルではシグナルハンドリングを駆使すれば似たようなことができないこともないが、明確にサポートしているものはほとんどない。この制御構文はシェル的には珍しい機能と言える。

PowerShell Coreのtry-catch制御構文は次のようになっている。

try-catch制御構文の基本的な使い方

try
{
    処理
}
catch [エラー種類],[エラー種類]
{
    エラー処理
}
catch
{
    エラー処理
}
finally
{
    最終処理
}

try-catch制御構文は、try {}で行われている処理において何らかのエラーが発生した場合に、処理をcatch {}の方に振るといった仕組みになっている。一旦処理がcatch {}に移ると、もう処理がtry {}に戻ることはない。なお、この処理ではエラーという言葉ではなく例外という言葉が使われることも多い。

PowerShell Coreのtry-catch制御構文では、catchの段階でエラーの種類を指定できるようになっており、エラーの種類ごとに後処理を分けるといったことができるようになっている。

finallyはswitchにおけるdefaultと似たようなものだが、必ず処理が通るという点が異なる。try {}の中の処理で最後に確実に処理する必要があるものなどをfinally {}に書いておくことになる。

try-catch制御構文はオブジェクト指向プログラミングの経験がないとちょっと動きが想像できないかもしれない。しかし、PowerShell Coreで記述されたソースコードではtry-catch制御構文がさまざまな場面で使われているので、どうやって動作するものかは知っておく必要がある。今回はこの制御構文を取り上げる。

try-catch制御構文を使ってみる

try-catch制御構文の動作を知るために、もっともシンプルな書き方で動作を試してみよう。

try-catchの使い方を知るためのコード

try
{
    "tryに入りました"
}
catch
{
    "catchに入りました"
}
finally
{
    "finallyに入りました"
}

これを実行すると、次のようにtry→finallyの順で実行されることを確認できる。

catchはエラーが発生しない限り実行されない

PS /Users/daichi> try {
>> "tryに入りました"
>> } catch {
>> "catchに入りました"
>> } finally {
>> "finallyに入りました"
>> }
tryに入りました
finallyに入りました
PS /Users/daichi>

ご覧のとおり、エラーが発生していないので、catchは実行されていない。一方、finallyはエラーが発生していなくても処理されていることがわかる。

次に、実際にエラーを発生させてみよう。

次のコードではNo-Such-Commandという存在しないコマンドレットを実行している。この処理ではSystem.Management.Automation.CommandNotFoundExceptionというエラーが発生するので、これを捕捉するようにcatch [System.Management.Automation.CommandNotFoundException]と記述している。

エラーを発生させて捕捉するコード

try
{
    "tryに入りました"
    No-Such-Command
    "No-Such-Commandを実行しました"
}
catch [System.Management.Automation.CommandNotFoundException]
{
    "catch [CommandNotFoundException]に入りました"
}
catch
{
    "catchに入りました"
}
finally
{
    "finallyに入りました"
}

実行すると次のようになる。

該当するcatchが実行され、なにも指定していないcatchは実行されないことがわかる

PS /Users/daichi> try {
>> "tryに入りました"
>> No-Such-Command
>> "No-Such-Commandを実行しました"
>> } catch [System.Management.Automation.CommandNotFoundException] {
>> "catch [CommandNotFoundException]に入りました"
>> } catch {
>> "catchに入りました"
>> } finally {
>> "finallyに入りました"
>> }
tryに入りました
catch [CommandNotFoundException]に入りました
finallyに入りました
PS /Users/daichi>

No-Such-Commandの実行を試みた段階でcatch [System.Management.Automation.CommandNotFoundException]へ処理が飛んでいることがわかる。さらに、そのあとにfinallyが実行されていることも確認できる。エラーが発生すれば該当するcatch []が実行され、捕捉対象を指定していないcatchはスルーされ、finallyは最後に実行されることがわかる。

今度はcatchの捕捉対象をcatch [System.IO.IOException]のように変更して、このcatchではエラーが捕捉できないように変更してみる。

catchの指定でエラーが捕捉されないように変更

try
{
    "tryに入りました"
    No-Such-Command
    "No-Such-Commandを実行しました"
}
catch [System.IO.IOException]
{
    "catch [CommandNotFoundException]に入りました"
}
catch
{
    "catchに入りました"
}
finally
{
    "finallyに入りました"
}

実行すると次のようになる。

何も指定していないcatchが実行され、その後にfinallyが実行されている

PS /Users/daichi> try {
>> "tryに入りました"
>> No-Such-Command
>> "No-Such-Commandを実行しました"
>> } catch [System.IO.IOException] {
>> "catch [CommandNotFoundException]に入りました"
>> } catch {
>> "catchに入りました"
>> } finally {
>> "finallyに入りました"
>> }
tryに入りました
catchに入りました
finallyに入りました
PS /Users/daichi>

catch [System.IO.IOException]の方は処理されずに、対象例外を指定していないcatch {}が実行されていることがわかる。つまり、try-catch制御構文は次のようなルールで処理が遷移することがわかる。

  1. try {}を実行する
  2. エラーが発生した場合、catch []に一致すれば一致したcatchに飛ぶ。一致するcatch []があった場合には指定のないcatch {}には飛ばない
  3. エラーが発生した場合、catch []に一致するものがなければcatch {}に飛ぶ
  4. エラーが発生した場合も発生していない場合も、最後にfinallyに飛ぶ


C言語などでは関数を実行した後にその都度エラー処理を書くが、PowerShell Coreの場合はC#やJavaのように、エラーが発生した場合の処理はcatch {}の方にまとめて書くといった使い方をする。

try-catch制御構文を使わないで同じようなコードを書くことはできる。コマンドレットやアプリケーションを実行するごとに結果を調べて処理を行うようにすればよい。

その場合はif制御構文がたくさん出てくることになるのだが、try-catch制御構文を使うとif制御構文の数を減らすことができる。ただし、やや煩雑になるのには注意が必要だ。どちらがよいかは好みの問題のような気がするが、どのみちtry-catch制御構文はPowerShell Coreスクリプトで多用されるので読めるようにはしておこう。

参考資料