68

I'd like to have a desktop notification whenever a command that has run for more than, say 15 seconds, finishes in an interactive shell.

In other words, I would like all commands to be wrapped in something like this

start=$(date +%s);
ORIGINAL_COMMAND;
[ $(($(date +%s) - start)) -le 15 ] || notify-send "Long running command finished"

What's the best way to accomplish this in bash?

aioobe
  • 1,616

9 Answers9

36

As far as I understood you want a wrapper. And you want to use a command through it so that it will give you desired notification if running time of your command is more than 15 sec. So here is it.

wrapper(){
    start=$(date +%s)
    "$@"
    [ $(($(date +%s) - start)) -le 15 ] || notify-send "Notification" "Long\
 running command \"$(echo $@)\" took $(($(date +%s) - start)) seconds to finish"
}

Copy this function in your ~/.bashrc and source ~/.bashrc as,

. ~/.bashrc

Useage

wrapper <your_command>

If it takes more than 15 sec you will get the desktop-notification describing the command and its time of execution.

Example

wrapper sudo apt-get update

screenshot of desktop-nitification

sourav c.
  • 46,120
32

In ~/.bashrc there is an alias alert defined as:

alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'

which can be used to notify the completion of command execution.

Usage:

$ the_command; alert

e.g.

$ sudo apt-get update; alert

snap1

You may customize the alias as per your need and desire.

rusty
  • 16,917
30

You want https://launchpad.net/undistract-me (installable from the Ubuntu archives with sudo apt-get install undistract-me) which does precisely what you're asking for, including working automatically (that is, without having to remember to add something extra to potentially long-running commands).

sil
  • 4,282
13

Apart from a wrapper like souravc suggested, there isn't really any good way to do this in bash. You can hack your way around it with a DEBUG trap and a PROMPT_COMMAND. A DEBUG trap is triggered whenever you run a command, and PROMPT_COMMAND is run just before the prompt is written.

So stuff for ~/.bashrc becomes something like

trap '_start=$SECONDS' DEBUG
PROMPT_COMMAND='(if (( SECONDS - _start > 15 )); then notify-send "Long running command ended"; fi)'

This is a hack, so don't be surprised if you encounter odd side-effects with this.

geirha
  • 47,279
5

EDIT

TL;DR: create autocompletion shortcut in .inputrc and function in .bashrc . Run command as usual, type in, but instead of ENTER, press the shortcut that you specified in .inputrc

The person who placed bounty on this question said:

"All of the existing answers require typing an additional command after the command. I want an answer that does this automatically."

While researching the solutions to this problem I've stumbled upon this question from stackexchange, which allows binding CtrlJ to a sequence of commands: Ctrla (move to beginning of line), place "mesure" string in front of the command you entered, Ctrlm (execute)

Thus you get functionality of auto-completion and separate ENTER command for measuring time, while perserving original purpose of the second function i posted bellow.

As of now, here are the contents of my ~/.inputrc file:

"\C-j": "\C-a measure \C-m"

And here are the contents of .bashrc (note , I haven't been using bash in forever - I use mksh as my shell , hence that's what you see in the original post. Functionality is still the same)

PS1=' serg@ubuntu [$(pwd)]
================================
$ '
function measure () 
{

/usr/bin/time --output="/home/xieerqi/.timefile" -f "%e" $@ 

if [ $( cat ~/.timefile| cut -d'.' -f1 ) -gt 15 ]; then

    notify-send "Hi , $@ is done !"

fi


}

Original Post

Here's my idea - use a function in .bashrc. Basic principle - use /usr/bin/time to measure the time it takes for command to complete, and if it is over 15 seconds, send notification.

function measure () 
{

if [ $( /usr/bin/time -f "%e" $@ 2>&1 >/dev/null ) -gt 15 ]; then

    notify-send "Hi , $@ is done !"

fi


}

Here I am redirecting output to /dev/null but to view output, redirecting to file can also be done.

A much better approach, IMHO, is to send output of time to some file in your home folder (just so you don't pollute your system with timefiles, and always know where to look). Here's that second version

function measure () 
{

/usr/bin/time --output=~/.timefile -f "%e" $@ 

if [ $( cat ~/.timefile | cut -d'.' -f1 ) -gt 15 ]; then

    notify-send "Hi , $@ is done !"

fi


}

And here's the screenshots of first and second version, in that order

First version, no output enter image description here

Second version, with output enter image description here

4

I've recently built a tool that serves this purpose. It can either be run as a wrapper or automatically with shell integration. Check it out here: http://ntfy.rtfd.io

To install it:

sudo pip install ntfy

To use it as a wrapper:

ntfy done sleep 3

To get notifications automatically, add this to your .bashrc or .zshrc:

eval "$(ntfy shell-integration)"
1

Your script works quite well, just make sure you include the 'shebang' (#!/bin/bash) line. Other ones to #!/bin/bash are mentioned here, but most of the time, #!/bin/bash works fine for Unix bash scripts. It is required by the script interpreter so it know what type of script it is.

This seems to work as a test script:

#!/bin/bash
start=$(date +%s);
   echo Started
   sleep 20;
   echo Finished!
[ $(($(date +%s) - start)) -le 15 ] || notify-send -i dialog-warning-symbolic "Header" "Message"

To modify this script, put the command(s) where the echo and sleep lines are.

Note with notify-send, you can use -i to specify an icon as well :-)

Also, make sure it is executable by running chmod +x /PATH/TO/FILE on it first.

Wilf
  • 30,732
0

I have created telert, a lightweight open-source tool that does exactly this. It lets you set up a shell hook that automatically sends a desktop notification whenever a command takes more than a specified number of seconds to complete. In addition to desktop alerts, Telert supports other providers like Telegram, Discord, Slack, audio alerts, and custom HTTP endpoints.

Creating it requires a couple of quick commands.

pip install telert
telert config desktop --set-default
eval "$(telert hook -l 15)"  # Auto-wrap all commands longer than 15s
echo 'eval "$(telert hook -l 15)"' >> ~/.bashrc # Make it persistent

No need to wrap individual commands — just configure it once and get notified automatically when long-running tasks finish. Telert also supports one-time notifications, command timing, and capturing exit codes for better tracking and monitoring.

Mihir
  • 1
-2

You can also do this with a simple if statement like this

#!/bin/bash

START=$(date +%s)

TIME=$(($START+15))

ORIGINAL_COMMAND;

END=$(date +%s)

if [ $END -gt $TIME ]
then
    notify-send "Task Completed"
fi

You can use your own variable names.

I can confirm that this works, I tested with a command that takes a long time to finish and one that does not and the notification comes for the one that takes a long time

You can also do this with any command by replacing ORIGINAL_COMMAND with $@ and running the script as ./script command.

Rumesh
  • 1,449