How do you pipe a long string to /dev/stdin via child_process.spawn() in Node.js?

后端 未结 4 1466
失恋的感觉
失恋的感觉 2021-02-07 07:39

I\'m trying to execute Inkscape by passing data via stdin. Inkscape only supports this via /dev/stdin. Basically, I\'m trying to do something like this

相关标签:
4条回答
  • 2021-02-07 08:02

    As Inkscape bug 171016 indicates, Inkscape does not support importing via stdin, but it is on their Wishlist.

    0 讨论(0)
  • 2021-02-07 08:04

    So, I figured out a work around. This seems like a bit of a hack, but it works just fine.

    First, I made this one line shell script:

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

    Then, I simply spawn that file and write to the stdin like this:

    cmd = spawn("shell_script");
    
    cmd.stdin.write(svg);
    cmd.stdin.end();
    cmd.stdout.pipe(pipe);
    

    I really think this should work without the shell script, but it won't (for me at least). This may be a Node.js bug.

    0 讨论(0)
  • 2021-02-07 08:23

    The problem comes from the fact that file descriptors in node are sockets and that linux (and probably most Unices) won't let you open /dev/stdin if it's a socket.

    I found this explanation by bnoordhuis on https://github.com/nodejs/node-v0.x-archive/issues/3530#issuecomment-6561239

    The given solution is close to @nmrugg's answer :

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

    After further work, you can now use the https://www.npmjs.com/package/posix-pipe module to make sure that the process sees a stdin that is not a socket.

    look at the 'should pass data to child process' test in this module which boils down to

    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])
    
    0 讨论(0)
  • 2021-02-07 08:29

    Alright, I don't have Inkscape, but this appears to solve the Node.js side of things. I'm using wc as a stand in Inkscape; the -c option simply outputs the number of bytes in a given file (in this case /dev/stdin).

    var child_process = require('child_process');
    
    /**
     * Create the child process, with output piped to the script's 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();
    

    The trick seems to be signaling that you're done writing to the stream. Depending on how large your SVG is, you may need to pay attention to backpressure from Inkscape by handling the 'drain' event.


    As for passing a stream into the child_process.spawn call, you instead need to use the 'pipe' option, and then pipe a readable stream into child.stdin, as shown below. I know this works in Node v0.10.26, but not sure about before that.

    var stream = require('stream');
    var child_process = require('child_process');
    
    /**
     * Create the child process, with output piped to the script's 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's standard input.
     */
    readable.pipe(wc.stdin);
    

    Obviously, this method is a bit more complicated, and you should use the method above unless you have good reason to (you're effectively implementing your own readable string).

    Note: The readable._push function must be implemented according to the docs, but it doesn't necessarily have to do anything.

    0 讨论(0)
提交回复
热议问题