0

Some colleauge suggested that I added a cron job for doing some things, but executing crontab -e and adding the following line:

 0 1 * * * . /path/to/some/file.bash

To make sure this thing was working, I changed it to

 0 1 * * * . /path/to/some/file.bash && date >> /some/log

so that I could check that /some/log has indeed none more line per day.

This did not happen though.

For the purpose of debugging, I've just added these three lines to crontab -e,

 * * * * * echo "uffa" >> /home/me/without-user
 * * * * * me echo "uffa" >> /home/me/with-user
 * * * * * . echo "uffa" >> /home/me/with-any-user

where me is my user name, which resulted in all three files being created, but only the first one growing one line per minute, as you can see from this check I'm making after 8 minutes:

$ for f in ~/with*; do echo ''; echo $f; cat $f; done

/home/emdeange/with-any-user

/home/emdeange/without-user
uffa
uffa
uffa
uffa
uffa
uffa
uffa
uffa

/home/emdeange/with-user

What's happening? Are the second and third line using a wrong syntax by having one more entry on the line? If so, then why does the file get created anyway?


I've just verified that running a nonsense command like jflkdasjflaksd > someFile does create an empty someFile, which tells me that the two lines

 * * * * * me echo "uffa" >> /home/me/with-user
 * * * * * . echo "uffa" >> /home/me/with-any-user

are just wrong, and the files are created before the error even takes place, because of how shell command line processing works.

However those are the lines that work for somebody else. What is happening?

0

2 Answers 2

5

Ok, first, there's two slightly different crontab formats. The one used for per-user crontabs, and another for the system crontabs (/etc/crontab and files in /etc/cron.d/).

Personal crontabs have five fields for the time and date, and the rest of the line for the command. System crontabs have five fields for the time and date, a sixth one for the user to run the command, and the rest of the line for the command. For totals of 6 and 7, if you like, though the last "field" with the command is a bit differently defined than the others.

Personal crontabs don't have the username field, since it's implicit from the crontabs who the owner is, and regular users aren't allowed to run programs under the identity of others anyway.

(As noted in comments, the root user's personal crontab is also just a personal crontab like that of any other user. It does not have the username field, even though root is a bit special in other ways. So not only is /etc/crontab a different file from the one you get with crontab -e as root, it also has a different format.)


Then there's the .. It tells the shell to read the script named as an argument, and to run it in the current shell (Some shells know source as an alias for .). Any function definitions and variable assignments would be visible after that, unlike when running a script as a separate program.

The line

0 1 * * * . /path/to/some/file.bash

tells the shell (that cron started) to run .../file.bash in the same shell. I'm not sure why they'd recommend doing that instead of just running the command directly without the dot. There's a possible slight optimization in not having to initialize a new shell, but the downside is that the script has to be runnable in the shell that cron starts. That wouldn't work if cron starts e.g. a plain sh, but the script is for zsh, or for Python for that matter.

If that line was in a global crontab, it'd mean to run /path/to/some/file.bash as the user .. That's likely not how it was meant.

I'd suggest just this for simplicity (after making the script executable and adding a proper hashbang line, if not already done):

0 1 * * * /path/to/some/file.bash

Then, if the . /some/script && date >> logfile didn't work, the first thing to look is if the script exits with an error. You used the && operator there, and it tells the shell to only run the right-hand command if the left-hand one exits successfully. You could do . /some/script; date >> logfile to run it unconditionally. Or heck, you could try . /some/script; printf "run at %s, exit status %d\n" "$(date)" "$?" >> logfile to save the exit status too.


As for these:

* * * * * echo "uffa" >> /home/me/without-user
* * * * * me echo "uffa" >> /home/me/with-user
* * * * * . echo "uffa" >> /home/me/with-any-user

In a personal crontab, the first tells the shell to run echo, the second to run a command called me, and the third to source a script called echo. All contain a redirection, and redirections are processed by the shell before the command starts, so your file is created in all cases. (They have to be, since the shell can't know if the command is runnable before it tries to, and if it succeeds, control passes to that command, so the shell can't do anything about the redirections any more.)

The two later ones probably give error messages, which you should get in email if your cron is set up properly.


However those are the lines that work for somebody else. What is happening?

As mentioned above, . /path/to/some/script tries to run the given script in the shell, it'll fail for a binary command, so . echo ... is not likely to work. 0 1 * * * username echo ... would work in a global crontab, but likely not in a personal one. 0 1 * * * . whatever isn't likely to work in a global one, as . probably isn't a valid username.

1
  • @KamilMaciorowski, right, cron doesn't care what the UID of the user is, or whatever; any crontab of a named user in /var/spool/cron/cron/crontabs (or such) is just like any other one. Added a note on that. (Annoyingly, root's personal crontab is/can be still there, so it's one more place to look in if you don't know where some job is defined.)
    – ilkkachu
    Commented Aug 10, 2022 at 14:00
1

The first line (without user name) is the syntax for user-owned crontab:

min hour dayofmonth month dayofweek command ...

The second line (with user name) is the syntax for /etc/crontab:

min hour dayofmonth month dayofweek username command ...

The third line is just incorrect. Technically "dot" command sources the script from file named "echo" and sets the "uffa" as an argument, but I doubt you have a script in a file named echo. On my system there is a binary /usr/bin/echo and attempt to source it throws an error:

...$ LANG=C . echo
bash: .: /usr/bin/echo: cannot execute binary file

I believe if you look at your system logs, you'll see this error message appearing from the cron daemon.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .