5

Edit From the comments below I wrote a confusing/misleading introduction so I'm rewriting it.

I have a bash script called "lock screen timer" that can be clicked on the desktop. After 30 minutes it locks the screen and the user has to enter their password to unlock the screen. However if the user changes their mind, or they want to reset the timer, they should be able to click the desktop shortcut again and it should kill the previouly running job which is sleeping and counting down.

I've done a little trial and error so far and hit a road-block.

The relevant code snippet is:

pgrep tv-timer > ~/tv-timer.log
PID=$$ # Current Process ID

Using cat ~/tv-timer.log:

16382
20711

One of these is equal to "$PID" above but the other is a previously running copy which I want to use kill #####.

What's the best way of figuring out which one <> "$PID" and killing it?

The first time the script is run there is only one entry equal to "$PID" which I don't want to kill.

Thanks for your help!


The proposed duplicate (Prevent duplicate script run at the sametime) is a problem within Parent and Child processes. Accepted answers are long and complicated involving wrapper scripts and/or multiple lines of code.

The solution sought here is one line of new code!

Indeed the accepted-elect answer here is based on duplicate OP attempt THAT DOES NOT WORK THERE!

  • 2
  • @Serg All the answers there (including yours) involve complex solutions not like the one line answer I was hoping for. The OP there seems to have the closest answer with pkill -f $(pgrep run_scrapy.sh | grep -v $$) but that didn't work for him within cron environment. That duplicate looks more of a cron problem than this simple little bash timer script. – WinEunuuchs2Unix Nov 13 '16 at 02:11
  • Maybe something like this might work: PID=$(pgrep -n tv-timer) && kill -9 $(pgrep tv-timer | grep -v $PID) The -n tells it to grab the newest/current one. – Terrance Nov 13 '16 at 02:35
  • @Terrance Thank you for the suggestion. Actually Serg and I just worked it out in Chat Room a few minutes ago. The pgrep tv-timer | grep -v $$ line returns the Process ID of the previously running version. – WinEunuuchs2Unix Nov 13 '16 at 02:39
  • Ah, OK. Very well done. =) – Terrance Nov 13 '16 at 02:40
  • Maybe change your algorithm? Instead of having a script that goes to sleep, start the script, as a one time shot, every 30 minutes from crontab (and save the $pid somewhere). The script checks that is the previous $pid process is still running - Whan crontab wants to invoke it again after 30 minutes, and if it is, it's killed before the actual payload of the script starts executing. – boardrider Nov 13 '16 at 19:57
  • @boardrider the script in question (linked in the answer) is a count down timer to lock the screen. There is a desktop shortcut link to invoke the timer. It's not a cron type of job at any stretch of the imagination. Thanks for honest suggestion though. – WinEunuuchs2Unix Nov 13 '16 at 19:59
  • Your process is ill-conceived in my opinion. It would be better to have the current instance of the program write its PID atomically into a lock file that can be read by later instances of the same program to signal the earlier instance. – David Foerster Nov 14 '16 at 01:20

2 Answers2

4

Please, change the line:

pgrep tv-timer | grep -v $$ > ~/tv-timer2.log

into this:

pgrep tv-timer | grep -v ^$$$ > ~/tv-timer2.log

In fact, if one tv-timer prosesses has, say, PID=26019 and should $$ be 6019, then grep would yield an empty string, which is not what you want.

Alessandro
  • 368
  • 3
  • 9
  • But all the code is working as is. I've tested it dozens of times. Results are confirmed using ps -aux | grep tv-timer from the command line. BTW after you have sufficient reputation you will be able to post this answer as a comment. If you want to test the whole script yourself please see: http://askubuntu.com/questions/837078/application-that-will-lock-screen-after-a-set-amount-of-time-for-ubuntu/837115#837115 – WinEunuuchs2Unix Nov 13 '16 at 16:21
  • grep is line matching tool, so lets say your PID given by $$ is 1234. If grep sees 1234 anywhere on the line, then the script will ignore a PID that might be 51234 for example. That means , your other instance won't be killed. Alessandro is right – Sergiy Kolodyazhnyy Nov 14 '16 at 02:24
2

Having spent many hours following the white rabbit into various alternate universes, I found the following to be the only reliable method is:

# If called a second time, kill the first version already running
kill $(pgrep -f "${0##*/}" | grep -v ^$$)

If you are interested in testing this see Lock Screen Timer code in Ask Ubuntu at: (Application that will lock screen after a set amount of time for Ubuntu)

Production version code snippet (TL;DR)

The pertinent code snippet from the lock-screen-timer program is this:

# Check if lock screen timer already running
pID=$(pgrep -f "${0##*/}") # All PIDs matching lock-screen-timer name
PREVIOUS=$(echo "$pID" | grep -v ^"$$") # Strip out this running copy ($$$)
if [ $PREVIOUS != "" ]; then
    zenity --info --title="Lock screen timer already running" --text="Previous lock screen timer has been terminated."
    kill "$PREVIOUS"
fi

pgrep -f "${0##*/}"

This finds all occurrences of the same named running program ID ${0##*/}. Although the executable file is called ~/bin/lock-screen-timer a desktop shortcut is used to call it. That can be named "Lock Screen Timer" or "Lock screen timer" or "Remind me of laundry cycle". It can't be hard-coded into the program as in the original question.

The resulting list of process id's are put in the variable $pID

echo "$pID" | grep -v ^"$$"

This takes the contents of $pID (all running occurrences of lock-screen-timer, or it's renamed desktop short cut) and pipes the list of process id's into the next command using the pipe (|) character.

The next command grep -v removes process ID's that match $$ which is the current running process ID. The carrot (^) tells grep to match the whole word not the character string. For example, the current process id may be 1518 and the previous version may be 11518, 21518 or 31518. In that case just matching on 4 digits the process id makes those 3 matches because 1518 is within 11518. The carrot matches on words so 1518 <> 11518. In the process id list the words are separated by spaces (in a variable) or new line characters (when ps -aux command displays them on screen).

The result of these two commands is the process ID of the previously running lock-screen-timer script. The process ID is put into the variable $PREVIOUS. If there wasn't a previous ID the value will be "" (null field).

if [ $PREVIOUS != "" ]; then

This tests if $PREVIOUS is not equal to (!=) a null / empty field "". Obviously we can only kill a previously running process ID if we have one!

zenity --info --title="Lock screen timer already running" ...

When running a desktop shortcut you can't echo messages to the user because the GUI won't display them. They end up in /var/log/syslog and you have to display them with cat or gedit, etc.

zenity is a nice little program to display dialog boxes and forms from bash to the GUI (Graphical User Interface), aka Desktop. The message text goes on to say Previous lock screen timer has been terminated.. This allows the user to start a new timer countdown or simply cancel. In essence calling the script a second time and aborting is how you can kill the first script already running.

kill "$PREVIOUS"

This simply kills the previously running version which we want to do whether we start a new lock-screen-timer countdown or not. This is substantially different from the original question because we've put the results of two cryptic commands into the single variable called $PREVIOUS.