3

Let's say I'm tailing some logs from a running app and I expect some long output to come soon that I would like to scroll to the top of once it's done. Does tmux have anything that could help me here? I'm thinking something like vim's marks, where I could press ma to make a mark called a, and then press 'a to jump to that line.

Ideal workflow:

  1. tail -f some stuff
  2. <prefix> m a to set a mark called a
  3. Do something which causes a lot of output to be logged
  4. <prefix> ' a to jump to where a was defined
4
  • 1
    Maybe just put a comment like # mymark before you start running command 3. and then you can search back to that unique string?
    – mattb
    Aug 12, 2021 at 23:47
  • @mattb That's a good point, but the problem I see is that, in the OP's use-case, the command is already running (tail -f ...) -- Presumably it's a log-running tail that is always in a particular pane. The new command in step 3 that causes the log to do something interested is coming in a separate pane, most likely (or even triggered by something entirely external). It is possible to type in the terminal while tailing something, so putting a comment in would work, but only if no additional input comes in while typing. That might be tough if tail'ing a rapidly growing log file. Aug 13, 2021 at 13:19
  • Still, I think you should post it as an answer (with a discussion of the caveats, of course) -- I'd upvote it. Aug 13, 2021 at 13:32
  • @NotTheDr01ds cheers! In the answer I posted I tried to incorporate what you pointed out above.
    – mattb
    Aug 13, 2021 at 13:44

2 Answers 2

3

While Tmux does have a "mark" function, I don't think it will work for your use-case.

In copy-mode, pressing Shiftx (a.k.a. "X") sets a mark, and Metax will then toggle between the current location and that mark. The problem is that when you exit copy-mode, the mark is lost. So you wouldn't be able to run a subsequent tail -f and get back to that point.

My personal recommendation would be to use a pager for the output that supports (a) following a growing file, and (b) mark/jump functionality. That can found pretty easily in the less command. Your workflow would be pretty close to what you describe:

  1. less +F some stuff
  2. Ctrl+c to temporarily exit follow-mode
  3. ma to set a mark called "a"
  4. F to restart follow-mode
  5. Do something which causes a lot of output to be logged
  6. Ctrl+c to temporarily exit follow-mode (you wouldn't want it scrolling while analyzing your output anyway).
  7. 'a to jump to your mark
  8. F to restart follow-mode when/if desired

Edit/Update

If you really want a "mark" and "goto-mark" function, it's possible, but very hacky, IMHO. I'm really only posting this because (a) I finally got it to (mostly) work, and (b) I want to get it out of my config, but don't want to lose work.

First, create two separate additional files:

.tmux-set_mark.tmux:

# Must use "run-shell" since it can run the
# command immediately.  The #() syntax always 
# uses the *previous* results.
# The capture-pane is hackery to get the current 
# line number we are on (starting from the 
# beginning of the history.
run "tmux set-environment TMUX_CUR_LINE $(tmux capture-pane -S - -E - -p | tac | awk 'NF {p=1} p' | wc -l)"

# Store it in a user-option on the current 
# pane.  This allows marks in multiple panes 
# to work.
set-option -F -p @TMUX_MARK "#{TMUX_CUR_LINE}"

display-message "Mark set at line #{@TMUX_MARK}"

.tmux-goto_mark.tmux:

# Must use "run-shell" since it can run 
# the command immediately.  The #() 
# syntax always uses the *previous* 
# results.
run-shell "tmux set-environment TMUX_CUR_LINE $(tmux capture-pane -S - -E - -p | tac | awk 'NF {p=1} p' | wc -l)"

# Set a user option on the pane with 
# the line number we need to goto.
# This is calculated, of course, by 
# subtracting the marked-line-number
# from the current-line-number
set-option -F -p @TMUX_GOTO "#{e|-:#{TMUX_CUR_LINE},#{@TMUX_MARK}}"

copy-mode

# Goto the line.  Again `run-shell` is needed
# here since send-keys can't expand the FORMAT 
# string, but run-shell can.

run-shell 'tmux display-message #{@TMUX_GOTO}'
run-shell 'tmux send-keys -X goto-line #{@TMUX_GOTO}'

# The next two lines are just additional
# hackery to use the built-in TMux marking
# ability so that the marked line is highlighted
# and at the top of the screen
send-keys -X set-mark
send-keys -X jump-to-mark

Then, in your .tmux.conf, set key-bindings to source these files:

unbind x
bind x source-file "$HOME/.tmux-set_mark.tmux"
unbind X
bind X source-file "$HOME/.tmux-goto_mark.tmux"

This is horrible, IMHO. Easier possible methods fail:

  • It would be easier if there was a better way to get the current line number you are on in the buffer, but there doesn't seem to be a variable for this. The closest I could find was history_size, but the history doesn't start until you begin scrolling your pane.
  • It would be easier if goto-line took an absolute line number, instead it is the relative line number from the bottom of the buffer in copy-mode, not the top.

There's one additional caveat/bug to this code. If you do this on the first "page" of a Tmux pane (before you start scrolling), the line that it hightlights will not be the one that you marked. The alternative is to not use the:

send-keys -X set-mark
send-keys -X jump-to-mark

But then the goto-line will always place the desired mark at the bottom of the pane, meaning you will always need to scroll down a pane.

1
  • Also, from unix.stackexchange.com/a/82068/438036: You can make less call Vim (assuming EDITOR=vim) on the file it is currently viewing by pressing v. less will even put your cursor in the correct location within Vim. When you exit Vim, you will find yourself back at less. If you made any changes to the file while you were in Vim, they will be reflected in less.
    – mattb
    Aug 13, 2021 at 17:15
1

Given the comment by @NotTheDr01ds to my comment below the OP, you could append a unique line to the file you're tailing (if you have write permission and appending if such a line doesn't interfere with any future use of that log file) before you start the process in step 3 by doing:

echo '# mymark' >> file.log && tail -f file.log

Or if that's not preferable, and if the file you're tailing isn't growing rapidly before you start the process in step 3, you could type directly into the terminal were the output of tail -f is shown

$ tail -f file.log
 > some line from the log
 > another line from the log
 # mymark!
 > line generated by step3
 > and there will be many more lines generated by step3...
 > .           .           .           .           .           
 > .           .           .           .           .           
 > .           .           .           .           .           

Then in both cases you could search backwards for the # mymark string in tmux's copy mode (with ?^# mymark).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .