5

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?

mechalynx
  • 1,306
  • 1
  • 9
  • 24
Drahcir
  • 11,772
  • 24
  • 86
  • 128
  • I did previously post a similar question http://stackoverflow.com/questions/40288461/no-such-device-or-address-dev-stdout but have voted to close becuase it was too broad and answers were relative to Python. – Drahcir Oct 28 '16 at 09:18
  • your original question currently has only one vote for closing by too broad answers, and it is not yet actually closed. you may keep going on your original question. – ymonad Oct 28 '16 at 09:52
  • @ymonad It's my vote, I want to close it because it needed to be completely rewritten and the only answer is specific to Python (which is not the cause). – Drahcir Oct 28 '16 at 10:10
  • you shouldn't have to redirect to stdout as execSync just returns the results of what would be output. – strobelight Oct 28 '16 at 15:26
  • @strobelight yes I know, that's what the problem is, it doesn't work on ubuntu, works as expected on OS X. – Drahcir Oct 28 '16 at 17:37
  • @chepner not while it has answers – Drahcir Oct 28 '16 at 17:38
  • so ```result = execSync('echo "hello world" ');``` doesn't return "hello world" in result on ubuntu? – strobelight Oct 28 '16 at 17:47
  • @Drahcir My mistake; I thought it was only accepted answers that prevented deletion. – chepner Oct 28 '16 at 18:04
  • @strobelight yes that works but not with `>> /dev/stdout`, as I mentioned it is a simplified version of the real problem, I must be able to reference stdout via a file descriptor. – Drahcir Oct 28 '16 at 18:05
  • stdout is file descriptor 1. – strobelight Oct 29 '16 at 01:57
  • @strobelight yes, like I said, I tried replacing /dev/stdout with /dev/fd/1 – Drahcir Oct 29 '16 at 07:11
  • @Drahcir yes, but /dev/xxxx is not a file descriptor. By default the file descriptor for stdout is 1, and the file descriptor for a file is whatever you got back as a file descriptor for when you opened the file. Perhaps you need to look into streams and not file descriptors. Your example wants a result returned from running a program. If that program writes results to stdout, you don't need to redirect to it. – strobelight Oct 29 '16 at 15:59
  • @strobelight The program doesn't naturally write results to stdout, it only writes to a file. This is why I must specify the file path `/dev/stdout`. – Drahcir Oct 29 '16 at 16:01
  • fs.open(path,'a',...) get a fd, use this or the value 1 for stdout. – strobelight Oct 29 '16 at 16:11
  • @strobelight This will set the new program's stdout to the same as the parent's stdout, it's the same as setting the 2nd `stdio` element to `"inherit"`. It means the output from the newly started program will be sent directly to the user's terminal. – Drahcir Oct 29 '16 at 16:17
  • so it sounds like the program is really passed some arg of a file to open to write to. Some programs allow "-" to refer to that. – strobelight Oct 29 '16 at 16:38
  • The simplifications here are perhaps *oversimplifications*. When `/dev/stdout` is parsed by a shell -- as it is in the case of `>>` -- it can be read as FD 1 even if the OS provides no actual `/dev/stdout` filesystem entity which a non-shell application would recognize. `>>` is thus not an acceptable substitute in terms of having identical behavior across platforms -- you'd be better off using a standard tool that writes to a named file, perhaps `tee`. – Charles Duffy Apr 17 '17 at 17:59

1 Answers1

2

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:

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.

Community
  • 1
  • 1
mechalynx
  • 1,306
  • 1
  • 9
  • 24