Skip to content

Instantly share code, notes, and snippets.

@akomakom
Last active June 14, 2024 14:38
Show Gist options
  • Save akomakom/2dab995b6a48899eee841cc0e4a3192e to your computer and use it in GitHub Desktop.
Save akomakom/2dab995b6a48899eee841cc0e4a3192e to your computer and use it in GitHub Desktop.
Bash completion file for tmux using a simple clusters file, autocompletes list names and starts ssh to all matches in a single window with synchronized keyboard input.

What this is

The two bash completion scripts provide two bash completion prefixes:

  • tm or tmuxmulti ("Connect in a split terminal to all hosts in the matched list(s)")
  • tmh or tmuxmultihost ("Tmux Multi by Host", same but for matching against the hostnames rather than list names)

The system-of-record for both of these is a ~/.tmux_clusters file (see below for format).

Installation

Option 1 (static)

  1. Copy one or both scripts to your /etc/bash_completion.d/
  2. Create a ~/.tmux_clusters with lists of your hosts (see below)
  3. Re-login or run . /etc/bash_completion in any shell you want to use this in.

Option 2 (dynamic, you can just git pull to update to latest)

  1. Check out this gist: git clone [email protected]:2dab995b6a48899eee841cc0e4a3192e.git
  2. Symlink the two files:
    ln -s /full/path/to/tmuxmulti /etc/bash_completion.d/
    ln -s /full/path/to/tmuxmultihost /etc/bash_completion.d/
    
  3. Follow the static instructions from step 2.

Usage

Given this ~/.tmux_clusters file:

ldap: ldap-001.my.secret.domain ldap-002.my.secret.domain
nfs: nfs-00.my.secret.domain nfs-01.my.secret.domain
docker-test: docker-test-01.my.secret.domain docker-test-02.my.secret.domain
docker-swarm: docker-swarm-01.my.secret.domain docker-swarm-02.my.secret.domain docker-swarm-03.my.secret.domain

Completion examples that will work using this file:

(All commands will only work from within tmux)

tm ldap           # connect to the two machines on the ldap: line
tm lda<TAB>       # completes to "tm ldap", same as previous
tm docker.*       # connects to 5 machines (in docker-test and docker-smarm)

