Topher Fangio
Topher Fangio

Reputation: 20667

Can a bash script tell if it's being run via cron?

Not having much luck Googling this question and I thought about posting it on SF, but it actually seems like a development question. If not, please feel free to migrate.

So, I have a script that runs via cron every morning at about 3 am. I also run the same scripts manually sometimes. The problem is that every time I run my script manually and it fails, it sends me an e-mail; even though I can look at the output and view the error in the console.

Is there a way for the bash script to tell that it's being run through cron (perhaps by using whoami) and only send the e-mail if so? I'd love to stop receiving emails when I'm doing my testing...

Upvotes: 33

Views: 21590

Answers (9)

Grisha Levit
Grisha Levit

Reputation: 8617

I'd like to suggest a new answer to this highly-voted question. This works only on systemd systems with loginctl (e.g. Ubuntu 14.10+, RHEL/CentOS 7+) but is able to give a much more authoritative answer than previously presented solutions.

if [[ $(loginctl show-session -P Service '') == 'crond' ]]; then
   echo "running in cron"
fi

To summarize: when used with systemd, crond (like sshd and others) creates a new session when it starts a job for a user. This session has an ID that is unique for the entire uptime of the machine. Each session has some properties, one of which is the name of the service that started it. loginctl can tell us the value of this property, which will be "crond" if and only if the session was actually started by crond.


Advantages over using environment variables:

  • No need to modify cron entries to add special invocations or environment variables
  • No possibility of an intermediate process modifying environment variables to create a false positive or false negative

Advantages over testing for tty:

  • No false positives in pipelines, startup scripts, etc

Advantages over checking the process tree:

  • No false positives from processes that also have crond in their name
  • No false negatives if the script is disowned

Upvotes: 6

FifthAxiom
FifthAxiom

Reputation: 300

Many of the commands used in prior posts are not available on every system (pstree, loginctl, tty). This was the only thing that worked for me on a ten years old BusyBox/OpenWrt router that I'm currently using as a blacklist DNS server. It runs a script with an auto-update feature. Running from crontab, it sends an email out.

[ -z "$TERM" ] || [ "$TERM" = "dumb" ] && echo 'Crontab' || echo 'Interactive'

In an interactive shell the $TERM-variable returns the value vt102 for me. I included the check for "dumb" since @edoceo mentioned it worked for him. I didn't use '==' since it's not completely portable.

Upvotes: 3

frank42
frank42

Reputation: 725

I also liked the idea from Tal, but also see the risk of having undefined returns. I ended up with a slightly modified version, which seems to work very smooth in my opinion:

CRON="$( pstree -s $$ | grep -c cron )"

So you can check for $CRON being 1 or 0 at any time.

Upvotes: 0

shekwi
shekwi

Reputation: 871

I had a similar issue. I solved it with checking if stdout was a TTY. This is a check to see if you script runs in interactive mode:

if [ -t 1 ] ; then 
    echo "interacive mode";
else
    #send mail
fi

I got this from: How to detect if my shell script is running through a pipe?

The -t test return true if file descriptor is open and refers to a terminal. '1' is stdout.

Upvotes: 21

edoceo
edoceo

Reputation: 360

What works for me is to check $TERM. Under cron it's "dumb" but under a shell it's something else. Use the set command in your terminal, then in a cron-script and check it out

if [ "dumb" == "$TERM" ]
then
    echo "cron"
else
    echo "term"
fi

Upvotes: 5

Tal
Tal

Reputation: 372

I know the question is old, but I just came across the same problem. This was my solution:

CRON=$(pstree -s $$ | grep -q cron && echo true || echo false)

then test with

if $CRON
then
    echo "Being run by cron"
else
    echo "Not being run by cron"
fi

same idea as the one that @eruciform mentioned - follows your PID up the process tree checking for cron.

Note: This solution only works specifically for cron, unlike some of the other solutions, which work anytime the script is being run non-interactively.

Upvotes: 9

Geoff Reedy
Geoff Reedy

Reputation: 36011

Here's two different options for you:

  • Take the emailing out of your script/program and let cron handle it. If you set the MAILTO variable in your crontab, cron will send anything printed out to that email address. eg:

    [email protected]
    # run five minutes after midnight, every day
    5 0 * * *       $HOME/bin/daily.job
    
  • Set an environment variable in your crontab that is used to determine if running under cron. eg:

    THIS_IS_CRON=1
    # run five minutes after midnight, every day
    5 0 * * *       $HOME/bin/daily.job
    

    and in your script something like

    if [ -n "$THIS_IS_CRON" ]; then echo "I'm running in cron"; else echo "I'm not running in cron"; fi
    

Upvotes: 11

d-_-b
d-_-b

Reputation: 6773

Why not have a command line argument that is -t for testing or -c for cron.

Or better yet:

[email protected]

If it's not specified, don't send an email.

Upvotes: 11

eruciform
eruciform

Reputation: 7736

you can try "tty" to see if it's run by a terminal or not. that won't tell you that it's specifically run by cron, but you can tell if its "not a user as a prompt".

you can also get your parent-pid and follow it up the tree to look for cron, though that's a little heavy-handed.

Upvotes: 26

Related Questions