How to detect if a Node.js script is running through a shell pipe?
Asked Answered
A

5

34

My question is similar to this one: How to detect if my shell script is running through a pipe?. The difference is that the shell script I’m working on is written in Node.js.

Let’s say I enter:

echo "foo bar" | ./test.js

Then how can I get the value "foo bar" in test.js?

I’ve read Unix and Node: Pipes and Streams but that only seems to offer an asynchronous solution (unless I’m mistaken). I’m looking for a synchronous solution. Also, with this technique, it doesn’t seem very straightforward to detect if the script is being piped or not.

TL;DR My question is two-fold:

  1. How to detect if a Node.js script is running through a shell pipe, e.g. echo "foo bar" | ./test.js?
  2. If so, how to read out the piped value in Node.js?
Aftonag answered 17/3, 2013 at 21:23 Comment(1)
Collateral note: in case you can not touch the original script and you still want to obtain a similar result of pipe try: ./test.js $(echo 'foo bar')Fibrovascular
S
24

Pipes are made to handle small inputs like "foo bar" but also huge files.

The stream API makes sure that you can start handling data without waiting for the huge file to be totally piped through (this is better for speed & memory). The way it does this is by giving you chunks of data.

There is no synchronous API for pipes. If you really want to have the whole piped input in your hands before doing something, you can use

note: use only node >= 0.10.0 because the example uses the stream2 API

var data = '';
function withPipe(data) {
   console.log('content was piped');
   console.log(data.trim());
}
function withoutPipe() {
   console.log('no content was piped');
}

var self = process.stdin;
self.on('readable', function() {
    var chunk = this.read();
    if (chunk === null) {
        withoutPipe();
    } else {
       data += chunk;
    }
});
self.on('end', function() {
   withPipe(data);
});

test with

echo "foo bar" | node test.js

and

node test.js
Saphead answered 17/3, 2013 at 23:25 Comment(2)
node test.js hangs for me in v10.16.0Knorring
If I do that I get: Property 'read' does not exist on type 'Writable'.ts(2339)Unessential
A
51

I just found out a simpler answer to part of my question.

To quickly and synchronously detect if piped content is being passed to the current script in Node.js, use the process.stdin.isTTY boolean:

$ node -p -e 'process.stdin.isTTY'
true
$ echo 'foo' | node -p -e 'process.stdin.isTTY'
undefined

So, in a script, you could do something like this:

if (process.stdin.isTTY) {
  // handle shell arguments
} else {
  // handle piped content (see Jerome’s answer)
}

The reason I didn’t find this before is because I was looking at the documentation for process, where isTTY is not mentioned at all. Instead, it’s mentioned in the TTY documentation.

Aftonag answered 18/3, 2013 at 19:39 Comment(5)
unfortunately, this will fail in a child process: node -p -e "require('child_process').exec(\"node -p -e 'process.stdin.isTTY'\", (err, res) => console.log('err:', err, 'res:', res))"Rook
TTY may be unavailable in some situations (e.g. Docker container launched without pseudo-tty) and checking process.stdin.isTTY will fail.Halmstad
Damn. Is there anything we we can do about this? Seems like a fairly simple problem for unix programs.Knorring
@Halmstad in what way does it fail? Is process.stdin === undefined or something? @Rook that works for me on Node 14Crenation
@CameronTacklind docker run -t --rm node node -pe 'process.stdin.isTTY' returns true while docker run --rm node node -pe 'process.stdin.isTTY' returns undefined. So the execution environment also affects process.stdin.isTTY. It being undefined does not always mean node process is piped.Halmstad
S
24

Pipes are made to handle small inputs like "foo bar" but also huge files.

The stream API makes sure that you can start handling data without waiting for the huge file to be totally piped through (this is better for speed & memory). The way it does this is by giving you chunks of data.

There is no synchronous API for pipes. If you really want to have the whole piped input in your hands before doing something, you can use

note: use only node >= 0.10.0 because the example uses the stream2 API

var data = '';
function withPipe(data) {
   console.log('content was piped');
   console.log(data.trim());
}
function withoutPipe() {
   console.log('no content was piped');
}

var self = process.stdin;
self.on('readable', function() {
    var chunk = this.read();
    if (chunk === null) {
        withoutPipe();
    } else {
       data += chunk;
    }
});
self.on('end', function() {
   withPipe(data);
});

test with

echo "foo bar" | node test.js

and

node test.js
Saphead answered 17/3, 2013 at 23:25 Comment(2)
node test.js hangs for me in v10.16.0Knorring
If I do that I get: Property 'read' does not exist on type 'Writable'.ts(2339)Unessential
K
14

It turns out that process.stdin.isTTY is not reliable because you can spawn a child process that is not a TTY.

I found a better solution here using file descriptors.

You can test to see if your program with piped in or out with these functions:

function pipedIn(cb) {
    fs.fstat(0, function(err, stats) {
        if (err) {
            cb(err)
        } else {
            cb(null, stats.isFIFO())
        }
    })
}

function pipedOut(cb) {
    fs.fstat(1, function(err, stats) {
        if (err) {
            cb(err)
        } else {
            cb(null, stats.isFIFO())
        }
    })
}

pipedIn((err, x) => console.log("in", x))
pipedOut((err, x) => console.log("out", x))

Here's some tests demonstrating that it works.

❯❯❯ node pipes.js
in false
out false
❯❯❯ node pipes.js | cat -
in false
out true
❯❯❯ echo 'hello' | node pipes.js | cat -
in true
out true
❯❯❯ echo 'hello' | node pipes.js
in true
out false
❯❯❯ node -p -e "let x = require('child_process').exec(\"node pipes.js\", (err, res) => console.log(res))"
undefined
in false
out false
❯❯❯ node -p -e "let x = require('child_process').exec(\"echo 'hello' | node pipes.js\", (err, res) => console.log(res))"
undefined
in true
out false
❯❯❯ node -p -e "let x = require('child_process').exec(\"echo 'hello' | node pipes.js | cat -\", (err, res) => console.log(res))"
undefined
in true
out true
❯❯❯ node -p -e "let x = require('child_process').exec(\"node pipes.js | cat -\", (err, res) => console.log(res))"
undefined
in false
out true
Knorring answered 25/11, 2019 at 1:47 Comment(0)
J
1

If you need to pipe into nodejs using an inline --eval string in bash, cat works too:

$ echo "Hello" | node -e "console.log(process.argv[1]+' pipe');" "$(cat)"
# "Hello pipe"
Jasmine answered 29/11, 2013 at 11:46 Comment(0)
C
0

You need to check stdout (not stdin like suggested elsewhere) like this:

if (process.stdout.isTTY) {
  // not piped
} else {
  // piped
}
Clothing answered 21/3, 2017 at 15:38 Comment(1)
Wouldn't that be the reverse? i.e. node foo.js | catLaure

© 2022 - 2024 — McMap. All rights reserved.