You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

当前进程stdin为何影响popen()调用的命令?如何解决?

根据popen的手册说明[重点标注]:

popen()函数通过创建管道、fork子进程、调用shell的方式启动进程。由于管道本质是单向的,type参数仅可指定读或写模式,不可同时指定两种模式,返回的文件流对应为只读或只写。

基于上述说明,popen返回的文件流无法同时进行读写操作,很容易理所当然认为当前进程的stdin不会影响传入popen()的shell命令的运行行为,但实际运行测试程序时会出现完全超出预期的现象。

测试代码
//filename pipe_cmd.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    int ret = 0;
    FILE* file = popen("ssh -t jhone@192.168.1.51  bash -c 'top' ", "r");

    int fd = fileno(file);
    char buffer[1024*100];
    
    if(NULL != file)
    {
        while (true) {
            memset(buffer, '\0', sizeof(buffer));
            int ret_read = read(fd, buffer, sizeof(buffer));
            
            if (ret_read > 0) {
              printf("%s", buffer);
            } else if (0 == ret_read) {
              ret = 0;
              break;
            } else {
              ret = -1;
              break;
            }
        }
    }
    
    return ret;
}

使用如下命令编译代码:
gcc pipe_cmd.c -o pipe_cmd

异常现象

运行生成的pipe_cmd程序时,可正常看到远程主机的进程信息,符合预期。但按下h键时,本地终端会显示top命令的帮助信息,该现象完全超出预期,显示内容如下:

Help for Interactive Commands - procps-ng version 3.3.9
Window 1:Def: Cumulative mode Off.  System: Delay 0.1 secs; Secure mode Off.

  Z,B,E,e   Global: 'Z' colors; 'B' bold; 'E'/'e' summary/task memory scale
  l,t,m     Toggle Summary: 'l' load avg; 't' task/cpu stats; 'm' memory info
  0,1,2,3,I Toggle: '0' zeros; '1/2/3' cpus or numa node views; 'I' Irix mode
  f,F,X     Fields: 'f'/'F' add/remove/order/sort; 'X' increase fixed-width

  L,&,<,> . Locate: 'L'/'&' find/again; Move sort column: '<'/'>' left/right
  R,H,V,J . Toggle: 'R' Sort; 'H' Threads; 'V' Forest view; 'J' Num justify
  c,i,S,j . Toggle: 'c' Cmd name/line; 'i' Idle; 'S' Time; 'j' Str justify
  x,y     . Toggle highlights: 'x' sort field; 'y' running tasks
  z,b     . Toggle: 'z' color/mono; 'b' bold/reverse (only if 'x' or 'y')
  u,U,o,O . Filter by: 'u'/'U' effective/any user; 'o'/'O' other criteria
  n,#,^O  . Set: 'n'/'#' max tasks displayed; Show: Ctrl+'O' other filter(s)
  C,...   . Toggle scroll coordinates msg for: up,down,left,right,home,end

  k,r       Manipulate tasks: 'k' kill; 'r' renice
  d or s    Set update interval
  W,Y       Write configuration file 'W'; Inspect other output 'Y'
  q         Quit
          ( commands shown with '.' require a visible task display window ) 
Press 'h' or '?' for help with Windows,

该表现如同top命令是直接在本地bash中调用的,但实际上它是通过popen启动的,和手册说明的预期行为不符。

备注:

  • 代码中的IP地址192.168.1.51是本地计算机的IP,经测试不是导致该现象的原因。

更新:

  • 不对代码做任何修改,将IP更换为其他不同IP的远程主机运行时,仍存在相同现象。
原因解释

出现该现象的核心原因是popen只会重定向子进程的单一标准IO流:当type为r时仅将子进程stdout接入管道,当type为w时仅将子进程stdin接入管道,其余文件描述符默认直接继承父进程状态。

  • 使用popen(..., "r")启动命令时,子进程的stdin依然绑定在父进程(也就是你的测试程序)的终端输入上,没有被断开或重定向。
  • 调用ssh时加了-t参数,会强制在远程分配伪终端,远程的top作为交互式程序会主动读取终端输入;本地ssh进程的stdin没有被重定向,会直接把本地终端输入的按键内容透传到远程伪终端,最终被top进程接收。因此按h键会触发top的帮助输出,输出内容再通过popen创建的管道传回测试程序、打印到本地终端,就出现了top仿佛在本地运行的错觉。
基于popen的解决方案

不需要自行实现pipe+fork的全套逻辑,只需要在popen调用的命令行中提前通过shell重定向把stdin绑定到空设备,切断子进程和本地终端输入的关联即可,修改后的popen调用写法如下:

FILE* file = popen("ssh -t jhone@192.168.1.51 bash -c 'top' </dev/null", "r");

该方案改动量最小,原理是让popen启动的shell子进程先把stdin重定向到/dev/null,后续fork出的ssh进程会继承这个重定向后的stdin,不再读取本地终端的输入,从根源上避免本地按键影响远程命令运行。
如果需要更灵活的控制逻辑,也可以在popen调用前临时将当前进程的stdin重定向到/dev/null,popen执行完成后再恢复原stdin,但直接在命令行追加</dev/null是最简洁、兼容性最好的实现方式。

内容的提问来源于stack exchange,提问作者John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 22:51:19