我们都知道,一般使用printf的打印都会直接打印在终端,如果想要保存在文件里呢?我想你可能想到的是重定向。例如:

$ program > result.txt

这样printf的输出就存储在result.txt中了。

当然了,如果你既想打印在终端,又想保存在文件,还可以使用tee命令:

program | tee result.txt

注:program为你运行的程序。

不过文本介绍了不是通过命令行的方式,而是通过代码实现。

写文件

你可能会想,那不用printf,直接将打印写入到文件不就可以了?类似于下面这样:

#include<stdio.h>
int main(void)
{
FILE *fp = fopen("log.txt","w+");
if(NULL == fp)
{
printf("open failed
");
return -1;
}
int a = 10;
fprintf(fp,"test content %d
",a);
fclose(fp);
return 0;
}

不过这需要将原先的printf改用fprintf,修改了最原始的代码。但是本文并不是说明如何实现一个logging功能,而是如何将printf的原始打印保存在文件中。

重定向

实际上,我们的程序在运行起来后,都会有三个文件描述符:

0 标准输入

1 标准输出

2 标准错误

一般标准输出都是都直接输出到终端。

我们可以用一个程序简单观察一下:

#include<stdio.h>
#include <unistd.h>
int main(void)
{
sleep(20);//为了避免立即退出
return 0;
}

假设编译出来的程序为test:

$ gcc -o test test.c
$ ./test &
$ ls -l /proc/`pidof test`/fd

pidof test用于获取test进程id,其fd目录可以看到打开的文件描述符, 其输出结果如下:

lrwx------ 1 root root 64 Nov 16 16:26 0 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 16 16:26 1 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 16 16:26 2 -> /dev/pts/0

看到了吗,0,1,2都重定向到了/dev/pts/0,其实就是当前终端。

$ tty
/dev/pts/0

所以如果我们要将printf的打印保存到文件中,实际上就让它重定向到这个文件就可以了。这里我们用到freopen函数:

FILE *freopen(const char *path, const char *mode, FILE *stream);

参数说明:

  • path:需要重定向到的文件名或文件路径。
  • mode:代表文件访问权限的字符串。例如,"r"表示“只读访问”、"w"表示“只写访问”、"a"表示“追加写入”。
  • stream:需要被重定向的文件流。

    那么要完成前面的任务也就很简单了:
#include<stdio.h>
#include <unistd.h>
int main(void)
{
FILE *fp = freopen("test.log","w",stdout);
if(NULL == fp)
{
printf("reopen failed
");
return -1;
}
printf("bianchengzhuji
");
printf("shouwangxiansheng
");
sleep(20);//便于观察
fclose(fp);
return 0;
}

重新编译后运行查看其打开的文件描述符:

lrwx------ 1 root root 64 Nov 16 16:34 0 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 16 16:34 1 -> /data/workspaces/test.log
lrwx------ 1 root root 64 Nov 16 16:34 2 -> /dev/pts/0

看到了吗,它现在重定向到test.log了,并且终端也没有printf的打印。随后我们也在文件test.log中看到了下面的内容:

bianchengzhuji

有人可能会有下面的疑问:

  • 怎么恢复?

    首先来看怎么恢复,实际上恢复的原理是类似的,既然最开始它从定向到了/dev/pts/0,那么我们只需要重定向回去就可以了,但是在不同的终端,它的tty名字可能不同,因此需要使用ttyname函数获取原先stdout的tty名字:
int ttyname_r(int fd, char *buf, size_t buflen);

又可以重新定向到/dev/pts/0了:

#include<stdio.h>
#include <unistd.h>
int main(void)
{
char ttyName[128] = {0};
ttyname_r(1,ttyName,128);//1为标准输出
FILE *fp = freopen("test.log","w+",stdout);
if(NULL == fp)
{
printf("reopen failed
");
return -1;
}
printf("bianchengzhuji
");
printf("shouwangxiansheng
");
sleep(20);
freopen(ttyName,"w+",stdout);
printf("std out to %s
",ttyName);
fclose(fp);
return 0;
}

最终运行会发现两个结果:

  • std out to 打印到终端
  • 打开的文件描述符1被重定向到/dev/pts/0

    还有没有别的保存方法呢?

    除了上面这种方式,还有一种方式是使用dup2:
int dup2(int oldfd, int newfd);

它是用来复制文件描述符的,会使得newfd成为oldfd的副本.所以与上面看到不同的是,标准输出和往fd写入的内容,都会存储在文件test.log中:

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd = open("test.log",O_WRONLY|O_CREAT);
dup2(fd,1);//1代表标准输出
printf("bianchengzhuji
");
printf("shouwangxiansheng
");
sleep(20);
close(fd);
return 0;
}

观察的结果:

lrwx------ 1 root root 64 Nov 17 17:03 0 -> /dev/pts/0
l-wx------ 1 root root 64 Nov 17 17:03 1 -> /data/workspaces/test.log
lrwx------ 1 root root 64 Nov 17 17:03 2 -> /dev/pts/0
l-wx------ 1 root root 64 Nov 17 17:03 3 -> /data/workspaces/test.log

这种情况适合于将标准输出的内容和其他写文件的内容一并保存到文件中。

如何关闭printf打印

实际上非常简单,进程启动后,只需要关闭文件描述符1(标准输出),2(标准错误)即可。什么情况下会需要呢?有些后台进程有自己的日志记录方式,而不想让printf的信息打印在终端,因此可能会关闭。

总结

文本旨在通过将printf的打印保存在文件中来介绍重定向,以及0,1,2文件描述符。如果你不想保留标准输出,可以将其重定向到/dev/null,如果想保留,且单独保留到特定文件,可以使用freopen,如果想保留,且和其他内容保留到同一文件,使用dup2。

如何优雅地将printf的打印保存在文件中?的更多相关文章

  1. 将数组打印到txt文件中

    用print_r 将数组打印到txt文件中.     1.function save_log($content='', $file='app') { $logDir = './logs'; $now ...

  2. 利用POI抽取word中的图片并保存在文件中

    利用POI抽取word中的图片并保存在文件中 poi.apache.org/hwpf/quick-guide.html 1.抽取word doc中的图片 package parse; import j ...

  3. C0302 将一个代码块中的内容保存在文件中, 查看一个rpm包是否可以安装

    #!/bin/bash # 这个脚本是用来描述和确认是否可以安装一个rpm包 # 在一个文件中保存输出 SUCCESS=0 E_NOARGS=65 if [ -z "$1" ] t ...

  4. 使用 python 把一个文件生成 C 语言中的数组并保存到头文件中

    (一)要做什么 之前有这么一个需求,是要把一个二进制文件里面的数据,转换成 C 代码里面的数组,可以看之前的一篇文章: NUC980 运行 RT-Thread 驱动 SPI 接口 OLED 播放 ba ...

  5. Python脚本连接数据库读取特定字段保存在文件中

    从Script表中取出Description字段作为文件名,并按协议将脚本归位相同的文件夹,取TestScript字段的内容写入文件 import MySQLdb import sys import ...

  6. RestAssured打印日志到文件中的方法

    参考https://stackoverflow.com/questions/14476112/how-to-get-rest-assured-log-into-something-printable- ...

  7. ios 将Log日志重定向输出到文件中保存

    对于真机,日志没法保存,不好分析问题.所以有必要将日志保存到应用的Docunment目录下,并设置成共享文件,这样才能取出分析. 首先是日志输出,分为c的printf和标准的NSLog输出,print ...

  8. 把打印的内容保存成文件(PDF)

    有时候网页的内容和打印的内容会有一些差异,需要把打印的内容倒出来.是有办法的. 1.以谷歌为内核的浏览器示例,按Ctrl+p快捷键打开打印对话框,如图: 2.点击更改按钮,更改打印机,会出现选择目标打 ...

  9. Android日志打印类LogUtils,能够定位到类名,方法名以及出现错误的行数并保存日志文件

    Android日志打印类LogUtils,能够定位到类名,方法名以及出现错误的行数并保存日志文件 在开发中,我们常常用打印log的方式来调试我们的应用.在Java中我们常常使用方法System.out ...

随机推荐

  1. Noip模拟58 2021.9.21(中秋祭&&换机房祭)

    第一次在学校过中秋节,给家里人视频电话,感觉快回家了很开心, 然后还吃了汉堡喝饮料非常爽,颓废了一会儿还换了新机房,$Linux2.0$非常dei,少爷机也非常快, 发现好像测评机又成了老爷机,这就是 ...

  2. 不可错过的stm32单片机直流电机驱动与测速详解

    stm32直流电机驱动与测速 说实话就现在的市场应用中stm32已经占到了绝对住到的地位,51已经成为过去式,32的功能更加强大,虽然相应的难度有所增加,但是依然阻止不了大家学习32的脚步,不说大话了 ...

  3. SpringCloud微服务实战——搭建企业级开发框架(十):使用Nacos分布式配置中心

    随着业务的发展.微服务架构的升级,服务的数量.程序的配置日益增多(各种微服务.各种服务器地址.各种参数),传统的配置文件方式和数据库的方式已无法满足开发人员对配置管理的要求: 安全性:配置跟随源代码保 ...

  4. DeWeb 与 Unigui的区别

    DeWeb 与 Unigui 相同: 都是采用Delphi开发网页的平台 不同: 1 DeWeb不需要安装控件, 而Unigui需要安装自己的控件 2 DeWeb无需要学习HTML/CSS/JavaS ...

  5. openssh 7.4 升级 8.3

    1.删除旧版本(如果是远程连接升级,不能卸载旧版本,否则连接会断开.安装8.3完也不能卸载7.4,否则要到服务器直连重新安装8.3.) # rpm -qa |grep openssh #rpm -e ...

  6. Java Logback简易教程

    本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可. 一.前言 本文以一个简单的项目为例,一步步展示logback的同步和异步配置方法,并且配置的日志要求满足阿里巴巴Java开发手册- ...

  7. 快速排序平均时间复杂度O(nlogn)的推导

    快速排序作为随机算法的一种,不能通过常规方法来计算时间复杂度 wiki上有三种快排平均时间复杂度的分析,本文记录了一种推导方法. 先放快速排序的伪代码,便于回顾.参考 quicksort(int L, ...

  8. Part 18 $http service in AngularJS

    In Angular there are several built in services. $http service is one of them. In this video, we will ...

  9. c++学习笔记(七)

    位运算和sizeof运算符 位运算 C语言中提供了一些运算符可以直接操作整数的位,称为位运算,因此位运算中的操作数都必须是整型的. 位运算的效率是比较高的,而且位运算运用好的话会达到意想不到的效果. ...

  10. vue+node+mongondb实战之路由

    看了一段时间vue的文档,一直没有机会来开发一个真正的vue项目,趁着这几天清闲,整合一下最新的技术,变学变练来开发一个vue的简单博客 有了开发博客的想法之后,谁知道第一步就被拦住了,看了vue的基 ...