1 (изменено: Rumata, 2020-11-24 19:37:13)

Тема: CMD/WSF/JS: поддержка ANSI последовательностей в консоли

Я уже писал похожую вещь на PowerShell: CMD/PS1: поддержка ANSI последовательностей в консоли.

Недавно обнаружил, что консоль WIN10 поддерживает ANSI-коды в условно ограниченном режиме: встроенные команды type и echo поддерживают в достаточной мере (управление положением курсора, цвета), powershell поддерживает тоже.

Дальше идет вольный перевод первой публикации на зарубежном форуме https://www.dostips.com/forum/viewtopic … amp;t=9857. Кстати, там недавно сообщили, что команда set /p тоже поддерживает их.

ECHO


for /f %a in ('echo prompt $e^| cmd') do @set "ESC=%a"
echo %ESC%[107m    %ESC%[0m & echo %ESC%[104m    %ESC%[0m & echo %ESC%[101m    %ESC%[0m

TYPE


for /f %a in ('echo prompt $e^| cmd') do @set "ESC=%a"
echo %ESC%[107m    %ESC%[0m >> example.txt
echo %ESC%[104m    %ESC%[0m >> example.txt
echo %ESC%[101m    %ESC%[0m >> example.txt
type example.txt

POWERSHELL


$ESC = [char]27
"$ESC[107m    $ESC[0m`n$ESC[104m    $ESC[0m`n$ESC[101m    $ESC[0m"

Другие команды и утилиты по умолчанию не поддерживают, но этим можно управлять. После включения/выключения начинает работать для последующих сеансов консоли:

ВКЛЮЧИТЬ


reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1

ВЫКЛЮЧИТЬ


reg delete HKCU\Console /v VirtualTerminalLevel

После включения вывод ANSI последовательностей начинает интерпретироваться.

В юниксах управляющие последовательности могут быть дополнительно закодированы следующим образом:


"\e["   <options> <command>
"\033[" <options> <command>
"\x1b[" <options> <command>
"^[["   <options> <command> # в консоли винды надо учетверять до "^^^^[["

Чтобы поддерживать все возможные варианты ANSI-кодов и быть минимально зависимым от реестровых настроек, написал небольшой скриптик -- на самом деле там больше описания чем кода. Если VirtualTerminalLevel включен в реестре, последовательности выводятся и интерпретируются напрямую в терминал, иначе эмулируется выводом через echo


<?xml :
: version="1.0" encoding="utf-8" ?>
<!-- :
@echo off

if "%~1" == "" timeout /t 0 1>nul 2>&1 && (
	cscript //nologo "%~f0?.wsf" /?
	goto :EOF
)

reg query "HKCU\Console" /v VirtualTerminalLevel 2>nul | find "0x1" >nul && (
	cscript //nologo "%~f0?.wsf" %*
	goto :EOF
)

for /f "tokens=* delims=" %%s in ( '
	cscript //nologo "%~f0?.wsf" %*
' ) do echo:%%s

goto :EOF
: -->

<package>
<job id="ansi">
<?job error="false" debug="false" ?>

<script language="javascript"><![CDATA[

var NAME    = 'ANSI';
var VERSION = '0.0.1';

]]></script>

<runtime>
<description><![CDATA[
Parse the specified text from the input file or pipe and output it
accordingly the ANSI codes provided within the text.

Escaping

Interpret the following escaped characters:
  \a        Bell
  \b        Backspace
  \e        Escape character
  \f        Form feed
  \n        New line
  \r        Carriage return
  \t        Horizontal tabulation
  \v        Vertical tabulation
  \\        Backslash
  \0nnn     The character by its ASCII code (octal)
  \xHH      The character by its ASCII code (hexadecimal)

ANSI sequences

  <ESC> [ <list> <code>

  <ESC>     Escape character in the form "\e", "\033", "\x1B", "^["
  <list>    The list of numeric codes
  <code>    The sequence code

Moves the cursor n (default 1) cells in the given direction. If the cursor
is already at the edge of the screen, this has no effect.
  \e[nA     Cursor Up
  \e[nB     Cursor Down
  \e[nC     Cursor Forward
  \e[nD     Cursor Back

Moves cursor to beginning of the line n (default 1).
  \e[nE     Cursor Next Line
  \e[nF     Cursor Previous Line

Cursor position
  \e[nG     Moves the cursor to column n.
  \e[n;mH   Moves the cursor to row n, column m.
  \e[n;mf   The same as above.

Erasing
  \e[nJ     Clears part of the screen. If n is 0 (or missing), clear from
            cursor to end of screen. If n is 1, clear from cursor to
            beginning of the screen. If n is 2, clear entire screen.
  \e[nK     Erases part of the line. If n is zero (or missing), clear from
            cursor to the end of the line. If n is one, clear from cursor
            to beginning of the line. If n is two, clear entire line.
            Cursor position does not change.

Colorizing
  \e[n1[;n2;...]m, where n's are as follows:

  0         All attributes off
  1         Increase intensity
  2         Faint (decreased intensity)
  3         Italic (not widely supported)
  4         Underline
  5         Slow blink
  6         Rapid blink
  7         Reverse (invert the foreground and background colors)
  8-29      Rarely supported
  30-37     Set foreground color (30+x, where x from the tables below)
  38        Set foreground color (Next arguments are 5;n or 2;r;g;b)
  39        Default foreground text color
  40-47     Set background color (40+x)
  48        Set foreground color (Next arguments are 5;n or 2;r;g;b)
  49        Default background color
  50-74     Rarely supported
  90-97     Set foreground color, high intensity (90+x)
  100-107   Set background color, high intensity (100+x)

ANSI colors (default usage)
  Intensity 0       1       2       3       4       5       6       7
  Normal    Black   Red     Green   Yellow  Blue    Magenta Cyan    White
  Bright    Black   Red     Green   Yellow  Blue    Magenta Cyan    White

References

http://en.wikipedia.org/wiki/ANSI_escape_code
http://misc.flogisoft.com/bash/tip_colors_and_formatting
http://stackoverflow.com/a/24273024/3627676
http://www.robvanderwoude.com/ansi.php#AnsiArt
]]></description>
<example><![CDATA[
Examples:

- Print data from pipe:
  echo \e[107m    \e[0m\n\e[104m    \e[0m\n\e[101m    \e[0m | ansi

- Prepare data in the file and print:
  del example.txt
  echo \e[107m    \e[0m>>example.txt
  echo \e[104m    \e[0m>>example.txt
  echo \e[101m    \e[0m>>example.txt
  ansi < example.txt
]]></example>
<named
	name="no-eol"
	helpstring="Skip explicit end of line (skip explicit CRLF)"
	type="simple"
	required="false"
	/>
<named
	name="no-space"
	helpstring="Skip the last trailing space printed by echo in DOS sessions"
	type="simple"
	required="false"
	/>
<named
	name="safe"
	helpstring="Reset all color attributes to defaults at the end of execution"
	type="simple"
	required="false"
	/>
</runtime>

<!-- <script language="javascript" src="./ansi.parse.js"></script> -->
<script language="javascript"><![CDATA[
function ansi_parse(text, options) {
	options = options || {};

	if ( options.safe ) {
		text += '\\e[0m';
	}

	var chars = {
		'a': String.fromCharCode(7),
		'b': '\b',
		'e': String.fromCharCode(27),
		'f': '\f',
		'n': '\n',
		'r': '\r',
		't': '\t',
		'v': '\v',
		'\\': '\\'
	};

	var re_src = [
		'(?:\\\\(' + '[abefnrtv\\\\]' + '))'	// Escaped chars above
	,       '|'
	,       '(?:\\\\(' + '0[0-7]{1,3}' + '))'	// ASCII code (oct)
	,       '|'
	,       '(?:\\\\(' + 'x[0-9a-fA-F]{1,2}' + '))'	// ASCII code (hex)
	,	'|'
	,	'(' + '\\^\\[' + ')'			// ^[ stands for <ESC>
	].join('');

	if ( options.no_eol && options.no_space ) {
		re_src += '|[ ]\\r?\\n';
	} else if ( options.no_eol ) {
		re_src += '|\\r?\\n';
	} else if ( options.no_space ) {
		re_src += '|[ ](?=\\r?\\n)';
	}

	var re = new RegExp(re_src, 'g');

	return text.replace(re, function($0, $1, $2, $3, $4) {
		return $1 ? chars[$1] :
			$2 ? String.fromCharCode(parseInt($2)) :
			$3 ? String.fromCharCode(parseInt('0' + $3)) :
			$4 ? chars.e : '';
	});
}
]]></script>
<!-- <script language="javascript" src="./ansi.main.js"></script> -->
<script language="javascript"><![CDATA[
var opts = WScript.Arguments.Named;

var text = WScript.StdIn.ReadAll();

var text = ansi_parse(text, {
	no_eol: opts.Exists('no-eol'),
	no_space: opts.Exists('no-space'),
	safe: opts.Exists('safe')
});

WScript.StdOut.Write(text);
]]></script>

</job>
</package>
( 2 * b ) || ! ( 2 * b )

2

Re: CMD/WSF/JS: поддержка ANSI последовательностей в консоли

Интересно.

3 (изменено: Rumata, 2020-11-26 22:52:47)

Re: CMD/WSF/JS: поддержка ANSI последовательностей в консоли

Еще интереснее становится с этим фрагментом:


var sh = WScript.CreateObject("WScript.Shell");
var trick = sh.Exec("powershell -nop -ep bypass -c exit");
while ( trick.Status == 0 ) {
	WScript.Sleep(50);
}

var esc = String.fromCharCode(27);

WScript.StdOut.WriteLine(esc + '[107m    ' + esc + '[0m');
WScript.StdOut.WriteLine(esc + '[104m    ' + esc + '[0m');
WScript.StdOut.WriteLine(esc + '[101m    ' + esc + '[0m');

Небольшая инъекция powershell-кода разрешает ANSI-последовательности в WSF/JS/VBS-скриптах. Вне зависимости от текущих настроек реестра -- можно дополнительно проверить, что HKCU\Console\VirtualTerminalLevel не существует или не равен 1, но код все равно работает. Об этом сказано здесь и здесь.

( 2 * b ) || ! ( 2 * b )