管道、dup2 和 exec()
Pipes, dup2 and exec()
我必须写一个 shell 可以 运行 管道。例如像 ls -l | wc -l
” 这样的命令。我已经成功地解析了用户给出的命令如下:
"ls" = firstcmd
"-l" = frsarg
"wc" = scmd
"-l" = secarg
现在我必须使用两个叉子,因为命令是两个和一个管道。
我为执行命令而编写的代码块如下:
pid_t pid;
int fd[2];
pipe(fd);
pid = fork();
if(pid==0)
{
dup2(fd[WRITE_END], STDOUT_FILENO);
close(fd[READ_END]);
execlp(firstcmd, firstcmd, frsarg, (char*) NULL);
}
else
{
pid=fork();
if(pid==0)
{
dup2(fd[READ_END], STDIN_FILENO);
close(fd[WRITE_END]);
execlp(scmd, scmd, secarg, (char*) NULL);
}
}
所以当我 运行 我的 shell 和我输入命令 ls -l | wc -l
(例如)时,execs 的结果没有显示,但是 shell保持 运行ning 正常。
奇怪的是,只有当我用 "exit" 或“^C”终止我的 shell 时,命令的结果才会显示。
那个输出有什么问题?为什么我输入命令后没有立即显示?
嗯,差不多了。您错过了在 fork 后处理关闭某些文件描述符的问题。
这里有一些参考:
- 关于管道,http://unixwiz.net/techtips/remap-pipe-fds.html
- 关于文件描述符,
http://www.usna.edu/Users/cs/aviv/classes/ic221/s14/lec/09/lec.html
这是我的代码:
#include <fcntl.h> //
#include <stdio.h> //
#include <stdlib.h> //
#include <string.h> //
#include <sys/types.h> //
#include <sys/wait.h> //
#include <sys/stat.h> //
#include <termios.h> //
#include <unistd.h> //
//
#define INPUT_END 1 // INPUT_END means where the pipe takes input
#define OUTPUT_END 0 // OUTPUT_END means where the pipe produces output
//
int main(int argc, char* argv[]) //
{ //
pid_t pid1; // [STDIN -> terminal_input, STDOUT -> terminal_output] (of the parent process)
pid_t pid2; //
int fd[2]; //
//
pipe(fd); // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input, fd[1] -> pipe_output]
pid1 = fork(); //
//
if(pid1==0) //
{ // I am going to be the wc process (i.e. taking input from the pipe)
close(fd[INPUT_END]); // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[1] -> pipe_output] (of the WC process)
dup2(fd[OUTPUT_END], STDIN_FILENO); // [STDIN -> pipe_output, STDOUT -> terminal_output, fd[1] -> pipe_output] (of the WC process)
close(fd[OUTPUT_END]); // [STDIN -> pipe_output, STDOUT -> terminal_output] (of the WC process)
execlp("wc", "wc", "-l",(char*) NULL); //
} //
else //
{ //
pid2=fork(); //
//
if(pid2==0) //
{ // I am going to be the ls process (i.e. producing output to the pipe)
close(fd[OUTPUT_END]); // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input] (of the ls process)
dup2(fd[INPUT_END], STDOUT_FILENO); // [STDIN -> terminal_input, STDOUT -> pipe_input, fd[0] -> pipe_input] (of the ls process)
close(fd[INPUT_END]); // [STDIN -> terminal_input, STDOUT -> pipe_input] (of the ls process)
execlp("ls","ls","-l",(char*) NULL); //
} //
//
close(fd[OUTPUT_END]); // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input] (of the parent process)
close(fd[INPUT_END]); // [STDIN -> terminal_input, STDOUT -> terminal_output] (of the parent process)
waitpid(-1, NULL, 0); // As the parent process - we wait for a process to die (-1) means I don't care which one - it could be either ls or wc
waitpid(-1, NULL, 0); // As the parent process - we wait for the another process to die.
// At this point we can safely assume both process are completed
} //
} //
您需要关闭父进程和子进程中的所有管道描述符(在子进程中复制之后)。在您的代码中,主要问题是 wc
进程不会退出,因为仍然存在编写器(因为父进程尚未关闭写入端)。变化如下所示。我还在父进程中添加了 waitpid
以等待 wc
进程。
pid_t pid;
int fd[2];
pipe(fd);
pid = fork();
if(pid==0)
{
dup2(fd[WRITE_END], STDOUT_FILENO);
close(fd[READ_END]);
close(fd[WRITE_END]);
execlp(firstcmd, firstcmd, frsarg, (char*) NULL);
fprintf(stderr, "Failed to execute '%s'\n", firstcmd);
exit(1);
}
else
{
pid=fork();
if(pid==0)
{
dup2(fd[READ_END], STDIN_FILENO);
close(fd[WRITE_END]);
close(fd[READ_END]);
execlp(scmd, scmd, secarg,(char*) NULL);
fprintf(stderr, "Failed to execute '%s'\n", scmd);
exit(1);
}
else
{
int status;
close(fd[READ_END]);
close(fd[WRITE_END]);
waitpid(pid, &status, 0);
}
}
好吧,对于类似这样的事情,一个简单而高效的工作是用管道内容制作一个脚本,然后从您的 C 代码中使用一些 exec 命令调用该脚本。
script.sh
#!/bin/sh
ls -l | wc -l
你只需在你的 C 程序中做这样的事情
char *argv[] = {"script.sh", NULL};
execv(argv[0], argv);
请注意,您必须将 script.sh 放在 C 程序的同一目录中。
我必须写一个 shell 可以 运行 管道。例如像 ls -l | wc -l
” 这样的命令。我已经成功地解析了用户给出的命令如下:
"ls" = firstcmd
"-l" = frsarg
"wc" = scmd
"-l" = secarg
现在我必须使用两个叉子,因为命令是两个和一个管道。 我为执行命令而编写的代码块如下:
pid_t pid;
int fd[2];
pipe(fd);
pid = fork();
if(pid==0)
{
dup2(fd[WRITE_END], STDOUT_FILENO);
close(fd[READ_END]);
execlp(firstcmd, firstcmd, frsarg, (char*) NULL);
}
else
{
pid=fork();
if(pid==0)
{
dup2(fd[READ_END], STDIN_FILENO);
close(fd[WRITE_END]);
execlp(scmd, scmd, secarg, (char*) NULL);
}
}
所以当我 运行 我的 shell 和我输入命令 ls -l | wc -l
(例如)时,execs 的结果没有显示,但是 shell保持 运行ning 正常。
奇怪的是,只有当我用 "exit" 或“^C”终止我的 shell 时,命令的结果才会显示。
那个输出有什么问题?为什么我输入命令后没有立即显示?
嗯,差不多了。您错过了在 fork 后处理关闭某些文件描述符的问题。
这里有一些参考:
- 关于管道,http://unixwiz.net/techtips/remap-pipe-fds.html
- 关于文件描述符, http://www.usna.edu/Users/cs/aviv/classes/ic221/s14/lec/09/lec.html
这是我的代码:
#include <fcntl.h> //
#include <stdio.h> //
#include <stdlib.h> //
#include <string.h> //
#include <sys/types.h> //
#include <sys/wait.h> //
#include <sys/stat.h> //
#include <termios.h> //
#include <unistd.h> //
//
#define INPUT_END 1 // INPUT_END means where the pipe takes input
#define OUTPUT_END 0 // OUTPUT_END means where the pipe produces output
//
int main(int argc, char* argv[]) //
{ //
pid_t pid1; // [STDIN -> terminal_input, STDOUT -> terminal_output] (of the parent process)
pid_t pid2; //
int fd[2]; //
//
pipe(fd); // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input, fd[1] -> pipe_output]
pid1 = fork(); //
//
if(pid1==0) //
{ // I am going to be the wc process (i.e. taking input from the pipe)
close(fd[INPUT_END]); // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[1] -> pipe_output] (of the WC process)
dup2(fd[OUTPUT_END], STDIN_FILENO); // [STDIN -> pipe_output, STDOUT -> terminal_output, fd[1] -> pipe_output] (of the WC process)
close(fd[OUTPUT_END]); // [STDIN -> pipe_output, STDOUT -> terminal_output] (of the WC process)
execlp("wc", "wc", "-l",(char*) NULL); //
} //
else //
{ //
pid2=fork(); //
//
if(pid2==0) //
{ // I am going to be the ls process (i.e. producing output to the pipe)
close(fd[OUTPUT_END]); // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input] (of the ls process)
dup2(fd[INPUT_END], STDOUT_FILENO); // [STDIN -> terminal_input, STDOUT -> pipe_input, fd[0] -> pipe_input] (of the ls process)
close(fd[INPUT_END]); // [STDIN -> terminal_input, STDOUT -> pipe_input] (of the ls process)
execlp("ls","ls","-l",(char*) NULL); //
} //
//
close(fd[OUTPUT_END]); // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input] (of the parent process)
close(fd[INPUT_END]); // [STDIN -> terminal_input, STDOUT -> terminal_output] (of the parent process)
waitpid(-1, NULL, 0); // As the parent process - we wait for a process to die (-1) means I don't care which one - it could be either ls or wc
waitpid(-1, NULL, 0); // As the parent process - we wait for the another process to die.
// At this point we can safely assume both process are completed
} //
} //
您需要关闭父进程和子进程中的所有管道描述符(在子进程中复制之后)。在您的代码中,主要问题是 wc
进程不会退出,因为仍然存在编写器(因为父进程尚未关闭写入端)。变化如下所示。我还在父进程中添加了 waitpid
以等待 wc
进程。
pid_t pid;
int fd[2];
pipe(fd);
pid = fork();
if(pid==0)
{
dup2(fd[WRITE_END], STDOUT_FILENO);
close(fd[READ_END]);
close(fd[WRITE_END]);
execlp(firstcmd, firstcmd, frsarg, (char*) NULL);
fprintf(stderr, "Failed to execute '%s'\n", firstcmd);
exit(1);
}
else
{
pid=fork();
if(pid==0)
{
dup2(fd[READ_END], STDIN_FILENO);
close(fd[WRITE_END]);
close(fd[READ_END]);
execlp(scmd, scmd, secarg,(char*) NULL);
fprintf(stderr, "Failed to execute '%s'\n", scmd);
exit(1);
}
else
{
int status;
close(fd[READ_END]);
close(fd[WRITE_END]);
waitpid(pid, &status, 0);
}
}
好吧,对于类似这样的事情,一个简单而高效的工作是用管道内容制作一个脚本,然后从您的 C 代码中使用一些 exec 命令调用该脚本。
script.sh
#!/bin/sh
ls -l | wc -l
你只需在你的 C 程序中做这样的事情
char *argv[] = {"script.sh", NULL};
execv(argv[0], argv);
请注意,您必须将 script.sh 放在 C 程序的同一目录中。