How can I make TMUX be active whenever I start a new shell session? - shell

Instead of having to type tmux every time, how could I have tmux always be used for new session windows?
So if I have no terminal windows open and then I open one, how can that first session be in tmux?
Seems like a .bashrc sort of thing perhaps?

warning this can now 'corrupt' (make it unable to open a terminal window - which is not good!) your Ubuntu logins. Use with extreme caution and make sure you have a second admin account on the computer that you can log into in case you have the same problems I did. See my other answer for more details and a different approach.
Given that warning, the simplest solution can be to append the tmux invocation to the end of your .bashrc, e.g.
alias g="grep"
alias ls="ls --color=auto"
# ...other stuff...
if [[ ! $TERM =~ screen ]]; then
exec tmux
fi
Note that the exec means that the bash process which starts when you open the terminal is replaced by tmux, so Ctrl-B D (i.e. disconnect from tmux) actually closes the window, instead of returning to the original bash process, which is probably the behaviour you want?
Also, the if statement is required (it detects if the current bash window is in a tmux process already) otherwise each time you start tmux, the contained bash process will attempt to start its own tmux session, leading to an infinite number of nested tmuxen which can be, err, quite annoying (that said, it looks cool).
However, there is a very small risk this can make bash behave in a way that other programs don't expect, since running bash can possibly cause it to turn into a tmux process, so it might be better to modify how you start your terminal emulator.
I use a small executable shell script ~/bin/terminal (with ~/bin in $PATH, so it is found automatically) that looks a bit like:
#!/bin/sh
exec gnome-terminal -e tmux
(I don't use gnome-terminal, so you might have to remove the exec, I'm not sure.)
Now whenever you run the terminal scipt you have a terminal with tmux. You can add this to your menu/desktop/keyboard shortcuts to replace the default terminal.
(This approach also allows you to more easily customise other things about the terminal emulator later, if you ever desire.)

My original, accepted answer, stopped working on my Ubuntu14 system after a recent upgrade.
Using either
[ -z "$TMUX" ] && command -v tmux > /dev/null && TERM=xterm-256color && exec tmux
or
[ $TERM != "screen" ] && TERM=xterm-256color && exec tmux
would stop me from being able to even login. I was only able to resolve this due to having a second admin login on the computer.
The fix for me on Ubuntu (and in osx too) was to change my terminal program to actually run tmux instead, i.e.
I still have
[ `uname -s` != Linux ] && exec tmux
as my last .bashrc line but that his only for my Mac OSX systems now.

If you want to have a single tmux session, put the following in your ~/.bashrc for bash or ~/.zshrc for zsh:
tmux attach &> /dev/null
if [[ ! $TERM =~ screen ]]; then
exec tmux
fi
The tmux attach line is to make sure if there is a session it attaches to and if there was no session you will not get the warning about "no session".

For me, I would love my tmux to be started every time I shell onto my remote machine, and when I detach or exit from tmux, the connection should be closed automatically. After digging into this issue for a while, the following code does exactly what I want and is believed to be the most optimized to the best of my knowledge.
[ -z "$TMUX" ] && { tmux attach || exec tmux new-session && exit;}
Note this line should be the first line in you bashrc file to make sure it is loaded first. We can't put an "exec" call in front of "tmux attach" because after the exec replaces the bash process with the tmux one, the connection will be closed even if there are no sessions to attach to. Therefore we need an "exit" call to terminate the connection after we detach or exit from the attached sessions. But putting an "exec" in front the new-session command is fine as that's the last command to be executed.

Append following line of code to the end of .bashrc,
[[ $TERM != "screen" ]] && exec tmux

