在C中创建外壳.如何实现输入和输出重定向? [英] Creating a shell in C. How would I implement input and output redirection?

查看:66
本文介绍了在C中创建外壳.如何实现输入和输出重定向?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用C创建外壳程序,我需要实现输入和输出重定向的帮助.

I'm creating a shell in C, and I need help implementing input and output redirection.

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

When I try to create a file using ">" I get an error message saying the file does not exist. When I try to do something like ls > test.txt; it won't create a new file.

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

I updated the code with the suggestions provided to me, but now I got different errors. However, a new file is still not created for the output redirection.

这是我的完整代码:

#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);
    }
  }
}

推荐答案

乍一看,除了您的closedup2在您的toks[in]案例中不正常外,没有任何明显的迹象表明解释了为什么在重定向时不创建输出文件(例如cat somefile > newfile).但是,您没有检查很多细微之处.

On first glance, other than your close and dup2 being out of order in your toks[in] case, there isn't anything readily apparent that explains why you do not create an output file when redirecting (e.g. cat somefile > newfile). However, there are a number of subtleties that you are not checking.

例如,您需要在调用dup2close之前先检查对open的调用是否成功. (否则,您尝试重定向未打开的文件描述符).简单的基本检查即可,例如

For example, you need to check whether your call to open succeeds in each case before calling dup2 and close. (otherwise, you are attempting to redirect a file-descriptor that is not open). Simple basic checking will do, e.g.

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'和world 'r'.您还应该检查以下内容的返回值: dup2,而在学究情况下是close)

(note: I've tweaked the permission to write the file as 0644 (user 'rw', group 'r' and world 'r'. You should also check the returns of dup2 and in the pedantic case close)

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

The bigger issues come in how you rearrange toks before your call to execvp. The reason you use dup2 or a pipe is that the exec.. function cannot handle redirection (e.g. it doesn't know what to do with '>' or '<'). So you are manually handling the redirection of input or output to a file by redirecting either the file to stdin on the input case or stdout (and/or stderr) to the file on the output case. In either case, you must remove the < filename or > filename tokens from toks before calling execvp or you will get an error.

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

If you insure that set each pointer in toks to NULL and you read no more than MAXTOKS - 1 (preserving a terminating NULL as required), then you can iterate over toks shifting the pointers to insure you do not send the < > and filename to execvp. After you find < or > in toks at index i and insure there is a toks[i+1] filename, something like:

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

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

Then passing toks to execvp will not generate the error (that I suspect is what you are experiencing)

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

There is also another corner-case nit you should be aware of. If your executable has any registered calls to atexit or other desctructors, the references are not part of your call to execvp. So if the call to execvp fails, you cannot call exit (which can invoke undefined behavior in a call to any post-exit function), so the proper call is to _exit which will not attempt any such calls.

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

A bare minimum of the working redirection would be something like the following. Not there are many other aspects of parsing and redirection not addressed below, but for your basic file creation problem, it provides a framework, e.g.

#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中发布所有代码,以便确保在代码的其他区域不会出现问题.

Let me know if this solves the error issue. The only other creation issue would be you don't have write permission where you are attempting to create the file. If this doesn't solve the issue, please post all your code in a MCVE so I can insure that problems are not created in other areas of the code.

发布完整代码后

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

Your biggest issue was in your use of strtok_r and not removing the filename (or setting it NULL before calling execvp), and in using i + 1 instead of i in your assignment to in and out, e.g.

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错误.这是这些 Doah!(或"id10t")错误之一(重写全大写的引号)

When you used i + 1, you set the index for tok[in] or tok[out] to one past the filename prompting the Bad Address error. It's one of those Doah! (or "id10t") errors... (rewrite the quote all-caps)

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

Further, before your call to execvp you must set tok[in] or tok[out] to NULL as you have removed the < and > and the file descriptor has already been duped, e.g.

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

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

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

You had also forgotten to reset your loop variables, e.g.

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

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

Cleaning what you had up a bit, you could do something like the following:

#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当然没有问题.

You will need to verify there are no additional issues, but it certainly has no problems with cat file1 > file2.

这篇关于在C中创建外壳.如何实现输入和输出重定向?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