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

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.

Related

Bind keyboard shortcut to bash function to interrupt sleep?

I have a collection of .jpg background images that I want to use as backgrounds for my i3-gaps desktop. Currently, I have these two lines in my i3 config file for my wallpapers.
exec --no-startup-id randomwallpaper
bindsym $mod+i exec --no-startup-id feh --bg-scale --randomize /home/user/Pictures/bgart/*.jpg
This is my randomwallpaper script. It uses feh to set an image and wal to create a colorscheme based off of it.
#!/bin/bash
cd /home/user/Pictures/bgart
for file in $(ls); do
shopt -s nullglob
for i in *.jpg; do
feh --bg-scale --randomize /home/user/Pictures/bgart/$i
wal -q -i $i
sleep 300
done
done
On startup, randomwallpaper starts and every 5 minutes the wallpaper changes along with the colorscheme. However, I can also press Win+I to manually switch to a random wallpaper. Is it possible to add a trigger of some sort to interrupt the cycle? Maybe have the script as a function and add a key to call it? That way, I can have the above script running and if I get bored of the wallpaper, I can switch to another with Win+I and still have it change 5 minutes later.
Unless you modified your bash with a built-in sleep, you can kill the sleep command. The script will then proceed to the next command as if sleep terminated normally. The only tricky part is to identify the correct process to kill. Here I assume that there is only one randomwallpaper process running on your system:
exec --no-startup-id randomwallpaper
bindsym $mod+i exec --no-startup-id sh -c 'pkill -P $(pgrep -ox randomwallpaper) sleep'
By the way; Your script could use some improvement. For instance, the variable file is unused and --randomize has no effect since you only supply one picture.
#!/bin/bash
shopt -s nullglob
cd /home/user/Pictures/bgart
while true; do
i=$(shuf -en1 ./*.jpg)
if [ -n "$i" ]; then
feh --bg-scale "$i"
wal -q -i "$i"
fi
sleep 300
done

How to detect if tmux is attached to a session in bash?

I have a .sh file which create a new session for tmux and add some windows, the file should be used only when no session exist. For instance:
tmux new-session -A -s `ax` -n ui -d
# add windows and other magic here...
I want prevent creating a session with the same name and recreating the windows in case the .sh file is accidentally re executed and the session is running.
Basically what I need is:
If a tmux session ax does not exist with that session name, create that session. If
I am not attached to a tmux session, attach to that session.
I would like to know how to detect if a tmux session exist and if tmux is attached to it, in this example ax is running and preventing the execution of the .sh script or if the session does not exit I want to re-execute the .sh script.
Currently I was thinking to use:
tmux ls | grep attached
I would like to know if you know a better way.
It's a little hard to understand what you mean. I'm interpreting it as
if a tmux session does not exist with that session name, create it. If I am not attached to a tmux session, attach to that session name.
If this is wrong, please comment.
I have similar functionality in my scripts. All I do is
tmuxstart() {
tmux ls | grep "sess" && { tmux a -t sess; return 0; }
#rest of tmux script to create session named "sess"
tmux a -t sess
}
If the session named "sess" exists, then I execute the next 2 grouped commands on the line (attach to it and exit the function).
Note that I do not have to check if I'm already attached to the function. tmux does this automatically. If you try to attach to a tmux session while in a session, it will respond
sessions should be nested with care, unset $TMUX to force
and not recursively attach. Tmux is smart enough to keep us from shooting ourselves in the foot.
You can use $TMUX to detect if already attached, my code is:
if [ ! "$TMUX" ]; then
tmux attach -t main || tmux new -s main
fi
I use this in my ~/.bashrc so if no sessions exist, start them & detach from all but ipfs session.
IPFS_SESSION=$(tmux attach-session -t ipfs 2>&1)
if [ "$IPFS_SESSION" == "sessions should be nested *" ]; then
unset TMUX
else
if ! tmux has-session -t sql; then tmux new-session -d -s sql; fi
if ! tmux has-session -t tmp; then tmux new-session -d -s tmp; fi
if ! tmux has-session -t ytdl; then tmux new-session -d -s ytdl; fi
if ! tmux has-session -t ipfs; then tmux new-session -s ipfs; fi
fi

How can I find which program is running on a specific tmux session?

On a remote machine, I have a tmux session with ID selu, and I am running a python program
python test.py that runs a C++ program main.cpp with different parameters several times. I want to learn the parameters of C++ program running at the moment in that session. How can I do this?
You can rely on the run-shell command. Here is an example of how to grep the current active process of the active pane.
bind-key M-v run-shell 'T=$(tmux display -p "#{pane_tty}" | sed "s=/dev/=="); if pgrep -t$T "fzf|vim|elvish" &> /dev/null; then tmux send-key M-v; elif pgrep -t$T "gdb" &> /dev/null; then tmux send-key PageUp; else tmux copy-mode; fi'
You can also add -t <your session> to run-shell in order to specify the needed session.

How to create panes that's won't be destroyed when I quit the command that it's running inside?

I would like to create panes in tmux like so :
$ tmux new-session -d -s mysession "while true; do sleep 1; ls; done"
$ tmux split-window -h "while true; do sleep 1; ls -l; done"
Running it this way, when I cancel the command that is running in the pane (window) I close it immediately. How to avoid this behaviour ?
Use the remain-on-exit window option to mark the window (and any panes it contains) to remain after the command it runs exits.
tmux new-session -d -s mysession "while true; do sleep 1; ls; done"
tmux set-option -t mysession:0 remain-on-exit
When you kill the command, the window will remain, and [the pane] will be labeled "Pane is dead". To restart the same command, issue respawn-window. To start a new command in the window (say, an interactive bash session), issue respawn-window bash.
respawn-window (and respawn-pane, which I forgot about but can be use to target an individual pane within a window) also take a -k option, which can be used to kill whatever is running in a window and either restart the command or start a new command. You could add something to your .tmux.conf like
bind-key C-c respawn-pane -k bash
Then, in any active pane, you can type Control-C to kill whatever is running in the pane and replace it with an interactive shell (remain-on-exit would not be necessary in this case, as you are immediately replacing the old command with a new one).

run xterm -e without terminating

I want to run xterm -e file.sh without terminating.
In the file, I'm sending commands to the background and when the script is done, they are still not finished.
What I'm doing currently is:
(cd /myfolder; /xterm -ls -geometry 115x65 -sb -sl 1000)
and then after the window pops up
sh file.sh
exit
What I want to do is something like:
(cd /myfolder; /xterm -ls -geometry 115x65 -sb -sl 1000 -e sh file.sh)
without terminating and wait until the commands in the background finish.
Anyone know how to do that?
Use hold option:
xterm -hold -e file.sh
-hold Turn on the hold resource, i.e., xterm will not immediately destroy its window when the shell command completes. It will wait
until you use the window manager to destroy/kill the window, or if you
use the menu entries that send a signal, e.g., HUP or KILL.
I tried -hold, and it leaves xterm in an unresponsive state that requires closing through non-standard means (the window manager, a kill command). If you would rather have an open shell from which you can exit, try adding that shell to the end of your command:
xterm -e "cd /etc; bash"
I came across the answer on Super User.
Use the wait built-in in you shell script. It'll wait until all the background jobs are finished.
Working Example:
#!/bin/bash
# Script to show usage of wait
sleep 20 &
sleep 20 &
sleep 20 &
sleep 20 &
sleep 20 &
wait
The output
sgulati#maverick:~$ bash test.sh
[1] Done sleep 20
[2] Done sleep 20
[3] Done sleep 20
[4]- Done sleep 20
[5]+ Done sleep 20
sgulati#maverick:~$
Building on a previoius answer, if you specify $SHELL instead of bash, it will use the users preferred shell.
xterm -e "cd /etc; $SHELL"
With respect to creating the separate shell, you'll probably want to run it in the background so that you can continue to execute more commands in the current shell - independent of the separate one. In which case, just add the & operator:
xterm -e "cd /etc; bash" &
PID=$!
<"do stuff while xterm is still running">
wait $PID
The wait command at the end will prevent your primary shell from exiting until the xterm shell does. Without the wait, your xterm shell will still continue to run even after the primary shell exits.

Resources