PowerShellの、パス指定まわりのややこしい話 Get-ChildItem (dir/ls) 編

毎度毎度、PowerShellの話題ばっかりでごめんなさい。

PowerShell では、ワイルドカードが拡張されています。例えば、

foreach($i in 1..9){ni $i.ToString('test_00\.txt') -type file}

とやって、
test_01.txt ~ test_09.txt
までの連番ファイルを作ったとします。その上で、

ls 'test_0[1-3].txt'

などとやると、ちゃんと、

test_01.txt
test_02.txt
test_03.txt

の三つが引っ掛かります。

このように、[]で文字を囲って、
[abc]は、a,b,c のどれか1文字に当てはまる。
[0-9]は、0~9までの1文字に当てはまる。
のような指定が可能になったんですね。
「おー、ちょっと便利だ」と思えなくもないのですが、これが、多大なる悲劇を生んでしまいます。というのも、従来のDOS/Windowsのワイルドカードというのは、
*
?
の2種類。どちらもディレクトリやファイルの名前として使うことが禁止されている文字です。しかし、[]は、ファイル名として使うことが可能なんですね。するとどうか。

foreach($i in 1..9){ni $i.ToString('test[00]\.txt') -type file}

test[01].txt ~ test[09].txt
までの連番ファイルは、ちゃんと作ることができました。
では、さっきみたいに、ls で 01~03 までリストアップしようとすると、あれ、どうすれば…ちょっとややこしそうだというのが想像がつくのではないでしょうか。

結論から言うと、

ls 'test[[]0[1-3]].txt'
# または
ls 'test``[0[1-3]].txt'

となります。

上の方は単純に、[abc]とか[1-3]のような感じで、ワイルドカード文字セットとして [ のみを指定すると
[[]
という、見た目ややこしい感じになる、というだけのことです。

下は…よくは分かりませんけど、lsの -path のワイルドカード文字セットの仕様として、
``
が文字エスケープに該当しているらしいです?
見ればわかるとおり、 ] はエスケープの必要は無いみたいです。

ところで、ファイル名にワイルドカードを使う気がなければ、 -literalpath というパラメータを用いることができます。

ls -literal 'test[01].txt'

しかし、ls -literalpath は、一切のワイルドカードが使えなくなってしまうのが欠点ですね。すなわち、

ls -literal 'test[0?].*'

としても、もちろん、?も*もそのままファイルやディレクトリの名前とみなそうとし、文字が不正である旨のエラーが出力されてしまいます。正直、[]ワイルドカードのみ、抑制するスイッチがあっても良かったと思うんですけどねぇ…。

ちなみに、FileSystem プロバイダ(他にも Registry とか Alias とか Environment とかが存在する)依存で、

ls *.* -filter 'test[0?].*'

とやると、思い通りの動作をするみたいですけど、ここら辺の動作、私はまだあまり分かっていません。すみません。

以上、Get-ChildItem 編は終わりです。実は、これはまだ、多大なる悲劇の序の口です。だって、ちゃんと意識すれば、意図通りの動作はさせられますし、ls の場合は、パイプの起点とすることが多い、オブジェクトを吐きだすためのコマンドレットなので、

ls *.*|?{$_.Name -match 'test\[0[1-3]\]\.txt'}

のようにパイプを使えば、多少動作は重くなりますが、正規表現だって使えますから。





以下、より突っ込んで行きます。


ls -path のエスケープが
``
であるということで、またまた別のややこしい事態が発生します。それは、
` というエスケープ用文字もまた、ファイル名として使える文字であること、です。

'a`a.txt ' は

ls 'a`a.txt'
ls 'a````a*.txt'

でマッチします。

'a``a.txt' はというと、

ls 'a``a.txt'
ls 'a````````a*.txt'

でマッチします。どうやら、
``
がエスケープ文字として扱われるのは、ワイルドカード文字らしきものを含んだ場合のみ、みたいです。逆に言えば、拡張子だけワイルドマッチさせたい場合も、ついうっかり、

ls 'a`a.*'

とやってしまうと、何も引っ掛からなくなってしまう、ということです。

また、 ] はエスケープの必要は無い、と書きましたが、ワイルドカードの文字セットに使いたい場合には、 []abc] のように文字セットの先頭に書くか、 [abc``]] のようにエスケープする必要があります。


PowerShell標準のワイルドカード演算( -like 演算子) のエスケープは、ls と違って、
` 1つだけです。つまり、以下の2つの最終出力は概ね等価。

ls 'test``[0[1-3]].txt'
ls '*.*'|?{$_.Name -like 'test`[0[1-3]].txt'}


-literalpath については、レジストリなどは、項目名として * ? なども受け付ける仕様になっているため、ほとんどのリソースに等価にアクセスできるようにしよう、という設計上、 * ? ともワイルドカード解釈しない、というのは有意なことではあります。


エイリアスはデフォルトで用意されているものを使用していますが、一応、リストアップしておきます。

ni : New-Item
ls : Get-ChildItem
?  : Where-Object


[PR]

インターネット広告の「トランスメディア」提供スキンアイコン # by mikamikanamiyuki | 2008-08-14 16:24 | プログラミング

PowerShellのカレントディレクトリ

PowerShellのカレントディレクトリは、 Get-Location コマンドレットで取得できます。

ただし、 これは見た目上のカレントディレクトリのようで、.NET Framework としてのカレントディレクトリ(本当のカレントディレクトリ)は、 [IO.Directory]::SetCurrentDirectory() を用いて、別途、設定しないとダメみたいです。

if( (Get-Location).Provider.Name -eq 'FileSystem' ){
  [IO.Directory]::SetCurrentDirectory((Get-Location).ProviderPath)
}

追記 :
[IO.Directory]::GetCurrentDirectory()
[IO.Directory]::SetCurrentDirectory()
の代わりに、
[environment]::CurrentDirectory
プロパティを用いても、取得と設定ができるみたいですね。


スクリプトなどでは、ファイルアクセス関連の .NET クラスライブラリを使う前に、こういう感じの文を挿入すべし、ですね(この書き方で、ローカルディレクトリと、Windows/SAMBAネットワークディレクトリに対応)。
ちなみに、else 文を書けば、ファイルシステム以外のドライブ・プロバイダにいる時に例外を飛ばすとかできます。

この仕様、えらく不親切ではあるものの、これはおそらく、PowerShell が、現在のロケーションとして、ファイルシステム以外にも、色々とサポートしていることに起因しているんじゃないかなー、と推測。
でもまあ、不親切ですよね?
[PR]

インターネット広告の「トランスメディア」提供スキンアイコン # by mikamikanamiyuki | 2008-08-10 07:02 | プログラミング

PowerShellのループカウント

PowerShell (v1.0で検証) では、for 文を用いたループカウントより、foreach 文を用いたループカウントの方が、処理速度が上っぽいです。検証用の一行スクリプトを例示すると、


#for を用いたループカウント

[long]$sum=0;$t_start=(Get-Date);for([int]$i=1;$i -le 100000;++$i){$sum+=$i;};((Get-Date)-$t_start).TotalSeconds


上記よりも、


#foreachを用いたループカウント

[long]$sum=0;$t_start=(Get-Date);foreach($i in 1..100000){$sum+=$i;};((Get-Date)-$t_start).TotalSeconds


とした方が時間がかかりません。

しかも、カウンタ変数を、シェルやスクリプトのどこかでそれ以前に、[int]以外の型、例えば [double] 型などにしていて、それをそのまま使ってしまうと、for 文ではそのまま [double] として扱われ続けますが、foreach 文だと、ちゃんと[int]型として参照するので、不意のパフォーマンスダウンも避けられます。

ちなみに、foreach 文で使われた参照変数は、その後に何かを代入すると、もともと宣言されていた型に戻ります。

例えば、


[double]$a=0.0
foreach($a in 1..10){}

$a.GetType()
#ここでは Int32 型([int]型と同じ)と出る

$a = [int]1 #念入りに強制[int]型キャストで代入してみる
$a.GetType()
#ここでは Double 型と出る

[PR]

インターネット広告の「トランスメディア」提供スキンアイコン # by mikamikanamiyuki | 2008-08-08 19:48 | プログラミング