Linux之文件和目录相关函数介绍(open close read write lseek stat/lstat opendir readdir closedir dup/dup2/fcntl )

1.文件描述符 :

              每打开一个文件都会创建三个文件描述符,一个输入,一个输出,还有一个ERR:

#define STDIN_FILENO  0
#define STDOUT_FILENO  1
#define STDERR_FILENO  2

         新打开一个文件会返回未使用的最小的一个文件描述符。

2.open close函数介绍:

        open和close函数就是用来打开和关闭文件的,open函数打开文件会返回相应的文件描述符,这两个函数要放在一起使用,open函数可以带第二个参数

        通过命令 man 2 open 可以查看open函数的信息如上,open函数一共有三种用法,只要知道它第一个参数传入的是文件路径,那个flags就是对文件的操作(只读,追加等等),mode_t mode表示它的权限(读写执行权限  分别在属主,文件所属组,文件的其他用户中的体现)

 

        对于第二个参数我们最经常用的就是 O_CREAT 如果文件不存在,创建一个新文件,O_RDWR 以可读可写方式打开一个文件,O_RDONLY 只读打开,O_WRONLY 只写打开,这三个里面必须使用一个而且只能使用一个,当然,最常用的也就是O_RDWR 既可以读又可以写,综合了那俩个,O_APPEND 在文件内容后面追加,对于其他的在使用的时候都可以通过 man 2 open 来查询相关参数的用法,不必要记住所有的参数,还有就是这些个参数可以通过管道符一起使用,见后面测试。

        对于第三个参数,这里也有很多,但是我们经常把这个参数的最高权限叠加起来,直接用 0777表示对属主,属组,其他用户 都具有rwx(读写执行)权限。对于返回值,只需要知道打开文件成功就会返回文件描述符,打开失败也有一堆返回值,但是很少用,也是用到时查询即可。打开失败就认为是返回一个负值。        对于close函数只要知道是关闭文件的就行,里面传入打开文件的描述符,对于close函数需要注意的一点是:对于一般的文件打开后即使自己没有关闭,操作系统也会将其关闭,但是对于一个常年运行的程序打开了必须要关闭(比如网路服务器),不然随着打开文件越来越多,占用越来越多的文件描述符,会占用大量文件描述符和系统资源。下面看一个具体的例子:

        我在我的IO目录下提前创建一个test.log文件,通过只读的方式打开:

头文件的引用:

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<stdlib.h>
  4 #include<sys/types.h>
  5 #include<unistd.h>
  6 #include<sys/stat.h>
  7 #include<fcntl.h>
  8 
  9 
 10 int main( )
 11 {
 12     int fd=open("./test.log",O_RDWR);
 13     if(fd<0)
 14     {
 15         perror("open error");
 16         return -1;
 17     }
 18     printf("打开成功\n");
 19     close(fd);
 20     return 0;
 21 }   

        注意这几个头文件后面几乎都会用到,要熟记,这里就简单写一个以只读的方式打开文件,如果打开成功就输出,打开失败就退出。

        因为我的目录下有这个文件,所以可以可以通过只读的方式打开成功。

        把这个给删了,就不能打开了,因此只读方式打开文件是不能创建文件的。把这个改成 O_CREAT 就可以创建文件了,或者用 O-RDWR | O_CREAT把这两个放一起用。

 int fd=open("./test.log",O_RDWR | O_CREAT, 0777);

        在后面直接给它加上最高权限.

        刚开始是没有test.log文件的,运行时候就有了test.log文件,O_APPEND需要结合后面的函数一起使用,就在后面在展示测试用例。

3.read write lseek 函数介绍:

        这三个函数也是经常一起使用的,在后面进程那块会进程用到,一个进程写,一个进程读。在这先简单介绍一下,从单词本身看,read write就是读,写的意思。

        先看write函数,承上open函数的flags用O_RDWR 也就是可读可写,就是以可写的方式打开一个文件后,才能使用write函数

        write函数的第一个参数fd就是open打开文件返回的文件描述符,第二个参数就是你要写入的内容,第三个参数就是你要写入的内容的大小(长度)。写完之后文件描述符就会移动到你最后写的位置,当需要读文件的时候,就需要用lseek将文件描述符 重新定位到文件开头。对于返回值,写入失败返回-1,当然也还有其他的返回值,在一般的情况下几乎不用判断是否写入失败,但是在网络编程中一定要进行判断,否则容易出错。

        对于read函数,参数和write是一样的,第一个是文件描述符,第二个就是从文件中读出的内容所存放的位置(也就是一个缓冲区),第三个就是就是这个缓冲区的大小。读取成功,就会返回读到的字节数,文件为空返回0表示读到的是文件描述符,读取失败返回-1.

         lseek函数就是用来移动文件描述符的,通过write和read的操作后,如果不关闭这个文件,那么文件描述符就会停留在你读写的最后位置。lseek的第二个参数是偏移量,直接传入数值即可,第三个参数有三个:

SEEK_END 文件偏移量将被设置为文件长度加上offset(end就是跳到文件的末尾)

SEEK_CUR 文件偏移量将被设置为当前文件描述符所在的长度加上offset(cur就是当前的位置)

SEEK_SET 这个就是最灵活的,自己设置文件描述符的位置,也就是文件描述符就在offset的位置这个也是最常用的,因为它的功能涵盖上面两个(个人观点)

        下面看一个综合这三个函数的例子:

10 int main( )
 11 {
 12     int fd=open("./test.log",O_RDWR | O_CREAT,0777);
 13     if(fd<0)
 14     {
 15         perror("open error");
 16         return -1;
 17     }
 18     write(fd,"hello world",sizeof("hello world"));
 19 
 20     lseek(fd,0,SEEK_SET);
 21     char buf[BUFSIZ];
 22     memset(buf,0,sizeof(buf));
 23     int n=read(fd,buf,sizeof(buf));
 24     printf("n=%d buf=%s",n,buf);
 25     printf("\n");
 26     close(fd);
 27     return 0;
 28 }

        使用的还是上面打开的那个文件,现在向里面写入“hello world”,刚开始这个文件是空的,刚刚创建了但是没有写入数据,写完数据后要对其进行读操作,但是这个时候的文件描述符是在文件末尾的,直接读取数据是读不到的,因此就要用到 lseek 函数将文件描述符重新移动到文件开头,定义一个缓冲区,注意 这个缓冲区大小 BUFSIZ 是系统的一个宏,表示大小,一般建议把缓冲区大小设置成这个比较方便,然后通过memset将其初始化为0,将读到的数据的长度用n表示,最后输出。

        输出结果长度为12,并且读到了写入的字符串。用lseek可以获取文件的长度,实现文件扩展,获取当前的文件描述符位置。

int len =lseek(fd,0,SEEK_END);//返回文件的长度
off_t broad =lseek(fd,1000,SEEK_END);//文件向后扩展
int loc=lseek(fd,0,SEEK_CUR);//获取当前文件描述符的位置

加入到上面的测试用例中:

        因为把那个确定当前位置的代码放到了那个lseek将文件描述符移动到文件开头那,所以这里返回的是0。

4.stat/lstat函数介绍;

        从这里开始就是对目录操作了,stat函数就是获取文件的属性,成功获取返回0,失败则返回-1.

        stat函数第一个参数就是要传入的文件,第二个参数就是你要带回来的目录信息,所以在使用这个函数之前,就需要自己先创建一个struct stat 类型的变量来保存获取的信息,这stat变量的所含的信息非常多,可以判断权限,文件属性,当然,不知道的也可以当场查询(man 2 stat)。

        st_dev 设备编号,st_ino inode节点的个数 st_mode 文件类型和存取权限 st_nlink 硬链接个数 st_uid/gid 用户id 组id  st_size 文件字节大小 st_mtime 最后一次修改时间(常用的几个)。

int main(int argc,char*argv[])
{
	struct stat st;
	stat(argv[1],&st);
	printf("st_uid=%d,st_gid=%d,st_size=%d\n",st.st_uid,st.st_gid,st.st_size);
	return 0;
}

 先测试一下uid和gid还有size的使用:

        这里通过main函数带入参数,还是用上面的test.log,这里输出的用户id和组id可以通过命令id来验证;

 

        说明上述结果是正确的。下面看看权限问题: 

用9位二进制数表示权限,0-2位表示其他人的权限,3-5位表示所在组的权限,6-8表示用户自己的权限。

0-2 表示其他人权限:其实下面这个很好记,后三个oth表示others,RWX分别表示读写执行

