如何重定向一个正在执行的程序的标准输出

如何在Linux系统中更改一个正在执行的程序的标准输出重定向到其他文件?事情的场景是这样的,由于同事的疏忽,忘了关闭一个springboot微服务的控制台日志输出,而这个进程启动后又会把标准输出和标准错误输出写到一个process.log的日志文件中,由于控制台信息输出太多,导致长时间日志磁盘占用过大,这时候又来了一个骚操作,直接把这个日志文件删除掉了😓。

直接删除文件这个肯定是没用的,通过rm删除文件将会从文件系统的目录结构上解除链接(unlink),然而如果文件是被打开的(有进程正在使用),那么进程将仍然可以读取该文件,磁盘空间也一直被占用,这样就会导致删除了文件,但是磁盘空间却未被释放,大量的文件句柄无法释放。
执行 lsof|grep [pid]|grep deleted 查看,确实出现了文件句柄泄露😲
lsof
然后查看对应进程的文件文件描述符(fd:file descriptor)
ls -l /proc/[pid]/fd
proc
可以看到文件描述符1和2都指向了被删除的日志文件
那么如何解决这个问题呢?杀掉进程是最简单的方法,但如果不重启呢?从上面fd表上我们看到1和2指向删除文件,那么我们能不能更改这个指向,重定向到/dev/null丢掉输出呢❔
答案是有的,主要依赖于Linux 的 close()、open()、dup2()函数。(open函数也可用creat函数替换)
close函数用于关闭一个已打开的文件,函数原型如下:

1
2
3
int close(int filedes);
返回值:若成功则返回0,出错则返回-1
参数:filedes是文件描述符。

open函数可以打开或创建一个文件,函数原型如下:

1
2
3
4
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
返回值:成功返回新分配的文件描述符,出错返回-1并设置errno
参数:pathname参数是要打开或创建的文件名,flags参数有一系列常数值,具体不在此处介绍

dup2函数可以复制一个文件的描述符,函数原型如下:

1
2
3
int dup2( int oldfd, int targetfd )
返回值:目标的文件描述符
参数:oldfd源描述符,targetfd目标描述符

简单来说,close关闭一个文件描述符,open打开一个文件并返回文件描述符,dup2将目标文件描述符变成源文件描述符的一个复制,即两个文件描述符都指向了源文件描述符指向的文件上去。所以我们可以关闭掉标准输出fd1,然后open /dev/null获得一个新的文件描述符,再将fd1指向/dev/null,这样就完成了重定向一个标准输出。
OK,我们用GDB尝试一下,首先gdb -p [pid]进入gdb调试💻

1
2
3
4
5
(gdb) p close(1)
$1 = 0
(gdb) p dup2(open("/dev/null", 2), 1) //2表示O_RDWR 0x0002
$2 = 1
(gdb) quit

这样就完成标准输出重定向到/dev/null,标准错误输出依此类推
再次查看对应进程的文件描述符列表,1和2都成功指向了/dev/null,并且文件句柄也被释放掉✌
proc1
不仅仅适用于这个场景,这个方法也可以把一个忘记了nohup启动的进程放置到后台运行等等其他方面。