Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cannot create /dev/stdout: No such device or address

I'm want to run a shell command via node and capture the result of stdout. My script works fine on OSX, but not on Ubuntu.

I've simplified the problem and script to the following node script:

var execSync = require('child_process').execSync,
    result = execSync('echo "hello world" >> /dev/stdout');

// Do something with result

Results in:

/bin/sh: 1: cannot create /dev/stdout: No such device or address

  • I have tried replacing /dev/stdout with /dev/fd/1
  • I have tried changing the shell to bash... execSync('echo ...', {shell : '/bin/bash'})

Like I said, the problem above is simplified. The real script accepts as a parameter the name of a file where results should be written, so I need to resolve this by providing access to the stdout stream as a file descriptor, i.e. /dev/stdout.

How can I execute a command via node, while giving the command access to its own stdout stream?

like image 837
Drahcir Avatar asked Oct 30 '22 17:10

Drahcir


1 Answers

On /dev/stdout

I don't have access to an OSX box, but from this issue on phantomjs, it seems that while on both OSX/BSD and Linux /dev/stdout is a symlink, nonetheless it seems to work differently between them. One of the commenters said it's standard on OSX to use /dev/stdout but not for Linux. In another random place I read statements that imply /dev/stdout is pretty much an OSX thing. There might be a clue in this answer as to why it doesn't work on Linux (seems to implicitly close the file descriptor when used this way).

Further related questions:

  • https://unix.stackexchange.com/questions/36403/portability-of-dev-stdout
  • bash redirect to /dev/stdout: Not a directory

The solution

I tried your code on Arch and it indeed gives me the same error, as do the variations mentioned - so this is not related to Ubuntu.

I found a blog post that describes how you can pass a file descriptor to execSync. Putting that together with what I got from here and here, I wrote this modified version of your code:

var fs = require('fs');
var path = require('path');

var fdout = fs.openSync(path.join(process.cwd(), 'stdout.txt'), 'a');
var fderr = fs.openSync(path.join(process.cwd(), 'stderr.txt'), 'a');

var execSync = require('child_process').execSync,
    result = execSync('echo "hello world"', {stdio: [0,fdout,fderr] });

Unless I misunderstood your question, you want to be able to change where the output of the command in execSync goes. With this you can, using a file descriptor. You can still pass 1 and 2 if you want the called program to output to stdout and stderr as inherited by its parent, which you've already mentioned in the comments.

For future reference, this worked on Arch with kernel version 4.10.9-1-ARCH, on bash 4.4.12 and node v7.7.3.

like image 200
mechalynx Avatar answered Nov 11 '22 02:11

mechalynx