コマンドレットのパラメータを列挙してみる。」に基づいた記述が含まれているおそれがあります。2017の24日目です。一般的なプログラミング言語では、文(statement)と式(expression)の違いは、値を返すのが式で、返さないのが文、という説明がされることが多いと思います。PowerShellではこの説明は成り立たたず、文が値を返したりしてるように見えて良く分かりません。3.0のものですが)やAST(ShowPSAstモジュールが便利!)を眺めながら考えてみたので軽くまとめようと思います。文(statement)と式(expression)の定義PowerShellでは言語要素として、文(statement)と式(expression)が明確に定義されています。言語要素の何が文であって、何が式であるかという定義は仕様できちんと決まっていて、ある言語要素が、状況によって文になったり式になったりと変化する、ということはありません。パイプライン、代入、ifやforやfunctionなどは文です。変数、数値/文字リテラル、オブジェクトのメンバ呼び出し、スクリプトブロック、単項または二項算術演算子で構成される式、カンマ演算子で構成される配列などは式です。仕様書での定義と、実際に構築されるASTに齟齬があることはあります。assignment-expressionと書かれています。厳密には、言語仕様書のgrammer節によれば、assignment-expressionはexpressionではなくpipelineであるということになっています(お前は何を言っているんだ)。いずれにせよパイプラインは文であるので、AST通り、代入は文であるという解釈で良いと思います。expressionとはっきり書いてあるんだから代入は式だろ!という意見を否定するものではないです。代入は文であると考えたほうが他の文法と整合性を取りやすいので、そういう立場をとりました。しばたさんが9日目に書かれた記事で取り上げられているように、配列に関しても同様の齟齬があります。いずれにせよ「1,2,3」のような配列は、式と考えて良いと思います。文と式の構造PowerShellには文と式が存在することは分かりました。それを考察するために、具体的にいくつかの文と式の構造を取り上げて見ていきます。ProcessName」みたいな|で繋ぐやつのことでしょ、と思われがちですが、言語仕様上は以下のようなものはすべてパイプラインです。�E算術式をパイプラインでコマンドと繋げたもの要はパイプラインというのは、一般的なプログラミング言語で、;で終わる一文に相当するものと考えればだいたい間違いないと思います。PowerShellだと文末の;は必須ではなく、改行でもOKです。パイプラインは以下のような構造を取ります。[]は省略可能を意味します。代入文すなわち、代入文(後述)を除くパイプラインは1個以上のパイプライン要素から構成されており、複数のパイプライン要素が存在する場合はパイプ演算子|で連結されます。パイプライン要素には式とコマンド(Get-Processとか)が存在します。式は1つ目のパイプライン要素にのみ許可されます。以上を踏まえると、�@、�Dはコマンドのみで構成されるパイプライン、�A、�Bは単独の式のみで構成されるパイプライン、�Eは1つの式とコマンドで構成されるパイプライン、�Cは代入文であることが分かります。代入文はパイプラインなので、文です。代入文は以下のような構造を取ります。文ただし、左辺の式は、変数やプロパティなど、代入が可能な式である必要があります。よって以下のような記述が可能です。右辺には文であれば何でも書けるのですが、実際に代入が行われるのは、パイプライン、if文、for文、switch文といった、パイプラインに値を出力する文と代入文に限られます。ちなみに�Dのようにパイプラインと代入文以外の文を右辺に指定できるようになったのは、PowerShell2.0からです。ところで上記�@や�Aは右辺が式です。代入文の右辺は文じゃなかったの?と思われると思いますが、パイプラインの節で述べた通り、単独の式もパイプラインであり、パイプラインは文なので、三段論法でいくと式は文として扱われることになります。$bの右辺はPipelineAst/CommandExpressionAst/VariableExpressionAstではなく、いきなりCommandExpressionAst/VariableExpressionAstとなっているので、この説明はASTの実装とはかみ合わないかもしれません。AssignmentStatementAst.Rightは確かにStatementAstを取るのですが、CommandExpressionAstはStatementAstから派生しているクラスなので、式の代入は問題なく行えます。代入文は上記�Eのようなことができることから分かる通り、値を返す文ですが、パイプラインには値を出力しません。値は返すがパイプライン出力がないものは、インクリメント演算子で構成される式($a++等)も同様です。if文パイプラインと代入文以外の文は色々あるわけですが、代表的なものとしてif文を取り上げます。シンプルなif文はこういう構造です。{文1文2....}おそらく多くの人が誤解しているのではないかと思いますが、条件節に書くのは式ではなくパイプラインです。パイプラインを実行した結果、出力値がtrue、またはboolに型変換してtrueになる場合に、ブロック内の複数の文が実行されます。よって以下のような記述が可能です。�@と�Aは普通の書き方ですが、実際には、1つの式のみ有するパイプラインを実行し、出力される値が判定されています。条件節はパイプラインなので、当然�Bのような書き方もできるわけです。代入文もパイプラインであるので、�Cの書き方もできてしまい、注意を要します。(if($true){}){}というような書き方はできません。※といっても実はこう書くと文法上はvalidであり、条件節内は「ifコマンド、パラメータ値1($true)、パラメータ値2(スクリプトブロック)」という解釈になってしまいます。{}という書き方はできません。(パーサーもエラーを出す)丸括弧式さて、普通のプログラミング言語だと、()はグループ化や演算子の優先順を変更するのに用いられるものの、別に文法そのものに影響を与えるものではないと思います。多くの場合、ASTでも()の情報はそぎ落とされます。ところがPowerShellでの丸括弧()は文法的な意味を有しており、ASTにもParenExpressionAstとして存在する、立派な式です。丸括弧式の構造は以下の通りです。(パイプライン)これは要するに、「パイプラインに()を付けると式になる」、ということです。()内のパイプラインで出力された値が返される式となります。具体的にどういうところで使うのかを示します。�Cコマンドのパラメータにコマンド実行結果を指定する�@の使い方は普通です。()はパイプラインを生成するので、「(1+3)」は「1つの式のみ有するパイプラインを実行し、パイプラインに値を出力し、その値を返す」という見た目より複雑な処理になります。※少なくともAST上はそうなりますが、実際は何らかの最適化処理が入ってる可能性はあります。代入を重ねる場合には�Aのような書き方は必要ないのですが、代入した結果をパイプラインの出力としたい場合は()を付ける必要があります。この場合、$aに1が代入され、コンソールにも1が出力されます。�Bで挙げている、式を許容するがパイプラインは許容しない言語要素というのは実はあまりないです。前述した通りif文の条件節は式じゃなくて、パイプラインを取るといった案配です。配列や連想配列の要素を取得するインデックス演算子[]は、式のみ許容されます。コマンドなどのパイプラインの出力値を指定したい場合は()が必須となるわけです。ちなみに、()内にはパイプライン以外の文(if文等)は指定できません。複数のパイプラインも指定できず、あくまで1つだけです。※任意の文あるいは複数の文を式としたい場合には、部分式演算子$()または@()を用います。両者とも内部の文がパイプラインに出力した値を返す、「部分式」となります。両者とも複数値が出力されると配列になりますが、@()は出力値が1つでも要素数1の配列を返す点が異なります。まとめ?PowerShellの文と式は厳密に定義されています。文は複数の文と式で構成されるし、式は複数の文と式で構成されています。文や式の構成要素が取る文や式の種類についても、各々、きちんと定義されています。PowerShellにおいて「値を返すか返さないか」、「パイプラインに出力されるかされないか」、「式であるか文であるか」という概念はすべて独立しています。そのため、PowerShellの文とは何である、式とは何である、ということを一言で説明することは難しいんじゃないかと思います。本記事でこれまで述べてきたとおり、「パイプラインは文で、要素として式やコマンドを取りますよ」とか、「ifは文で、条件節にはパイプラインを取りますよ」みたいな、各論でしか表現できないのではないかなぁと、私はそういう結論に至りました。ここまで書いてちゃぶ台をひっくり返すようなことを言いますが、ある言語要素が文であるか式であるか、ここまで仕様書を読んだりASTを追ったりして把握するのは、まあ楽しくなくはないですが、知らなくても別に大丈夫だと思われます。別に、ifの条件節には条件式を書くのだと理解していても不都合は特にないかと。{}」とかの、余分な()を取り除くのには文法の知識が役立ちます。それ程問題にはならないかもしれません。埋め込み型のプレゼンテーションです。勉強会(4/9)には多数の方にお越しいただき、ありがとうございました。PowerShell勉強会は今後も年2回くらいのペースで続けて行きたいと思っていますので、どうぞよろしくお願い致します。新機能と関連OSSのご紹介」のスライドを公開します。前半は以前のものとだいたい同じですが、正式版対応版にアップデートしています。今回は去年から今年にかけて、PowerShell関連ソフトウェアとしてOSS化したものを、まとめて紹介しました。以下は今回紹介したもののリストです。Chefまたデモで用いたサンプルファイルも公開します。このzipにも同梱してますが、PSScriptAnalyzerのカスタムルールはこんな感じで作ります。作り方は、ASTを受け取って、中身をチェックして、ルールに該当するならDiagnosticRecordを返すというのが基本になります。変数に半角英数字以外の文字種が含まれていると警告するカスタムルール。半角英数字以外の文字種が使われています。Test-UsingVarsWithNonAsciiCharacterついでにPesterのサンプルコードも。2つのパラメータを足し算する関数、Invoke-Additionに対するテストコードの例となります。埋め込み型のプレゼンテーションです。昨日3/12(土)開催の、わんくま同盟大阪勉強会#66にお越しいただいた方、どうもありがとうございました。私の行った「PowerShellコマンドとエラー処理」というセッションの資料を公開します。PowerShellのエラー体系についてまとまった資料がないように思いますので、参考にしていただければ幸いです。|Tweet注:最後に書きましたが、この記事の内容には未解決問題が残っています。はじめに前回は、パイプラインを下流の関数内で打ち切る方法について説明しました。今回は逆に、下流でパイプラインが打ち切られた場合、上流の関数ではどう対応すべきなのか、特にリソース解放処理を焦点にして考察してみます。パイプラインが打ち切られるケース前回も説明しましたが、パイプライン内の関数またはコマンドレットで何らかの例外が発生すると、パイプライン処理がその時点で打ち切られます。パイプライン処理が打ち切られると、後続のprocessブロックのみならず、パイプラインに含まれるすべてのendブロックの実行もスキップされてしまいます。ところでコマンドレットや関数では何かエラーが発生した場合、コマンドレットではWriteErrorメソッド、関数ではWrite-Errorコマンドレットを使ってエラーストリームに、生の例外オブジェクトをラップしたErrorRecordを出力し、呼び出し側のErrorActionの設定にエラー時の処理を委ねるのが基本です。呼び出し時のErrorAction指定がContinue、SilentlyContinue、Ignoreの場合は、パイプラインが中断することはありませんが、StopやInquireで中断した場合は例外(ActionPreferenceStopException)がthrowされ、パイプラインは打ち切られます。-First(StopUpstreamCommandsException)が実行された場合も、同様にパイプラインは打ち切られます。下流のコマンドでパイプラインが打ち切られるケースというのは色々あり得るので、endブロックというのは実行が確約されたものでは全くない、ということに留意しておく必要があります。確実に実行したい後処理というのは色々あると思いますが、特に確保したリソースの解放というのは確実に実行してもらわないと困ります。前述のような背景があるので、何らかのリソースを用いる関数を書く際に、beginブロックでリソースを確保し、processブロックでリソースを利用し、endブロックでリソースを解放する、ということは基本はNGということになります。この点、コマンドレットクラスであれば、IDisposableインターフェースを実装しておけば、コマンドレット終了時にDisposeメソッドをPowerShellが呼んでくれるので、その中にリソース解放処理を記述しておけばOKです。関数ではこの手法が使えないので、代替案を考える必要があります。鷲ノ巣であえとす氏が考案した、beginブロックに後処理用の関数を定義しておく方法では、同じ関数内のprocessブロックで発生したエラーをcatchしてリソース解放処理を走らせることは可能です。残念ながら下流で発生した例外をcatchすることはできず、その場合は後処理がスキップされてしまいます。リソース解放処理を含めた関数あえとす氏の方法を若干アレンジして、下流で例外が発生した場合も確実にリソース解放の後処理を走らせる方法を考えてみました。以下にコードを示します。※クラス構文を使ってますが、これは単に、Disposeできるオブジェクトのサンプルだと思ってください。}}processブロック内のtryブロックでパイプライン下流に値を出力したとき、下流で例外が出てもcatchブロックが実行されないので、代わりに、必ず実行されるfinallyブロックから後処理を呼び出すようにしてみました。パイプライン下流で例外が発生しなかった場合には、processブロック内ではリソース解放処理はしたくないので、例外発生の有無を$processingという変数の値を見ることで確認しています。パイプライン出力したあと下流で例外が出ていれば、$processingの値は$trueのままになるので判断可能です。パイプラインが中断することなく、最後まで実行される場合は、endブロック内でリソース解放処理を行います。$falseの間に例外が発生する可能性のある処理を記述した上で、catchブロックで再throwすれば、併せて対応できるのではないかと思います。2このように、下流でパイプライン打ち切りがあるとendブロックは実行されませんが、リソース解放処理は、きちんとprocessブロック内のfinallyブロックから呼び出されています。end)もちろんパイプラインが最後まで正常に実行された場合も、ちゃんと最後にendブロックでリソース解放が行えるようになっています。endにまたがって機能するusingってどういう構文になるんでしょうね。(14:22追記)と、ここまで書いておいて、この方法では下流で発生した例外には対処できますが、上流で例外が発生した場合はリソース解放が実行されないという問題に気付きました...。すればいいんだ...。|TweetPowerShellのパイプライン処理まず、PowerShellのパイプライン処理について軽くおさらいします。�@begin→�Abegin→�Bbegin→(�@process→�Aprocess→�Bprocess→)×n→�@end→�Aend→�Bendの順に実行されます。(processブロックで「1入力に対し1出力する」場合以外は必ずしもこうならないですが)さて、�Aか�Bのprocessブロック実行中に、何らかの条件を満たした時、パイプラインのprocessの後続処理を打ち切りたい場合はどうすれば良いでしょうか。breakを使った駄目な例ネットでよく見かける以下のようなコード、すなわち「パイプラインはbreakで処理を打ち切ることができる」というのは実は正しくないのです。}}このコードはv2までではそもそも正しく動作しませんが、v3以降では条件によっては正しく動作しているように見えるのが、誤解の元なのかと思います。(というか私も誤解してました。