簡體   English   中英

C 中的 shell 程序問題

[英]Issues with shell program in C

我正在為學校項目創建 Linux 類型 shell 程序。 到目前為止,我已經使用execvp和基本管道實現了基本的 Linux 外部命令,如“ls”、“ps”等。 作為項目的一部分,用戶可以在交互模式或批處理模式下運行程序。 在交互模式下,用戶只需在提示時輸入命令。 對於批處理模式,用戶在命令行中指定一個文件,其中包含要執行的命令列表。

我遇到的問題是批處理模式。 在批處理模式下,如果列出了無效的命令(例如“kdfg”,輸出為“kdfg: command not found”),之后的一切都會繼續,但之后的一切都會執行兩次。 所以如果我在一行上有一個“kghd”,而下一行是“ls”,那么“ls”命令將被執行兩次。 我實際上一直在查看我的代碼幾個小時並嘗試了很多東西,但無濟於事。

我的代碼如下所示:

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


您可能會忽略PipedCommands(...) function,因為我認為問題不在於那里。

下面是一個簡單的批處理文件:

kldfg
whoami

下面是使用上述批處理文件的output

kldfg
kldfg: command not found

whoami
jco0100

whoami
jco0100

whoami 命令應該只執行一次,但它似乎執行了兩次。 之后,程序將恢復到應有的交互模式,並且從那里一切運行良好。 有誰知道為什么會這樣。 這僅在輸入未知命令時發生。 如果批處理文件中的所有命令都有效,則不會輸出兩次。 僅對於具有未知命令的批處理文件,未知命令之后的所有命令都會輸出兩次。

這是另一個例子:

批處理文件:

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

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

在運行命令之前,我通過斷開子進程上的標准輸入來使其工作:

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

從此鏈接: 如果我 fork() 然后執行 execv(),誰擁有控制台?

我在調試它時很擅長。 為了讓你開始這條路:

使用調試符號編譯您的 C 程序:

$ gcc --debug your-program.c

現在調試您的 C 程序:

$ gdb a.out

這啟動了一個 gdb 交互式 shell。

在 gdb 本身中:

(gdb) list
(gdb) set follow-fork-mode parent
(gdb) breakpoint 69
  1. 列出你的代碼
  2. 告訴調試器在 fork() 時跟隨父級
  3. 在第 69 行設置斷點

運行程序:

(gdb) run batch.txt

它將在第 69 行暫停。執行下一行:

(gdb) next

打印一個變量:

(gdb) print *coms

繼續運行:

(gdb) continue

我將 rest 留給您探索。

我仍然不確定它有什么問題。 在您的 fork 因未知命令而失敗后, InputString()中發生了一些奇怪的事情。 InputString()開始從getchar()返回重復項。

我什至不知道你可以用標准輸入做到這一點。 也許只是以正常方式從文件中讀取,而不是破壞標准輸入,看看問題是否消失。

不要寫太多 linux 代碼,我正在嘗試做更多。 我取出 fork 並等待命令,以便可以在 mingw64 中構建它(因為 windows 構建不支持它們)並且似乎無法重現該問題。

所以我認為問題在於你要去那里的多線程設置。

“pid”變量在每個分叉之間共享。 這意味着當 fork 命令被調用時,“pid”被設置為循環中最后一個 fork 返回的值。

看起來您正在使用 if 語句檢查“pid”變量以查看該線程是否可以執行該命令。 但是主線程不會繼續運行嗎?

我不知道 fork() 返回什么,但“pid”未初始化,不知道這是否重要。

也許這有幫助?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM