Is there any variable that cron sets when it runs a program ? If the script is run by cron, I would like to skip some parts; otherwise invoke those parts.
How can I know if the Bash script is started by cron ?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
I’m not aware that cron does anything to its environment by default that can be of use here, but there are a couple of things you could do to get the desired effect.
1) Make a hard or soft link to the script file, so that, for example, myscript and myscript_via_cron point to the same file. You can then test the value of $0 inside the script when you want to conditionally run or omit certain parts of the code. Put the appropriate name in your crontab, and you’re set.
2) Add an option to the script, and set that option in the crontab invocation. For example, add an option -c, which tells the script to run or omit the appropriate parts of the code, and add -c to the command name in your crontab.
And of course, cron can set arbitrary environment variables, so you could just put a line like RUN_BY_CRON="TRUE" in your crontab, and check its value in your script.
Method 2
Scripts run from cron are not run in interactive shells. Neither are startup scripts. The differentiation is that interactive shells have STDIN and STDOUT attached to a tty.
Method 1: check if $- includes the i flag. i is set for interactive shells.
case "$-" in
*i*)
interactive=1
;;
*)
not_interactive=1
;;
esac
Method 2: check is $PS1 is empty.
if [ -z "$PS1" ]; then
not_interactive=1
else
interactive=1
fi
references:
- https://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html
- https://www.gnu.org/software/bash/manual/html_node/Is-this-Shell-Interactive_003f.html
Method 3: test your tty. it’s not as reliable, but for simple cron jobs you should be ok, as cron does not by default allocate a tty to a script.
if [ -t 0 ]; then
interactive=1
else
non_interactive=1
fi
Keep in mind that you can however force an interactive shell using -i, but you’d probably be aware if you were doing this…
Method 3
First, get cron’s PID, then get the current process’s parent PID (PPID), and compare them:
CRONPID=$(ps ho %p -C cron) PPID=$(ps ho %P -p $$) if [ $CRONPID -eq $PPID ] ; then echo Cron is our parent. ; fi
If your script is started by another process that might have been started by cron, then you can walk your way back up the parent PIDs until you get to either $CRONPID or 1 (init’s PID).
something like this, maybe (Untested-But-It-Might-Work<TM>):
PPID=$$ # start from current PID CRON_IS_PARENT=0 CRONPID=$(ps ho %p -C cron) while [ $CRON_IS_PARENT -ne 1 ] && [ $PPID -ne 1 ] ; do PPID=$(ps ho %P -p $PPID) [ $CRONPID -eq $PPID ] && CRON_IS_PARENT=1 done
From Deian:
This is a version tested on RedHat Linux
# start from current PID
MYPID=$$
CRON_IS_PARENT=0
# this might return a list of multiple PIDs
CRONPIDS=$(ps ho %p -C crond)
CPID=$MYPID
while [ $CRON_IS_PARENT -ne 1 ] && [ $CPID -ne 1 ] ; do
CPID_STR=$(ps ho %P -p $CPID)
# the ParentPID came up as a string with leading spaces
# this will convert it to int
CPID=$(($CPID_STR))
# now loop the CRON PIDs and compare them with the CPID
for CRONPID in $CRONPIDS ; do
[ $CRONPID -eq $CPID ] && CRON_IS_PARENT=1
# we could leave earlier but it's okay like that too
done
done
# now do whatever you want with the information
if [ "$CRON_IS_PARENT" == "1" ]; then
CRON_CALL="Y"
else
CRON_CALL="N"
fi
echo "CRON Call: ${CRON_CALL}"
Method 4
If your script file is invoked by cron and it contains a shell in the first line like #!/bin/bash you need to find the parent-parent name for your purpose.
1) cron is invoked at the given time in your crontab, executing a shell
2) shell executes your script
3) your script is running
The parent PID is available in bash as variable $PPID. The ps command to get the parent PID of the parent PID is:
PPPID=`ps h -o ppid= $PPID`
but we need the name of the command, not the pid, so we call
P_COMMAND=`ps h -o %c $PPPID`
now we just need to test the result for “cron”
if [ "$P_COMMAND" == "cron" ]; then RUNNING_FROM_CRON=1 fi
Now you can test anywhere in your script
if [ "$RUNNING_FROM_CRON" == "1" ]; then ## do something when running from cron else ## do something when running from shell fi
Good luck!
Method 5
Works on FreeBSD or on Linux:
if [ "Z$(ps o comm="" -p $(ps o ppid="" -p $$))" == "Zcron" -o
"Z$(ps o comm="" -p $(ps o ppid="" -p $(ps o ppid="" -p $$)))" == "Zcron" ]
then
echo "Called from cron"
else
echo "Not called from cron"
fi
You can go as far up the process tree as you wish.
Method 6
A simple echo $TERM | mail [email protected] in cron showed me that on both Linux and AIX, cron seems to set $TERM to ‘dumb’.
Now theoretically there may still be actual dumb terminals around, but I suspect that for most occasions, that should suffice…
Method 7
A generic solution to the question “is my output a terminal or am I running from a script” is:
( : > /dev/tty) && dev_tty_good=y || dev_tty_good=n
Method 8
There is no authoritative answer, but the prompt ($PS1) and terminal ($TERM) variables are pretty decent here, as is the shell’s “interactive” option flag (special parameter $- containing i). Some systems set TERM=dumb while most leave it empty, so we’ll just check for either and also check for the interactive option flag:
if [ "${TERM:-dumb}$PS1$-" != "dumb${-#*i}" ]; then
echo "This is not a cron job"
fi
The above code substitutes the word “dumb” when there is no value for $TERM. Therefore, the conditional fires when there is no $TERM or $TERM is set to “dumb” or if the $PS1 variable is not empty or if $- does not match itself when removing all characters up to the first i (this removes nothing when there is no i).
You can alternatively check the terminal name. Cron usually (verify this!) doesn’t allocate a terminal, so you could run tty and the POSIX standard mandates that (if it really has no terminal) it must say not a tty in its output:
if [ "$(tty)" != "not a tty" ]; then echo "This is not a cron job" fi
Another way to take advantage of cron not allocating a terminal would simply be to test for whether standard input is open. You can do this with ! [ -t 0 ], but this will give you an improper answer if you’re piping data into the script.
I’ve tested both options on Debian 9 & 11 (TERM=, tty is not a tty), CentOS 6.4 & 7.4 (TERM=dumb, tty is not a tty), and FreeBSD 7.3 && 11.2 (TERM=, tty is not a tty).
Method 9
This bash function should work on systems with either cron or crond.
Tested under Debian bullseye.
## Returns 0 (success) if we are running under Cron
function undercron ()
{
local cronpid=$(pgrep --uid=root --oldest --exact '^crond?$')
for ((ppid=PPID; ppid > 1; ppid=$(ps ho %P -p $ppid))); do
if ((ppid == cronpid)); then
return 0
fi
done
return 1
}
Method 10
There is a problem I do not see anybody mentioning. Most test fail when I run my script from a remote computer using ssh!
ssh <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="25574a4a5165141c170b14131d0b170b1415">[email protected]</a> ./SmartHome.sh
Then (say) [ -t 0 ] and ( : > /dev/tty) and $TERM all indicate wrong (OK, they are all actually right, but are not indicating what we need).
So, the simplest universal solution is to call the script from the cron with an argument, as mentioned in the most upvoted answer under 2). Then, ANY other invocation will be seen as manual run. I use – (but can be anything) as the first argument to cron call and then check for it in my script:
[ "$1" == "-" ] && shift || Run_Manually=1
Method 11
I’ve tested several OS (Ubuntu, CentOS, SUSE) and executed a script which includes printenv through SSH and local terminals and it seems these variables are never available if crond executes a script, but available in all other cases:
LESSOPEN LESSCLOSE
So checking if this variable is not set, should work:
if [[ -z ${LESSOPEN+x} ]]; then
echo "Script is executed by cron"
else
echo "Script is executed manually"
fi
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0