しばたテックブログ

PowerShellを中心に気分で書いている技術ブログです。

Get-ChildItemの動作に関する問題についての指摘事項

本エントリはブログに書くべきものなのか迷いましたが、文章量が多くなったためここに記します。
また、出題者を責める意図は無いためリンクは張りませんのでご了承ください。

出題された問題

先日Twitter上でPowerShellに関する以下の問題を見かけました。

問題

Windows Powershellにおいて、以下のコマンドを実行した。
実行結果はどうなる?

PS > (Get-ChildItem).Length

回答欄

  1. オブジェクトが無いのでエラーになる
  2. ディレクトリ配下のファイルの総容量が表示される
  3. ディレクトリ配下のファイルの数が表示される
  4. コマンドが存在しないためエラーになる

出題者の意図としては3. ディレクトリ配下のファイルの数が表示されるを選ばせたかったのでしょうが、残念ながらこの選択肢は全て正しくありません。
(前提条件次第では回答欄の内容にマッチする場合もあるのですが、本エントリでは回答欄として不適切の意味で"正しくない"と表現しています)

以下、何が正しくないのかについて順に指摘していきます。

PowerShellにおけるロケーション

まず、DocsにあるGet-ChildItemのヘルプを確認してみると、

Gets the items and child items in one or more specified locations.

とあり、このコマンドレットは「特定の ロケーション にある要素および子要素を取得する」ものです。

blog.shibata.tech

でも触れたとおり、PowerShellにおいて ロケーション はファイルシステム以外のデータ構造も対象とするより抽象度の高い概念です。

このためGet-ChildItemGet-DirectoryItemの様な名前でなくGet-ChildItemが選ばれているといえます。
(とはいえ、ファイルシステムを扱うのが主であるため、現実としては利便性のためにdirlsといったエイリアスが設定されていますが...)

先述の問題の場合、例えばロケーションをレジストリにすると

> cd HKCU:\Software\
> (Get-ChildItem).Length

で取得できるのは「レジストリの値とサブキーの総数」になります。

この問題を正しく出題するためにはロケーションをドライブレベルで確定する必要があります。

PowerShellのバージョン間での挙動の違い

PowerShellにおけるLengthプロパティはバージョン間で挙動が違います。

PowerShell 2.0まで

LengthプロパティはSystem.Arrayのプロパティであり、配列でないスカラ値には使用できません。

例えば

# PowerShell 2.0ではスカラ値にLengthは使えない
> (123).Length
> 

といった指定は何も返しませんし、Strict Modeのレベルによってはエラーとなります。

> Set-StrictMode -Version 2.0
> (123).Length
. : このオブジェクトにプロパティ 'Length' が見つかりません。このプロパティが存在することを確認してください。
発生場所 行:1 文字:7
+ (123). <<<< Length
    + CategoryInfo          : InvalidOperation: (.:OperatorToken) []、RuntimeException
    + FullyQualifiedErrorId : PropertyNotFoundStrict

PowerShell 3.0以降

配列でないスカラ値に対してLengthプロパティが拡張され、

# スカラ値のLengthは1
> (123).Length 
> 1

# $nullのLengthは0
> $null.Length
> 0

といった値を返す様になっています。

最初の問題に戻ると、出題時にバージョンを指定していないので挙動が不定となり回答を確定することが出来ません。
ロケーションの明示もないため、(Get-ChildItem).Lengthが返しうる結果としては、

  • エラー
  • $null
  • 0
  • 1以上の数

のいずれかになります。

敢えて選択肢から答えを選ぶとするなら

  • 1 オブジェクトが無いのでエラーになる
  • 3 ディレクトリ配下のファイルの数が表示される

のいずれか、が近い感じでしょうか。

System.IO.FileInfo.Length プロパティ

【2018/06/11追記】

一点指摘漏れがありました。
(ついさっきまで完全に失念していました...)

例として適当なディレクトリで単一のファイルを作成するケースを考えます。

mkdir sample
cd .\sample\
# 適当なファイル(BOM付きUTF-16)を作成
"Hello World!" | Out-File -FilePath .\sample.txt

ここで(Get-ChildItem).Lengthを実行すると、

> (Get-ChildItem).Length
30

sample.txtのファイルサイズを返します。

これは対象フォルダ内にファイルが一つしかない場合はGet-ChildItemの戻り値はSystem.IO.FileInfo型となり、この型にあるLengthプロパティが使われるためです。

この場合2. ディレクトリ配下のファイルの総容量が表示されるが正答になります。

ちなみに、

mkdir sample2
cd .\sample2\
# 適当なサブディレクトリを作成
mkdir subdir

とサブディレクトリ1つだけの場合はGet-ChildItemの戻り値はSystem.IO.DirectoryInfo型となり、この型にはLengthプロパティはありませんので、(Get-ChildItem).Lengthの結果は

  • エラー
  • $null
  • 1

のいずれかになります。

【追記ここまで】

どう出題すればよかったか?

この問題で不足していたのは環境定義です。

PowerShellの環境はその気になればいくらでもカスタマイズできるので抜け道を全て埋めるのは難しいのですが、一般的な余興レベルとしては

問題

Windows 10においてWindows PowerShellを起動し、直ちに以下のコマンドを実行した。
実行結果はどうなる?

PS > (Get-ChildItem).Length

回答欄

  1. オブジェクトが無いのでエラーになる
  2. ディレクトリ配下のフォルダ、ファイルの総容量が表示される
  3. ディレクトリ配下のフォルダ、ファイルの数が表示される
  4. コマンドが存在しないためエラーになる

とし、

Windows 10

  • PowerShell 5.0~5.1 であることが確定している

Windows PowerShellを起動し、直ちに以下のコマンドを実行した。

  • 環境をカスタマイズしない限りはユーザープロファイルのフォルダ、またはC:\WINDOWS\system32(管理者として実行した場合)がカレントロケーションとなる

の条件を暗示するくらいで良いでしょう。

補足

ちなみに、PowerShellの言語モードや、対象となるロケーションのアクセス権などによっても(Get-ChildItem).Lengthの結果が変わってしまいますが、さすがにそれは穿ち過ぎなので指摘の対象から外しています。