none
PowerShell5.0 の Sort-Object および 演算子 -gt , -lt の評価順序に関して

    質問

  • こんにちは。siino です。

    以下の手順で取得したデータを "FullName"属性で昇順ソートした場合、-gt, -lt の評価と異なる結果が出てしまいました。

    本現象に心当たりがありましたらご教示いただけますでしょうか。

     【再現手順】
    1. 以下コマンドを実施し、Windows\ 配下のフォルダ一覧を $Lists変数に格納
      ※ データが大量にあるので、Name, FullName, Parent 属性程度に絞る
     > $Lists = Get-ChildItem -Path C:\Windows\ -Recurse | Select Name,FullName,Parent

    2. FullName属性を昇順ソートした状態で取得したいので、 Sort-Object でソートをかけなおし、別変数($Sorted)に格納

     > $Sorted = $Lists | sort -Property FullName

    3. $Sorted 内の FullName 属性に入力された文字列を -gt , -lt で比較すると -gt, -lt の評価が逆転している

     > $oldKey = $Sorted[0].FullName

     > foreach ($Key in $Sorted.FullName) { if($Key -lt $oldKey) {write-host "`""$Key"`" -lt `" $OldKey"`"}; $OldKey = $Key}

     出力結果:

     "C:\Windows\assembly\GAC\ADODB" -lt "C:\Windows\assembly\GAC_MSIL\WsatConfig\3.0.0.0__b03f5f7f11d50a3a\WsatConfig.exe"

     "C:\Windows\Speech\Common" -lt "C:\Windows\Speech_OneCore\Engines\TTS\ja-JP\NUSData\M1041Sayaka.voiceAssistant.WVE"

     "C:\Windows\System32\Intel\DPTF" -lt "C:\Windows\System32\Intel_OpenCL_ICD64.dll"

     "C:\Windows\System32\Speech\Common" -lt "C:\Windows\System32\Speech_OneCore\VoiceActivation\ja-JP\VoiceActivation_ja-JP.dat"

     "C:\Windows\SysWOW64\Speech\Common" -lt "C:\Windows\SysWOW64\Speech_OneCore\VoiceActivation"

    【確認したいこと】
    ①. sort で対象属性を昇順ソートしなおす場合に何か他の記述が必要なのでしょうか。

    ②. "\" と "_" であれば、Asciiコード的に若いのは "\" (\:0x5c _:0x5f)であり、 -gt, -lt の評価は正しいと思いますが、

      何故 Sort-Object ではココの部分だけ逆転してしまうのでしょうか。

    ③. -gt, -lt で逆転しない結果を得るにはどのような措置をとるべきでしょうか。

    #本件PowerShellで文字列の2分探索を行おうと考えており、

    #手早く一意な値を大量に取ろうとして発覚しました。。

    以上、どうぞよろしくお願いいたします。

    2018年7月13日 0:33

すべての返信

  • ①. sort で対象属性を昇順ソートしなおす場合に何か他の記述が必要なのでしょうか。

    既定の比較で問題ないならこれだけで問題ないです。

    ②. "\" と "_" であれば、Asciiコード的に若いのは "\" (\:0x5c _:0x5f)であり、 -gt, -lt の評価は正しいと思いますが、何故 Sort-Object ではココの部分だけ逆転してしまうのでしょうか。

    文字列の比較に絶対的な「正しい」はありません。例えば"A"と"a"は、Unicodeのコードポイントで比較するなら"A" < "a"が「正しい」ですが、Case-Insensitiveな比較であれば"A" = "a"が「正しい」ことになります。

    で、Sort-Objectと-lt/-gtが異なるのは、それぞれが使っている比較関数がそうなっているから、以上の答えはできないかと。Sort-Objectが使う比較関数についてのドキュメントは見つけられませんでしたが、どうやら[String]::Compare辺りを使用しているようです。

    ③. -gt, -lt で逆転しない結果を得るにはどのような措置をとるべきでしょうか。

    比較関数を統一するのが解決策となるでしょう。といってもSort-Objectで指定できるのは比較関数ではなくソートキーの導出までっぽいので、Sort-Objectの代わりに[Array]::Sortを使用するとか。ただしこのメソッドは配列自身を変更するものなのに注意。

    [Array]::Sort($lists, [Comparison[Object]]{Param($l,$r) if ($l.FullName -clt $r.FullName) {return -1 } elseif ($ l.FullName -ceq $r.FullName) { return 0 } else { return 1 } })

    // そもそも-gt, -ltはCase-Insensitiveなので、2分探索には相応しくないのではないでしょうか。せめて-cgt, -cltを使うべきでは。

    ちなみに、ArrayクラスにはBinarySearchメソッドも用意されています。

    2018年7月13日 2:18
  • Sort-Objectコマンドのデフォルトの文字列比較順は、ASCIIコード順ではなく、カレントカルチャを用いた比較順になります。日本語環境すなわちカレントカルチャがja-jpの場合だと _、\ の順になります。

    Sort-Objectコマンドで-lt、-gt演算子と同様のロケール非依存(InvariantCulture)なソートを行うには、以下のようにします。

    Sort-Object -Culture 127



    2018年7月13日 2:37
    モデレータ
  • Hongliang さん

    ご回答いただきありがとうございました。

    [Array]::Sort や、BinarySearch メソッドなど、便利なメソッドが沢山あるんですね。
    これらは全く使ったことがなかったので、使い方を勉強してみます。
    #今回は Sort-Object のCulture オプションでソートを行ってみます!

    また、gt, lt 比較で大文字小文字の区別するのをすっかり失念しておりました。
    ご指摘いただいた通り -cgt, -clt での比較をするようにしてみます。

    2018年7月20日 1:17
  • 牟田口大介 さん

    ご回答いただきありがとうございました。

    Sort-Objectは使用環境に引っ張られるのを初めて知りました。

    当時Sort-Object のオプションを見てみたのですが、
    Culture は全く使い方が分からず、スルーしてしまっておりました。
    #検索してもあまり利用方法についての記載もなく・・・。

    以下のカルチャクラスを参照しているので、
    明示的に127の"invariant culture"を利用する。と理解できました。

    Title: CultureInfo Class
    URL: <ttps://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo%28VS.71%29.aspx>

    頂いたCultureオプションを追記したところ正しくスクリプトも動作いたしました。
    ご教示いただきありがとうございます。

    #この場合2バイト文字とかも正しくソートされる(2分探索で探索ミスにならずに調べきれる)ものなのでしょうか・・・?

    2018年7月20日 1:29
  • もうちょっと補足すると、日本語環境では円記号問題があり、\u005C;バックスラッシュと\u00A5;円記号は同一視するように作られています。この影響で\u005C;バックスラッシュは\u005F;アンダーラインより後ろに配置されます。英語など他のカルチャを指定するとこのような動作は行われません。
    2018年7月20日 1:37