【问题标题】:How to implement &> and &>> in a shell program?如何在 shell 程序中实现 &> 和 &>>?
【发布时间】:2014-03-23 00:30:01
【问题描述】:

我正在实现自己的 shell,并且我已经设法让 I/O 重定向与管道一起工作。但是,我无法理解我应该如何重定向 stderr 以便我可以在我的代码中合并 >&>>& 功能。

另外,实现|&的逻辑是否遵循?

这是我的代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void execute(char **, int, char **);
void handle_signal(int);
int parse(char *, char **, char **, int *);
void chop(char *);

#define INPUT_STRING_SIZE 80

#define NORMAL              00
#define OUTPUT_REDIRECTION  11
#define INPUT_REDIRECTION   22
#define PIPELINE            33
#define BACKGROUND          44
#define OUTPUT_APP  55

typedef void (*sighandler_t)(int);

int main(int argc, char *argv[])
{
    int i, mode = NORMAL, cmdArgc;
    size_t len = INPUT_STRING_SIZE;
    char *cpt, *inputString, *cmdArgv[INPUT_STRING_SIZE], *supplement = NULL;
    inputString = (char*)malloc(sizeof(char)*INPUT_STRING_SIZE);

    char curDir[100];

    while(1)
    {
        mode = NORMAL;
        getcwd(curDir, 100);
        printf("%s@%s->", getlogin(),curDir);
        getline( &inputString, &len, stdin);
        if(strcmp(inputString, "exit\n") == 0)
            exit(0);
        cmdArgc = parse(inputString, cmdArgv, &supplement, &mode);
        if(strcmp(*cmdArgv, "cd") == 0)
        {
            chdir(cmdArgv[1]);
        }
        else 
            execute(cmdArgv, mode, &supplement);
    }
    return 0;
}

int parse(char *inputString, char *cmdArgv[], char **supplementPtr, int *modePtr)
{
    int cmdArgc = 0, terminate = 0;
    char *srcPtr = inputString;
    //printf("parse fun%sends", inputString);
    while(*srcPtr != '\0' && terminate == 0)
    {
        *cmdArgv = srcPtr;
        cmdArgc++;
        //printf("parse fun2%sends", *cmdArgv);
        while(*srcPtr != ' ' && *srcPtr != '\t' && *srcPtr != '\0' && *srcPtr != '\n' && terminate == 0)
        {
            switch(*srcPtr)
            {
                case '&':
                    *modePtr = BACKGROUND;
                    break;
                case '>':
                    *modePtr = OUTPUT_REDIRECTION;
                    *cmdArgv = '\0';
                    srcPtr++;
                    if(*srcPtr == '>')
                    {
                        *modePtr = OUTPUT_APP;
                        srcPtr++;
                    }
                    while(*srcPtr == ' ' || *srcPtr == '\t')
                        srcPtr++;
                    *supplementPtr = srcPtr;
                    chop(*supplementPtr);
                    terminate = 1;
                    break;
                case '<':
                    *modePtr = INPUT_REDIRECTION;
                    *cmdArgv = '\0';
                    srcPtr++;
                    while(*srcPtr == ' ' || *srcPtr == '\t')
                        srcPtr++;
                    *supplementPtr = srcPtr;
                    chop(*supplementPtr);
                    terminate = 1;
                    break;
                case '|':
                    *modePtr = PIPELINE;
                    *cmdArgv = '\0';
                    srcPtr++;
                    while(*srcPtr == ' ' || *srcPtr == '\t')
                        srcPtr++;
                    *supplementPtr = srcPtr;
                    //chop(*supplementPtr);
                    terminate = 1;
                    break;
            }
            srcPtr++;
        }
        while((*srcPtr == ' ' || *srcPtr == '\t' || *srcPtr == '\n') && terminate == 0)
        {
            *srcPtr = '\0';
            srcPtr++;
        }
        cmdArgv++;
    }
    /*srcPtr++;
    *srcPtr = '\0';
    destPtr--;*/
    *cmdArgv = '\0';
    return cmdArgc;
}

void chop(char *srcPtr)
{
    while(*srcPtr != ' ' && *srcPtr != '\t' && *srcPtr != '\n')
    {
        srcPtr++;
    }
    *srcPtr = '\0';
}

void execute(char **cmdArgv, int mode, char **supplementPtr)
{
    pid_t pid, pid2;
    FILE *fp;
    int mode2 = NORMAL, cmdArgc, status1, status2;
    char *cmdArgv2[INPUT_STRING_SIZE], *supplement2 = NULL;
    int myPipe[2];
    if(mode == PIPELINE)
    {
        if(pipe(myPipe))                    //create pipe
        {
            fprintf(stderr, "Pipe failed!");
            exit(-1);
        }
        parse(*supplementPtr, cmdArgv2, &supplement2, &mode2);
    }
    pid = fork();
    if( pid < 0)
    {
        printf("Error occured");
        exit(-1);
    }
    else if(pid == 0)
    {
        switch(mode)
        {
            case OUTPUT_REDIRECTION:
                fp = fopen(*supplementPtr, "w+");
                dup2(fileno(fp), 1);
                break;
            case OUTPUT_APP:
                fp = fopen(*supplementPtr, "a");
                dup2(fileno(fp), 1);
                break;
            case INPUT_REDIRECTION:
                fp = fopen(*supplementPtr, "r");
                dup2(fileno(fp), 0);
                break;
            case PIPELINE:
                close(myPipe[0]);       //close input of pipe
                dup2(myPipe[1], fileno(stdout));
                close(myPipe[1]);
                break;
        }
        execvp(*cmdArgv, cmdArgv);
    }
    else
    {
        if(mode == BACKGROUND)
            ;
        else if(mode == PIPELINE)
        {
            waitpid(pid, &status1, 0);      //wait for process 1 to finish
            pid2 = fork();
            if(pid2 < 0)
            {
                printf("error in forking");
                exit(-1);
            }
            else if(pid2 == 0)
            {
                close(myPipe[1]);       //close output to pipe
                dup2(myPipe[0], fileno(stdin));
                close(myPipe[0]);
                execvp(*cmdArgv2, cmdArgv2);
            }
            else
            {
                ;//wait(NULL);
                //waitpid(pid, &status1, 0);
                //waitpid(pid2, &status2, 0);
                close(myPipe[0]);
                close(myPipe[1]);
            }
        }
        else
            waitpid(pid, &status1, 0);
            //wait(NULL);
    }
}

任何帮助将不胜感激!

好的,根据 Jonathan Leffler 的建议,我已修改我的代码以包含重定向。现在我想看看我是否可以通过尝试让用户输入“>&”而不是“&>”来改变它,并且仍然实现“>&”的功能。 “&>>”和“&|”也是如此。

但是,当我尝试发出命令时,说 echo hello >& a.txt,我得到一个名为“&”的文件,该文件现在包含字符串 hello!我不确定这里出了什么问题。 @JonathanLeffler - 你能看看并建议我可能做错了什么吗?这是更新的代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>


void execute(char **, int, char **);
void handle_signal(int);
int parse(char *, char **, char **, int *);
void chop(char *);

#define INPUT_STRING_SIZE 80

#define NORMAL              00
#define OUTPUT_REDIRECTION  11
#define INPUT_REDIRECTION   22
#define PIPELINE            33
#define BACKGROUND          44
#define OUTPUT_APP  55
#define OUTPUT_REDIRECTION_WITH_ERROR 66
#define OUTPUT_REDIRECTION_WITH_APPEND_ERROR 77
#define PIPELINE_WITH_ERROR 88


typedef void (*sighandler_t)(int);



int main(int argc, char *argv[])
{
    int i, mode = NORMAL, cmdArgc;
    size_t len = INPUT_STRING_SIZE;
    char *cpt, *inputString, *cmdArgv[INPUT_STRING_SIZE], *supplement = NULL;
    inputString = (char*)malloc(sizeof(char)*INPUT_STRING_SIZE);

    char curDir[100];

    while(1)
    {
        mode = NORMAL;
        getcwd(curDir, 100);
        printf("%s@%s->", getlogin(),curDir);
        getline( &inputString, &len, stdin);
        if(strcmp(inputString, "exit\n") == 0)
            exit(0);
        cmdArgc = parse(inputString, cmdArgv, &supplement, &mode);
        if(strcmp(*cmdArgv, "cd") == 0)
        {
            chdir(cmdArgv[1]);
        }
        else 
            execute(cmdArgv, mode, &supplement);
    }
    return 0;
}

