Как вы нажимаете длинную строку на /dev/stdin через child_process.spawn() в Node.js?

Я пытаюсь выполнить Inkscape, передавая данные через stdin. Inkscape поддерживает это только через /dev/stdin. В основном, я пытаюсь сделать что-то вроде этого:

echo "<sgv>...</svg>" | inkscape -z -f /dev/stdin -A /dev/stdout

Я не хочу писать SVG на диск.

Я просто использовал stdin.write(), но он не работает (возможно, из-за /dev/stdin):

var cmd = spawn("inkscape", ["-z", "-f", "/dev/stdin", "-A", "/dev/stdout"], {encoding: "buffer", stdio: ["pipe", stdoutPipe, "pipe"]});

cmd.stdin.write(svg);

Это работает, но я должен написать SVG на диск:

var cmd = spawn("inkscape", ["-z", "-f", "/dev/stdin", "-A", "/dev/stdout"], {encoding: "buffer", stdio: [fs.openSync('file.svg', "a"), stdoutPipe, "pipe"]});

Я попытался передать поток в stdio, но я просто продолжаю получать TypeError: Incorrect value for stdio stream: [object Object]

Любые идеи?

Добавление

В примерах используется Inkscape, но мой вопрос применим к любой произвольной программе, используя /dev/stdin.

Кстати, это сработало бы для меня:

var exec = require('child_process').exec;
exec("echo \"<svg>...</svg>\" | inkscape -z -f /dev/stdin -A /dev/stdout | cat", function (error, stdout, stderr) {});

Кроме того, мой SVG слишком длинный, поэтому он выдает ошибку: Error: spawn Unknown system errno 7

Ответы

Ответ 1

Итак, я разобрался с работой. Это кажется немного взломанным, но все работает отлично.

Сначала я сделал одну оболочку оболочки script:

cat | inkscape -z -f /dev/stdin -A /dev/stdout | cat

Затем я просто создаю этот файл и пишу в stdin следующим образом:

cmd = spawn("shell_script");

cmd.stdin.write(svg);
cmd.stdin.end();
cmd.stdout.pipe(pipe);

Я действительно думаю, что это должно работать без оболочки script, но это не будет (для меня, по крайней мере). Это может быть ошибка Node.js.

Ответ 2

Хорошо, у меня нет Inkscape, но это, похоже, решает проблему Node.js. Я использую wc как стенд в Inkscape; параметр -c просто выводит количество байтов в данном файле (в данном случае /dev/stdin).

var child_process = require('child_process');

/**
 * Create the child process, with output piped to the script stdout
 */
var wc = child_process.spawn('wc', ['-c', '/dev/stdin']);
wc.stdout.pipe(process.stdout);

/**
 * Write some data to stdin, and then use stream.end() to signal that we're
 * done writing data.
 */
wc.stdin.write('test');
wc.stdin.end();

Трюк, похоже, сигнализирует о том, что вы закончили запись в поток. В зависимости от того, насколько велика ваша SVG, вам может потребоваться обратить внимание на противодавление от Inkscape, обработав событие 'drain'.


Что касается передачи потока в вызов child_process.spawn, вам вместо этого нужно использовать опцию 'pipe', а затем передать читаемый поток в child.stdin, как показано ниже. Я знаю, что это работает в Node v0.10.26, но не уверен до этого.

var stream = require('stream');
var child_process = require('child_process');

/**
 * Create the child process, with output piped to the script stdout
 */
var wc = child_process.spawn('wc', ['-c', '/dev/stdin'], {stdin: 'pipe'});
wc.stdout.pipe(process.stdout);

/**
 * Build a readable stream with some data pushed into it.
 */
var readable = new stream.Readable();
readable._read = function noop() {}; // See note below
readable.push('test me!');
readable.push(null);

/**
 * Pipe our readable stream into wc standard input.
 */
readable.pipe(wc.stdin);

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

Примечание. Функция readable._push должна быть реализована в соответствии с документами, но необязательно что-то делать.

Ответ 3

Проблема возникает из-за того, что файловые дескрипторы в node являются сокетами и что Linux (и, вероятно, большинство Unices) не позволит вам открывать /dev/stdin, если это сокет.

Я нашел это объяснение bnoordhuis на https://github.com/nodejs/node-v0.x-archive/issues/3530#issuecomment-6561239

Данное решение близко к ответу @nmrugg:

var run = spawn("sh", ["-c", "cat | your_command_using_dev_stdin"]);

После дальнейшей работы вы можете теперь использовать модуль https://www.npmjs.com/package/posix-pipe, чтобы убедиться, что процесс видит stdin, который не является сокетом.

посмотрите на тест "должен передать данные в дочерний процесс" в этом модуле, который сводится к

var p = pipe()
var proc = spawn('your_command_using_dev_stdin', [ .. '/dev/stdin' .. ],
    { stdio: [ p[0], 'pipe', 'pipe' ] })
p[0].destroy() // important to avoid reading race condition between parent/child
proc.stdout.pipe(destination)
source.pipe(p[1])

Ответ 4

Как Ошибка Inkscape 171016 указывает, что Inkscape не поддерживает импорт через stdin, но он находится в их списке пожеланий.