如何检测一个 Node.js 脚本是否通过 shell 管道运行?

33

我的问题类似于这个: 如何检测我的shell脚本是否通过管道运行?。 不同之处在于,我正在使用Node.js编写的shell脚本。

假设我输入:

echo "foo bar" | ./test.js

那么我该如何在test.js文件中获取"foo bar"的值?

我已经阅读了Unix and Node: Pipes and Streams,但那只提供了一个异步解决方案(除非我理解有误)。我正在寻找同步解决方案。此外,使用这种技术,似乎很难直接检测脚本是否正在通过管道运行。

简而言之,我的问题有两个:

  1. 如何检测Node.js脚本是否通过shell管道运行,例如:echo "foo bar" |./test.js
  2. 如果是,则如何在Node.js中读取传递的值?

附注:如果您无法接触原始脚本,但仍希望获得类似管道的结果,请尝试:./test.js $(echo'foo bar') - borracciaBlu
5个回答

50

我刚刚发现了我问题的一部分更简单的答案。

要快速且同步地检测在 Node.js 中是否正在将管道内容传递给当前脚本,请使用 process.stdin.isTTY 布尔值:

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

因此,在脚本中,您可以执行以下操作:

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

我之前为什么没有找到这个,是因为我在查看process的文档时,并没有提到isTTY。而是在TTY文档中提到了它。


1
不幸的是,这将在子进程中失败:node -p -e "require('child_process').exec(\"node -p -e 'process.stdin.isTTY'\", (err, res) => console.log('err:', err, 'res:', res))" - maxlath
1
在某些情况下(例如在没有伪终端的情况下启动Docker容器),TTY可能无法使用,并且检查process.stdin.isTTY将失败。 - Lacek
该死。我们能做些什么来解决这个问题吗?对于Unix程序来说,这似乎是一个相当简单的问题。 - Chet
@Lacek 它失败的方式是什么?是 process.stdin === undefined 还是其他什么原因?@maxlath 在 Node 14 上对我有效。 - Cameron Tacklind
1
@CameronTacklind docker run -t --rm node node -pe 'process.stdin.isTTY' 返回 true,而 docker run --rm node node -pe 'process.stdin.isTTY' 返回 undefined。因此,执行环境也会影响 process.stdin.isTTY。它的值为 undefined 并不总是意味着 Node 进程被管道化了。 - Lacek

24

管道被设计用于处理像“foo bar”这样的小输入,也可以处理巨大的文件。

流API确保您可以在等待完全通过管道传输巨大文件之前开始处理数据(这对于速度和内存更好)。 它的做法是提供数据块。

没有同步API可用于管道。 如果你真的希望在执行某些操作之前将整个输入流保存在手中,则可以使用

注意:只使用node >= 0.10.0因为该示例使用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);
});

测试一下

echo "foo bar" | node test.js

node test.js

1
node test.js hangs for me in v10.16.0 - Chet

14

原来发现process.stdin.isTTY不可靠,因为可以生成一个不是TTY的子进程。

我在这里找到了一个更好的解决方案,使用文件描述符

你可以使用这些函数测试你的程序是否带有管道输入或输出:

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))

这里有一些测试,证明它有效。

❯❯❯ 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

1
如果你需要在bash中使用内联的--eval字符串来将内容输入到nodejs中,cat也可以实现:
$ echo "Hello" | node -e "console.log(process.argv[1]+' pipe');" "$(cat)"
# "Hello pipe"

0
你需要像这样检查 stdout(而不是其他地方建议的 stdin):
if (process.stdout.isTTY) {
  // not piped
} else {
  // piped
}

1
那不是反过来吗?即 node foo.js | cat - coolaj86

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接