Ncurses shell escape删除父进程输出

时间:2013-09-07 23:33:49

标签: ncurses curses

我正在使用Ubuntu Linux 12.04并编写一个使用ncurses的程序。我的程序有一个执行从属进程的选项(“shell转义”)。在创建从属流程之前我做了

reset_shell_mode( );
putp( exit_ca_mode );  // From <term.h>

然后当从属进程退出时,我用

恢复我的curses显示
putp( enter_ca_mode );  // From <term.h>
reset_prog_mode( );
refresh( );

这很好用。但是,我的程序还希望在启动从属进程之前输出一些信息。它还希望在从属进程退出但在返回完整的curses显示之前输出一些附加信息。因此我有(缩写):

reset_shell_mode( );
putp( exit_ca_mode );
printf( "Don't forget... blah, blah\n" );
system( external_command );
printf( "Updating, etc\n" );
putp( enter_ca_mode );
reset_prog_mode( );
refresh( );

问题是我的程序在调用system()之前和之后产生的文本没有出现。我想也许它仍然会进入一些与curses相关的缓冲区。我不知道。

如何让父进程也在终端以及子进程上输出?

2 个答案:

答案 0 :(得分:1)

Curses保留自己的缓冲区,了解屏幕应该是什么样子。当你调用refresh()时,它调整屏幕以匹配该缓冲区,这意味着curses不知道的所有内容都将被覆盖(*)。

printf,以及任何外部命令的输出,绕过该缓冲区,直接进入屏幕(更确切地说,是标准输出,恰好连接到屏幕,因为它们从shell继承了它们的标准输出)。

因此,要将printf输出转换为curses,需要将printf替换为printw。要将其他程序的输出转换为curses,您必须将其输出捕获到程序中,然后将其提供给curses。

执行此操作的简单方法是将输出重定向到文件,然后读取文件:

system("ls > tempfile");
if ((fp=fopen("tempfile", "r"))!=NULL) {
    while (fgets(buf, sizeof buf, fp))
        printw("%s", buf);
    fclose(fp);
}

警告:这个例子被大量删除,为您提供一个想法。它没有很好地捕获错误,它使用容易出现各种缓冲区溢出的fgets,并且它使用临时文件的常量名称,这会导致很多并发问题。

更好的方法是在流程和您尝试运行的程序之间创建管道:

int p[2];
pipe(p);
if (fork()==0) {  // child process
    close(1);
    dup(p[1]);
    close(p[1]);
    close(p[0]);
    execlp("ls", "ls", NULL);
} else {          // parent process
    close(p[1]);
    if ((fp=fdopen(p[0], "r"))!=NULL) {
        while (fgets(buf, sizeof buf, fp))
            printw("%s", buf);
        fclose(fp);
    }
}

同样,这个例子被大量删除(我直接在浏览器中键入它,从未编译或运行它)。要真正理解它,并添加所有缺少的错误检查,了解linux / unix进程模型,管道,文件描述符与C文件指针 - 那里有很多教程,这远远超出了你原来的问题。

但是,总结一下:如果你想让curses在屏幕上放置任何东西,你必须使用适当的curses函数。一旦curses刷新屏幕,绕过curses的所有东西都可能被覆盖。

(*)如果curses认为屏幕和内部缓冲区之间只有区别,它只会更新不同的字符,而不是整个屏幕。因此,如果您的外部程序写入屏幕的部分屏幕,而诅咒认为不需要更新,则会将这些部分单独保留,这意味着程序输出的一部分将保留。

答案 1 :(得分:0)

在示例中

reset_shell_mode( );
putp( exit_ca_mode );
printf( "Don't forget... blah, blah\n" );
system( external_command );
printf( "Updating, etc\n" );
putp( enter_ca_mode );
reset_prog_mode( );
refresh( );

reset_shell_mode()来电尝试恢复终端设置。有一个问题。 curses(一般来说,不仅仅是ncurses)将终端模式设置为“raw”(允许您在不受I / O缓冲干扰的情况下读取输入字符),但设置输出缓冲(表现)。

它使用setvbuf的某些变体执行此操作,根据标准 无法 可靠地关闭/打开:

  

在流指向的流与打开的文件相关联之后可以使用setvbuf()函数,但在之前 任何其他操作(不成功的除外)呼叫setvbuf())在流上执行。

这不仅仅是一个很好的细节;如果你试图放弃缓冲,一些实现转储核心。所以ncurses保持一致。但同样需要注意的是:

  • 直到late 2012(为了解决信号问题),ncurses使用与标准输出相同的缓冲区(或者输入其初始化的任何流)。
  • 从那时起,ncurses使用单独的缓冲区。有一些特殊情况,例如putp使用相同的输出缓冲,但printw使用一个单独的缓冲区,在其重绘操作(例如refresh)期间进行刷新。

在任何一种情况下,此示例的修复方法是在使用与前一次调用不同的输出流时使用fflush。这应该有效:

reset_shell_mode( );
putp( exit_ca_mode );
printf( "Don't forget... blah, blah\n" );
fflush(stdout); // added
system( external_command );
printf( "Updating, etc\n" );
putp( enter_ca_mode );
fflush(stdout); // added
reset_prog_mode( );
refresh( );