Linux C程式設計一站式學習程式設計練習:實現簡單的Shell
阿新 • • 發佈:2018-12-15
Linux C程式設計一站式學習P585程式設計練習:
實現簡單的Shell
用講過的各種C函式實現一個簡單的互動式Shell,要求:
- 給出提示符,讓使用者輸入一行命令,識別程式名和引數並呼叫適當的exec函式執行程式,待執行完成後再次給出提示符。
- 識別和處理以下符號: · 簡單的標準輸入輸出重定向(<和>):仿照例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$