TMUX pane tabs, or 'locking/freezing' pane splits in place - tmux

When using tmux, I generally have an editor open in a split on top, and a shell at the bottom. Right now, I'm writing a python script, and on the bottom half of the split I can't find a way to toggle back and forth between ipython and bash: the best I can do is create a nested split between those two, sharing the bottom half of the screen. Right now this looks something like the following.
Is there something like tabs within a pane, so I can switch back and forth between bash/ipython while keeping the editor locked/frozen in place at the top? It's not ideal right now to have to choose between switching to a new tab for one of the shell prompts or using these tiny splits. Tmux is so flexible it seems like there'd be a way to do this straightforward pane-lock. Thanks

Resolved using nested tmux sessions. I based my solution on a much-stripped down version of nested-tmux. I launch a nested session and then use tabs within that nested session. To launch a nested session, I use the following script tmux-nested.sh (adapted from the nested-tmux repo):
#!/bin/sh
TMUX_PARENT=$(basename "$TMUX")
TMUX_PARENT="${TMUX_PARENT%%,*}"
export TMUX_PARENT
_SOCK="r$RANDOM"
tmux -L "$_SOCK" new-session -s "$_SOCK"
This creates a nested session with socket given a random filename (a hardcoded value wouldn't permit multiple nested sessions, say in different tabs in the parent session).
In my .tmux.conf file, I add the following line on startup to change the tmux prefix for nested sessions:
# nested session logic
if-shell '! [ -z "$TMUX_PARENT" ]' 'set-option -g prefix C-b'
(where my normal prefix is a backtick)
EDIT:
I expanded this function into a much more robust implementation, which can be found below:
# Nest tmux sessions
tmux_nested() {
local usagestring='usage: tmux-nested [-n | -a nested-session-# | -l]'
if [[ -z $TMUX ]]; then
# ensure invoked within active session
echo "'tmux_nested' should be invoked inside an active tmux session"
return 1
fi
# read flags and check for number of existing nested sessions
local nested session_name
nested=$(tmux list-sessions -F '#{session_name}' | \
ggrep -P '^nested[\d]+' --color=never | sort -V)
while getopts ':lna:' opt; do
case $opt in
l)
[[ -z $nested ]] && nested="No nested sessions running"
echo "$nested" && return 0;;
n)
# if new session requested: create, set <C-b> tmux prefix, and attach
for (( i = 1;; i++ )); do
# get lowest available numeric value for new session name
if ! tmux has-session -t "nested$i" 2> /dev/null; then
session_name="nested$i" && break
fi
done
tmux new-session -ds "$session_name"
tmux send-keys -t "$session_name" \
"tmux set prefix C-b" ENTER "clear" ENTER
env TMUX='' tmux attach -t "$session_name"
return $?;;
a)
# attach to specified session if requested
session_name="nested${OPTARG}"
if ! env TMUX='' tmux attach -t "$session_name"; then
echo "Try 'tmux_nested -l'?" && return 1
fi;;
*) echo "$usagestring" && return 1;;
esac
done
# incorrect invocation, report incorrect invocation and exit
echo "$usagestring" && return 1
}

Related

tmux: list all the tmux windows with last 5 lines of output

I am running a lot of tmux session like
Each tmux session is started as:
today=`date +%Y-%m-%d-%H_%M_%S_%N`
tmux new-session -d -s "$today" zsh /home/path/to/script.sh "with_params"
If i want to only view the list of tmux sessions. I can do by
tmux ls | awk '{print $1}';
Now what i want is to monitor their output using the while loop to show the session name and the recent output:
while true;
do;
echo "##########################################"
??? For list of tmux session; do
sleep 1;
??? session name
??? recent last 5 lines of output
done
echo "##########################################"
done;
??? : What commands should i use
You can use capture-pane to show the last five lines of output:
tmux capture-pane -p -S- -E-|sed '/^$/d'|tail -5
Add -t to specify the pane you want to see - if you just give a session it will use the active pane in the current window.
Add -e if you want colour sequences included.

How to set and use variable in tmux.conf depending on whether an environment variable is set

(Disclaimer: I am fully aware that there are solutions to the problem I describe below that involve writing and calling shell scripts that interact with a running tmux server, or set the necessary environment variables before starting the tmux server. I am specifically posting this questions to see if it possible to solve this problem without the use of such scripts.)
Problem Summary
In my .tmux.conf file, I am trying to set a local variable VALUE to different values depending on whether an environment variable FOO has been set before invoking tmux or not. I then want to use VALUE in other tmux commands. Unfortunately, I either cannot set VALUE correctly or access it after it has been set.
Previous Attempts
According to what I have found in the manpage and in other Q&A posts that contain sample tmux code, there are several ways to implement the above.
Attempt 1
I first tried using the if-shell command. I attempted using this command both with and without the -b flag; the result was the same in either case.
I have seen from examples that I can assign variables with the syntax VALUE=bar. Given that, here is a minimal example of my configuration:
if-shell '[ -z "$FOO" ]' \
'VALUE=bar' \
'VALUE=baz'
set-environment -g RESULT $VALUE
Terminal session:
$ echo $FOO
$ tmux
[detached (from session 0)]
$ tmux showenv -g VALUE
VALUE=bar
$ tmux showenv -g RESULT
RESULT=
$ killall tmux
$ export FOO=foo
$ echo $FOO
foo
$ tmux
[detached (from session 0)]
$ tmux showenv -g VALUE
VALUE=baz
$ tmux showenv -g RESULT
RESULT=
So while VALUE seems to have been set correctly, RESULT does not seem to able to access VALUE.
Attempt 2
The manpage also mentions that commands can be conditionally executed using %if statements. Using this format, I tried the following configuration:
%if #{#(if [ -z "$FOO" ]; then echo 1; else echo 0)}
VALUE=bar
%else
VALUE=baz
%endif
set-environment -g RESULT $VALUE
For the expression in the %if statement, I tried several variations, such as
#{#([ -z "$FOO" ])} (I believe this shouldn't work since this command does not produce any output, but it was worth a try.)
#{==:#(if [-z "$FOO" ]; then echo 1; else echo 0),1} (Just in case an explicit comparison would work)
Even with these variations, regardless of whether FOO was set or not, I got the following:
$ tmux
[detached (from session 0)]
$ tmux showenv -g VALUE
VALUE=baz
$ tmux showenv -g RESULT
RESULT=baz
Thus while VALUE was accessible, it was always baz.
Unfortunately, I have been able to find no useful examples regarding the formats used in conditional statements. The manpage describes how to access tmux variables and some formatting hints; however, regarding accessing environment variables, all I could find was a way to use shell commands:
In addition, the first line of a shell command's output may be inserted using #(). For example, #(uptime) will insert the system's uptime. When constructing formats, tmux does not wait for #() commands to finish; instead, the previous result from running the same command is used, or a placeholder if the command has not been run before.
I am unsure of whether this means I need to call commands in #() twice to avoid using a placeholder value, which may be a possible error on my part.
I was also unable to find a way to print the result of #{} commands easily to debug this part of the statement.
Summary of Questions
While I would appreciate any pointers to information that may help me solve this problem, the most pressing questions for me are:
Why is VALUE being set correctly, yet not accessible to RESULT in Attempt 1?
How should my conditional be written in Attempt 2 to ensure that VALUE is set correctly?
The way tmux runs the config is by parsing the config file into a set of commands, and then executing them (there is a command queue, so the config file is parsed and appended to the queue and then executed from the queue). So there are distinct parse and execution steps.
The problem you are running into with attempt 1, is that the if-shell is run at execution time, but the $VALUE expansion happens at parse time. VALUE is not set when the set-environment command is parsed.
In attempt 2, #() is not processed inside %if so that won't work. However, you can use the variable directly in formats (if it is set). %if happens at parse time.
So you need to make sure assignment and expansion happen in the right order. You have a couple of choices.
You could make tmux expand the variable at command execution time rather than parse time. You can do this by wrapping the setenv inside run-shell, so something like:
if-shell '[ -z "$FOO" ]' \
'VALUE=bar' \
'VALUE=baz'
run 'tmux setenv -g RESULT $VALUE'
Or you could do the assignment at parse time like you tried in attempt 2, but you can't use #() - you need to use a format instead:
%if #{==:#{FOO},}
VALUE=bar
%else
VALUE=baz
%endif
setenv -g RESULT $VALUE
(Note that X=Y in the config file is equivalent to setenv -g X=Y except it happens when parsing rather than executing - both set the global environment. So you could get rid of VALUE and do either RESULT=bar or setenv -g RESULT bar inside the %if.)
Also you can use display -p to print formats. In master and 2.9 you can add -v to see how they are parsed:
$ tmux setenv -g FOO bar
$ tmux display -pv '#{==:#{FOO},baz}'
# expanding format: #{==:#{FOO},baz}
# found #{}: ==:#{FOO},baz
# modifier 0 is ==
# expanding format: #{FOO}
# found #{}: FOO
# format 'FOO' found: bar
# replaced 'FOO' with 'bar'
# result is: bar
# expanding format: baz
# result is: baz
# compare == left is: bar
# compare == right is: baz
# replaced '==:#{FOO},baz' with '0'
# result is: 0
0

How to close all windows to the right in tmux

