自定义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);
else
printf("%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.txt
redir_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 4096
typedef 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;
}