Bash scripts with tmux to launch a 4-paned window - bash

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?

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

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.

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.

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}

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.

Related

Prevent tmux from exiting when script finishes

I'd like to prevent tmux from exiting automatically when the script finishes. For example, I have a command something like this(below), this does run a server for an app, but sometimes this unexpectedly exits with an error. When that happens, I'm unable to check what was wrong with the code, command.
tmux new-session -d -s "visited" 'bash -ic "visited --server"';
Following one is a simplified command of what I'd like to do.
tmux new-session -d -s "pwd" 'bash -c pwd' # but actually I need to load .bashrc
tmux a -t pwd # this should attach to the shell, but in my environment this does show "can't find session pwd" since the shell is already exited
I read a github issue and tried to add set exit-empty off to ~/.tmux.conf and restart the server tmux kill-server and re-tried the above, but seemed it doesn't work.
Just let tmux create a normal session and inject the keystrokes:
tmux new-session -d -s pwd
tmux send-keys pwd C-m
tmux a -t pwd
If you end your statement with && bash then it will drop you into a shell when completed succesful. If you want to do that only when command fails then instead use || bash.
E.g.
# Drops you into a shell if it fails, otherwise closes the window
tmux new-session -d -s "visited" 'bash -ic "visited --server" || bash';
# Drops you into a shell when finished successful, otherwise closes window.
tmux new-session -d -s "visited" 'bash -ic "visited --server" && bash';
# Always drops you into a shell when done.
tmux new-session -d -s "visited" 'bash -ic "visited --server" || bash && bash';

Tmux not responding to commands in shell script

I have a simple bash script that creates a new tmux session and does some layout:
#!/usr/local/bin/bash
tmux new-session -s $1
tmux split-window -h -p 50 -t 1
tmux new-window
tmux split-window -h -p 50 -t 1
The contents are in an executable script. When i execute the script with the name of the session as the argument, I get a new tmux session but there is only one un-split window, instead of the two [split] windows that I am telling it to create. If I run the scripts one by one on the shell prompt then I do get the desired outcome. So why is this not working in the script?
The problem is that first command starts tmux and wait for it to finish before continue. What you need to do is write custom tmux.conf file and add pass it through -f filename.conf with first command.
the other possible way is use tmux -d
#!/bin/bash
tmux new-session -d -s $1
tmux split-window -h -p 50 -t $1
tmux new-window -t $1
tmux split-window -h -p 50 -t $1
tmux attach -t $1

Bash script for tmux managing

I'd like to start tmux from terminal, split it window and execute in the splits some commands. Furthermore, I'd like to did with bash script, that I start from terminal. Now, I've been able to start tmux only.
#!/bin/sh
tmux
# How to split tmux window there, and start commands in splits?
There is a script started tmux, splitted for to panes and started ls in the left pane and top in the right one.
#!/bin/sh
tmux new-session -d -s main ;
tmux split-window -h ;
tmux select-pane -L
tmux send-keys -t main 'ls' C-m
tmux select-pane -R
tmux send-keys -t main 'top' C-m
tmux attach-session -d -t main

Open a program in new terminal tab using shell script

This is a small shell script i wrote .
x-terminal-emulator -e "optirun yarpserver" &
sleep 6
x-terminal-emulator -e "optirun iCub_SIM" &
sleep 60
x-terminal-emulator -e "optirun simCartesianControl" &
sleep 30
x-terminal-emulator -e "optirun iKinCartesianSolver --context simCartesianControl/conf --part left_arm" &
What this does is , opens a new terminal for each program . What i want to do is open new terminal tab instead of a terminal . How should i go about doing this ?
This thread is really old but if someone comes here, I leave a bash script that I created to launch multiple tabs to run different commands:
#!/bin/bash
# Array of commands to run in different tabs
commands=(
'tail -f /var/log/apache2/access.log'
'tail -f /var/log/apache2/error.log'
'tail -f /usr/local/var/postgres/server.log'
)
# Build final command with all the tabs to launch
set finalCommand=""
for (( i = 0; i < ${#commands[#]}; i++ )); do
export finalCommand+="--tab -e 'bash -c \"${commands[$i]}\"' "
done
# Run the final command
eval "gnome-terminal "$finalCommand
Just add the commands in the array and execute.
Source: http://joaoperibeiro.com/command-line-script-to-launch-multiple-tabs/
I think your best option is to use tmux to do the job. Here just a quick example and a step by step explanation to it. Here I use only vertical splits which may be confusing you should read into the tmux manpage to see how to select-panes.
First create a new tmux session in detached mode
Then send the appropriate command to launch your first program
Create a new vertical Split
Send appropriate command to launch your second program
and so on ...
tmux new-session -d -s foo
tmux send-keys -t foo 'optirun yarpserver' Enter
tmux split-window -v -t foo
tmux send-keys -t foo 'optirun iCub_SIM' Enter
tmux split-window -v -t foo
tmux send-keys -t foo 'optirun simCartesianControl' Enter
tmux split-window -v -t foo
tmux send-keys -t foo 'optirun iKinCartesianSolver --context simCartesianControl/conf --part left_arm' Enter
Hope this helps you.

How do I set tmux to open specified windows at startup?

How do I set up tmux so that it starts up with the specified windows opened?
You can write a small shell script that launches tmux with the required programs. I have the following in a shell script that I call dev-tmux. A dev environment:
#!/bin/sh
tmux new-session -d 'vim'
tmux split-window -v 'ipython'
tmux split-window -h
tmux new-window 'mutt'
tmux -2 attach-session -d
So everytime I want to launch my favorite dev environment I can just do
$ dev-tmux
I was trying to create a complex grid of panes and had to deal with switching and splitting panes over and over again. Here are my learnings:
tmux new-session \;
Gets you started with a new session. To split it horizontal or vertical use split-window -h or -v subsequently, like that:
tmux new-session \; split-window -v \; split-window -h \;
Creates 3 panes, like this:
------------
| |
|----------|
| | |
------------
To run commands in that panes, just add them with the send-keys 'my-command' command and C-m which executes it:
tmux new-session \; \
send-keys 'tail -f /var/log/monitor.log' C-m \; \
split-window -v \; \
split-window -h \; \
send-keys 'top' C-m \;
And the resulting session should look like that.
------------
| tail |
|----------|
| | top |
------------
Now I tried to again sub-divide the bottom left pane, so switching either back using last-pane, or in more complex windows, with the select-pane -t 1 where 1 is the number of the pane in order created starting with 0.
tmux new-session \; \
send-keys 'tail -f /var/log/monitor.log' C-m \; \
split-window -v \; \
split-window -h \; \
send-keys 'top' C-m \; \
select-pane -t 1 \; \
split-window -v \; \
send-keys 'weechat' C-m \;
Does that. Basicaly knowing your way around with split-window and select-pane is all you need. It's also handy to pass with -p 75 a percentage size of the pane created by split-window to have more control over the size of the panes.
tmux new-session \; \
send-keys 'tail -f /var/log/monitor.log' C-m \; \
split-window -v -p 75 \; \
split-window -h -p 30 \; \
send-keys 'top' C-m \; \
select-pane -t 1 \; \
split-window -v \; \
send-keys 'weechat' C-m \;
Which results in a session looking like that
------------------
| tail |
|----------------|
| | top |
|----------| |
| weechat | |
------------------
You can source different sessions from your .tmux.conf like so:
# initialize sessions
bind S source-file ~/.tmux/session1
bind s source-file ~/.tmux/session2
And then format the sessions as you require:
#session1
new -s SessionName -n WindowName Command
neww -n foo/bar foo
splitw -v -p 50 -t 0 bar
selectw -t 1
selectp -t 0
This would open 2 windows, the second of which would be named foo/bar and would be split vertically in half (50%) with foo running above bar. Focus would be in window 2 (foo/bar), top pane (foo).
You can then start your preferred tmux session (in this case, session1) with PrefixShifts
Use tmuxinator - it allows you to have multiple sessions configured, and you can choose which one to launch at any given time. You can launch commands in particular windows or panes and give titles to windows. Here is an example use with developing Django applications.
Sample config file:
# ~/.tmuxinator/project_name.yml
# you can make as many tabs as you wish...
project_name: Tmuxinator
project_root: ~/code/rails_project
socket_name: foo # Not needed. Remove to use default socket
rvm: 1.9.2#rails_project
pre: sudo /etc/rc.d/mysqld start
tabs:
- editor:
layout: main-vertical
panes:
- vim
- #empty, will just run plain bash
- top
- shell: git pull
- database: rails db
- server: rails s
- logs: tail -f logs/development.log
- console: rails c
- capistrano:
- server: ssh me#myhost
See the README at the above link for a full explanation.
:~$ tmux new-session "tmux source-file ~/session1"
session1
neww
split-window -v 'ipython'
split-window -h
new-window 'mutt'
create an alias in .bashrc
:~$ echo `alias tmux_s1='tmux new-session "tmux source-file ~/session1"'` >>~/.bashrc
:~$ . ~/.bashrc
:~$ tmux_s1
have a look # https://github.com/remiprev/teamocil
you can specify your structure using YAML
windows:
- name: sample-window
splits:
- cmd: vim
- cmd:
- ipython
width: 50
- cmd:
height: 25
This script starts a session with the name "e" and three windows
#!/bin/sh
tmux new-session -s e -n etc -d 'cd /etc; bash -i'
tmux new-window -t e:1 -n home 'cd ~; bash -i'
tmux new-window -t e:2 -n log 'cd /var/log; bash -i'
tmux select-window -t e:1
tmux -2 attach-session -t e
From my "get.all" script, which I invoke each morning to run a bunch of subsequent "get.XXX" jobs to refresh the software that I track. Some of them are auto-quitting. Others require more interaction once the get has finished (like asking to build emacs).
#!/bin/sh
tmux att -t get ||
tmux \
new -s get -n capp \; \
send-keys 'get.capp' C-m \; \
neww -n emacs \; \
send-keys 'get.emacs' C-m \; \
neww -n git \; \
send-keys 'get.git' C-m \; \
neww -n mini \; \
send-keys 'get.mini' C-m \; \
neww -n port \; \
send-keys 'get.port' C-m \; \
neww -n rakudo \; \
send-keys 'get.rakudo' C-m \; \
neww -n neil \; \
send-keys 'get.neil && get.neil2 && exit' C-m \; \
neww -n red \; \
send-keys 'get.red && exit' C-m \; \
neww -n cpan \; \
send-keys 'get.cpan && exit' C-m \; \
selectw -t emacs
If you just want to split screen on 2 panes (say horizontally), you can run this command (no tmux or shell scripts required):
tmux new-session \; split-window -h \;
You screen will look like this:
[ks#localhost ~]$ │[ks#localhost ~]$
│
│
│
│
│
│
│
│
│
│
│
[10] 0:ks#localhost:~* "localhost.localdomain" 19:51 31-янв-16
First i had the approach from #jasonwryan but if you have more then 2 configs, it can get confusing.
So i created an alias function:
tmx () {
tmux "$2" source-file "$HOME/.tmux/$1.conf";
}
In ~/.tmux/ i have multiple sessions for many uses. For example i work for different companies that have each another dev environment. So with the alias function above i can simply call: tmx company1 and load the config i need.
Update: The purpose of "$2" after the tmux command is, that you are able to pass additional tmux args.
And this is how I do it:
#!/bin/bash
function has-session {
tmux has-session -t name_of_my_session 2>/dev/null
}
if has-session ; then
echo "Session already exists"
else
cd /path/to/my/project
tmux new-session -d -s name_of_my_session 'vim'
tmux split-window -h -p 40 start_web_server
tmux split-window -v
tmux attach-session -d -t name_of_my_session
fi
I have one file for each of my project. Also you can group them to have some for work some for hobby projects.
Also you can move it to ~/bin folder, add it to PATH and give tmux_my_awesome_project name. Then you will be able to run it from each place.
You can use tmux hooks to execute commands when a new session is created. Specifically, this is achieved using the session-created hook.
For example, to split the screen three ways with top running in the lower right you can add the following line to your ~/.tmux.conf file:
set-hook -g session-created 'split -h ; split -v top'
The advantage of this method is that you don't have to run tmux in any special way (i.e., shell script or alias) to get the desired result. You can also combine this with tmux new-session -A -s mysession described in this other answer such that the hook commands are only run when you first create the session and not on subsequent attachments.
This feature was added by d15d54c2c back in 2016. The first release to include it is is 2.4.
Try bro, it is a project bootstrapper.
It provides easy apis to interact with tmux.
It looks something like this :
#!/bin/sh
# # project_name/tasks/init.sh
structure project_name
window editor
run 'vim'
window terminal
run 'echo happy coding ...'
focus editor
connect project_name
To launch a project all one needs to do is run following command -
bro start <project_name>
Checkout the screencast below, it shows how to get started with bro and use its tmux api.
https://user-images.githubusercontent.com/8296449/50532502-2cf3e400-0b6e-11e9-84af-c5f77a04e5cc.gif
There is a tmux plugin for this.
Check out tmux-resurrect
Restore tmux environment after system restart.
Tmux is great, except when you have to restart the computer. You lose all the running programs, working directories, pane layouts etc. There are helpful management tools out there, but they require initial configuration and continuous updates as your workflow evolves or you start new projects.
tmux-resurrect saves all the little details from your tmux environment so it can be completely restored after a system restart (or when you feel like it). No configuration is required. You should feel like you never quit tmux.
Or tmux-continuum
Features:
continuous saving of tmux environment
automatic tmux start when computer/server is turned on
automatic restore when tmux is started
This works for me. Creating 5 windows with the given names and auto selecting to the home window.
new -n home
neww -n emacs
neww -n puppet
neww -n haskell
neww -n ruby
selectw -t 1
smux.py allows you to simply list the commands you want in each pane, prefixed with a line containing three dashes.
Here's an example smux file that starts three panes.
---
echo "This is pane 1."
---
cd /tmp
git clone https://github.com/hq6/smux
cd smux
less smux.py
---
man tmux
If you put this in a file called Sample.smux, you can then run the following to launch.
pip3 install smux.py
smux.py Sample.smux
Full disclaimer: I am the author of smux.py.
tmuxp support JSON or YAML session configuration and a python API. A simple tmuxp configuration file to create a new session in YAML syntax is:
session_name: 2-pane-vertical
windows:
- window_name: my test window
panes:
- pwd
- pwd
The plugin tmux-continuum will auto save your tmux session and load it the next time tmux starts, should be easier to setup than some of the custom script solutions here.
Features:
continuous saving of tmux environment
automatic tmux start when computer/server is turned on
automatic restore when tmux is started
To save your session on demand you can use the tmux-resurrect plugin. tmux-resurrect is also required to run tmux-continuum
tmux-resurrect saves all the little details from your tmux environment
so it can be completely restored after a system restart (or when you
feel like it). No configuration is required. You should feel like you
never quit tmux.
To simply open tmux with multiple panes and run some commands, I created the following bash file:
#!/bin/bash
tmux split-window -v -p 30 -c ~/path/to/folder1
tmux split-window -h -p 66 -c ~/path/to/folder2
tmux split-window -h -p 50 'mongod'
Run the bash file to get the following:
-----------------------------------
| |
|---------------------------------|
| folder1 | folder2 | mongod |
-----------------------------------
I've create this script. It does not need tmuxinator, ruby or others. It is just a bash script, configurable:
A file named config should contains something like this:
combo=()
combo+=('logs' 'cd /var/log; clear; pwd')
combo+=('home' 'cd ~; clear; pwd')
and the bash code should be:
#!/bin/bash
if [ -r config ]; then
echo ""
echo "Loading custom file"
. config
else
. config.dist
fi
tmux start-server
window=0
windownumber=-1
for i in "${combo[#]}"; do
if [ $((window%2)) == 0 ]; then
name=${i}
((windownumber++))
else
command=${i}
fi
if [ ${combo[0]} == "${i}" ]; then
tmux new-session -d -s StarTmux -n "${name}"
else
if [ $((window%2)) == 0 ]; then
tmux new-window -tStarTmux:$windownumber -n "${name}"
fi
fi
if [ $((window%2)) == 1 ]; then
tmux send-keys -tStarTmux:$windownumber "${command}" C-m
fi
((window++))
done
tmux select-window -tStarTmux:0
tmux attach-session -d -tStarTmux
I just tried using all the ideas on this page and I didn't like any of them. I just wanted a solution that started tmux with a specific set of windows when my terminal opened. I also wanted it to be idempotent, i.e. opening a new terminal window takes over the tmux session from the previous one.
The above solutions often tend to open multiple tmux sessions and I want just one. First, I added this to my ~/.bash_profile:
tmux start-server
if [[ -z "$TMUX" ]]
then
exec tmux attach -d -t default
fi
then I added the following to my ~/.tmux.conf:
new -s default -n emacs /usr/local/bin/emacs
neww -n shell /usr/local/bin/bash
neww -n shell /usr/local/bin/bash
selectw -t 1
now every time I start a terminal or start tmux or whatever, I either reattach to my existing desired setup (the session named default), or create a new session with that setup.
I know my solution is quite similar to others, but I couldn't find any that had exactly what I was looking for :
I want to open titled windows at specific paths
I want to have as many windows and panes as I want
I want to run arbitrary commands in each of the panes, persisted in history
I want to focus the window I want at the end of the script
I want being able to spawn this very quickly
I want it to be easy to maintain
For my project named johndoe, I create a johndoe.conf file which is essentially a bash script, somewhere in my configs (~/.config/tmux/tmux-sessions/johndoe.conf).
This file is simple to maintain because it does not have a myriad of \ everywhere like I saw in other answers:
# Create a new session named johndoe, with a first window named Main
# at the specified starting path.
# The -d is for detached mode, which allows me to continue defining the rest of the session
# before attaching to it. Without -d, tmux would open the client right away and
# ignore the rest of the session definition
tmux new -d -s johndoe -n 'Main' -c ~/dev/own/johndoe
# Simulate the user entering some docker command in the first window available
# in the target session named (-t) johndoe
tmux send -t johndoe 'docker compose up -d' Enter
# Create a new window in the target session, with the title 'UI run'
tmux neww -t pers -n 'UI run' -c ~/dev/own/johndoe/front-end
# Simulate user entering a command to the first pane
tmux send -t pers:'UI run.0' 'git status --short' Enter
# Split this window horizontally
tmux split-window -t pers:'UI run' -h -c ~/dev/own/johndoe/front-end
# Simulate user entering a command to the second pane in this window
tmux send -t pers:'UI run.1' 'npm run dev' Enter
tmux neww -t johndoe -n 'API run' -c ~/dev/own/johndoe/back-end/Api
tmux send -t johndoe:'API run' 'dotnet run --no-build' Enter
# Focus the first window
tmux select-window -t johndoe:'Main'
# Attach the current terminal to the only session available
# (you might want to add "-t johndoe" here if you need several sessions running in parallel)
tmux a -d
I create a bash/zsh alias to source this session configuration :
alias tmuxjohndoe='. ~/.config/tmux/tmux-sessions/johndoe.conf'
I spend just the right amount of debugging time figuring out that I need to give execution permissions to the johndoe.conf file :-)
Now from anywhere, I just run tmuxjohndoe !
You should specify it in your tmux config file (~/.tmux.conf), for example:
new mocp
neww mutt
new -d
neww
neww
(opens one session with 2 windows with mocp launched in first and mutt in second, and another detached session with 3 empty windows).

Resources