Linux 系统编程:构建基础文件操作库与理解标准错误流 stderr
Linux 系统编程中封装基础文件操作库涉及定义文件结构、打开、刷新缓冲区、写入及关闭文件。通过自定义 myFILE 结构体模拟标准 IO 行为,实现行缓冲或全缓冲策略。标准错误流 stderr 用于输出错误信息,与标准输出流 stdout 分离,便于重定向调试。C 语言使用 fprintf/perror,C++ 使用 cerr/stderr,可通过 Shell 重定向符号将不同流输出至不同文件或合并。

Linux 系统编程中封装基础文件操作库涉及定义文件结构、打开、刷新缓冲区、写入及关闭文件。通过自定义 myFILE 结构体模拟标准 IO 行为,实现行缓冲或全缓冲策略。标准错误流 stderr 用于输出错误信息,与标准输出流 stdout 分离,便于重定向调试。C 语言使用 fprintf/perror,C++ 使用 cerr/stderr,可通过 Shell 重定向符号将不同流输出至不同文件或合并。

#define LINE_SIZE 1024
#define FLUSH_NOW 1 // 立即刷新
#define FLUSH_LINE 2 // 行刷新
#define FLUSH_FULL 4 // 全缓冲
typedef struct _myFILE {
unsigned int flags; // 文件刷新方式
int fileno; // fd
char cache[LINE_SIZE]; // 缓冲区
int cap; // 容量
int pos; // 下次写入的位置
} myFILE;
// C 语言创建结构体变量需要加 struct 关键字,因此使用 typedef 重命名
打开文件本质是开辟一块存放文件数据的空间。
myFILE* my_fopen(const char* path, const char* flag) {
int flag1 = 0; // 系统调用的文件打开方式
int iscreate = 0; // 文件是否被创建
mode_t mode = 0666; // 默认权限设置
if (strcmp(flag, "r") == 0) {
flag1 = O_RDONLY;
} else if (strcmp(flag, "w") == 0) {
flag1 = (O_WRONLY | O_CREAT | O_TRUNC);
iscreate = 1;
} else if (strcmp(flag, "a") == 0) {
flag1 = (O_WRONLY | O_CREAT | O_APPEND);
iscreate = 1;
}
int fd = 0;
if (iscreate)
fd = open(path, flag1, mode); // 创建新文件权限限制才有效,传三个参数
else
fd = open(path, flag1); // 打开已经存在的文件不会修改文件权限,使用两个参数即可
if (fd < 0) return NULL; // 打开文件失败返回 NULL
// 堆区开辟的空间出了函数不会销毁
myFILE* fp = (myFILE*)malloc(sizeof(myFILE));
if (fp == NULL) return NULL;
fp->fileno = fd;
fp->flags = FLUSH_LINE; // 设置行刷新
fp->cap = LINE_SIZE;
fp->pos = 0;
return fp;
}
刷新缓冲区实质是将缓冲区内容写入需要刷新的文件中。
void my_fflush(myFILE* fp) {
// 将缓冲区 pos 个字节的内容写入 fp 的文件中,并将 pos 置 0
write(fp->fileno, fp->cache, fp->pos);
fp->pos = 0;
}
写文件的本质是将内容拷贝到缓冲区中,条件允许就刷新缓冲区。
ssize_t my_fwrite(myFILE* fp, const char* data, int len) {
// 写入的本质是拷贝,条件允许就刷新
memcpy(fp->cache + fp->pos, data, len); // 需要考虑扩容与越界问题,此处不做处理,从简
fp->pos += len;
// 刷新方式为行刷新且缓冲区遇到\n就刷新缓冲区
if ((fp->flags & FLUSH_LINE) && fp->cache[fp->pos - 1] == '\n') {
my_fflush(fp);
}
return len;
}
先刷新缓冲区,在关闭文件并释放空间。
void my_fclose(myFILE* fp) {
my_fflush(fp); // 刷新缓冲区
close(fp->fileno); // 关闭文件
free(fp); // 释放空间
}
mystdio.h
#pragma once
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define LINE_SIZE 1024
#define FLUSH_NOW 1
#define FLUSH_LINE 2
#define FLUSH_FULL 4
typedef struct _myFILE {
unsigned int flags;
int fileno;
char cache[LINE_SIZE];
int cap;
int pos;
} myFILE;
myFILE* my_fopen(const char* path, const char* flag);
void my_fflush(myFILE* fp);
ssize_t my_fwrite(myFILE* fp, const char* data, int len);
void my_fclose(myFILE* fp);
mystdio.c
#include "mystdio.h"
myFILE* my_fopen(const char* path, const char* flag) {
int flag1 = 0;
int iscreate = 0;
mode_t mode = 0666;
if (strcmp(flag, "r") == 0) {
flag1 = O_RDONLY;
} else if (strcmp(flag, "w") == 0) {
flag1 = (O_WRONLY | O_CREAT | O_TRUNC);
iscreate = 1;
} else if (strcmp(flag, "a") == 0) {
flag1 = (O_WRONLY | O_CREAT | O_APPEND);
iscreate = 1;
}
int fd = 0;
if (iscreate)
fd = open(path, flag1, mode);
else
fd = open(path, flag1);
if (fd < 0) return NULL;
myFILE* fp = (myFILE*)malloc(sizeof(myFILE));
if (fp == NULL) return NULL;
fp->fileno = fd;
fp->flags = FLUSH_LINE;
fp->cap = LINE_SIZE;
fp->pos = 0;
return fp;
}
void my_fflush(myFILE* fp) {
write(fp->fileno, fp->cache, fp->pos);
fp->pos = ;
}
{
(fp->cache + fp->pos, data, len);
fp->pos += len;
((fp->flags & FLUSH_LINE) && fp->cache[fp->pos - ] == ) {
my_fflush(fp);
}
len;
}
{
my_fflush(fp);
close(fp->fileno);
(fp);
}
testfile.c
#include "mystdio.h"
#include <stdio.h>
#include <unistd.h>
#define FILENAME "log.txt"
int main() {
// 使用自己封装的函数以写方式打开文件
myFILE* fp = my_fopen(FILENAME, "w");
if (fp == NULL) return 1;
const char* str = "hello linux";
int cnt = 10;
char buff[128];
while (cnt) {
// 将格式化数据转成字符串到 buff 中
sprintf(buff, "%s - %d", str, cnt);
// 将 buff 写入文件
my_fwrite(fp, buff, strlen(buff));
cnt--;
sleep(1);
my_fflush(fp); // 写完一组数据就刷新缓冲区
}
my_fclose(fp); // 关闭文件
return 0;
}
运行结果

