1. 程式人生 > >Linux C程式設計一站式學習程式設計練習:實現簡單的Shell

Linux C程式設計一站式學習程式設計練習:實現簡單的Shell

Linux C程式設計一站式學習P585程式設計練習:

實現簡單的Shell

用講過的各種C函式實現一個簡單的互動式Shell,要求:

  1. 給出提示符,讓使用者輸入一行命令,識別程式名和引數並呼叫適當的exec函式執行程式,待執行完成後再次給出提示符。
  2. 識別和處理以下符號: · 簡單的標準輸入輸出重定向(<和>):仿照例30.5 “wrapper”,先dup2然後exec。 · 管道(|):Shell程序先呼叫pipe建立一對管道描述符,然後fork出兩個子程序,一個子程序關閉讀端,呼叫dup2把寫端賦給標準輸出,另一個子程序關閉寫端,呼叫dup2把讀端賦給標準輸入,兩個子程序分別呼叫exec執行程式,而Shell程序把管道的兩端都關閉,呼叫wait等待兩個子程序終止。

你的程式應該可以處理以下命令: ○ls△-l△-R○>○file1○ ○cat○<○file1○|○wc△-c○>○file1○ ○表示零個或多個空格,△表示一個或多個空格

思路:

main函式(主程序)以一個迴圈從標準輸入stdin不斷讀取指令,直到ctrl+c退出。讀取的指令首先作為字串存放在input_str中,然後呼叫split函式對其進行拆分,根據管道(|)切割成多條指令,存放在cmds中。程式假設拆分後得到的指令最多10條。

按順序處理cmds中的每一條指令:對每條指令,fork一個子程序並呼叫exec函式處理它。exec函式中使用take_token函式對指令根據空格和重定向符進行拆分,拆分後得到記錄執行檔案和引數的陣列token(token[0]為指令的可執行檔名,作為execvp函式的第一個引數;整個token陣列作為execvp函式的第二個引數)。若有重定向符">“或”<"存在,則讀取緊跟重定向符的字串作為檔名,根據符號重定向輸入或輸出到指定檔案。 程式中使用管道fd[10][2]作為多條指令輸入輸出之間的通訊手段:假設當前執行的為第n條指令

  • 若當前指令有上一條指令,從管道fd[n-1][0]讀取上一條指令的輸出作為輸入,並關閉管道寫端fd[n-1][1](57~61行);
  • 若當前指令有下一條指令,重定向其輸出到管道fd[n][1]被下一條指令讀取,並關閉管道讀端fd[n][0](63~65行);
  • 若當前指令是最後一條指令,恢復輸出到標準輸出stdout;
  • 以上步驟中子程序都由父程序fork而來,因此屬於子程序之間通過管道相互通訊,父程序需要關閉管道的讀寫兩段。

在程式中我的邏輯是,管道fd[n]溝通第n和第n+1個指令,因此fork出第n+1個子程序時,父程序可以關閉第n個(即前一個)管道的讀寫端。 (這裡每次沒關乾淨管道,下此迴圈開始又呼叫pipe,不知道會不會有洩露之類的。。實在不太懂,如果有人能幫我指出就非常感謝_(:3」∠)_)

我的程式原始碼如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#define BUFF 512

int split(char input_str[], char *cmds[]);
void take_token(char cmd[], char *token[], char *file[]);
void exec(char cmd[]);

/* test:
cat < file1|wc -c > file1
cat<file1 | wc -c > file1
cat     < file1| wc -c>file1
cat < file1 | wc -c > file1     
ls -l -R > file1
ls -l -R>   file1
ls -l -R >file1   
*/

int main()
{
    char input_str[BUFF];  // 輸入指令字串
    char *cmds[10];        // 分割完的指令字串
    int fd[10][2];         // 管道
    int i, pid, cmd_num;
    int save_stdin, save_stdout;
    
    save_stdin = dup(STDIN_FILENO);
    save_stdout = dup(STDOUT_FILENO);
    
    while (1) {
        printf("username:path$ ");
        fgets(input_str, BUFF, stdin);
        cmd_num = split(input_str, cmds);
        
        if (cmd_num > 1) {
            if (pipe(fd[0]) < 0) {   // 當前pipe
                perror("pipe");
                exit(1);
            }
        }
        
        i = 0;
        while (cmds[i] != NULL) {
/*             printf("%s\n", cmds[i]);
            i++; */
            pid = fork();
            if (pid < 0) {
                perror("fork");
                exit(1);
            } else if (pid == 0) {   // 當前指令子程序
                /* printf("1\n"); */
                if (i > 0) {         // 若有上一條,讀上一pipe並關閉寫端
                    /* printf("2\n"); */
                    close(fd[i-1][1]);
                    dup2(fd[i-1][0], STDIN_FILENO);
                }
                if (cmds[i+1] != NULL) {     // 若有下一條,寫當前pipe並關閉讀端
                    close(fd[i][0]);
                    dup2(fd[i][1], STDOUT_FILENO);
                } else {   // 當前指令是最後一條,恢復標準輸出
                    dup2(save_stdout, STDOUT_FILENO);
                }
                exec(cmds[i]);
            } else {                 // 當前指令父程序
                if (cmds[i+1] != NULL && cmds[i+2] != NULL) {
                    if (pipe(fd[i+1]) < 0) {   // 當前pipe
                        perror("pipe");
                        exit(1);
                    }
                }
                
                if (i > 0) {         // 兩次fork後關閉上一條指令的父程序pipe讀寫
                    close(fd[i-1][0]); // 順序:fork子1,fork子2,關閉父讀寫
                    close(fd[i-1][1]); // 這個if塊寫在i++後面會阻塞子程序。。
                }
                waitpid(pid, NULL, 0);           // 等待指令執行結束
                i++;

            }
        }
    }
    return 0;
}

int split(char input_str[], char *cmds[])
{
    int i = 0; // 指令個數
    char *str = NULL, *saveptr = NULL;
    char *enter = NULL;
    
    for (i=0, str=input_str; ; i++, str=NULL){
        cmds[i] = strtok_r(str, "|", &saveptr);
        if (cmds[i] == NULL) {
            enter = strrchr(cmds[i-1], '\n');
            *enter = ' ';   // 替換最末尾換行符
            break;
        }
    }
    return i;
}

void take_token(char cmd[], char *token[], char *file[]) {
    int i;
    char *op;
    char *str = NULL, *saveptr = NULL;
    int fd, std_fileno, file_mode;
    
    if ((op = strrchr(cmd, '<')) != NULL) {
        std_fileno = STDIN_FILENO;
        file_mode = O_RDONLY;
    }
    else if ((op = strrchr(cmd, '>')) != NULL) {
        std_fileno = STDOUT_FILENO;
        file_mode = O_WRONLY | O_CREAT | O_TRUNC;
    }
    

    if (op) {
        *op = '\0';
        *file = strtok_r((op+1), " ", &saveptr);
        fd = open(*file, file_mode, 0666);
        if (fd < 0) {
            perror("open");
            exit(1);
        }

        dup2(fd, std_fileno);

        //printf("[[%s]]", *file);
    }

    for (i=0, str=cmd, saveptr = NULL; ; i++, str=NULL) {
        token[i] = strtok_r(str, " ", &saveptr);
        if (token[i] == NULL)
            break;
    }
    return ;
}

void exec(char cmd[])
{
    char *tokens[100];
    char *str, *saveptr, *file;
    int i, mode;
    
    take_token(cmd, tokens, &file);

    execvp(tokens[0], tokens);
    perror(tokens[0]);
    return ;
}

執行結果示例:

[email protected]:/mnt/c/Users/saltyfish/Desktop/test$ ./test1
username:path$ ls
1.jpg  clean  file1  file2  img  Makefile  pytest  test  test1  test1.c  test1.o  test.cpp  tmp  tmp.c
username:path$ ls -l
total 72
-rwxrwxrwx 1 qxy qxy  7448 Dec 11  2017 1.jpg
drwxrwxrwx 1 qxy qxy  4096 Oct  3 14:59 clean
-rwxrwxrwx 1 qxy qxy 10216 Oct 17 12:45 file1
-rwxrwxrwx 1 qxy qxy     4 Oct  3 14:26 file2
drwxrwxrwx 1 qxy qxy  4096 Oct  3 15:21 img
-rwxrwxrwx 1 qxy qxy    93 Jun 23  2017 Makefile
drwxrwxrwx 1 qxy qxy  4096 Oct  3 15:21 pytest
drwxrwxrwx 1 qxy qxy  4096 Oct  3 15:21 test
-rwxrwxrwx 1 qxy qxy 13560 Oct 17 12:26 test1
-rwxrwxrwx 1 qxy qxy  4420 Oct 17 12:26 test1.c
-rwxrwxrwx 1 qxy qxy  4784 Oct 17 12:26 test1.o
-rwxrwxrwx 1 qxy qxy   974 Mar 25  2018 test.cpp
-rwxrwxrwx 1 qxy qxy  9936 Sep 23 10:06 tmp
-rwxrwxrwx 1 qxy qxy   779 Oct 16 22:35 tmp.c
username:path$ ls -l -R > file1
username:path$ cat <file1 | wc -c >file2
username:path$ cat file2
10216
username:path$