From: www.itworld.com

Unix Tip: Am I being run by cron?

by Sandra Henry-Stocker

December 7, 2004 —

 

Send your Unix questions today!


See additional Unix tips and tricks


Have you ever wanted to force a script to act differently when it
is invoked by cron? If the script is run interactively, for example,
you might want the script to prompt the user for some information
while, if the script is run through cron, you might instead want to
run with a set of defaults.



While this isn't the most straightforward thing to do, a set of
commands can be concocted to determine when a script is run through
cron. In order to do this, we need to first recognize that all
scripts run by cron have one characteristic in common -- they have
the same "grandparent" process. Let's quickly examine why this is
the case.



The parent process of any script run through cron is the process
the cron daemon spawns to run the script. To test this, you could
put a script like this in your crontab file:



-------------------------- cut here --------------------------
#!/bin/bash

echo $PPID >> /tmp/ppids
-------------------------- cut here --------------------------


Then add the script to your crontab file:

* * * * * /export/home/bin/test-cron


What you will notice after a few minutes is that each line in your
/tmp/ppids file will contain a different process ID. In other words,
each running of the script has a different parent process. In
addition, each of these processes only exists while the cron job is
being run.


2406
2411
2416


The variable $PPID, available in bash and ksh, has a counterpart in
ps output. The PPID column in "ps -ef" output contains the parent
process ID for each listed process. For our purposes, we can limit
the ps output that we are going to process by using the -o option.
The command "ps -ef -o ppid,pid" will list ONLY process IDs and
parent process IDs (i.e., PIDs and PPIDs).



For an example, the following command will print the parent of the
mountd process. The response to this command will almost always be
"1" (/etc/init).



ps -ef -o ppid,pid | grep " `pgrep -x mountd$`$" | awk '{print $1}'



How does this work? First, we are using the ps command to list the
PID and PPID of each process, but we are listing them in reverse
order (parent first). Then, we are using a pgrep command to find
the PID for the mountd command. We use the -x option to make sure
we don't also find "automountd". The grep command then limits the
ps output to the single line that ends with mountd's PID, the extra
blank ensuring that we don't find other processes that might just
end in the same string of digits. Lastly, we limit what is printed
to only the first field on the line -- the PPID of the mountd process.



We can then construct a script which uses the same technique to find
the parent process of the parent process -- in other words, the
grandparent process.



In this script, we first determine the process ID for cron. Notice
that we use the -x option with pgrep, just in case the letters "cron"
appear within some other process's name.



We then use a version of the command that we concocted above to find
the parent of this process's parent. We'll call the new parameter
$GPID (for "grandparent ID").


-------------------------- cut here --------------------------
#!/bin/bash

cronID=`pgrep -x cron`
GPID=`ps -ef -o ppid,pid | grep " $PPID$" | awk '{print $1}'`

if [ "$cronID" == "$GPID" ]; then
    echo I am being run through cron
else
    echo I am NOT being run through cron
fi
-------------------------- cut here --------------------------


This trick will work for a process which is run by cron, but will
fail if a process run by cron *calls* the process in question. If
a process is being called by a process which is run by cron, the
cron process would, as you'd suspect, be its great-grandparent, not
the grandparent. Can we provide a way to determine, then, whether
cron is an ancestor of a process rather than strictly its
grandparent? Well ... yes!



For this task, we need a more generally useful command. And, if we
are using Solaris, we have such a command in "ptree". This command
display a "process tree" -- an indented list of the genealogy of a
process. For example, when I run this command on one of my servers,
I usually see something like this:


% ptree $$
335   /usr/local/sbin/sshd
  2675  /usr/local/sbin/sshd
    2677  /usr/local/sbin/sshd
      2679  -bash
        2682  ptree 2679


This command displays the lineage of the ptree command as I used it
within my ssh session on the particular server. The ultimate
ancestor of my ptree command is the daemon /usr/local/sbin/sshd
process which allows me to log in with an SSH client.



To view the lineage of the mountd command, we could use a command
like this:


% ptree `pgrep -x mountd`
375   /usr/lib/nfs/mountd

We notice that ptree does not take us all the way back to the init
process. This works to our advantage. To find the ultimate
(non-init) ancestor to any process, we can do this:


% ptree  | head -1 | awk '{print $2}'


To capture this in a script, we might use this command:

APID=`ptree $$ | head -1 | awk '{print $2}'`


If the APID (ancester process ID) is /usr/sbin/cron, we know that
the process has been run by cron. Otherwise it was not. Here is an
example of the script logic:


-------------------------- cut here --------------------------
#!/bin/bash

APID=`ptree $$ | head -1 | awk '{print $2}'`

if [ "$APID" == "/usr/sbin/cron" ]; then
    echo "I am being run through cron"
else
    echo "I am NOT being run through cron"
fi
-------------------------- cut here --------------------------


NOTE: The term "interactive" is sometimes used to differentiate
commands which are run on the command line from commands which are
included in a script which is run on the command line. In this
way of looking at things, a script which is sourced with a command
such as ". ./myscript" is interactive the same script invoked on
the command line with "./myscript" is non-interactive. This use
of the term "interactive" is NOT the one that we are using in this
column. Here, "interactive" implies that the user is running the
script on the command line as opposed to having the script run
via cron.