I just made it a keyboard shortcut (in Linux Mint not Ubuntu; so I'm not sure if it is this easy)...
It might be hard to see, but the custom shortcut is gnome-terminal --window --maximize -e tmux. This starts a new gnome-terminal window maximized and then executes tmux for you.
I additionally have another custom shortcut that starts a "normal" gnome-terminal maximized (it's the same without the -e tmux).
I feel this is the best way because you can start whatever terminal whatever way you want and is the most customizable way.

I started with this https://wiki.archlinux.org/index.php/Tmux#Bash and enhanced it to reclaim detached sessions and make new ones if all sessions were already attached
# .bashrc
case $- in
*i*)
if command -v tmux>/dev/null; then
if [[ ! $TERM =~ screen ]] && [[ -z $TMUX ]]; then
if tmux ls 2> /dev/null | grep -q -v attached; then
exec tmux attach -t $(tmux ls 2> /dev/null | grep -v attached | head -1 | cut -d : -f 1)
else
exec tmux
fi
fi
fi
;;
esac

To enable tmux for login and ssh sessions, you can add this to the end of your .bashrc:
# enable tmux at login
PNAME="$(ps -o comm= $PPID)";
if [ $PNAME == "login" ] || [ $PNAME == "sshd" ] ; then
exec tmux
fi
This script looks for the parent process of the bash shell. If bash was started from logging in or from ssh, it will execute tmux. If you want this to work with a GUI terminal, you can add that in there as well. For example, if you want to start tmux automatically when you start Ubuntu's standard gnome-terminal, you would use this:
PNAME="$(ps -o comm= $PPID)";
if [ $PNAME == "login" ] || [ $PNAME == "sshd" ] || [ $PNAME == "gnome-terminal" ] ; then
exec tmux
fi
I've tested the above on Live Ubuntu Desktop and I was able to log in afterwards. This should not break the GUI login unless it invokes the login command to log in. I am not aware of a linux GUI that does this.

Within xfce4 (I'm running Fedora 24 XFCE spin, it's great), I've found the simplest solution is to edit panel shortcuts to so they run:
xfce4-terminal -e tmux
This same command can be used to replace the Keyboard Application Shortcut.
I had previously inserted an if statement into my .bashrc, but it caused login to fail (loop back to the login box whenever a correct password was entered).
The command for Thunar's Open Terminal Here command differs slightly. To change that goto:
Thunar > Edit > Configure Custom Actions... > Open Terminal Here > Edit button, and replace:
exo-open --working-directory %f --launch TerminalEmulator
with: xfce4-terminal --working-directory %f -e tmux

A one-liner that also makes sure the terminal type is set correctly for 256 colors:
[ -z "$TMUX" ] && export TERM=xterm-256color && exec tmux

How about adding
# If not running interactively, do not do anything
[[ $- != *i* ]] && return
[[ -z "$TMUX" ]] && exec tmux
to your .bashrc. It also works for zsh.
Taken from https://wiki.archlinux.org/index.php/Tmux#Start_tmux_with_default_session_layout

Related

How to completely close terminal (Ubuntu bash) from TMUX session?

Can someone please help as how can I close the bash terminal completely from inside of TMUX session (closing TMUX session as well)?
Below code is from my .bashrc where TMUX session starts as soon as terminal launches
if command -v tmux &> /dev/null && [ -z "$TMUX" ]; then tmux attach -t default -c "$HOME" || tmux new -s default -c "$HOME" fi
Thanks!
You can use tmux detach -P to both detach and send HUP to the parent process, which should make it exit as well.

Run command in new background tmux window and wait for process to finish

I'm trying to use tmux in a script, so that it runs a command that takes some time (let's say 'ping -c 5 8.8.8.8', for example) in a new hidden pane, while blocking the current script itself until the ping ends.
By "hidden pane", I mean running the command in a new pane that would be sent in background, and is still accessible by switching panes in order to monitor and/or interact with it (not necessarily ping).
(cf. EDIT)
Here is some pseudo bash code to show more clearly what I'm trying to do:
echo "Waiting for ping to finish..."
echo "Ctrl-b + p to switch pane and see running process"
tmux new-window -d 'ping -c 5 8.8.8.8' # run command in new "background" window
tmux wait-for # display "Done!" only when ping command has finished
echo "Done!"
I know the tmux commands here don't really have any sense like this, but this is just to illustrate.
I've looked at different solutions in order to either send a command in background, or wait until a process has finished in an other pane, but I still haven't found a way to do both correctly.
EDIT
Thanks to Nicholas Marriott for pointing out the -d option exists when creating a new window to avoid switching to it automatically. Now the only issue is to block the main script until the command ends.
I tried the following, hoping it would work, but it doesn't either (the script doesn't resume).
tmux new-window -d 'ping -c 5 8.8.8.8; tmux wait -S ping' &
tmux wait $!
Maybe there is a way by playing with processes (using fg,bg...), but I still haven't figured it out.
Similar questions:
[1] Make tmux block until programs complete
[2] Bash - executing blocking scripts in tmux
[3] How do you hide a tmux pane
[4] how to wait for first command to finish?
You can use wait-for but you need to give it a channel and signal that channel when your process is done, something like:
tmux neww -d 'ping blah; tmux wait -S ping'
tmux wait ping
echo done
If you think you might run the script several times in parallel, I suggest making a channel name using mktemp or similar (and removing the file when wait-for returns).
wait-for can't automatically wait for stuff like pane or windows exiting, silence in a pane, and so on, but I would like to see that implemented at some point.
The other answers are only working if you're already within a tmux session.
But if you are outside of it you've to use something like this:
tmux new-session -d 'vi /etc/passwd' \; split-window -d 'vi /etc/group' \; attach
If you want to call this within a script you should check whether or not "$TMUX" is set. (Or just unset to force a nested tmux window).
#!/bin/sh
export com1="vi /etc/passwd"
export com2="vi /etc/group"
if [ -z $TMUX ]
then
export doNewSession="new-session -d 'exit 0'"
else
export doNewSession=""
fi
tmux $doNewSession \; split-window -d $com1 \; split-window -d $com2 \; attach;
[ -z $TMUX ] && exit 0
My solution was to make a named pipe and then wait for input using read:
#!/bin/sh
rm -f /wait
mkfifo /wait
tmux new-window -d '/bin/sh -c "ping -c 5 8.8.8.8; echo . > /wait"'
read -t 10 WAIT <>/wait
[ -z "$WAIT" ] &&
echo 'The operation failed to complete within 10 seconds.' ||
echo 'Operation completed successfully.'
I like this approach because you can set a timeout and, if you wanted, you could extend this further with other tmux controls to kill the ongoing process if it doesn't end the way you want.

