github soundcloud
tmux
May 12, 2019
3 minutes read

Luke Smith has a great video about a script he made with a guy called Jaywalker. It lets you copy the output from commands you have run in the suckless terminal (st) by selecting it in dmenu.

Since I already use Luke’s dropdown tmux terminal 99% of the time (see ddspawn) I started thinking about how to achieve the same thing with tmux=. The benefits being that it would work in any terminal emulator. I also want to use fzf instead of dmenu.

People in the comments had some ideas, like running tee:

| tee >(xclip -selection clipboard)"

It could be a convenient alias. The downside for me is that I don’t really plan ahead that I want to copy a command’s output. And you can’t know in advance if the output is surprising.

urxvt got print-pipe:

URxvt*print-pipe:  cat > $HOME/$(echo urxvt.dump.$(date +'%Y%M%d%H%m%S'))

But I still prefer to use tmux since I can use it in any terminal. I also enjoy it’s API compared to URXvt.

My idea is to use tmux capture-pane. To print the visible contents of the last visited pane:

tmux capture-pane -p

You got the -t parameter to specify a session name or pane ID (echo $TMUX_PANE in that case). -S controls how many lines of the scrollback buffer to get. -S -100 gives you the last 100 lines. If you want everything, you should set -S to the the same value as history-limit in the tmux config file. I did not investigate what part of this code is st specific, so let’s just try something like this:

tmpfile=$(mktemp /tmp/st-cmd-output.XXXXXX)
tmux capture-pane -p -S -1000 > "$tmpfile"
trap 'rm "$tmpfile"' 0 1 15
sed -n "w $tmpfile"
ps1="$(grep "\S" "$tmpfile" | tail -n 1 | cut -d' ' -f1)"
chosen="$(grep -F "$ps1" "$tmpfile" | sed '$ d' | tac | dmenu -p "Copy which command's output?" -i -l 10 | sed 's/[^^]/[&]/g; s/\^/\\^/g')"
eps1="$(echo "$ps1" | sed 's/[^^]/[&]/g; s/\^/\\^/g')"
  • Use fzf - cross-compatible with Mac and whatever

For times I just want to browse around and copy different stuff using Vim movements, I found a simple way to get the contents into a Emacs temp buffer:

(defun tmux-capture-pane()
  (interactive)
  (with-output-to-temp-buffer "*tmux-capture-pane*"
    (shell-command "tmux capture-pane -p"
                   "*tmux-capture-pane*"
                   "*Messages*")
    (pop-to-buffer "*tmux-capture-pane*")))

I have started to rewrite the matching using regex:

import re

command = re.escape('[const@main]~% lsblk')
output = r'.*?'
prompt = re.escape('[const@main]')

# Notice the capture group around "output"
pattern = r'{command}\n({output}){prompt}'.format(command=command, output=output, prompt=prompt)
match = re.search(pattern, cap, re.MULTILINE|re.DOTALL)

There’s sed:

sed -n '/BEGIN/,/END/p' tmux.txt

And awk:

awk '/StartPattern/,/EndPattern/' FileName

Seems like you have to escape a bunch of stuff though.

I have no idea what sed -n "w $tmpfile" does. On my system it deletes the contents of the file.


Back to posts