Is there a way to issue a command to close all tmux windows unless something is open in that window? For example, an open file, a running process, etc.?
I am hoping for something that functions as a web browser where you can right click and select close all other tabs to the right. I'd like to issue this in tmux, and similar to the web browser example, have "busy" windows or panes prompt me to close them or silently fail to close.
I have seen this question, but I don't necessarily want to issue the command to all windows.
Here's a shell alternative:
for win_id in $(tmux list-windows -F '#{window_active} #{window_id}' | awk '/^1/ { active=1; next } active { print $2 }'); do tmux kill-window -t "$win_id"; done
And here's the same (readable version):
for win_id in $(tmux list-windows -F '#{window_active} #{window_id}' | \
awk '/^1/ { active=1; next } active { print $2 }')
do
tmux kill-window -t "$win_id"
done
Edit: I made a plugin with this!
https://github.com/pschmitt/tmux-forsaken
I just built a script to do so, here it is:
#!/usr/bin/env python3
import subprocess
import os
import re
result = subprocess.run(['tmux', 'list-windows'], stdout=subprocess.PIPE)
result = result.stdout.decode('utf-8')
lines = result.splitlines()
should_close_next = False
for line in lines:
if should_close_next:
window = line.split(':')[0]
os.system(f'tmux kill-window -t {window}')
continue
match = re.search("active", line)
if match:
should_close_next = True
And to integrate it with your tmux add to your tmux.conf
bind-key "k" run-shell "kill_panes_to_right.py\n"
Best

ZSH: Behavior on Enter

I realize, when I'm in my terminal, I would expect to press Enter on empty input to make a ls or a git status when I'm on a git repos.
How can I achieve that? I mean, have a custom behavior on Empty input -> Enter in zsh?
EDIT: Thanks for the help. Here's my take with preexec...
precmd() {
echo $0;
if ["${0}" -eq ""]; then
if [ -d .git ]; then
git status
else
ls
fi;
else
$1
fi;
}
On Enter zsh calls the accept-line widget, which causes the buffer to be executed as command.
You can write your own widget in order to implement the behaviour you want and rebind Enter:
my-accept-line () {
# check if the buffer does not contain any words
if [ ${#${(z)BUFFER}} -eq 0 ]; then
# put newline so that the output does not start next
# to the prompt
echo
# check if inside git repository
if git rev-parse --git-dir > /dev/null 2>&1 ; then
# if so, execute `git status'
git status
else
# else run `ls'
ls
fi
fi
# in any case run the `accept-line' widget
zle accept-line
}
# create a widget from `my-accept-line' with the same name
zle -N my-accept-line
# rebind Enter, usually this is `^M'
bindkey '^M' my-accept-line
While it would be sufficient to run zle accept-line only in cases where there actually was a command, zsh would not put a new prompt after the output. And while it is possible to redraw the prompt with zle redisplay, this will probably overwrite the last line(s) of the output if you are using multi-line prompts. (Of course there are workarounds for that, too, but nothing as simple as just using zle accept-line.
Warning: This redfines an (the most?) essential part of your shell. While there is nothing wrong with that per se (else I would not have posted it here), it has the very real chance to make your shell unusable if my-accept-line does not run flawlessly. For example, if zle accept-line were to be missing, you could not use Enter to confirm any command (e.g. to redefine my-accept-line or to start an editor). So please, test it before putting it into your ~/.zshrc.
Also, by default accept-line is bound to Ctrl+J, too. I would recommend to leave it that way, to have an easy way to run the default accept-line.
In my .zshrc I use a combination of precmd and preexec found here:
http://zsh.sourceforge.net/Doc/Release/Functions.html#Hook-Functions
I also find that the git-prompt is super useful:
https://github.com/olivierverdier/zsh-git-prompt

How to duplicate ssh session on tmux

I want to duplicate my ssh session again.
For example, my window-name is "user#host'. I wish to press prefix key + S to do 'ssh user#host on a new window'
$ tmux bind S confirm-before "neww ssh #W"
After try this, it just issue a ssh command without the option 'user#host'
The tmux version is 1.8 on CentOS 7.
You can try something like this, though it is a little ugly. Place this into your tmux.conf:
bind S neww "$(ps -ao pid,tty,args | sort | awk '$1 ~ /#{pane_pid}/{VAR=$2} $2 ~ VAR && $3 ~ /ssh/{$1=\"\"; $2=\"\"; print}')"
Explanation
Creat a binding named S and have it open a new window, using the argument as the initial command
bind S neww "..."
Execute the output of the inner command
$(...)
List the pid, tty, and command (with arguments) of all processes
ps -ao pid,tty,args | ...
Sort by pid
... | sort | ...
Feed output into awk
... | awk '...'
Find tty of current pane/window, and place it in VAR (#{} is substituted by tmux)
$1 ~ /#{pane_pid}/{VAR=$2}
Find process that has the tty we found earlier AND has a command that starts with ssh. Note that we are assuming that the pid of the ssh session is greater than the shell it was invoked in. This should be true in most cases.
$2 ~ VAR && $3 ~ /ssh/{...}
Remove pid, tty, and print the remainder. This will be the ssh command with all arguments and options. This is the command that will get executed in a new window.
$1=\"\"; $2=\"\"; print

Resources