tmh ldap<TAB>     # interactive completion to one of the ldap full hostnames
tmh ldap          # connects to both ldap machines (same as ldap-.* in this case)
tmh ldap-.*       # connects to both ldap machines 
tmh docker-swarm-0[1-2].*  # connects to 01 and 02 (not 03).
# Sample system-of-record file for the auto-completion
# format is:
# alias: list of hostnames
# hostnames may appear in multiple lists.
ldap: ldap-001.my.secret.domain ldap-002.my.secret.domain
nfs: nfs-00.my.secret.domain nfs-01.my.secret.domain
docker-test: docker-test-01.my.secret.domain docker-test-02.my.secret.domain
docker-swarm: docker-swarm-01.my.secret.domain docker-swarm-02.my.secret.domain docker-swarm-03.my.secret.domain
# -*- mode: shell-script; sh-basic-offset: 8; indent-tabs-mode: t -*-
# ex: ts=8 sw=8 noet filetype=sh
#
# tmuxmulti(1) completion by Akom,
# adapted from Aaron Spettl <[email protected]>, adapted from the
# Debian GNU/Linux dput(1) completion by Roland Mas <[email protected]>
# Modified by James Mackie <[email protected]> to enable node name matching.
have tmux &&
_tmuxmulti()
{
local cur prev options paroptions clusters clusters_containing_cword
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
# all options understood by tmux (not currently used)
options=''
# get the names of all defined clusters
clusters=$(
{
grep -v "#" ~/.tmux_clusters | sed -e 's/^\([a-z0-9.-]\+\):.*$/\1/i' 2> /dev/null || /bin/true
} | sort -u)
# get the names of any clusters that contain $cur
# this is so that you can use node names to retreive any clusters they might be in
clusters_containing_cword=$(
{
grep -- "$cur" ~/.tmux_clusters | grep -v "^#" | sed -e 's/^\([a-z0-9.-]\+\):.*$/\1/i' 2> /dev/null || /bin/true
} | sort -u)
# use options and clusters for tab completion, except there isn't yet
# at least one character to filter by
# reason: don't show options if the user types "tm <tab><tab>"
paroptions="$clusters"
[ -n "$cur" ] && paroptions="$paroptions $options"
#case $prev in
#'tmuxmulti')
COMPREPLY=( "${COMPREPLY[@]}" $( compgen -W "$paroptions" | grep -- "^$cur") )
# ;;
#esac
return 0
}
[ "$have" ] && complete -F _tmuxmulti tmuxmulti
[ "$have" ] &&
tmuxmulti() {
local HOSTS HOST SSHARGS
if [ -z "$*" ]; then
echo "Please provide a list of clusters separated by spaces [TAB to list]..."
echo "You may pass ssh options as well, ie: -l user cluster1 cluster2 ..."
echo "Regex allowed: cluster.*"
return 1
fi
for cluster in $* ; do
HOST="$(grep -- "${cluster}:" ~/.tmux_clusters | grep -v "^#" | cut -d : -f 2)"
if [ -z "$HOST" ] ; then
SSHARGS="$SSHARGS $cluster" # this is not a cluster, pass it as an option to ssh instead
else
HOSTS="$HOSTS $HOST"
fi
done
local hosts=( ${HOSTS} )
echo "Connecting to ${#hosts[@]} hosts, starting with '${hosts[0]}'"
tmux new-window -n 'splitnew' "ssh $SSHARGS ${hosts[0]}"
sleep 0.5 # Primarily for tmux on mac, ensure first window exists
unset hosts[0];
for i in "${hosts[@]}"; do
echo "Splitting to '$i'"
tmux split-window -h -t 'splitnew' "ssh $SSHARGS $i"
tmux select-layout -t 'splitnew' tiled > /dev/null
done
tmux select-window -t 'splitnew'
tmux set-window-option -t 'splitnew' synchronize-panes on > /dev/null
tmux rename-window -t 'splitnew' "$1"
tmux select-pane -t 0 # select first pane
}
# Alias
[ "$have" ] && complete -F _tmuxmulti tm
[ "$have" ] && tm() { tmuxmulti $*; }
# -*- mode: shell-script; sh-basic-offset: 8; indent-tabs-mode: t -*-
# ex: ts=8 sw=8 noet filetype=sh
#
# tmuxmultihost(1) completion by Akom,
# adapted from Aaron Spettl <[email protected]>, adapted from the
# Debian GNU/Linux dput(1) completion by Roland Mas <[email protected]>
# Modified by James Mackie <[email protected]> to enable node name matching.
have tmux &&
_tmuxmultihost()
{
local cur prev options paroptions clusters clusters_containing_cword
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
# all options understood by tmux (not currently used)
options=''
# get the names of all defined clusters
clusters=$(
{
sed -e 's/^.*:\(.*\)$/\1/i' ~/.tmux_clusters 2> /dev/null || /bin/true
} | xargs | sort -u)
# use options and clusters for tab completion, except there isn't yet
# at least one character to filter by
# reason: don't show options if the user types "tmh <tab><tab>"
paroptions="$clusters"
[ -n "$cur" ] && paroptions="$paroptions $options"
#case $prev in
#'tmuxmultihost')
COMPREPLY=( "${COMPREPLY[@]}" $( compgen -W "$paroptions" | grep -- "^$cur") )
# ;;
#esac
return 0
}
[ "$have" ] && complete -F _tmuxmultihost tmuxmultihost
[ "$have" ] &&
tmuxmultihost() {
local HOSTS HOST SSHARGS
if [ -z "$*" ]; then
echo "Please provide a list of hosts separated by spaces [TAB to list]..."
echo "You may pass ssh options as well, ie: -l user cluster1 cluster2 ..."
echo "Regex allowed: cluster.*"
return 1
fi
for host in $* ; do
#HOST="$(grep -- "${host}" ~/.tmux_clusters | grep -v "^#")"
HOST=$(grep -- "${host}" ~/.tmux_clusters | grep -v "^#" | cut -d : -f 2 | tr ' ' '\n' | grep -v '^$' | grep -- "${host}" | sort -u)
if [ -z "$HOST" ] ; then
SSHARGS="$SSHARGS $cluster" # this is not a cluster, pass it as an option to ssh instead
else
HOSTS="$HOSTS $HOST"
fi
done
local hosts=( ${HOSTS} )
echo "Connecting to ${#hosts[@]} hosts, starting with '${hosts[0]}'"
tmux new-window -n 'splitnew' "ssh $SSHARGS ${hosts[0]}"
sleep 0.5 # Primarily for tmux on mac, ensure first window exists
unset hosts[0];
for i in "${hosts[@]}"; do
echo "Splitting to '$i'"
tmux split-window -h -t 'splitnew' "ssh $SSHARGS $i"
tmux select-layout -t 'splitnew' tiled > /dev/null
done
tmux select-pane -t 'splitnew'
tmux set-window-option -t 'splitnew' synchronize-panes on > /dev/null
tmux rename-window -t 'splitnew' "$1"
}
# Alias
[ "$have" ] && complete -F _tmuxmultihost tmh
[ "$have" ] && tmh() { tmuxmultihost $*; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment