文件内有3个特定的文件标识符,stdin,stdout,stderr所以在我们打印fd的时候会发现从3开始,不过当我们close的时候会有如下发现
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define FILE_NAME(number) "log.txt" #number
int main()
{
//第一次执行0
close(0);
//第二次执行2
//close(2);
//第三次执行1
//close(1);
int fd0=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_APPEND,0666);
printf("%d\n",fd0);
return 0;
}
执行close(0)以后:
把stdin(键盘)即fd=0(键盘)改到文件,从文件输入内容,然后输出到显示器中
执行close(2)以后:
以上我们可以得到一个规律,即那一个被close掉,fd就会去占用它
执行close(1)以后:
这个需要我们查看log.txt1,具体原因在于fd即log.txt1占用了stdout,把原本输出到显示器中的内容输出到了文件中
在实际使用中我们通常使用dup2这个系统调用(不过这个fd输出依旧是3),这个就叫做重定向
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define FILE_NAME(number) "log.txt" #number
int main()
{
int fd0=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_APPEND,0666);
dup2(fd0,0);
printf("%d\n",fd0);
return 0;
}
那么重定向的功能就可以把shell再完善一下,这样就可以支持ls -l > log.txt ls-l >> log.txt cat < log.txt的功能
#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<ctype.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
char LineCommand[NUM];
#define OPT_NUM 64
char* myargv[OPT_NUM];//指针数组
int lastcode=0;
int lastsign=0;
//普通
#define NONE_REDIR 0
//输入重定向
#define INPUT_REDIR 1
//输出重定向
#define OUTPUT_REDIR 2
//追加重定向
#define APPEND_REDIR 3
#define trimSpace(start) do{ while(isspace(*start)) ++start; }while(0)
//用来确认是什么重定向,默认没有
int redirType = NONE_REDIR;
char* redirFile=NULL;
void commandCheck(char* command)
{
char* start=command;
char* end=command+strlen(command);
while(start<end)
{
if(*start=='>')
{
*start='\0';
++start;
if(*start=='>')
{
//有两个>>是追加重定向
redirType=APPEND_REDIR;
++start;
}
//否则只有一个,是输出重定向
else
{
redirType=OUTPUT_REDIR;
}
//跳过空格
trimSpace(start);
redirFile=start;
break;
}
else if(*start=='<')
{
redirType=INPUT_REDIR;
*start='\0';
++start;
trimSpace(start);
redirFile=start;
break;
}
++start;
}
}
int main()
{
while(1)
{
char LineCommand[NUM]={0};
char* myargv[OPT_NUM]={NULL};
//输出提示符
printf("用户名@主机 当前路径# ");
//刷新缓冲区,因为没有\n不会刷新缓冲区
fflush(stdout);
//获取用户输入,预留一个\0,所以需要-1
char* s=fgets(LineCommand,sizeof(LineCommand)-1,stdin);
assert(s!=NULL);
保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用
(void)s;
//这里会把用户的\n也输入进去,就会有2个\n
//清楚最后一个\n
LineCommand[strlen(LineCommand)-1]='\0';
//printf("test:%s\n",LineCommand);
//分割,例如ls -l > log.txt 会被分割成ls -l \0 log.txt stroke读到\0结束
commandCheck(LineCommand);
//我们输入的是
//"ls -a -l" 但是系统想要读取的是"ls" "-a" "-l"
//字符串切割
//用空格切割
myargv[0]=strtok(LineCommand," ");
int i=1;
if(myargv[0]!=NULL&&strcmp(myargv[0],"ls")==0)
{
myargv[i++]="--color=auto";
}
//如果没有子串strtok会返回NULL
//而我们的exec*系列的函数需要以NULL结束
while(myargv[i++]=strtok(NULL," "));
//子进程运行结束以后,继续用的还是父进程,即shell
if(myargv[0]!=NULL&&strcmp(myargv[0],"cd")==0)
{
//我们执行cd ..没有用,原因在于我们改变的是子进程的目录,父进程的目录没有改变
//这种指令需要我们父进程自己执行,这种指令叫做内置/内建指令
if(myargv[1]!=NULL)
{
chdir(myargv[1]);
continue;
}
}
if(myargv[0]!=NULL&&myargv[1]!=NULL&&strcmp(myargv[0],"echo")==0)
{
if(strcmp(myargv[1],"$?")==0)
{
printf("%d,%d\n",lastcode,lastsign);
}
else
{
printf("%s\n",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>=0);
(void)id;
if(id==0)
{
//child
switch(redirType)
{
case NONE_REDIR : break;
case OUTPUT_REDIR : {int fd=open(redirFile,O_WRONLY|O_CREAT|O_TRUNC,0666); dup2(fd,1); break;}
case APPEND_REDIR : {int fd=open(redirFile,O_WRONLY|O_CREAT|O_APPEND,0666); dup2(fd,1); break;}
case INPUT_REDIR : {int fd=open(redirFile,O_RDONLY); dup2(fd,0);break;}
default: printf("错误\n"); break;
}
execvp(myargv[0],myargv);
//只要返回一定失败
exit(-1);
}
int status=0;
pid_t ret = waitpid(id,&status,0);
lastcode=(status>>8)&0xff;
lastsign=status&0x7f;
}
return 0;
}
linux下一切皆文件