目录
重定向
文件属性操作
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
ssize_t read(int fd, void *buf, size_t count);文件内容操作
read() 函数用于从文件描述符 fd 指定的文件中读取数据,将读取的数据存储到buf指向的内存缓冲区中,最多读取count字节的数据。函数返回实际读取的字节数,如果返回值为0,则表示已到达文件末尾;如果返回值为-1,则表示出现错误,此时会设置errno变量以指示具体的错误类型。
浅谈重定向
fileno是struct FILE 封装了fd
那么重定向的本质就是在内核中改变文件描述符表特定下标的内容,和上层无关。接口调用只管1,不管指向谁。
说白了就是上层不变,底层改变
当不加fflush(stdout)时数据出不来,而加fflush(stdout)数据就出来了
正常进程结束后会刷新缓冲区,但是这次不正常,在return之前要刷新的时候我close了,数据还在1号stdout那里,但是我无法通过1号写到os里,所以直接没了。把close注释掉就可以。
深入重定向
dup2
那么每次都要fflush和close很麻烦,要没有简洁的方法?就是dup2()
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(){
int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0){
perror("open");
return 2;
}
dup2(fd,1);
printf("fd:%d\n",fd);
fprintf(stdout,"fprintf,fd:%d\n",fd);
close(fd);
return 0;
}
如果不关闭文件描述符,数据可能不会立即刷新到文件中。这是因为操作系统通常会对文件进行缓冲,直到缓冲区满或文件被关闭时,数据才会被写入磁盘。
如果程序长时间运行而不关闭文件,可能会出现以下情况:
- 数据未写入:输出可能会滞留在缓冲区中,而不及时写入文件。
- 数据丢失风险:在程序异常终止的情况下,缓冲区中的数据可能会丢失。
为确保数据及时写入文件,使用 fflush(stdout)
可以手动刷新输出缓冲区,但最好还是在不再需要写入时调用 close(fd)
。这样可以确保资源被妥善管理,并确保所有数据都已写入。
最终不在显示屏输出数据,而是在log.txt中输出因为上层调用fd值1,但是fd值1指向了log.txt
缓冲区
缓冲区的理论理解
缓冲区:用户缓冲区和内核缓冲区。
几乎所有的缓冲区都有两种好处:解耦和提高效率
提高效率:主要提高的是使用者的效率和提高刷新IO的效率
我们知道调用系统调用是有成本的(没有量化的标准),所以尽量少调用效率就提高了。
比如我们使用10次printf/fprintf把数据放到c语言提供的缓冲区,10次调用printf数据都在c语言提供的缓冲区,最后只需调用一次系统调用就可以把数据写到os缓冲区,效率提高了
缓冲区是什么?其实就是一段内存空间。由c语言维护就叫语言级缓冲区,由c++维护就叫语言级缓冲区,由os维护就叫内核级缓冲区。
为什么要有缓冲区?要给上层提供良好的IO体验,(并且对大家都好,我os积攒一大批数据往硬件上刷新,我只需要硬件配合我一次。我语言层积攒一大批数据往os刷新只需os配合我一次),间接提高整体效率
缓冲区怎么办?
a.刷新策略
1. 立即刷新(相当于这个缓冲区存在价值不大,相当于没缓冲而且多了次拷贝)--fflush/fsync
2. 行刷新。显示器(显示器是给用户看的,所以行刷新主要照看用户的习惯,不快不慢)
3. 全缓冲。缓冲区写满才刷新,对应普通文件
b. 特殊情况
1. 进程退出,强制退出
2. 强制
策略代表都适用,所以上述策略用户级能用,内核级也能用,但是我们不关心内核策略我们不关心,所以只关心用户级。
代码分析
#include<unistd.h>
2 #include<stdio.h>
3 #include<stdlib.h>
4 #include<string.h>
5 int main(){
6 printf("hello printf\n");
7 fprintf(stdout,"hello fprintf\n");
8 const char*ch="hello write\n";
9 write(1,ch,strlen(ch));
10 // fork();
11 return 0;
12 }
fork之后父子进程各有一份C语言输出缓冲区了,所以父子进程都会输出,但是write因为不向缓冲区中写入,所以子进程创建出来无法输出write的数据
vim /usr/include/libio.h 查看c语言提供的缓冲区,同时也可以看出条件编译
那么c++也得有,其他任何语言也得有
进程的程序替换不会影响进程关联的或者打开的文件。
代码详解
重定向shell
输入重定向:
filename是指针加下标方式
测试代码:
测试:
完整代码:
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include <fcntl.h>
#include<errno.h>
#include<ctype.h>
#define SIZE 512
#define NUM 32 //最多32个选项
#define skippath(p)do{p+=(strlen(p)-1);while(*p!='/')p--;}while(0)
#define skipspace(cmd,pos) do{while(1){if(isspace(cmd[pos]))pos++;else break;}}while(0)
#define NONE_REDIR 0 //无重定向
#define IN_REDIR 1 //输入重定向
#define OUT_REDIR 2 //输出重定向
#define APP_REDIR 3 //追加重定向
int redir_type=NONE_REDIR;//重定向类型
char*filename=NULL;//文件名
char cwd[SIZE*2];
int lastcode=0;
const char* getpwd(){
const char* cwd=getenv("PWD");
if(cwd==NULL)return "None";
return cwd;
}
const char* getname(){
const char* name=getenv("USER");
if(name==NULL)return "None";
return name;
}
const char* gethostname(){
const char* hostname=getenv("HOSTNAME");
if(hostname==NULL)return "None";
return hostname;
}
const char* gethome(){
const char*home=getenv("HOME");
if(home==NULL)home="/root";
return home;
}
void makeline(){
char line[SIZE];//命令行
const char* hostname=gethostname();
const char* name=getname();
const char* cwd=getpwd();
skippath(cwd);
snprintf(line,sizeof(line),"[%s@%s %s]>",name,hostname,strlen(cwd)==1?"/":cwd+1);
printf("%s",line);
fflush(stdout);
}
int getcommandline(char usercommand[],size_t n){
char*s=fgets(usercommand,n,stdin);
if(s==NULL)return -1;
s[strlen(s)-1]='\0';
return strlen(usercommand);
}
char*gargv[NUM];
void splitcommand(char usercommand[],size_t t){
(void)t;//代表暂时不用t
gargv[0]=strtok(usercommand," ");
int dex=1;
while((gargv[dex++]=strtok(NULL," ")));
}
void exectuecommand(){
pid_t id=fork();
if(id<0)exit(-1);
else if(id==0){
if(filename!=NULL){//文件不为空才执行重定向
//重定向设置
if(redir_type==IN_REDIR){
int fd=open(filename,O_RDONLY);//打开文件
dup2(fd,0);//替换
}
else if(redir_type==OUT_REDIR){
int fd=open(filename,O_CREAT | O_WRONLY | O_TRUNC,0666);
dup2(fd,1);
}
else if(redir_type==APP_REDIR){
int fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);
dup2(fd,1);
}
else{}
}
//child
execvp(gargv[0],gargv);
exit(errno);
}
else{
//father
int status=0;
pid_t rid=waitpid(id,&status,0);
if(rid>0){
//success
lastcode=WEXITSTATUS(status);
if(lastcode!=0)printf("%s:%s:%d\n",gargv[0],strerror(lastcode),lastcode);
}
}
}
void cd(){
const char*path=gargv[1];
if(path==NULL)path=gethome();
chdir(path);
//刷新环境变量
char tmp[SIZE*2];
getcwd(tmp,sizeof(tmp));
snprintf(cwd,sizeof(cwd),"PWD=%s",tmp);
putenv(cwd);
}
int cheak(){
int yes=0;
const char*enter=gargv[0];
if(strcmp(enter,"cd")==0){
yes=1;
cd();
}
else if(strcmp(enter,"echo")==0&&strcmp(gargv[1],"$?")==0){
yes=1;
printf("%d\n",lastcode);
lastcode=0;
}
return yes;
}
void checkdir(char*cmd){
int pos=0;
int end=strlen(cmd);
while(pos<end){
if(cmd[pos]=='>'){
if(cmd[pos+1]=='>'){
cmd[pos++]=0;
pos++;
redir_type=APP_REDIR;
skipspace(cmd,pos);
filename=cmd+pos;
}
else {
cmd[pos++]=0;
redir_type=OUT_REDIR;
skipspace(cmd,pos);
filename=cmd+pos;
}
}
else if(cmd[pos]=='<'){
cmd[pos++]=0;//将左右分开
redir_type=IN_REDIR;
skipspace(cmd,pos);
filename=cmd+pos;
}
else{
pos++;
}
}
}
int main()
{
int quit=0;
while(!quit){//一直执行
//0.重置
redir_type=NONE_REDIR;//重定向类型
filename=NULL;//文件名
makeline();//1.命令行
char usercommand[SIZE];//命令字符串
int a=getcommandline(usercommand,sizeof(usercommand));//2.获得字符串
if(a<=0)return 1;
checkdir(usercommand);//2.1检查是否有重定向
splitcommand(usercommand,sizeof(usercommand));//3.分割字符串
int n=cheak();//4.检查内建命令
if(n)continue;
exectuecommand();//n.执行命令
}
return 0;
}