目录
自主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
{}
}
// child
execvp(gArgv[0], gArgv);
exit(errno);
}
else
{
// fahter
int 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 3
int 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{}
//child
execvp(gArgv[0],gArgv);
exit(errno);
}
else{
//father
int 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