繁体   English   中英

用C创建外壳。如何实现输入和输出重定向?

[英]Creating a shell in C. How would I implement input and output redirection?

我正在用C创建外壳,我需要帮助来实现输入和输出重定向。

当我尝试使用“>”创建文件时,我收到一条错误消息,指出该文件不存在。 当我尝试做ls> test.txt之类的东西时; 它不会创建一个新文件。

我使用提供给我的建议更新了代码,但是现在我遇到了不同的错误。 但是,仍然没有为输出重定向创建新文件。

这是我的完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#define MAX_BUF 160
#define MAX_TOKS 100

int main(int argc, char **argv) 
{
    char *pos;
    char *tok;
    char *path;
    char s[MAX_BUF];
    char *toks[MAX_TOKS];
    time_t rawtime;
    struct tm *timeinfo;
    static const char prompt[] = "msh> ";
    FILE *infile;
    int in;
    int out;
    int fd0;
    int fd1;
    in = 0;
    out = 0;

 /* 
 * process command line options
*/

  if (argc > 2) {
    fprintf(stderr, "msh: usage: msh [file]\n");
    exit(EXIT_FAILURE);
  }
  if (argc == 2) {
    /* read from script supplied on the command line */
    infile = fopen(argv[1], "r");
    if (infile == NULL) 
    {
       fprintf(stderr, "msh: cannot open script '%s'.\n", argv[1]);
       exit(EXIT_FAILURE);
    }
  } else {
      infile = stdin;
  }

  while (1) 
  {
    // prompt for input, if interactive input
     if (infile == stdin) {
     printf(prompt);
  }

/*
 * read a line of input and break it into tokens 
 */

  // read input 
  char *status = fgets(s, MAX_BUF-1, infile);

  // exit if ^d or "exit" entered
  if (status == NULL || strcmp(s, "exit\n") == 0) {
       if (status == NULL && infile == stdin) {
              printf("\n");
        }
        exit(EXIT_SUCCESS);
  }

  // remove any trailing newline
  if ((pos = strchr(s, '\n')) != NULL) {
    *pos = '\0';
   }

   // break input line into tokens 
    char *rest = s;
    int i = 0;

  while((tok = strtok_r(rest, " ", &rest)) != NULL && i < MAX_TOKS) 
  {
      toks[i] = tok;
      if(strcmp(tok, "<") == 0)
      {
          in = i + 1;
           i--;
       }
       else if(strcmp(tok, ">")==0)
       {
          out = i + 1;
          i--;
       }
       i++;
  }

  if (i == MAX_TOKS) {
      fprintf(stderr, "msh: too many tokens");
      exit(EXIT_FAILURE);
  }
  toks[i] = NULL;

/*
 * process a command
 */

  // do nothing if no tokens found in input
  if (i == 0) {
     continue;
  }


  // if a shell built-in command, then run it 
  if (strcmp(toks[0], "help") == 0) {
      // help 
       printf("enter a Linux command, or 'exit' to quit\n");
       continue;
   } 
  if (strcmp(toks[0], "today") == 0) {
       // today
       time(&rawtime);
       timeinfo = localtime(&rawtime);
       printf("Current local time: %s", asctime(timeinfo));
      continue;
  }
  if (strcmp(toks[0], "cd") == 0) 
  {
     // cd 
     if (i == 1) {
         path = getenv("HOME");
     } else {
         path = toks[1];
     }
     int cd_status = chdir(path);
     if (cd_status != 0) 
     {
         switch(cd_status) 
         {
            case ENOENT:
                printf("msh: cd: '%s' does not exist\n", path);
                break;
            case ENOTDIR:
                printf("msh: cd: '%s' not a directory\n", path);
                break;
            default:
                printf("msh: cd: bad path\n");
          }
      }
     continue;
  }

  // not a built-in, so fork a process that will run the command
  int rc = fork();
  if (rc < 0) 
  {
     fprintf(stderr, "msh: fork failed\n");
      exit(1);
   }
   if (rc == 0) 
   {
        if(in)
        {
            int fd0;
            if((fd0 = open(toks[in], O_RDONLY, 0)) == -1)
            {
                perror(toks[in]);
                exit(EXIT_FAILURE);
            }
            dup2(fd0, 0);
            close(fd0);
         }

        if(out)
        {
           int fd1;
           if((fd1 = open(toks[out], O_WRONLY | O_CREAT | O_TRUNC | O_CREAT, 
            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
            { 
               perror (toks[out]);
               exit( EXIT_FAILURE);
             }
            dup2(fd1, 1);
            close(fd1);
        }
        // child process: run the command indicated by toks[0]
        execvp(toks[0], toks);
        /* if execvp returns than an error occurred */
        printf("msh: %s: %s\n", toks[0], strerror(errno));
        exit(1);
     } 
    else 
    {
        // parent process: wait for child to terminate
       wait(NULL);
    }
  }
}

乍一看,除了closedup2toks[in]出现问题外,没有任何明显的东西可以解释为什么在重定向时不创建输出文件(例如cat somefile > newfile )。 但是,您没有检查很多细微之处。

例如,您需要在调用dup2close之前检查每种情况下的open调用是否成功。 (否则,您尝试重定向未打开的文件描述符)。 简单的基本检查就可以了,例如

if (in) {
    int fd0;
    if ((fd0 = open(toks[in], O_RDONLY)) == -1) {
        perror (toks[in]);
        exit (EXIT_FAILURE);
    }
    dup2(fd0, 0);
    close(fd0);
}

if (out)
{
    int fd1;
    if ((fd1 = open(toks[out], 
                O_WRONLY | O_CREAT | O_TRUNC | O_CREAT, 
                S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {            
        perror (toks[out]);
        exit (EXIT_FAILURE);
    }
    dup2(fd1, 1);
    close(fd1);
}

注意:我已经调整了将文件写为0644的权限(用户'rw' ,组'r''r' 。您还应该检查dup2的返回值,并且在学究的情况下请close

更大的问题在于如何在调用execvp之前重新排列toks 使用dup2或管道的原因是exec..函数无法处理重定向(例如,它不知道如何处理'>''<' )。 因此,通过将文件重定向到输入案例上的stdin或将stdout (和/或stderr )重定向到输出案例上的文件,您正在手动处理输入或输出到文件的重定向。 无论哪种情况,都必须在调用execvp之前从toks删除< filename> filename令牌, toks会出现错误。

如果确保将toks每个指针都toksNULL并且读取的内容不超过MAXTOKS - 1 (根据需要保留终止NULL ),则可以遍历toks移动指针,以确保不将< >filename发送给execvp 在索引i toks中找到<> ,并确保有toks[i+1]文件名后,类似:

            while (toks[i]) {
                toks[idx] = toks[i+2];
                i++; 
            }

然后将toks传递给execvp不会生成错误(我怀疑这是您遇到的问题)

您还应该注意另一个极端情况。 如果您的可执行文件具有对atexit或其他析构函数的任何已注册调用,则这些引用不是您对execvp的调用的一部分。 因此,如果对execvp的调用失败,则无法调用exit (它可以在对任何后退出函数的调用中调用未定义的行为),因此正确的调用是_exit ,它将不尝试任何此类调用。

最小的工作重定向将类似于以下内容。 解析和重定向没有很多其他方面在下面没有解决,但是对于您的基本文件创建问题,它提供了一个框架,例如

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

enum {ARGSIZE = 20, BUF_SIZE = 1024};    /* constants */

void execute (char **args);

int main (void) {

    while (1) {

        char line[BUF_SIZE] = "",
            *args[ARGSIZE],
            *delim = " \n",
            *token;
        int argIndex = 0;

        for (int i = 0; i < ARGSIZE; i++)  /* set all pointers NULL */
            args[i] = NULL;

        printf ("shell> ");             /* prompt */

        if (!fgets (line, BUF_SIZE, stdin)) {
            fprintf (stderr, "Input canceled - EOF received.\n");
            return 0;
        }
        if (*line == '\n')              /* Enter alone - empty line */
            continue;

        for (token = strtok (line, delim);        /* parse tokens */
                token && argIndex + 1 < ARGSIZE; 
                token = strtok (NULL, delim)) {
            args[argIndex++] = token;
        }

        if (!argIndex) continue;        /* validate at least 1 arg */

        if (strcmp (args[0], "quit") == 0 || strcmp (args[0], "exit") == 0)
            break;

        execute (args);  /* call function to fork / execvp */

    }
    return 0;
}

void execute (char **args)
{
    pid_t pid, status;
    pid = fork ();

    if (pid < 0) {
        perror ("fork");
        return;
    }
    else if (pid > 0) {
        while (wait (&status) != pid)
            continue;
    }
    else if (pid == 0) {
        int idx = 0,
            fd;
        while (args[idx]) {   /* parse args for '<' or '>' and filename */
            if (*args[idx] == '>' && args[idx+1]) {
                if ((fd = open (args[idx+1], 
                            O_WRONLY | O_CREAT, 
                            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
                    perror (args[idx+1]);
                    exit (EXIT_FAILURE);
                }
                dup2 (fd, 1);
                dup2 (fd, 2);
                close (fd);
                while (args[idx]) {
                    args[idx] = args[idx+2];
                    idx++; 
                }
                break;
            }
            else if (*args[idx] == '<' && args[idx+1]) {
                if ((fd = open (args[idx+1], O_RDONLY)) == -1) {
                    perror (args[idx+1]);
                    exit (EXIT_FAILURE);
                }
                dup2 (fd, 0);
                close (fd);
                while (args[idx]) {
                    args[idx] = args[idx+2];
                    idx++; 
                }
                break;
            }
            idx++;
        }
        if (execvp (args[0], args) == -1) {
            perror ("execvp");
        }
        _exit (EXIT_FAILURE);   /* must _exit after execvp return, otherwise */
    }                           /* any atext calls invoke undefine behavior  */
}

使用/输出示例

最少使用> filename< filename

$ ./bin/execvp_wredirect
shell> ls -al tmp.txt
ls: cannot access 'tmp.txt': No such file or directory
shell> cat dog.txt
my dog has fleas
shell> cat dog.txt > tmp.txt
shell> ls -al tmp.txt
-rw-r--r-- 1 david david 17 Feb 25 01:52 tmp.txt
shell> cat < tmp.txt
my dog has fleas
shell> quit

让我知道这是否解决了错误问题。 唯一的其他创建问题是您在尝试创建文件的地方没有写权限。 如果这样做不能解决问题,请在MCVE中发布所有代码,以便确保在代码的其他区域不会出现问题。


发布完整代码后

最大的问题是使用strtok_r而不删除文件名(或在调用execvp之前将其设置为NULL ),以及在分配inout使用i + 1而不是i ,例如

tok = strtok_r(rest, delim, &rest);
while(tok != NULL && i < MAX_TOKS) 
{
    toks[i] = tok;
    if(strcmp(tok, "<") == 0)
    {
        in = i;
        i--;
    }
    else if(strcmp(tok, ">")==0)
    {
        out = i;
        i--;
    }
    i++;
    tok = strtok_r(NULL, delim, &rest);
}

使用i + 1 ,将tok[in]tok[out]的索引设置为文件名后一个,提示出现Bad Address错误。 这就是这些多哈之一! (或“ id10t”)错误...(改写全大写的引号)

此外,在调用execvp之前,必须将tok[in]tok[out]NULL因为您已删除<>并且文件描述符已被复制,例如

            dup2(fd0, 0);
            close(fd0);
            toks[in] = NULL;

            dup2(fd1, 1);
            close(fd1);
            toks[out] = NULL;

您还忘记了重置循环变量,例如

while (1) 
{
    in = out = 0;       /* always reset loop variables */
    for (int i = 0; i < MAX_TOKS; i++)
        toks[i] = NULL; /* and NULL all pointers */

清理一下您的工作后,可以执行以下操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>  /* missing headers */
#include <sys/wait.h>

#define MAX_BUF 160
#define MAX_TOKS 100

int main(int argc, char **argv) 
{
    char *delim = " \n";    /* delimiters for strtok_r (including \n) */
//     char *pos;           /* no longer used */
    char *tok;
    char *path;
    char s[MAX_BUF];
    char *toks[MAX_TOKS];
    time_t rawtime;
    struct tm *timeinfo;
    static const char prompt[] = "msh> ";
    FILE *infile;
    int in;
    int out;
//     int fd0;     /* unused and shadowed declarations below */
//     int fd1;     /* always compile with -Wshadow */
    in = 0;
    out = 0;

   /* 
    * process command line options
    */

    if (argc > 2) {
        fprintf(stderr, "msh: usage: msh [file]\n");
        exit(EXIT_FAILURE);
    }
    if (argc == 2) {
        /* read from script supplied on the command line */
        infile = fopen(argv[1], "r");
        if (infile == NULL) {
            fprintf(stderr, "msh: cannot open script '%s'.\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    } else {
        infile = stdin;
    }

    while (1) 
    {
        in = out = 0;       /* always reset loop variables */
        for (int i = 0; i < MAX_TOKS; i++)
            toks[i] = NULL;

        // prompt for input, if interactive input
        if (infile == stdin) {
            printf(prompt);
        }

    /*
     * read a line of input and break it into tokens 
     */

        // read input 
        char *status = fgets(s, MAX_BUF-1, infile);

        // exit if ^d or "exit" entered
        if (status == NULL || strcmp(s, "exit\n") == 0) {
            if (status == NULL && infile == stdin) {
                printf("\n");
            }
            exit(EXIT_SUCCESS);
        }


        // break input line into tokens 
        char *rest = s;
        int i = 0;

        tok = strtok_r(rest, delim, &rest);
        while(tok != NULL && i < MAX_TOKS) 
        {
            toks[i] = tok;
            if(strcmp(tok, "<") == 0)
            {
                in = i;     /* only i, not i + 1, you follow with i-- */
                i--;
            }
            else if(strcmp(tok, ">")==0)
            {
                out = i;    /* only i, not i + 1, you follow with i-- */
                i--;
            }
            i++;
            tok = strtok_r(NULL, delim, &rest);
        }

        if (i == MAX_TOKS) {
            fprintf(stderr, "msh: too many tokens");
            exit(EXIT_FAILURE);
        }
        toks[i] = NULL;

    /*
     * process a command
     */

        // do nothing if no tokens found in input
        if (i == 0) {
            continue;
        }

        // if a shell built-in command, then run it 
        if (strcmp(toks[0], "help") == 0) {
            // help 
            printf("enter a Linux command, or 'exit' to quit\n");
            continue;
        } 
        if (strcmp(toks[0], "today") == 0) {
            // today
            time(&rawtime);
            timeinfo = localtime(&rawtime);
            printf("Current local time: %s", asctime(timeinfo));
            continue;
        }
        if (strcmp(toks[0], "cd") == 0) 
        {
            // cd 
            if (i == 1) {
                path = getenv("HOME");
            } else {
                path = toks[1];
            }
            int cd_status = chdir(path);
            if (cd_status != 0) 
            {
                switch(cd_status) 
                {
                    case ENOENT:
                        printf("msh: cd: '%s' does not exist\n", path);
                        break;
                    case ENOTDIR:
                        printf("msh: cd: '%s' not a directory\n", path);
                        break;
                    default:
                        printf("msh: cd: bad path\n");
                }
            }
            continue;
        }

        // not a built-in, so fork a process that will run the command
        pid_t rc = fork(), rcstatus;       /* use type pid_t, not int */
        if (rc < 0) 
        {
            fprintf(stderr, "msh: fork failed\n");
            exit(1);
        }
        if (rc == 0) 
        {
            if(in)
            {
                int fd0;
                if((fd0 = open(toks[in], O_RDONLY, 0)) == -1)
                {
                    perror(toks[in]);
                    exit(EXIT_FAILURE);
                }
                dup2(fd0, 0);
                close(fd0);
                toks[in] = NULL;
            }

            if(out)
            {
                int fd1;
                if((fd1 = open(toks[out], O_WRONLY | O_CREAT | O_TRUNC | O_CREAT, 
                    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
                { 
                    perror (toks[out]);
                    exit( EXIT_FAILURE);
                }
                dup2(fd1, 1);
                close(fd1);
                toks[out] = NULL;
            }

            // child process: run the command indicated by toks[0]
            execvp(toks[0], toks);
            /* if execvp returns than an error occurred */
            printf("msh: %s: %s\n", toks[0], strerror(errno));
            exit(1);
        } 
        else 
        {
            // parent process: wait for child to terminate
            while (wait (&rcstatus) != rc)
                continue;
        }
    }
}

您将需要验证没有其他问题,但是cat file1 > file2当然没有问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM