Linux系统编程(三)--- 文件IO

目录

一、文件描述符

二、open函数

三、read/write函数

四、lseek函数

五、close函数

六、stat函数

练习:实现stat功能:

七、目录操作

八、FILE指针与文件描述符的转换

转换函数:

九、软链接和硬链接

1.硬链接

2.软链接


文件IO顾名思义就是linux系统中专门操作文件的函数

一、文件描述符

文件描述符是一个非负整数,是Linux内核用来标识进程打开文件的索引。可以把它理解为文件的"代号"或"编号"

特点:

每个进程都有自己的文件描述符表

文件描述符从0开始分配

0、1、2是系统预留的标准描述符

新打开的文件从3开始分配
0 STDIN_FILENO 标准输入键盘

1 STDOUT_FILENO 标准输出屏幕

2 STDERR_FILENO 标准错误屏幕

二、open函数

int open(const char *pathname, int flags, mode_t mode);

pathname:文件路径(字符串)

flags:打开方式标志

mode:权限模式(八进制,当创建文件时使用

返回值:成功返回文件描述符(≥0),失败返回-1

flag参数:

O_RDONLY 只读打开 只能读取,不能写入

O_WRONLY 只写打开 只能写入,不能读取

O_RDWR 读写打开 既可读又可写

O_CREAT 创建文件 文件不存在时创建

O_TRUNC 截断文件 打开时清空文件内容

O_APPEND 追加模式 写入时追加到文件末尾

示例:

复制代码
#include <stdio.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
    if(argc != 2)
    {
        printf("%s <filename>\n", argv[0]);
        return -1;
    }
    // 打开文件:读写模式 + 创建(如果不存在)
    // 0777:权限设置为rwxrwxrwx //0666则为rw-rw-rw-
    int fd = open(argv[1], O_RDWR|O_CREAT, 0777);
    if(fd < 0)
    {
        perror("open fail");
        return -1;
    }
    
    printf("success\n");    
    return 0;
}

三、read/write函数

ssize_t read(int fd, void *buf, size_t count); //读

fd:文件描述符

buf:缓冲区指针(存放读取的数据)

count:要读取的字节数

返回值:实际读取的字节数,0表示文件结束,-1表示错误
ssize_t write(int fd, const void *buf, size_t count); //写

fd:文件描述符(1表示标准输出)

buf:要写入的数据缓冲区

count:要写入的字节数

返回值:实际写入的字节数,-1表示错误

示例:实现cp功能:

cs 复制代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("%s <filename> <filename>\n",argv[0]);
		return -1;
	}

	int fd = open(argv[1],O_RDONLY|O_CREAT,0666);
	int fd2 = open(argv[2],O_WRONLY|O_CREAT,0666);
	if(fd < 0 || fd2 <0)
	{
		perror("open fail");
		return -1;
	}

	char buf[1024];
	int ret =0;
	while((ret = read(fd,buf,sizeof(buf)))!=0)
	{
        //buf[ret] = '\0';  // 添加字符串结束符
        //printf("ret = %d buf = %s\n", ret, buf);
		write(fd2,buf,ret);
	}

	return 0;
}

四、lseek函数

off_t lseek(int fd, off_t offset, int whence); //移动文件读写位置

fd:文件描述符

offset:偏移量(正数向后,负数向前)

whence:起始位置参考点

返回值:新的文件偏移量,-1表示错误

whence参数:

SEEK_SET文件开头从文件开始位置计算偏移

SEEK_CUR当前位置从当前读写位置计算偏移

SEEK_END文件末尾从文件结束位置计算偏移

示例:创建空洞文件hole.c

cs 复制代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
    if(argc != 3)
    {
        printf("%s <源文件> <空洞文件>\n", argv[0]);
        return -1;
    }
    int fd = open(argv[1], O_RDONLY|O_CREAT, 0666);
    int fd2 = open(argv[2], O_WRONLY|O_CREAT, 0666);
    
    if(fd < 0 || fd2 < 0)
    {
        perror("open fail");
        return -1;
    }
    // 获取源文件大小
    off_t len = lseek(fd, 0, SEEK_END);
    
    // 在新文件中跳到(len-1)位置
    lseek(fd2, len-1, SEEK_SET);
    
    // 写入一个字节,创建空洞文件
    write(fd2, "", 1);
    
    close(fd);
    close(fd2);
    return 0;
}

五、close函数

int close(int fd);

释放系统资源(文件描述符是有限的)

确保数据写入磁盘

避免资源泄漏

六、stat函数

int stat(const char *pathname, struct stat *statbuf);

pathname:文件路径

statbuf:stat结构体指针(存放文件属性)

返回值:成功返回0,失败返回-1

简单示例:获取文件大小

cs 复制代码
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
    struct stat st;
    if(stat(argv[1], &st) < 0)
    {
        perror("fail");
        return -1;
    }
    printf("size = %ld\n", st.st_size);
    return 0;
}

具体结构体成员使用命令Man 2 stat来查看手册

练习:实现stat功能:

重点理解st_mode的用法

cs 复制代码
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
int main(int argc, const char *argv[])
{
	if (argc != 2)
	{
		printf("Usage: %s <filename>\n",argv[0]);
		return -1;
	}

	struct stat st;
	if (stat(argv[1],&st) < 0)
	{
		perror("stat fail");
		return -1;
	}


	printf("  File: %s \n",argv[1]);
	printf("  Size: %ld\t Blocks: %ld\tIO Block: %ld ",st.st_size,st.st_blocks,st.st_blksize);
	
	
	switch (st.st_mode & S_IFMT)
	{
	case	S_IFSOCK:
			puts(" socket file");	
			break;
	case S_IFLNK:
			puts(" symlink file");	
			break;
	case S_IFREG:
			puts(" regular file");	
			break;
	case S_IFBLK:
			puts(" block file");	
			break;
	case S_IFDIR:
			puts(" directory file");	
			break;
	case S_IFCHR:
			puts(" character file");	
			break;
	case S_IFIFO:
			puts(" pipe file");	
			break;
	default:
			puts(" unkonwn file");	
			break;
	}

	printf("Device: %ld\t Inode: %ld Links: %ld\n",st.st_dev,st.st_ino,st.st_nlink);
	printf("Access: (%#o/",st.st_mode&(S_IRWXU|S_IRWXG|S_IRWXO));
	
	switch (st.st_mode & S_IFMT)
	{
	case	S_IFSOCK:
		    putchar('s');
			break;
	case S_IFLNK:
			putchar('l');
			break;
	case S_IFREG:
			putchar('-');
			break;
	case S_IFBLK:
			putchar('b');
			break;
	case S_IFDIR:
			putchar('d');
			break;
	case S_IFCHR:
			putchar('c');
			break;
	case S_IFIFO:
			putchar('p');
			break;
	default:
			putchar('?');
			break;
	}
    //所有者 
	st.st_mode&S_IRUSR?putchar('r'):putchar('-');
	st.st_mode&S_IWUSR?putchar('w'):putchar('-');
	st.st_mode&S_IXUSR?putchar('x'):putchar('-');
	//所属组
	st.st_mode&S_IRGRP?putchar('r'):putchar('-');
	st.st_mode&S_IWGRP?putchar('w'):putchar('-');
	st.st_mode&S_IXGRP?putchar('x'):putchar('-');
	//其他人 
	st.st_mode&S_IROTH?putchar('r'):putchar('-');
	st.st_mode&S_IWOTH?putchar('w'):putchar('-');
	st.st_mode&S_IXOTH?putchar('x'):putchar('-');

	printf(")  Uid:(%d/%s)   Gid:(%d/%s)\n",st.st_uid,getpwuid(st.st_uid)->pw_name,st.st_gid,getgrgid(st.st_gid)->gr_name);
	struct tm *ptm = localtime(&st.st_atime);
	printf("Access: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday,
			ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
	ptm = localtime(&st.st_mtime);
	printf("Modify: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday,
			ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
	ptm = localtime(&st.st_ctime);
	printf("Change: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday,
			ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
	return 0;
}

七、目录操作

DIR *opendir(const char *name);//打开

struct dirent *readdir(DIR *dirp);//读取(注意:目录操作没有写)

int closedir(DIR *dirp);//关闭

示例:计算文件的里面的目录和文件数量

cs 复制代码
#include <stdio.h>
#include <dirent.h>
int main(int argc, const char *argv[])
{
    if(argc != 2)
    {
        printf("fail:%s <目录名>\n", argv[0]);
        return -1;
    }
    // 打开目录
    DIR *dp = opendir(argv[1]);
    if (dp == NULL)
    {
        perror("fail");
        return -1;
    }
    
    struct dirent *pd = NULL;
    int cntdir = 0;   // 目录计数
    int cntfile = 0;  // 文件计数
    
    while(1)
    {
        // 读取目录项
        pd = readdir(dp);
        if (pd == NULL)  // 读取完毕
        {
            break;
        }
        
        // 跳过隐藏文件(.开头)
        if(pd->d_name[0] != '.')
        {
            // 根据类型计数
            if(pd->d_type == DT_DIR)
            {
                ++cntdir;
            }
            else if(pd->d_type == DT_REG)
            {
                ++cntfile;
            }
        }
    }
    printf("目录有%d个,文件有%d个\n", cntdir, cntfile);
    closedir(dp);
    return 0;
}

注意:这里的.是表示当前目录, ..是表示上一级目录, 使用ls -a 可以查看隐藏文件

八、FILE指针与文件描述符的转换

首先这两者的区别是:

文件IO每次读写都会进入内核,而标准IO使用用户空间缓冲区(通常8KB或更大)满了再进入内核或者强制fflush()

转换函数:

FILE *fdopen(int fd, const char *mode);

int fileno(FILE *stream);

示例:相互转换

cs 复制代码
#include <stdio.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
    if(argc != 2)
    {
        printf("%s <filename>\n", argv[0]);
        return -1;
    }
    // 使用open打开文件,获得文件描述符
    int fd = open(argv[1], O_RDWR|O_CREAT, 0777);
    if(fd < 0)
    {
        perror("open fail");
        return -1;
    }
    printf("fd=%d\n", fd);
    // 文件描述符 → FILE指针
    FILE *fp = fdopen(fd, "r");  // 模式必须与fd打开模式一致
    if(fp == NULL)
    {
        perror("fail");
        return -1;
    }
    // FILE指针 → 文件描述符
    fd = fileno(fp);
    printf("fileno = %d\n", fd);
    close(fd);
    fclose(fp);
    return 0;

}

注意:混用IO的话,不管是先用的是文件IO还是标准IO都是先关文件IO再关标准IO

九、软链接和硬链接

Linux系统中一个文件由目录项(struct dirent)、inode表和数据块组成

目录项 :包括文件名inode 节点号

Inode :包含文件的一些信息。

例如,inode号,文件类型,文件大小等待。

数据块:文件具体内容存放的地方。

1.硬链接

本质:是给我们的文件取别名。

与源文件直接关联。修改一个,另一个同时被修改 。删除一个另一个不会有影响。
Linux系统中规定,只有在文件的打开次数为0(即文件这个时候没有打开),并且文件的硬连接数为0,此时文件才会被删除

特点:

(1)不占用硬盘空间

(2)不能对目录操作

(3)不能跨文件系统

命令 :ln 源文件名 硬链接文件名

2.软链接

本质:类似于我们 windows 的快捷方式,用来记录我们目标的路径

特点:

(1)占用磁盘空间

(2)可以对目录操作

(3)可以跨文件系统

命令:ln -s 源文件 软链接文件

两者区别:

硬链接原文件和新文件的inode编号一致。而软链接不一样。

对原文件删除,会导致软链接不可用,因为软连接存储是路径,而硬链接不受影响

相关推荐
安科士andxe7 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
小白同学_C10 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖10 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
2601_9491465310 小时前
Shell语音通知接口使用指南:运维自动化中的语音告警集成方案
运维·自动化
儒雅的晴天10 小时前
大模型幻觉问题
运维·服务器
Gofarlic_OMS11 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
通信大师11 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
dixiuapp11 小时前
智能工单系统如何选,实现自动化与预测性维护
运维·自动化
不做无法实现的梦~12 小时前
ros2实现路径规划---nav2部分
linux·stm32·嵌入式硬件·机器人·自动驾驶
Elastic 中国社区官方博客12 小时前
如何防御你的 RAG 系统免受上下文投毒攻击
大数据·运维·人工智能·elasticsearch·搜索引擎·ai·全文检索