本エントリはブログに書くべきものなのか迷いましたが、文章量が多くなったためここに記します。
また、出題者を責める意図は無いためリンクは張りませんのでご了承ください。
出題された問題
先日Twitter上でPowerShellに関する以下の問題を見かけました。
問題
Windows Powershellにおいて、以下のコマンドを実行した。
実行結果はどうなる?PS > (Get-ChildItem).Length
回答欄
- オブジェクトが無いのでエラーになる
- ディレクトリ配下のファイルの総容量が表示される
- ディレクトリ配下のファイルの数が表示される
- コマンドが存在しないためエラーになる
出題者の意図としては3. ディレクトリ配下のファイルの数が表示される
を選ばせたかったのでしょうが、残念ながらこの選択肢は全て正しくありません。
(前提条件次第では回答欄の内容にマッチする場合もあるのですが、本エントリでは回答欄として不適切の意味で"正しくない"と表現しています)
以下、何が正しくないのかについて順に指摘していきます。
PowerShellにおけるロケーション
まず、DocsにあるGet-ChildItemのヘルプを確認してみると、
Gets the items and child items in one or more specified locations.
とあり、このコマンドレットは「特定の ロケーション にある要素および子要素を取得する」ものです。
でも触れたとおり、PowerShellにおいて ロケーション はファイルシステム以外のデータ構造も対象とするより抽象度の高い概念です。
このためGet-ChildItem
はGet-DirectoryItem
の様な名前でなくGet-ChildItem
が選ばれているといえます。
(とはいえ、ファイルシステムを扱うのが主であるため、現実としては利便性のためにdir
やls
といったエイリアスが設定されていますが...)
先述の問題の場合、例えばロケーションをレジストリにすると
> 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
回答欄
- オブジェクトが無いのでエラーになる
- ディレクトリ配下のフォルダ、ファイルの総容量が表示される
- ディレクトリ配下のフォルダ、ファイルの数が表示される
- コマンドが存在しないためエラーになる
とし、
Windows 10
- PowerShell 5.0~5.1 であることが確定している
Windows PowerShellを起動し、直ちに以下のコマンドを実行した。
- 環境をカスタマイズしない限りはユーザープロファイルのフォルダ、または
C:\WINDOWS\system32
(管理者として実行した場合)がカレントロケーションとなる
の条件を暗示するくらいで良いでしょう。
補足
ちなみに、PowerShellの言語モードや、対象となるロケーションのアクセス権などによっても(Get-ChildItem).Length
の結果が変わってしまいますが、さすがにそれは穿ち過ぎなので指摘の対象から外しています。