嵌入式解谜日志之Linux操作系统—文件编程1
1.功能:从指定的文件中stream中读取nmemb个元素,每个元素size大小的这么多数据,存放到指定ptr文件中。fgets和fputs实现文件的拷贝,对于二进制文件大量存在'\0',fgets读取文件没有问题,但是fputs输出字符串,遇到'\0'就会结束输出。②遇到'\0'('\n'会被读到最终的buffer中),'\n'(换行)会读取结束;1.功能:关闭流指针,并且刷新流中的数据到指定位
文件编程的两种方式
1.标准IO的库函数实现:C库→系统
2.系统调用实现:系统中的函数

Linux系统将一切操作转换为文件:
目的:方便操作
文件的通用操作方式:①打开文件
②读取/编写文件
③关闭文件
一.库函数(标准C库中的标准IO函数)
通过减少对系统的调用来提高效率
对应函数:
(1)打开文件:fopen
FILE *fopen(const char *pathname,const char *mode);
1.功能:流打开函数 打开pathname指定的文件,关联一个流
①流(stream):流就是内存和硬盘直接形成一个数据通道
②FILE *:流指针(描述的文件操作的这个动态对象) 关联到流上,并代表这个流
③fopen的作用:相当于将硬件上的文件转换成了内存的流指针
④errno:是系统设计错误的全局变量
errno =2
printf("想要输出的字符串");
输出效果:想要的输出的字符串:错误原因
实参:FILE *fd=fopen(argv[i],"r")
2.参数 :① @pathname:表示打开的文件
②@mode:模式
【r 只读】【 r+ 可读可写】:文件必须存在
【w 只写】【w+ 可读可写】:如果文件存在,截断成0;如果不存在,则创建一个文件
【a 追加的写】【a+ 追加的读写】:如果文件存在,则在末尾写,如果不存在,则创建
3.返回值:成功返回FILE *指针
失败NULL并设置errno用来表明错误的原因
(2)读写:
系统中,默认打开三个文件,直接用:
流指针 相应设备 系统中的文件描述符
标准输入 stdin 关联到键盘 0
标准输出 stdout 关联到屏幕 1
标准出错 stderr 关联到屏幕 2
1.fgetc/fputc:单个字符读写
fgetc()
int fgetc(FILE *stream);
①功能:从stream中读取字符
实参:fgetc(fp);
②参数:@stream 要读取的文件流指针(FILE *stream就是上面的fp)
③返回值:
成功:读取到字符的ASCII码值
失败:EOF(文件结尾的标志EOF(-1),并不真正存在在文件中)
getchar()<=>fgetc(stdin)
fputc()
函数原型:int fputc(int c, FILE *stream)
功能:将字符c写到stream中
调用的实参:fputc(c,fp)
参数:@c:想要写的字符
@steam:想要写入的文件()
返回值:成功:返回被写入的字符
失败:返回EOF;
window系统中,实现文件操作是需要区分文本文件和二进制文件的
Linux系统中,操作是不区分文本文件和二进制文件
2.fgets/fputs:读写字符串
fgets和fputs实现文件的拷贝,对于二进制文件大量存在'\0',fgets读取文件没有问题,但是fputs输出字符串,遇到'\0'就会结束输出
fgets()
char *fgets(char *s,int size,FILE *stream);
①功能:从stream中读取一个字符串,将字符串保存到s指定的size大小一块空间
实参:fgets(buf,sizeof(buf),fp)
②参数:@s:表示存储读取结果的一块字符空间
@size:表示一次读取多少个字节
@stream就是要读取的文件
③返回值:
成功:返回s
失败:NULL
注意:
//读取文件结尾也返回NULL
//fgets读取结束,
①EOP会结束;
②遇到'\0'('\n'会被读到最终的buffer中),'\n'(换行)会读取结束;
③size空间读满会读取结束;
//fgets会自己在buffer中最后一个字符的后面加上'\0',处理成字符串。
fputs()
函数原型:char *fputs(char *s,FILE *stream);
实参:fputs(s,fp)
1.功能:将s中的字符串,输出到stream对应的文件中
2.参数:@s:表示的存放字符串的一块空间
@stream:要输出的文件
3.返回值:成功 非负值
失败 EOF
遇到'\0'输出会结束。(字符串的结束标志)
3.fread/fwrite
fread()
函数原型:size_t fread(void *ptr,szie_t size,size_t nmemb,FILE *stream);//按对象的二进制读写
实参:fread(buf,sizeof(char),1023,fp_s)
1.功能: 从指定的文件中stream中读取nmemb个元素,每个元素size大小的这么多数据,存放 到指定ptr文件中
2.参数: @ptr 存储数据的空间的首地址 :存储数据的空间char buf[1024]
@size 每个元素的大小
@nmemb 元素的个数 1024-1;
@stream 要读取的文件:流指针:fp_s
3.返回值: 成功 返回读取到的对象个数
失败 返回0(达到结尾返回0)
fwrite()
函数原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
实参:fwrite(buf,szieof(char),ret,fp_d);
1.功能: 将ptr所在空间的nmemb个大小为size的
2.参数: @ptr 存储数据的空间的首地址
@size 每个元素的大小
@nmemb 元素的个数
@stream 要写入的文件:流指针:fp_d
3.返回值: 成功 返回读取到的对象个数
失败 返回0(达到结尾返回0
fread和fwrite配合使用时
char buf[1024];
int ret = fread(buf,sizeof(char),1023,fp_s);
fwrite(buf,sizeof(char),ret,fp_d);
(3)关闭:fclose()
函数原型:int fclose(FILE *stream);
实参:fclose(fd);
1.功能: 关闭流指针,并且刷新流中的数据到指定位置,同时关闭底层的文件描述符
2.参数: @stream:要关闭的流指针(文件)
3.返回值: 成功 返回0
失败 返回EOF

| 函数 | 功能描述 |
|---|---|
FILE *fopen(const char *path, const char *mode) |
打开文件,返回文件指针(FILE*);mode 为打开模式(如 "r" 读,"w" 写)。 |
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) |
从文件读取数据到内存,返回成功读取的元素个数。 |
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) |
将内存数据写入文件,返回成功写入的元素个数。 |
int fclose(FILE *stream) |
关闭文件,释放资源,返回 0 表示成功。 |
int fgetc(FILE *stream) / int fputc(int c, FILE *stream) |
读写单个字符。 |
char *fgets(char *s, int size, FILE *stream) / int fputs(const char *s, FILE *stream) |
读写字符串(fgets 会读取换行符,fputs 不会自动添加换 |
辅助函数:feof 与 ferror
int feof(FILE *stream); // 判断是否到达文件末尾
int ferror(FILE *stream); // 判断是否发生读写错误
示例:区分 EOF 与错误
#include <stdio.h>
int main(void)
{
int ret;
while ((ret = fgetc(stdin)) != EOF) {
printf("ret = %c:%d\n", ret, ret);
}
if (feof(stdin)) {
printf("file end!\n");
} else if (ferror(stdin)) {
printf("file error!\n");
}
return 0;
}
a
ret = a:97
file end!
其他格式化输出函数
| 函数 | 说明 |
|---|---|
printf(format, ...) |
输出到标准输出 |
fprintf(fp, format, ...) |
输出到文件流 |
sprintf(str, format, ...) |
输出到字符串缓冲区 |
snprintf(str, size, format, ...) |
安全版本,防止溢出 |
缓存机制
1.行缓冲,1k,terminal,主要用于人机交互
缓冲区满或者遇到\n刷新 //大小为1024 个字节
//行缓存多是关于终端的一些操作
刷新条件:1.遇到\n刷新
2.缓存区满刷新
3.程序正常结束刷新
4.fflush强制刷新 //fflush(stdout)
2.全缓冲,4K,主要用于文件的读写
缓存区满刷新缓存区 //大小为4096
//对普通文件进行标注IO操作,建立的缓存一般为全缓存
刷新条件:1.缓存区满刷新
2.程序结束刷新
3.fflush强制刷新 //fflush(fp);
./a.out > 1.txt 输出重定向
./a.out < 1.txt 输入重定向
3.无缓冲,0k,主要用于出错处理信息的输出 stderr
//不对数据缓存直接刷新
定位函数 --- fseek() | ftell() | rewind()
(1)fseek()
函数原型:int fseek(FILE *stream, long offset, int whence);
实参:fseek(fp,0,SEEK_SET); //文件开头定位到偏移量为0的位置(开头)
fseek(fp,100,SEEK_SET); //将文件开头向后偏移100
1.功能:将stream指定的文件,从whence的位置,定位到offset的位置
2.参数:@stream:表示要定位的文件
@offset :偏移量
offset < 0,需要考虑参考点是否可以
offset > 0,如果大于文件本身大小是可以的
注意:文件往后偏移量如果大于文件本身是可以的,如果想占有存储空间,
必须要有写的操作多出来的空间,被称为空洞,用\0占位
@whence :参考点
3.参考点:
SEEK_SET //文件开头
SEEK_CUR //文件当前位置
SEEK_END //文件末尾
4.返回值: 成功 返回0
失败 返回-1 && errno会被设置
创建空洞文件:
1.先定位
2.偏移一定字节数(超过直接文件大小的偏移量)
3.然后做一次写操作(不管写入多大,如果小于偏移量,用\0占位)
创建空洞文件示例
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("hole.txt", "w");
if (fp == NULL)
{
perror("fopen fail");
return -1;
}
int n = 0;
scanf("%d", &n);
fseek(fp, n - 1, SEEK_SET); // 定位到目标位置前一个字节
fputc('\0', fp); // 写入一个空字符,创建空洞
fclose(fp);
return 0;
}
效果:创建大小为 n 字节的空洞文件,中间填充 \0
模拟云盘下载:复制文件并创建空洞
#include <stdio.h>
int main(int argc, const char* argv[])
{
if (argc != 3)
{
printf("Usage: %s <src> <dest>\n", argv[0]);
return -1;
}
FILE *fp_s = fopen(argv[1], "r");
FILE *fp_d = fopen(argv[2], "w");
if (fp_s == NULL || fp_d == NULL)
{
perror("fopen fail");
return -1;
}
// 获取源文件大小
fseek(fp_s, 0, SEEK_END);
long len = ftell(fp_s);
printf("len = %ld\n", len);
// 创建同大小空洞文件
fseek(fp_d, len - 1, SEEK_SET);
fputc('\0', fp_d);
fflush(fp_d);
// 重置指针,开始复制
rewind(fp_s);
rewind(fp_d);
int ret = 0;
while ((ret = fgetc(fp_s)) != EOF)
{
fputc(ret, fp_d);
}
fclose(fp_s);
fclose(fp_d);
return 0;
}
理想效果:成功复制文件,目标文件大小与源一致,内容完全相同。
(2)ftell()
函数原型:long ftell(FILE *stream);
实参:int len=ftell(fp);
1.功能:获得stream对应文件的当前位置指示器的值,获得文件的大小
(3)rewind()
函数原型:void rewind(FILE *stream);
实参:rewind(fp);
功能:将文件定位到开头
时间转换函数:

