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

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

Related

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.

Killing process of a user before deleting

I'm trying to put an if else statement in my script that if deleting of a user is going on, it should automatically check if any process is running by any user and it should be able to kill all those and then delete user.
if ps -u $name
then
sudo pkill -u $name
sudo userdel $name -r -f
echo "The user - $name - has been deleted!"
done < "$fname" # double-quote fname
else
sudo userdel $name -r -f
echo "The user - $name - has been deleted!"
done < "$fname" # double-quote fname
fi
I know I'm failing to put the ps command in action, I know how to list user processes and kill them all, but failing to automatically put that into script to check if processes running, then do this else do this.. 'if processes running by user' is my problem.
man ps mentions the U flag to ps to filter by effective user ID. -o defines the output, so you can just get the process IDs. Since ps also prints "PID" as column header, you need to discard the first line of the ps output.
for pid in `ps U "$name" -o pid | head -n -1`
do
sudo kill -KILL $pid
done
should do the trick.
Rather than looping, you can read all running PIDs for user $name into an array. Test if there is anything in the array, then if so, kill all procs without looping. It's just another approach to the problem. Example:
!#/bin/bash
uprocs=( $(pgrep -u $name ) ) # fill array with PIDs owned by $name
[ "${#uprocs[#]}" -gt 0 ] && # if procs exist, kill them all
sudo kill "${uprocs[#]}"
sudo userdel $name -r -f # delete the user
unset uprocs # unset array
If you wanted to use a loop, you could then also do:
for i in ${uprocs[#]}; do
sudo kill $i
done
Also, you should move away from placing sudo inside scripts (there are times it is correct), but generally, if you are administering the system with a script, you should run the script as root. Either by:
sudo ./scriptname.sh
or by using the super user command:
su -c ./scriptname.sh
That way the script is run with UID=0 and you don't have to precede all commands by sudo within the script. There are advantages/disadvantages either way, but generally you want to avoid filling scripts with sudo foo.

not able to use ssh within a shell script

I have this shell script which ssh to other server, find few specific files(.seq files older than 50 mnts) and writes their name to another file.
#! /usr/bin/bash
while read line
do
#echo $line
if [[ $line =~ ^# ]];
then
#echo $line;
continue;
else
serverIP=`echo $line|cut -d',' -f1`
userID=`echo $line|cut -d',' -f2`
fi
done < sftp.conf
sshpass -p red32hat ssh $userID#$serverIP
cd ./perl
for files in `find -name "*.seq" -mmin +50`
do
#sshpass -p red32hat scp *.seq root#rinacac-test:/root/perl
echo $files>>abcde.txt
done
exit;
#EOF
Now problem is that when I run it.. neither it writes to abcde.txt file nor it is exiting from the remote server. when I manually execute the exit command...it exists saying "perl no such file or directory"... while I have perl sub directory in my home directory..
other thing is when I run the for loop portion of the script on the 2nd server(by directly logging into it) it is working fine and writing to abcde.txt filr...please help...
ssh takes commands either on standard input or as the last parameter. You can therefore do this (very dynamic but tricky to get the expansions right):
ssh user#host <<EOF
some
custom
commands
EOF
or this (less dynamic but can take simple parameters without escaping):
scp my_script.sh user#host:
ssh user#host './my_script.sh'

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.

GNU Screen: How can I create a screen in the background if it doesn't exist?

"screen -R -D -S test" will create a session named test if it doesn't exist, or connect to it if it does
"screen -d -m -S test" will create a new detached session named test, whether it exists or not, possibly leading to multiple sessions named test:
There are several suitable screens on:
9705.test (06/18/2012 06:42:58 PM) (Detached)
9639.test (06/18/2012 06:42:57 PM) (Detached)
How can I create a detached session named test, but only if one doesn't already exist?
I believe you're looking for the -d -R combination:
screen -d -R -S test
From man screen:
-d -R Reattach a session and if necessary detach or even create it
first
EDIT
If you just want to create a background screen only if it doesn't exist, a little shell function in your ~/.bashrc or ~/.zshrc will work:
function bgsc {
if screen -list | awk '{print $1}' | grep -q "$1$"; then
echo "screen $1 already exists" > &2
else
screen -d -m -S $1
fi
}
Then just call bgsc test.

Resources