16

First, yes I have seen this question:

Find (and kill) old processes

The answers there are incorrect and do not work. I have voted and commented accordingly.

The processes I want to kill look like this when listed with ps aux | grep page.py:

apache     424  0.0  0.1   6996  4564 ?        S    07:02   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache    2686  0.0  0.1   7000  3460 ?        S    Sep10   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache    2926  0.0  0.0   6996  1404 ?        S    Sep02   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache    7398  0.0  0.0   6996  1400 ?        S    Sep01   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache    9423  0.0  0.1   6996  3824 ?        S    Sep10   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache   11022  0.0  0.0   7004  1400 ?        S    Sep01   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache   15343  0.0  0.1   7004  3788 ?        S    Sep09   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache   15364  0.0  0.1   7004  3792 ?        S    Sep09   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache   15397  0.0  0.1   6996  3788 ?        S    Sep09   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache   16817  0.0  0.1   7000  3788 ?        S    Sep09   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache   17590  0.0  0.0   7000  1432 ?        S    Sep07   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache   24448  0.0  0.0   7000  1432 ?        S    Sep07   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py
apache   30361  0.0  0.1   6996  3776 ?        S    Sep09   0:00 /usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py

I'm looking to setup a simple daily cron that will find and kill any page.py processes older than an hour.

The accepted answer on the aforementioned question does not work, as it doesn't match a range of times, it simply matches processes that have been running from 7 days to 7 days 23 hours 59 minutes and 59 seconds. I don't want to kill processes that have been running from 1-2 hours, but rather anything greater than 1 hour.

The other answer to the aforementioned question using find does not work, at least not on Gentoo or CentOS 5.4, it either spits out a warning, or returns nothing if the advice of said warning is followed.

16 Answers 16

26

GNU Killall can kill processes older than a given age, using their processname.

if [[ "$(uname)" = "Linux" ]];then killall --older-than 1h page.py;fi
1
  • 1
    That option is not available on CentOS 6.6. Version: killall (PSmisc) 22.6.
    – Onnonymous
    Sep 28, 2015 at 9:36
11

find doesnt always work, not every system has etimes available, and it might be my regex newb status, but I dont think you need anything more than this:

ps -eo pid,etimes,comm,user,tty | awk '{if ($4 ~ /builder/ && $5 ~ /pts/ && $2>600) print $1}'
  • list all processes and provide columns PID,ELAPSED(etimes = seconds), COMMAND, USER, TT (thanks @ahoffman)
  • with awk print the PID where the 4th column ($4, USER) contains text 'builder', and 5th column ($5, TT) contains text 'pts' and the ELAPSED column has a value larger than 600 sec (thanks @amtd)

you can then pipe that to kill or whatever your need may be.

4
  • I think this is one of the more robust solutions, especially in terms of your use of ps, but I'd fold the multiple greps into the single awk, and for safety restrict the pattern matches to particular columns (to rule out e.g. a command name matching builder, etc.)
    – jmtd
    Feb 6, 2013 at 16:08
  • This is fine when your time scope is in days, but won't work if you want test the elapsed time in hours, minutes or seconds. Nov 12, 2016 at 9:46
  • use "etimes" instead of "etime", this will return the elapsed time in seconds which is much easier to parse.
    – ahofmann
    Sep 20, 2019 at 8:06
  • @jmtd & ahofmann: I've updated as per your comment. I hope this is as you've intended
    – eugenevd
    Sep 30, 2019 at 10:56
10

Thanks to Christopher's answer I was able to adapt it to the following:

find /proc -maxdepth 1 -user apache -type d -mmin +60 -exec basename {} \; \
| xargs ps | grep page.py | awk '{ print $1 }' | sudo xargs kill

-mmin was the find command I was missing.

5
  • 3
    Not sure if -mmin is suitable for detecting the age of a process.
    – LatinSuD
    Sep 15, 2010 at 18:19
  • It doesn't appear the /proc/ directories get modified a great deal, so this seems to work. That being said, I wouldn't want to claim it's impossible. Sep 15, 2010 at 18:31
  • I don't think this answers your question since this answer is too narrow and question is wider.
    – poige
    May 29, 2012 at 5:23
  • And I'd say even more — it doesn't work at all: find /proc -maxdepth 1 -type d -name 1 -mmin +60 -ls — /sbin/init isn't being listed in despite uptime counts for days, not hours. It seems you can't rely on /proc/'s dirs modification time.
    – poige
    May 29, 2012 at 5:53
  • 3
    The timestamps in /proc can't be depended on for this, unfortunately. At least not any more. Jan 10, 2013 at 21:46
6
# get elapsed time in seconds, filter our only those who >= 3600 sec
ps axh -O etimes  | awk '{if ($2 >= 3600) print $2}'

If you want you can feed ps with list of PIDs to lookup within, for e. g.:

