Linux基础 7:自定义shell的编写
自定义shell的编写
- 一.自定义shell的编写。
- 1.打印命令行提示符。
- 2.获取用户输入字符串进行字符串分割保存。
- 3.调用系统调用接口执行命令(使用子进程)
- 4.对于内建命令的特殊处理。
- 1.cd
- 2.cd 特殊符号识别:
- 5.export和echo作为内建命令:
- 1.export
- 2.echo
- 二 . 文件部分:
- 1.语言级别的IO输入:
- 1.程序默认打开的三个流:
- 2.操作系统上的接口:
- 1.基本概念:
- 2.系统调用接口:
- 3.文件描述符:
- 4.语言方面的文件结构体:
- 5.系统方面的文件结构体:
- 6.read write close 访问过程!
- 3.实现重定向:
- 1.fd的分配规则:
- 2.重定向的优化:
- 4.缓冲区的意义:
- 1.无缓冲&&行缓冲&&完全缓冲
- 2.证明语言层缓冲区的存在:
- 3.缓冲区的语言结构---->os底层结构:
- 5.实现一个FILE类型的结构体封装系统调用:
- 1.mystdio.h
- 2.mystdio.c
- 3.file_text.c
一.自定义shell的编写。
1.打印命令行提示符。

1.命令行提示符由哪几部分组成?
[用户名@主机名 文件路径]$
2.通过getenv函数获取环境变量内容返回对应字符串。
3.变量保存字符串然后打印。
void Get_Hint()
{char* Username = getenv("USER");char* Hostname = getenv("HOSTNAME");char* Pwd = getenv("PWD");if(strcmp(Username,"root")==0){printf("[root@%s %s]#",Hostname,Pwd);}else{printf("[%s@%s %s]$",Username,Hostname,Pwd);}
}
2.获取用户输入字符串进行字符串分割保存。
1.变量保存获取到的一行字符串。
2.定义全局的char* 类型的数组保存数据。
void Get_String(char* arr)
{//1.获取一行字符串scanf("%[^\n]s",arr);
}void Slicing(char* arr)
{int i=0;slistr[i++] = strtok(arr,Group);while(slistr[i++] = strtok(NULL,Group));//1.命令显示颜色的条件:if(strcmp(slistr[0],"ls")==0){slistr[i-1] = (char*)("--color");slistr[i] = NULL;}
}
3.调用系统调用接口执行命令(使用子进程)

1,使用子进程去执行接口命令。
2.int execvp(const char* file , char * const argv[]);
3.参数一传命令 ,参数二传命令+选项的一个char* 类型的数组。
4.可以去执行非内建命令。
void execute()
{int id = fork();if (id == 0){execvp(slistr[0], slistr);exit(1);}int status = 0;waitpid(id, &status, 0);
}

4.对于内建命令的特殊处理。

1.cd
1,内建命令:执行命令应该要改变当前的路径,使用子进程去执行的命令影响不了bash进程。改变不了操作系统当前的一个工作路径。应该让父进程去执行cd命令才可以改变bash工作路径。
2.进入execute函数进行判断当前执行的命令是不是内建命令或者普通命令。
3.内建命令需要进行单独的判断:


2.cd 特殊符号识别:
1.我们知道cd存在特殊符号可以通过getcwd()函数去获取当前的工作路径。
2.通过putenv()函数进行环境变量的更新。
3.有效的解决特殊符号存在环境变量中的问题!
void cmd()
{//1.考虑对内建命令进行处理:if(strcmp(slistr[0],"cd")==0){if(strcmp(slistr[1],"~")==0){strcpy(nearpath,getenv("PWD"));chdir(getenv("OLDPWD"));}else if (strcmp(slistr[1],"-")==0){if(flagnear == 0){strcpy(nearpath,getenv("PWD"));chdir(nearpath);flagnear = 1;return;}else{char* tmp = getenv("PWD");chdir(nearpath);strcpy(nearpath,tmp);}}else if (strcmp(slistr[1],"..")==0 || strcmp(slistr[1],"../")==0){strcpy(nearpath,getenv("PWD"));chdir("..");}else{strcpy(nearpath,getenv("PWD"));chdir(slistr[1]);}//更新环境变量char temp[1024];//获取当前的工作路径!getcwd(temp,1024);snprintf(path,SIZE,"PWD=%s",temp);putenv(path);return;}
}