S_IROTH 读 100  4

S_IWOTH 写 010  2

S_IXOTH 执行 001 1

S_IRWXO 掩码 111  7  

3-5 表示所属组权限:(先不管后三位)

S_IRGRP 读 100 000

S_IWGRP 写 010 000

S_IXGRP 执行 001 000

S_IRWXGRP 掩码  111 000和上面判断是否拥有该权限是一样的。

6-8 表示所属用户权限:

S_IRUSR 读 100 000 000

S_IWUSR 写 010 000 000

S_IXUSR 执行 001 000 000

S_IRWXUSR 掩码 111 000 000

         当需要判断是否具有某项权限时,直接用得到的st_mode和和对应的权限 & 即可,如果具有该权限则返回true,否则返回false。

  7 int main(int argc,char*argv[])
  8 {
  9     struct stat st;
 10     stat(argv[1],&st);
 11     if(st.st_mode & S_IROTH)
 12     {
 13         printf("----R----\n");
 14     }
 15     if(st.st_mode & S_IWOTH)
 16     {
 17         printf("----W----\n");
 18     }
 19     if(st.st_mode & S_IXOTH)
 20     {
 21         printf("----X----\n");
 22     }
 23     return 0;
 24 }

        就来判断一下上面的test.log的others权限,判断用户权限和所属组权限和这样一样的。

        test.log文件只有rx权限。 下面了解一下通过stat函数来判断文件类型:

         上面是第一种判断方法,就是通过st_mode 与掩码(S_IFMT)相与,如果得到的结果是S_IFREG 说明是一个普通文件,如果得到的结果是S_IFDIR 说明其是一个目录文件,以此类推。

  7 int main(int argc,char*argv[])
  8 {
  9     struct stat st;
 10     stat(argv[1],&st);
 11     if((st.st_mode & S_IFMT) == S_IFREG)
 12         printf("这是一个普通文件\n");
 13     if ((st.st_mode & S_IFMT) == S_IFDIR)
 14         printf("这是一个目录文件\n");
 15     if ((st.st_mode & S_IFMT) == S_IFLNK)
 16         printf("这是一个链接文件\n");
 17     if ((st.st_mode & S_IFMT) == S_IFSOCK)
 18         printf("这是一个socket文件\n");
 19 }  

         这里就简单测试其中的几个,其他的读者要是感兴趣可以自行测试。

        注意 我第一个传入的参数是  .  这个表示当前目录,所以它会输出这是一个目录文件。

        下面看第二种判断方法:通过给定的宏变量直接传入st_mode 如果返回true,说明就是这个类型的文件。注意上面的前缀是S_IF ,这里的前缀是S_IS

  7 int main(int argc,char*argv[])
  8 {
  9     struct stat st;
 10     stat(argv[1],&st);
 11     if((S_ISREG(st.st_mode)))
 12         printf("这是一个普通文件\n");
 13     if (S_ISDIR((st.st_mode)))
 14         printf("这是一个目录文件\n");
 15     if (S_ISLNK((st.st_mode)))
 16         printf("这是一个链接文件\n");
 17     if (S_ISSOCK((st.st_mode)))
 18         printf("这是一个socket文件\n");
 19 }
~     

        

        lstat函数和stat函数的用法基本是一样的,对于普通文件来说,lsat和stat函数完全一样,对于软链接文件来说,lstat函数获取的是链接文件本身的属性,stat函数获取的是链接文件指向的文件的属性,这个就是他们的唯一一点区别。

