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 утра вчера, и у меня совершенно нет идей. Я просто не знаю, что я делаю не так. Почему это не работает?