PowerShell使っててハマったところメモ

どうも、クソ忙しい年末からまじもんの年末へと移行してクズそのものな生活リズムへと移行できましたね。
なじむ……やはりクズな連休的生活リズムは実によくなじむぞッ!

とりあえず来年になって忘れっちまう前に、今年にPowerShellとかいう半端モンのカスに苦しめられた部分をメモしておいた部分を日記につけておこうと思う。

PowerShell 落とし穴的にハマったところ

エスケープは` (backquote)

まぁこれは、ハマったというか、あーそうなんだって感じのアレです。

スクリプトブロックがレキシカルスコープをサポートしていない

えー、C*deZineだか@*tだかその両方だか忘れましたが、とにかくそこらへんのを参考にして書いた人が多いかと思います。僕もそうです。
PowerShellのキモはなんといってもパイプですが、そのパイプを使う時にLinqでいうところのラムダ式的な感じで、スクリプトブロックを渡しますね。そして、そこの連載では大事な記述が抜け落ちてます。スクリプトブロックは標準状態ではダイナミックスコープで変数とかの解決を行っているよーです。
正直、こういったモノを扱うときフツーのプログラマならレキシカルスコープを持つことを期待すると思うんですけど。なんでこんなクソ仕様なんですかね?

さらに、親スコープの変数へは、参照ではなく値コピーを行っているような、でも%とか?に渡している時は参照しているような、そういう挙動をしてくれてます。お仕事が佳境っててまだ検証してないです。
できればこのまま二度と触りたくないですが。

あ、ちなみに2.0以降ならクロージャを使いたい場合はスクリプトブロックのケツに.GetNewClosure()をつけてやればレキシカルスコープ的な感じになるっぽいです。

配列がオブジェクトとの比較演算子オーバーロードしている

どういうことか。
つまり配列への各種比較演算子(powershellでは-eqとか-ge)は、配列へのフィルタとして機能する。
よって

$li = 'abc','adddadada','fffff','d','fffff','abc','abc'
$li -eq 'abc'

の結果は abc, abc, abcとなる

$li = 0..10
if ($li -eq 0) {'アルヨ'} else {'ネェヨ'}
#=> この場合、0が返るが、0は条件式でfalse的扱いを受けるのでネェヨがでる

if ($($li -eq 0) -ne $null) {'アルヨ'} else {'ネェヨ'}
#=> こうすればアルヨがでる

ちなみに-neはnot equalのこと

そして上記をふまえた場合、配列がnullかどうか調べる場合は

$list -eq $null
# これだと$list | ?{ $_ -eq $null} と同じアレになってしまう

$null -eq $list
# これなら期待通りの動作となる。

とまぁ、この落とし穴を踏み抜く可能性は限定されてそうだけど、僕はバッチリ踏み抜いて検証にクソ時間かかったので、どこかで他の誰かも踏みそうではある。
つうかせっかくパイプ処理実装してあんのに、どうしてこういうゴミみたいな動作を付加しなくちゃあならなかったの? 馬鹿なの?

echoの挙動

Write-Hostとは違う。
Write-Hostは常に標準出力へと流れるが、echoはコンテクストによって、関数の返り値として扱われたりするのでヤバイ

# echoだと……

function echotest() {
    echo "aaa"
    echo "bbb"
    echo "ccc"
}

>>> echotest
aaa
bbb
ccc

>>> $a = echotest
>>> $a
aaa
bbb
ccc


# Write-Hostだと……

function echotest() {
    write-host "aaa"
    write-host "bbb"
    write-host "ccc"
}

>>> echotest
aaa
bbb
ccc

>>> $b = echotest
aaa
bbb
ccc

>>> $b

えー、若干わかりにくいかもしれませんね。
とりあえず、echoは関数内で実行された場合はreturnというか、yield的な感じで扱われます。
対してWrite-Hostは常に標準出力に流れます。

多くの入門記事で「echoはWrite-Hostのalias」と書かれていて、鵜呑みにして僕みたいに不可解な挙動に悩まされる人もいるんじゃないでしょーか。っていうかPowerShellのshow-aliasだっけ? で確認してもechoはWrite-HostのAliasだーって表示されてた覚えがあるんで、なんでこんな挙動の差が出てくるのか僕にはさっぱりです。なんか環境依存の原因だったりするのかもしれませんね?

他のツール(dos時代のとか)を使うと出力とか返り値がヤバイ

これはツールの種類によるのかもしれませんが、僕の場合はbcpとかいうこれまたそれ自体がkissassなツールを使っていて陥りました。
これはfunctionじゃなくてfilterでしか確認していない(functionとfilterの違いは適当にぐぐってください)のだけれども、ツール自体が実行ログとかを出力するようなモンの場合、そのログやなんかが勝手にfilterの返り値となります。多分functionでもなる気がします。

これはちょっとソースが手元にないのであれですが、例えばファイルの名前とか中身でフィルターしていって、さらにbcpで処理して、成功したものを削除するーーなんてのを書いた場合、bcpのログ各行に対してrmが発行されてエラーの山に失禁します。

僕は

Write-Host $(bcp hogehogehohgehogehoge)

って感じで標準出力に逃がしてやることで回避しました。

そして、今気がついたんですけど、もしかして前の項で書いたechoの挙動ってGit Bashのechoが動いてて、それであんな扱いになったとか……?
うーん、よくわかんないですけど、まぁ今時Git入れてない環境とか少ないと思うんでどっちでも同じっすよね。

PowerShell柔軟すぎ

とまぁ、他にも色々ハマった気がするんですけどこんな感じでした。
もちろんどんな言語でも使い始めはそれぞれハマるところとかはあるんで、それをもってクソだと言わすのはあんま良くないよなーと思いつつも、驚き最小の原則から外れている箇所が多すぎてどうも良い印象ないですね。
特にキモである部分の、パイプとスクリプトブロックの動作がどうにも不安定な気がします。備えよう。

あとなんかImportの動作とかもわかりずれえ

あと何気にパワーがあるので、かなり柔軟に色々出来ます。構文も柔軟です。でも標準状態のサポートはかなりショボいです。.NETとの親和性も微妙だし。そしてISEとかなんなのあれ? 意味あんの?
言語としては、ぶっちゃけJavaScriptとかPerlと同じような、つまり悪い意味の柔軟さとか標準状態のショボさとか感じます。まぁでもShellScriptよりかは100倍取っ付きやすいと思います。すいませんPerlは触ったことないのにイメージで書きました。
シェル環境としてもcmdよりかはマシです。同じくそったれのケツの穴であることには変わりないですが。


そんなこんなで、手元に環境なくて検証とか不十分ですけど、これから使い始めようって思ってる方とか、参考になったらうれしいなーって思いますよ。Windows7とかSQLServer2008以降では標準環境らしいですしね。あとWindowsServerもCUI限定のとか出るとか、まじなんですかねー