模拟封装C库函数 + 添加重定向功能到myshell

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

模拟封装C库函数

添加重定向功能到myshell


模拟封装C库函数

下面我们来模拟实现如下fopen、fclose、fwrite、fflush这几个C库函数代码如下

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>

#define NUM 1024
#define NONE_FLUSH 0x0 //无缓冲
#define LINE_FLUSH 0x1 //行缓冲
#define FULL_FLUSH 0x2 //全缓冲

typedef struct _MyFILE
{
	int _fileno;
	char _buffer[NUM];
	int _end;//buffer缓冲区的结尾
	int _flags;//标记fflush刷新策略
}MyFILE;

//fopen
MyFILE* my_fopen(const  char* filename, const char* method)
{
	assert(filename);
	assert(method);

	int flags = O_RDONLY;

	if (strcmp(method, "r") == 0)
	{
	}
	else if (strcmp(method, "r+") == 0)
	{
	}
	else if (strcmp(method, "w") == 0)
	{
		flags = O_WRONLY | O_CREAT | O_TRUNC;//写入方式
	}
	else if (strcmp(method, "w+") == 0)
	{
	}
	else if (strcmp(method, "a") == 0)
	{
		flags = O_WRONLY | O_CREAT | O_APPEND;//追加方式
	}
	else if (strcmp(method, "a+") == 0)
	{
	}

	int fileno = open(filename, flags, 0666);
	if (fileno < 0)
	{
		return NULL;
	}
	MyFILE* fp = (MyFILE*)malloc(sizeof(MyFILE));
	if (fp == NULL) return fp;
	memset(fp, 0, sizeof(MyFILE));
	fp->_fileno = fileno;
	fp->_flags |= LINE_FLUSH;//默认设置为行缓冲
	fp->_end = 0;
	return fp;
}
//fflush
void my_fflush(MyFILE* fp)
{
	assert(fp);
	if (fp->_end > 0)
	{
		write(fp->_fileno, fp->_buffer, fp->_end);
		fp->_end = 0;
		syncfs(fp->_fileno);//把数据刷新到磁盘上
	}
}
//fwrite
void my_fwrite(MyFILE* fp, const char* start, int len)
{
	assert(fp);
	assert(start);
	assert(len > 0);

	//abcde123
	strncpy(fp->_buffer + fp->_end, start, len);//将数据写入到了缓冲区的结尾
	fp->_end += len;//使end永远指向有效字符的下一个位置

	if (fp->_flags & NONE_FLUSH)
	{
	}
	else if (fp->_flags & LINE_FLUSH)
	{
		if (fp->_end > 0 && fp->_buffer[fp->_end - 1] == '\n')
		{
			//仅仅是写入到内核中
			write(fp->_fileno, fp->_buffer, fp->_end);
			fp->_end = 0;
			syncfs(fp->_fileno);
		}
	}
	else if (fp->_flags & FULL_FLUSH)
	{
	}

}
//fclose
void my_fclose(MyFILE* fp)
{
	my_fflush(fp);//先刷新缓冲区
	close(fp->_fileno);
	free(fp);
}
int main()
{
	MyFILE* fp = my_fopen("log.txt", "w");
	if (fp == NULL)
	{
		printf("my_fopen error\n");
		return 1;
	}
	const char* s = "hello my 111\n";
	my_fwrite(fp, s, strlen(s));
	printf("消息立即刷新");
	sleep(2);

	const char* ss = "hello my 222";
	my_fwrite(fp, ss, strlen(ss));
	printf("写入了一个不满足刷新条件的字符串\n");
	sleep(2);

	const char* sss = "hello my 333";
	my_fwrite(fp, sss, strlen(sss));
	printf("写入了一个不满足刷新条件的字符串\n");
	sleep(2);

	const char* ssss = "end\n";
	my_fwrite(fp, ssss, strlen(ssss));
	printf("写入了一个满足刷新条件的字符串\n");
	sleep(2);

	const char* sssss = "aaaaaaa";
	my_fwrite(fp, sssss, strlen(sssss));
	printf("写入了一个不满足刷新条件的字符串\n");
	sleep(1);
	my_fflush(fp);
	sleep(2);
	my_fclose(fp);
	return 0;
}

gif动图演示


添加重定向功能到myshell

在我之前的一篇博文中对shell进行了模拟实现

不过上述实现的shell有一个不足之处无法完成重定向操作

在myshell当中添加重定向功能的步骤大致如下

  1. 实现一个CheckDir函数完成检测是否有重定向行为的函数
  2. 对于获取到的命令进行遍历判断若命令当中包含重定向符号 >、>>、<则该命令需要进行处理。为了方便后续操作我们把遇到的重定向符号设置为\0。
  3. 实现一个DROP_SPACE函数完成跳过重定向符号后的空格的操作
  4. 设置一个g_redir_flag变量重定向标志位当其为0时表示输入重定向为1时表示输出重定向为2时表示追加重定向。
  5. 设置一个g_redir_filename变量用来实时指向不同重定向后的文件
  6. 在fork创建进程那利用switch case语句完成不同重定向操作内部利用open及dup2函数完成重定向操作。

总体代码如下

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<assert.h>
#include<ctype.h>
#define SEP " "//分隔符
#define NUM 1024
#define SIZE 128

char command_line[NUM];
char* command_args[SIZE];
char env_buffer[NUM];//for test

#define NONE_REDIR - 1
#define INPUT_REDIR 0  //输入重定向
#define OUTPUT_REDIR 1 //输出重定向
#define APPEND_REDIR 2 //追加重定向
//跳过重定向符号多余的空格
#define DROP_SPACE(s) do { while (isspace(*s)) s++; } while (0)

int g_redir_flag = NONE_REDIR;//重定向标志位
char* g_redir_filename = NULL;//文件名
//对应上层的内建命令
int ChangeDir(const char* new_path)
{
    chdir(new_path);
    return 0;//调用成功
}

void PutEnvInMyShell(char* new_env)
{
    putenv(new_env);
}

//CheckDir检测
void CheckDir(char* commands)
{
    assert(commands);
    //[start, end)
    char* start = commands;//头指针
    char* end = commands + strlen(commands);//尾指针
    // ls -a -l>log.txt
        // 从前往后遍历
    while (start < end)
    {
        if (*start == '>')
        {
            if (*(start + 1) == '>')
            {
                //追加重定向
                *start = '\0';
                start += 2;//跳过多余的>使start指向空格或文件的位置
                DROP_SPACE(start);//跳过>>后多余的空格
                g_redir_flag = APPEND_REDIR;//设置为追加重定向
                g_redir_filename = start;//更新指向文件的地址
                break;
            }
            else
            {
                //输出重定向
                *start = '\0';
                start++;//使start指向空格或文件的位置
                DROP_SPACE(start);//跳过>后多余的空格
                g_redir_flag = OUTPUT_REDIR;//设置为输出重定向
                g_redir_filename = start;//更新指向文件的地址
                break;//解析完毕
            }
        }
        else if (*start == '<')
        {
            //输入重定向
            *start = '\0';
            start++;//时start指向空格或文件的位置
            DROP_SPACE(start);//跳过<后多余的空格
            g_redir_flag = INPUT_REDIR;//设置为输入重定向
            g_redir_filename = start;//更新指向文件的地址
            break;
        }
        else
        {
            start++;
        }
    }
}

int main()
{
    //shell本质就是一个死循环
    while (1)
    {
        g_redir_flag = NONE_REDIR;
        g_redir_filename = NULL;
        //1、显示提示符
        printf("[张三@我的主机名 当前目录]# ");
        fflush(stdout);

        //2、获取用户输入
        memset(command_line, '\0', sizeof(command_line) * sizeof(char));
        fgets(command_line, NUM, stdin);//键盘标准输入stdin获取到的是C风格的字符串'\0'结尾
        command_line[strlen(command_line) - 1] = '\0';//清空回车键的\n

        //$$重定向操作
        //ls -a -l>log.txt  或者  cat < file.txt  或者  ls -a -l>>log.txt 或者  ls -a -l                                                                                             
        //ls -a -l<log.txt -> ls -a -l\0log.txt
        CheckDir(command_line);

        //3、"ls -a -l -i" -> "ls" "-a" "-l" "-i" 字符串切分
        command_args[0] = strtok(command_line, SEP);
        int index = 1;
        //给ls命令添加颜色
        if (strcmp(command_args[0]/*程序名*/, "ls") == 0)
        {
            command_args[index++] = (char*)"--color=auto";
        }
        // = 是故意这么写的
            // strtok截取成功返回字符串起始地址;截取失败返回NULL
        while (command_args[index++] = strtok(NULL, SEP));
        //for debug
              //for (int i = 0; i < index; i++)
              //{
              //  printf("%d : %s\n", i, command_args[i]);
              //}

            //4、TOOD编写后面的逻辑内建命令
        if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL)
        {
            ChangeDir(command_args[1]);//让调用方进行路径切换父进程
            continue;
        }
        if (strcmp(command_args[0], "export") == 0 && command_args[1] != NULL)
        {
            //目前环境变量信息在command_line会被清空
                //此处我们需要自己保存一下环境变量内容
            strcpy(env_buffer, command_args[1]);
            PutEnvInMyShell(env_buffer);//export myval=100;
            continue;
        }
        //5、创建进程执行
        pid_t id = fork();
        if (id == 0)
        {
            int fd = -1;
            switch (g_redir_flag)
            {
            case NONE_REDIR:
                break;
            case INPUT_REDIR:
                fd = open(g_redir_filename, O_RDONLY);
                dup2(fd, 0);//输入重定向
                break;
            case OUTPUT_REDIR:
                fd = open(g_redir_filename, O_WRONLY | O_CREAT | O_TRUNC);
                dup2(fd, 1);//输出重定向
                break;
            case APPEND_REDIR:
                fd = open(g_redir_filename, O_WRONLY | O_CREAT | O_APPEND);
                dup2(fd, 1);//追加重定向
                break;
            default:
                printf("Bug?\n");
                break;
            }
            //child
                //6、程序替换
            execvp(command_args[0]/*此处下标0就是保存的我们要执行的程序名字*/, command_args);
            exit(1);//执行到这里子进程一定替换失败
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if (ret > 0)
        {
            printf("等待子进程成功sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF);
        }
    }//end while 
    return 0;
}

运行结果如下

gitee代码链接shell的模拟实现完整版

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: shell

“模拟封装C库函数 + 添加重定向功能到myshell” 的相关文章