如何在通过管道向PHP进程输入后保持输入流处于打开状态?
这个问题的核心其实不是PHP的限制,而是管道(pipe)本身的特性:当管道的发送端(比如你例子里的echo foo)写完数据后,会立即关闭输出流,这就导致PHP的STDIN直接到达了EOF(文件末尾)——不管你是用stream_get_contents一次性读完,还是只读了一部分,后续再尝试读取都会因为管道已经关闭而失败,不会阻塞等待新输入。
你试过的几种方法无效的原因也很明确:
- 关闭并重新打开STDIN:PHP里的STDIN关联的是系统的标准输入文件描述符,管道关闭后,这个描述符已经没有可用的输入源了,重开也没用。
- 读取精确字节数:虽然暂时没触发流的EOF标志,但管道本身已经没有数据可提供了,后续读取还是会直接返回EOF。
下面给你几个可行的解决方案,根据你的场景选择:
1. 用cat拼接管道输入和终端输入(最简便)
如果你的需求是先通过管道传入初始数据,之后还要接收用户的交互输入,可以用cat命令把管道数据和终端输入拼接起来,让cat作为持续的输入源:
# 把管道数据和终端输入合并后传给PHP cat <(echo "foo") - | php foo.php
对应的PHP代码可以正常读取初始数据后继续等待输入:
// 读取管道传入的初始数据 $initialInput = stream_get_contents(STDIN); echo "初始输入内容:$initialInput\n"; // 此时fgets会阻塞等待用户在终端输入 echo "请输入后续内容:"; $line = fgets(STDIN); echo "你输入的内容:$line";
原理是cat <(echo "foo") -会先输出echo的内容,然后持续读取终端的输入(-代表STDIN),直到用户手动终止,这样PHP的STDIN流就不会提前关闭。
2. 区分交互模式和管道输入
如果你的脚本需要同时支持管道输入和交互输入,可以先判断STDIN是否是终端(交互环境),再做不同处理:
// 检查STDIN是否连接到终端(交互模式) if (posix_isatty(STDIN)) { // 交互模式:直接等待用户输入 echo "请输入内容:"; $input = fgets(STDIN); echo "你输入了:$input"; } else { // 管道输入:读取所有管道数据后,提示用户切换到交互模式 $pipeInput = stream_get_contents(STDIN); echo "已读取管道输入:$pipeInput\n"; echo "若需要后续输入,请在终端直接运行脚本(不要用管道)\n"; }
这个方法需要PHP的posix扩展支持,大部分Linux/Unix环境默认都有。
3. 用进程替换传递初始数据(保留STDIN用于交互)
另一种方式是用bash的进程替换,把初始数据作为文件传给脚本,而STDIN仍然保留给终端输入:
# 把echo的输出作为临时文件传给脚本,STDIN还是终端 php foo.php <(echo "foo")
然后PHP里通过命令行参数读取这个"文件"的内容,STDIN正常用于交互:
// 读取进程替换传入的初始数据文件 $initialFile = $argv[1]; $initialInput = file_get_contents($initialFile); echo "初始输入:$initialInput\n"; // 用STDIN等待用户输入 echo "请输入后续内容:"; $line = fgets(STDIN); echo "你输入的内容:$line";
这种方式的好处是脚本逻辑更清晰,初始数据和交互输入完全分离。
总结一下:要让STDIN能继续读取,关键是要保证输入源不提前关闭——管道本身是一次性的,所以要么用工具(比如cat)维持输入流,要么换一种方式传递初始数据,把STDIN留作交互使用。
内容的提问来源于stack exchange,提问作者Tim

