綜合例項:用C語言實現一個自定義的shell程式
一個shell需要實現若干功能,比如解釋執行命令,支援輸入輸出重定向,支援管道,後臺執行程式等。首先對要實現的功能做一個簡要介紹:
(1)輸出重定向:就是把執行某命令後的結果輸出到某個檔案。例如:
ls -al > list.txt
(2)輸入重定向:這裡的<不是類似於輸出重定向中的操作符>,而是從檔案中取出資料到指定的檔案中。例如:
cat > newfile < test3
上述命令將test3中的資料取出,然後輸入到newfile這個檔案中。
(3)管道:就是先後執行2個命令,把前一個命令的結果當成後一個命令的輸入。例如:
ls -a | grep mysql
上例就是先顯示所有檔案,然後在ls命令的顯示結果裡用grep命令查詢包含mysql的檔案。
(4)命令後臺執行:可以在命令結尾加一個&讓此程式在後臺執行,即使shell關閉也不影響程式的繼續執行。
關鍵函式的功能及說明
(1)void print_prompt()
本函式只打印myshell的提示符,即“myshell$$”。
(2)void get_input(char *buf)
獲得一條使用者輸入的待執行的命令,引數buf用於存放輸入的命令。如果輸入的命令過長(大於256個字元),則終止程式。命令以換行符“\n”作為結束標誌。
(3)void explain_input(char *buf, int *argcount, char arglist[100][256])
解析buf中存放的命令,把每個選項存放在arglist中。如輸入的命令“ls -l /tmp”,則arglist[0]、arglist[1]、arglist[2]指向的字串分別為“ls”、“-l”、“/tmp”。
(4)do_cmd(int argcount, char arglist[100][256])
執行arglist中存放的命令,argcount為待執行命令的引數個數。
(5)int find_command(char *command)
功能是分別在當前目錄下、/bin、/usr/bin目錄下查詢命令的可執行程式。
myshell.c程式原始碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#define normal 0 /* 一般的命令 */
#define out_redirect 1 /* 輸出重定向 */
#define in_redirect 2 /* 輸入重定向 */
#define have_pipe 3 /* 命令中有管道 */
void print_prompt(); /* 列印提示符 */
void get_input(char *); /* 得到輸入的命令 */
//二維陣列作為形參,必須指明列的數字
void explain_input(char *, int *, char a[ ][256]); /* 對輸入命令進行解析 */
void do_cmd(int, char a[ ][256]); /* 執行命令 */
int find_command(char *); /* 查詢命令中的可執行程式 */
int main(int argc, char **argv)
{
int i;
int argcount = 0;
char arglist[100][256];
char **arg = NULL;
char *buf = NULL;
buf = (char *)malloc(256);
if( buf == NULL ) {
perror("malloc failed");
exit(-1);
}
while(1) {
/* 將buf所指向的空間清零 */
memset(buf, 0, 256);
print_prompt();
get_input(buf);
/* 若輸入的命令為exit或logout則退出本程式 */
if( strcmp(buf,"exit\n") == 0 || strcmp(buf,"logout\n") == 0 )
break;
for (i=0; i < 100; i++)
{
arglist[i][0]='\0';
}
argcount = 0;
explain_input(buf, &argcount, arglist);
do_cmd(argcount, arglist);
}
if(buf != NULL) {
free(buf);
buf = NULL;
}
exit(0);
}
void print_prompt()
{
printf("myshell$$ ");
}
/*獲取使用者輸入*/
void get_input(char *buf)
{
int len = 0;
int ch;
ch = getchar();
while (len < 256 && ch != '\n') {
buf[len++] = ch;
ch = getchar();
}
if(len == 256) {
printf("command is too long \n");
exit(-1); /* 輸入的命令過長則退出程式 */
}
buf[len] = '\n';
len++;
buf[len] = '\0';
}
/* 解析buf中的命令,將結果存入arglist中,命令以回車符號\n結束 */
/* 例如輸入命令為"ls -l /tmp",則arglist[0]、arglist[1]、arglsit[2]分別為ls、-l和/tmp */
void explain_input(char *buf, int *argcount, char arglist[100][256])
{
char *p = buf;
char *q = buf;
int number = 0;
while (1) {
if ( p[0] == '\n' )
break;
if ( p[0] == ' ' )
p++;
else {
q = p;
number = 0;
while( (q[0]!=' ') && (q[0]!='\n') ) {
number++;
q++;
}
strncpy(arglist[*argcount], p, number+1);
arglist[*argcount][number] = '\0';
*argcount = *argcount + 1;
p = q;
}
}
}
void do_cmd(int argcount, char arglist[100][256])
{
int flag = 0;
int how = 0; /* 用於指示命令中是否含有>、<、| */
int background = 0; /* 標識命令中是否有後臺執行識別符號& */
int status;
int i;
int fd;
char* arg[argcount+1];
char* argnext[argcount+1];
char* file;
pid_t pid;
/*將命令取出*/
for (i=0; i < argcount; i++) {
arg[i] = (char *) arglist[i];
}
arg[argcount] = NULL;
/*檢視命令列是否有後臺執行符*/
for (i=0; i < argcount; i++) {
if (strncmp(arg[i], "&",1) == 0) {
if (i == argcount-1) {
background = 1;
arg[argcount-1] = NULL;
break;
}
else {
printf("wrong command\n");
return ;
}
}
}
for (i=0; arg[i]!=NULL; i++) {
if (strcmp(arg[i], ">") == 0 ) {
flag++;
how = out_redirect;
if (arg[i+1] == NULL)
flag++;
}
if ( strcmp(arg[i],"<") == 0 ) {
flag++;
how = in_redirect;
if(i == 0)
flag++;
}
if ( strcmp(arg[i],"|")==0 ) {
flag++;
how = have_pipe;
if(arg[i+1] == NULL)
flag++;
if(i == 0 )
flag++;
}
}
/* flag大於1,說明命令中含有多個> ,<,|符號,本程式是不支援這樣的命令的
或者命令格式不對,如"ls -l /tmp >" */
if (flag > 1) {
printf("wrong command\n");
return;
}
if (how == out_redirect) { /*命令只含有一個輸出重定向符號> */
for (i=0; arg[i] != NULL; i++) {
if (strcmp(arg[i],">")==0) {
file = arg[i+1];
arg[i] = NULL;
}
}
}
if (how == in_redirect) { /*命令只含有一個輸入重定向符號< */
for (i=0; arg[i] != NULL; i++) {
if (strcmp (arg[i],"<") == 0) {
file = arg[i+1];
arg[i] = NULL;
}
}
}
if (how == have_pipe) { /* 命令只含有一個管道符號| */
/* 把管道符號後門的部分存入argnext中,管道後面的部分是一個可執行的shell命令 */
for (i=0; arg[i] != NULL; i++) {
if (strcmp(arg[i],"|")==0) {
arg[i] = NULL;
int j;
for (j=i+1; arg[j] != NULL; j++) {
argnext[j-i-1] = arg[j];
}
argnext[j-i-1] = arg[j];
break;
}
}
}
if ( (pid = fork()) < 0 ) {
printf("fork error\n");
return;
}
switch(how) {
case 0:
/* pid為0說明是子程序,在子程序中執行輸入的命令 */
/* 輸入的命令中不含>、<和| */
if (pid == 0) {
if ( !(find_command(arg[0])) ) {
printf("%s : command not found\n", arg[0]);
exit (0);
}
execvp(arg[0], arg);
exit(0);
}
break;
case 1:
/* 輸入的命令中含有輸出重定向符> */
if (pid == 0) {
if ( !(find_command(arg[0])) ) {
printf("%s : command not found\n",arg[0]);
exit(0);
}
fd = open(file,O_RDWR|O_CREAT|O_TRUNC,0644);
dup2(fd,1);
execvp(arg[0],arg);
exit(0);
}
break;
case 2:
/* 輸入的命令中含有輸入重定向符< */
if (pid == 0) {
if ( !(find_command (arg[0])) ) {
printf("%s : command not found\n",arg[0]);
exit(0);
}
fd = open(file,O_RDONLY);
dup2(fd,0);
execvp(arg[0],arg);
exit(0);
}
break;
case 3:
/* 輸入的命令中含有管道符| */
if(pid == 0) {
int pid2;
int status2;
int fd2;
if ( (pid2 = fork()) < 0 ) {
printf("fork2 error\n");
return;
}
else if (pid2==0) {
if ( !(find_command(arg[0])) ) {
printf("%s : command not found\n",arg[0]);
exit(0);
}
fd2 = open("/tmp/youdonotknowfile",
O_WRONLY|O_CREAT|O_TRUNC,0644);
dup2(fd2, 1);
execvp(arg[0], arg);
exit(0);
}
if (waitpid(pid2, &status2, 0) == -1)
printf("wait for child process error\n");
if ( !(find_command(argnext[0])) ) {
printf("%s : command not found\n",argnext[0]);
exit(0);
}
fd2 = open("/tmp/youdonotknowfile",O_RDONLY);
dup2(fd2,0);
execvp (argnext[0],argnext);
if ( remove("/tmp/youdonotknowfile") )
printf("remove error\n");
exit(0);
}
break;
default:
break;
}
/* 若命令中有&,表示後臺執行,父程序直接返回不等待子程序結束 */
if ( background == 1 ) {
printf("[process id %d]\n",pid);
return ;
}
/* 父程序等待子程序結束 */
if (waitpid (pid, &status,0) == -1)
printf("wait for child process error\n");
}
/* 查詢命令中的可執行程式 */
int find_command (char *command)
{
DIR* dp;
struct dirent* dirp;
char* path[] = { "./", "/bin", "/usr/bin", NULL};
/* 使當前目錄下的程式可以被執行,如命令"./fork"可以被正確解釋和執行 */
if( strncmp(command,"./",2) == 0 )
command = command + 2;
/* 分別在當前目錄、/bin和/usr/bin目錄查詢要可執行程式 */
int i = 0;
while (path[i] != NULL) {
if ( (dp = opendir(path[i]) ) == NULL)
printf ("can not open /bin \n");
while ( (dirp = readdir(dp)) != NULL) {
if (strcmp(dirp->d_name,command) == 0) {
closedir(dp);
return 1;
}
}
closedir (dp);
i++;
}
return 0;
}
執行結果: