目录
一,标准C库中的文件IO操作
在介绍Linux文件IO及目录操作之前,先了解下标准C库中的文件IO的一些操作。
1. 标准C库对文件操作常用函数
函数名 | 说明 |
fopen | 打开文件,返回FILE* |
fclose | 关闭文件 |
fread | 读取文件 |
fwrite | 写入文件 |
fgets | 读取一行或指定个字符串,成功返回指向读取到的字符串的首地址,失败返回NULL |
fputs | 写入一个字符串到文件中 |
fscanf | 格式化读取文件 |
fprintf | 格式化写入文件 |
fseek | 把文件读写指针定位到指定的位置 |
fgetc | 从文件中读取一个字符,成功返回这个字符,失败返回EOF |
fputc | 把一个字符写入到文件,成功返回这个字符,失败返回EOF |
feof | 当设置了文件结束标识符时,返回非零值,否则返回0 |
fflush | 刷新流的输出缓冲区 |
C库文件操作大致过程:
- 使用fopen()函数打开文件,fopen()返回FILE*,FILE是一个结构体,包含文件描述符,文件读写位置等信息;
- 通过文件描述符,可以找到对应文件;通过文件读写位置,可以在对应文件中定位读写的位置。
- 当写入文件时,文件内容并不会立即被写入文件,而是被放到缓冲区,当满足条件时,缓冲区被刷新,缓冲区中的内容写入到文件中。
2. 标准C库IO和Linux系统IO的关系
3. 虚拟地址空间
当运行一个可执行程序时,系统会自动为该程序分配一个虚拟地址空间,以32位系统为例,虚拟地址空间大小是4G。其中3G~4G之间的空间是内核区,用户无法在此空间进行读写操作;0~3G之间是用户区,包含了环境变量、命令行参数、堆栈空间、共享库等等部分,比如我们在程序中定义的变量会在用户区中。
上面提到的文件描述符在内核区中的PCB进程控制块中,该块中有一个文件描述符表,表的大小是1024,用户每打开一个文件,就会占用一个文件描述符(空闲的最小的一个)。
二、 Linux中的文件操作
2.1 Linux中的文件IO操作常用函数
int open(const char *pathname,int flags); // 打开一个已存在的文件
int open(const char *pathname,int flags,mode_t mode); // 打开文件,可以创建文件
int close(int fd); // 关闭文件
// 读写文件
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf,size_t count);
// 定位文件
off_t lseek(int fd, off_t offset, int whence);
// 获取文件信息
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
// 文件属性相关
int access(const char *pathname,int mode); // 判断文件权限,或者判断文件是否存在
int chmod(const char *pathname,int mode); // 修改文件权限
int chown(const char *path,uid_t owner,gid_t group); // 修改文件的所有者或所在组
int truncate(const char *path, off_t length); // 扩展或缩减文件大小
想要了解某个函数的具体说明,可以使用命令: $ man 2 函数名。表示要查看第2节中的某个函数(第2节是关于Linux系统的库函数介绍,第3节是C库函数介绍)。比如查看open函数的说明,可以使用命令:
$ man 2 open
显示如下:
在Linux系统中,除了返回值表示具体含义的(比如open()函数返回值表示文件描述符),通常以int类型作为返回值的函数,当函数执行成功时,返回0,失败返回-1。
2.2 文件IO函数详细介绍
■ 打开/关闭文件
打开文件使用open()函数,关闭文件使用close()函数。
int open(const char *pathname,int flags); // 打开一个已存在的文件
int open(const char *pathname,int flags,mode_t mode); // 打开文件,可以创建文件
int close(int fd); // 关闭文件
参数说明:
@pathname:要打开的文件路径;
@flags:文件权限设置及其它设置,表示本进程的读写权限,而不是文件本身的权限;
权限包括:O_RDONLY O_WRONLY O_RDWR
只读 只写 读写
以上权限是互斥的,使用时只能选择其中一个
@ mode:是一个八进制的数,表示创建出的新文件的创建权限,
文件最终的权限是 mode & ~umask;umask的作用就是舍弃某些权限,让创建的文件更合理。
mode表示的就是如下【-rwxrwxr-x】,如下红线标出的权限对应的八进制数应该是0775
@fd:文件描述符;
返回值:
open()返回值是文件描述符,返回-1表示文件打开失败;
close()成功返回0,失败返回-1;
■ 读写文件
read()从文件中读取数据,write()向文件中写入数据。
// 读写文件
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf,size_t count);
参数介绍:
- fd:文件描述符
- buf:要读取/写入的内容
- count:要读取的内容的大小/要写入的内容的大小,单位:byte
返回值:
- open():>0表示读取到的字节数,=0表示文件已经读完,=-1表示文件读取失败;
- write():>0表示实际写入的字节数,=0表示没有数据写入,=-1表示写入失败;
■ 定位文件
lseek()可以定位文件读/写指针,在需要的位置开始读/写。
// 定位文件
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd:文件描述符
- offset:以whence为基础的文件指针偏移量
- whence:取值
- SEEK_SET:文件指针偏移的位置设置为文件开始;
- SEEK_CUR:文件指针偏移的位置设置为当前指针所在位置;
- SEEK_END:文件指针偏移的位置设置为文件末尾;
返回值:返回文件指针的位置。
lseek()函数的作用:
- 移动文件指针到文件头:lseek(fd,0,SEEK_SET);
- 获取当前文件指针的位置:lseek(fd,0,SEEK_CUR);
- 获取文件大小:lseek(fd,0,SEEK_END);
- 扩展文件长度:lseek(fd,100,SEEK_END);
使用示例,使用以上介绍的函数,模拟cp命令,复制文件,并设置复制后的文件是否扩展文件大小。
/**
* @file Example1.c
* @author
* @brief 模拟cp命令,并设置是否扩展文件,
* @version 0.1
* @date 2022-08-25
*
* @copyright Copyright (c) 2022
*
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int extend_file(int fd,int length);
int main(int argc , char *argv[])
{
if(argc != 4)
{
printf("%s srcFile dstFile extend_length\ninput error!\n",argv[0]);
return -1;
}
// 打开文件
int srcfd = open(argv[1],O_RDONLY);
if(srcfd == -1)
{
perror("open");
return -1;
}
int dstfd = open(argv[2],O_WRONLY|O_CREAT,0664);
if(dstfd == -1)
{
perror("open");
close(srcfd);
return -1;
}
// 读取文件并写入
ssize_t rdLen=0;
char rdBuf[1024]={0};
while ( (rdLen = read(srcfd,rdBuf,sizeof(rdBuf))) > 0)
{
write(dstfd,rdBuf,rdLen);
}
close(srcfd);
// 扩展文件
int extend_length = atoi(argv[3]);
if( extend_length > 0)
{
int ret = extend_file(dstfd,extend_length);
if(ret == -1)
{
close(dstfd);
return -1;
}
}
close(dstfd);
return 0;
}
/**
* @brief 扩展文件
*
* @param fd :文件描述符
* @param length :扩展的文件长度
* @return int :失败返回-1,成功返回0
*/
int extend_file(int fd,int length)
{
int ret = lseek(fd,length,SEEK_END);
if (ret == -1)
{
perror("lseek");
return -1;
}
ret = write(fd,"",1);
if(ret == -1)
{
perror("write");
return -1;
}
return 0;
}
运行结果:
■ 获取文件信息
获取文件信息使用stat()函数,获取链接文件本身的信息使用lstat()函数。
// 获取文件信息
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
参数:
- pathname:获取信息的文件名;
- statbuf:文件信息,为stat结构体,成员包含以下:
返回值:获取成功返回0,失败返回-1;
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; // 最后一次属性改变的时间
};
其中需要关注的是st_mode成员,st_mode表示文件的类型和存取的权限,是unsigned int类型,有16位,每个位代表不同的含义:
各个位表示的含义如下:
判断某个文件是否的普通文件可以先使用stat()函数获取到文件信息,然后使用if (st_mode & S_IFMT == S_IFREG)判断。
使用示例:使用stat()函数模拟实现ls -l命令,显示某个文件的信息:
/**
* @file ls_l.c
* @author
* @brief 使用stat/lstat函数模拟实现 ls -l命令
* -rw-rw-r-- 1 zoya zoya 13 8月 22 15:27 a.txt
* @version 0.1
* @date 2022-08-23
*
* @copyright Copyright (c) 2022
*
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <pwd.h> // 获取文件所有者
#include <grp.h> // 获取所在组
#include <time.h>
#include <string.h>
int main(int argc, char *argv[])
{
// 1. 判断输入的参数是否正确
if(argc < 2)
{
printf("Input argument is less 2,parameters list: %s filename\n",argv[0]);
return -1;
}
// 获取传入的文件的信息
struct stat statbuf;
int ret = stat(argv[1],&statbuf);
if (ret == -1)
{
perror("stat");
return -1;
}
// 获取文件类型和文件权限
//-rw-rw-r-- 1 zoya zoya 13 8月 22 15:27 a.txt
char szFiletype[11]={0};
mode_t mode = statbuf.st_mode;
// 文件类型
switch (mode & S_IFMT)
{
case S_IFLNK:
szFiletype[0]='l';
break;
case S_IFDIR:
szFiletype[0]='d';
break;
case S_IFREG:
szFiletype[0]='-';
break;
case S_IFBLK:
szFiletype[0]='b';
break;
case S_IFSOCK:
szFiletype[0]='s';
break;
case S_IFCHR:
szFiletype[0]='c';
break;
case S_IFIFO:
szFiletype[0]='p';
break;
default:
szFiletype[0]='?';
break;
}
// 文件访问权限
// 文件所有者
szFiletype[1] = (mode & S_IRUSR?'r':'-');
szFiletype[2] = (mode & S_IWUSR?'w':'-');
szFiletype[3] = (mode & S_IXUSR?'x':'-');
// 文件所有者所在组
szFiletype[4] = (mode & S_IRGRP?'r':'-');
szFiletype[5] = (mode & S_IWGRP?'w':'-');
szFiletype[6] = (mode & S_IXGRP?'x':'-');
// 其他人
szFiletype[7] = (mode & S_IROTH?'r':'-');
szFiletype[8] = (mode & S_IWOTH?'w':'-');
szFiletype[9] = (mode & S_IXOTH?'x':'-');
// 硬链接数
int linkNum = statbuf.st_nlink;
// 文件所有者
char *fileUsr = getpwuid(statbuf.st_uid)->pw_name;
// 文件所在组
char *groupName = getgrgid(statbuf.st_gid)->gr_name;
// 文件大小
off_t fileSize = statbuf.st_size;
// 获取修改时间
char *time = ctime(&statbuf.st_mtime); // 将秒数转换为表示时间的字符串
char sztime[512]={0};
strncpy(sztime,time,strlen(time)-1);
char buf[1024]={0};
sprintf(buf,"%s %d %s %s %ld %s %s",szFiletype,
linkNum,fileUsr,groupName,fileSize,sztime,argv[1]);
printf("%s\n",buf);
return 0;
}
■ 文件属性相关
// 文件属性相关
int access(const char *pathname,int mode); // 判断文件权限,或者判断文件是否存在
int chmod(const char *pathname,int mode); // 修改文件权限
int chown(const char *path,uid_t owner,gid_t group); // 修改文件的所有者或所在组
int truncate(const char *path, off_t length); // 扩展或缩减文件大小
查看uid、gid可以使用命令【 id 用户名】。
access()函数中mode取值如下:
- F_OK 判断文件是否存在
- R_OK 判断是否有读的权限
- W_OK 判断是否有写的权限
- X_OK 判断是否有执行权限
chmod()函数中mode是八进制的数字;
truncate()函数中参数length表示文件最终要变成的大小。
上面的函数返回值:成功返回0,失败返回-1。
三,目录操作函数
1.目录的增删改
目录的增、删、改常用函数有:
int rename(const char *oldpath,const char *newpath); // 重命名目录
int chdir(const cahr *path); // 改变目录
char *getcwd(char *buf,size_t size); // 获取当前进程的工作目录
int mkdir(const char *pathname,mode_t mode); // 创建具有mode权限的目录
int rmdir(const char *pathname); // 删除目录
getcwd()函数中:
参数buf保存获取到的路径;
参数size是数组大小;
返回值,成功返回获取到的路径,失败返回NULL。
2. 目录遍历函数
DIR *opendir(const char *name); // 打开目录
struct dirent *readdir(DIR *dirp); // 读取目录
int closedir(DIR *dirp); // 关闭目录
目录遍历函数中需要了解结构体dirent:
struct dirent {
ino_t d_ino; // 此目录进入点的inode
off_t d_off; // 目录文件开头至此目录进入点的位移
unsigned short int d_reclen; // d_name的长度,不包含NULL字符
unsigned char d_type; // d_name 所指的文件类型
char d_name[256]; // 文件名
};
其中文件类型d_type参数有:
- DT_BLK 块设备
- DT_CHR 字符设备
- DT_DIR 目录
- DT_LNK 软链接
- DT_FIFO 管道
- DT_REG 普通文件
- DT_SOCK 套接字
- DT_UNKNOWN 未知
示例:可以使用以上函数统计某个目录下的某种文件或目录的个数:
/**
* @file example.c
* @author your name (you@domain.com)
* @brief 统计某个目录下普通文件、目录、软链接等的个数
* @version 0.1
* @date 2022-08-26
*
* @copyright Copyright (c) 2022
*
*/
#include "fileIO.h"
typedef enum{
E_TYPE_BLK=0, E_TYPE_CHR, E_TYPE_DIR, E_TYPE_LNK, E_TYPE_FIFO, E_TYPE_REG, E_TYPE_SOCK, E_TYPE_UNKNOWN, E_TYPE_TOTAL,} FILE_TYPES;
int g_nCount[E_TYPE_TOTAL];
char *szFileType[] = {"块设备","字符设备","目录","软连接","管道","普通文件","套接字","未知文件"};
int getFiles(const char *dirname);
int main(int argc, char *argv[])
{
// 判断命令参数
if(argc < 2)
{
printf("%s dirname,please input correct parameters!\n",argv[0]);
return -1;
}
// 格式化打印
printf("统计:\t\t");
for(int i=0;i<E_TYPE_TOTAL;i++)
{
printf(" %s ",szFileType[i]);
}
printf("\n");
for (int j=1;j<argc;j++)
{
memset(g_nCount,0,sizeof(g_nCount));
// 获取目录中的文件信息
int ret = getFiles(argv[j]);
if(ret == -1)
{
return -1;
}
// 打印结果
printf("%s\n\t\t",argv[j]);
for(int i=0;i<E_TYPE_TOTAL;i++)
{
printf(" %d\t",g_nCount[i]);
}
printf("\n");
}
return 0;
}
int getFiles(const char *dirname)
{
// 打开目录
DIR *pdir = opendir(dirname);
if(pdir == NULL)
{
perror("opendir");
return -1;
}
// 读取目录
int totals=0;
struct dirent *pd;
while((pd = readdir(pdir)) != NULL)
{
// 如果是.或.. ,忽略
if(strcmp(pd->d_name,".")==0 || strcmp(pd->d_name,"..")==0)
continue;
// 判断文件类型
switch (pd->d_type)
{
case DT_DIR: // 如果是目录,递归调用该函数打开目录
{
g_nCount[E_TYPE_DIR]++;
char name[512]={0};
sprintf(name,"%s/%s",dirname,pd->d_name);
getFiles(name);
}
break;
case DT_BLK:
g_nCount[E_TYPE_BLK]++;
break;
case DT_CHR:
g_nCount[E_TYPE_CHR]++;
break;
case DT_LNK:
g_nCount[E_TYPE_LNK]++;
break;
case DT_FIFO:
g_nCount[E_TYPE_FIFO]++;
break;
case DT_REG:
g_nCount[E_TYPE_REG]++;
break;
case DT_SOCK:
g_nCount[E_TYPE_SOCK]++;
break;
case DT_UNKNOWN:
g_nCount[E_TYPE_UNKNOWN]++;
break;
default:
{
perror("readdir");
}
break;
}
}
// 关闭目录
closedir(pdir);
return 0;
}
显示结果:
四,与文件描述符相关的函数
int dup(int oldfd); // 复制文件描述符
int dup2(int oldfd,int newfd); //重定向文件描述符
int fcntl(int fd,int cmd,...); // 复制文件描述符,设置/获取文件的状态标志
函数说明:
dup():复制一个新的文件描述符
-oldfd:旧的文件描述符;
-假设fd指向文件a.txt,且fd=3,那么执行 int fd1 = dup(fd);后,fd1也指向a.txt文件,但fd1是从 空闲的文件描述符表中找一个最小的,
– 返回值:返回一个新的文件描述符。
dup2():重定向文件描述符
– 如果oldfd指向文件a.txt,newfd指向b.txt,那么int ret = dup2(oldfd,newfd);调用后,newfd和b.txt会被关闭,然后newfd指向a.txt;
– 需要注意,oldfd必须是一个有效的文件描述符;当oldfd和newfd值相同时,表示什么都不会做。
fcntl():复制文件描述符;设置文件属性或状态
– 参数 fd:文件描述符
-参数 cmd:对文件的操作,可以有以下取值
-F_DUPFD:复制文件描述符,复制参数fd,返回值是一个新的文件描述符
-F_GETFL:获取指定文件描述符的文件状态flag(和open函数的参数flag一样)
-F_SETFL:设置指定文件描述符的文件状态flag
-必选项:O_RDONLY O_WRONLY O_RDWR
-可选项:
-O_APPEND:追加数据
-O_NONBLOCK:设置成非阻塞
使用示例1:dup()复制一个文件描述符
/**
* dup()函数复制文件描述符
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main()
{
// 打开一个文件
int fd = open("1.txt",O_RDWR | O_CREAT,0664);
if(fd == -1)
{
perror("open");
return -1;
}
// 复制文件描述符
int fd1 = dup(fd);
if(fd1 == -1)
{
perror("dup");
return -1;
}
printf("fd = %d, fd1 = %d\n",fd,fd1);
// 关闭fd
close(fd);
// 对fd1写入数据
char *str = "Hello";
write(fd1,str,strlen(str));
// 关闭fd1
close(fd1);
return 0;
}
使用示例2:dup2()重定向文件描述符
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main()
{
int fd1 = open("2.txt",O_RDWR|O_CREAT,0664);
int fd2 = open("3.txt",O_RDWR|O_CREAT, 0664);
if(fd1 == -1 || fd2 == -1)
{
perror("open");
return -1;
}
printf("fd1=%d, fd2=%d\n",fd1,fd2);
int fd3 = dup2(fd1,fd2); // 复制fd1到fd2
if(fd3 == -1)
{
perror("dup2");
return -1;
}
printf("fd1=%d, fd2=%d, fd3=%d\n",fd1,fd2,fd3);
close(fd1);
char *str = "Hello dup2";
write(fd2,str,strlen(str));
close(fd2);
return 0;
}
运行结束后,2.txt文件中有内容,3.txt文件中没有内容
使用示例3:使用fcntl()设置文件状态flag,并向文件写入数据。
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main()
{
// 复制文件描述符
// 打开文件
// int fd = open("1.txt",O_RDWR);
// int ret = fcntl(fd,F_DUPFD);
// printf("fd = %d, ret = %d\n",fd,ret);
// 修改或者获取文件的flag
int fd = open("1.txt",O_WRONLY);
if(fd == -1)
{
perror("open");
return -1;
}
// 修改文件描述符的状态,给flag加入O_APPEND标记,可以追加数据
// 获取文件描述符的状态
int fflag = fcntl(fd,F_GETFL);
// 设置文件描述符的状态
fflag |= O_APPEND;
int ret = fcntl(fd,F_SETFL,fflag);
if(ret == -1)
{
perror("fcntl");
return -1;
}
char *str = " nihao ";
write(fd,str,strlen(str));
close(fd);
return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/46093.html