How to exit tmux without leaving my terminal

I'm using tmux on bash, and letting it start automatically from .bashrc. Sometimes I want it disabled, and I should edit my .bashrc to do so. Editting a file everytime I disable tmux is quite troublesome, and I think the easiest way to do the same thing is exiting tmux without leaving terminal. Can I do that?
When I type exit, bash and terminal close. I tried exec bash , but it just restarted bash inside tmux.
I start tmux from code below, according to https://wiki.archlinux.org/index.php/tmux#Bash.
if [[ $DISPLAY ]]; then
# If not running interactively, do not do anything
[[ $- != *i* ]] && return
[[ -z "$TMUX" ]] && exec tmux
fi
If I just run tmux in code above instead of exec tmux, I can achieve my goal. But I don't like that, because I don't understand why the code uses exec tmux rather than tmux and don't wanna change it rashly, and when I run tmux I shoud type exit or C-d twice in order to exit terminal.
(Note: this question should really be on unix.stackexchange.com). One simple solution is to replace the line
[[ -z "$TMUX" ]] && exec tmux
with
[[ -z "$TMUX" ]] && { tmux; [ ! -f ~/dontdie ] && exit || rm ~/dontdie; }
This runs tmux as before, but when it exits goes on to test for the existence of a file, ~/dontdie. If the file does not exist the && exit is done and the terminal closes as before. If, however, you create the file before leaving tmux, then you will do the || rm ... part, which removes the file, and continues through the rest of the .bashrc file, leaving you in the bash shell.
So, to stay in the terminal, from the tmux window you type the commands:
touch ~/dontdie; exit
instead of just exit, and you will exit tmux and continue in bash.
To make it easier you can add a binding in ~/.tmux.conf to a key, such as X:
bind-key X send-keys 'touch ~/dontdie; exit' Enter
Then you simply type your prefix character, Control-B by default, then X to create the file and exit tmux.

How can I run complicated commands on establishing a mosh connection?

With ssh I can do this:
ssh REMOTE -t 'tmux a || tmux'
With mosh the best I can do is this:
mosh REMOTE -- tmux a
But this won't work:
mosh REMOTE -- tmux a || tmux
Neither does this: (It doesn't matter whether it's single quote or double quote, I tried both)
mosh REMOTE -- 'tmux a || tmux'
So, my question is: How am I supposed to do this job?
Well, it seems that I have to explicitly use a shell to execute command:
mosh REMOTE -- sh -c 'tmux a || tmux'
EDIT
Instead of doing tmux a || tmux, a better way is add new-session to ~/.tmux.conf and just run tmux. That would make things much easier. I can do things like this now:
mosh REMOTE -- tmux
Awesome!
There might be more complicated commands than the examples given above. I wanted to make a command that reattaches to an existing tmux session if one exists but is not already attached, or a new one if there are none available.
Looking at this example, I would have done something like this:
function tmosh() {
mosh $1 -- (tmux ls | grep -vq attached && tmux at -t $( tmux ls | grep -vm1 attached | cut -d: -f1 ) ) || tmux new
}
But that doesn't work, per the original question above.
My solution so far is to have a wrapper script on the host servers:
tmux-reattach-if-exists
which consists simply of:
(tmux ls | grep -vq attached && tmux at -t $( tmux ls | grep -vm1 attached | cut -d: -f1 )) || tmux new
Then I used called the script on the client from mosh like this:
function tmosh() {
mosh $1 -- tmux-reattach-if-exists
}
If there's a solution that can do this via .tmux.conf directly that would be great but I couldn't seem to work that out.
Put this at the end of your .bashrc
s1="`ps $PPID|grep mosh|awk '{print $5}'`"
s2=mosh-server
if [[ "$s1" == "$s2" ]]; then source .moshrc; fi
If called by mosh-server, bash will execute whatever it finds in $HOME/.moshrc - so just put your commands in a file named .moshrc in your home directory.
Because mosh invokes a login shell, you should have a line
source .bashrc
in your .bash_profile, or put the above lines in .bash_profile.

