В официальном блоге 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.