在c++中使用posix pipe()和dup()重定向I/O问题



我必须修改我为以前的家庭作业编写的一个简单的shell来处理I/O重定向,并且我在使管道工作方面遇到了麻烦。看来,当我从stdin读写stdout和复制的文件描述符分离过程后,管的工作方式,但是如果我使用任何像printf,流,,fgets,等尝试,看看输出出现在管,它会向控制台即使文件描述符stdin和stdout显然是管的一个副本(我不知道如果这是正确的短语,但是我认为关键是明确)。

我有99.9%的把握,我所做的一切至少在纯C中应该是这样的——比如在dup()之后适当地关闭所有的文件描述符——文件I/O工作得很好,所以这似乎是一个细节问题,我不知道,也找不到任何信息。我花了一天的大部分时间尝试不同的事情,过去的几个小时谷歌试图弄清楚我是否可以重定向cin和cout到管道,看看是否会解决它,但似乎它比它的价值在这一点上更多的麻烦。

应该通过重定向stdin和stdout工作,因为cin和cout应该与stdio同步?我认为它应该,特别是因为命令可能是用C编写的,所以他们会使用studio,我认为。但是,如果我尝试像"cat [file1] [file2] | sort"这样的命令,它将cat [file1] [file2]的结果打印到命令行,并且排序没有得到任何输入,因此它没有输出。同样明显的是,cout和cin也不受dup()的影响,所以我根据事实得出了这个结论这是我的代码的缩短版本减去所有的错误检查和类似的东西,我相信我处理得很好。如果需要的话,我可以发布完整的代码,但它太多了,所以我将从这个开始。

我重写了这个函数,以便父进程为每个命令派生出一个子进程,并在必要时用管道将它们连接起来,然后等待子进程死亡。同样,文件描述符0和1上的write和read工作(即写入和读取管道),stdio上的file指针stdin和stdout不起作用(不写入管道)。

非常感谢,这简直要我的命了。

更新:我没有为每个不同的命令改变字符串cmd,所以它似乎不起作用,因为管道只是去到相同的命令,所以最终输出是相同的…不好意思,但是谢谢,因为我发现了strace的问题。

int call_execv( string cmd, vector<string> &argv, int argc,
    vector<int> &redirect)
{
    int result = 0, pid, /* some other declarations */;
    bool file_in, file_out, pipe_in, pipe_out;
    queue<int*> pipes; // never has more than 2 pipes
    // parse, fork, exec, & loop if there's a pipe until no more pipes
    do
    {
        /* some declarations for variables used in parsing */
        file_in = file_out = pipe_in = pipe_out = false;
        // parse the next command and set some flags
        while( /* there's more redirection */ )
        {
            string symbol = /* next redirection symbol */
            if( symbol == ">" )
            {
                /* set flags, get filename, etc */
            }   
            else if( symbol == "<" )
            {
                /* set flags, get filename, etc */
            }
            else if( pipe_out = (symbol == "|") )
            {
                    /* set flags, and... */
                int tempPipes[2];               
                pipes.push( pipe(tempPipes) );
                break;
            }
        }
        /* ... set some more flags ... */
        // fork child
        pid = fork();
        if( pid == 0 )  // child
        {
            /* if pipe_in and pipe_out set, there are two pipes in queue. 
            the old pipes read  is dup'd to stdin, and the new pipes
            write is dup'd to stdout, other two FD's are closed */
            /* if only pipe_in or pipe_out, there is one pipe in queue.
            the unused end is closed in whichever if statement evaluates */
            /* if neither pipe_in or pipe_out is set, no pipe in queue */
            // redirect stdout
            if( pipe_out ){
                // close newest pipes read end
                close( pipes.back()[P_READ] );
                // dup the newest pipes write end
                dup2( pipes.back()[P_WRITE], STDOUT_FILENO );
                // close newest pipes write end
                close( pipes.back()[P_WRITE] );
            }
            else if( file_out ) 
                freopen(outfile.c_str(), "w", stdout);
            // redirect stdin
            if( pipe_in ){
                close( pipes.front()[P_WRITE] );
                dup2( pipes.front()[P_READ], STDIN_FILENO );
                close( pipes.front()[P_READ] );
            }
            else if ( file_in ) 
                freopen(infile.c_str(), "r", stdin);

            // create argument list and exec
            char **arglist = make_arglist( argv, start, end );
            execv( cmd.c_str(), arglist );
            cout << "Execution failed." << endl;    
            exit(-1); // this only executes is execv fails
        }   // end child
        /* close the newest pipes write end because child is writing to it.
           the older pipes write end is closed already */
        if( pipe_out ) 
            close( pipes.back()[P_WRITE] );
        // remove pipes that have been read from front of queue
        if( init_count > 0 )    
        {
            close( pipes.front()[P_READ] ); // close FD first
            pipes.pop();                    // pop from queue
        }   
    } while ( pipe_out );
    // wait for each child process to die
    return result;
}

不管是什么问题,您都没有检查任何返回值。如何知道pipe()dup2()命令是否成功?你确认stdoutstdin真的指向execv前面的管道了吗?execv保留你给它的文件描述符吗?不确定,以下是execve文档中的相应段落:

  • 默认情况下,文件描述符在execve()中保持打开状态。标记为close-on-exec的文件描述符被关闭;参见fcntl(2)中FD_CLOEXEC的描述。(如果文件描述符关闭,这将导致释放该进程在基础文件上获得的所有记录锁。详细信息请参见fcntl(2)。posix . 1的授权- 2001说如果文件描述符0、1和2在执行execve()操作成功后被关闭,那么进程将获得特权,因为设置了set-user_ID或set-group_ID在执行的文件上设置了任务位,那么系统可能会为每个文件描述符打开一个未指定的文件。作为一般原则,没有可移植程序,无论是否具有特权,都可以假设这三个文件描述符在执行()期间将保持关闭状态。

您应该添加更多的调试输出,看看到底发生了什么。你在你的节目中使用strace -f(跟踪儿童)吗?

以下内容:

queue<int*> pipes; // never has more than 2 pipes
// ...
int tempPipes[2];               
pipes.push( pipe(tempPipes) );

不应该工作。不确定它是如何编译的,因为pipe()的结果是int。只需注意,tempPipes将超出作用域,并且其内容将丢失。

应该是这样的:

struct PipeFds
{
    int fds[2];
};
std::queue<PipeFds> pipes;
PipeFds p;
pipe(p.fds); // check the return value
pipes.push(p);

最新更新