int parse(char *inputString, char *cmdArgv[], char **supplementPtr, int *modePtr)
{
    int cmdArgc = 0, terminate = 0;
    char *srcPtr = inputString;
    //printf("parse fun%sends", inputString);
    while(*srcPtr != '\0' && terminate == 0)
    {
        *cmdArgv = srcPtr;
        cmdArgc++;
        //printf("parse fun2%sends", *cmdArgv);
        while(*srcPtr != ' ' && *srcPtr != '\t' && *srcPtr != '\0' && *srcPtr != '\n' && terminate == 0)
        {
            switch(*srcPtr)
            {
            /*  case '&':
                    *modePtr = BACKGROUND;
                    break; */
                case '>':
                    *modePtr = OUTPUT_REDIRECTION;
                    *cmdArgv = '\0';
                    srcPtr++;
                    if(*srcPtr == '>')
                    {
                        *modePtr = OUTPUT_APP;
                        srcPtr++;
                    }
                    else if(*srcPtr == '>&')
                    {
                        *modePtr = OUTPUT_REDIRECTION_WITH_ERROR;
                        srcPtr++;
                    }
                    else if(*srcPtr == '>>&')
                    {
                        *modePtr = OUTPUT_REDIRECTION_WITH_APPEND_ERROR;
                        srcPtr++;
                    }
                    while(*srcPtr == ' ' || *srcPtr == '\t')
                        srcPtr++;
                    *supplementPtr = srcPtr;
                    chop(*supplementPtr);
                    terminate = 1;
                    break;
                case '<':
                    *modePtr = INPUT_REDIRECTION;
                    *cmdArgv = '\0';
                    srcPtr++;
                    while(*srcPtr == ' ' || *srcPtr == '\t')
                        srcPtr++;
                    *supplementPtr = srcPtr;
                    chop(*supplementPtr);
                    terminate = 1;
                    break;
                case '|':
                    *modePtr = PIPELINE;
                    *cmdArgv = '\0';
                    srcPtr++;
                    if(*srcPtr == '|')
                    {
                        *modePtr = PIPELINE;
                        srcPtr++;
                    }
                    else if(*srcPtr == '|&')
                    {
                        *modePtr = PIPELINE_WITH_ERROR;
                        srcPtr++;
                    }
                    while(*srcPtr == ' ' || *srcPtr == '\t')
                        srcPtr++;
                    *supplementPtr = srcPtr;
                    //chop(*supplementPtr);
                    terminate = 1;
                    break;
            }
            srcPtr++;
        }
        while((*srcPtr == ' ' || *srcPtr == '\t' || *srcPtr == '\n') && terminate == 0)
        {
            *srcPtr = '\0';
            srcPtr++;
        }
        cmdArgv++;
    }
    /*srcPtr++;
    *srcPtr = '\0';
    destPtr--;*/
    *cmdArgv = '\0';
    return cmdArgc;
}

void chop(char *srcPtr)
{
    while(*srcPtr != ' ' && *srcPtr != '\t' && *srcPtr != '\n')
    {
        srcPtr++;
    }
    *srcPtr = '\0';
}

void execute(char **cmdArgv, int mode, char **supplementPtr)
{
    pid_t pid, pid2;
    FILE *fp;
    int mode2 = NORMAL, cmdArgc, status1, status2;
    char *cmdArgv2[INPUT_STRING_SIZE], *supplement2 = NULL;
    int myPipe[2];
    if(mode == PIPELINE)
    {
        if(pipe(myPipe))                    //create pipe
        {
            fprintf(stderr, "Pipe failed!");
            exit(-1);
        }
        parse(*supplementPtr, cmdArgv2, &supplement2, &mode2);
    }
    pid = fork();
    if( pid < 0)
    {
        printf("Error occured");
        exit(-1);
    }
    else if(pid == 0)
    {
        switch(mode)
        {
            case OUTPUT_REDIRECTION:
                fp = fopen(*supplementPtr, "w+");
                dup2(fileno(fp), 1);
                break;
            case OUTPUT_REDIRECTION_WITH_ERROR:
                fp = fopen(*supplementPtr, "w+");
                dup2(fileno(fp), 1);
                dup2(2, 1);
                break;
            case OUTPUT_REDIRECTION_WITH_APPEND_ERROR:
                fp = fopen(*supplementPtr, "a");
                dup2(fileno(fp), 1);
                dup2(2, 1);
                break;
            case OUTPUT_APP:
                fp = fopen(*supplementPtr, "a");
                dup2(fileno(fp), 1);
                break;
            case INPUT_REDIRECTION:
                fp = fopen(*supplementPtr, "r");
                dup2(fileno(fp), 0);
                break;
            case PIPELINE:
                close(myPipe[0]);       //close input of pipe
                dup2(myPipe[1], fileno(stdout));
                close(myPipe[1]);
                break;
            case PIPELINE_WITH_ERROR:
                close(myPipe[0]);
                dup2(myPipe[1], 1);
                dup2(2, 1);
                close(myPipe[1]);
                break;
        }
        execvp(*cmdArgv, cmdArgv);
    }
    else
    {
        if(mode == BACKGROUND)
            ;
        else if(mode == PIPELINE)
        {
            waitpid(pid, &status1, 0);      //wait for process 1 to finish
            pid2 = fork();
            if(pid2 < 0)
            {
                printf("error in forking");
                exit(-1);
            }
            else if(pid2 == 0)
            {
                close(myPipe[1]);       //close output to pipe
                dup2(myPipe[0], fileno(stdin));
                close(myPipe[0]);
                execvp(*cmdArgv2, cmdArgv2);
            }
            else
            {
                ;//wait(NULL);
                //waitpid(pid, &status1, 0);
                //waitpid(pid2, &status2, 0);
                close(myPipe[0]);
                close(myPipe[1]);
            }
        }
        else
            waitpid(pid, &status1, 0);
            //wait(NULL);
    }
}

【问题讨论】:

  • 研究一些现有的免费shell的源码,比如GNU bash

标签: c shell unix


【解决方案1】:

对于所有 I/O 重定向,这些操作都是对文件描述符的直接操作。关键函数是dup2()

&gt;&amp;2 表示法将标准输出(文件描述符,fd,1)重定向到标准错误(fd = 2):

dup2(2, 1);

这使得现有的打开文件描述符 2 和(不一定打开的)文件描述符 1 引用相同的描述符。 (我在这个答案的第一版中将论点重新放在了前面;也在下一次对dup2() 的调用中,但最后两个是正确的。)

&amp;&gt; 表示法将标准错误重定向到与标准输出相同的位置:

dup2(1, 2);

使用管道 (|&amp;),您首先要这样做:

pipe(pair);
…fork()…
…in the writer…
dup2(pair[1], 1);
dup2(1, 2);
close(pair[0]);
close(pair[1]);

使用&gt;&gt; 表示法,您以附加模式打开文件,然后使用dup2()


这些可以使用dup() 而不是dup2() 来完成吗?如果是的话,应该怎么做?

是的,您可以使用 Peano Arithmetic 进行数学运算。这很难,为什么要麻烦?

不同之处在于dup() 将描述符复制到最低的可用描述符。所以,假设标准输入(0)和标准输出(1)都是打开的,可以模拟:

dup2(1, 2);

与:

close(2);
dup(1);

只要您只处理最大为 5 的描述符,这是可以管理的。但是,dup2() 更容易使用;即使关闭的描述符少于目标描述符,它也会复制到指定的描述符。


修复更新的代码

您能否看看我上面更新的问题并提出我可能做错了什么?

当收到这样的请求时,我做的第一件事就是在我通常的一组编译器警告标志下编译它(从问题中逐字复制),在这种情况下,警告(通过-Werror 选项转换为错误) 既丰富又严肃。如果您没有编译并看到此类警告,那么您的生活将变得比需要的更艰难!

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror    pipes-22585525.c -o pipes-22585525
pipes-22585525.c: In function ‘main’:
pipes-22585525.c:45:9: error: implicit declaration of function ‘strcmp’ [-Werror=implicit-function-declaration]
         if(strcmp(inputString, "exit\n") == 0)
         ^
pipes-22585525.c:34:11: error: unused variable ‘cpt’ [-Werror=unused-variable]
     char *cpt, *inputString, *cmdArgv[INPUT_STRING_SIZE], *supplement = NULL;
           ^
pipes-22585525.c:32:27: error: variable ‘cmdArgc’ set but not used [-Werror=unused-but-set-variable]
     int i, mode = NORMAL, cmdArgc;
                           ^
pipes-22585525.c:32:9: error: unused variable ‘i’ [-Werror=unused-variable]
     int i, mode = NORMAL, cmdArgc;
         ^
pipes-22585525.c:30:14: error: unused parameter ‘argc’ [-Werror=unused-parameter]
 int main(int argc, char *argv[])
              ^
pipes-22585525.c:30:26: error: unused parameter ‘argv’ [-Werror=unused-parameter]
 int main(int argc, char *argv[])
                          ^
pipes-22585525.c: In function ‘parse’:
pipes-22585525.c:84:40: error: multi-character character constant [-Werror=multichar]
                     else if(*srcPtr == '>&')
                                        ^
pipes-22585525.c:84:21: error: comparison is always false due to limited range of data type [-Werror=type-limits]
                     else if(*srcPtr == '>&')
                     ^
pipes-22585525.c:89:40: error: multi-character character constant [-Werror=multichar]
                     else if(*srcPtr == '>>&')
                                        ^
pipes-22585525.c:89:21: error: comparison is always false due to limited range of data type [-Werror=type-limits]
                     else if(*srcPtr == '>>&')
                     ^
pipes-22585525.c:119:40: error: multi-character character constant [-Werror=multichar]
                     else if(*srcPtr == '|&')
                                        ^
pipes-22585525.c:119:21: error: comparison is always false due to limited range of data type [-Werror=type-limits]
                     else if(*srcPtr == '|&')
                     ^
pipes-22585525.c: In function ‘execute’:
pipes-22585525.c:160:43: error: unused variable ‘status2’ [-Werror=unused-variable]
     int mode2 = NORMAL, cmdArgc, status1, status2;
                                           ^
pipes-22585525.c:160:25: error: unused variable ‘cmdArgc’ [-Werror=unused-variable]
     int mode2 = NORMAL, cmdArgc, status1, status2;
                         ^
cc1: all warnings being treated as errors
$

使用int main(void) 可以避免有关argcargv 的警告,直到您真正解析传递给shell 的参数。 strcmp() 的警告通过包含 &lt;string.h&gt; 得到修复。其他未使用的变量警告也应该被修复。它们是普通问题,但应予以修复,以便代码编译时无警告。

另一组警告示例如下:

pipes-22585525.c: In function ‘parse’:
pipes-22585525.c:84:40: error: multi-character character constant [-Werror=multichar]
                     else if(*srcPtr == '>&')
                                        ^
pipes-22585525.c:84:21: error: comparison is always false due to limited range of data type [-Werror=type-limits]

变量srcPtrchar *;它一次只能容纳一个字符。允许使用多字符常量,但具有实现定义的值。您唯一可以确定的是,单个字符永远不能保存值'&gt;&amp;',这是第二条消息告诉您的。第一条消息暗示你应该写:

                     else if (strncmp(srcPtr, ">&", 2) == 0)

有一个警告。假设符号为"&lt;&lt;";在 Bash 中,还有一个符号 "&lt;&lt;&lt;"。在测试"&lt;&lt;" 之前测试"&lt;&lt;&lt;" 至关重要,否则您将永远看不到较长的符号,因为较短的符号总是匹配的。重定向也是如此;您需要小心确保没有任何早期测试妨碍检测到较晚的符号之一。

解决这些问题,您可能会顺利解决问题。如果您仍然卡住,请再次 Ping。 (哦,我已经看到程序在第一次接受我严格的编译选项时会产生大量警告 - 这不是坏代码。但是由于编译器可以告诉你你做错了什么,你在浪费你的时间(可以说是我的,但这将对您的未来有所帮助)通过不让编译器告诉您出了什么问题。记住,它比您更了解 C!)

我使用这些编译器选项,或随之而来的次要变体,基本上我所有的 C 代码。其中大部分也可以在 C++ 编译器下正确编译和运行,但那是我选择穿的一件毛衣,你不必穿。

【讨论】:

  • 我认为您将论点转为dup2
  • 非常感谢大家!如果这对我有用或者我仍然被卡住,我会尝试并发表评论......感谢所有帮助!
  • @JonathanLeffler 这些可以使用 dup() 而不是 dup2() 来完成吗?如果是这样,它必须怎么做?我只是问一下,以便我了解这些功能的用法之间的区别....
  • 感谢 gr8 的解释!我将尝试对此进行编码并检查它是否适合我....如果我可以让它工作,我会很快解决它... :)
  • @user2784743:请参阅更新“修复更新的代码”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-02
  • 1970-01-01
  • 2020-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多