1. Introduction
When working in Linux, it’s very common to use redirections. For example, we can run a program and silence the output that it generates when we execute it. However, bash doesn’t provide any direct method to redirect the output once a process is running. So, in this article, we’ll learn different ways to redirect or copy the output of a running process. This can be useful, for example, if we forget to redirect the output when executing the program.
To do this, we’ll look at three different methods — the first using gdb, another using strace, and finally, a third that uses screen. The third approach will be a bit different.
2. Using gdb to Redirect the Output
gdb is a powerful tool for code debugging in Linux, and we can use it to execute code on a running process. In this case, *we’ll use it to redirect the output by attaching gdbto the running process, redirecting the desired output, and finally detaching from the running process when we’re done*.
The way we can redirect the output is by closing the current file descriptor and then reopening it, pointing to the new output. We’ll do this using the open and dup2 functions.
There are two default outputs in Unix systems, stdout and stderr. stdout is associated with file descriptor 1 and stderr to 2. So, for example, if we want to redirect the standard output, we need to act on file descriptor 1.
Let’s suppose we want to redirect the output of a process that has the PID 14560. So, we should start by attaching gdb to that PID:
$ gdb -p 14560
Once we’re attached to the process, we can redirect the file descriptor 1 to /tmp/process_stdout:
(gdb) p dup2(open("/tmp/process_stdout", 1089, 0777), 1)
Note that we used the number 1089 because this represents O_WRONY | O_CREAT | O_APPEND.
We can also redirect standard error in exactly the same way just by using 2 as the file descriptor:
(gdb) p dup2(open("/tmp/process_stderr", 1089, 0777), 2)
At this point, output has been redirected. However, the process is stopped and gdb is attached to it.
As we’ll no longer need gdb, we can detach from the process and quit gdb. This will also make the process continue its execution:
(gdb) q
Alternatively, we could have silenced the output by redirecting to /dev/null:
(gdb) p dup2(open("/dev/null"), 1)
We can also write all gdb commands in a “command file” and use the parameter -x to execute it.
Let’s create a file called gdb_redirect:
p dup2(open("/tmp/process_stdout", 1089, 0777), 1)
p dup2(open("/tmp/process_stderr", 1089, 0777), 2)
q
Then, we just need to run gdb using our file gdb_redirect:
$ gdb -p 14560 -x gdb_redirect
3. Using strace to Inspect All Write Calls
We can also use strace to inspect system calls. With this method, we won’t interrupt the original output. Instead, we’ll be copying it to some other location.
Let’s inspect all writes to stderr of PID 14560:
$ 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
With those parameters, strace attaches to PID 14560, traces write calls and sets the maximum string size to 100,000. Then, we use grep to print only writes to file descriptor 2.
We can redirect these to a file called /tmp/process_stderr:
$ strace -etrace=write -s 100000 -p 14560 2>&1 | grep --line-buffered '^write(2,' > /tmp/process_stderr
If we don’t like strace‘s output format, we can use sed to print only the string:
$ 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
Also, strace can print the hexadecimal and ASCII output being written to any file descriptor. We do this using the -ewrite=fd option:
$ 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. Using screen to Write the Output to a File
If the process is running inside a screen session, we can log the current window to a file. As with strace, we won’t replace the output — we’ll copy it instead. However, we can’t copy any arbitrary file descriptor. We can copy only what is being written to the screen window.
To do this, we use screen command log, or its hotkey C-a H. This will enable logging to a file called screenlog.n, with n being the screen window number. After that, we can detach from the screen and let the process run, and we’ll see the output in screenlog.n. We can stop logging in at any time by again using the log command or its hotkey.
Also, we can use the command logfile to specify where to write the output:
:logfile process_output
Then, when we run log, the output will be in process_output instead of screenlog.n:
5. Conclusion
In this article, we saw three ways of redirecting the output of an already running process.
First, we used gdb to replace the current output with a new one. Then, we intercepted all calls to write with strace and copied the desired output to another place. And finally, we took advantage of screen logging the window to a file.