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

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.

Related

How to create a Tmux and Xpanes session into one splited window

I'm running a bash script that creates an Xpanes and Tmux sessions based on list of hosts (SSH to each host in the list with some extra arguments).
The issue now is that every xpanes command is created in a different window instead of one window with split panes.
If I'm running the same command separately (not via the script) it is work as excepted.
What am i missing here? or how can i attached all those windows to one splitted window?
Here is my code:
#!/bin/bash
#validate if TMUX session is running
if ! [ -n "$TMUX" ]; then
echo "This must be run from a Tmux session, please create a Tmux session first"
exit 0
fi
set +x
for srv in `cat hostsList.txt`; do \
echo "[$srv]"
RAND=`echo $srv | md5sum | awk '{print $1}'`
cp ~/.ssh/id_rsa{,_$RAND}
cp ~/.ssh/id_rsa{.pub,_$RAND.pub}
xpanes -c "{}" "<my SSH command>" ;
rm ~/.ssh/id_rsa_$RAND ~/.ssh/id_rsa_${RAND}.pub &
done

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 write a shell script that starts tmux session, and then runs a ruby script

I want to write a shell script that does this:
First, create a tmux session
Second, run a ruby script called "run.rb" INSIDE the tmux session
In pseudo-code, what I want to do:
tmux new -s my_session
ruby run.rb # NOTE: I want this to run inside the my_session tmux session.
tmux detach
How do I do this? (More posts I read, more confusing it gets.)
#!/bin/bash
tmux new-session -d -s my_session 'ruby run.rb'
Create a file named my_script.sh and give it the above contents.
Make the file executable by running:
chmod 755 my_script.sh
or
chmod +x my_script.sh
Then run the shell script:
./my_script.sh
Making the shell script executable
When you perform the chmod 755 filename command you allow everyone to read and execute the file, and the file owner is allowed to write to the file as well. You may need this for Perl and other scripts that should be run via a webserver. If you apply 755 to a directory, it means that everyone can go to it and get its file listing.
These permissions are usually translated into textual representation of rwxr-xr-x.
You can alternatively use chmod +x file_name on a file to make it executable.
K M Rakibul Islam's updated code contains an unnecessary detach command at the end which causes an error message "no client found" (my_session has already been detached and thus is not in scope so tmux cannot understand which session you want to detach). The correct code should be:
#!/bin/bash
tmux new-session -d -s my_session 'ruby run.rb'
With some experimenting, I figured out how to control tmux via shell script.
tmux new-session -d -s htop-session 'htop'; # start new detached tmux session, run htop
tmux split-window; # split the detached tmux session
tmux send 'htop -t' ENTER; # send 2nd command 'htop -t' to 2nd pane. I believe there's a `--target` option to target specific pane.
tmux a; # open (attach) tmux session.
The above splits the tmux session into two window, and runs htop in both.
To answer original question, you can run a ruby script and not detached the tmux session with command below:
tmux new-session -s ruby_session 'ruby run.rb'; # open tmux session and run ruby script.
You could use teamocil to do this easily. You could just create a YAML file:
windows:
- name: rubysession
root: ~
layout: tiled
panes:
- ruby run.rb; tmux detach
If you named it 'rubysession.yml' then run:
teamocil rubysession
And that would work perfectly for your purpose and require no hacks. Also teamocil is awesome for loads of other uses!
If you want to keep your tmux session alive after starting some commands, a possible solution is to start a bash with an init file:
tmux new -d -s mysession "bash --init-file foo.script"
where foo.script would contain your commands. Alternatively, you can feed the command to the shell directly from the command line:
tmux new -d -s mysession2 "bash --init-file <(echo ruby run.rb)"
Note that --init-file was meant for reading system wide initialization files like /etc/bash.bashrc so you might want to 'source' these in your script.
I am not sure if this is still interesting for you, but I like to give you an answer / hint: in case you want, for example, start multiple tmux sessions by shell script and execute some command, you can do it like follow:
# just for test and show case
mkdir test_1 test_2
echo "current tmux sessions"
tmux ls
echo "kill all tmux sessions"
tmux kill-server
declare -a directories=("test_1" "test_2")
for i in "${directories[#]}"
do
cd ${i}
pwd
tmux new -d -s ${i} "ls -la"
cd ..
done
For the demonstration, the script will create a folder test_1 and test_2. After that I have defined an array with the two folders and run through the two folders and start a tmux session with the current folder name and execute the command "ls -la".
If you like to run through all sub directories in your current directory, please replace "for i in "${directories[#]}" with "for f in *; do". Here is an example that also exclude symbolic folders:
echo "current tmux sessions"
tmux ls
echo "kill all tmux sessions"
tmux kill-server dependencies
for f in *; do
if [[ -d "$f" && ! -L "$f" ]]; then
cd ${f}
pwd
tmux new -d -s ${i} "ls -la"
cd ..
fi
done
Here is a link to the gist file: https://gist.github.com/AICDEV/cf1497793bb1c27cb9b94d56c209ad6f

copying last bash command into clipboard

I realized that I've spent more time on this issue than necessary, hence the question.
Sometimes I need to save the last typed shell command into the clipboard.
I can do something like this:
echo !! | xsel --clipboard
Which works successfully.
But when I try to alias the above command:
alias echoxs='echo !! | xsel --clipboard'
Things do not work as expected. In particular, the clipboard contents become literally !!. Obviously, I am missing something about how bash preprocesses commands and aliases. My hope was that an alias, as is intuitive, would be something like a C macro, and that typing the alias would be equivalent to typing its target.
I've tried other approaches and none seem to work. Using HISTFILE inside a script does not work because either commands are cached by the shell session and not immediately written to the file, or multiple terminals mess with the file such that the last command in the file is not always reliably the last command in the current session.
alias='history 1 | xsel --clipboard'
Almost works, except all fails when attempting to modify (eg, cut or sed) the output of history because it is a built-in command.
Is the a way to get the shell's last command through sane stdout?
I'm not sure to understand what you said about "failing when attempting to modify the output of history", so I hope my solution will suit you. I'm using fc to get the last command:
fc -ln -1 | xsel --clipboard
Here are the meaning of the options:
l is to use the standard output
n is to hide the command history number
-1 is to get the last command from the history
Client: pass the option -XY to the ssh command to enable (trusted) X11 forwarding for this session:
ssh -XY USER#IP
Server: check /etc/ssh/sshd_config to make sure X11 forwarding is enabled on server
X11Forwarding yes
yum install xclip -y
echo `hostname -I` `hostname` >> /etc/hosts
echo "alias cplastcmd='history 2 | cut -c 8- | head -n 1 | xclip -selection clipboard'" >> ~/.bashrc
Restart bash and type cplastcmd to copy last bash command to clipboard via X11.

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

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

Resources