ps h -O etimes 1 2 3
1
  • 2
    etimes works only for newer ps
    – Tino
    Jul 18, 2015 at 21:45
4

I think you can modify some of those previous answers to fit your needs. Namely:

for FILE in (find . -maxdepth 1 -user processuser -type d -mmin +60)
  do kill -9 $(basename $FILE) # I can never get basename to work with find's exec.  Let me know if you know how!
done

Or

ps -eo pid,etime,comm | awk '$2!~/^..:..$/ && $3~/page\.py/ { print $1}' | kill -9

I think the second may best fit your needs. The find version would wind up nuking other processes by that user


--Christopher Karel

2
  • 7
    Don't use kill -9 except as a last resort. Use -SIGINT or -SIGTERM. Sep 15, 2010 at 23:01
  • This is using the format of the elapsed time as test criteria, not its value. ps will output time in ^..:..$ format when it is less than an hour. Nov 12, 2016 at 9:49
4
apt-get install psmisc

killall -o 1h $proc_name
1
  • Could you help explain more about the psmisc utility? The OP mentioned CentOS; is it available as an RPM?
    – Castaglia
    Mar 31, 2016 at 4:22
4

The problem

Converting etime (elapsed time) column of ps command to seconds. The time specification is in this format [[dd-]hh:]mm:ss. Newer versions of ps have an etimes column which outputs etime value in seconds.

The solution: simple custom awk function

This custom awk function supports all formats of etime column (e.g. 03-12:30:59, 00:07 etc.). Just paste it in your awk script, it is one-liners friendly solution.

