Pull to refresh

Выход Node.JS версии 0.6.3 и две ошибки, найденные при работе с кодировками дочерних процессов в консоли Windows XP

Node.JS *
Tutorial
В официальном блоге Node.JS вчера (25 ноября) объявлено о выходе новой версии Node, под номером 0.6.3. Изменения не очень значительны: исправлен десяток ошибок да недочётов.

Я счёл это небезынтересным подарком ко дню рождения (по приятному совпадению, 25 ноября мне исполнилось 33 года). Однако в тот же день, задавшись вопросом «Как принять вывод Windows-команды, вызванной из node.js?», я начал в Windows XP серию экспериментов, конечным итогом которой стало обнаружение сразу двух ошибок Node при работе с кодировками под Windows.

Во-первых, при вызове команды методом require('child_process').exec(…) Node ожидает от неё вывода в кодировке UTF-8, тогда как в русифицированной системе Windows команды (например, dir) выводят текст в кодировке CP866.

Во-вторых, если дочерний консольный процесс изменит кодировку консоли, то будет воздействовать и на кодировку консоли родительского процесса Node (в частности, на вывод методом console.log) — получается, что консоль у них одна и та же, или что-то в этом же дýхе.

А теперь немного подробностей.

С первым из двух найденных мною багов нетрудно столкнуться, если запустить самый простой скрипт вызова дочернего процесса с записью его вывода в буквальном виде в файл:
var fs = require('fs'); // file system

require('child_process').exec('dir', function(err, outstr){
   fs.createWriteStream('testfile.txt', {
      flags: 'w',
      encoding: 'binary'
   }).write(outstr);
});

Вместо русских букв в файле будет чепуха.

Как sdevalex справедливо предположил, для этой проблемы существует обходной путь: достаточно использовать Windows-команду «chcp» для изменения кодировки вызываемого процесса. Скрипт, составленный с учётом этого, выводит в текстовый файл желаемый вид вывода команды:
var forker = require('child_process');
var fs = require('fs'); // file system

forker.exec('chcp 65001 | dir', function(err, outstr){
   fs.createWriteStream('testfile.txt', {
      flags: 'w',
      encoding: 'binary'
   }).write(outstr);
});

Однако же на этом обходном пути вы можете наткнуться и на вторую ошибку, если захотите вывести результаты не только в файл, но и в консоль, для чего достаточно составить вот какой скрипт:
var clog = console.log;
clog('\nRunning under Node.js version ' + process.versions.node + ' on ' +
   process.arch + '-type processor, ' + process.platform + ' platform.');

var forker = require('child_process');
var fs = require('fs'); // file system

forker.exec('chcp 65001 | dir', function(err, outstr){
   fs.createWriteStream('testfile.txt', {
      flags: 'w',
      encoding: 'binary'
   }).write(outstr);
   clog('\n' + outstr);
});

Можете наткнуться. А можете, как ни странно, и не наткнуться. Зависит это от того, используются ли у вас в консоли растровые шрифты или векторные (в роли которых в Windows XP выступают шрифты Lucida Console), то есть от настройки в центре второй вкладки привычного диалогового окна свойств консоли:

[окно свойств консоли]

Насколько я помню, в Windows XP по умолчанию в консоли используются растровые шрифты (поправьте меня, если я ошибаюсь). А значит, если вы не меняли у себя эту настройку, то вышеприведённый скрипт в файл («testfile.txt») будет выводить желаемый текст, а в консоль выведет нечто малопривлекательное:

[скриншот консоли]

А всё это потому, что в растровой консоли команда «chcp» меняет только кодировку текста, выводимого командами; растровые шрифты не могут к ней подстроиться, так что даже вывод самóй команды «chcp» в консоли выглядит неприглядно:

[скриншот chcp]

Если же консоль у вас настроена на отображение текста векторными шрифтами (Lucida Console), то вы и не заметите этой проблемы, потому что у вас вывод скрипта будет выглядеть корректно в любой кодировке, какую бы вам ни вздумалось заранее задать в консоли командою «chcp»:

[скриншот векторной консоли]

На этом этапе волосы должны подняться дыбом на голове и мерно шевелиться. Потому что понятно, что перед нами необычайно коварная проблема, позволяющая разработчику (если он пользуется векторной консолью) буквально десятком строк кода сочинить такой скрипт, который у него у самогó будет прекрасно работать, а у массы некоторых других пользователей (в растровой консоли) станет работать преотвратительно.

Но что же это за проблема?

Быть может, Node не справляется с выводом в консоль Windows, потому что в JavaScript строки юникодовые, а в консоли Windows они в кодировке CP866? А вот и нет, дело не в этом — что нетрудно доказать простым тестовым выводом в консоль:

[скриншот теста]

Быть может, Node переключается на мусор, когда выводимые символы выходят за пределы кодировки CP866? Тоже нет, и достаточно вывести строку «\u2248\u0422\u0435\u0441\u0442», чтобы вполне убедиться в этом. Сам символ «\u2248» заменится вопросительным знаком, но остаток строки не пострадает.

Оказывается, что справедлива другая догадка: этот мусор в растровой консоли имеет ту же природу, что и мусор, выводимый командою «chcp 65001» вместо сообщения об изменении кодовой страницы. Более того: именно ею-то он и вызван. Мы подавали эту команду в дочернем процессе и были намерены переменить кодировку текста, выводимого командою «dir» — однако команда «chcp» подействовала и на родительскую консоль!

Чтобы вполне продемонстрировать это, хватает несложного теста:

[скриншот очередного теста]

Как нетрудно видеть, «chcp 65001» из дочернего процесса воздействует на окно консоли родительского процесса (воздействует до тех пор, пока не будет подана команда «chcp 866» и не возвратит кодовую страницу CP866, используемую по умолчанию).

Понимание этой новой ошибки позволяет нам обнаружить более совершенный путь обхода ранее найденной ошибки. Вызвав «chcp 65001» перед командою «dir», нам поневоле придётся вызвать «chcp 866» ещё раз, чтобы вернуть консоль в исходное состояние перед выводом текста, выданного командою «dir»:

var clog = console.log;
clog('\nRunning under Node.js version ' + process.versions.node + ' on ' +
   process.arch + '-type processor, ' + process.platform + ' platform.');

var forker = require('child_process');
var fs = require('fs'); // file system

forker.exec('chcp 65001 | dir', function(err, outstr){
   fs.createWriteStream('testfile.txt', {
      flags: 'w',
      encoding: 'binary'
   }).write(outstr);
   forker.exec('chcp 866', function(){
      clog('\n' + outstr);
   });
});

Этот скрипт ужé способен невозбранно вывести безупречный текст не только в тестовый файл, но также и в консоль:

[скриншот очередного теста]

У этого обходного пути, при всей его безупречности, есть непреодолимый архитектурный недостаток: два вызова .exec(), ясное дело, асинхронны, так что между ними консоль на некоторое время остаётся в ненормальном режиме.

Обе найденные ошибки были поведаны разработчикам Node через GitHub: donnerjack13589 вчера создал issue 2190, а я сегодня создал issue 2196.
Tags:
Hubs:
Total votes 11: ↑7 and ↓4 +3
Views 3.2K
Comments Comments 10