shell执行的命令通常有两种
第三方提供的对应的在磁盘中有具体二进制文件的可执行程序(由子进程程序执行
shell内部,自己实现的方法,由自己(父进程)来执行
什么叫做文件
站在系统的角度,只要是能够被读取或者能够被写出的设备都可以叫做文件。
当前路径
进程运行起来所处的路径为当前路径。
直接清空
命令行>一个为文件
系统的文件访问的接口
open
包含3个头文件
第一个参数为打开的目标文件,第二个表示打开文件时所需要的参数,参数的传入用或运算|
参数:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
以上三个常数,必须指定一个且只能指定一个
O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限(注意默认的权限)
O_APPEND:追加写
打开成功则返回文件的描述符
打开失败返回:-1
write
read
close
lseek
文件描述符——fd
fd就是文件描述符的小整数。
下面三个是系统默认打开的
0:键盘
1:显示器
2:显示器
文件
被进程打开的文件(内存文件)
没有被打开的文件(磁盘上,文件=内容+属性)(磁盘文件)
文件描述符的本质是数组的下标
fd的分配原则是:最小的,没有被占用的文件描述符
看下面这个代码就没有验证上面的结论
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { close(2); int fd=open("test.txt",O_RDWR|O_CREAT,0666); if(fd<0) perror("open"); printf("%d\n",fd); close(fd); return 0; }
结果输出的就是2
上面关闭的2.
当关闭1的时候,那么发生的就是重定向。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { close(1); int fd=open("test.txt",O_RDWR|O_CREAT,0666); if(fd<0) perror("open"); printf("%d\n",fd); fflush(stdout); close(fd); return 0; }
我们发现1就没有在显示器上打印出来,而是写到了test.txt
中
为什么会这样呢?看下面的这个图
就是因为把1号文件描述符关上之后,打开的新的文件就会占用1。导致原本可以输入到显示器中的,现在显示到文件中。
重定向的本质:
像上面那样我们还需要手动的去关闭——close(1)
。其实系统提供了这样的接口
dup2
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main() { int fd=open("test.txt",O_RDWR|O_CREAT,0666); if(fd<0) perror("open"); dup2(fd,1); printf("hhhhhhh\n"); close(fd); return 0; }
hhhhhhh
就被写到了文件中。
语言的缓存区
看代码:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main() { int fd=open("test.txt",O_RDWR|O_CREAT,0666); if(fd<0) perror("open"); fprintf(stdout,"maole "); fprintf(stdout,"is cool\n"); char str[]="emmmmmmmmmmm\n"; //系统接口 write(1,str,sizeof(str)); fork(); close(fd); return 0; }
#当我们直接把结果打印到显示器上的时候 ./myfile #结果就是 maole is cool emmmmmmmmmmm #当我们把结果重定向到test.txt中的时候 #test.txt文件的内容为 emmmmmmmmmmm maole is cool maole is cool
为什么c语言提供的接口打印2次,而系统提供的接口打印1次?
C语言它有缓存区的概念,当执行fork的时候,当代码执行完的时候,数据还没有刷新,当刷新的时候,父进程的数据就会进行写实拷贝
就会刷新到文件,而刷新到文件就是更新数据,所以要进行写实拷贝——即子进程就要对原来缓存区的数据进行拷贝。
系统的接口直接进入内核的缓存区中,此时父进程就没有数据了,那么子进程也就不能没有数据进行拷贝,那么最后的结果就只有一份数据
为什么显示到显示器上的时候就是一次呢?
因为显示到显示器中行刷新,当执行fork的时候,数据已经刷新到显示器中,fork再进行创建子进程的时候也就没缓存区的数据了。
如果把上面代码中的\n去掉的话,结果就和写到文件中是一样的,因为没有进行行刷新
看代码:
int main() { int fd=open("test.txt",O_RDWR|O_CREAT,0666); if(fd<0) perror("open"); fprintf(stdout,"maole "); fprintf(stdout,"is cool"); char str[]="emmmmm"; write(1,str,sizeof(str)); fork(); close(fd); return 0; }
结果就是:
emmmmmmaole is coolmaole is cool
缓存区在哪里?
缓存区就在系统的内核中,系统的内核有该结构体存储。
语言的缓存区,是语言自己封装的。
为什么要用缓冲区?
缓存区只是语言上存在的
缓冲区的刷新策略
立即刷新
行刷新(遇到\n)
满刷新
还有一些特殊的情况:
用户强制刷新:比如fflush
进程退出
缓存区的存在可以提高效率,减少I/O操作
看下面这段代码
int main() { printf("hjhgfdfghj"); close(1); return 0; }
我们发现什么都没有打印。
为什么会这样呢?
是因为在关闭标准输出之后
close(1)
,数据还没有进入标准输出的文件之中。那么最后程序执行完毕之后,也就不会显示什么内容。
:::info
标准输出stdout
标准错误stderr
都是显示到显示器上,那么他们之间有什么差别
:::
虽然1,2都是对应的打开显示器文件,但是他们是不同的,可以认为是同一个文件被打开了两次。
看下面这个代码:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <assert.h> #include <string.h> #include <stdlib.h> #include <errno.h> int main() { errno=1; printf("stdout 1\n"); perror("stderr 1"); errno=2; printf("stdout 2\n"); perror("stderr 2"); return 0; }
在命令行中输入:
- 当运行
./myfile
的时候,发现都输出到显示器中了。 - 把./myfile重定向到ok.txt文件中的时候,只要标准输出的显示到文件中,标准错误的还是显示到显示器上
再在后面添加2 >err.txt,就把错误的信息打印到err.txt中了
如果把所有的信息打印到同一个文件中:看下面的命令
其实对上面的命令,我们要知道它的本质——把新建fd替换1、2文件描述符
模拟实现缓存区
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <assert.h> #include <string.h> #include <stdlib.h> struct MyFile_ { int fd; char buffer[1024]; int end; };//缓冲区 typedef struct MyFile_ MyFile; MyFile* fopen_(const char* path,const char* mode) { assert(path); assert(mode); MyFile* ret=NULL; if(strcmp(mode,"r")==0) { } else if(strcmp(mode,"w")==0) { } else if(strcmp(mode,"r+")==0) { } else if(strcmp(mode,"w+")==0) { int fd=open(path,O_WRONLY|O_CREAT,0666); if(fd>=0) { ret=(MyFile*) calloc(1,sizeof(MyFile)); ret->fd=fd; ret->end=0; } } else if(strcmp(mode,"a")==0) { } else if(strcmp(mode,"a+")==0) { } else{ } return ret; } void fputs_(const char* str,MyFile* ret) { assert(ret); strcpy(ret->buffer+ret->end,str); ret->end+=strlen(str); if(ret->fd==0) { } else if(ret->fd==1) { //标准输出 if(ret->buffer[ret->end-1]=='\n') { write(ret->fd,ret->buffer,ret->end); ret->end=0; } } else if(ret->fd==2) { } else{ } } //刷新 void fflush_(MyFile* ret) { assert(ret); write(ret->fd,ret->buffer,ret->end); //刷新到内核的缓存区 syncfs(ret->fd); ret->end=0; } //关闭 //关闭的时候也要进行刷新 void fclose_(MyFile* ret) { assert(ret); fflush_(ret); close(ret->fd); free(ret); } int main() { MyFile* p=fopen_("test.txt","w+"); fputs_("mao le id cool",p); fork(); fclose_(p); return 0; }
更新模拟shell
在模拟的shell中添加重定向
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <assert.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define INPUT 1 #define OUTPUT 2 #define APPEND 3 #define NOOPT 0 int option=NOOPT; char* inspaced(char* start) { assert(start); char* end=start+strlen(start)-1; while(start<=end) { if(*end=='>') { option=INPUT; *end='\0'; if(*(end-1)=='>') { option=APPEND; } end++; break; } else if(*end=='<') { option=OUTPUT; *end='\0'; end++; break; } else { --end; } } if(start<=end) return end; else return NULL; } char temp[20]; int main() { extern char** environ; //查看环境变量 // int i=0; // while(environ[i]) // { // printf("%s\n",environ[i++]); // } static char* opt[100]; static char sstr[100]; while(1) { printf("[root@ml is cool root]# "); fflush(stdout); memset(opt,0,100); //scanf("%s",sstr); if(fgets(sstr,100,stdin)==NULL) continue; sstr[strlen(sstr)-1]='\0'; // 检查字符是否为重定向 char* file_name=inspaced(sstr); int i=0; char* str; for(str=strtok(sstr," ");str!=NULL;str=strtok(NULL," ")){ opt[i++]=str; } if(strcmp(opt[0],"ls")==0) opt[i++]="--color=auto"; // printf("i=%d,opt[0]=%s\n",i,opt[0]); else if(strcmp(opt[0],"ll")==0) { i=0; opt[i++]="ls"; opt[i++]="-l"; opt[i++]="--color=auto"; } else if(strcmp(opt[0],"cd")==0) { if(opt[1]) chdir(opt[1]); } else if(strcmp(opt[0],"export")==0&&opt[1]!=NULL) { // strcpy(temp,opt[1]); // putenv(temp); putenv(opt[1]); } opt[i]=NULL; // for(i=0;opt[i];i++) // printf("%s\n",opt[i]); // sleep(100); pid_t id=fork(); if(id==0) { // printf("ML:%s\n",getenv("ML")); // printf("PATH:\n%s\n",getenv("PATH")); if(file_name) { int fd=-1; switch(option) { case INPUT: fd=open(file_name,O_WRONLY | O_TRUNC | O_CREAT, 0666); dup2(fd,1); break; case OUTPUT: fd=open(file_name,O_CREAT|O_RDWR); dup2(fd,0); break; case APPEND: fd=open(file_name, O_WRONLY | O_APPEND | O_CREAT, 0666); dup2(fd,1); break; default: break; } } // execvpe(opt[0],opt,environ); execvp(opt[0], opt); exit(1); } pid_t status=0; pid_t ret=waitpid(id,&status,0); if(ret) { if(WIFEXITED(status)) { printf("return no error %d\n",WEXITSTATUS(status)); } else{ printf("return error %d\n",status&0x7f); } } } return 0; }