function sec(T){C=split(T,A,"[:-]"); return A[C>3?C-3:99]*86400 + A[C>2?C-2:99]*3600 + A[C>1?C-1:99]*60 + A[C>0?C-0:99]*1}
  • sec(T) converts T to seconds
  • T time specification in [[dd-]hh:]mm:ss format (e.g. etime)
  • C count of fields in T (equivalent to awk's NF variable)
  • A array of fields in T (equivalent to awk's $ variable)
  • A[C>3?C-3:99] this is safe way to reference the fourth value (i.e. number of days) in reverse order. This approach is useful because days and hours are optional. If the array is not long enough it dereference A[99] which will yield 0 value. I assume 99 is high enough for most use cases.
  • returns seconds as integer

Real world example

This bash oneliner will kill soffice.bin process running under current user if the process is older than 180 seconds.

kill -9 $(ps cx -o command,etime,pid | awk '/^soffice.bin/ {if (sec($2)>180) {print $3}} function sec(T){C=split(T,A,"[:-]"); return A[C>3?C-3:99]*86400 + A[C>2?C-2:99]*3600 + A[C>1?C-1:99]*60 + A[C>0?C-0:99]*1}')
2
  • 1
    MUCH better that the other answers. it also handles multi procs. Jan 19, 2017 at 13:13
  • It would be better to place 'command' or 'args' at the end of the 'ps' format list to be able to grep on the full command / args string. Placing it at the beginning will lead to ps truncating the longer commands.
    – Maksym
    Jan 19, 2018 at 15:21
1

The lstart field in ps gives a consistent time format which we can feed to date to convert to seconds since the epoch. Then we just compare that to the current time.

#!/bin/bash
current_time=$(date +%s)
ps axo lstart=,pid=,cmd= |
    grep page.py |
    while read line
    do
        # 60 * 60 is one hour, multiply additional or different factors for other thresholds 
        if (( $(date -d "${line:0:25}" +%s) < current_time - 60 * 60 ))
        then
            echo $line | cut -d ' ' -f 6    # change echo to kill
        fi
    done
1

this should work

killall --older-than 1h $proc_name

3
  • 1
    How does this add or improve [the already existing answers]?
    – Reaces
    Oct 12, 2015 at 14:02
  • 2
    @Reaces: In all fairness, I had to search for the one answer mentioning --older-than and it's easy to overlook it. Compared to the other answers, this is much easier, and it's available now on EL7 as well.
    – Sven
    Oct 12, 2015 at 14:21
  • @Reaces this just makes it easier than writing scripts using awk/sed etc to kill a process, this i suppose is much simpler and cleaner Oct 27, 2015 at 16:09
0

I modified the answer they gave you in previous post

ps -eo pid,etime,comm | 
egrep '^ *[0-9]+ +([0-9]+-[^ ]*|[0-9]{2}:[0-9]{2}:[0-9]{2}) +/usr/bin/python2.6 /u/apps/pysnpp/current/bin/page.py' | 
awk '{print $1}' | 
xargs kill

The regular expression searches for 2 types of second argument:

  • Days in the form of digits and a minus sign.
  • Hours:minutes:seconds expression.

That should match everything except young processes who would have the form minutes:seconds.

1
  • Alternatively we could try do it the way PS does it. Substract the first argument of /proc/uptime from the 22nd argument of /proc/*/stat.
    – LatinSuD
    Sep 15, 2010 at 18:44
0

This is probably overkill, but I got curious enough to finish it and test that it works (on a different process name on my system, of course). You can kill the capturing of $user and $pid to simplify the regexp, which I only added for debugging, and didn't feel like ripping back out. Named captures from perl 5.10 would shave off a couple more lines, but this should work on older perls.

You'll need to replace the print with a kill, of course, but I wasn't about to actually kill anything on my own system.

#!/usr/bin/perl -T
use strict; use warnings;

$ENV{"PATH"} = "/usr/bin:/bin";                                                       

my (undef,undef,$hour) = localtime(time);                                             
my $target = $hour - 2; # Flag process before this hour                               
my $grep = 'page.py';                                                   

my @proclist = `ps -ef | grep $grep`;                                                 
foreach my $proc (@proclist)                                                          
{                                                                                     
    $proc =~ /(\w+)\s+(\d+)\s+\d+\s+\d+\s+(.*?).*/;                   
    my $user = $1;                                                                    
    my $pid = $2;                                                                     
    my $stime = $3;                                                                   

    $stime =~ s/(\d+):(\d+)/$1/;                                                      

    # We're going to do a numeric compare against strings that                        
    # potentially compare things like 'Aug01' when the STIME is old                   
    # enough.  We don't care, and we want to catch those old pids, so                 
    # we just turn the warnings off inside this foreach.                              
    no warnings 'numeric';                                                            

    unless ($stime > $target)                                                         
    {                                                                                 
        print "$pid\n";                                                               
    }                                                                                 
}

0

I have a server with wrong dates in /proc and find doesn't work so I wrote this script:

#!/bin/bash

MAX_DAYS=7  #set the max days you want here
MAX_TIME=$(( $(date +'%s') - $((60*60*24*$MAX_DAYS)) ))

function search_and_destroy()
{
        PATTERN=$1
        for p in $(ps ux|grep "$PATTERN"|grep -v grep| awk '{ print $2 }')
        do
            test $(( $MAX_TIME - $(date -d "`ps -p $p -o lstart=`" +'%s') )) -ge 0 && kill -9 $p
        done
}

search_and_destroy " command1 "
search_and_destroy " command2 "
0

Python version using the ctime of the process entries in /proc:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# kills processes older than HOURS_DELTA hours

import os, time

SIGNAL=15
HOURS_DELTA=1

pids = [int(pid) for pid in os.listdir('/proc') if pid.isdigit()]

for pid in pids:
    if os.stat(os.path.join('/proc', str(pid))).st_ctime < time.time() - HOURS_DELTA * 3600:
        try:
            os.kill(pid, SIGNAL)
        except:
            print "Couldn't kill process %d" % pid
0

I use this simple script it takes two arguments name of process and age in seconds.

#!/bin/bash
# first argument name of the process to check
# second argument maximum age in seconds
# i.e kill lighttpd after 5 minutes
#   script.sh lighttpd 300 
process=$1
maximum_runtime=$2
pid=`pgrep $process`
if [ $? -ne 0 ]
then
        exit 0
fi
process_start_time=`stat /proc/$pid/cmdline --printf '%X'`
current_time=`date +%s`
let diff=$current_time-$process_start_time

if [ $diff -gt $maximum_runtime ]
then
        kill -3 $pid
fi
1
  • unfortunately it can handle only one PID.
    – Gábor
    Jan 27, 2022 at 14:09
0

I was not satisfied with the other solution, most of them are too cryptic ( my bash knowledge is kind of limited) and so I cannot customize them ...
I have created my own solution, It is probably not the best but it works and it is readable

You can save this script in a file and make it executable ( eventually call it with using cron )

#!/bin/bash
## time in second that trigger the kill
LIMIT=10
## [] skip the grep from the process list
PROC_NAME="[m]y process name"
## etimes return the time in seconds
TIME_SEC=$(ps axo etimes,pid,command | grep "$PROC_NAME" | awk {'print$1'})
PID=$(ps axo etimes,pid,command | grep "$PROC_NAME" | awk {'print$2'})

if [ -n "$TIME_SEC" ] 
    then
    if (( $TIME_SEC > $LIMIT )); then
        kill $PID
    fi
fi
-2

72=3days 48=2days 24=1day

a1=$(TZ=72 date +%d) ps -ef| cat filex.txt | sed '/[JFMASOND][aepuco][nbrylgptvc] '$a1'/!d' | awk '{ print $2 " " $5 " " $6 }' > file2.txt

it works :)

1
  • 1
    That may well be, but it's pretty difficult to read and learn from. Consider reformatting with some newlines and stuff... scripts are better than one-liners for instruction. Apr 25, 2015 at 7:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .