目录
自主shell实现
获取基本变量
实现命令行
获取用户命令字符串
命令行字符串分割
内建命令CD()
chdir
getcwd
putenv
检查是否为内建命令
检查是否为重定向
执行命令
主函数设置
测试用例
项目代码
自主shell实现
根据之前学的内容,我们已经可以模拟一个shell的实现;一个shell程序需要循环做以下的事情: 1.获取命令行
2.解析命令行
3.创建子程序
4.替换子程序
5.父进程等待子进程退出
获取基本变量
首先我们根据shell发现我们要实现自助shell就要先获取以下变量
我们可以在env查看我们需要的变量,以及使用getenv()
获取我们需要的变量
//获取用户名
const char* GetUserName(){const char* name = getenv("USER");if(name == NULL) return "None";return name;
}const char* GetHome(){const char* home = getenv("HOME");if(home == NULL) return "/";return home;
}const char* GetHostName(){const char* hostname = getenv("HOSTNAME");if(hostname == NULL) return "None";return hostname;
}const char* GetCwd(){const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}
实现命令行
我们要把从env获取的内容拼成一个字符串
#define SIZE 512
#define SkipPath(p) do{ p+= (strlen(p)-1); while(*p != '/') p--; }while(0)void MakeCommandLineAndPrint(){char line[SIZE];const char* username = GetUserName();const char* hostname = GetHostName();const char* cwd = GetCwd();//只保留相对路径SkipPath(cwd);//写入指定缓冲区snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);//打印命令行printf("%s",line);fflush(stdout);
}
获取用户命令字符串
#define SIZE 512
#define ZERO '\0'//获取用户指令
int GetUserCommand(char command[], size_t n){char* s = fgets(command, n, stdin);if(s == NULL) return -1;//由于fgets也会获取我们输入的回车换行符//为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'command[strlen(command)-1] = ZERO;return strlen(command);
}
命令行字符串分割
认识字符串分割函数
char *strtok(char *str, const char *delim);参数说明:
str
:指向要分解的字符串的指针。在第一次调用时,这个参数应该是要分解的字符串;在随后的调用中,应该为NULL
,因为strtok
会保存上一次的位置。
delim
:包含分隔符的字符串。函数工作原理:
strtok
在第一次调用时,会在字符串str
中查找包含在delim
中的任意一个字符,找到后将其替换为 null 字符 ('\0'
),并返回指向该位置的指针。在随后的调用中,
strtok
会从上次替换的 null 字符之后开始查找,重复同样的过程。函数返回值:
成功时,
strtok
返回指向下一个标记的指针。当没有更多的标记时,返回
NULL
。注意事项:
strtok
会修改原始字符串,因为它会在找到的分隔符位置放置 null 字符。使用
strtok
时,通常需要使用一个临时变量来保存原始字符串的副本,以避免破坏原始数据。
【示例代码】
#include <stdio.h>
#include <string.h>int main() {char str[] = "a-b-c-d-e";char *token;/* 获取第一个标记 */token = strtok(str, "-");/* 继续获取其他标记 */while (token != NULL) {printf("%s\n", token);token = strtok(NULL, "-");}return 0;
}
在这个例子中,字符串 "a-b-c-d-e"
将被分割为 "a"
, "b"
, "c"
, "d"
, 和 "e"
,每个部分由换行符分隔打印出来。
#define SEP " "
#define NUM 32
#define SkipSpace(cmd, pos) do{\while(1){\if(isspace(cmd[pos]))\pos++;\else break;\}\
}while(0)char *gArgv[NUM];//分割命令
void SplitCommand(char command[], size_t n){(void)n; // "ls -a -l -n" -> "ls" "-a" "-l" "-n"gArgv[0] = strtok(command, SEP);int index = 1;//故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL//刚好让gArgv最后一个元素是NULL, 并且while判断结束while((gArgv[index++] = strtok(NULL,SEP)));
}
内建命令CD()
我们需要让父进程执行CD命令,我们打印的命令行是父进程打印的,如果子进程执行cd命令是无法进行路径切换的
认识几个函数
chdir
在 C 语言中,
chdir
函数用于更改当前工作目录。这个函数属于标准库中的unistd.h
头文件。int chdir(const char *path);参数说明:
path
:指向新工作目录路径的指针。函数返回值:
成功时,
chdir
返回 0。出错时,返回 -1,并且设置
errno
来指示错误。
chdir
函数的行为会根据不同的操作系统和文件系统有所不同,但它通常用于更改进程的当前工作目录。更改当前工作目录后,所有相对路径的文件操作都将相对于新的工作目录进行。
getcwd
在 C 语言中,
getcwd
函数用于获取当前工作目录的路径。这个函数定义在unistd.h
头文件中,它返回一个指向字符串的指针,该字符串包含了当前工作目录的绝对路径。char *getcwd(char *buf, size_t size);参数说明:
buf
:指向用于存储当前工作目录路径的缓冲区的指针。如果buf
是NULL
,则getcwd
会分配足够大的缓冲区来存储路径,调用者必须使用free
函数来释放这块内存。
size
:缓冲区buf
的大小。函数返回值:
成功时,
getcwd
返回指向当前工作目录路径字符串的指针,该字符串存储在buf
中。出错时,返回
NULL
,并设置errno
来指示错误。
putenv
在 C 语言中,
putenv
函数用于添加或修改环境变量。环境变量是影响程序运行的外部参数,它们通常用于配置程序的行为。putenv
函数定义在stdlib.h
头文件中。int putenv(char *string);参数说明:
string
:指向一个形式为 “name=value” 的字符串的指针,其中name
是环境变量的名称,value
是要赋予环境变量的值。函数返回值:
成功时,
putenv
返回 0。出错时,返回非零值,并设置
errno
来指示错误。使用
putenv
添加或修改环境变量后,这些更改仅对当前进程及其子进程有效,不会影响父进程或其他无关进程。
void Cd()
{const char *path = gArgv[1];if(path == NULL) path = GetHome();// path 一定存在chdir(path);// 刷新环境变量char temp[SIZE*2];getcwd(temp, sizeof(temp));snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd); // OK
}
★ps:如果不修改环境变量,则命令行上路径是变了,但打印的确没有变
检查是否为内建命令
char *gArgv[NUM];
int lastcode = 0;int CheckBuildin()
{int yes = 0;const char *enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0){yes = 1;Cd();}else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){yes = 1;printf("%d\n", lastcode);lastcode = 0;}return yes;
}
检查是否为重定向
void CheckRedir(char cmd[])
{// > >> <// "ls -a -l -n > myfile.txt"int pos = 0;int end = strlen(cmd);while(pos < end){if(cmd[pos] == '>'){if(cmd[pos+1] == '>'){cmd[pos++] = 0;pos++;redir_type = App_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}else{cmd[pos++] = 0;redir_type = Out_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}}else if(cmd[pos] == '<'){cmd[pos++] = 0;redir_type = In_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}else{pos++;}}
}
执行命令
void ExecuteCommand()
{pid_t id = fork();if(id < 0) Die();else if(id == 0){//重定向设置if(filename != NULL){if(redir_type == In_Redir){int fd = open(filename, O_RDONLY);dup2(fd, 0);}else if(redir_type == Out_Redir){int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);dup2(fd, 1);}else if(redir_type == App_Redir){int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}else{}}// childexecvp(gArgv[0], gArgv);exit(errno);}else{// fahterint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);}}
}
主函数设置
int main(){int quit = 0;while(!quit){//1.我们需要自己输出一个命令行MakeCommandLineAndPrint(); //2.获取用户命令字符串char usercommand[SIZE];int n = GetUserCommand(usercommand, sizeof(usercommand));if(n <= 0) return 1;//检测是否为重定向CheckRedir(usercommand);// 3. 命令行字符串分割. SplitCommand(usercommand, sizeof(usercommand));//4.检测是否为内建命令n = CheckBuildin();if(n) continue;// 5. 执行命令ExecuteCommand();}return 0;
}
测试用例
[wuxu@Nanyi myshell]$ gcc -o myshell myshell.c -std=c99
myshell.c: In function ‘Cd’:
myshell.c:152:2: warning: implicit declaration of function ‘putenv’ [-Wimplicit-function-declaration]putenv(cwd); // OK ^
[wuxu@Nanyi myshell]$ pwd
/home/wuxu/lesson20/myshell
[wuxu@Nanyi myshell]$ cd ..
[wuxu@Nanyi lesson20]$ pwd
/home/wuxu/lesson20
[wuxu@Nanyi lesson20]$ ls
log.txt myshell test test1.c test2.c
[wuxu@Nanyi lesson20]$ ls -a
. .. log.txt myshell test test1.c test2.c
[wuxu@Nanyi lesson20]$ ls -a -l
total 36
drwxrwxr-x 3 wuxu wuxu 4096 Sep 1 17:43 .
drwx------ 11 wuxu wuxu 4096 Sep 1 20:50 ..
-rw-rw-rw- 1 wuxu wuxu 50 Aug 31 19:49 log.txt
drwxrwxr-x 2 wuxu wuxu 4096 Sep 1 21:15 myshell
-rwxrwxr-x 1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r-- 1 wuxu wuxu 468 Aug 31 19:36 test1.c
-rw-rw-r-- 1 wuxu wuxu 413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ echo $?
0
[wuxu@Nanyi lesson20]$ echo "hello wuxu" > log.txt
[wuxu@Nanyi lesson20]$ ll
total 28
-rw-rw-rw- 1 wuxu wuxu 11 Sep 1 21:16 log.txt
drwxrwxr-x 2 wuxu wuxu 4096 Sep 1 21:15 myshell
-rwxrwxr-x 1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r-- 1 wuxu wuxu 468 Aug 31 19:36 test1.c
-rw-rw-r-- 1 wuxu wuxu 413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ echo "hello Nanyi" >> log.txt
[wuxu@Nanyi lesson20]$ ll
total 28
-rw-rw-rw- 1 wuxu wuxu 23 Sep 1 21:16 log.txt
drwxrwxr-x 2 wuxu wuxu 4096 Sep 1 21:15 myshell
-rwxrwxr-x 1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r-- 1 wuxu wuxu 468 Aug 31 19:36 test1.c
-rw-rw-r-- 1 wuxu wuxu 413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ ^C
[wuxu@Nanyi lesson20]$
项目代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p+= (strlen(p)-1); while(*p != '/') p--; }while(0)
#define SkipSpace(cmd, pos) do{\while(1){\if(isspace(cmd[pos]))\pos++;\else break;\}\
}while(0)// "ls -a -l -n > myfile.txt"
#define None_Redir 0
#define In_Redir 1
#define Out_Redir 2
#define App_Redir 3int redir_type = None_Redir;
char *filename = NULL;// 为了方便,我就直接定义了
char cwd[SIZE*2];
char *gArgv[NUM];
int lastcode = 0;void Die()
{exit(1);
}//获取用户名
const char* GetUserName(){const char* name = getenv("USER");if(name == NULL) return "None";return name;
}const char* GetHome(){const char* home = getenv("HOME");if(home == NULL) return "/";return home;
}const char* GetHostName(){const char* hostname = getenv("HOSTNAME");if(hostname == NULL) return "None";return hostname;
}const char* GetCwd(){const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}void MakeCommandLineAndPrint(){char line[SIZE];const char* username = GetUserName();const char* hostname = GetHostName();const char* cwd = GetCwd();//只保留相对路径SkipPath(cwd);//写入指定缓冲区snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);//打印命令行printf("%s",line);fflush(stdout);
}//获取用户指令
int GetUserCommand(char command[], size_t n){char* s = fgets(command, n, stdin);if(s == NULL) return -1;//由于fgets也会获取我们输入的回车换行符//为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'command[strlen(command)-1] = ZERO;return strlen(command);
}//分割命令
void SplitCommand(char command[], size_t n){(void)n; // "ls -a -l -n" -> "ls" "-a" "-l" "-n"gArgv[0] = strtok(command, SEP);int index = 1;//故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL//刚好让gArgv最后一个元素是NULL, 并且while判断结束while((gArgv[index++] = strtok(NULL,SEP)));
}void ExecuteCommand(){pid_t id = fork();if(id < 0 ) Die();else if(id == 0){//重定向设置if(filename != NULL){if(redir_type == In_Redir){int fd = open(filename,O_RDONLY);dup2(fd,0);}}else if(redir_type == Out_Redir){int fd = open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);dup2(fd,0);}else if(redir_type == App_Redir){int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}else{}//childexecvp(gArgv[0],gArgv);exit(errno); }else{//fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){//wait success}}
}void Cd(){const char* path = gArgv[1];if(path == NULL) path = GetHome();//path 一定存在chdir(path);//刷新环境变量char temp[SIZE*2];getcwd(temp, sizeof(temp));snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd); // OK
}int CheckBuildin(){int yes = 0;const char *enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0){yes = 1;Cd();}else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){yes = 1;printf("%d\n",lastcode);lastcode = 0;
}return yes;
}void CheckRedir(char cmd[]){// > >> <// "ls -a -l -n > myfile.txt"int pos = 0;int end = strlen(cmd);while(pos < end){if(cmd[pos] == '>'){//是否为追加重定向if(cmd[pos+1] == '>'){//是追加重定向cmd[pos++] = 0;pos++;redir_type = App_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}else{//是输出重定向cmd[pos++] = 0;redir_type = Out_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}}else if(cmd[pos] == '<')//是输入重定向{cmd[pos++] = 0;redir_type = In_Redir;SkipSpace(cmd,pos);filename = cmd + pos;}else{pos++;}}
}int main(){int quit = 0;while(!quit){//1.我们需要自己输出一个命令行MakeCommandLineAndPrint(); //2.获取用户命令字符串char usercommand[SIZE];int n = GetUserCommand(usercommand, sizeof(usercommand));if(n <= 0) return 1;//检测是否为重定向CheckRedir(usercommand);// 3. 命令行字符串分割. SplitCommand(usercommand, sizeof(usercommand));//4.检测是否为内建命令n = CheckBuildin();if(n) continue;// 5. 执行命令ExecuteCommand();}ret