Can a cronjob be written in a shell? - shell

I need to write a cronjob in .sh file. Is it possible to write a cronjob in .sh file?
My cronjob is
*/1 * * * * bash /abc/def/ghi/sample.sh
This cronjob will be executed once every minute.

Update According to comments by Jonathan Leffler, the variable SHELL may not be recognized by cron (depending on the version) and is not POSIX mandated. This would make the method in the question a better option, on that account alone.
The cron will (attempt to) execute whatever is there. The way you have it, it will run bash with following arguments, thus bash will run your script. (Thanks to Jonathan Leffler in the comment.)
Or, you can tell cron to run all commands via the shell of your choice by setting the SHELL variable
SHELL=/bin/bash
* * * * * /abc/def/ghi/sample.sh
See man 5 crontab on a Linux system for a lot of detail and examples.
Given the questions posed in comments this seems appropriate to add.
To set up a cron job we need to make a "crontab" file. This is done by invoking
crontab -e
The option -e is for "edit." The editor comes up and then you can enter the line from your question, then "save" and "quit". Which editor comes up depends on the settings, but if you didn't do anything it will most likely be vi. The 'root' may need to 'allow' this capability for a user. You can view the contents of your crontab by crontab -l.
When the time comes the command will get executed and the script "/path/sample.sh" will run. In that script your first line should be #!/bin/bash. The line you show does not go in the script, it belongs to the crontab file. The rest of the script "sample.sh" should consist of code that bash shell can execute.
NOTE Please see (additional) comments by Jonathan Leffler for more detail and expertise.

Related

-bash: */10: No such file or directory when running cron job

In my current directory, I have a bash script called run_job.sh. This script runs perfectly fine.
I'm trying to schedule this script to run every 10 minutes using a cronjob. Here is the code that I am using:
*/10 * * * * run_job.sh
Now, when I do this, I get the following error:
-bash: */10: No such file or directory when running
I'm new to cron jobs so I'm not sure why I'm getting this error. Any help would be much appreciated.
The error message in your question suggests that you passed the crontab snippet to Bash somehow. That's not how you schedule a job; you type the command crontab -e at the Bash prompt and edit your Cron schedule in your favorite editor. That's where you would type in this snippet. When you save the file and exit the editor, cron will take your new schedule into use.
However, you should be aware that your normal PATH and other features of your interactive environment will not be available to cron jobs. At a minimum, you will probably need to specify the path to your script. If it's in $HOME/bin/run_job.sh, that's what you need to put in the final field in the crontab entry. (There may be more tweaks you have to do which can't be inferred from the information you have provided; see e.g. CronJob not running for further tips.)
What you are showing is the scheduling line from crontab (see man crontab). Further, I recommend against the /10 format, as it is not accepted by cron on all operating systems. To be safe, it is better to be explicit, as in:
0,10,20,30,40,50 * * * * run_job.sh

Execute a bash script the first time shell opens

Is there a way to execute a bash script automatically on a daily basis, that is I want the bash script to be executed every day the first time I open a shell terminal?
Thanks!
Bash has two files, from the user perspective, that perform "setup" when it is launched:
.bash_profile - This file is executed whenever you open an interactive login shell. This file may also be named .profile in certain distributions or configurations. .profile is usually used for non-Bash specific configuration items. Also be aware that if you have the little used .bash_login, .bash_profile will prevent that file from being used, though it is otherwise equivalent. .bash_profile is standard.
.bashrc - This file is executed for all other bash instances. Note that it is common for people to call .bashrc from .bash_profile to create consistency.
A login shell is spawned when you login; via ssh, telnet, at a console, etc. You can also force the launch of a login shell (forcing .bash_profile) to be processed by starting a shell under su like so:
su - username
Here, the dash indicates that this should be processed as a login shell.
Neither of these seem to be the correct answer for your question, however, unless you are certain to login once each day and only once each day.
A better approach in your case would be to use the cron. Crontab allows you to schedule jobs to run at any desired interval. For daily execution, you would likely want a line configured like so:
0 5 * * * /home/user/script
This would cause the user's script to execute at 5am every day. The columns are:
0 5 * * *
^ ^ ^ ^ ^------ Day of week
^ ^ ^ ^-------- Month of year
^ ^ ^---------- Day of month
^ ^------------ Hour of day
^-------------- Minute of hour
Each of those fields can also represent a comma separated list or even an arithmetic expression. For example, the following will execute the script four times during the 5 AM hour:
*/4 5 * * *
If you want the script to run when you open the shell terminal only, add it to your ~/.bashrc, /etc/bash.bashrc or /etc/bashrc file. This will execute anytime an interactive non login shell is started.
If you want it to execute daily, create a cron for it in /etc/crontab or crontab -e
While informative, the provided answers don't actually solve for the original requirement.
The request is for a script to be run once a day, at the first login, and not again the rest of the day, but then again upon the first login the next day, and the next, etc...
To achieve this you can place the script you want to execute in ~/bin or whatever location you want it in.
At the bottom of your script.sh add these three lines which will remove the execution of the script upon subsequent logins.
cat ~/.bash_profile | grep -v script.sh > bash_profile.tmp
rm -f ~/.bash_profile
mv bash_profile.tmp ~/.bash_profile
What these three lines do:
Reads in your .bash_profile and writes everything EXCEPT the line that contains script.sh to a tmp file
Deletes the existing bash profile that contains the execution of your script
Renames the tmp file, without the script.sh line, to be your new .bash_profile on subsequent logins.
THEN, use 'crontab -e' to add a line to the crontab, that will put back that line to your .bash_profile every morning at a time you would deem to be after your last login of the day but before your first login of the day.
This example is set for zero minutes + four hours, or 4:00am.
0 4 * * * echo "~/bin/script.sh" >> ~/.bash_profile
A problem exists with this, however.
If, for example, the user only logs into the system M-F and not on Sat or Sun. The crontab will still add the line to the profile Sat and Sun morning, meaning that come Monday morning there will be three identical lines. This will cause the script to run three times.
To mitigate this, an IF statement is wrapped around it to check if the command already exists in the file before adding it.
0 4 * * * if [ "$(grep -c '~/bin/script.sh' ~/.bash_profile)" -eq 0 ]; then echo "~/bin/script.sh" >> ~/.bash_profile ; fi
The end result is:
Your script will execute upon first login of the day
At the end, your script will remove the trigger for it to execute on login
Your script will not execute on subsequent logins
Every morning, crontab will add the trigger back to your .bash_profile for it to be executed on the next login, which would be the first login that day.
But crontab will only add the trigger if it doesn't already exist.
note: This is likely not the most efficient or eloquent solution, but it does work, and is pretty simple to understand.

Keeping a terminal window open after running script from crontab

I have this script
#!/bin/sh
curl -4 http://wttr.in/Colorado\ Springs
that I want to automatically execute each morning. I have my crontab entry as
* 7 * * * (path to script)
But either the script doesnt run, or it runs and then immediately closes the shell. I know that my cronjobs are running as I have other scripts for backups that run on an hourly basis but cant figure out what detail I am missing here. I found one suggestion to include $SHELL in the script but that made no difference. Any suggestions?
Usually when I have to keep the terminal open I would exec bash as my last command. I do that when I write installer script which would open terminal; do the job and get lost after that. But if there is an error then I want the terminal to stay there so that I can read the error.
exec is used to replace the current program with argument which we provide to exec.
Actually, I don't know what are you trying to achieve with this call in your crontab. Do you want to see the weather report on your terminal? Do you want to save the weather report in the file? Get it in your emails?
If you do no redirections, you'll get the report in your mail.
If you want to have it in a file, just do:
curl wttr.in/Colorado+Springs > file
If you want to have it on you terminals do
curl wttr.in/Colorado+Springs | wall
Please note that you don't need -4, http:// and you can replace \space with +.
(DISCLAIMER: I'm the author of wttr.in)

Cannot run bash script from crontab when it works from command line bash

I have a strange problem of being to able to run a bash script from commandline but not from the crontab entry for root. I am running Ubuntu 12.04.
* * * * 1-5 root /home/xxxxxx/jmeter/VerificationService-0.0.1-SNAPSHOT/jmeter-cron-randomise.sh >> /home/xxxxxxx/jmeter/VerificationService-0.0.1-SNAPSHOT/cron.log
If I run the script from the cmd line using bash, it works fine but sh fails with following error:
> jmeter-cron-randomise.sh: 7: jmeter-cron-randomise.sh: arithmetic
> expression: expecting primary: " % 1 "
Having googled the problem, it seems like standard shell doesn't have the same math operators, like % (modulus), as bash. I'm Not sure why the cron job is failing in the script? I am assuming it is because it's not using the bash shell? It's definitely being fired by the cron daemon (can see it in /var/log/syslog). Any help much appreciated.
You likely need to tell cron that the shell to use is the bash shell as it defaults to sh. You can do that for all crontab entries by putting this line in your crontab:
SHELL=/bin/bash
Note that this will cause all scripts in the crontab to be run under bash which may not be what you want. If you want to change the crontab line itself to just run bash, change it to this:
* * * * 1-5 root /bin/bash /home/xxxxxx/jmeter/VerificationService-0.0.1-SNAPSHOT/jmeter-cron-randomise.sh >> /home/xxxxxxx/jmeter/VerificationService-0.0.1-SNAPSHOT/cron.log 2>&1
Note that I have also caused stderr to be written to the cron.log file (2>&1) which may not be what you want but is pretty common practice. This may help you further diagnose errors from the script.
In case this helps anyone: for me this appeared to be because I had ended up with "DOS" line endings (CR-LF) instead of "unix" line endings (LF). This can be checked using od or your favourite hex dump tool, e.g.:
od -c <script_file>
... and look for \r\n instead of just \n.
It seems (and this article supports it) that the CR character stops the "shebang" from working because it's interpreted as part of the shell executable's filename.
(The line endings themselves appeared because the file came from a git repository and was transferred via a Windows machine).
I also encountered this problem trying to schedule a database backup as root and it made me pull my hair out! I was working on a CentOS 7 box.
Whenever I would check /var/spool/mail/root I would see a log:
sh: root: command not found, yet the command would run perfectly in the terminal.
This is what worked for me:
I created the crontab entry using crontab -e while logged in as root.
Using the command above as an example:
* * * * 1-5 root /home/xxxxxx/jmeter/VerificationService-0.0.1-SNAPSHOT/jmeter-cron-randomise.sh >> /home/xxxxxxx/jmeter/VerificationService-0.0.1-SNAPSHOT/cron.log
I deleted the root user entry like:
* * * * 1-5 /home/xxxxxx/jmeter/VerificationService-0.0.1-SNAPSHOT/jmeter-cron-randomise.sh >> /home/xxxxxxx/jmeter/VerificationService-0.0.1-SNAPSHOT/cron.log
That solved my problem.

How to test things in crontab

This keeps happening to me all the time:
1) I write a script(ruby, shell, etc).
2) run it, it works.
3) put it in crontab so it runs in a few minutes so I know it runs from there.
4) It doesnt, no error trace, back to step 2 or 3 a 1000 times.
When I ruby script fails in crontab, I can't really know why it fails cause when I pipe output like this:
ruby script.rb >& /path/to/output
I sorta get the output of the script, but I don't get any of the errors from it and I don't get the errors coming from bash (like if ruby is not found or file isn't there)
I have no idea what environmental variables are set and whether or not it's a problem. Turns out that to run a ruby script from crontab you have to export a ton of environment variables.
Is there a way for me to just have crontab run a script as if I ran it myself from my terminal?
When debugging, I have to reset the timer and go back to waiting. Very time consuming.
How to test things in crontab better or avoid these problems?
"Is there a way for me to just have crontab run a script as if I ran it myself from my terminal?"
Yes:
bash -li -c /path/to/script
From the man page:
[vindaloo:pgl]:~/p/test $ man bash | grep -A2 -m1 -- -i
-i If the -i option is present, the shell is interactive.
-l Make bash act as if it had been invoked as a login shell (see
INVOCATION below).
G'day,
One of the basic problems with cron is that you get a minimal environment being set by cron. In fact, you only get four env. var's set and they are:
SHELL - set to /bin/sh
LOGNAME - set to your userid as found in /etc/passwd
HOME - set to your home dir. as found in /etc/passwd
PATH - set to "/usr/bin:/bin"
That's it.
However, what you can do is take a snapshot of the environment you want and save that to a file.
Now make your cronjob source a trivial shell script that sources this env. file and then executes your Ruby script.
BTW Having a wrapper source a common env. file is an excellent way to enforce a consistent environment for multiple cronjobs. This also enforces the DRY principle because it gives you just one point to update things as required, instead of having to search through a bunch of scripts and search for a specific string if, say, a logging location is changed or a different utility is now being used, e.g. gnutar instead of vanilla tar.
Actually, this technique is used very successfully with The Build Monkey which is used to implement Continuous Integration for a major software project that is common to several major world airlines. 3,500kSLOC being checked out and built several times a day and over 8,000 regression tests run once a day.
HTH
'Avahappy,
Run a 'set' command from inside of the ruby script, fire it from crontab, and you'll see exactly what's set and what's not.
To find out the environment in which cron runs jobs, add this cron job:
{ echo "\nenv\n" && env|sort ; echo "\nset\n" && set; } | /usr/bin/mailx -s 'my env' you#example.com
Or send the output to a file instead of email.
You could write a wrapper script, called for example rbcron, which looks something like:
#!/bin/bash
RUBY=ruby
export VAR1=foo
export VAR2=bar
export VAR3=baz
$RUBY "$*" 2>&1
This will redirect standard error from ruby to the standard output. Then you run rbcron in your cron job, and the standard output contains out+err of ruby, but also the "bash" errors existing from rbcron itself. In your cron entry, redirect 2>&1 > /path/to/output to get output+error messages to go to /path/to/output.
If you really want to run it as yourself, you may want to invoke ruby from a shell script that sources your .profile/.bashrc etc. That way it'll pull in your environment.
However, the downside is that it's not isolated from your environment, and if you change that, you may find your cron jobs suddenly stop working.

Resources