二.文件IO(系统调用)
| 函数 | 功能描述 |
|---|---|
|
打开文件,返回文件描述符(非负整数); |
|
从文件描述符 |
|
将 |
|
关闭文件描述符,返回 0 表示成功。 |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h> // 包含open函数的标志定义
int main() {
// 打开源文件(只读)和目标文件(创建、只写、截断)
int src_fd = open("source.txt", O_RDONLY);
// O_WRONLY:只写;O_CREAT:不存在则创建;O_TRUNC:存在则清空
int dest_fd = open("dest.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (src_fd == -1 || dest_fd == -1) {
perror("文件打开失败");
exit(1);
}
// 缓冲区:一次读写1024字节
char buf[1024];
ssize_t n;
// 循环读写,直到文件末尾(read返回0)
while ((n = read(src_fd, buf, sizeof(buf))) > 0) {
// 确保所有数据都被写入(可能需要多次write)
ssize_t total_written = 0;
while (total_written < n) {
ssize_t written = write(dest_fd, buf + total_written, n - total_written);
if (written == -1) {
perror("写入失败");
exit(1);
}
total_written += written;
}
}
if (n == -1) { // 检查read是否出错
perror("读取失败");
exit(1);
}
// 关闭文件描述符
close(src_fd);
close(dest_fd);
printf("文件复制成功\n");
return 0;
}
系统调用的打开标志(open 的 flags 参数)
| 标志 | 含义 |
|---|---|
O_RDONLY |
只读打开。 |
O_WRONLY |
只写打开。 |
O_RDWR |
读写打开。 |
O_CREAT |
若文件不存在则创建(需配合 mode 参数指定权限)。 |
O_TRUNC |
若文件存在则清空内容。 |
O_APPEND |
追加模式,写入数据到文件末尾。 |
文件权限与 umask
实际文件权限 = mode & ~umask
mode: 0666 (rw-rw-rw-)
umask: 0022 (----w--w-)
~umask:111101101
结果:0644 (rw-r--r--)
read 与 write 系统调用
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
- read:从文件描述符读取数据到缓冲区
- write:将缓冲区数据写入文件描述符
注意:
read读取的数据若用于字符串处理,需手动添加\0结尾。
使用 read/write 实现 cat 功能
#include<stdio.h>
#include<unistd.h>
int main(void)
{
char buf[1024];
int ret = read(0, buf, sizeof(buf)); // 从stdin读
write(1, buf, ret); // 写入stdout
return 0;
}
运行结果:
$ ./a.out
hello
ret = 6 buf = hello
关键注意事项
- 错误处理:文件操作可能失败(如文件不存在、权限不足),需通过返回值(
fopen返回NULL,open返回-1)判断,并使用perror打印错误原因。 - 资源释放:文件使用完毕后必须关闭(
fclose或close),否则会导致资源泄漏(文件描述符耗尽)。 - 缓冲刷新:标准 IO 的缓冲数据不会立即写入磁盘,可通过
fflush强制刷新,或在fclose时自动刷新。 - 二进制与文本模式:在 Windows 系统中,
"b"模式(如"rb"、"wb")用于二进制文件(避免换行符转换),Linux 系统无区别。
(4)lseek定位文件偏移
off_t lseek(int fd, off_t offset, int whence);
- 功能:移动文件读写位置
- 常用操作:
lseek(fd, 0, SEEK_SET);→ 定位开头lseek(fd, 0, SEEK_END);→ 定位末尾off_t len = lseek(fd, 0, SEEK_END);→ 获取文件大小
三、标准 IO 与系统调用 IO 的核心区别
| 维度 | 标准 IO(stdio.h) | 系统调用 IO(unistd.h) |
|---|---|---|
| 接口类型 | 库函数(封装系统调用) | 系统调用(直接与内核交互) |
| 缓冲机制 | 有缓冲(全缓冲、行缓冲、无缓冲) | 无缓冲(每次调用直接操作硬件) |
| 操作对象 | 文件指针(FILE*) |
文件描述符(int,非负整数) |
| 效率 | 高(减少系统调用次数) | 低(频繁调用系统调用,开销大) |
| 可移植性 | 高(符合 C 标准,跨平台一致) | 低(不同操作系统的系统调用细节可能不同) |
| 适用场景 | 普通文件读写、文本处理、用户态应用程序 | 设备驱动、高性能 IO、需要精确控制的场景 |

目录:
(1)打开目录:opendir
函数原型:DIR *opendir(const char *name)
使用时需要添加头文件:
#include<sys/types.h>
#include<dirent.h>
1.功能:打开一个目录文件并关联一个目录流
2.实参:DIR *dp=opendir("目录名")
3.参数:@name:要打开目录名字字符串
4.返回值:
成功:目录流指针
失败:NULL并且errno会被设置

(2)读写:目录没有写的操作,读readdir
添加头文件:
#include<dirent.h>
函数原型:struct dirent *readdir(DIR *dirp)
struct dirent {
ino_t d_ino; 索引节点编号
off_t d_off; 指向节点的编号
unsigned short d_reclen; 该编号并非偏移量; 此记录的长度
unsigned char d_type; 文件类型;并非所有文件系统类型都支持此类型
char d_name[256]; 空字符结尾的文件名
};
1.实参:readdir(dp)

2.功能:从dirp中读取目录项,返回对应一个结构体
3.参数:@dirp:要操作的目录流指针
4.返回值:
成功 :读到的某一个目录项的结构体指针
失败:NULL&&errno
(3)关闭:closedir
#include<dirent.h>
函数原型:int clsoedir(DIR *dirp);
实参:clsoedir(dp);
1.功能:关闭目录流
2.参数:dirp:目录流指针
3.返回值:
成功 0
失败:-1并且errno会被设置


硬链接 vs 软链接 对比
| 特性 | 硬链接 | 软链接 |
|---|---|---|
| inode 编号 | 与原文件相同 | 不同 |
| 是否占用空间 | 否(仅目录项) | 是(存储路径) |
| 能否跨文件系统 | 否 | 是 |
| 能否链接目录 | 否 | 是 |
| 原文件删除后状态 | 仍可访问 | 变为无效(悬空链接) |
获取文件状态:stat 系统调用
#include <sys/stat.h>
int stat(const char *pathname, struct stat *statbuf);
- 功能:获取指定路径文件的状态信息。
- 参数:
pathname:文件路径。statbuf:接收信息的struct stat缓冲区。
- 返回值:
- 成功:
0 - 失败:
-1,并设置errno
- 成功:
struct stat 主要字段
struct stat {
dev_t st_dev; /* 包含文件的设备 ID */
ino_t st_ino; /* inode 编号 */
mode_t st_mode; /* 文件类型和权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 所有者 UID */
gid_t st_gid; /* 所属组 GID */
off_t st_size; /* 文件大小(字节) */
blkcnt_t st_blocks; /* 分配的 512 字节块数 */
time_t st_atime; /* 最后访问时间 */
time_t st_mtime; /* 最后修改时间 */
time_t st_ctime; /* 最后状态变更时间(如权限修改) */
}
文件类型宏定义(st_mode & S_IFMT)
| 宏定义 | 含义 | 对应字符 |
|---|---|---|
S_IFSOCK |
套接字 | s |
S_IFLNK |
符号链接 | l |
S_IFREG |
普通文件 | - |
S_IFBLK |
块设备 | b |
S_IFDIR |
目录 | d |
S_IFCHR |
字符设备 | c |
S_IFIFO |
FIFO(命名管道) | p |
权限宏定义(掩码)
| 类别 | 宏定义 | 说明 |
|---|---|---|
| 用户 | S_IRUSR |
读权限 |
S_IWUSR |
写权限 | |
S_IXUSR |
执行权限 | |
| 组 | S_IRGRP |
组读权限 |
S_IWGRP |
组写权限 | |
S_IXGRP |
组执行权限 | |
| 其他 | S_IROTH |
其他人读权限 |
S_IWOTH |
其他人写权限 | |
S_IXOTH |
其他人执行权限 |
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.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!\n");
return -1;
}
// 文件类型名称与符号映射
char type_name[7][20] = {"socket", "symbolic", "regular", "block", "directory", "character", "FIFO"};
char type[7] = {'s', 'l', '-', 'b', 'd', 'c', 'p'};
int i = 0;
// 判断文件类型
switch (st.st_mode & S_IFMT)
{
case S_IFSOCK: i = 0; break;
case S_IFLNK: i = 1; break;
case S_IFREG: i = 2; break;
case S_IFBLK: i = 3; break;
case S_IFDIR: i = 4; break;
case S_IFCHR: i = 5; break;
case S_IFIFO: i = 6; break;
}
// 输出基本信息
printf("File: %s\n", argv[1]);
printf("Size: %ld\tBlocks: %ld\tIO Blocks: %ld\t %s\n",
st.st_size, st.st_blocks, st.st_blksize, type_name[i]);
printf("Device: %ld\t Inode: %ld\t Links: %ld\n",
st.st_dev, st.st_ino, st.st_nlink);
// 输出权限字符串
printf("Access: (%#o/", (st.st_mode & S_IRWXU) | (st.st_mode & S_IRWXG) | (st.st_mode & S_IRWXO));
putchar(type[i]);
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('-');
putchar(')');
printf(" Uid: (%d) Gid: (%d)\n", st.st_uid, st.st_gid);
// 格式化输出时间
struct tm *ptm = localtime(&st.st_atim.tv_sec);
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_mtim.tv_sec);
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_ctim.tv_sec);
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;
}
理想运行结果
$ ./file_info hello.c
File: hello.c
Size: 1234 Blocks: 8 IO Blocks: 4096 regular
Device: 2052 Inode: 123456 Links: 1
Access: (0644/-rw-r--r--) Uid: (1000) Gid: (1000)
Access: 2025-08-14 11:30:12
Modify: 2025-08-14 11:29:45
Change: 2025-08-14 11:29:45
更多推荐



所有评论(0)