using php command on cronjob
I was struggling with issues related to cronjobs, and after considerable poking around and getting help from others, I now have it working right. So, I thought it a good idea to post it here in case others are having a similar issue. I'm using CENTOS 7.
- The "php" command is used to execute a block of php code, on the command line or with a cronjob. The format is simple:
php [fully qualified pathname to the file].php
- The part that caused me trouble was needing to have the first lines in the file as shown here. And followed by your php code in the normal way:
#!/usr/bin/php
<?php
[lines of php code]
?>
So my cronjob is simply:
[cronjob definition] php /../../../myphpcode.php
That "#!/usr/bin/php" was the stumbling block for me.
(My code include mysql database operations coded in php of course. One way I use this is initialize tables in various databases once a week.)
9 Replies
✓ Best Answer
You write:
So my question is, does this conform to your recommendation because of the shebang in the target script with executable permission, but that flies in the face of the 'php' in the cronjob command.
In
5 14 * * fri root php /home/ken/scripts/DuplicateBridge/resetfriday.php > /dev/null
if you have the shebang and the file has executable permission, the php is superfluous. It doesn't hurt or help you. So, your crontab could be coded as
5 14 * * fri root /home/ken/scripts/DuplicateBridge/resetfriday.php > /dev/null
and it would function the same. The shebang is a comment to php(1). From man php:
TIPS
You can use a shebang line to automatically invoke php from
scripts. Only the CLI version of PHP will ignore such a first line
as shown below:
#!/usr/bin/php
<?php
// your script
?>
Finally, regarding the /dev/null, I've been told that's how to dump any output the script might generate that's not wanted.
/dev/null is a canonical Unix device that represents the bit bucket. Anything you write there disappears immediately and irrecoverably. /dev/null has been a feature of Unix since the first PDP-11 edition outlined in the Bell System Technical Journal (Vol. 57, No. 6, Pt. 2 Jul/Aug 1978). This is before Unix had virtual memory…which didn't happen until the 3BSD Berkeley distribution in 1980 (the 2nd edition for the VAX).
Although most people consider /dev/null an output-only device, it is not. You can read from it as well. When you do this, your program will get EOF on the first read attempt. It's fairly common to see stuff like:
/the/path/to/foo < /dev/null
in test suites to test if foo handles EOF on stdin gracefully.
If you really want to get rid of all extraneous output, you should change your crontab line to be like this:
5 14 * * fri root php /home/ken/scripts/DuplicateBridge/resetfriday.php > /dev/null 2>&1
to send stderr to /dev/null too (2>&1
means 'append stderr to stdout').
So, here's a thought… Why don't you do a little bit of work on your cron(8) jobs and implement logging so that your jobs leave tracks? Here's the Unix-y way to do it using syslogd(8):
https://www.php.net/manual/en/function.syslog.php
and here's one that's simpler using php(1) facilities only:
https://www.go4expert.com/articles/simple-logging-php-t1036/
Since your jobs are running as root, you can create files in /var/log and write to them. It makes solving problems like your initial report about cron(8) much easier. Also, because (I gather) they do database operations, they are necessarily going to be i/o-bound…and a little more 'o' is not going to materially affect their performance (frankly, your time is much more valuable).
Hint: if you write your own log files (i.e. don't use syslogd(8)), you'll want to have Linux rotate/remove them automagically. See:
https://linux.die.net/man/8/logrotate
For extra credit, implement something like these on your jobs:
• -D, --debug
An integer in the range 0..5 indicating the verbosity of
diagnostic info. The default is 0.
• -n, --dry-run
Log what the script would do but don't actually do it.
• -d, --database
An string giving the database name on which to operate.
The default is 'whatever_your_production_database_is'.
See: https://gist.github.com/DenesKellner/29a696e4420a4f3e162a935e5cb9ab4f (this may be php(1) version-dependent…it uses lambdas…I don't know).
--database, --debug and --dry-run will help you compress those 24-hour-to-one-week wait times to several hundred runs in a few minutes when coupled with a new script to load a test database with test data.
Further hint: in --debug and --dry-run, these
https://www.php.net/manual/en/language.constants.magic.php
and these
https://www.php.net/manual/en/reserved.constants.php
will be your friends (the former probably much more than the latter).
-- sw
@k4c4gorman --
See my last post to your thread:
First, your path /../../../myphpcode.php is clearly in error. The leading / makes it so… I've corrected this obvious typo in my references to this path in the text below.
Second, If you want to use the #!/usr/bin/php shebang notation,
- ../../../myphpcode.php needs to have executable permission (-rwxr-xr-x);
- ../../../myphpcode.php is a filesystem path relative to cron(8)'s idea of CWD…not the same as your login session's idea of CWD (ditto for HOME); and
- the crontab entry should just be
../../../myphpcode.php
…the php part is superfluous.
Third, if you want to run your script as
php ../../../myphpcode.php
then it's a sure bet that cron(8)'s idea of PATH is not the same as your login session's idea of PATH. Use the fully-qualified path for php(1):
/usr/bin/php -f ../../../myphpcode.php
#2 also applies.
cron(8) is not the shell!!! ALWAYS use fully-qualified filesystem paths for ANYTHING you put in crontab files!
-- sw
@stevewi --
Thank you for those comments. First, here is a sample that is rock-solid (hiding part of the path).
3 22 * * mon root php /home/.../.../.../run_monPM.php
Regarding your comment --
"your path /../../../myphpcode.php is clearly in error. The leading / makes it so"
-- that leading '/' is not a typo. I copied that line directly from the cron table (again, hiding the "interior" part of the path).
Does it surprise you that it works? I'm quite open to learning why my implemtation is wrong and what I assume are special conditions at play in my case that makes my implementation work. Is accessing the cron table as root the reason?
Perhaps my 'path' description in my post is weak, but the above reliably works. I'll continue to monitor the results to see if there's some quirk that gives me success but won't under general conditions.
The fully qualified path discussion leaves me confused. That #!/usr/bin/php
in my executable seems to take care of finding php. What am I missing here?
I certainly agree with the executable permissions requirement. And it is exactly as you state in my case.
I didn't read anywhere that the 'php' is superfluous. Interesting. I'm not inclined to edit it out as I find it soothing :) to see a command there. OK, I'm weird.
Regarding your comment -- "your path /../../../myphpcode.php is clearly in error. The leading / makes it so" -- that leading '/' is not a typo. I copied that line directly from the cron table (again, hiding the "interior" part of the path).
Just because you copy-pasted it from someplace else does not make it correct. /../../../myphpcode.php indicates that mycode.php lives in a directory 3 levels above the filesystem root. Unless you're using some weird filesystem that supports multiple levels of meta-roots, this is impossible.
Take a minute and think about this:
- / -- starts at the filesystem root;
- ../ -- go up one level from there (impossible so ignore);
- ../ -- go up one level from there (impossible so ignore);
- ../ -- go up one level from there (impossible so ignore);
- mycode.php -- look for a file called mycode.php.
What you should be saying is /mycode.php if that's what you mean…rather than relying on this cryptic quirk in filesystem path notation. What are you going to do if you move to a new distro that doesn't do this?
I won't even begin to address what kind of Russian Roulette it is to store application files at /…
Similarly, your command
3 22 * * mon root php /home/.../.../.../run_monPM.php
is also in error. There is no such notation '…' in a relative filesystem path. Perhaps you meant '..'?
Does it surprise you that it works?
Yes…absolutely and without qualification… This is probably a defect in how your version of php(1) interprets filesystem paths given on the command line (the file path you give is interpreted/resolved by php(1)…not cron(8) or the shell). What are you going to do when you move to a new version of php(1) that fixes this?
I'm quite open to learning why my implemtation [sic] is wrong and what I assume are special conditions at play in my case that makes my implementation work.
I'm glad it works for you. However, I'm equally glad I'm not the one who has to support this stuff ;-)
In the product divisions at (Bill 'n' Dave's) HP, we had a local colloquialism for "stuff that shouldn't work but does" -- tribal magic. Tribal magic was always a bad thing™… It always changed when you changed tribes (often defying your perception of the physical universe and your previous experience). Tribal magic was always the driver behind TONS of extra work writing reams of mostly worthless documentation to explain it to some marketing 'droid who fancied himself an engineer or the next (usually younger) guy in the food chain when you were moving on.
Worst of all, tribal magic usually meant that you (as the new initiate) had to figure it out without the wisdom of the shamans (who had all moved on to other jobs) when the CEO of SuperMegaCorp Consolidated, Inc., the proverbial big customer, reported it as a bug and the repair got assigned to you…and the customer wants a fix plus verification plus tests last week!
See also: Hey…it's Windows… -- a problem-domain-specific synonym for tribal magic that is specific to the the coding practices of tribes in/around Redmond, WA…usually uttered in exasperation.
Is accessing the cron table as root the reason?
Probably not. For the most part, the super-user only has privilege to bypass the file ownership/permission mechanisms of the filesystem (the reality is more nuanced than that but you can Google the exhaustive explanation).
I didn't read anywhere that the 'php' is superfluous. Interesting. I'm not inclined to edit it out as I find it soothing :) to see a command there.
See: https://en.wikipedia.org/wiki/Shebang_(Unix)
You want EITHER
- shebang and executable permission; OR
- php(1) and no executable permission.
Both are superfluous. php(1) and executable permission has the potential be dangerous…especially around non-privileged users and others you don't implicitly trust to do the right thing™. Even in the first case, if you have people using your system that you don't implicitly trust, you want to limit executable permission…not give it to the world. This is why Ken Thompson implemented permissions in the first place (since you know it's a system cron(8) job, I would make the permissions -rwxr--r-- …or even -rwxr-x---…and give the file ownership of root/root -- depends on how lucky you feel and how much damage the script can cause if misused).
Choose (wisely) and be consistent…
OK, I'm weird.
Aren't we all?
Anyway, since your problem has been solved, I'm going to end my participation in this discussion. You have your assignment. This material will be covered on the final exam. Good luck to you. Peace out.
-- sw
I will study your post very carefully.
One thing that needs clarification is the /…/…/ is meant to indicate ellipses to represent something legit goes there -- very unfortunate "code". Ugh! I don't have that as code anywhere, just in these posts. My bad. That obviously negates some of your comments. Sorry to put you through that weirdness.
The actual path, and actual filename, is
/home/ken/scripts/DuplicateBridge/resetfriday.php
Are you saying that the cronjob should actually just be
/resetfriday.php
That doesn't make sense to me. Like I say, I will study what you wrote, but know what that actual path is maybe would give you a different commentary on this topic.
Another thing about /../../../myphpcode.php
.
"myphpcode.php" is a made-up name for a sample custom script that does the work on a selected table within that script. The "resetfriday.php" you see above is the actual name for a script that operates on a table pertaining to the DuplicateBridge website. And I believe that this code means my custom script lives three levels below root. Can't be anything wrong with that.
Stay tuned and I'll get back to you. The first thing I will do is go root:root and change the permissions.
You write:
One thing that needs clarification is the /…/…/ is meant to indicate ellipses to represent something legit goes there -- very unfortunate "code".
I always explain that situation this way:
/the/absolute/path/to/whatever/mycode.php
The path names . and .. have special meanings.
You also write:
The actual path, and actual filename, is
/home/ken/scripts/DuplicateBridge/resetfriday.php
Are you saying that the cronjob should actually just be
/resetfriday.php
That doesn't make sense to me
It shouldn't because that's not what I said. What I said was that
/../../../mycode.php
was the same as
/mycode.php
for the reasons I explained.
You write:
And I believe that this code means my custom script lives three levels below root. Can't be anything wrong with that.
No… it means 3 levels above the root…again for the reasons I explained. Path interpretation commences from the first component of the path (/) not the last (mycode.php). If you had coded it as (without the leading slash):
../../../mycode.php
the path would be interpreted thus:
- starts at $CWD (whatever that is for the program processing the path);
- ../ -- go up one level from there;
- ../ -- go up one level from there;
- ../ -- go up one level from there;
- mycode.php -- look for a file called mycode.php.
-- sw
I need to clarify why I'm still engaged in this discussion and layout my exact plans to see of I'm properly understanding your valuable advice.
First, my cronjobs fire off at various days/times throughout the week. I saw the trend I hoped for over the week and figured this was all put to bed. But today, the friday job didn't work.
Here's my take on this, without needing any ../../../../.. representation and up/down levels explanation. The code will speak for itself.
- I believe this correctly addresses the friday target script:
/home/ken/scripts/DuplicateBridge/resetfriday.php
- This shebang code appears as the first line in the target script. Here's the structure of my various scripts driven by cronjobs:
#!/usr/bin/php
<?php
[php code]
?>
I believe the shebang serves the purpose of locating the server php code for execution of my php code that follows it. By the way, I executed that resetfriday.php script today "directly" (not depending on cron) and it did do the expected operation on the associated table.
- About your comment
shabang and executable permission; OR php(1) and no executable permission.
I'm using the shebang inside my target script as you see above, and permissions/ownership for the actual target script shown here:
rwxr-x--- 1 root ken 251 Oct 18 17:37 resetfriday.php
and cronjob like this:
5 14 * * fri root php /home/ken/scripts/DuplicateBridge/resetfriday.php > /dev/null
(will mention that /dev/null below)
So my question is, does this conform to your recommendation because of the shebang in the target script with executable permission, but that flies in the face of the 'php' in the cronjob command.
In other words, I am using the shebang and the php, albeit one in the target script and the other in the cronjob. Yes, I confess to being confused about that. Could you please clarify for me.
Finally, regarding the /dev/null, I've been told that's how to dump any output the script might generate that's not wanted.
Thank you again.
@stevewi
Thank you for all that! You've been so helpful to me working through something in which I've had very little experience as you can tell. Others will be fortunate to have you looking in on their activities and lending a helping hand.
The other thing you should look at is composer(1):
so you can take advantage of others' work. MySQL, logging, daemonisation, etc are all subjects of phenomenal amounts of work by others. composer(1) lets you use that work in your own projects so that you can save time by not re-inventing and testing it.
For example, here's one for logging that's pretty comprehensive:
https://github.com/Seldaek/monolog
I haven't coded in php for a long time… I'm primarily a Ruby/Python guy…and, when I need a compiled language, Go or Rust. After several years of doing Ruby/Python, I find php to be unnecessarily verbose because of it's too-much-C++-like syntax. IMHO, php has other deficiencies:
There's no debugger that isn't flaky and not a PITA to setup and keep working unless you spend $$$ on one. Writing debug information to log files and trying to figure out what went wrong after the fact is so 1970s…
php is effectively a typeless language. It will still send your program off into the weeds when you do
if ($a = $b) {...}
when you meant
if ($a == $b) {...}
God invented computers to figure this stuff out for me!
That's not to say php isn't useful…it is…to a huge number of people. I'm just not one of them anymore. It took me awhile to figure out that php had run out of gas for me…probably far longer than it should have (old-fart-hood dies hard).
I could write reams on why I loathe perl(1) too… Suffice to say, a language where any string of random punctuation is a valid program is just wrong…so totally wrong… Useful? Perhaps… Then again, a crowbar can be useful (?!?!?!? … prob a valid perl(1) program) in opening a can of beans too ;-)
-- sw