我们都知道,一般使用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. rabbitmq死信队列和延时队列的使用

    死信队列&死信交换器:DLX 全称(Dead-Letter-Exchange),称之为死信交换器,当消息变成一个死信之后,如果这个消息所在的队列存在x-dead-letter-exchange ...

  2. 文件挂载swap

    根目录使用率超过79%,根目录总共45G,/home目录下有文件6G的swap,在新加的300G分区/OracleDB中建立4个G的swap替代/home下在swap文件 1.创建4个G的空文件 #  ...

  3. sql注入理解

    一.SQL注入产生的原因和危害 1.原因 SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序.而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原 ...

  4. CSS学习笔记:display属性

    目录 一.display属性概述 1. 块级元素和行内元素的区别 2.常见的块级元素和行内元素 3. display属性常见的属性值 二.测试display取各属性值的效果 1. 测试inline和b ...

  5. Latex使用CJK包添加字体

    最近写论文时有个中文期刊提供的LaTeX模板使用CJK宏包,大致是这样的: \documentclass{article} \usepackage{CJK} \begin{document} \beg ...

  6. 【java+selenium3】特殊元素iframe的定位及详解(三)

    一.iframe 内联框架 1.自己写个网页,仅供理解iframe演示使用,如下 <!DOCTYPE html> <html> <head> <meta ch ...

  7. java框架面试高频问题(SpringMVC)

    1.SpringMVC是什么? 请说出你对它的理解? SpringMVC是Spring将Web层基于MVC封装后的框架. 在没有SpringMVC之前,Web层的Servlet负责的事情很多,很杂.  ...

  8. [第二章]c++学习笔记2(类和对象的基础3)

    隐藏的概念 隐藏的作用 使用例 成员函数的重载与缺省(附使用例) 注意事项

  9. 从零搭建vsftpd

    先吐槽一下这个工具,配置繁琐,限制规则复杂,报错信息不够详细,学起来吃力. 准备工作 [root@vsftp-server ~]# mkdir /data/ #创建ftp目录 [root@vsftp- ...

  10. Trivy 容器镜像扫描工具学习

    简介 官方地址:https://github.com/aquasecurity/trivy Trivy是aqua(专注云原生场景下的安全)公司的一款开源工具,之前历史文章也有对aqua的一些介绍. T ...