Add misc stuff
[misc.git] / bin / tm
1 #!/bin/bash
2
3 # Copyright (C) 2011, 2012, 2013, 2014, 2016 Joerg Jaspert <joerg@debian.org>
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 # .
9 # 1. Redistributions of source code must retain the above copyright
10 #    notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 #    notice, this list of conditions and the following disclaimer in the
13 #    documentation and/or other materials provided with the distribution.
14 # .
15 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26 # Always exit on errors
27 set -e
28 # Undefined variables, we don't like you
29 set -u
30 # ERR traps are inherited by shell functions, command substitutions and
31 # commands executed in a subshell environment.
32 set -E
33
34 ########################################################################
35 # The following variables can be overwritten outside the script.
36 #
37
38 # We want a useful tmpdir, so set one if it isn't already.  Thats the
39 # place where tmux puts its socket, so you want to ensure it doesn't
40 # change under your feet - like for those with a daily-changing tmpdir
41 # in their home...
42 declare -r TMPDIR=${TMPDIR:-"/tmp"}
43
44 # Do you want me to sort the arguments when opening an ssh/multi-ssh session?
45 # The only use of the sorted list is for the session name, to allow you to
46 # get the same session again no matter how you order the hosts on commandline.
47 declare -r TMSORT=${TMSORT:-"true"}
48
49 # Want some extra options given to tmux? Define TMOPTS in your environment.
50 # Note, this is only used in the final tmux call where we actually
51 # attach to the session!
52 TMOPTS=${TMOPTS:-"-2"}
53
54 # The following directory can hold session config for us, so you can use them
55 # as a shortcut.
56 declare -r TMDIR=${TMDIR:-"${HOME}/.tmux.d"}
57
58 # Should we prepend the hostname to autogenerated session names?
59 # Example: Call tm ms host1 host2.
60 # TMSESSHOST=true  -> session name is HOSTNAME_host1_host2
61 # TMSESSHOST=false -> session name is host1_host2
62 declare -r TMSESSHOST=${TMSESSHOST:-"true"}
63
64 # Allow to globally define a custom ssh command line.
65 TMSSHCMD=${TMSSHCMD:-"ssh"}
66
67 # Debug output
68 declare -r DEBUG=${DEBUG:-"false"}
69
70 # Save the last argument, it may be used (traditional style) for
71 # replacing
72 args=$#
73 TMREPARG=${!args}
74
75 # Where does your tmux starts numbering its windows? Mine does at 1,
76 # default for tmux is 0. We try to find it out, but if we fail, (as we
77 # only check $HOME/.tmux.conf you can set this variable to whatever it
78 # is for your environment.
79 if [[ -f ${HOME}/.tmux.conf ]]; then
80     bindex=$(grep ' base-index ' ${HOME}/.tmux.conf || echo 0 )
81     bindex=${bindex//* }
82 else
83     bindex=0
84 fi
85 declare TMWIN=${TMWIN:-$bindex}
86 unset bindex
87
88 ########################################################################
89 # Nothing below here to configure
90
91 # Should we group the session to another? Set to true if -g on
92 # commandline
93 GROUPSESSION=false
94
95 # Should we open another session, even if we already have one with
96 # this name? (Ie. second multisession to the same set of hosts)
97 # This is either set by the getopts option -n or by having -n
98 # as very first parameter after the tm command
99 if [[ $# -ge 1 ]] && [[ "${1}" = "-n" ]]; then
100     DOUBLENAME=true
101     # And now get rid of it. getopts won't see it, as it was first and
102     # we remove it - but it doesn't matter, we set it already.
103     # getopts is only used if it appears somewhere else in the
104     # commandline
105     shift
106 else
107     DOUBLENAME=false
108 fi
109
110 # Store the first commandline parameter
111 cmdline=${1:-""}
112
113 # Get the tmux version and split it in major/minor
114 TMUXVERS=$(tmux -V 2>/dev/null || echo "tmux 1.3")
115 declare -r TMUXVERS=${TMUXVERS##* }
116 declare -r TMUXMAJOR=${TMUXVERS%%.*}
117 declare -r TMUXMINOR=${TMUXVERS##*.}
118
119 # Save IFS
120 declare -r OLDIFS=${IFS}
121
122 # To save session file data
123 TMDATA=""
124
125 # Freeform .cfg file or other session file?
126 TMSESCFG=""
127
128 ########################################################################
129 function usage() {
130     echo "tmux helper by Joerg Jaspert <joerg@ganneff.de>"
131     echo "There are two ways to call it. Traditional and \"getopts\" style."
132     echo "Traditional call as: $0 CMD [host]...[host]"
133     echo "Getopts call as: $0 [-s host] [-m hostlist] [-k name] [-l] [-n] [-h] [-c config] [-e]"
134     echo "Note that traditional and getopts can be mixed, sometimes."
135     echo ""
136     echo "Traditional:"
137     echo "CMD is one of"
138     echo " ls          List running sessions"
139     echo " s           Open ssh session to host"
140     echo " ms          Open multi ssh sessions to hosts, synchronizing input"
141     echo "             To open a second session to the same set of hosts put a"
142     echo "             -n in front of ms"
143     echo " k           Kill a session. Note that this needs the exact session name"
144     echo "             as shown by tm ls"
145     echo " \$anything   Either plain tmux session with name of \$anything or"
146     echo "             session according to TMDIR file"
147     echo ""
148     echo "Getopts style:"
149     echo "-l           List running sessions"
150     echo "-s host      Open ssh session to host"
151     echo "-m hostlist  Open multi ssh sessions to hosts, synchronizing input"
152     echo "             Due to the way getopts works, hostlist must be enclosed in \"\""
153     echo "-n           Open a second session to the same set of hosts"
154     echo "-g           Group session - attach to an existing session, but keep seperate"
155     echo "             window control"
156     echo "-k name      Kill a session. Note that this needs the exact session name"
157     echo "             as shown by tm ls"
158     echo "-c config    Setup session according to TMDIR file"
159     echo "-e SESSION   Use existion session named SESSION"
160     echo "-r REPLACE   Value to use for replacing in session files"
161     echo ""
162     echo "TMDIR file:"
163     echo "Each file in \$TMDIR defines a tmux session. There are two types of files,"
164     echo "those without an extension and those with the extension \".cfg\" (no \"\")."
165     echo "The filename corresponds to the commandline \$anything (or -c)."
166     echo ""
167     echo "Content of extensionless files is defined as:"
168     echo "  First line: Session name"
169     echo "  Second line: extra tmux commandline options"
170     echo "  Any following line: A hostname to open a shell with in the normal"
171     echo "                      ssh syntax. (ie [user@]hostname)"
172     echo ""
173     echo "Content of .cfg files is defined as:"
174     echo "  First line: Session name"
175     echo "  Second line: extra tmux commandline options"
176     echo "  Third line: The new-session command to use. Place NONE here if you want plain"
177     echo "              defaults, though that may mean just a shell. Otherwise put the full"
178     echo "              new-session command with all options you want here."
179     echo "  Any following line: Any tmux command you can find in the tmux manpage."
180     echo "              You should ensure that commands arrive at the right tmux session / window."
181     echo "              To help you with this, there are some variables available which you"
182     echo "              can use, they are replaced with values right before commands are executed:"
183     echo "              SESSION - replaced with the session name"
184     echo "              TMWIN   - see below for explanation of TMWIN Environment variable"
185     echo ""
186     echo "NOTE: Both types of files accept external listings of hostnames."
187     echo "      That is, the output of any shell command given will be used as a list"
188     echo "      of hostnames to connect to (or a set of tmux commands to run)."
189     echo ""
190     echo "NOTE: Session files can include the Token ++TMREPLACETM++ at any point. This"
191     echo "      will be replaced by the value of the -r option (if you use getopts style) or"
192     echo "      by the LAST argument on the line if you use traditional calling."
193     echo "      Note that with traditional calling, the argument will also be tried as a hostname,"
194     echo "      so it may not make much sense there, unless using a session file that contains"
195     echo "      solely of LIST commands."
196     echo ""
197     echo "NOTE: Session files can include any existing environment variable at any point (but"
198     echo "      only one per line). Those get replaced during tm execution time with the actual"
199     echo "      value of the environment variable. Common usage is $HOME, but any existing var"
200     echo "      works fine."
201     echo ""
202     echo "Environment variables recognized by this script:"
203     echo "TMPDIR     - Where tmux stores its session information"
204     echo "             DEFAULT: If unset: /tmp"
205     echo "TMSORT     - Should ms sort the hostnames, so it always opens the same"
206     echo "             session, no matter in which order hostnames are presented"
207     echo "             DEFAULT: true"
208     echo "TMOPTS     - Extra options to give to the tmux call"
209     echo "             Note that this ONLY affects the final tmux call to attach"
210     echo "             to the session, not to the earlier ones creating it"
211     echo "             DEFAULT: -2"
212     echo "TMDIR      - Where are session information files stored"
213     echo "             DEFAULT: ${HOME}/.tmux.d"
214     echo "TMWIN      - Where does your tmux starts numbering its windows?"
215     echo "             This script tries to find the information in your config,"
216     echo "             but as it only checks $HOME/.tmux.conf it might fail".
217     echo "             So if your window numbers start at anything different to 0,"
218     echo "             like mine do at 1, then you can set TMWIN to 1"
219     echo "TMSESSHOST - Should the hostname appear in session names?"
220     echo "             DEFAULT: true"
221     echo "TMSSHCMD   - Allow to globally define a custom ssh command line."
222     echo "             This can be just the command or any option one wishes to have"
223     echo "             everywhere."
224     echo "             DEFAULT: ssh"
225     echo "DEBUG      - Show debug output (remember to redirect it to a file)"
226     echo ""
227     exit 42
228 }
229
230 # Simple "cleanup" of a variable, removing space and dots as we don't
231 # want them in our tmux session name
232 function clean_session() {
233     local toclean=${*:-""}
234
235     # Neither space nor dot nor : or " are friends in the SESSION name
236     toclean=${toclean// /_}
237     toclean=${toclean//:/_}
238     toclean=${toclean//\"/}
239     echo ${toclean//./_}
240 }
241
242 # Merge the commandline parameters (hosts) into a usable session name
243 # for tmux
244 function ssh_sessname() {
245     if [[ ${TMSORT} = true ]]; then
246         local one=$1
247         # get rid of first argument (s|ms), we don't want to sort this
248         shift
249         local sess=$(for i in $@; do echo $i; done | sort | tr '\n' ' ')
250         sess="${one} ${sess% *}"
251     else
252         # no sorting wanted
253         local sess="${*}"
254     fi
255     clean_session ${sess}
256 }
257
258 # Setup functions for all tmux commands
259 function setup_command_aliases() {
260     local command
261     local SESNAME
262     SESNAME="tmlscm$$"
263     if [[ ${TMUXMAJOR} -lt 2 ]] || [[ ${TMUXMINOR} -lt 2 ]]; then
264         # Starting tmux 2.2, this is no longer needed
265         # Debian Bug #718777 - tmux needs a session to have lscm work
266         tmux new-session -d -s ${SESNAME} -n "check" "sleep 3"
267     fi
268     for command in $(tmux list-commands|awk '{print $1}'); do
269         eval "tm_$command() { tmux $command \"\$@\" >/dev/null; }"
270     done
271     if [[ ${TMUXMAJOR} -lt 2 ]] || [[ ${TMUXMINOR} -lt 2 ]]; then
272         tmux kill-session -t ${SESNAME} || true
273     fi
274 }
275
276 # Run a command (function) after replacing variables
277 function do_cmd() {
278     local cmd=$*
279     cmd1=${cmd%% *}
280     if [[ ${cmd1} =~ ^# ]]; then
281         return
282     elif  [[ ${cmd1} =~ new-window ]]; then
283         TMWIN=$(( TMWIN + 1 ))
284     fi
285
286     cmd=${cmd//SESSION/$SESSION}
287     cmd=${cmd//TMWIN/$TMWIN}
288     cmd=${cmd/$cmd1 /}
289     debug $cmd1 $cmd
290     eval tm_$cmd1 $cmd
291 }
292
293 # Use a configuration file to setup the tmux parameters/session
294 function own_config() {
295     if [[ ${1} =~ .cfg$ ]]; then
296         TMSESCFG="free"
297     fi
298     # Set IFS to be NEWLINE only, not also space/tab, as our input files
299     # are \n seperated (one entry per line) and lines may well have spaces.
300     local IFS="
301 "
302     # Fill an array with our config
303     if [[ -n ${TMDATA[@]:-""} ]] && [[ ${#TMDATA[@]} -gt 0 ]]; then
304         olddata=("${TMDATA[@]}")
305     fi
306
307     TMDATA=( $(sed -e "s/++TMREPLACETM++/${TMREPARG}/g" "${TMDIR}/$1") )
308     # Restore IFS
309     IFS=${OLDIFS}
310
311     SESSION=${SESSION:-$(clean_session ${TMDATA[0]})}
312
313     if [ "${TMDATA[1]}" != "NONE" ]; then
314         TMOPTS=${TMDATA[1]}
315     fi
316
317     # Seperate the lines we work with
318     local IFS=""
319     local -a workdata=(${TMDATA[@]:2})
320     IFS=${OLDIFS}
321
322     # Lines (starting with line 3) may start with LIST, then we get
323     # the list of hosts from elsewhere. So if one does, we exec the
324     # command given, then append the output to TMDATA - while deleting
325     # the actual line with LIST in.
326     local TMPDATA=$(mktemp -u -p ${TMPDIR} .tmux_tm_XXXXXXXXXX)
327     trap "rm -f ${TMPDATA}" EXIT ERR HUP INT QUIT TERM
328     local index=0
329     while [[ ${index} -lt ${#workdata[@]} ]]; do
330         if [[ "${workdata[${index}]}" =~ ^LIST\ (.*)$ ]]; then
331             # printf -- 'workdata: %s\n' "${workdata[@]}"
332             local cmd=${BASH_REMATCH[1]}
333             if [[ ${cmd} =~ \$\{([0-9a-zA-Z_]+)\} ]]; then
334                 repvar=${BASH_REMATCH[1]}
335                 reptext=${!repvar}
336                 cmd=${cmd//\$\{$repvar\}/$reptext}
337             fi
338             echo "Line ${index}: Fetching hostnames using provided shell command '${cmd}', please stand by..."
339
340             $( ${cmd} >| "${TMPDATA}" )
341             # Set IFS to be NEWLINE only, not also space/tab, the list may have ssh options
342             # and what not, so \n is our seperator, not more.
343             IFS="
344 "
345             out=( $(tr -d '\r' < "${TMPDATA}" ) )
346
347             # Restore IFS
348             IFS=${OLDIFS}
349
350             workdata+=( "${out[@]}" )
351             unset workdata[${index}]
352             unset out
353             # printf -- 'workdata: %s\n' "${workdata[@]}"
354         elif [[ "${workdata[${index}]}" =~ ^SSHCMD\ (.*)$ ]]; then
355             TMSSHCMD=${BASH_REMATCH[1]}
356         fi
357         index=$(( index + 1 ))
358     done
359     rm -f "${TMPDATA}"
360     trap - EXIT ERR HUP INT QUIT TERM
361     debug "TMDATA: ${TMDATA[@]}"
362     debug "olddata: ${olddata[@]:-''}"
363     if [[ -n ${olddata[@]:-""} ]]; then
364         TMDATA=( "${olddata[@]}" "${workdata[@]}" )
365     else
366         TMDATA=( "${TMDATA[@]:0:2}" "${workdata[@]}"  )
367     fi
368     declare -r TMDATA=( "${TMDATA[@]}" )
369     debug "TMDATA now ${TMDATA[@]}"
370 }
371
372 # Simple overview of running sessions
373 function list_sessions() {
374     local IFS=""
375     if output=$(tmux list-sessions 2>/dev/null); then
376         echo $output
377     else
378         echo "No tmux sessions available"
379     fi
380 }
381
382 # We either have a debug function that shows output, or one that
383 # plainly returns
384 if [[ ${DEBUG} == true ]]; then
385         eval "debug() { >&2 echo \$* ; }"
386 else
387         eval "debug() { return ; }"
388 fi
389
390 setup_command_aliases
391
392 ########################################################################
393 # MAIN work follows here
394 # Check the first cmdline parameter, we might want to prepare something
395 case ${cmdline} in
396     ls)
397         list_sessions
398         exit 0
399         ;;
400     s|ms|k)
401         # Yay, we want ssh to a remote host - or even a multi session setup - or kill one
402         # So we have to prepare our session name to fit in what tmux (and shell)
403         # allow us to have. And so that we can reopen an existing session, if called
404         # with the same hosts again.
405         SESSION=$(ssh_sessname $@)
406         declare -r cmdline
407         shift
408         ;;
409     -*)
410         while getopts "lnhs:m:c:e:r:k:g:" OPTION; do
411             case ${OPTION} in
412                 l) # ls
413                     list_sessions
414                     exit 0
415                     ;;
416                 s) # ssh
417                     SESSION=$(ssh_sessname s ${OPTARG})
418                     declare -r cmdline=s
419                     shift
420                     ;;
421                 k) # kill session
422                     SESSION=$(ssh_sessname s ${OPTARG})
423                     declare -r cmdline=k
424                     shift
425                     ;;
426                 m) # ms (needs hostnames in "")
427                     SESSION=$(ssh_sessname ms ${OPTARG})
428                     declare -r cmdline=ms
429                     shift
430                     ;;
431                 c) # pre-defined config
432                     own_config ${OPTARG}
433                     ;;
434                 e) # existing session name
435                     SESSION=$(clean_session ${OPTARG})
436                     shift
437                     ;;
438                 n) # new session even if same name one already exists
439                     DOUBLENAME=true
440                     ;;
441                 r) # replacement arg
442                     TMREPARG=${OPTARG}
443                     ;;
444                 g) # Group session, not simple attach
445                     declare -r GROUPSESSION=true
446                     SESSION=$(clean_session ${OPTARG})
447                     shift
448                     ;;
449                 h)
450                     usage
451                     ;;
452             esac
453         done
454         ;;
455     *)
456         # Nothing special (or something in our tmux.d)
457         if [ $# -lt 1 ]; then
458             SESSION=${SESSION:-""}
459             if [[ -n "${SESSION}" ]]; then
460                 # Environment has SESSION set, wherever from. So lets
461                 # see if its an actual tmux session
462                 if ! tmux has-session -t "${SESSION}" 2>/dev/null; then
463                     # It is not. And no argument. Show usage
464                     usage
465                 fi
466             else
467                 usage
468             fi
469         elif [ -r "${TMDIR}/${cmdline}" ]; then
470             for arg in "$@"; do
471                 own_config ${arg}
472             done
473         else
474             # Not a config file, so just session name.
475             SESSION=${cmdline}
476         fi
477         ;;
478 esac
479
480 havesession="false"
481 if tmux has-session -t ${SESSION} 2>/dev/null; then
482     havesession="true"
483 fi
484 declare -r havesession
485
486 # And now check if we would end up with a doubled session name.
487 # If so add something "random" to the new name, like our pid.
488 if [[ ${DOUBLENAME} == true ]] && [[ ${havesession} == true ]]; then
489     # Session exist but we are asked to open another one,
490     # so adjust our session name
491     if [[ ${#TMDATA} -eq 0 ]] && [[ ${SESSION} =~ ([ms]+)_(.*) ]]; then
492         SESSION="${BASH_REMATCH[1]}_$$_${BASH_REMATCH[2]}"
493     else
494         SESSION="$$_${SESSION}"
495     fi
496 fi
497
498 if [[ ${TMSESSHOST} = true ]]; then
499     declare -r SESSION="$(uname -n|cut -d. -f1)_${SESSION}"
500 else
501     declare -r SESSION
502 fi
503
504 # We only do special work if the SESSION does not already exist.
505 if [[ ${cmdline} != k ]] && [[ ${havesession} == false ]]; then
506     # In case we want some extra things...
507     # Check stupid users
508     if [ $# -lt 1 ]; then
509         usage
510     fi
511     tm_pane_error="create pane failed: pane too small"
512     case ${cmdline} in
513         s)
514             # The user wants to open ssh to one or more hosts
515             do_cmd new-session -d -s ${SESSION} -n "${1}" "'${TMSSHCMD} ${1}'"
516             # We disable any automated renaming, as that lets tmux set
517             # the pane title to the process running in the pane. Which
518             # means you can end up with tons of "bash". With this
519             # disabled you will have panes named after the host.
520             do_cmd set-window-option -t ${SESSION} automatic-rename off >/dev/null
521             # If we have at least tmux 1.7, allow-rename works, such also disabling
522             # any rename based on shell escape codes.
523             if [ ${TMUXMINOR//[!0-9]/} -ge 7 ] || [ ${TMUXMAJOR//[!0-9]/} -gt 1 ]; then
524                 do_cmd set-window-option -t ${SESSION} allow-rename off >/dev/null
525             fi
526             shift
527             count=2
528             while [ $# -gt 0 ]; do
529                 do_cmd new-window -d -t ${SESSION}:${count} -n "${1}" "${TMSSHCMD} ${1}"
530                 do_cmd set-window-option -t ${SESSION}:${count} automatic-rename off >/dev/null
531                 # If we have at least tmux 1.7, allow-rename works, such also disabling
532                 # any rename based on shell escape codes.
533                 if [ ${TMUXMINOR//[!0-9]/} -ge 7 ] || [ ${TMUXMAJOR//[!0-9]/} -gt 1 ]; then
534                     do_cmd set-window-option -t ${SESSION}:${count} allow-rename off >/dev/null
535                 fi
536                 count=$(( count + 1 ))
537                 shift
538             done
539             ;;
540         ms)
541             # We open a multisession window. That is, we tile the window as many times
542             # as we have hosts, display them all and have the user input send to all
543             # of them at once.
544             do_cmd new-session -d -s ${SESSION} -n "Multisession" "'${TMSSHCMD} ${1}'"
545             shift
546             while [ $# -gt 0 ]; do
547                 set +e
548                 output=$(do_cmd split-window -d -t ${SESSION}:${TMWIN} "'${TMSSHCMD} ${1}'" 2>&1)
549                 ret=$?
550                 set -e
551                 if [[ ${ret} -ne 0 ]] && [[ ${output} == ${tm_pane_error} ]]; then
552                     # No more space -> have tmux redo the
553                     # layout, so all windows are evenly sized.
554                     do_cmd select-layout -t ${SESSION}:${TMWIN} main-horizontal >/dev/null
555                     # And dont shift parameter away
556                     continue
557                 fi
558                 shift
559             done
560             # Now synchronize them
561             do_cmd set-window-option -t ${SESSION}:${TMWIN} synchronize-pane >/dev/null
562             # And set a final layout which ensures they all have about the same size
563             do_cmd select-layout -t ${SESSION}:${TMWIN} tiled >/dev/null
564             ;;
565         *)
566             # Whatever string, so either a plain session or something from our tmux.d
567             if [ -z "${TMDATA}" ]; then
568                 # the easy case, just a plain session name
569                 do_cmd new-session -d -s ${SESSION}
570             else
571                 # data in our data array, the user wants his own config
572                 if [[ ${TMSESCFG} = free ]]; then
573                     if [[ ${TMDATA[2]} = NONE ]]; then
574                         # We have a free form config where we get the actual tmux commands
575                         # supplied by the user, so just issue them after creating the session.
576                         do_cmd new-session -d -s ${SESSION} -n "'${TMDATA[0]}'"
577                     else
578                         do_cmd ${TMDATA[2]}
579                     fi
580                     tmcount=${#TMDATA[@]}
581                     index=3
582                     while [ ${index} -lt ${tmcount} ]; do
583                         do_cmd ${TMDATA[$index]}
584                         (( index++ ))
585                     done
586                 else
587                     # So lets start with the "first" line, before dropping into a loop
588                     do_cmd new-session -d -s ${SESSION} -n "${TMDATA[0]}" "'${TMSSHCMD} ${TMDATA[2]}'"
589
590                     tmcount=${#TMDATA[@]}
591                     index=3
592                     while [ ${index} -lt ${tmcount} ]; do
593                         # List of hostnames, open a new connection per line
594                         set +e
595                         output=$(do_cmd split-window -d -t ${SESSION}:${TMWIN} "'${TMSSHCMD} ${TMDATA[$index]}'" 2>&1)
596                         set -e
597                         if [[ ${output} =~ ${tm_pane_error} ]]; then
598                             # No more space -> have tmux redo the
599                             # layout, so all windows are evenly sized.
600                             do_cmd select-layout -t ${SESSION}:${TMWIN} main-horizontal >/dev/null
601                             # And again, don't increase index
602                             continue
603                         fi
604                         (( index++ ))
605                     done
606                     # Now synchronize them
607                     do_cmd set-window-option -t ${SESSION}:${TMWIN} synchronize-pane >/dev/null
608                     # And set a final layout which ensures they all have about the same size
609                     do_cmd select-layout -t ${SESSION}:${TMWIN} tiled >/dev/null
610                 fi
611             fi
612             ;;
613     esac
614     # Build up new session, ensure we start in the first window
615     do_cmd select-window -t ${SESSION}:${TMWIN}
616 elif [[ ${cmdline} == k ]]; then
617     # So we are asked to kill a session
618     tokill=${SESSION//k_/}
619     do_cmd kill-session -t ${tokill}
620     exit 0
621 fi
622
623 # If we should group our session or not
624 if [[ ${GROUPSESSION} == true ]]; then
625     # Grouping means opening a new session, but sharing the sessions with
626     # another session (existing and new windows). But window control is separate.
627     sesname="$$_${SESSION}"
628     tmux ${TMOPTS} new-session -s ${sesname} -t ${SESSION}
629     tmux ${TMOPTS} kill-session -t ${sesname}
630 else
631     # Do not group, just attach
632     tmux ${TMOPTS} attach -t ${SESSION}
633 fi