#include <stdio.h>
int main() {
perror("error:");
fprintf(stdout, "hello fprintf stdout\n");
fprintf(stderr, "hello fprintf stderr\n");
return 0;
}
看现象
我们可以看到一部分数据存入了文件中,但是一部分数据没有存入文件中。

实质是标准输出重定向,修改 1 号 fd 里面的内容,其余的是 2 号 fd 里面的内容,因此直接打印到显示器上。
为什么有了标准输出流还要有标准错误流呢???
因为我们在编写程序的时候不能保证一直都是正确的代码,当我们查错误的时候就可以通过标准错误流查询。标准输出流主要用于程序的正常输出信息,标准错误流则用于输出程序的错误信息,两者共同确保了程序的运行状态可以被正确地监控和理解。
如果想让 2 和 1 都重定向到文件中怎么做?
1、将数据存入不同的文件
将 1 号的内容重定向到 ok.txt 文件,将 2 号的内容存入 err.txt 文件。
命令行代码
[jkl@host file3]$ ./a.out error:: Success hello fprintf stdout hello fprintf stderr
[jkl@host file3]$ ./a.out 1>ok.txt 2>err.txt
[jkl@host file3]$ cat ok.txt
hello fprintf stdout
[jkl@host file3]$ cat err.txt
error:: Success
hello fprintf stderr
运行结果

2、将数据存入同一个文件
命令行代码
[jkl@host file3]$ ./a.out 1>all.txt 2>&1
[jkl@host file3]$ cat all.txt
error:: Success
hello fprintf stderr
hello fprintf stdout
运行结果

总结
#include <iostream>
int main() {
std::cout << "hello cout" << std::endl;
std::cerr << "hello cerr" << std::endl;
return 0;
}
命令行代码
[jkl@host file3]$ g++ test_stderr.cpp
[jkl@host file3]$ ./a.out
hello cout
hello cerr
[jkl@host file3]$ ./a.out > log.txt
hello cerr
[jkl@host file3]$ cat log.txt
hello cout
运行结果


微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online