powershell から fzf を使用する際の文字化け対策


環境

PS > $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.18362.145
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.18362.145
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

fzf は Scoop で入手済。

Scoop install fzf

そのまま使おうとするとマルチバイト文字が文字化けしてしまいます。

PS > ls -name
hoge_folder
ほげほげフォルダー

PS > ls -name | fzf

  ?????????????
> hoge_folder
  2/2

対策

function psFzf {
    $origin = [System.Console]::OutputEncoding
    $utf8 = [System.Text.Encoding]::GetEncoding("utf-8")
    $OutputEncoding = $utf8
    [System.Console]::OutputEncoding = $utf8
    $out = ($input | fzf.exe)
    [System.Console]::OutputEncoding = $origin
    return $out
}

このような関数でパイプすれば文字化けしません(少々のレイアウト崩れはご愛嬌…)。

PS > ls -name | psFzf

  ほげほげフォルダー
> hoge_folder
  2/2

変数に格納することもできます。

PS > ls -name | psFzf | sv hoge

パイプの下流にも渡せます。

PS > ls -name | psFzf | cd # 選択した先にディレクトリ移動

ポイント

$OutputEncoding に加えて [System.Console]::OutputEncoding も設定する必要があります。
前者を変更する記事ばかりヒットしたので数カ月間ハマりました。

$OutputEncoding はスコープ的に関数内でのみ適用されますが [System.Console]::OutputEncoding はセッション全体に影響するので関数の最後でもとに戻してやる必要があります。

2020/03/14 加筆

上記 psFzf を使っていると、無視できない頻度で異常終了(フリーズしてからターミナルのウィンドウごと消える)が起きるようです。
同様のツールとして有名な peco や gof も同じ要領で文字化けに対処することができ、これらは今のところ問題ないようです。

psGof.ps1
function psGof ([switch]$fuzzy) {
    $origin = [System.Console]::OutputEncoding
    $utf8 = [System.Text.Encoding]::UTF8
    $OutputEncoding = $utf8
    [System.Console]::OutputEncoding = $utf8
    if ($fuzzy) {
        $out = $input | gof.exe -f
    }
    else {
        $out = $input | gof.exe
    }
    [System.Console]::OutputEncoding = $origin
    return $out
}
psPeco.ps1
function psPeco ([switch]$fuzzy) {
    $origin = [System.Console]::OutputEncoding
    $utf8 = [System.Text.Encoding]::UTF8
    $OutputEncoding = $utf8
    [System.Console]::OutputEncoding = $utf8
    if ($fuzzy) {
        $out = $input | peco.exe --initial-filter=Fuzzy
    }
    else {
        $out = $input | peco.exe
    }
    [System.Console]::OutputEncoding = $origin
    return $out
}

謎の挙動

実用上の問題はないのですが、peco は上記ラッパーをかませると図のようにレイアウトが崩れます。全角文字1つにつきカーソルが余計に動いているようです。
[System.Console]::OutputEncoding の部分をコメントアウトすると発生しないので、おそらくこの設定が影響しているようです。
しかし再現性も微妙で、Windows7 からアップグレードした Windows10 では発生し、最初から Windows10 として購入した PC では発生しないという謎……。今後の解明が待たれます。

2020/06/16 加筆

peco のバージョンを上げたらレイアウト崩れは解消されました。本質的な解決ではありませんが業務上は差し支えないので良しとします。