1

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

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

然后当从属进程退出时,我恢复我的诅咒显示

putp( enter_ca_mode );  // From <term.h>
reset_prog_mode( );
refresh( );

这工作正常。但是,我的程序还想在启动从属进程之前输出一些信息。它还希望在从属进程退出时但在返回完整的诅咒显示之前输出一些附加信息。因此我有(缩写):

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() 之前和之后生成的文本没有出现。我想也许它仍然会进入一些与诅咒相关的缓冲区。我不知道。

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

4

2 回答 2

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 认为不需要更新的屏幕部分,它将不理会这些部分,这意味着您的程序输出的一部分将保留。

于 2013-12-05T12:39:45.157 回答
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)将终端模式设置为“原始”(以允许在不受 I/O 缓冲干扰的情况下读取输入字符),但它设置输出缓冲(用于性能)。

它使用 的一些变体来做到这一点setvbuf,根据标准,这些变体不能可靠地关闭/打开:

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

这不仅仅是一个很好的细节。如果您尝试丢弃缓冲,某些实现会转储核心。所以 ncurses 保持一致。但同样有一点需要注意:

  • 直到2012 年底(解决信号问题),ncurses 使用与标准输出相同的缓冲区作为其输出(或任何输入其初始化的流)。
  • 从那时起,ncurses 使用单独的缓冲区。有一些特殊情况,例如putp使用相同的输出缓冲,但printw使用一个单独的缓冲区,该缓冲区在其重新绘制操作期间进行 ncurses 刷新,例如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( );
于 2016-05-14T14:44:50.060 回答