shell执行的命令通常有两种
站在系统的角度,只要是能够被读取或者能够被写出的设备都可以叫做文件。
进程运行起来所处的路径为当前路径。
命令行>一个为文件
包含3个头文件
第一个参数为打开的目标文件,第二个表示打开文件时所需要的参数,参数的传入用或运算|
参数:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
以上三个常数,必须指定一个且只能指定一个
O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限(注意默认的权限)
O_APPEND:追加写
打开成功则返回文件的描述符
打开失败返回:-1
fd就是文件描述符的小整数。 下面三个是系统默认打开的 0:键盘 1:显示器 2:显示器
文件描述符的本质是数组的下标
fd的分配原则是:最小的,没有被占用的文件描述符
看下面这个代码就没有验证上面的结论
cpp#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的时候,那么发生的就是重定向。
cpp#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
cpp#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
就被写到了文件中。
看代码:
cpp#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;
}
shell#当我们直接把结果打印到显示器上的时候
./myfile
#结果就是
maole is cool
emmmmmmmmmmm
#当我们把结果重定向到test.txt中的时候
#test.txt文件的内容为
emmmmmmmmmmm
maole is cool
maole is cool
为什么c语言提供的接口打印2次,而系统提供的接口打印1次?
C语言它有缓存区的概念,当执行fork的时候,当代码执行完的时候,数据还没有刷新,当刷新的时候,父进程的数据就会进行写实拷贝
就会刷新到文件,而刷新到文件就是更新数据,所以要进行写实拷贝——即子进程就要对原来缓存区的数据进行拷贝。
系统的接口直接进入内核的缓存区中,此时父进程就没有数据了,那么子进程也就不能没有数据进行拷贝,那么最后的结果就只有一份数据
为什么显示到显示器上的时候就是一次呢?
因为显示到显示器中行刷新,当执行fork的时候,数据已经刷新到显示器中,fork再进行创建子进程的时候也就没缓存区的数据了。
如果把上面代码中的\n
去掉的话,结果就和写到文件中是一样的,因为没有进行行刷新
看代码:
cppint 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操作 看下面这段代码
cppint main()
{
printf("hjhgfdfghj");
close(1);
return 0;
}
我们发现什么都没有打印。 为什么会这样呢?
是因为在关闭标准输出之后
close(1)
,数据还没有进入标准输出的文件之中。那么最后程序执行完毕之后,也就不会显示什么内容。
相关信息
标准输出stdout
标准错误stderr
都是显示到显示器上,那么他们之间有什么差别
虽然1,2都是对应的打开显示器文件,但是他们是不同的,可以认为是同一个文件被打开了两次。 看下面这个代码:
cpp#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
中了如果把所有的信息打印到同一个文件中:看下面的命令
cpp#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中添加重定向
cpp#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;
}