- 前言
- 文件IO函数使用
- 虚拟地址空间
- 打开关闭文件
- 文件拓展
- stat结构体
- 模拟实现 ls-l 命令
- 文件属性 *** 作
- 判断文件属性 access
- 修改文件权限 chmod
- 修改文件大小 truncate
- 目录 *** 作函数
- 创建目录mkdir
- 删除目录 rmdir
- 重命名目录 rename
- 修改工作目录 chdir
- 目录遍历函数
- 文件描述符
- 复制文件描述符
- 重定向文件描述符
- 修改文件描述符
- 最后
文件IO *** 作,个人认为这是较为重要的内容,因为程序永远不可能不合文件打交道,本篇涉及的内容都是在后面会经常用到的,也算是在变相复习了吧!总之这里很重要。本文所使用的环境为云主机CentOS8系统,使用 xshell 进行远程连接调试,gcc版本为8.5,所有示例均经过测试无误。
文件IO函数使用标准 C库函数 的底层会调用不同系统的底层函数,实现文件 *** 作,具有跨平台性,所以推荐使用 标准C库函数,增加跨平台性。
写文件需要用到缓冲区,然后再刷新到磁盘中
虚拟地址空间程序虚拟出来的不存在的一段虚拟内存空间,为了解决程序加载内存方便理解而虚拟的空间,可以简单的分为 内核区 和 用户区,用户无法直接 *** 作内核区,需要调用系统的API 来实现 *** 作。
在内核区针对文件读写有一个单独的 PCB(进程控制块),在其中有一个文件描述符表,大小为1024,每打开一个新文件,就会占用一个文件描述符,而且占用的是空闲的最小的一个文件描述符,在 文件描述符表 中,有默认三个是 打开状态的,分别是 标准输入,标准输出和标准错误,占用 0 1 2 位。
如果一个文件被多个程序打开,那么他们返回到 文件描述符表 的FILE* 指针是不同的
如下例子可以理解open函数的使用
#include#include #include #include #include int main() { // 打开一个文件 int fd = open("a.txt", O_RDONLY); //打印错误号 if(fd == -1) { perror("open"); } // 读写 *** 作 // 关闭文件 close(fd); return 0; }
打开关闭文件目前该文件夹下是没有 a.txt 文件的,所以会打印一个错误信息:open: No such file or directory
使用如下文件来创建一个有读写权限的文件:
#include文件拓展#include #include #include #include int main() { // 创建一个新的文件,O_CREAT 为可选选项,创建文件使用 | 为按位或 //主要就是可以增加一个权限 int fd = open("create.txt", O_RDWR | O_CREAT, 0777); if(fd == -1) { perror("open"); } // 关闭文件 close(fd); return 0; }
使用如下 *** 作实现 对文件增大容量
#include#include #include #include #include int main() { int fd = open("hello.txt",O_RDWR); //获取文件长度 if (fd==-1) { perror("open"); return -1; } //拓展文件长度 int res = lseek(fd,100,SEEK_END); if (res==-1) { perror("lseek"); return -1; } //一定要写入一个空数据,否则不生效 write(fd," ",1); //关闭文件 close(fd); return 0; }
得到结果:
这个 *** 作的主要作用可以通过迅雷的例子理解:迅雷在下载文件的时候,会预先提示申请空间,然后在申请的空间内将下载的文件填满申请的空间,其中申请的空间就是通过这个 *** 作实现的。
stat结构体可以通过下面的 成员变量 获得文件属性
struct stat { dev_t st_dev; // 文件的设备编号 ino_t st_ino; // 节点 mode_t st_mode; // 文件的类型和存取的权限 nlink_t st_nlink; // 连到该文件的硬连接数目 uid_t st_uid; // 用户ID gid_t st_gid; // 组ID dev_t st_rdev; // 设备文件的设备编号 off_t st_size; // 文件字节数(文件大小) blksize_t st_blksize; // 块大小 blkcnt_t st_blocks; // 块数 time_t st_atime; // 最后一次访问时间 time_t st_mtime; // 最后一次修改时间 time_t st_ctime; // 最后一次改变时间(指属性) };
对文件权限记录的说明
使用不同的标志位来记录不同的权限
- 0-2 位:其他组用户的权限
- 3-5 位:同组其他用户的权限
- 6-8 位:使用者的权限
- 9-11 位:特殊权限位:有三个权限位,可以设置组id,设置用户id,设置粘住位
- 12-15 位:使用一个或两个标记位(八进制),来表示一个文件类型
如果要判断一个文件类型,就使用 该文件文件类型码 与 掩码 进行与 *** 作,然后由得到的值与 各个类型做比较,就可以得到文件类型了
有如下例子:
#include#include #include #include int main() { //创建文件 struct stat statbuf; //传地址,保存该文件信息 int ret = stat("a.txt",&statbuf); //如果打开失败,就打印错误信息 if(ret==-1) { perror("stat"); return -1; } //输出文件大小 printf("size: %ldn",statbuf.st_size); return 0; }
输出该文件的大小:
代码如下:
#include#include #include #include #include //根据id 获取文件所有者 的头文件 #include //获取文件所在组 的头文件 #include //将秒数 转化 为时间 #include //模拟实现 ls -l 命令 // -rw-r--r-- 1 root root 78 Nov 25 22:09 ls-l.c //两个参数,一个是文件路径(文件名称),另一个是 命令行后的参数 int main(int argc,char * argv[]) { //检查输入参数是否正确,给出提示 if(argc<2) { printf("%s filenamen",argv[0]); return -1; } //通过 stat 结构体获取文件各种信息 struct stat st; //存储文件信息的结构体 int ret=stat(argv[1],&st); if(ret==-1) { //如果有错误就 输出错误信息 perror("stat"); return -1; } //获取文件类型和文件权限 //使用一个数组来存储各个表示字符,方便表示状态 char perms[11]={0}; //保存文件类型和文件权限的字符串 //使用选择语句,通过 按位与 操作,得到文件权限信息 switch (st.st_mode&S_IFMT) { case S_IFLNK: //软链接 标识 perms[0]='1'; break; case S_IFDIR: //目录 标识 perms[0]='d'; break; case S_IFREG: //普通文件 标识 perms[0]='-'; break; case S_IFBLK: //块设备 标识 perms[0]='1'; break; case S_IFCHR: //字符设备 标识 perms[0]='c'; break; case S_IFSOCK: //套接字 标识 perms[0]='s'; break; case S_IFIFO: //管道文件 标识 perms[0]='t'; break; default: perms[0]='?'; break; } // 判断文件权限 //文件所有者的权限判断:与 权限标志位 做 与 操作,得到,如果为1,就有该权限,否则就是没有改权限 perms[1] = st.st_mode & S_IRUSR ? 'r' : '-'; perms[2] = st.st_mode & S_IWUSR ? 'w' : '-'; perms[3] = st.st_mode & S_IXUSR ? 'x' : '-'; //文件所在组 权限 is read group 的英文缩写 perms[4] = st.st_mode & S_IRGRP ? 'r' : '-'; perms[5] = st.st_mode & S_IWGRP ? 'w' : '-'; perms[6] = st.st_mode & S_IXGRP ? 'x' : '-'; //其他人 权限 is read other perms[7] = st.st_mode & S_IROTH ? 'r' : '-'; perms[8] = st.st_mode & S_IWOTH ? 'w' : '-'; perms[9] = st.st_mode & S_IXOTH ? 'x' : '-'; //获取硬链接数 int linkNum=st.st_nlink; //文件所有者 从 uid 获取到所有者 名字 char* fileUser = getpwuid(st.st_uid)->pw_name; //文件所在组 char * fileGrp=getgrgid(st.st_gid)->gr_name; //文件大小 long int fileSize = st.st_size; //修改时间 char* time= ctime(&st.st_mtime); //因为时间在格式化后 会 默认哟一个回车符,需要将其去掉 char mtime[512]={0}; strncpy(mtime,time,strlen(time)-1); //将time的内容拷贝到mtime中,并将最后的一个回车符去掉 //将内容输出 char buf[1024]; sprintf(buf,"%s %d %s %s %ld %s %s",perms,linkNum,fileUser,fileGrp,fileSize,mtime,argv[1]); //输出该信息 printf("%sn",buf); return 0; }
运行结果:
access函数,可以判断文件的各种属性
int access(const char *pathname,int mode);
作用:判断某个文件时候有某个权限,判断文件是否存在
参数:
- pathname: 判断文件路径
- mode:
R_OK: 判断是否有读权限
W_OK:判断是否有写权限
X_OK:判断是否有执行权限
F_OK:判断文件是否存在
返回值:成功返回0,失败返回-1
如下例子,可以判断该文件是否存在,如果存在就输出 “文件存在!” 提示信息,否则 就 直接输出错误信息
#include#include int main() { int ret = access("a.txt",F_OK); if(ret==-1) { perror("access"); } else { printf("文件存在n"); } return 0; }
得到如下结果:
chmod函数,可以修改文件权限
#include
int chmod(const char *pathname, mode_t mode);
参数:
- pathname: 修改文件路径
- mode: 需要修改的权限值,使用八进制数表示
返回值:成功返回0,失败返回-1
如下例子,可以修改文件权限:
#include//需要包含的头文件 #include int main() { //用三个八进制数 来表示权限 八进制数,以0 开头 int ret=chmod("a.txt",0775); if(ret==-1) { perror("chmod"); return -1; } else { printf("修改权限成功!!n"); } return 0; }
执行文件更改:
chown 函数可以修改文件所在组,使用方法和以上基本相同,可以通过查询帮助文档,获取更多信息:
man 2 chown # 查看chown 函数的详细信息
使用该命令,获得帮助文档:
要获取 uid 和 gid 可以通过查看 /etc 下的文件来获得:
truncate函数,对文件 缩减 或 修改
#include
#include
int truncate(const char *path, off_t length);
作用:缩减 或 扩展 文件的尺寸到指定的大小
参数:
- path:需要修改的文件的路径
- length:需要最终文件变成的大小
返回值:
成功返回0,失败返回-1
有如下代码,修改 b.txt 文件的大小:
#include#include #include int main() { int ret = truncate("b.txt",20); if(ret==-1) { perror("truncate"); return -1; } else { printf("修改文件大小成功,设置为20字节大小!n"); } return 0; }
运行成功后:
拓展后的 b.txt
如果将 b.txt 进行缩小,则会直接发生 截断,会直接丢弃末尾文件内容
比如将其改为 5,运行结果如下:
使用该函数可以方便的 拓展 或 缩减 文件大小
目录 *** 作函数 创建目录mkdir与 shell 命令同名,可以创建目录
#include
#include
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
pathname:创建目录的路径
mode:权限 三个八进制数
返回值:
成功返回0,失败返回-1
执行如下代码,可以在当前文件夹下,创建 aaa 目录
#include#include #include int main() { int ret=mkdir("aaa",0777); if(ret==-1) { perror("mkdir"); return -1; } else { printf("创建目录成功!n"); } return 0; }
执行该文件成功,创建 aaa 目录:
要注意创建文件的权限,即使是 八进制775,系统内部会对其做调整,因为在 赋予权限时,并不是单纯的根据代码中规定的进行权限设置的,而是 mode & ~umask & 0777 这三个结合后的结果,才是最终的权限(目的是为了系统文件安全)
删除目录 rmdir使用函数删除目录,只能删除 空目录,具体使用方法和 mkdir 命令类似
重命名目录 rename可以实现对 文件目录(文件夹) 的重命名,例子如下:
#include修改工作目录 chdirint main() { int ret = rename("aaa", "bbb"); if(ret == -1) { perror("rename"); return -1; } return 0; }
chdir 函数,可以修改当前程序的工作目录,而不是更改文件夹的没给你做
#include
int chdir(const char *path);
作用:修改进程的工作目录
程序时默认在当前路径进行 *** 作的,可以使用 chdir 更改工作目录
比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder
参数:
path : 需要修改的工作目录
返回值:
成功返回0,失败返回-1
#include
char *getcwd(char *buf, size_t size);
作用:获取当前工作目录
参数:
- buf : 存储的路径,指向的是一个数组(传出参数)
- size: 数组的大小
返回值:
返回的指向的一块内存,这个数据就是第一个参数
有如下程序:
#include#include #include #include #include int main() { // 获取当前的工作目录 char buf[128]; getcwd(buf, sizeof(buf)); printf("当前的工作目录是:%sn", buf); // 修改工作目录 int ret = chdir("/root/clearn/lesson03"); if(ret == -1) { perror("chdir"); return -1; } // 创建一个新的文件 八进制664 int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664); if(fd == -1) { perror("open"); return -1; } close(fd); // 获取当前的工作目录 char buf1[128]; getcwd(buf1, sizeof(buf1)); printf("当前的工作目录是:%sn", buf1); return 0; }
执行后效果:
在 lesson03 目录下,可以找到创建的文件:
设计的主要几个函数:
// 打开一个目录 #include#include DIR *opendir(const char *name); 参数: - name: 需要打开的目录的名称 返回值: DIR * 类型,理解为 目录流 错误返回NULL // 读取目录中的数据 #include struct dirent *readdir(DIR *dirp); 一次读取各个目录实体 - 参数:dirp是 opendir 返回的结果 - 返回值: struct dirent,代表读取到的文件的信息 读取到了末尾或者失败了,返回NULL // 关闭目录 #include int closedir(DIR *dirp);
在遍历目录,读取目录的时候,dirent函数会返回相关的目录信息(返回一个结构体),如下:
遍历目录下所有文件数量:
代码如下:
#include#include #include #include #include #include #include //使用该头文件,NULL 就不会显示未定义了 //#define NULL ((void *)0) int getFileNum(const char * path); //argc是命令行总的参数个数 argv[]是argc个参数,其中第0个参数是程序的全名 int main(int argc,char* argv[]) { // 没有参数 if(argc<2) { //输出默认的第0个参数,即程序全名 printf("%s pathn",argv[0]); } int num = getFileNum(argv[1]); printf("普通文件的个数为:%d n",num); return 0; } //用于获取目录下 所有普通文件的个数 int getFileNum(const char * path) { //打开目录 DIR* dir=opendir(path); //如果该文件打开失败或者为空,就输出错误信息 if(dir==NULL) { perror("opendir"); exit(0); } struct dirent * ptr=readdir(dir); //记录普通文件的个数 int toatl=0; //不断读入目录 while((ptr=readdir(dir))!=NULL) { //获取名称 char * dname=ptr->d_name; //忽略 . 和 .. 即忽略当前目录和父目录 if(strcmp(dname,".")==0||strcmp(dname,"..")==0) //使用字符串比较函数 strcmp { //忽略这两个 目录 continue; } //判断是否是普通文件 还是目录 if(ptr->d_type==DT_DIR) { //是目录,就继续读取这个目录 char newpath[256]; //将新目录 格式化输出到newpath中 sprintf(newpath,"%s/%s",path,dname); //递归这个过程 toatl += getFileNum(newpath); } if(ptr->d_type==DT_REG) { //如果是普通文件,就 统计数量 toatl++; } } //关闭目录 closedir(dir); return toatl; }
运行结果如下:
复制的文件描述符,与原本的文件描述符使用是一致的,有如下例子,可以通过复制的文件描述符, *** 作该文件(即使该文件已经被关闭了,但是复制的文件描述符仍然有效)
#include
int dup(int oldfd);
作用:复制一个新的文件描述符
fd=3, int fd1 = dup(fd),
fd指向的是a.txt, fd1也是指向a.txt
从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符
#include#include #include #include #include #include int main() { //以读写方法打开文件 int fd = open("a.txt",O_RDWR|O_CREAT,0664); //复制文件描述符 int fd1=dup(fd); if(fd1==-1) { perror("dup"); return -1; } //输出文件描述符位置 printf("fd : %d , fd1 : %d n",fd,fd1); //关闭文件 close(fd); char * str="hello world"; //通过复制的文件描述符,向文件中写入信息 int ret=write(fd1,str,strlen(str)); if(ret==-1) { perror("write"); return -1; } else { printf("write success!n"); } return 0; }
执行结果:
dup2 函数可以改变文件描述符的指向,即重定向
#include
int dup2(int oldfd, int newfd);
作用:重定向文件描述符
oldfd 指向 a.txt, newfd 指向 b.txt
调用函数成功后:newfd 和 b.txt 做close, newfd 指向了 a.txt
oldfd 必须是一个有效的文件描述符
oldfd和newfd值相同,相当于什么都没有做
样例代码如下:
#include#include #include #include #include #include int main() { int fd=open("1.txt",O_RDWR|O_CREAT,0664); if(fd==-1) { perror("open"); return -1; } //打开两个文件 int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664); if(fd1 == -1) { perror("open"); return -1; } printf("fd : %d, fd1 : %dn", fd, fd1); //重定向 文件描述符,fd1 指向与fd相同的文件 //fd2 指向的和 fd1 一致 int fd2 = dup2(fd, fd1); if(fd2 == -1) { perror("dup2"); return -1; } //通过 fd1 去写数据,实际 *** 作的是 1.txt 而不是2.txt,因为其已经被重定向了 char * str="hello dup2"; //返回写入的长度 int len=write(fd1,str,strlen(str)); if(len==-1) { perror("write"); return -1; } printf("fd : %d, fd1 : %d, fd2 : %dn",fd,fd1,fd2); //fd2 不用关闭,因为其和fd1 是一样的 close(fd); close(fd1); return 0; }
执行成功如图:
可以实现5种不同的功能,这里主要使用两种
#include
#include
int fcntl(int fd, int cmd, … );
参数:
fd : 表示需要 *** 作的文件描述符
cmd : 对文件描述符做 什么 *** 作
F_DUPFD :复制文件描述符,复制的为第一个参数 fd,得到一个新的文件描述符
int ret = fcnt1(fd,F_DUPFD);
F_GETFL : 获取指定的文件描述符状态 flag
获取的flag和通过open 函数传递的flag 为同一个
F_SETFL :设置文件描述符状态 flag
必选项:O_RDonLY ,O_WRDOLY, O_RDWD
O_APPEND 表示最佳数据,在原有文件后面继续写数据
NonBLOK 设置为非阻塞
阻塞和非阻塞:描述函数调用的行为,阻塞:调用某个函数,无法立刻得到其返回值,非阻塞:调用某个函数可以立刻得到其返回值
如下例子演示,修改文件描述符,向文件中追加数据
#include#include #include #include int main() { //复制文件描述符 //int fd1=open("1.txt",O_RDONLY); //使用fcntl 赋值文件描述符 // int ret=fcntl(fd1,F_DUPFD); //修改 或 获取 文件状态 flag int fd=open("1.txt",O_RDWR); //现在是以读写方式打开文件 if(fd == -1) { perror("open"); return -1; } //下面使用 fcntl修改文件描述符的 flag,加入 O_APPEND 标记 //先 获取文件描述符 flag int flag=fcntl(fd,F_GETFL); //相当于 flag=flag|O_APPEND,将 O_APPEND 加入到 flag中 flag|=O_APPEND; int ret = fcntl(fd,F_SETFL,flag); if(ret == -1) { perror("fcntl"); return -1; } char * str = "nihao"; write(fd, str, strlen(str)); close(fd); return 0; }
运行结果:追加信息成功
如果打开了文件,直接 使用 write 进行写入,会覆盖之前的信息 ,在使用fcntl 追加之后,就是其后新增其他数据了
最后感谢观赏,一起提高,慢慢变强
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)