Bash scripts with tmux to launch a 4-paned window
Asked Answered
F

5

82

Can anyone help explain what's going on with tmux, bash, and exec? I'm trying to set up a tmux session with a 4-pane window. Ideally, I want to run a command in 3 of the panes: e.g. a Ruby Thin server and a couple of Ruby daemons. This is what I have so far:

~/.bin/tmux-foo:

#!/bin/sh

tmux new-session -d -s foo 'exec pfoo "bundle exec thin start"'
tmux rename-window 'Foo'
tmux select-window -t foo:0
tmux split-window -h 'exec pfoo "bundle exec compass watch"'
tmux split-window -v -t 0 'exec pfoo "rake ts:start"'
tmux split-window -v -t 1 'exec pfoo'
tmux -2 attach-session -t foo

~/.bin/pfoo:

#!/bin/bash
cd ~/projects/foo
rvm use ree

# here I want to execute command1 2 3 or 4...

exec $SHELL

It all works... but when I ctlr-c in the first pane that is running the thin server, it stops the thin server and returns to the shell. However, the command is not in the history; i.e. if I hit the up key I don't get the bundle exec thin start command... I get some other command from my bash history. I'm wondering if there's any way to arrange these scripts so that I get the commands in the bash history.

Also... I've tried many combinations of exec, exec $SHELL -s ..., and exec $SHELL -s ... -I and I'm not quite sure what is going on...

Can anyone help explain the general idea of what is going on with tmux and bash and exec here?

Fitted answered 27/3, 2011 at 4:42 Comment(1)
For the record, I use github.com/jimeh/tmuxifier now (rather than tmuxinator, which has non-shell dependencies).Fitted
A
79

As others have mentioned, your commands are being run by the shell script before launching your $SHELL; there is no general way the instance of $SHELL can know what its parent ran before starting it.

To get the “initial command” into the shell history, you need to feed the command keystrokes directly to the instance of $SHELL itself (after it has been started, of course). In other contexts I might suggest using a small Expect program to spawn an instance of $SHELL, feed it the keystrokes, then use interact to tie the tty to the expect-spawned $SHELL.

But in the context of tmux, we can just use send-keys:

#!/bin/sh

tmux new-session -d -s foo 'exec pfoo'
tmux send-keys 'bundle exec thin start' 'C-m'
tmux rename-window 'Foo'
tmux select-window -t foo:0
tmux split-window -h 'exec pfoo'
tmux send-keys 'bundle exec compass watch' 'C-m'
tmux split-window -v -t 0 'exec pfoo'
tmux send-keys 'rake ts:start' 'C-m'
tmux split-window -v -t 1 'exec pfoo'
tmux -2 attach-session -t foo
Argue answered 11/10, 2011 at 2:43 Comment(3)
May I ask, what is 'exec pfoo' supposed to do? I tried running the line on my instance but it's not creating the session (it seems to be exiting right after trying to create a session and then adds a number of characters). Wonder if it could be related to my PuTTy settings.Putumayo
'exec pfoo' is a placeholder here for whatever command you actually want to execute.Tautology
'exec pfoo' is executing one of your pfoo scripts, saved inside one of your $PATH directories.Magnetograph
H
22

tmuxinator lets you specify this with a nice yaml file. For your case you could have:

# ~/.tmuxinator/foo.yml
# you can make as many tabs as you wish...

project_name: foo
project_root: ~/projects/foo
rvm: ree
tabs:
  - main:
      layout: tiled
      panes:
        - bundle exec thin start
        - bundle exec compass watch
        - #empty, will just run plain bash
        - rake ts:start

You can of course have extra windows etc.

Hasbeen answered 18/4, 2012 at 20:1 Comment(4)
So good to know about tmuxinator, thanks for this !Caldera
10 years later and tmuxinator is still my preferred answer. Super easy to start/stop running a project that needs multiple terminals with one command.Fatherinlaw
Nice, but how about a linux version?Amritsar
@Amritsar - tmuxinator is cross platform. There is a debian/ubuntu package, it's available from homebrew/linuxbrew, or see the github page for other ways to install it.Hasbeen
S
7

Place the following into the command prompt [all as one line], it will open 4 tmux panels automatically (I know this wasn't the question, but this looks somewhat easier than what I saw posted):

    tmux new-session \; \split-window -v \; \split-window -h \; \select-pane -t 0 \; \split-window -h

Now you can take that command and use it with whatever scripting language you like [you need to double the escape characters {backslash characters} if using perl...and probably other languages].

This runs the subsequent command in the newer tmux panel, reverting to the first and splitting it at the end.

Sazerac answered 31/10, 2020 at 22:21 Comment(0)
R
3

You are running the command and then entering the interactive shell; the command run from the script, not being in an interactive shell, doesn't get recorded in the history. You really want a way to stuff (that's a technical term :) once upon a time it was TIOCSTI for "terminal ioctl(): stuff input") input for the shell into the window.

With tmux, it looks like you use buffers for this. Something along the lines of (untested)

#! /bin/bash
cd ~/projects/foo
rvm use ree

if [[ $# != 0 ]]; then
  tmux set-buffer "$(printf '%s\n' "$*")" \; paste-buffer -d
fi

exec ${SHELL:-/bin/sh}
Rhodarhodamine answered 27/3, 2011 at 5:26 Comment(2)
Hmm interesting... it doesn't quite work, but I see what you mean. I'll try to play around with the idea. Thank you!Fitted
In (newer?) ksh, there is a environment variable setting, its one of the HIST* vars, and if you set it the right way, it will capture alias and function definitions executed from the .profile files into the current (non-interactive) shell's history. As both new ksh and bash are Posix compliant, it's highly likely that bash can do this too. (Sorry, its a long story about why I can't dig out and give you the exact syntax about how to do this ;-( )Doily
S
3

You can create a bash script like this.

#!/bin/sh

tmux new-session -d -s mysession
tmux send-keys -t mysession "cd ~" Enter
tmux split-window -h -t mysession
tmux send-keys -t mysession "watch -n 1 df -H" Enter
tmux split-window -v -p 50 -t mysession
tmux send-keys -t mysession "htop" Enter
tmux select-pane -t mysession:0.0
tmux split-window -v -p 50 -t mysession
tmux send-keys -t mysession "cd ~" Enter
tmux select-pane -t mysession:0.0
# Attach to session 
tmux attach -t mysession

You can change commands inside "" in each send-keys command as per your preference.

Solander answered 30/12, 2022 at 11:5 Comment(1)
Pretty helpful example, if it helps, the order of the panes are: 1st command: top left. 2nd command: top right. 3rd command: bottom right. 4th command: bottom leftIdaidae

© 2022 - 2024 — McMap. All rights reserved.