Powershellで再描画時に画面のちらつきを抑える方法

投稿者: Anonymous

Powershellにて下記のコードのようにclsを使ってコンソールの文字列を書き換えると、おそらくまず画面をクリアして文字列を描画することが原因で、描画中の文字列がちらつきます。

$text = "なかきよのとおのねぶりのみなめざめなみのりぶねのおとのよきかな"
0..$text.Length | %{
    cls
    $text.Substring($_)
    sleep -Milliseconds 100
}

Powershellのコンソールでダブルバッファリングなどの仕組みを使ってちらつきを抑える方法はあるのでしょうか?


追記

上記の質問では画面がちらつく例として徐々に文字を消していくサンプルコードをご提示しましたが、達成したいことはコンソールゲームのように1つのコンソールの中で複数行の文字列を加除置換することです。
ansiconConEmuなどの外部アプリケーションに頼らない方法があればご教示ください。
外部への配布も視野に入れているため、可能であればレジストリも変更せずに済む方法が望ましいです。
後出しで申し訳ありません。

文字列を置換するサンプルコード

$lines = @("いろはにほへと",
           "ちりぬるをわか",
           "よたれそつねな",
           "らむうゐのおく",
           "やまけふこえて",
           "あさきゆめみし",
           "ゑひもせすん ")
$a = 0..6
0..8 | %{
    cls
    $sb = New-Object System.Text.StringBuilder
    $i = $_ - 1
    $a | %{
        $y = 6 - $_
        $a | %{
            $x = $_
            $s = $lines[$x][$y]
            if($x -eq $i) {
                $s = "・"
            } elseif($x -gt $i) {
                $s = "■"
            }
            $sb.Insert(0, $s) > $null
        }
        $sb.Insert(0, "`n") > $null
    }
    $sb.ToString()
    sleep -Milliseconds 500
}

いただいたご回答について

~~いただいたご回答を試したのですが、解決できておりません。~~
→解決しました。

キャリッジリターン`rを入れる方法について、clsを使わずに既存の文字列を削除していく方法が読み取れませんでした。

$text = "しんぶんし"
Write-Host -NoNewline $text
0..$text.Length | %{
    Write-Host -NoNewline $text.Substring($_), " `r"
    sleep -Milliseconds 100
}
# clsがないので文字が追加されていきます "しんぶんししんぶんし  んぶんし  ぶんし  んし  し    "

ANSIエスケープシーケンスを使って文字列を操作できることは確認しましたが、出力済みの文字列を操作する方法が分かりません。
※レジストリは修正済みです(未修正でもエスケープシーケンスは使えました)
※Powershellでは動作しますが、手元のPowershell ISEではANSIエスケープシーケンスが動作しませんでした
Console Virtual Terminal Sequencesの他のシーケンスをいくつか試しましたが動作しませんでした(チートシートの色変更も動くものと動かないものがあるので、そういうものかもしれませんが)

"SNS" + [char]0x1B + "[2DO"  #SOS
$text = "トマト"
$esc = [char]0x1B
$text + $esc + "[4Dエ" #トエト
#出力済みの文字列をカーソルで消そうとしてもうまく行きません
Write-Host -NoNewline $text
0..$text.Length | %{
    Write-Host -NoNewline $esc+"[2D "
    sleep -Milliseconds 100
}
#for文では左記のように出力されてしまいます "トマト2D 2D 2D 2D"

解決

ANSI VT100 エスケープシーケンスを使えば、同等のことがエスケープシーケンスを使って出来るのでは?

文字列が1行に収まるならば使うエスケープシーケンスは、例えば3つくらい。
文字列を表示後にカーソルの行桁位置を表示文字列の先頭に移動して、時間毎に1文字づつ消していけば良いでしょう。折り返したりして複数行になる場合は、もっとプログラミングが必要ですが。
Console Virtual Terminal Sequences

  • ESC [ <n> A CUU Cursor Up Cursor up by <n>
  • ESC [ <n> G CHA Cursor Horizontal Absolute Cursor moves to <n>th position horizontally in the current line
  • ESC [ <n> P DCH Delete Character Delete <n> characters at the current cursor position, shifting in space characters from the right edge of the screen.

Unix系ならば元からエスケープシーケンスをサポートしているでしょう。

Windows10 1607以後ならば、以下の記事の方法で有効に出来るはずです。
Colored text output in PowerShell console using ANSI / VT100 codes
Windows console with ANSI colors handling
SetConsoleMode function
WindowsコマンドプロンプトでのANSIエスケープシーケンスの使用
ANSIエスケープシーケンス チートシート

それ以前、あるいは上記対処(設定変更)をしたくない場合は以下記事のようにansiconとかコンソールエミュレータを使うとか。
PowerShell で ANSI escape sequences を表示する
ansicon / adoxa/ansicon
Maximus5/ConEmu
cbucher/ConsoleZ


追記
@metropolis さんコメントの方法も、今使える制御コードを使った方法で以下の繰り返しでしょう。
pythonのpipでダウンロードの進行状況を1行の中で更新しているのと同様でしょう。

  • 改行せずに文字列を表示し、先頭1文字を削って保存
  • 続けて空白を1文字表示し、カーソルを行の先頭に戻す

これも表示内容が1行以内なら冒頭の方法同様に使えると思います。

2行以上になるなら、エスケープシーケンスを有効にして、先に表示開始位置を退避して、文字列表示後に位置を復元するとか、あるいはclsで必ず画面左上隅から表示するなら、初回だけclsを使い、後は画面を消去せずに左上隅に移動するエスケープシーケンスを使うとかすれば、短くした文字列の最後に1文字空白を表示することで表示が短くなっていくでしょう。


追記

紹介記事回答の後ろの方に、PowerShellは自分自身でVTサポートをアクティブにしているとの記述がありました。Windows10では適用されているようです。8.1とか7ではどうなるかは書いてありませんが、試してみるとか。

PowerShell ISEでサポートされていないのは、単にスケジュールの問題なのか、互換性のために敢えて残しているのか、あたりが考えられます。

Re (c):
PowerShell automatically activates VT (virtual terminal) support for itself when it starts (in recent releases of Windows 10 this applies to both Windows PowerShell and PowerShell Core).

PowerShellは、起動時に自動的にVT(仮想端末)サポートをアクティブにします(Windows 10の最近のリリースでは、これはWindows PowerShellとPowerShell Coreの両方に適用されます)。

Therefore, if you relay an external program’s output via PowerShell, VT sequences are recognized; using Out-Host is the simplest way to do that (Write-Host would work too):

したがって、PowerShellを介して外部プログラムの出力をリレーすると、VTシーケンスが認識されます。 Out-Hostを使用するのが最も簡単な方法です(Write-Hostも機能します)。

.t.exe | Out-Host

Note: Use Out-Host only if you mean to print to the console; if, by contrast, you want to capture the external program’s output, use just $capturedOutput = .test.exe

注:コンソールに印刷する場合のみ、Out-Hostを使用してください。 対照的に、外部プログラムの出力をキャプチャする場合は、$capturedOutput = . test.exe を使用します。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *