我们都知道,一般使用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. 函数指针和qsort函数

    1.函数指针的形式: 函数指针:int (*funcP) (int *a, int *b) 表示定义了一个funcP函数指针,指向了返回值为int类型,参数为int* 和int* 的函数 使用方式: ...

  2. 攻防世界 杂项 10.2017_Dating_in_Singapore

    题目描述: 01081522291516170310172431-050607132027262728-0102030209162330-02091623020310090910172423-0201 ...

  3. 最短路计数(SPFA× Dijkstra√)

    题目描述 给出一个n个顶点m条边的无向无权图,顶点编号为1−n.问从顶点1开始,到其他每个点的最短路有几条. 输入格式 第一行包含2个正整数n,m,为图的顶点数与边数. 接下来M行,每行2个正整数x, ...

  4. Pod 健康检查和服务可用性检查

    Kubernetes 对 Pod 的健康状态可以通过两类探针来检查:LivenessProbe 和 ReadinessProbe,kubelet 定期执行这两类探针来针对容器的健康状况. Livene ...

  5. typedef的用法 单向链表的查找、增加、删除、销毁。

    一:typedef的用法.    写一个数据结构(计算机存储数据的一种方式,是抽象的,可以人为组织,提高算法效率),我们需要注意:接口友好,模块化,规范命名等方面,在接口友好方面,typedef是非常 ...

  6. LoadRunner12浏览器录制(谷歌火狐)

    一.使用谷歌浏览器 下载的版本 65.0.3325.162(正式版本)(64 位)安装之前要记得把电脑现有的谷歌浏览器卸载了. 1.下载地址:https://www.chromedownloads.n ...

  7. Jackson & fastJson的使用

    Jackson import lombok.Data; @Data public class Student { private Long id; private String name; priva ...

  8. IIS设置URL重写,实现页面的跳转的重定向方法

    默认IIS是不提供URL重写模块的. 请注意,不要将IIS默认的HTTP重定向理解为url重写. 安装url重写模块 url重写,是要从iis的应用市场下载url重写组件才可以的. URL重写工具的下 ...

  9. ndarray 数组的创建和变换

    ndarray数组的创建方法 1.从python中的列表,元组等类型创建ndarray数组 x = np.array(list/tuple) x = np.array(list/tuple,dtype ...

  10. [loj574]黄金矿工

    记$dep_{x}$为1到$x$的边权和,当$x$上的矿工挖了$y$上的黄金时($y$在$x$子树内),显然$\sum_{e}c_{e}=dep_{y}-dep_{x}$ 由此,对于$u$上权值为$v ...