简体   繁体   English

C 中的 shell 程序问题

[英]Issues with shell program in C

I am creating a Linux type shell program for a school project.我正在为学校项目创建 Linux 类型 shell 程序。 So far I have implemented the basic Linux external commands like "ls","ps", etc., using execvp and basic pipes.到目前为止,我已经使用execvp和基本管道实现了基本的 Linux 外部命令,如“ls”、“ps”等。 As part of the project, the user can either run the program in interactive mode or batch mode.作为项目的一部分,用户可以在交互模式或批处理模式下运行程序。 In interactive mode the user just enters a command when prompted.在交互模式下,用户只需在提示时输入命令。 For batch mode, the user specifies a file in the command line where there is a list of commands to execute.对于批处理模式,用户在命令行中指定一个文件,其中包含要执行的命令列表。

The problem I am having is in batch mode.我遇到的问题是批处理模式。 In batch mode, if an invalid command is listed (eg "kdfg", for which "kdfg: command not found" while be the output), everything afterward continues, but everything afterward is executed twice.在批处理模式下,如果列出了无效的命令(例如“kdfg”,输出为“kdfg: command not found”),之后的一切都会继续,但之后的一切都会执行两次。 so if I have a "kghd" on one line and the next line is "ls", then the "ls" command will be executed twice.所以如果我在一行上有一个“kghd”,而下一行是“ls”,那么“ls”命令将被执行两次。 I've literally been looking at my code for hours and have tried a bunch of stuff, but to no avail.我实际上一直在查看我的代码几个小时并尝试了很多东西,但无济于事。

My code is displayed below:我的代码如下所示:

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

char* InputString();
char** GetArgs(char* com);
void Free(char** args);
char** GetCommands(char** line);
void PipedCommands(char* line);
int batch = 0; //Acts as bool for if there is a batch file given at command line
FILE* bFile; //This is just to make a quick tweek to the file if necssary to prevent any undefined behavior and keep track of where we are in the fil.
int b_fd;
int stdin_cpy;

int main(int argc, char** argv)
{
    pid_t pid;
    int status;
    int fd;
    int exitCom = 0; //acts as bool to check if an exit command was given.
    char* line;
    
    if(argc > 1) //check if batch file was given.
    {
        /*
        if(freopen(argv[1], "r", stdin) == NULL) //I added this in case the file isn't found
        {
            printf("\nCould not open \"%s\". Terminated\n\n", argv[1]);
            exit(1);
        }
        */
        //The following is to append a newline at the end of the file (if there isn't one).
        //For some reaosn, there seems to be some undefined behavior if the input file isn't
        //stricitly ended with a newline.
        bFile = fopen(argv[1], "r+"); //open for reading and updating
        if(bFile == NULL)
        {
            printf("\nCould not open \"%s\". Terminated\n\n", argv[1]);
            exit(1);
        }
        fseek(bFile, -1, SEEK_END); //go to last character of file
        if(fgetc(bFile) != '\n') //If last character is not a newline, append a newline to the file.
        {
            fprintf(bFile, "\n");
        }
        fclose(bFile); //close the file.
        
        bFile = fopen(argv[1], "r"); //open file to keep track of when it ends
        b_fd = open(argv[1], O_RDONLY); //open file again (with file descriptor this time) to duplicate it to stdin
        stdin_cpy = dup(fileno(stdin)); //keep track of stdin file.
        dup2(b_fd, 0); //duplicate to stdin so program takes input from bFile
        close(b_fd);
        batch = 1;
    }
    
    
    //int i=0; //this was used for debugging purposes
    while(1)
    {
        printf("\n");
        char** coms = GetCommands(&line);
        for(int i=0; coms[i] != NULL; ++i) //loop goes through each command returned from GetCommands(...)
        {
            //fork and wait.
            pid = fork();
            wait(&status);
            
            if(pid == 0)
            {
                int pipedCommand = 0;
                //printf("\ncoms[%d]: %s\n", i, coms[i]);
                for(int j=0; j<strlen(coms[i]); ++j)
                {
                    if(coms[i][j] == '|')
                    {
                        pipedCommand = 1;
                        break;
                    }
                }
                if(pipedCommand == 1)
                {
                    PipedCommands(coms[i]);
                    exit(1);
                }
                char** args = GetArgs(coms[i]);
                //printf("\nargs[0]: %s\n", args[0]);
                if(strcmp(args[0],"exit") == 0)
                {
                    exit(5); //if exit command was given, exit status will be 5 (I just used 5 becuse I felt like it).
                }
                //printf("\nNo exit\n");
                printf("\n");
                execvp(args[0],args);
                printf("%s: command not found\n", args[0]);
                exit(1); //Normal exit exits with 1 or 0.
            }
            //Parent continues after child exits
            else if(pid > 0)
            {
                //check exit status of child
                if(WEXITSTATUS(status) == 5)
                    exitCom = 1; //set bool exitCom to 1 (true), indicating that the exit command was given
            }
        }
        if(pid > 0)
        {
            free(line);
            free(coms);
            //Now that all commands in the line were executed, check exitCom and if it is 1 (exit command was given), the shell can now exit.
            if(exitCom == 1)
            {
                printf("\n");
                exit(0);
            }
        }
/*
        if(i >= 5)
        {
            printf("\nFORCED EXIT\n");  //this was used for debugging purposes
            exit(1);
        }
        ++i;
*/
    }

    return 0;
}

char* InputString()
{
    int len = 20;
    char* str = (char*)malloc(sizeof(char)*len);
    char* buff;
    unsigned int i=0;

    if(str != NULL)
    {
        int c = EOF;
        //printf("%c", fgetc(bFile));
        while( ((c = getchar()) != '\n') && (c != EOF) )
        {   
            /*
            //printf("%c", fgetc(bFile));
            //fgetc(bFile);
            if(feof(bFile))
            {
                printf("\n\nEnd of the line\n\n");
            }
            */
            str[i++] = (char)c;
            if(i == len)
            {
                len = len*2;
                str = (char*)realloc(str,sizeof(char)*len);
            }
        }
        str[i] = '\0';
        buff = (char*)malloc(i);
    }
    if(batch == 1)
    {
        if(fgets(buff, i, bFile) == NULL) //Once the end of file has been reached
        {
            dup2(stdin_cpy, 0); //revert input back to original stdin file so user can now enter commands interactively (this happens if exit command was not given)
            close(stdin_cpy); //close stdin_copy
            fclose(bFile); //close bFile as we have reached the end of it
            batch = 0;
        }
    }
    printf("\n");
    return str;
}

//User enters a line of commands (1 or more). Commands are separated with a ';' being the delimeter.
char** GetCommands(char** line)
{
    char** coms = (char**)malloc(sizeof(char*)); 
    char delim[] = ";";
    
    if(batch == 0)
        printf("prompt> ");
        fflush(stdout);
    
    *line = InputString();
    if(batch == 1)
        printf("%s\n", *line);
    
    strcat(*line, ";");
    
    int i=0;
   coms[i] = strtok(*line, delim);
   while(coms[i] != NULL)
   {
       ++i;
       coms = (char**)realloc(coms, sizeof(char*) * (i+1));
        coms[i] = strtok(NULL, delim);
        //printf("\ni: %d\n", i); 
   }
   
   return coms;
}
    

//A command obtained from GetCommands(...) is separated into various arguments with a space, ' ', being the delimiter.
char** GetArgs(char* com)
{
    
    char** args = (char**)malloc(sizeof(char*));
    char delim[] = " ";

    //printf("\nline: %s\n", line);
   int i=0;
   args[i] = strtok(com, delim);
   while(args[i] != NULL)
   {
       ++i;
       args = (char**)realloc(args, sizeof(char*) * (i+1));
        args[i] = strtok(NULL, delim);
   }
   
   return args;
}


void PipedCommands(char* line)
{
    char** coms = (char**)malloc(sizeof(char*));
    int numComs;
    char delim[] = "|";
    
    int i=0;
    coms[i] = strtok(line, delim);
   while(coms[i] != NULL)
   {
        ++i;
        coms = (char**)realloc(coms, sizeof(char*) * (i+1));
        coms[i] = strtok(NULL, delim);
   }
   numComs = i;
   
   int fd[2];
   pid_t pid;
   int status;
   int prev_p = 0;
    
  // printf("\nnumComs: %d\n", numComs);
    for(int i=0; i<numComs; ++i)
    {
        //printf("\ni: %d\n", i);
        pipe(fd);
        pid = fork();
        wait(&status);
        if(pid == 0)
        {
            //printf("\nChild\n");
            if(i < numComs-1)
            {
                //printf("\ni < numComs-1\n");
                //printf("%s", coms[i]);
                //printf("coms[%d]: %s", i, coms[i]);
                //printf("\nBefore dup2\n");
                char** args = GetArgs(coms[i]);
                //printf("\nexecvp in if\n");
                if(prev_p != 0)
                {
                    dup2(prev_p, 0);
                    close(prev_p);
                }
                dup2(fd[1], 1);
                close(fd[1]);
                execvp(args[0],args);
                printf("%s: command not found\n", args[0]);
                exit(3);
            }
            else
            {
                //printf("\nelse\n");
                //printf("coms[%d]: %s", i, coms[i]);
                //printf("\nBefore dup2 in else\n");
                if(prev_p != 0)
                {
                    dup2(prev_p, 0);
                    close(prev_p);
                }
                //close(fd[0]);
                close(fd[1]);
                char** args = GetArgs(coms[i]);
                printf("\n");
                execvp(args[0],args);
                printf("%s: command not found\n", args[0]);
                exit(3);
            }
        }
        close(prev_p);
        close(fd[1]);
        prev_p = fd[0];
        if(WEXITSTATUS(status) == 3)
        {
            close(fd[0]);
            close(prev_p);
            close(fd[1]);
            return;
        }
    }
    close(fd[0]);
    close(prev_p);
    close(fd[1]);
        
}


You can probably ignore the PipedCommands(...) function as I do not think the problem lies there.您可能会忽略PipedCommands(...) function,因为我认为问题不在于那里。

Below is a simple batch file:下面是一个简单的批处理文件:

kldfg
whoami

Below is the output using the above batch file下面是使用上述批处理文件的output

kldfg
kldfg: command not found

whoami
jco0100

whoami
jco0100

The whoami command should only execute once, but it appears to execute twice. whoami 命令应该只执行一次,但它似乎执行了两次。 After that, the program reverts to interactive mode as it should and everything runs fine from there.之后,程序将恢复到应有的交互模式,并且从那里一切运行良好。 Does anyone have any idea why this is happening.有谁知道为什么会这样。 This only happens when an unknown command is entered.这仅在输入未知命令时发生。 If all commands in the batch file are valid, nothing is outputted twice.如果批处理文件中的所有命令都有效,则不会输出两次。 It's only for batch files that have an unknown command that all commands after the unknown one are outputted twice.仅对于具有未知命令的批处理文件,未知命令之后的所有命令都会输出两次。

Here's another example:这是另一个例子:

Batch file:批处理文件:

date
kldfg
whoami; ls | wc -l
date | wc -c

Output: Output:

date
Tue Apr 13 19:43:19 CDT 2021

kldfg
kldfg: command not found

whoami; ls | wc -l
jco0100
34

date | wc -c
29

whoami; ls | wc -l
jco0100
34

date | wc -c
29

I got it working by disconnecting stdin on the child process before running the command:在运行命令之前,我通过断开子进程上的标准输入来使其工作:

...
freopen("/dev/null", "r", stdin); // disconnect
execcvp(args[0], args);
...

From this link: If I fork() and then do an execv(), who owns the console?从此链接: 如果我 fork() 然后执行 execv(),谁拥有控制台?

I had a crack at debugging it.我在调试它时很擅长。 To get you started on that road:为了让你开始这条路:

Compile your C program with debugging symbols:使用调试符号编译您的 C 程序:

$ gcc --debug your-program.c

Now debug your C program:现在调试您的 C 程序:

$ gdb a.out

This start a gdb interactive shell.这启动了一个 gdb 交互式 shell。

In gdb itself:在 gdb 本身中:

(gdb) list
(gdb) set follow-fork-mode parent
(gdb) breakpoint 69
  1. list your code列出你的代码
  2. tell the debugger to follow the parent when fork()告诉调试器在 fork() 时跟随父级
  3. set a breakpoint at line 69在第 69 行设置断点

Run the program:运行程序:

(gdb) run batch.txt

It will pause at line 69. Execute next line:它将在第 69 行暂停。执行下一行:

(gdb) next

Print a variable:打印一个变量:

(gdb) print *coms

Continue running:继续运行:

(gdb) continue

I leave the rest for you to explore.我将 rest 留给您探索。

I'm still not sure what's wrong with it.我仍然不确定它有什么问题。 Something strange happens in InputString() after your fork fails with an unknown command.在您的 fork 因未知命令而失败后, InputString()中发生了一些奇怪的事情。 InputString() begins returning duplicates from getchar() . InputString()开始从getchar()返回重复项。

I didn't even know you could do that with stdin.我什至不知道你可以用标准输入做到这一点。 Maybe just read from the file in a normal fashion, rather than clobbering stdin, and see if the problem goes away.也许只是以正常方式从文件中读取,而不是破坏标准输入,看看问题是否消失。

Don't write linux code much, I am trying to do more of that.不要写太多 linux 代码,我正在尝试做更多。 I took the fork and wait commands out so I could build it in mingw64 (because they arent supported in windows builds) and can't seem to reproduce the issue.我取出 fork 并等待命令,以便可以在 mingw64 中构建它(因为 windows 构建不支持它们)并且似乎无法重现该问题。

So I think the issue is in the multi-threading setup you have going there.所以我认为问题在于你要去那里的多线程设置。

The "pid" variable is shared between every fork. “pid”变量在每个分叉之间共享。 which means when the fork command is called "pid" is set to whatever the last fork in the loop returned.这意味着当 fork 命令被调用时,“pid”被设置为循环中最后一个 fork 返回的值。

It looks like you are using an if statement checking the "pid" variable to see if this thread can execute the command.看起来您正在使用 if 语句检查“pid”变量以查看该线程是否可以执行该命令。 But wouldn't the main thread keep running right through that?但是主线程不会继续运行吗?

I don't know what fork() returns but "pid" is uninitialized, don't know if that matters.我不知道 fork() 返回什么,但“pid”未初始化,不知道这是否重要。

Maybe this helps?也许这有帮助?

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

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