操纵标准输出:获得控制并过滤它

时间:2013-02-05 22:46:58

标签: stdout ncurses io-redirection

我已阅读this stackoverflow question,我在重定向stdout方面取得了成功。 但是所提到的代码的工作方式,我完全松开了我的终端,这意味着我无法在其上打印任何内容。

我想要实现的是使用ncurses格式化我的程序的输出(将使用我的一些代码和一些我无法修改的库),并且有一个窗口,其中任何虚假的printf将显示。

有没有办法实现这个目标?

    saved_stdout = dup(STDOUT_FILENO);  /* save stdout for display later $

    if( pipe(out_pipe) != 0 ) {          /* make a pipe */
        exit(1);
    }

    dup2(out_pipe[1], STDOUT_FILENO);   /* redirect stdout to the pipe */
    close(out_pipe[1]);

    initscr();                      /* Start curses mode            */

这样我就无法在屏幕上打印任何内容。

1 个答案:

答案 0 :(得分:0)

为了实现我想要的我必须解决一个问题:默认情况下所有输出函数都使用STDOUT,对于大多数输出​​函数,我无法改变这种行为(想想一个lib中的embbeded printf我没有或者为自己辩护的原因。

因此,如果你修改STDOUT,一切都会受到影响。

但我希望我的程序能够保持对STDOUT的控制。 所以你不得不分开得到真正的STDOUT和什么得到 STDOUT。 分开的声音很像叉子...... 所以分叉吧。

基本上,您将子程序中的所有程序的大部分分叉,并为其修改STDOUT管道。 父进程保持对STDOUT的控制,并且能够读取管道并对其中的数据执行任何操作。

#include <ncurses.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <iostream>
#include <sstream>
#define MAX_LEN 99

WINDOW *create_newwin(int height, int width, int starty, int startx, int vert, int horz);

char buffer[MAX_LEN+1] = {0};
int out_pipe[2];
int saved_stdout;

volatile sig_atomic_t s_interrupted = 0;
volatile sig_atomic_t alarm_expired = 0;


static void s_signal_handler (int signal_value)
{
    if (signal_value == SIGTERM || signal_value == SIGSTOP || signal_value == SIGINT)
        s_interrupted = 1;
    if (signal_value == SIGALRM)
        //signal(SIGALRM,alarm_wakeup);
        alarm_expired = 1;
}

static void s_catch_signals (void)
{
    struct sigaction action;
    action.sa_handler = s_signal_handler;
    action.sa_flags = 0;
    sigemptyset (&action.sa_mask);
    sigaction (SIGINT, &action, NULL);
    sigaction (SIGTERM, &action, NULL);
    sigaction (SIGALRM, &action, NULL);
}

int main (int arg, char **argv) {

    const char mesg[]="Testing stdout redirection";
    WINDOW *my_win, *my_win2;
    int cycle = 0, pid, p[2], row, col;

if(pipe(p) == -1)
{
    perror("pipe call error");
    return(1);
}

    switch (pid=fork())
    {
        case -1: perror("error: fork call");
            return(2);

        case 0:  /* if child then write down pipe */
            if(dup2(p[1], 1) == -1 ) /* stdout == write end of the pipe */
            {
                perror( "dup2 failed" );
                return(1);
            }
            s_catch_signals ();
            setvbuf(stdout, NULL, _IOLBF, 1000);
            while(!s_interrupted)
            {
                printf("Test %d\n",cycle);
                cycle++;
                usleep(10000);
            }
            printf("\n\tChild quitting cleanly\n\n");
            break;
        default:
            s_catch_signals ();
            std::string mystr;
            if(dup2(p[0], 0 ) == -1 ) /* stdin == read end of the pipe */
            {
                perror( "dup2 failed" );
                return(1);
            }
            initscr();              /* start the curses mode */
            getmaxyx(stdscr,row,col);       /* get the number of rows and columns */
            mvprintw(row/2,(col-strlen(mesg))/2,"%s",mesg);
                            /* print the message at the center of the screen */

            refresh();
            my_win2 = create_newwin(10,col,row-10, 0, 0, 0);    
            wrefresh(my_win2);
            my_win = create_newwin(8,col-2,row-9, 1, ' ', ' ');
            wrefresh(my_win);
            wprintw(my_win,"This screen has %d rows and %d columns\n",row,col);
            wprintw(my_win,"Try resizing your window(if possible) and then run this program again\n");
            scrollok(my_win,TRUE);
            int myy,myx;
            std::stringstream ss;
            while(!s_interrupted)
            {
                while( std::getline(std::cin, mystr) )
                {
                    ss.clear();
                    ss << mystr << std::endl;
                    mystr = ss.str();
                    waddstr(my_win,mystr.c_str());
                    wrefresh(my_win);
                }
                usleep(100000);

            }
            endwin();


            break;
    }
    endwin();
    printf("\n\tProgram quitting cleanly\n\n");
    return (EXIT_SUCCESS);
}

WINDOW *create_newwin(int height, int width, int starty, int startx, int vert, int horz)
{   WINDOW *local_win;

    local_win = newwin(height, width, starty, startx);
    box(local_win, vert , horz);        /* 0, 0 gives default characters 
                     * for the vertical and horizontal
                     * lines            */
    wrefresh(local_win);        /* Show that box        */

    return local_win;
}

此程序正好显示了这种行为:主要产生一个子节点,它将自己的STDOUT重定向到一个管道。 然后父管读取该管道,并在受控窗口内输出管道。 屏幕的其余部分可以由父母自由操作,但这是必要的。

switch (pid=fork())
{
    case 0:  /* if child then write down pipe */
        if(dup2(p[1], 1) == -1 ) /* stdout == write end of the pipe */
        {
            perror( "dup2 failed" );
            return(1);
        }

当调用fork时,如果我们是子节点(pid == 0),则取出之前创建的管道,并将我们自己的STDOUT切换到该管道的写入端。

setvbuf(stdout, NULL, _IOLBF, 1000);

将缓冲模式更改为line,以便我们的父级可以更轻松地抓取它。

while(!s_interrupted)
{
    printf("Test %d\n",cycle);
    cycle++;
    usleep(10000);
}

生成一些简单的文本以检查它是否有效。

    switch (pid=fork())
    {
        default:
        if(dup2(p[0], 0 ) == -1 ) /* stdin == read end of the pipe */
        {
            perror( "dup2 failed" );
            return(1);
        }

如果我们是父节点,请将STDIN设置为我们之前创建的管道的读取端。

    initscr();              /* start the curses mode */
    getmaxyx(stdscr,row,col);       /* get the number of rows and columns */
    mvprintw(row/2,(col-strlen(mesg))/2,"%s",mesg);
                    /* print the message at the center of the screen */

    refresh();
    my_win2 = create_newwin(10,col,row-10, 0, 0, 0);    
    wrefresh(my_win2);
    my_win = create_newwin(8,col-2,row-9, 1, ' ', ' ');
    wrefresh(my_win);
    wprintw(my_win,"This screen has %d rows and %d columns\n",row,col);
    wprintw(my_win,"Try resizing your window(if possible) and then run this program again\n");
    scrollok(my_win,TRUE);

初始化ncurses并创建一些窗口。 我们使用my_win2绘制边框,而无边界的my_win实际包含数据。 此外,我们将my_win设置为自动滚动,以便我们可以继续打印它,而不必担心空间不足。

        while( std::getline(std::cin, mystr) )
        {
            ss.clear();
            ss << mystr << std::endl;
            mystr = ss.str();
            waddstr(my_win,mystr.c_str());
            wrefresh(my_win);
        }
        usleep(100000);

在一个轻松的节奏循环中,我们检查我们的STDIN,如果我们有新线,我们只需将它们添加到我们的滚动窗口。 因此,当我们的进程被安排在活动状态时,我们会花时间处理大量输出而不是重复轮询。

这证明是提出问题的有效解决方案。