我已经构建了以下程序来尝试管道我自己的shell。 StringArray
只是我构建的char**
。代码运行正常,但是当我输入cat txt.txt | grep a
时,没有任何内容打印回屏幕。调试时,我看到代码似乎停止了152(打印输出命令所在的位置),其中pid==0
和i==0
。
对于上下文,我在检测到管道后在另一个函数中调用此函数。
void doPipe(StringArray sa)
{
printf("In 69\n");
int filedes[2]; // pos. 0 output, pos. 1 input of the pipe
int filedes2[2];
int num_cmds = 0;
char *command[256];
pid_t pid;
int err = -1;
int end = 0;
// Variables used for the different loops
int i = 0;
int j = 0;
int k = 0;
int l = 0;
// First we calculate the number of commands (they are separated
// by '|')
while (sa[l] != NULL){
if (strcmp(sa[l],"|") == 0){
num_cmds++;
}
l++;
}
num_cmds++;
// Main loop of this method. For each command between '|', the
// pipes will be configured and standard input and/or output will
// be replaced. Then it will be executed
while (sa[j] != NULL && end != 1){
k = 0;
// We use an auxiliary array of pointers to store the command
// that will be executed on each iteration
while (strcmp(sa[j],"|") != 0){
command[k] = sa[j];
j++;
if (sa[j] == NULL){
// 'end' variable used to keep the program from entering
// again in the loop when no more arguments are found
end = 1;
k++;
break;
}
k++;
}
// Last position of the command will be NULL to indicate that
// it is its end when we pass it to the exec function
command[k] = NULL;
j++;
printf("In 121\n");
// Depending on whether we are in an iteration or another, we
// will set different descriptors for the pipes inputs and
// output. This way, a pipe will be shared between each two
// iterations, enabling us to connect the inputs and outputs of
// the two different commands.
if (i % 2 != 0){
pipe(filedes); // for odd i
}else{
pipe(filedes2); // for even i
}
pid=fork();
if(pid==-1){
if (i != num_cmds - 1){
if (i % 2 != 0){
close(filedes[1]); // for odd i
}else{
close(filedes2[1]); // for even i
}
}
printf("Child process could not be created\n");
return;
}
if(pid==0){
printf("In 148\n");
// If we are in the first command
if (i == 0){
printf("In 152\n");
dup2(filedes2[1], STDOUT_FILENO);
}
// If we are in the last command, depending on whether it
// is placed in an odd or even position, we will replace
// the standard input for one pipe or another. The standard
// output will be untouched because we want to see the
// output in the terminal
else if (i == num_cmds - 1){
printf("In 162\n");
if (num_cmds % 2 != 0){ // for odd number of commands
dup2(filedes[0],STDIN_FILENO);
printf("In 166\n");
}else{ // for even number of commands
dup2(filedes2[0],STDIN_FILENO);
printf("In 166\n");
}
// If we are in a command that is in the middle, we will
// have to use two pipes, one for input and another for
// output. The position is also important in order to choose
// which file descriptor corresponds to each input/output
}else{ // for odd i
if (i % 2 != 0){
dup2(filedes2[0],STDIN_FILENO);
dup2(filedes[1],STDOUT_FILENO);
}else{ // for even i
dup2(filedes[0],STDIN_FILENO);
dup2(filedes2[1],STDOUT_FILENO);
}
}
if (execvp(command[0],command)==err){
kill(getpid(),SIGTERM);
}
}
// CLOSING DESCRIPTORS ON PARENT
if (i == 0){
close(filedes2[1]);
}
else if (i == num_cmds - 1){
if (num_cmds % 2 != 0){
close(filedes[0]);
}else{
close(filedes2[0]);
}
}else{
if (i % 2 != 0){
close(filedes2[0]);
close(filedes[1]);
}else{
close(filedes[0]);
close(filedes2[1]);
}
}
waitpid(pid,NULL,0);
i++;
}
}
答案 0 :(得分:4)
您的一个重大问题可能是在管道构造的每次迭代中执行waitpid
。等待应该在结束时完成(记住列表中的pids)。
我在理解你的代码时遇到了一些困难,所以我做了一些简化和清理工作。特别是,在任何地方做if (i % 2 ...)
都会让事情变得更难。
我已经清理并修复了代码。我添加了一个结构,以便更容易管理[请原谅无偿的样式清理]:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
typedef struct {
int pipe_fildes[2];
} pipectl_t;
#define CLOSEME(_fd) \
do { \
close(_fd); \
_fd = -1; \
} while (0)
void
doPipe(char **sa)
{
pipectl_t pipes[2];
pipectl_t *pipein;
pipectl_t *pipeout;
pipectl_t *pipetmp;
int num_cmds = 0;
char *command[256];
pid_t pidlist[256];
pid_t pid;
int err = -1;
int end = 0;
// Variables used for the different loops
int icmd = 0;
int j = 0;
int k = 0;
int l = 0;
// First we calculate the number of commands (they are separated
// by '|')
for (int l = 0; sa[l] != NULL; ++l) {
if (strcmp(sa[l], "|") == 0)
num_cmds++;
}
num_cmds++;
for (int ipipe = 0; ipipe <= 1; ++ipipe) {
pipes[ipipe].pipe_fildes[0] = -1;
pipes[ipipe].pipe_fildes[1] = -1;
}
pipein = &pipes[0];
pipeout = &pipes[1];
// Main loop of this method. For each command between '|', the
// pipes will be configured and standard input and/or output will
// be replaced. Then it will be executed
while (sa[j] != NULL && end != 1) {
// We use an auxiliary array of pointers to store the command
// that will be executed on each iteration
k = 0;
while (strcmp(sa[j], "|") != 0) {
command[k] = sa[j];
j++;
k++;
if (sa[j] == NULL) {
// 'end' variable used to keep the program from entering
// again in the loop when no more arguments are found
end = 1;
break;
}
}
// Last position of the command will be NULL to indicate that
// it is its end when we pass it to the exec function
command[k] = NULL;
j++;
// swap input and output, so previous child's output becomes the new
// child's input
// NOTE: by doing this here, in one place, we eliminate all the i % 2
// if statements
pipetmp = pipein;
pipein = pipeout;
pipeout = pipetmp;
// are we the last command?
int lastflg = (icmd == (num_cmds - 1));
// last command does _not_ have an output pipe, so don't create one
if (! lastflg)
pipe(pipeout->pipe_fildes);
pid = fork();
// NOTE: fork failure almost never happens and is fatal
if (pid == -1) {
printf("Child process could not be created\n");
return;
}
// process child
if (pid == 0) {
// NOTE: after we've dup'ed a file descriptor, we close it
// first command does _not_ have a pipe for input
if (icmd > 0)
dup2(pipein->pipe_fildes[0],STDIN_FILENO);
CLOSEME(pipein->pipe_fildes[0]);
// last command does _not_ have a pipe for output
if (! lastflg)
dup2(pipeout->pipe_fildes[1],STDOUT_FILENO);
CLOSEME(pipeout->pipe_fildes[1]);
// close the parent sides of the pipes (in this child)
// close previous child's output descriptor (the feed for our input)
CLOSEME(pipein->pipe_fildes[1]);
// close next child's input descriptor (our feed for its input)
CLOSEME(pipeout->pipe_fildes[0]);
if (execvp(command[0], command) == err) {
#if 0
kill(getpid(), SIGTERM);
#else
exit(1);
#endif
}
}
// close all input descriptors for _this_ child
CLOSEME(pipein->pipe_fildes[0]);
CLOSEME(pipein->pipe_fildes[1]);
// close output side of _this_ child's output pipe [which becomes next
// child's input pipe]
CLOSEME(pipeout->pipe_fildes[1]);
pidlist[icmd] = pid;
icmd++;
}
// wait for all pids _after_ the entire pipeline is constructed
for (int icmd = 0; icmd < num_cmds; ++icmd)
waitpid(pidlist[icmd], NULL, 0);
}
// main -- main program
int
main(int argc,char **argv)
{
char *cp;
char *bp;
char buf[1000];
char **av;
char *avlist[256];
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
default:
break;
}
}
while (1) {
printf("> ");
fflush(stdout);
cp = fgets(buf,sizeof(buf),stdin);
if (cp == NULL)
break;
av = avlist;
bp = buf;
while (1) {
cp = strtok(bp," \t\r\n");
bp = NULL;
if (cp == NULL)
break;
*av++ = cp;
}
*av = NULL;
doPipe(avlist);
}
return 0;
}
<强>更新强>
当我运行此代码时,相同的命令
cat txt.txt | grep a
仅显示为执行第一个命令,而不是管道之后的第二个命令。 (它在txt文件中输出但没有grep)
我在发布之前测试了整个程序。我只是使用cat/grep
命令重新测试。它奏效了,但这是我的计划不变。
为什么会发生这种情况的任何想法?我在我的代码中实现了你的doPipe方法,并传入了我的StringArray sa,它也只是一个char **。
我的建议是:
gdb
上使用doPipe
断点并查看参数。对于这两个程序,它们应该是相同的。StringArray
确实是char **
,请在您的版本中将其替换,以确保它没有任何区别。那是void doPipe(char **sa)
并查看您的代码是否仍然编译。在断点处的gdb
中,您应该可以在两个程序上执行ptype sa
StringArray
看起来有点&#34; Java-esque&#34;对我来说:-)我避免使用它,特别是因为execvp
需要char **
sa
已正确NULL
终止。如果它不是管道中的最后一个命令可能是假的/垃圾,那么对失败的execvp
的错误检查不是那么健全。num_cmds
是否相同。cat txt.txt | grep a | sed -e s/a/b/
。如果您获得cat
和grep
,而不是sed
,则表示num_cmds
不正确"|"
置于单独的标记中。也就是说,此代码适用于cat txt.txt | grep a
,但不可以使用:cat txt.txt|grep a
更新#2:
顺便说一句,如果您的管道代码仍然无效(例如,最后一个命令未执行),请检查最后一个令牌是否有换行符(即换行符不是&# 39;正确剥离。我已经尝试了所有这些,但仍然无法使用我的重定向代码来解决这个问题。从本质上讲,我对此代码中应该检查的位置感到困惑&#39;&lt;&#39;或&#39;&gt;&#39;
进行常规解析以支持重定向(例如<
或>
),管道(例如|
),每行多个命令(例如;
),嵌入式子-shells(例如(echo the date is ; date)
和分离的工作(例如&
)可能需要一些小心,你需要一个多层次的方法。
我怀疑在你获得管道和/或重定向工作之后,你的任务就是实现更多的shell语法。我以前做过这件事,所以,不是你把它弄得零碎,这就是你需要做的......
您需要扫描输入缓冲区char-by-char并将令牌保存到&#34;令牌&#34;也有类型的struct。您需要这些结构的链表。更多内容如下。
当您遇到带引号的字符串时,您需要删除引号:"abc"
- &gt; abc
,注意转义引号:"ab\"c
- &gt; ab"c
。
另外,你必须要小心所谓的字符串,这些字符串与[perl
调用]&#34;&#34; bareword&#34;字符串:echo abc
。如果我们有abc"d ef"ghi
,则需要将其连接成一个字符串标记:abcd efghi
还必须考虑重定向器上的反斜杠。 echo abc > def
是一个重定向,可将abc
放入文件def
。但是,echo abc \> def
应该直接输出abc > def
到stdout。另一方面反斜杠&#34;标点符号&#34;很相似。
您还必须处理标点符号 周围有空格的事实。也就是说,echo abc>def
必须像处理echo abc > def
一样处理。
另外,引用字符串中的标点符号应该被视为上面的转义。也就是说,echo abc ">" def
不重定向,[再次]应该被视为一个简单的命令。
此外,如果当前行以反斜杠结束(例如\<newline>
),这意味着下一行是&#34;继续&#34;线。你应该删除反斜杠和换行符。然后,读取另一行并继续构建令牌列表。
此外,虽然&
可以用于分离的作业,例如:date &
,但它也可以是重定向的一部分,如gcc -o myshell myshell.c 2>&1 >logfile
好的,为了管理所有这些,我们需要令牌和令牌结构的类型:
// token types
typedef enum {
TOKEN_NORMAL, // simple token/string
TOKEN_QUO1, // quoted string
TOKEN_QUO2, // quoted string
TOKEN_SEMI, // command separater (e.g. ;)
TOKEN_OREDIR, // output redirector (e.g. >)
TOKEN_IREDIR, // input redirector (e.g. <)
TOKEN_PIPE, // pipe separater (e.g. |)
TOKEN_AMP // an & (can be detach or redirect)
} toktype_t;
// token control
typedef struct token token_t;
struct token {
token_t *tok_next; // forward link
token_t *tok_prev; // backward link
toktype_t tok_type; // token type
char tok_str[256]; // token value
};
// token list
typedef struct tlist tlist_t;
struct token {
tlist_t *tlist_next; // forward link
tlist_t *tlist_prev; // backward link
token_t *tlist_head; // pointer to list head
token_t *tlist_tail; // pointer to list tail
};
最初,在解析输入行[注意延续]之后,我们只有一个tlist
。
如果列表中包含;
个分隔符,我们会将它们拆分为创建子列表。然后我们循环访问子列表并按顺序执行命令。
查看子命令时,如果它以&
结尾,则必须以分离方式运行该命令。我们注意到并将其从列表背面弹出。
好的,现在我们有一个可能是以下形式的列表:
cat < /etc/passwd | grep root | sed -e s/root/admin/ > /tmp/out
现在,我们对|
进行了进一步拆分,因此我们有一个包含三个元素的列表:
cat < /etc/passwd
grep root
sed -e s/root/admin/ > /tmp/out
实际上,每个&#34;线&#34;是tlist
,这是列表的二维列表:
list_of_tlists:
|
|
tlist[0] --> cat --> < --> /etc/passwd
|
|
tlist[1] --> grep --> root
|
|
tlist[2] --> sed --> -e --> s/root/admin/ --> > /tmp/out
在我们创建管道时,我们会注意重定向,并根据需要执行open
而不是pipe
。
好的,这是摘要。
请在此处查看我的答案:Implementing input/output redirection in a Linux shell using C以获得完整且完整的实施。
在该页面上,有代码可以进行重定向。它可能适用于通过将代码与我在此处发布的代码合并来包含管道。
OP要求帮助进行重定向和管道。
旁注:当时,有一连串的shell实施问题。所以,我最终制作了一个完整的shell,可以完成所有的工作。但是,该版本太大而无法在SO上发布。因此,在该页面中,找到我发布的pastebin链接。它有完整的源代码。它可以下载,构建和运行。
您可能不想直接使用那个代码,但它应该会给您一些想法。此外,完整版可能会做的事情与我上面描述的有所不同。