popen 和 JS ffi 的管道损坏错误

Hea*_*ray 3 shell pipe tr head

我正在为 nodejs使用ffi,这在很大程度上与这个问题无关,这实际上是为了更好地理解管道,但确实提供了一些上下文。

function exec(cmd) {
  var buffer = new Buffer(32);
  var result = '';
  var fp = libc.popen('( ' + cmd + ') 2>&1', 'r');
  var code;

  if (!fp) throw new Error('execSync error: '+cmd);

  while( !libc.feof(fp) ){
    libc.fgets(buffer, 32, fp)
    result += buffer.readCString();
  }
  code = libc.pclose(fp) >> 8;

  return {
    stdout: result,
    code: code
  };
}
Run Code Online (Sandbox Code Playgroud)

这让我想到了这段代码,当我使用这个 exec 函数运行时

 tr -dc "[:alpha:]" < /dev/urandom | head -c ${1-8}
Run Code Online (Sandbox Code Playgroud)

我收到错误:

write error: Broken pipe
tr: write error
Run Code Online (Sandbox Code Playgroud)

但我确实得到了我期望的输出:8 个随机数。这让我很困惑,但后来在一些疯狂的谷歌搜索中,我发现这个堆栈答案完全适合我的情况。

不过,我还有一些问题。

为什么:

tr -dc "[:alpha:]" < /dev/urandom | head -c ${1-8}
Run Code Online (Sandbox Code Playgroud)

使用我的 exec 命令调用时抛出管道损坏错误,但从 shell 调用时则不抛出?我不明白为什么当我打电话时:

tr -dc "[:alpha:]" < /dev/urandom
Run Code Online (Sandbox Code Playgroud)

它无休止地读取,但是当我通过管道将其发送到:

head -c ${1-8}
Run Code Online (Sandbox Code Playgroud)

它可以正常工作而不会抛出损坏的管道错误。似乎这head将需要它需要的东西,并且tr会永远阅读。至少它应该扔断管;head会消耗前 8 个字节,然后tr仍然会输出输出,并且会tr因为head已停止运行而抛出损坏的管道。

这两种情况对我来说都有意义,但似乎它们是彼此独有的。我不明白调用之间有什么不同:

exec(tr -dc "[:alpha:]" < /dev/urandom | head -c ${1-8})
Run Code Online (Sandbox Code Playgroud)

tr -dc "[:alpha:]" < /dev/urandom | head -c ${1-8}
Run Code Online (Sandbox Code Playgroud)

直接从命令行,特别是为什么<一个无尽的文件变成了某个东西,然后|它变成了某个东西,这使得它不会无休止地运行。我已经这样做了多年,从来没有质疑过为什么会这样。

最后,可以忽略这个破损的管道错误吗?有办法解决吗?我在我的 C++ ish javascript 代码中做错了什么吗?我是否缺少某种流行的基础知识?

- - - 编辑

搞乱一些代码

exec('head -10 /dev/urandom | tr -dc "[:alpha:]" | head -c 8')
Run Code Online (Sandbox Code Playgroud)

不会抛出管道错误!

Sté*_*las 5

通常,tr不应该能够写入该错误消息,因为在管道的另一端在head.

您收到该错误消息是因为不知何故,正在运行的进程tr已配置为忽略 SIGPIPE。我怀疑这可能是通过在popen()那里用您的语言实现的。

您可以通过执行以下操作来重现它:

sh -c 'trap "" PIPE; tr -dc "[:alpha:]" < /dev/urandom | head -c 8'
Run Code Online (Sandbox Code Playgroud)

您可以通过执行以下操作来确认正在发生的事情:

strace -fe signal sh your-program
Run Code Online (Sandbox Code Playgroud)

(或者如果不使用 Linux,则在您的系统上等效)。然后你会看到类似的东西:

rt_sigaction(SIGPIPE, {SIG_IGN, ~[RTMIN RT_1], SA_RESTORER, 0x37cfc324f0}, NULL, 8) = 0
Run Code Online (Sandbox Code Playgroud)

或者

signal(SIGPIPE, SIG_IGN)
Run Code Online (Sandbox Code Playgroud)

在同一进程或其后代之一执行/bin/sh解释该命令行并启动tr和之前的一个进程中完成head

如果您执行 a strace -fe write,您将看到类似以下内容:

write(1, "AJiYTlFFjjVIzkhCAhccuZddwcydwIIw"..., 4096) = -1 EPIPE (Broken pipe)
Run Code Online (Sandbox Code Playgroud)

write系统调用失败,并EPIPE错误而不是触发SIGPIPE。

无论如何tr都会退出。当忽略 SIGPIPE 时,由于该错误(但也会触发错误消息)。如果不是,则在收到 SIGPIPE 时退出。你这样做要退出,因为你不希望它携带阅读做/dev/urandom的8个字节后就一直read通过head

为避免该错误消息,您可以使用以下命令恢复 SIGPIPE 的默认处理程序:

trap - PIPE
Run Code Online (Sandbox Code Playgroud)

打电话之前tr

popen("trap - PIPE; { tr ... | head -c 8; } 2>&1", ...)
Run Code Online (Sandbox Code Playgroud)