5.export和echo作为内建命令:
1.export
1.export设置一个新的环境变量:
2.使用setenv()
3.当overwrite值为0时,按照name和value去给环境变量新增内容:
void Export(int * flag)
{if(strcmp(slistr[0],"export")==0){char str[2048] = {0};strcpy(str,slistr[1]);int pos = 0;while(str[pos++] != '=');str[pos-1] = '\0';setenv(str,str+pos,0);*flag = 1;}return;
}
2.echo

1.echo 空格 ---->打印空格:
2.echo 字符串 -----> 打印字符串:
3.echo $环境变量名称 ---->打印环境变量内容
4.echo $?打印最近一次的进程退出码。
void execute()
{int flag = 0;//1.cd 判断if(flag == 0)cmd(&flag); //2.export判断if(flag == 0)Export(&flag);//3.echo判断:if(flag == 0)Echo(&flag);//4.创建子进程处理普通命令if(flag == 0){int id = fork();if(id == 0){execvp(slistr[0],slistr);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) lastcode = WEXITSTATUS(status); }
}void Echo(int* flag)
{if(strcmp(slistr[0],"echo")==0){if(slistr[1] == NULL)printf("\n");else if (slistr[1][0] == '$'){if(slistr[1][1] == '?')printf("%d\n",lastcode);elseprintf("%s\n",getenv(slistr[1]+1));}else printf("%s\n",slistr[1]);*flag = 1;}
}

二 . 文件部分:
1.语言级别的IO输入:
1.程序默认打开的三个流:
1.标准输入流:stdin ---->键盘
2.标准输出流:stdout ---->显示器
3.标准错误流:stderr ---->显示器

1.因为程序开始执行就默认打开三个流。
2.stdin stdout stdree 类型FILE* 类型的变量。
3.我们使用语言的文件操作都会使用fopen函数打开一个文件流!
4.使用fclose去关闭一个文件流。
功能------------ 函数名------------ 适用于
字符输入函数-- fgetc ---------------所有输入流
字符输出函数 --fputc ---------------所有输出流
文本行输入函数-- fgets------------ 所有输入流
文本行输出函数 --fputs ------------所有输出流
格式化输入函数 --fscanf ----------所有输入流
格式化输出函数 --fprintf -----------所有输出流
二进制输入 --fread ------------------文件
二进制输出 —fwrite ------------------文件
2.操作系统上的接口:

1.基本概念:
1.操作系统提供系统调用接口,语言方面去进行上层的封装。
2.我们C语言的文件操作就是封装了操作系统提供的系统调用接口。
2.系统调用接口:
fopen----->open

1.int open(const char *pathname, int flags, mode_t mode);
2.pathname:需要打开的文件名称。
3.flags:是一个位图32位的有效保存不同的文件打开要求。
4.mode_t mode:是一个八进制的文件权限掩码(写文件并且文件不存在)

1.O_RDONLY : 读权限
2.O_WRONLY : 写权限
3.O_RDWR : 可读可写权限 ----->一个open只能使用上面三个的一个。
4.O_CREAT : 读一个文件但是这个文件没有创建。
5.补充:O_APPEND : 写文件的时候可以进行文件内容的追加。
6.O_TRUNC : 写一个文件从头开始之前的内容删除。
综上所述:可以使用|把上面的内容搭配在一起可以实现C语言文件操作上的只读,只写(没有创建) , 追加 , 内容。
18 int main()19 {20 //1.写方式打开文件如果没有就创建再一次打开覆盖写入:21 int id = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC);22 //2.写方式打开文件如果没有就创建再一次打开追加内容:
E> 23 int id = open("log.txt",O_WRONLY|O_CREAT|O_APPEND); 24 return 0;25 }
3.文件描述符:
1.概念:open函数的返回值就是文件描述符:
2.在task_struct 中保存了一个指针指向一个数组,数组中保存了一个file*
3.数组的下标就是文件描述符:
4.进程默认打开三个文件,开始的时候0 , 1 , 2 下标就被使用!



4.语言方面的文件结构体:
1.FILE*
2.stdin stdout stderr 都是FILE* 类型的数据。
3.FILE中一定有一个成员是int fd(文件标识符)
4.fopen函数访问FILE对象,调用成员内容本质还是底层封装了系统调用接口。
5.系统方面的文件结构体:
1.file 类型是系统封装的文件类型。
2.主要包括三个内容:属性 缓冲区 方法集
3.方法集:函数指针保存对应外设提供的方法。
4.缓冲区:保存数据
5.属性:文件属性
6.read write close 访问过程!



