タイトルは一度いってみたかっただけです、生意気言ってごめんなさい。
他の言語同様、PowerShell にも一次配列があります。こんなやつ。
PowerShell は、型を持っているので Object[]
以外にも T[]
(型の配列) などもあるのですが、他言語から見ると配列の扱いに癖があるように思います。まとまった記事にしたことなかったので、癖(挙動を知らなければ罠に思える)についてまとめます。
目次
- 目次
- TL;DR
- 何がこまるの
- 罠となるポイント
- 暗黙の型変換
- 簡略化された配列宣言
- 要素の連結がオペレータによっては遅い
- 標準出力での配列型の要素が単体な場合の自動的な型変換
- オペレータの配列と単体での挙動の違い
- PowerShell がはやしてるプロパティ
- ジャグ配列内での配列維持に,が必要
- まとめ
- なんでこの記事書いたの
TL;DR
PowerShell の一時配列は、
- 左辺に右辺が合わせられる「暗黙の型変換」
- @() を使うとTの一時配列がObjectに型変換される
- += は要素が多くなると遅くなるので List[T] やArrayListを使ってるなら .Add() を使いましょう
@(1,2,3) -eq 1
などオペレーター(-eq)の配列に対する「フィルタリング」を防止するにはヨーダ記法1 -eq @(1,2,3)
を使いましょう- PowerShell がはやしてるプロパティ (.Count) には注意
- ジャグ配列内での配列維持には単項の
,
オペレーターを使いましょう
何がこまるの
配列と配列の比較をしよう思ったら違った!とかいうのはよくあるんじゃないでしょうか。
配列は特によく扱うデータ型ですが、その挙動が直感とずれることが多いと「この言語なに」となるでしょう。
この記事は認識にあるずれを明確にすることで、PowerShellを思ったように扱うことを目的にしています。
罠となるポイント
経験上、PowerShell で一次配列で困るっていうのはどれか*1に当てはまってきました。*2
「一言いう」とこうですね。
はまりポイント | 一言 | 凶悪度*3 |
---|---|---|
暗黙の型変換 | 仕方ない | 3/10 |
簡略化された配列宣言 | かき捨て以外は型に縛ってやる(やりすぎツライ | 4/10 |
要素の連結がオペレータによっては遅い | 仕様が古いんです。メソッド使って | 1/10 |
標準出力での配列型の要素が単体な場合の自動的な型変換 | 知っててもほぼ回避できないから最悪ねっ! | 10/10 |
オペレータの配列と単体での挙動の違い | 初見で期待する動きじゃない、生まれ変わってこい | 8/10 |
PowerShell がはやしてるプロパティ | もはや生やしてるプロパティ使いたくない | 6/10 |
ジャグ配列内での配列維持に,が必要 | 罠としか言えない | 9/10 |
はじめにいっておきます。PowerShell は型を持っているといいますが、基本 Object
| Object[]
にしたがります。これを覚えておいてください。*4
順番にいきます。
暗黙の型変換
これは一次配列に限りません。が、どうも混同されているケースが多いようです。それぐらい厄介なわけですね。
PowerShell は、動的型付け言語なので実行時に型が決定されます。この暗黙の型変換には原則があるのですが、これを知らないと型で何かしらの操作をしようと期待した時に、期待と異なる型になってあわわわ。とか。
.GetType()
メソッドでの型での判別が予期しない結果になる- function (関数) で型指定で受けようとしても意図した型でないものが渡ってきてエラーになる
暗黙の型変換のルール
これだけ覚えておいてください。
実行時にオペレータの左辺(左オペランド)と右辺(右オペランド)の型が違う場合、「左辺の型に右辺の型が変化」します。
変数の型は、.GetType()
メソッドで調べられるので使っていきましょう。
いくつか例を見てみます。
シンプルな型変換例
まずは簡単な例です。
一番下の$hoge
の型を考えます。
この例では、左辺に System.String 型、右辺に System.Int32 型 の変数をAppend しています。そのため、ルール通り $hoge
は System.String型 となります。
変数$b
が System.Int32 型 (1) から System.String 型 ("1")に暗黙に型変換されたのですね。
暗黙の型変換の失敗例
先ほどの左辺と右辺を逆にしてみましょう。すると暗黙の型変換に失敗します。
先ほどと同様に、左辺の System.Int32 型 に System.String 型 を暗黙に型変換しようとしたのですね。
int -> string は暗黙に型変換可能ですが、string -> int はできないのでエラーとなった訳です。
一次配列の型変換
さて、System.Int32 型を配列に入れたいと思って、@()
で括ったらどんな型になるでしょうか?
配列の中身は System.Int32 型ままですが、配列全体は System.Int32[]
ではなく、System.Object[]
となります。おそらく期待する型は System.Int32[]
だと思うので違和感があるのではないでしょうか。
実際のところ、@()
は、[object[]]()
と同義です。知っておけば、そんなものか。ですが、シラナイと好ましくないと感じるでしょう。
型をゆるふわにやって事故って、型をきっちりしようとしたらとても使いにくい。特に一次配列はなんでも Object[]
にしたがるので扱いにくさが目についてしまいます。
回避策
左辺に合わせて右辺の型は暗黙に変換される。これを覚えておきましょう。インテリセンスでこういうの指摘してほしいですよね。
ScriptAnalyzer での静的な解析わんちゃんですね。
PowerShell Tools for Visual Studio は... ほげ。現状打つ手なしなのでPR投げてください。現在は Microsoft も開発に参加して実装速度が上がってます。
簡略化された配列宣言
次は 配列宣言です。
PowerShell で配列を宣言する方法はいくつかあります。
よくある簡略な方法
一番多く表現される方法は先ほどの、@()
でしょう。
先ほど説明した通り、System.Object[]
になるので注意です。
明示的な宣言
他言語では、C# でいう var
のような型推論を使っていてもインテリセンスがあるので困りません。しかし、PoweShell や PowerShell ISE のインテリセンスは型サポートが貧弱なのでさもすると型を把握できなくなります。さっきの例が System.Object[]
になるのはその例です。
こういうときは、型を明示しておくと安心できるでしょう。(このあと説明する要素が単数の場合を除いては)
単数を一次配列にする
これがはまりどころでもあり、回避策です。
1
は System.Int32 型ですが、@(1)
は System.Object[]型です。そう、簡略な方法でしめした@()
で出力を囲むと配列扱いになります。
これ、この後も触れるのですが結構えげつないですが、効果のあるやり方です。PoweShell の覚えたくないけど便利なやり方 頻出トップ5に入りそうなぐらい。
要素の連結がオペレータによっては遅い
配列に要素を追加するときにどうしていますか?
もしかして、+=
を使ってませんか?要素が増えたときに死にますよ、それ。
回避策
ジェネリクスの List[T] を使ってるなら .Add
メソッドをつかってください。廃棄物のArrayListでも同様です。
詳しくは以前まとめたのでどうぞ。
標準出力での配列型の要素が単体な場合の自動的な型変換
私が思う PowerShell の配列の罠 で断然トップはこれです。えげつなさヤバイ。
この挙動地雷以外のなんて呼べばいいんですかね。一次配列は結果が単数の場合、配列ではなくなってしまいます。*5
回避策
ここで、先ほどの単数を配列にする という @()
が現れます。*6
はい。
オペレータの配列と単体での挙動の違い
ここでいうオペレータは特に-eq
| -ceq
演算子を指します。
このオペレータ、比較判定していると思いますよね?
配列にやってみましょう。
配列 1,2,3,4,5,6,7,8,9,10 は、1とは違うので期待する動作は false が返ってくることですが....?
知らない人にはまさかの 1 が返ってきます。
これは、-eq
オペレータが配列に対してはフィルタオペレータだからです。
そのため、左辺の 1..10 の中に 1 が含まれていたので、該当した 1がとりだされました。
これ、if文の中でやっていると、 bool に対して 1 は暗黙に true に変換されるのでまさかの逆に結果になります。
では、右辺が配列の左辺に含まれないものだった場合は?
結果は空です。しかしこの結果がまた厄介です。
なんとか判定したくても、さて。
細かい解説ははぐれメタルセンセーを。
null に関しては、上記ブログだけでは気づきにくいパターンもあります。例えばこれ、結果に気づけましたか?
回避策
原則ルールを思いだしてください。左辺に暗黙の型変換されるのです。そして配列に対してでなければ、-eq
は比較判定に使えます。
判定したい値を左辺に持ってくるのが回避策になります。
null も左辺に持ってきましょう。
PowerShell がはやしてるプロパティ
PowerShell は、.NET 標準の型に独自のプロパティやメソッドを生やしています。*7
CodeProperty、ScriptProperty、NoteProperty、AliasProperty などとあったら.NET 標準ではなく PowerShell が生やしているプロパティです。
TypeName: System.Int32 Name MemberType Definition ---- ---------- ---------- pstypenames CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorlib, Version=4.0.0.0, Culture=ne... psadapted MemberSet psadapted {CompareTo, Equals, GetHashCode, ToString, GetTypeCode, GetType, ToBoolean, ToChar, ToS... psbase MemberSet psbase {CompareTo, Equals, GetHashCode, ToString, GetTypeCode, GetType, ToBoolean, ToChar, ToSByt... psextended MemberSet psextended {} psobject MemberSet psobject {BaseObject, Members, Properties, Methods, ImmediateBaseObject, TypeNames, get_BaseObjec... CompareTo Method int CompareTo(System.Object value), int CompareTo(int value), int IComparable.CompareTo(System.Ob... Equals Method bool Equals(System.Object obj), bool Equals(int obj), bool IEquatable[int].Equals(int other) GetHashCode Method int GetHashCode() GetType Method type GetType() GetTypeCode Method System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode() ToBoolean Method bool IConvertible.ToBoolean(System.IFormatProvider provider) ToByte Method byte IConvertible.ToByte(System.IFormatProvider provider) ToChar Method char IConvertible.ToChar(System.IFormatProvider provider) ToDateTime Method datetime IConvertible.ToDateTime(System.IFormatProvider provider) ToDecimal Method decimal IConvertible.ToDecimal(System.IFormatProvider provider) ToDouble Method double IConvertible.ToDouble(System.IFormatProvider provider) ToInt16 Method int16 IConvertible.ToInt16(System.IFormatProvider provider) ToInt32 Method int IConvertible.ToInt32(System.IFormatProvider provider) ToInt64 Method long IConvertible.ToInt64(System.IFormatProvider provider) ToSByte Method sbyte IConvertible.ToSByte(System.IFormatProvider provider) ToSingle Method float IConvertible.ToSingle(System.IFormatProvider provider) ToString Method string ToString(), string ToString(string format), string ToString(System.IFormatProvider provide... ToType Method System.Object IConvertible.ToType(type conversionType, System.IFormatProvider provider) ToUInt16 Method uint16 IConvertible.ToUInt16(System.IFormatProvider provider) ToUInt32 Method uint32 IConvertible.ToUInt32(System.IFormatProvider provider) ToUInt64 Method uint64 IConvertible.ToUInt64(System.IFormatProvider provider)
特にGet-Process
などはわかりやすいでしょう。試してみてください。
細かく知りたい人へ
id:aetos382 さんの神まとめを見ればなんとなく理解が深まるでしょう。
罠になりやすい例
PowerShell で、配列の長さを取るときに、良く用いられるのが 謎の.Count
プロパティです。
でもこれ、Length
プロパティの AliasProperty
として PowerShell が配列にのみ設定しているんですね。
そのため、配列じゃない型には存在しません。
ここで、標準出力での配列型の要素が単体な場合の自動的な型変換 を思い出してください。そう、結果を @()
で囲んで配列に強制変換する謎手法を使わないと、.Count が取れない!という状況になったりするんですね。*8
回避策
私あまり @()
好きじゃないので、結果が少ないとわかっているなら Measure-Object
で数えることが多いです。
.Length
プロパティアクセスと比べてコストが高いので嫌なんですが、数が少なければ誤差なので。
あるい型をしっかり配列か確認して、Lengthプロパティを使うといいでしょう。
ジャグ配列内での配列維持に,が必要
ジャグ配列は、その配列要素も配列である配列です。つまり@(@(1,2,3),@(4,5,6))
のようなものです。この例のジャグ配列要素へのインデックスアクセスはイメージ通りにできます。
@(@()) は @() というまさかの罠
しかし、PowerShell は、@(@(<配列>))
は@(<配列>)
とみなします。つまりこうなっちゃいます。
解決策
そこで利用するのが、単項配列演算子の,
です。,
を対象配列の前に置くことでジャグ配列でも配列を維持します。
厄介ですね!
まとめ
どれも知っていれば回避はできます。そういう問題ではない?そうですか。
まぁ気軽に2,3 行書いたり、シェル上でCmdlet を1つ実行するだけのシーンが多いと楽なんでしょうねぇ。*9
なんでこの記事書いたの
罠が多いという声がおおいので書けという天の声が聞こえました。
「そろそろPowerShellの一次元配列についてひとこと言っておくか」という記事がもしかして必要なのではないだろうか。
— 牟田口大介 (@mutaguchi) September 3, 2015