突然ですがクイズです
csvのデータ件数を数えるPowerShellコマンドを実行します。
2行のcsvファイルに対して実行したところ、2
が結果として表示されました。(先頭のAAA,BBB
はcsvヘッダです)
AAA,BBB
1,2
3,4
PS C:\Users\hoge> (import-csv test.csv).count
2
では、同じPowerShellコマンドを次のようなデータ1件のcsvファイルに実行したらどうなるでしょうか
AAA,BBB
1,2
答え
何も表示されません
PS C:\Users\hoge> (import-csv test.csv).count
PS C:\Users\hoge>
いいたいこと
原因の解説
PowerShellのコマンドは結果の個数によって型が変わるものがあります。たとえば次のようになることがあります
結果の個数 | 型 | 例 |
---|---|---|
2個以上 | 配列 | @(1,2,3) |
1個 | 要素単体 | 1 |
0個 | $null | $null |
2個以上の時は配列なので.count
で個数が取得できますが、1個の時は配列ではなく要素単体です。
1個の時は要素のcountプロパティへアクセスし、countプロパティがないと結果が$null
となってしまいます。
データ1件のtest.csv
では(import-csv test.csv).count
が$null
になってしまうのが、
何も表示されなかった理由です。
なお、この挙動になるかどうかはコマンド次第です。import-csv
やget-aduser
なんかでは起きます。
ls
は要素がcount
プロパティを持っていてかつ1
を返すので、
結果が1個でも.count
で1
を返してくれます
ちなみに$null.count
はエラーではなく0
になります。なので結果が0個なら.count
で0
が表示されます
回避策
世間の記事では@()
で包んで配列にするのが多いようです
@(import-csv test.csv).count
これだと確かに結果が1個の時は配列にできますが、
結果が2個以上の時は「配列の配列」になってしまい.count
が1
を返すと思うかもしれません。
しかし、PowerShellは「配列の配列」は配列に変換するので、問題ありません。(むしろそのPowerShellの仕様が問題な気も)
@(@(1,2,3))
=> @(1,2,3)
別の方法として個数を数えるmeasure
を使うこともできます。
配列だろうとオブジェクトだろうと$null
だろうとうまく数えてくれます
(import-csv test.csv | measure).count
| measure
は配列の個数が多いと重くなるそうですが、そんなに多くない時は問題ありません。
さらなる闇
test.csvの中身をヘッダのみのデータ0件にしてみます
AAA,BBB
データ0件のcsvでは確かに読み込み結果は$null
のようです...
PS C:\Users\hoge> (import-csv test.csv) -eq $null
True
PS C:\Users\hoge> $csv = (import-csv test.csv)
PS C:\Users\hoge> $csv -eq $null
True
PS C:\Users\hoge> $csv.gettype()
null 値の式ではメソッドを呼び出せません。
発生場所 行:1 文字:1
+ $csv.gettype()
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) []、RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
しかし@()
で包むと挙動が$null
と違います。
$null
のようで$null
では無い何かのようです・・・
PS C:\Users\hoge> @($csv).count
0
PS C:\Users\hoge> @($null).count
1
PS C:\Users\hoge> $a = $null
PS C:\Users\hoge> @($a).count
1
気が向けば次回でこのあたりの記事も書きます
参考
PowerShellのCountプロパティについてあれやこれや
そろそろ PowerShell の一次配列の罠と回避について一言いっておくか
補足
以上は PowerShell 5.1.18362.145 で動かしています。バージョンによって挙動が変わる部分もあるようなのでご注意ください