5.opendir readdir closedir函数介绍:

        由后缀dir就可以了解这个是对目录的操作,opendir打开一个目录,readdir读取目录信息,closedir就是关闭目录了。

        opendir打开一个目录,打开成功返回一个DIR类型的指针,打开失败返回空指针。

 

        readdir的参数传入刚才opendir返回的指针, 返回一个struct dirent 类型的指针。返回的struct dirent*指针中存放着目录下的文件信息:

        下面这个就是文件的类型d_type

        下面看一个循环读取目录的例子:

  8 int main(int agrc,char*argv[])
  9 {
 10 
 11     DIR*pDir=opendir(argv[1]);
 12     if(pDir==NULL)
 13     {
 14         perror("opendir");
 15         return -1;  
 16     }
 17     struct dirent *pDent=NULL;
 18     while(pDent=readdir(pDir))
 19     {   
 20         printf("%s----->",pDent->d_name);
 21         switch(pDent->d_type)
 22         {   
 23             case DT_BLK:
 24                printf("块文件\n");break;
 25             case DT_REG:
 26               printf("普通文件\n");break;
 27             case DT_LNK:
 28                 printf("链接文件\n");break;
 29             case DT_DIR:
 30                 printf("目录文件\n");break;
 31             case DT_SOCK:
 32                 printf("socket文件\n");break;
 33             default:
 34                 printf("其他\n");
 35         }
 36     }
 37     printf("Hello World");
 38     closedir(pDir);
 39     return 0;
 40 }

        这个也是通过main函数传入参数,先创建一个pDir指针来打开目录文件,在创建一个pDent来接受循环读取的文件信息,用switch语句判断文件类型,循环读取,当pDent为空就读完了。

        这里传入的是当前目录,还是那个 “ . ” 。 注意读完了记得使用closedir将其关闭。

6.dup/dup2函数介绍:

        这两个函数是用来对文件描述符进行复制的,也有一点区别。先看dup函数:

         dup函数要传入oldfd就是要复制的fd,如果复制成功,返回新的绑定好的newfd,这两个fd指向同一个文件。下面来简单验证一下这个newfd和oldfd是不是指向同一个文件:我们只需要用一个文件描述符写,一个文件描述符读取即可,主要另一个能读到写入的内容,就可以证明newfd和oldfd指向同一个文件。

  9 int main(int argc,char *argv[])
 10 {
 11     int fd=open(argv[1],O_RDWR);
 12     if(fd<0)
 13         {
 14             perror("open error");
 15             return -1;
 16         }
 17     int newfd=dup(fd);//复制文件描述符
 18     printf("len=%d\n",newfd);
 19     char buf[BUFSIZ];
 20     memset(buf,0,(sizeof(buf)));
 21     write(newfd,"ABDCD",strlen("ABDCD"));
 22     int ret=read(fd,buf,sizeof(buf));
 23     printf("改变量:%s\n",buf);
 24     close(newfd);
 25     close(fd);
 26     return 0; 
 27 }

        打开一个文件后通过dup复制一个新的文件描述符,同时指向当前的文件,然后执行写操作,然后进行读取,结合前面提到的lseek函数,设想一下这里会不会出现问题,如果这俩个文件描述符指向同一个文件,那么写入文件的时候,两个文件描述符会一起向后走,此时就是文件的末尾,此时进行读取文件一定会读取失败。

        没有读到数据,但是当前dup产生的新的文件描述符确实是存在的,位于第四个。如果加上lseek函数将文件描述符移动到文件开头是否就会读到数据呢?答案是肯定的。

     lseek(fd,0,SEEK_SET);

        通过上述案例证明了dup函数能够复制一个新的文件描述符,和原来的文件描述符一起指向原来的文件。

        对于dup2函数,第一个参数传入oldfd,第二个参数是指定的新的文件描述符,和dup的作用相同将newfd一起指向oldfd,当newfd初始未指向文件时,newfd和oldfd指向oldfd所指向的文件,当newfd初始时指向了某个文件,调用dup2函数之后,newfd原来所指向的文件将会被关闭,newfd将和oldfd一起指向oldfd指向的文件。下面对dup2函数进行验证:

  8 int main(int argc,char*argv[])
  9 {
 10     int oldfd=open(argv[1],O_RDWR);
 11     if(oldfd<0)
 12     {
 13         perror("open error");
 14         return -1;
 15     }
 16 
 17     int newfd=open(argv[2],O_RDWR);
 18     if(newfd<0)
 19     {
 20         perror("open error");
 21         return -1;
 22     }
 23     dup2(oldfd,newfd);
 24     printf("newfd=%d,oldfd=%d\n",newfd,oldfd);
 25     char buf[BUFSIZ];
 26     memset(buf,0,sizeof(buf));
 27     write(newfd,"HELLO WORLD",strlen("HELLO WORLD"));
 28     lseek(oldfd,0,SEEK_SET);
 29     int ret=read(oldfd,buf,sizeof(buf));
 30     printf("读到了:%s\n",buf);
 31     close(newfd);
 32     close(oldfd);
 33     return 0;
 34 }

        首先打开两个文件,然后调用dup2将两个文件的文件描述符都指向oldfd(第一参数),然后让newfd写文件,oldfd读文件,如果能读到文件,那么就是验证成功,当然,注意lseek函数的使用。

        验证成功,还有一个注意事项是最后记得关闭文件,养成良好的习惯。第二个就是dup2还可以实现输出重定向,把dup2的第二个参数换成STDOUT_FILENO,这样操作后,STDOUT_FILENO这个文件描述符也指向这个文件了 ,本来他是负责将数据输出到屏幕上,现在就可以实现直接输出到文件里。

  8 int main(int argc,char *argv[])
  9 {
 10     int fd=open(argv[1],O_RDWR | O_CREAT ,0777);
 11     if(fd<0)
 12     {
 13         perror("open error");
 14         return -1;
 15     }
 16     dup2(fd,STDOUT_FILENO);
 17     printf("HELLO WORLD I AM NIUBI!");
 18     close(fd);
 19     return 0;
 20 }

 

        这里我们新创建啊一个文件,执行完查看发现里面输入了数据,使用了printf屏幕上却没有输出数据,数据输出到文件里了。 以上就是dup和dup2的用法。

