文件描述符+深入理解缓冲区+Shell(实现简单的命令行解释器)

当前路径即为当前进程的工作路径,故其工作路径可以被修改

文件描述符的分配规则:

在当前进程中,从files_struct数组当中,找到当前没有被使用的 最小的一个下标,作为新的文件描述符。

 程序替换时不影响pcb及其内部的pid细节。

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


#define NUM 1024
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_ENDIR 2
#define APPEND_ENDIR 3
#define trimSpace(start) do{\
            while(isspace(*start)) ++start;\
                           }while(0)

#define OPT_NUM 64


char lineCommand[NUM];
char* myargv[OPT_NUM];
int lastCode=0;
int lastSig=0;

int redirType=NONE_REDIR;
char* redirFile=NULL;

void commandCheck(char* commands)
{
    assert(commands);
    char* start=commands;
    char* end=commands+strlen(commands);
    while(start<end)
    {
        if(*start=='>')
        {
            *start=0;
            start++;
            if(*start == '>')
            {
                //"ls -a >> file.log"
                redirType=APPEND_ENDIR;
                start++;
            }
            else
            {
                redirType=OUTPUT_ENDIR;
            }
            trimSpace(start);
            redirFile=start;
            break;
        }
        else if(*start=='<')
        {
            *start='\0';
            start++;
            trimSpace(start);
            redirType=INPUT_REDIR;
            redirFile=start;
            break;
        }
        else
        {
            start++;
        }
    }
}

int main()
{
    while(1)
    {
    redirType=NONE_REDIR;
    redirFile=NULL;
    errno=0;
    printf("用户名@主机名 当前路径#");
    fflush(stdout);
    char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
    assert(s!=NULL);
    (void)s;
    lineCommand[strlen(lineCommand)-1]=0;
    //printf("test:%s\n",lineCommand);
    commandCheck(lineCommand);
    //字符串切割
    myargv[0]=strtok(lineCommand," ");
    int i=1;
        if(myargv[0] != NULL && strcmp(myargv[0],"ls")==0)
        {
            myargv[i++]=(char*)"--color=auto";
        }
        if(myargv[0]!=NULL&&myargv[1]!=NULL&&strcmp(myargv[0],"echo")==0)
        {
            if(strcmp(myargv[1],"$?")==0)
            {
                printf("%d,%d\n",lastCode,lastSig);
            }
            else
            {
                printf("%s\n",myargv[1]);
            }
            continue;
        }
    while(myargv[i++]=strtok(NULL," "));
        //若是cd命令则不需要创建子进程,让shell执行对应的命令,本质是执行系统接口。
        //向cd这种不需要子进程执行而是shell执行的命令---内建/内置命令
        if(myargv[0]!=NULL&&strcmp(myargv[0],"cd")==0)
        {
            if(myargv[1]!=NULL) chdir(myargv[1]);
            continue;
        }
#ifdef DEBUG
    for(int i=0;myargv[i];i++)
    {
        printf("myargv[%d]:%s\n",i,myargv[i]);
    }
#endif
        //执行命令
        pid_t id=fork();
        assert(id!=-1);
        if(id==0)
        {
            //因为命令是子进程执行的,真正重定向的工作一定是由子进程来完成
            //如何重定向?父进程给子进程提供信息
            //
            switch(redirType)
            {
                case NONE_REDIR:
                break;
                case INPUT_REDIR:
                {
                    int fd=open(redirFile,O_RDONLY);
                    if(fd<0)
                    {
                        perror("open");
                        exit(errno);
                    }
                    dup2(fd,0);
                }
                break;
                case OUTPUT_ENDIR:
                case APPEND_ENDIR:
                {
                    umask(0);
                    int flags=O_WRONLY | O_CREAT;
                    if(redirType==APPEND_ENDIR) flags |= O_APPEND;
                    else flags|=O_TRUNC;
                    int fd=open(redirFile,flags,0666);
                    if(fd<0)
                    {
                        perror("open");
                        exit(errno);
                    }
                    dup2(fd,1);
                }
                break;
                default:
                printf("bug?\n");
            }
            execvp(myargv[0],myargv);
            exit(1);
        }

        //if(id==0)
        //{
        //    execvp(myargv[0],myargv);
        //    exit(1);
        //}
        int status=0;
        pid_t ret=waitpid(id,&status,0);
        assert(ret>0);
        (void)ret;
        lastCode=((status>>8)&0xff);
        lastSig=(status&0x7f);
    }
    return 0;
}

一个简单的shell脚本,命令行解释器。

缓冲区的本质就是一段内存。

FILE的本质是结构体,结构体中有一段为缓冲区。

#include<stdio.h>
#include<string.h>
#include<unistd.h>

int main()
{
    //C接口
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");
    const char* fputsString="hello fputs\n";
    fputs(fputsString,stdout);

    //系统接口
    const char* wstring ="hello write\n";
    write(1,wstring,strlen(wstring));
    


    return 0;
}

 结果:

hello printf
hello fprintf
hello fputs
hello write
cat myfile.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>

int main()
{
    //C接口
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");
    const char* fputsString="hello fputs\n";
    fputs(fputsString,stdout);

    //系统接口
    const char* wstring ="hello write\n";
    write(1,wstring,strlen(wstring));
    
    fork();

    return 0;
}

 结果:

[lzy@centos myfile]$ ./myfile > log.txt
[lzy@centos myfile]$ cat log.txt
hello write
hello printf
hello fprintf
hello fputs
hello printf
hello fprintf
hello fputs

 缓存区刷新策略问题

共识:如果有一块数据,一次性写入到外设效率最高。

缓冲区会结合具体的设备,定制自己的刷新策略。

a.立即刷新-----无缓冲

b.行刷新(\n)------行缓存---显示器

c.缓冲区满-----全缓冲---磁盘文件

1.用户强制刷新

2.进程退出---一般要进行缓冲区刷新

 //代码结束之前,进行子进程创建
    //1.没有> ,可以得到4条输出结果
    //stdout默认使用行刷新,在fork之前,三条c函数已经将数据打印到了显示器上,FILE,及进程内部不在存在对应的数据了
    //2.如果进行 > , 写入的目标不再是显示器(stdout) 而是普通文件,采用的刷新策略是全缓存,之前的3条c函数,虽然带了\n,但是不足以将stdout缓冲区写满!数据并没有刷新
    //执行fork()函数时,stdout属于父进程,创建子进程,后进程退出!!!先退出的要进行缓冲区刷新(修改)
    //写时拷贝,!!! 数据会显示两次
    //3.write没有FILE,而是fd,就没有c提供的缓冲区。
 

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值