1. Introduction
As a terminal multiplexer, the tmux utility can often become the go-to environment when working within a terminal. Similar to shells, terminal multiplexers provide a specialized context, functions, features, and utilities for easier command line interface (CLI) operation. One potentially useful feature of tmux in particular is its logging and data manipulation facilities.
In this tutorial, we explore how to log all input, output, or both in a tmux session. First, we briefly refresh our knowledge about the structure of a session in tmux. Next, we continue with tmux session data extraction. After that, we use a special mode to get contents from a pane. Then, we examine tmux buffers in-depth and see a way to apply them when capturing a whole pane. Finally, we set up an automatic way to log all tmux sessions.
Notably, most commands that start with tmux can be input within a session after pressing the bind key (usually Ctrl+B), followed by a : colon.
We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15 and tmux 3.3a. Unless otherwise specified, it should work in most POSIX-compliant environments.
2. tmux Session Structure
A tmux session consists of at least one window. Each window contains at least a single pane.
Panes are what we usually work with when interacting with tmux. In particular, each pane starts with a shell by default, so we can run different commands. Further, we can move between panes even while a command is running.
When using a terminal emulator, we can often select contents only from the currently visible part of the window and its panes within a tmux session.
However, data scrolls upward and goes out of view. When using a terminal multiplexer, scrolling is rarely integrated with the scroll of the terminal emulator.
Thus, we might require an extraction method that accounts for that and data logging in general.
3. Extract Data From a tmux Session
Sometimes, we don’t even need tmux to generate a certain piece of data.
3.1. Detach From tmux
Ironically, a terminal multiplexer might become a hindrance in some situations. Whether it’s the lack of scroll integration with the terminal, character interpretation, or another limitation, we still have an option in such cases.
To get the output of a certain command or script that we can run outside a tmux session, a detach (Ctrl+B, D) often suffices:
$ tmux
[...]
[Ctrl+B, D]
$ runcommand
[...]
Since this way, we can use the native terminal emulator scroll, the output of runcommand might be easier to copy and save elsewhere.
Once done, we just list-sessions and attach-session to the one we want:
$ tmux list-sessions
0: 1 windows (created Sun Apr 7 07:04:00 2024)
$ tmux attach-session -t 0
Although this approach doesn’t employ tmux directly, it does leverage the versatility of the session preservation feature.
3.2. File Redirection
Regardless of the environment, streams and redirection are a core part of Linux. Essentially, if we need data at a certain place, we can use streams to navigate it to where it needs to go.
For example, let’s use basic file redirection:
$ date > now.txt
After running this command, we have the output of date, i.e., the current date and time, stored within the now.txt file. This way, we intercept the output that we would otherwise see on the screen (standard output, stdout) and > redirect it to a file.
By doing so, we don’t need to manipulate the screen or copy and paste from the terminal.
Of course, both of these solutions don’t lend themselves to all situations, especially when it comes to output that’s already on the screen and isn’t reproducible.
4. Get tmux Pane Data in Copy Mode
As a full-fledged environment, tmux offers its own mechanics for copying from a given pane. In particular, the so-called copy mode is a special state of the tmux session, in which we can perform several actions with the contents of a given pane:
- scroll through similar to a pager like less
- search for text
- select text
- copy selected text to the clipboard
Notably, unlike selecting via the mouse within a terminal emulator, copy mode considers pane bounds and only works with the current pane data.
To extract data from a pane in copy mode, we follow several steps:
- Ctrl+B, [: enter copy mode
- arrow and navigation keys: scroll around to reach one end of the selection
- Space or Ctrl+Space: start selecting
- Enter or Ctrl+W: copy selection
At the last step, tmux automatically exits copy mode and stores the contents of the selection within the clipboard. Notably, the copied data is usually accessible from both the GUI, if any, and within the terminal or terminal emulator.
Further, we can use the integrated mouse support of tmux to use the mouse for entering copy mode and selecting:
setw -g mouse on
After enabling the mouse [-g]lobally, we just click at one end of a desired selection and drag the mouse to its other end.
Either way, we can paste the result back into the terminal, assign it to a variable, use it within a text editor, or just leave it stored in the clipboard.
5. tmux Buffers
When talking about capturing data, tmux buffers are one of the best mechanisms to do so.
Just like when using vi, a buffer in tmux is a named content storage. Essentially, buffers are like separate streams of data that we can append to and generally manipulate.
5.1. Create Buffer
To begin with, let’s create a buffer:
$ tmux set-buffer -b buf1 'Buffer data.'
Here, *we use the set-buffer (setb) subcommand to create the buf1 [-b]uffer and initialize it* with the Buffer data. string.
If we omit the buffer name, tmux uses buffer# with consecutive integers starting from 0 in place of the # octothorp. On the other hand, we can rename a buffer with the -n option of the set-buffer subcommand.
5.2. Show and Paste Buffer
To verify the contents of a [-b]uffer by name, we use show-buffer (showb):
$ tmux show-buffer -b buf1
Buffer data.
This can be especially useful when using command substitution.
Once we see the data, we might want to leverage paste-buffer (pasteb) to insert it within a certain pane:
$ tmux paste-buffer -b buf1
Buffer data. Appended
The difference between the two subcommands is subtle, but important as paste-buffer only inserts the text as if it were typed.
5.3. List and Choose Buffer
To see a list of all buffers, we use list-buffers (lsb):
$ tmux list-buffers
buf1: 22 bytes: "Buffer data. Appended."
To change the output format, we can use the -F option.
The choose-buffer (chooseb) subcommand also shows a list of buffers but also enables the user to get the data from one of them interactively:
$ tmux choose-buffer
[...]
(0) buf1: 11:34: Buffer data. Appended.
┌ buf1 (sort: time) ───────────────────┐
│ Buffer data. Appended. │
│ │
│ │
│ │
└──────────────────────────────────────┘
In the interactive screen, we move via the arrow keys and select a buffer with Return. Moreover, there are several useful operations:
- Ctrl+S: search by name or content
- t, T, Ctrl+T, P: toggle tag, remove tag, tag all buffers, or paste tagged buffers
- e: open buffer in editor
- f: filter items by format
- O, r: change sort field or reverse sort order
In fact, the choose-buffer subcommand accepts the -F option, which can specify a format for the output.
5.4. Append to Buffer
Of course, if a buffer already exists, we can also append to it by again using the set-buffer (setb) subcommand, this time with -a:
$ tmux set-buffer -b buf1 -a ' Appended.'
At this point, buf1 has its previous contents and Appended. following them.
5.5. Save and Load Buffer
The save-buffer (saveb) and load-buffer (loadb) subcommands dump buffer data to and get buffer data from a path.
First, let’s save a buffer:
$ tmux save-buffer -b buf1 /home/baeldung/buf1
$ cat /home/baeldung/buf1
Buffer data. Appended.
Now, we can load the file back into another buffer:
$ tmux load-buffer -b buf2
$ tmux show-buffer -b buf2
Buffer data. Appended.
Notably, like other commands, load-buffer creates the buffer if it doesn’t exist.
Critically, since buffers attach and relate to a given server, they are global and don’t go away with sessions.
6. Manual tmux Pane Save With capture-pane
Sometimes, we don’t just need part of the tmux pane contents. Instead, the data from the whole session, both input (stdin) and output (stdout), might be of interest.
In such cases, we employ another subcommand that tmux offers: capture-pane (capturep). The capture-pane subcommand can grab, process, and save the contents of a pane.
6.1. Standard Output
In one of its most basic forms, capture-pane dumps a whole pane to stdout:
$ tmux capture-pane -p -t 1
Here, *we output the contents of [-t]arget pane 1 to the current standard out[-p]ut*. This way, we can get the data from another pane into the current one. Of course, if we don’t indicate a target, we’d copy and paste the text from and to the current pane.
6.2. Buffer Output
Further, capture-pane can dump the contents of a pane into a buffer:
$ tmux capture-pane -b pane1 -t 1
This command places the contents of the [-t]arget pane 1 into [-b]uffer pane1.
By doing so, we can then use save-buffer and dump those contents into a file or even edit them in place through choose-buffer.
6.3. Data Augmentation
By default, capture-pane only gets the visible contents of the pane. However, *we can set the [-S]tart and [-E]nd lines for the capture*:
- line 0 is the first visible line
- negative line numbers are in the hidden history
- -S followed by – means from the beginning of the known history
- -E followed by – means the last of the visible lines
Normally, the capture mechanism doesn’t include escape sequences. This means that any highlighted text or control sequences get ignored. To preserve all special codes, we can add the -e flag. Further, -P can handle incomplete sequences.
Since this can cause issues, we can also add the -C flag, which escapes non-printable characters via their \xxx octal notation.
6.4. Demonstration
First, we open two panes and run a command within the first one (pane 0, left):
baeldung@xost:~$ echo Pane 0 contents. │root@xost:~$
Pane 0 contents. │
baeldung@xost:~$ │
│
│
│
[10] 0:bash* "baeldung@xost: ~" 02:22 10-Apr-24
Next, we go to the second pane (pane 1, right) and perform a capture of the first line (0):
baeldung@xost:~$ echo Pane 0 contents. │root@xost:~# tmux capture-pane -t 0 -p -S 0 -E 0
Pane 0 contents. │baeldung@xost:~$ echo Pane 0 contents.
baeldung@xost:~$ │root@xost:~#
│
│
│
[10] 0:bash* "baeldung@xost: ~" 02:22 10-Apr-24
Thus, we get the relevant line from the left pane in stdout of the right pane.
7. Automatically Log All tmux Panes
In rare cases, we might want to keep track of commands and their output for all tmux sessions.
To achieve this, we can theoretically use tmux hooks. However, they might not always work reliably for all panes. Thus, we leverage the shell facilities.
7.1. Shell tmux Logging Script
In particular, we create a tmux logging script:
$ cat tmux-global-logging.sh
#!/usr/env bash
if [ -n "$TMUX_PANE" ] && [ "$TMUX_PANE_LOGGING" != "1" ]; then
export TMUX_PANE_LOGGING=1
else
exit 0
fi
LOGS=$HOME/.tmux/logs
mkdir --parents $LOGS
LOG_PATH="$LOGS/$(date +%Y%m%d%H%M%S).pane${TMUX_PANE//[^0-9]/}.log"
tmux pipe-pane -t "${TMUX_PANE}" "exec cat - >> $LOG_PATH"
The script first verifies conditions around two variables:
- $TMUX_PANE is defined, meaning the shell is within a tmux session
- $TMUX_PANE_LOGGING isn’t equal to 1, meaning we haven’t started logging yet
Of course, if either isn’t true, we just exit. Otherwise, we set $TMUX_PANE_LOGGING to 1 as a marker.
After that, we create the directory for the $LOGS and set the $LOG_PATH to the file path of the specific log file. Notably, the latter includes the pane number and date to avoid conflicts.
At this point, we run the tmux subcommand pipe-pane, which copies and redirects all output, including any commands we type, through the supplied command. In this case, that’s an exec and cat that goes to the $LOG_FILE.
7.2. Automatically Start Logging
Once we have the logging script, we make it executable:
$ chmod +x tmux-global-logging.sh
After that, we set it up to run on each shell spawn. To do so, it’s usually a good idea to append it to the .bashrc file within the home directory of the user we want to enable logging for:
$ cat $HOME/.bashrc
[...]
$HOME/.tmux/tmux-global-logging.sh
In this case, we place the script itself within the directory that also holds the logs.
These actions should be enough for new tmux panes with the Bash shell to begin logging to the relevant file.
7.3. Customization
Much of the output from tmux is formatted with special control characters and sequences:
$ cat -A 20240407000656.pane0.log
^[[?2004h^[]0;baeldung@xost: ~/.tmux^G^[[01;33mbaeldung@xost^[[00m:^[[01;34m~/.tmux^[[00m# echo Content.^M$
^[[?2004l^MContent.^M$
^[[?2004h^[]0;baeldung@xost: ~/.tmux^G^[[01;33mbaeldung@xost^[[00m:^[[01;34m~/.tmux^[[00m# exit^M$
^[[?2004l^Mlogout^M$
They position elements and ensure the correct visualization. In addition, backspaces are also kept, so deleted data may also be part of the output.
Because of this, we might have trouble reading the log in environments that don’t interpret these characters and sequences.
So, depending on the requirements, we might also want to install ansifilter:
$ apt install ansifilter
In particular, ansifilter can strip a considerable amount of special and non-printable combinations within the data.
Now, we slightly change the tmux-global-logging.sh script:
$ cat tmux-global-logging.sh
#!/usr/env bash
if [ -n "$TMUX_PANE" ] && [ "$TMUX_PANE_LOGGING" != "1" ]; then
export TMUX_PANE_LOGGING=1
else
exit 0
fi
LOGS=$HOME/.tmux/logs
mkdir --parents $LOGS
LOG_PATH="$LOGS/$(date +%Y%m%d%H%M%S).pane${TMUX_PANE//[^0-9]/}.log"
tmux pipe-pane -t "${TMUX_PANE}" "exec cat - | ansifilter >> $LOG_PATH"
Now, the logging should look much cleaner:
$ cat -A 20240407000657.pane0.log
0;baeldung@xost: ~/.tmux$
baeldung@xost:~/.tmux# echo Content.$
Content.$
0;baeldung@xost: ~/.tmux$
baeldung@xost:~/.tmux# exit$
logout$
Of course, we can apply more processing and even manual corrections via stream editors like sed.
8. Summary
In this article, we talked about ways to extract data from tmux panes manually or automatically.
In conclusion, there are many methods to get and save data from a tmux session, so the choice usually comes down to requirements and preference.