7.fcntl函数介绍 :

        fcntl函数的第一个功能和dup一样,也是对文件描述符的复制。

        fcntl函数有三个参数,它的功能主要是由第二个参数决定的,当第二个参数传入F_DUPFDS时,就是对文件描述符的复制,此时第三个参数传入0就行,函数的返回值就是一个新的文件描述符,这个和dup函数基本一样,就是比它复杂一点。

  9 int main(int argc,char *argv[])
 10 {
 11     int fd=open(argv[1],O_RDWR);
 12     if(fd<0)
 13     {
 14         perror("open error");
 15         return -1;
 16     }
 17     int newfd=fcntl(fd,F_DUPFD,0);
 18     char buf[BUFSIZ];
 19     memset(buf,0,sizeof(buf));
 20     int ret=read(newfd,buf,sizeof(buf));
 21     printf("读到的内容:%s\n",buf);
 22     close(fd);
 23     close(newfd);
 24     return 0;
 25 }

        验证fcntl的第一个功能,和上述的dup一样的方式,这次没有写入,直接通过newfd进行读取。

  

        验证成功,和第一个功能和dup完全相同。下面看第二个功能,就是对flags的获取和设置,想要去改变flags必须先获取flag,然后再重新设置。

        当用来获取flags时,第三个参数传入0,返回值就是或得到的flags,当用来设置flag时,第三个参数就要传入要设置的flags。下面用O_RDWR方式打开一个文件,然后用fnctl获取其flags(此时第二个参数传入F_GETFL),最后设置flags(这时候传入F_SETFL)。

  9 int main(int argc,char *argv[])
 10 {
 11     int fd=open(argv[1],O_RDWR);
 12     if(fd<0)
 13     {
 14         perror("open error");
 15         return -1;
 16     }
 17     int flag=fcntl(fd,F_GETFL,0);//得到flag属性
 18     flag = flag | O_APPEND;//把文件的flag属性改成了追加
 19     fcntl(fd,F_SETFL,flag);//设置flag属性
 20     write(fd,"HELLO WORLD HH",strlen("HELLO WORLD HH"));
 21     char buf[BUFSIZ];
 22     lseek(fd,0,SEEK_SET);//因为之前写入将文件描述符号弄到了文件末尾,所以这里要移动文件描述符到文件开头
 23     int ret=read(fd,buf,sizeof(buf));
 24     printf("读出的内容%s\n",buf);
 25     close(fd);
 26     return 0;
 27 } 

        综合前面的,看一个案例,打开一个文件后,先通过fcntl获取flags属性,然后将flags改成了O_APPEND(注意修改文件的flags需要用到管道符,还有文件的flags就是你打开文件的方式,只读,只写) 也就是可以在文件后面追加内容, 最后再读取数据。

         test.log里面之前就有一个HELLO WORLD ,根据这个结果,确实是在后边追加了数据。以上就是fcntl的常用功能。

8.说明

        文件操作这里涉及到的函数比较多,如果需要用的时候,即使相关参数忘记了也没有关系,可以及在Linux上面查看其用法,但是对于常用的参数还是有必要记一下,这些函数通过 man 2/3 +函数 都能查看其详细的用法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值