访问过程:第一个参数就是文件描述符表,根据struct files_struct * files 和fd找到对应文件的指针,访问操作系统的文件结构体,然后对文件结构体做操作。可以实现read write close 。
3.实现重定向:
1.fd的分配规则:
1.stdin stdout stderr 分配为0 , 1 , 2 三个下标。
2.关闭一个stdin —> open一个新的文件那么0位置中保存新的文件指针。
3.通过关闭+创建的方式可以实现文件的重定向。
2.重定向的优化:

1.使用dup2命令可以帮我们把数组中的下标位置的文件指针做修改。
2.具体规则如下:
1.如何完成重定向。
2.oldfd —>新的文件的fd
3.newfd是三个流的任意一个。
4.dup2(fd , 0)dup2(fd , 1)dup2(fd , 2)
5.第2个参数的不同实现输入重定向输出重定向错误重定向

//定义的宏
#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)void CheckRedir(char in[])
{// ls -a -l// ls -a -l > log.txt// ls -a -l >> log.txt// cat < log.txtredir_type = NoneRedir;filename = NULL;int pos = strlen(in) - 1;while( pos >= 0 ){if(in[pos] == '>'){if(in[pos-1] == '>'){redir_type = AppendRedir;in[pos-1] = STREND;pos++;IgnSpace(in, pos);filename = in+pos;break;}else{redir_type = StdoutRedir;in[pos++] = STREND;IgnSpace(in, pos);filename = in+pos;//printf("debug: %s, %d\n", filename, redir_type);break;}}else if(in[pos] == '<'){redir_type = StdinRedir;in[pos++] = STREND;IgnSpace(in, pos);filename = in+pos;//printf("debug: %s, %d\n", filename, redir_type);break;}else{pos--;}}
}
4.缓冲区的意义:
1.开辟内存+提高效率。
2.缓冲区和操作系统内核没有关系,语言层自带缓冲区:
1.无缓冲&&行缓冲&&完全缓冲
1.程序结束语言自动刷新数据到系统。
2.程序默认是行刷新!
3.特殊情况改变语言缓冲区到os缓冲区的刷新方式。
2.证明语言层缓冲区的存在:
1.向显示器打印,刷新方案就是行刷新。
2.向文件中进行重定向,刷新方案变成全缓冲。
3.产生子进程发生写时拷贝。
4.子进程拷贝一份父进程缓冲区数据。
5.结尾进行全部刷新产生两份12345666666


3.缓冲区的语言结构---->os底层结构:

5.实现一个FILE类型的结构体封装系统调用:
1.mystdio.h
#pragma once#include <stdio.h>#define SIZE 4096typedef struct _myFILE
{//char inbuffer[];char outbuffer[SIZE];int pos;int cap;int fileno;
}myFILE;myFILE *my_fopen(const char *pathname, const char *mode);
int my_fwrite(myFILE *fp, const char *s, int size);
void my_fclose(myFILE *fp);
2.mystdio.c
#include "mystdio.h"
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>myFILE *my_fopen(const char *pathname, const char *mode)
{int flag = 0;if(strcmp(mode, "r") == 0){flag |= O_RDONLY;}else if(strcmp(mode, "w") == 0){flag |= (O_CREAT|O_WRONLY|O_TRUNC);}else if(strcmp(mode, "a") == 0){flag |= (O_CREAT|O_WRONLY|O_APPEND);}else{return NULL;}int fd = 0;if(flag & O_WRONLY){umask(0);fd = open(pathname, flag, 0666);}else{fd = open(pathname, flag);}if(fd < 0) return NULL;myFILE *fp = (myFILE*)malloc(sizeof(myFILE));if(fp == NULL) return NULL;fp->fileno = fd;return fp;
}
int my_fwrite(myFILE *fp, const char *s, int size)
{return write(fp->fileno, s, size);
}
void my_fclose(myFILE *fp)
{close(fp->fileno);free(fp);
}
3.file_text.c
#include "mystdio.h"
#include <string.h>const char *filename = "./log.txt";int main()
{myFILE *fp = my_fopen(filename, "w");if(fp == NULL) return 1;const char *s = "hello myself stdio interface\n";my_fwrite(fp, s, strlen(s));my_fclose(fp);return 0;
}

