Piping/dup2() не работает должным образом (Реализация Unix Shell в C)

Сначала я опубликую свой код, а затем объясню проблему, с которой я столкнулся:

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

#define MAX_ARGS 20
#define BUFSIZE 1024

int get_args(char* cmdline, char* args[])
{
  int i = 0;

  /* if no args */
  if((args[0] = strtok(cmdline, "\n\t ")) == NULL)
    return 0;

  while((args[++i] = strtok(NULL, "\n\t ")) != NULL) {
    if(i >= MAX_ARGS) {
      printf("Too many arguments!\n");
      exit(1);
    }
  }
  /* the last one is always NULL */
  return i;
}

void execute(char* cmdline)
{
  int pid, async, oneapp;
  char* args[MAX_ARGS];
  char* args2[] = {"-l", NULL};
  int nargs = get_args(cmdline, args);
  if(nargs <= 0) return;

  if(!strcmp(args[0], "quit") || !strcmp(args[0], "exit")) {
    exit(0);
  }

  printf("before the if\n");
  printf("%s\n",args[nargs - 2]);
  int i = 0;

// EDIT: THIS IS WHAT WAS SUPPOSED TO BE COMMENTED OUT
/*
  while (args[i] != ">" && i < nargs - 1) {
      printf("%s\n",args[i]);
      i++;
  }
*/
  // Presence of ">" token in args
  // causes errors in execvp() because ">" is not
  // a built-in Unix command, so remove it from args
  args[i - 1] = NULL;

  printf("Escaped the while\n");

// File descriptor array for the pipe
int fd[2];

// PID for the forked process
pid_t fpid1;

// Open the pipe
pipe(fd);

// Here we fork
fpid1 = fork();

if (fpid1 < 0)
{
    // The case where the fork fails
   perror("Fork failed!\n");
   exit(-1);
}
else if (fpid1 == 0)
{
       //dup2(fd[1], STDOUT_FILENO);
       close(fd[1]);
       //close(fd[0]);

       // File pointer for the file that'll be written to
       FILE * file;

       // freopen() redirects stdin to args[nargs - 1],
       // which contains the name of the file we're writing to
       file = freopen(args[nargs - 1], "w+", stdin);

       // If we include this line, the functionality works
       //execvp(args[0],args);

       // We're done writing to the file, so close it
       fclose(file);

       // We're done using the pipe, so close it (unnecessary?)
       //close(fd[1]);
}
else
{
   // Wait for the child process to terminate
   wait(0);
   printf("This is the parent\n");

   // Connect write end of pipe (fd[1]) to standard output
   dup2(fd[1], STDOUT_FILENO);

   // We don't need the read end, so close it
   close(fd[0]);

   // args[0] contains the command "ls", which is
   // what we want to execute
   execvp(args[0], args);

   // This is just a test line I was using before to check
   // whether anything was being written to stdout at all
   printf("Exec was here\n");
}

// This is here to make sure program execution
// doesn't continue into the original code, which
// currently causes errors due to incomplete functionality
exit(0);

  /* check if async call */
  printf("Async call part\n");
  if(!strcmp(args[nargs-1], "&")) { async = 1; args[--nargs] = 0; }
  else async = 0;

  pid = fork();
  if(pid == 0) { /* child process */
    execvp(args[0], args);
    /* return only when exec fails */
    perror("exec failed");
    exit(-1);
  } else if(pid > 0) { /* parent process */
    if(!async) waitpid(pid, NULL, 0);
    else printf("this is an async call\n");
  } else { /* error occurred */
    perror("fork failed");
    exit(1);
  }
}

int main (int argc, char* argv [])
{
  char cmdline[BUFSIZE];

  for(;;) {
    printf("COP4338$ ");
    if(fgets(cmdline, BUFSIZE, stdin) == NULL) {
      perror("fgets failed");
      exit(1);
    }
    execute(cmdline) ;
  }
  return 0;
}

Так в чем проблема? Просто: приведенный выше код создает файл с ожидаемым именем, то есть именем, указанным в командной строке, который помещается в args[nargs - 1]. Например, запустив программу, а затем набрав

ls > test.txt

Создает файл с именем test.txt... но на самом деле он ничего не записывает в него. Мне удалось заставить программу печатать символы мусора в файл несколько раз, но это происходило только во время приступов отчаянного кодирования, когда я просто пытался заставить программу писать что-то в файл.

Я думаю, что мне удалось сузить причину проблем до этой области кода:

else if (fpid1 == 0)
{
       printf("This is the child.\n");

       //dup2(fd[1], STDOUT_FILENO);
       close(fd[1]);
       //close(fd[0]);

       // File pointer for the file that'll be written to
       FILE * file;

       // freopen() redirects stdin to args[nargs - 1],
       // which contains the name of the file we're writing to
       file = freopen(args[nargs - 1], "w+", stdout);

       // If we include this line, the functionality works
       //execvp(args[0],args);

       // We're done writing to the file, so close it
       fclose(file);

       // We're done using the pipe, so close it (unnecessary?)
       //close(fd[1]);
}
else
{
   // Wait for the child process to terminate
   wait(0);
   printf("This is the parent\n");

   // Connect write end of pipe (fd[1]) to standard output
   dup2(fd[1], STDOUT_FILENO);

   // We don't need the read end, so close it
   close(fd[0]);

   // args[0] contains the command "ls", which is
   // what we want to execute
   execvp(args[0], args);

   // This is just a test line I was using before to check
   // whether anything was being written to stdout at all
   printf("Exec was here\n");
}

Более конкретно, я считаю, что проблема в том, как я использую (или пытаюсь использовать) dup2() и функциональность конвейера. Я в основном выяснил это в процессе устранения. Я провел несколько часов, комментируя, перемещая код, добавляя и удаляя тестовый код, и обнаружил следующие вещи:

1.) Удаление вызовов функции dup2 () и использование execvp(args[0], args) выводит результат команды ls на консоль. Родительский и дочерний процессы начинаются и заканчиваются правильно. Итак, вызовы execvp() работают правильно.

2.) Линия

file = freopen(args[nargs - 1], "w+", stdout)

Успешно создает файл с правильным именем, поэтому вызов freopen() не завершается неудачей. Хотя это не сразу доказывает, что эта функция работает правильно, как написано сейчас, рассмотрим факт № 3:

3.) В блоке дочернего процесса, если мы сделаем freopen перенаправление на выходной файл из stdin (а ​​не stdout) и раскомментируем вызов execvp(args[0], args), например так:

   // freopen() redirects stdin to args[nargs - 1],
   // which contains the name of the file we're writing to
   file = freopen(args[nargs - 1], "w+", stdin);

   // If we include this line, the functionality works
   execvp(args[0],args);

и запустите программу, затем она заработает и результат команды ls будет успешно записан в выходной файл. Зная это, вполне можно сказать, что freopen() тоже не проблема.

Другими словами, единственное, что я не смог успешно сделать, - это передать выходные данные вызова execvp(), который выполняется в родительском процессе, в stdout, а затем из stdout в файл с помощью freopen().

Любая помощь приветствуется. Я был в этом с 10 утра вчера, и у меня совершенно нет идей. Я просто не знаю, что я делаю не так. Почему это не работает?

0 ответов

Другие вопросы по тегам