How do I get the command history in a screen session using Bash?

If I start a screen session with screen -dmS name, how would I access the command history of that screen session with a script?
Using the ↑, the last executed command appears, even in screen.
I use the default bash shell on my system and so might not work with other shells.
this is what I have in my ~/.screenrc file so that each new screen window gets its own command history:
Default Screen Windows With Own Command History
To open a set of default screen windows, each with their own command history file, you could add the following to the ~/.screenrc file:
screen -t "window 0" 0 bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
screen -t "window 1" 1 bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
screen -t "window 2" bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
Ensure New Windows Get Their Own Command History
The default screen settings mean that you create a new window using Ctrl+a c or Ctrl+a Ctrl+c. However, with just the above in your ~/.screenrc file, these will use the default ~/.bash_history file. To fix this, we will overwrite the key bindings for creating new windows. Add this to your ~/.screenrc file:
bind c screen bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
bind ^C screen bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
Now whenever you create a new screen window, it's actually launching a bash shell, setting the HISTFILE environmental variable to something that includes the current screen window's number ($WINDOW).
Command history files will be shared between screen sessions with the same window numbers.
Write Commands to $HISTFILE on Execution
As is normal bash behavior, the history is only written to the $HISTFILE file by upon exiting the shell/screen window. However, if you want commands to be written to the history files after the command is executed, and thus available immediately to other screen sessions with the same window number, you could add something like this to your ~/.bashrc file:
export PROMPT_COMMAND="history -a; history -c; history -r; ${PROMPT_COMMAND}"
screen doesn't maintain a history of the commands you type. Your shell may or may not keep a history. Since you appear to use bash, you can use the history command.
screen does appear to have a crude approximation of a history search (it merely searches the scrollback buffer for a command line. See the screen man page under the "history" command (bound to C-a { by default).
#technosaurus is right. $HISTFILE is written when bash exits, so you could exit one bash session, and start a new one, and the history should have been preserved through the file.
But I think there is a better way to solve your problem. The bash manual includes a description of the history built-in command. It allows you to save this history with history -w [filename] and read the history with history -r [filename]. If you don't provide a filename, it will use $HISTFILE.
So I would propose that you save the history inside your screen session to a specific file (or to your default $HISTFILE if you want). Then read the history file in the other bash session you want to access the history from. This way you don't have to exit the original bash session.
When you exit a terminal (or shell) the shell writes its history to $HISTFILE, so to get its history in another terminal you can type exit in the terminal you want the history of and it will get written.
cat $HISTFILE
#or tac, less, $EDITOR, ... depending on how you want to "access" it
use this:
screen -L
with capital L
it will store a copy of terminal input and output to a file named screenlog.0
or if you use -S to name it, the log file gets the screen name
I put the next lines into my .bashrc:
case "$TERM" in
screen)
declare SCREEN_NAME=$(echo $STY | sed -nr 's/[^.]*\.(.*)/\1/p')
if [[ $SCREEN_NAME ]]; then
HISTFILE="${HISTFILE}.${SCREEN_NAME}.${WINDOW}"
declare -p HISTFILE
fi
unset SCREEN_NAME
;;
*)
;;
esac
My default .bashrc has this 'case' basically with 'xterm*|rxvt*)' value, so I only added my 'screen' part into it. If you have not this 'case', you can use the next instead of it:
if [[ $TERM == screen ]]; then
declare SCREEN_NAME=$(echo $STY | sed -nr 's/[^.]*\.(.*)/\1/p')
if [[ $SCREEN_NAME ]]; then
HISTFILE="${HISTFILE}.${SCREEN_NAME}.${WINDOW}"
declare -p HISTFILE
fi
unset SCREEN_NAME
fi
And after I have an own bash_history for all window of my all screen.
Note: this not work in chroot!
history will show all the history command.

Resources