Contents

重定向已运行进程的输出

1. 简介

在 Linux 中工作时,使用重定向 非常常见。例如,我们可以运行一个程序并在我们执行它时将其生成的输出静音。 但是,bash不提供任何直接方法来在进程运行后重定向输出。因此,在本文中,我们将学习重定向或复制正在运行进程的输出的不同方法。这可能很有用,例如,如果我们在执行程序时忘记重定向输出。

为此,我们将研究三种不同的方法——第一种使用gdb ,另一种使用strace ,最后,第三种使用*screen *。第三种方法会有点不同。

2. 使用gdb重定向输出

gdb是 Linux 中一个强大的代码调试工具,我们可以使用它在正在运行的进程上执行代码。在这种情况下,我们将使用它来重定向输出,方法是将gdb附加到正在运行的进程,重定向所需的输出,最后在完成后从正在运行的进程中分离出来

我们可以重定向输出的方法是关闭当前文件描述符,然后重新打开它,指向新的输出。我们将使用*open *和dup2 函数来执行此操作。

Unix 系统中有两个默认输出,stdoutstderrstdout与文件描述符1stderr关联到2。因此,例如,如果我们想要重定向标准输出,我们需要对文件描述符1进行操作。

**假设我们要重定向 PID 为 14560 的进程的输出。**因此,我们应该首先将gdb附加到该 PID:

$ gdb -p 14560

一旦我们附加到进程,我们可以将文件描述符 1重定向到*/tmp/process_stdout*:

(gdb) p dup2(open("/tmp/process_stdout", 1089, 0777), 1)

请注意,我们使用了数字1089,因为它代表O_WRONY | O_CREAT | O_APPEND

我们也可以通过使用2作为文件描述符以完全相同的方式重定向标准错误:

(gdb) p dup2(open("/tmp/process_stderr", 1089, 0777), 2)

此时,输出已被重定向。但是,该进程已停止并且gdb已附加到该进程。

由于我们不再需要gdb,我们可以从进程中分离并退出gdb。这也将使进程继续执行:

(gdb) q

或者,我们可以通过重定向到*/dev/null*来使输出静音:

(gdb) p dup2(open("/dev/null"), 1)

我们也可以将所有gdb命令写入一个“命令文件”,并使用参数-x*来执行它*。

让我们创建一个名为gdb_redirect的文件:

p dup2(open("/tmp/process_stdout", 1089, 0777), 1)
p dup2(open("/tmp/process_stderr", 1089, 0777), 2)
q

然后,我们只需要使用我们的文件gdb_redirect运行**gdb

$ gdb -p 14560 -x gdb_redirect

3. 使用strace检查所有写调用

我们还可以使用strace来检查系统调用。使用这种方法,我们不会中断原始输出。相反,我们会将其复制到其他位置。

让我们检查对 PID 14560的stderr的所有写入:

$ strace -etrace=write -s 100000 -p 14560 2>&1 | grep --line-buffered '^write(2,'
write(2, "\r", 1)                       = 1
write(2, "17243001856 bytes (17 GB, 16 GiB) copied, 1185 s, 14.6 MB/s", 59) = 59
write(2, "\r", 1)                       = 1
write(2, "17249965568 bytes (17 GB, 16 GiB) copied, 1186 s, 14.5 MB/s", 59) = 59

使用这些参数,strace附加到 PID 14560,跟踪写入调用并将最大字符串大小设置为 100,000。然后,我们使用grep 仅打印对文件描述符2的写入。

我们可以将它们重定向到一个名为*/tmp/process_stderr*的文件:

$ strace -etrace=write -s 100000 -p 14560 2>&1 | grep --line-buffered '^write(2,' > /tmp/process_stderr

如果我们不喜欢strace的输出格式,我们可以使用*sed *只打印字符串:

$ strace -etrace=write -s 100000 -p 14560 2>&1 | sed -n -r 's/^write\(2,\s*"(.+)",\s*[[:digit:]]+\)\s*=\s*[[:digit:]]+$/\1/p'
\r
25754656256 bytes (26 GB, 24 GiB) copied, 1710 s, 15.1 MB/s
\r
25761747456 bytes (26 GB, 24 GiB) copied, 1711 s, 15.1 MB/s

此外,** strace可以打印正在写入任何文件描述符的十六进制和 ASCII 输出**。我们使用*-ewrite=fd*选项执行此操作:

$ strace -ewrite=2 -etrace=write -p 14560 2>&1 | grep  --line-buffered '^ |'
 | 00000  0d                                                .                |
 | 00000  33 33 30 31 32 30 32 37  39 30 34 20 62 79 74 65  33012027904 byte |
 | 00010  73 20 28 33 33 20 47 42  2c 20 33 31 20 47 69 42  s (33 GB, 31 GiB |
 | 00020  29 20 63 6f 70 69 65 64  2c 20 32 31 36 32 20 73  ) copied, 2162 s |
 | 00030  2c 20 31 35 2e 33 20 4d  42 2f 73                 , 15.3 MB/s      |
 | 00000  0d                                                .                |
 | 00000  33 33 30 31 39 36 32 31  38 38 38 20 62 79 74 65  33019621888 byte |
 | 00010  73 20 28 33 33 20 47 42  2c 20 33 31 20 47 69 42  s (33 GB, 31 GiB |
 | 00020  29 20 63 6f 70 69 65 64  2c 20 32 31 36 33 20 73  ) copied, 2163 s |
 | 00030  2c 20 31 35 2e 33 20 4d  42 2f 73                 , 15.3 MB/s      |

4.使用*screen *将输出写入文件

如果进程在*screen *会话中运行,我们可以将当前窗口记录到文件中。与strace一样,我们不会替换输出 - 我们将复制它。但是,我们不能复制任意文件描述符。我们只能复制正在写入*screen *窗口的内容。

**为此,我们使用screen 命令log或其热键C-a H。**这将启用记录到名为screenlog.n的文件,其中nscreen 窗口编号。之后,我们可以脱离屏幕并让进程运行,我们将在screenlog.n中看到输出。我们可以随时再次使用log命令或其热键停止登录。

此外,我们可以使用命令logfile来指定写入输出的位置:

:logfile process_output

然后,当我们运行log时,输出将在process_output而不是screenlog.n中: