A clear and practical introduction to system-level logic for C beginners. While educational, it oversimplifies the complexities of process management and timing accuracy found in professional tools.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Cron Jobs in C from Scratch
Added:Hello everybody and welcome back to a new programming session. In today's session, we're going to explore how cron jobs work, what they are, and we're going to create a small minimal cron job implementation in C ourselves. It's going to be super simple, super minimal.
It's going to just execute a task that we want to execute in certain intervals.
So, let's have a look at what crownrops even are. What is a crown? A chronoprop is an automated task that is scheduled to run automatically on Unix like operating systems like like Linux or Mac OS at specified times or intervals.
What's the point? For example, you have a directory and you want to make sure that this directory is always empty.
Then you can create a chron job, a so-called chronop. Why does it even Well, then you can create a chron job.
And that chronoprop can be any command with any interval that runs at any interval. And for example, you can you can uh automate a removal command, the rm command to remove a specific directory in at certain inter intervals. Or you can automate a CP command to copy files from a source folder to a destination folder at certain intervals to back up your data to an external hard drive or use something more elaborate such as rync in intervals which also backs up your data or even our clone which backs up your files for example nightly something like that. That is what a chron job is. That's the purpose. You want to you can even create uh a script that automatically runs every day or every hour or every minute.
This is super powerful for automating your life and it always runs in the back background.
Chron is a service in Linux that always runs in the background. So you after restarting your computer you don't have to manually start a process again that runs and period periodically um does some action. No, you just submit a task to um the chron demon and then that task is repeatedly executed.
For example, if we on Linux do chronab e, then that's the that's the way to edit chron jobs in Linux. For example, I have a single chronop um in this file.
This opens just a simple file that you can define your chronob.
For example, I have a chron job that removes the thumbnails directory.
every um and let's check the syntax.
There's a zero here, an asterisk followed by three more asterisk and then the actual command that we want to execute here.
The syntax here is that let's have a look here to define the time you can provide concrete values for minute, hour, day of month, month and [snorts] day of the week. And I want to remove this directory every single minute. This is just a sample command because it's also possible to simply deactivate thumbnails in Linux. That would solve the problem as well. But uh I have set up this command and every single minute my thumbnails directory is cleared. This saves me some storage space and also I don't need to keep any thumbnails.
Then hour is every hour. day of month is every day of month etc. So this is the command to set to remove a specific directory in the zerooth in the zero minute uh or is this every hour? This might be the minute zero every hour, right? That that is an every hour executing command. So it's it's an hourly command. I was mistaken. All right. And that's what crunch jobs can do.
Yeah. And if you want to execute something every minute, you just replace the zero here with an asterisk. This this would be every minute, every hour, every day, every month, every year.
All right? So there's some syntax to it.
We're not going to do to implement this syntax in our C program as elaborately.
We're just going to find some simpler solution.
And then let's see what else there is to talk about. So chronoprops are driven by the chron demon. Chron d it's a background process that continuously checks the system time and configuration files every minute. So this file right here is the configuration file and every minute the service checks this file checks if anything has been updated. If no then well it reads the file and just executes the job the jobs that are due.
And that's already it. It's super super simple. And this is an example here for example every zerooth minute every 3 hours every day every month every year. So minute zero hour ah no in minute zero hour three of every day we back up something.
All right.
So, this is how chronobs work. And to create our own little chron job variant, I would say we can just do something super simple like um let's create a new directory and call it chron my chron job or chron chron executor or or chronrunner something like that.
Chronunner chronrunner then create a source file chronrunner C include standard IO and a main method.
That's already it. I don't need any input arguments. I just need a print f here. Print f running the executor for now. We're also not going to create a background process. Well, you can make this a back background process by configuring a systemd service. If you want to find out about that, then look up systemd and then create service. And then you can also auto start this executable and ensure that it it's always running in the background. We're not going to do that. We're going to use the we're going to do something bare bones today. So in our chronrunner projects directory, there's now a source file. We're going to make it chronrunner.
And that compiles the file. As you can see, it maps to the CC chronrunner. C-or chroner um command here. And now now we can execute it. And it prints out running the executor.
Now we want to be able to specify a task that we want to execute. How do we do it? Well, we first want to be able to run any task. And as you can see in the chronab uh chronab program here, the task is a simple bash command, right? So, it's an executable with arguments.
And how does Chron likely execute this? I would say it uses system probably. System is a is something that I haven't even used before. It's a standard C library function apparently that takes as input a command which is a string. As you can see here, it's a constant character pointer and then it forks the process, creates a child process and then execute this executes the shell command specified in command using exe.
Okay. So somehow for us the relevant part is that this command that we pass here is executed and we can try it. So let's do system and what can we let's try to to execute something for example we want to execute the command let's try some command in the in the bash terminal first print f hello hello and as you can see it prints hello like or maybe even simpler echo echo hello then it prints hello. So if we type echo hello here then compile this. Let's just write up a proper compilation command rather than using make chron uh cc chronrunner c output file is chronrunner and then I also want to enable all warnings and extra warnings and I want to use GCC.
And now it's warning me about an implicit declaration. That's because I haven't included the standard lip.h file here.
Include standard lip. And now if I compile this, it compiles without a warning. And now I expect that it prints out running the executor. And then the system command here runs and it prints out hello. So let's try it. So compile.
Ah no I wanted to compile using this command and let's run it. And as you can see the hello the echo hello resulted in hello being printed to the console. And now you can make this arbitrarily complex.
And this is by the way a way to a way to make to to use C as an automation language. You can just execute shell commands like that. So let's try again.
And as you can see now I'm listing the directories here the directory contents here. And if I execute the chronrunner it prints running the executor and then prints the output of ls-lh.
Perfect. So this is already a very good intermediate result because we're now able to execute commands.
And now all that's left for something like chron um like the chron like a real chron job is to have a file where we can specify um some interval. And the simplest interval for me would be for this for the sake of this video seconds because I want to be able to see results quickly and I want to invoke some command every single second. So we don't have to wait minutes. But also you can of course you can just provide a really large number and then it will be many hours or days or even weeks >> [snorts] >> um of an interval. All right. So what do we do for example? Super simple. Let's just assume we have an integer or a long and that long integer or maybe even an unsigned long just because we want to make sure that we fit a large number is the interval. And the interval is let's just say it's the interval in seconds and the value is one.
>> [gasps] >> But actually, it's an over complication.
I create such a simple program and I make and I um nitpick at at the data type. I should just use int. It's simpler for the sake of this video. If you want to go make some if you want to use some really large intervals, then use an unsigned long, but I'm not going to over complicate my my small executor here, my small chron executor here. So I have an interval of a single second and I want to be able to execute this command every single second. So let's do a while true.
So as long as the program is running, we do something. And what is that something? It's the system command. And of course now we need to wait a bit. And for waiting we just use the sleep command. And here we need to be careful because if we look up the man page of sleep then this yields the user commands page which is the bash command for sleep. If we want to make sure that we use the C library sleep command then we have to do man three sleep. Three is standard C library as far as I know.
Yeah it's a library functions manual.
So there's the sleep command. We sleep a certain number of seconds.
That's the interval. And for it to work, we need to include uni std.
Just let's just have a short look at the description. Sleep causes the calling thread to sleep either until the number of real-time seconds specified in seconds have elapsed or until a signal arrives which is not ignored. By the way, there's a small um detail here.
This refers to real time seconds. So this is the number of seconds you experience as a human. But there's also something like a processor time. um which does not count the time that a thread is suspended or sleeping and only counts the time this the thread the processor is actually doing things. But of course um we care only about real time because we are humans.
And so we just need to do sleep and we sleep for our interval and we include uni std because this was part of the main page. And now actually I think we should be able to compile this and run this command. Let's see what happens. I would expect now every single second that the directory entries are the directory contents are printed.
Let's have a look.
And as you can see, every single second our command is executed. That's nice.
And now I've messed up my terminal. Ah, this seems to happen sometimes if I do some weird character combination. Um, how do I escape this?
Oh god. Let me just close this one. I can't even close this. Ah, I can here.
And let's do a new terminal. And there we go. This one was quick to fix.
Okay, another sip of coffee and let's continue. So now we have a minimal executor. And by the way, if you have some super simple task and you want to execute it periodically, then all you need to do is actually set the interval here according to what what you want.
For example, if you want to execute something every single minute, then you just specify an interval of 60 seconds.
Specify your command here. It can be anything. And then you sleep for that.
and then you yeah this just sleeps for that interval and takes care of doing that job. So this is technically a chron job. It's just a hardcoded chron job. Um the only difference is that this does not run automatically. So if you want to run this automatically, you can for example add it to the startup applications on your machine where you can just add a custom command here and specify that you want to execute from your projects directory from the chroner. You want to execute the chroner and then this will execute um your personal chron job. Okay, so that's already it. This will then automatically start whenever you run your computer and it's already working. So if you want to do something minimal, that's already it.
Now to get a bit closer to the actual chronab implementation or chron job um which has this chronab thing here and some file configuring the chron jobs. We should also introduce some kind of format. For example, let's call let's create a file called chron job.comcon.
And in this file, I want to be able to specify a number of seconds such as 60 or let's just do one. And this number of seconds is followed then by it's followed by the command that I want to execute. For example, ls- LH. So this is now my configuration file and it specifies the interval. every single second I want to be able to execute ls-lh.
That's already it. And let's try to now load this file here. Load the interval and load the command. This is now exciting because I don't know how to parse these files properly.
Um so see how to parse a file.
read text from a file and parse lines into words in C. So this sounds like it's related.
We can of course use fget get C which gets characters from a file. Okay.
Is there a read line function in C?
Read line function in C.
There is a read line function. Yes.
Okay. So this might be it. It gets a line from a user with editing from a user. Ah, this is from this is reading a line from a from the user's uh keyboard.
So, it's not reading from a file. It's not exactly what we want. Read line returns the text of the line read. A blank line returns the empty string. If end of file is encountered while reading a line and the line is empty, read line returns null. Or is this even?
Yeah. No, this is reading from from the user via some prompt. So this is not what we want read line from file function in C.
Ah there is f get s. So let's have a look at the man page for fget get s here and this is a function defined in standard io and [clears throat] it here let's go down. Fgets reads in at most one less than size characters from stream.
So we have some file that we had have to open here and stores them into the buffer pointed to by s reading stops after an end of file or a new line.
If a new line is read, it is stored into the buffer [clears throat] fgets.
So, we're going to use this this function.
And how do we how do we use it?
Actually, the real chronob um reads this file every single minute. We're not going to do that. We're just going to read the file at the beginning of the program's execution.
So, let's do it here. F.
And we want to read into a into a target buffer a string of a maximum file size from a given stream from a file. Files and streams are the same thing. If you want to learn that concept and um deepen these uh these standard C standard library concepts then have a look at the non nonsense C programming and then have a look at the non nonsense C programming course which I have linked in the description below the video where I explain all these things in detail.
So from SK fgets we write into some buffer for example entry let's call it buffer entry this is a character array of let's say we accept a th00and characters it really doesn't matter. We can also mems set that entire entry uh mems set to zero just to be sure there's no garbage data inside. So we set the the buffer to value zero of size n. So the buffer entry value zero and the maximum size is now size. I'm going to define that value in a second. Size here.
And now let's do define size,000.
So we allocate this buffer on the stack.
Then we set all values to zero. And now we read from a file and we read up to size bytes into the entry buffer.
And we want to read from a [snorts] file.
Let's call it the conf file.
And now we only need to properly open the configuration file. And we can do that using f open man. This is a file stream opening function here. So we specify a path name and a mode. Let's do that. So file file pointer configuration file is equal to f open and we open in the current directory.
Let's just hardcode this uh chronob.conf conf and the mode that we use is simply read only mode. So the argument point the argument mode points to a string beginning with one of the following uh sequences possibly followed by additional characters as described below. And we only need to read from the configuration file. We don't need to edit it or write data into it. So let's just open it in read only mode. another sip of coffee and then we can continue.
So just like that there's an entry. We set everything to zero. We read um technically the fop function can return null. I think if the opening fails right here upon successful completion fop return a file pointer otherwise null is returned and the error number is set to indicate the error. So let's make sure that we that we properly open the file here. If confile equals null then we lock the error using p error.
This is an error locking logging mechanism that considers the error number and prints all the information we need. But we can also append our own or prepend our own error message. So uh failed opening chronob.com and then we can return because we failed return minus one and then this. Why do we return minus one? because it's usually used as an as a failure code.
Aside from that, what is what else is there? We are opening the file. We're checking is it null? If it's null, we stop the execution and if it's not null, then we continue.
So it might be already working. We are getting the the line of text from the configuration file. we write it into entry and now I think it makes sense to print f this string here and see if it's properly read. So let's compile again. This complains about me not including string.h because this is providing me set. So let's include string.h as well. And now let's see what happens.
GCC compiles without an error. Let me just do this so you can see the compilation command. And chroner and as you can see nothing is ah no it's working because now it's partly working. The chroner is executed.
It prints running the executor here. It prints the content of the chron job.conf conf and then it continues printing the directory contents and I increased the interval I think right I increased it to 60 seconds here which is of course the reason why ls-lh is only printed once now and after 60 seconds it will probably print another time don't know if we should wait probably not we I'll just continue talking and then we will see another print out of ls-la H.
So we are properly reading this simple single line from the configuration file.
Now the next step is to parse it.
And this is the part where this ah wait now it printed yet another time. So the intervals are still working. And this is not a part which I think is not very trivial but there's a function for it. I think it's called s scanf um which is reading an input string um using some formatting string. So you pass in a string as in our case the one followed by ls-lh.
You pass in the format string that you expect and then this scanf command um puts the values into this format string. [clears throat] So I don't know exactly how it's going to handle um how it's going to handle the fact that we have a one. We want to put the one into the interval variable. Then we want to put this entire blob here into another variable. But this can be an arbitrarily long command. This could be something like this. Hello, I am Daniel.
And this would be a very long command.
So, I'm not sure how to parse this. Um, s scanf variable um no uh space separated string. Let's just check it.
Reading a string with spaces spaces with san f. So, the following line will start reading a number followed by anything different from tabs or new lines. This is Oh, this is a very similar format. So we have a number followed by a string here.
And this is a format string followed by anything different from tabs or new lines. [snorts] So anything not a tap, not a new line. But what is it? I don't know. And it's been it's put into buffer.
H.
You want the C conversion specifier which just reads a sequence of characters without special handling for whites space.
Ah, and we can just specify how many individual characters we expect here.
Okay.
Note that you need to fill the buffer with zeros first because the C specifier doesn't write a null terminator. This is what we have already done. We are already doing this with mems set.
[snorts] If you want to scan to the end of the string, just use this.
This is interesting. I find this quite This isn't a solution that I can understand.
So maybe I'm just going to use it and we're going to hardcode something that's probably trash. It's probably not ideal, but I mean, if it works, then it works.
So, let's scan or let's parse the the command. Let's parse the the chronob config. And we have already read it here.
Read the chronob config. And down there we parse it. Um we do int interval seconds is some variable.
It has no value associated with it. And we do san f and the first value that we expect is a number. So simple decimal.
The second value that we expect after the number and this by the way can be a a multi-digit number. So this will take care of it. Then we have a space and after that space let's say we expect up to 800 characters. So formatting specifier 800 characters. I know it's not ideal because the size here is 1,000. But I don't know exactly how wide this decimal can be. So I'm just going to use 800.
This means that the decimal can be up to 200 digits. This is not never going to be used by anyone.
Let's say we accept 50digit numbers.
950. So, this is probably going to be good enough. Followed by a new line.
And then I know I don't even know if I need to follow it up with a new line.
Probably this is already enough. Let's have a look here. Yeah. And then we write the value into the interval in seconds. And we write the val the rest of the value. So anything that is this aggregation of characters into the buffer. The buffer is in our case does not even yet exist.
So let's create a command buffer here.
Ah, [sighs] so this is this character array here is the entry is the complete entry for the chron job configuration file. And then we create another buffer that is a command a specific command. It's also up to size by or in our case up to 950. I don't want to do so much hard coding. Let's just do this. this I know it can overflow it but whoever is going to create a command that is 900 or a thousand characters long and when it's your fault if my program fails so I'm just going to keep it simple if we if you want to make it perfect then make it perfect I'm not going to make it perfect I'm going to make it fit most use cases for most people on earth and then the command is just a character array we need to me set it so that all values are zero We provide the command here zero and we zero out up to size and then we write the value into command like this.
And this is now now we can just print the value of the interval in seconds and we can print the value of the command. Let's just pass here parsed command is equal to a string new line and then we pass in the command here. So now I would expect that that our executable prints out the interval in seconds here and because it's one in the configuration file it's going to print one and then the parse command is going to equal echo hello I am Daniel. So, this is going to be a long command. If this works, I'm going to freak out. This is quite exciting. This is fun.
This is really um coding on the on the boundary of what I'm comfortable with.
But, okay, I'm not doing it eligently.
It's just exploring new things, which is always fun. So, warning, passing argument two of scanf from incompatible pointer type. So, I'm we're doing something wrong. I'm passing the interval in seconds as an integer pointer.
which should be correct because I want the value to be filled by s scanf and just like it's being done here. So let's reread the error. Maybe I misunderstood something. Warning passing argument two of scanf from incompatible pointer type.
I'm passing a pointer type.
It's an int pointer and I would expect that this is that we can write an int pointer here or does scanf only allow values that are no waits allows a variable arguments list and this can be as far as I know integers and all of that stuff.
Um, maybe do I just need to pass in the interval like this? But this does not make sense to me. No, no, no. This does not make sense. Something is wrong here.
S scanf is reading a decimal followed by 950 characters.
What's the error here?
expected const character pointer restrict but arguments of type int for the ah I got it okay of course um we are passing a format string here but we need to specify from which original string we want to parse the data which is what I forgot so we want to parse the data from the entry which is actually the first argument to s scanf here this is the original string followed by the format string and the rest is the number of is the variable list. So original string format string variable list and now if we compile it it works. Let's see if the result is that is the thing that I expected. So again I'm going to print or it's I expect it to print interval seconds one and parse command echo hello I'm Daniel. Let's try it.
And it indeed properly parses the configuration file. A that's awesome. So running the executor one, this is the full string and then it's parsing the string into two separate substrings. I think it would be even simpler than um to just split on the first space to be honest now I'm thinking of it. So you don't even need to use scanf. You can just split the string at the first space and then then the parsing is also done.
But okay, I have used the over complicated imprecise uh solution, but I'm going to leave it that way. Now, SKF is by I think I remember in one episode I uh parsed a string by manually splitting it somehow based on some characters that I encountered semicolons or space. I don't remember. And then someone angrily commented that I should use S scanf instead. And this time I use f sanf. And I'm almost sure that someone angrily comments or was about to comment that I should instead split the string manually. So there's never it's always um just a matter of preference. There's no real right or wrong here.
Okay.
Um, so we know that the interval in seconds is properly parsed. Let me just try to rerun this. So every single second the command hello, I'm Daniel is supposed to be invoked. Of course, this still lists directory contents because I'm passing the ls-l to system. So let's edit this and run the command that is parsed.
And if we compile this and execute it now, it's printing hello, I am Daniel every single second. And now if I edit the chron chronop conf file, for example, to invoke this every 3 seconds, let's see what happens without recompiling. I execute it. And now it's printing hello, I am Daniel every 3 seconds. So the chron job is in and of itself working.
There's just one more thing that I want to fix up because in the real chron job here chronap e uh h no chron tabap e it's possible to of course uh specify several commands like this. Of course they can all be different. Um so I want to be able to do the same thing in my chronop configuration.
So instead of just parsing the chron job config and reading a single line, I want to be able to read multiple.
So what are we doing here? We need to be able to loop over multiple commands and multiple intervals somehow.
Right? So I want to be able to do the following. Every 3 seconds I want to print out hello, I'm Daniel. And every every no let's do the following. Every second I want to print out hello I'm Daniel.
And every two seconds I want to print the contents of the current directory.
Of course you can make it arbitrarily complex or every 3 seconds.
So good.
Now let's try to figure this one out. So instead of a single interval in seconds and a single command of a specific size, we now have several.
So to parse the chronob config um well how many? Ah okay. Now, technically, we should allow a an unlimited number of of commands, of course, but we can also just hardcode it and and allow up to 1,000 commands, which is something that nobody ever going is going to exceed. And then we save ourselves the the um maloc and the memory allocation stuff.
So, I really like keeping things simple.
So let's read the chron job config and while we well we have to check something we have to check are we have we reached the end of the the end of the file let's check fget s again here reading stops after an end of file or a new line if a new line is red, it's stored into the buffer.
End of file, what is the return value? It's null on error when the end of file occurs.
So while f get s the return value of f get s is what is it? It's an integer.
No, it's a character pointer. Wait, does it return also the character pointer reads and at most less than size characters from stream and stores them into the buffer pointed to by s. What's the return value?
Fgets returns s on success and null on error. Ah okay, got it.
Character pointer um line is equal to this. And then we check if line is equal to null then we have reached the end end of file and we do the following. We do oh no we don't even need that need to store that value. We can just do while f get s is not null. That might do it.
So as long as we can read, we do read and we do.
[sighs] So I'm I'm thinking should I store all the chron jobs that I configure in a in some array or should I just read the file and execute what is due to be executed. I think reading the file and storing it somehow in some parsed format is simpler and for that a structure might do. So let's define a new structure [clears throat] strct um chron entry that is let's make this a type definition type def strct chron entry and we store the interval in seconds and a command character [snorts] command command and it's the command is up to size bytes in size.
Okay, that might do it. So, we read stuff and then I'm thinking how can I hack all of this together? So, instead of now allocating the entries here, let's do the following. We create a chron entry array. Chron entry um entries or chron entries that is of size. Well, how many entries can we have? We can have a,000 entries.
size and then I allocate this and then I mess set this entire array up here chron entries all to zero and the total size of this is the size because we have a th00and entries multiplied by the size of uh actually it's the size of chron entries multiplied with size of and then destruct itself. Chron entry and this should zero out this entire array.
So we have a thousand chron entries here and we have um and each has a size in bytes of chron entry or maybe even simplest I think more adequate would be and probably more correct would be just to to do size.
Well, actually size should be the command command size and then we should have a different size that is um chron entries something like max chron entries is also a th00and but they could technically be different.
And so here we have max chron entries and down here we have yeah max crown entries multiplied by size of a single chron entry and we zero out all of this. Then we read command size. So a single command from the config from the configuration file.
And here now the next step would be we read commands.
I would say let's break it down again and just print entry although entry does not exist anymore.
So I think we should create a temporary buffer of command size here. It is just a line of text and then we print the line and each time in each each iteration we need to make sure that this is set to zero. So line zero up to command size is zeroed out and then we print the line.
Why is why are there red highlights around the while here? Probably I'm missing a closing parenthesis. Yes. So now what do I expect to happen?
I expect to happen that the entire chron job file is being printed. We print the chron job file line by line.
I don't this is not even going to compile because I still need to replace a couple of of sizes. Um maybe it now makes sense to remove much of all of [snorts] this stuff here. That's probably not necessary anymore. Or well, we don't need the mems set anymore. We need to the interval in seconds and we need the command.
And the command has a size of command size and this needs to be fixed up here as well in the definition of the chron entry.
And then let's do parse the chronob config inside here.
Oops.
And then we can still print f the parsed variant like this.
And after this loop here, the entire file should be parsed. And then for Let's just let's just um delete this y loop for now. I just want to check if this works for now.
No, there's [clears throat] an error.
Invalid application of size off to complete. Ah, okay. Size off doesn't work like that. Let me fix that. Where's size off?
It's up there. size of strct.
What's wrong there?
Why is that there? It looks like closing parenthesis is missing here.
But now down here there's also something missing.
Ah, now I get it. Okay, so I was messing up the closing parenthesis. Let's let's clean this recompile and let's have a look what other errors wait for us.
Invalid application of size off to incomplete type strruct chron entry strct ah strruct entry doesn't even exist because this up here is an anonymous strct. There's no name associated with the strct. If there was a name, it would be declared here. This instead is a full type definition and this is the type. So the C compiler or preprocessor I don't know the compiler doesn't even know that this is a strct. So we can just do size of and size of chron entry because it's a full type. It's not a strruct.
And now this error has gone. Let's have a look what else there is. Entry undeclared first use in this function.
Uhhuh. Fgets fget gets s entry undeclared read the crunch config fget gets s entry right we read of course not into entry oops here we read into the line which is the new variable we we have allocated up here and now still errors let's Check warning format not a string letter. Let's just cover the errors the real errors not the warnings first. Error entry undeclared arm. Okay, I need to search for all occurrences of entry. I need to replace them with line. Okay, there's no more entry. Now we can recompile. The error list has reduced a bit even more here.
So command undeclared. Command is undeclared because this is the Y loop down here. We don't need it for now.
Let's just write and recompile.
And now there's only a one a warning format not a string literal and no format arguments. Right? Because we are printing somewhere. Where are we printing? Here we are printing the line.
But this ah of course we are print we need to print this string format this format string here and then pass in as input argument the line.
So if we now compile this uh still doesn't look too good error command undated. Of course that's because I reintroduced this y loop here.
I just keep reintroducing it to such that it's not deleted. But I can also store it in some store [clears throat] it in some vim buffer. I can do a copy right register r delete. And now I can probably do some other things here like this. And I can still paste this register r paste. Yeah.
Okay. I got it in in a temporary vim buffer. If you want to learn about these whim hacks then also let me know in the comments. So now I have persisted this even if I undo and redo other things I can still do I can still recover this always. This is nice.
Okay. So let's compile it. This time it compiles. As you can see there's no no warning, nothing. What should happen now? I would expect this to properly parse the file chroner and we do have that's of course wrong a Russian d uh is the parse command. This is of course completely wrong. Let's check what's even going on here. So the value of command well command command size maybe we need to do before we do anything here we need to me set command to zero of command size.
Let's just check again.
Ah okay. And now the parse command is nothing. That's already a bit better. At least it's not garbage.
And we want to parse interval seconds and command into into from the line into the variables and then we print them down here.
But why is command empty?
Running the executor, we mems set line to zero. We print f the line.
Let's check what is printed.
There's no print f of the line. That's interesting.
So it looks like it's reading it doesn't read the file.
While fget s is not null, we mess set the line to zero and we print it. Ah, of course that doesn't make s. Oh my god.
[sighs and gasps] Of course, I'm reading the line here and I'm immediately mems setting it to zero. So, the data is lost. Of course, I need to mess set it to zero um after allocating it first and then later after having used it probably let's say at the end of of this of this loop.
Let's recompile. And now this looks a bit better. So we are printing the full line and then the parsed interval and the command. Perfect. So now we're already approaching a final solution.
And I think this now is very healthy already. Let's now do the following.
We can store now all the data that we have parsed into into our chron entry array which is this here chron entries.
So let's do this.
Chron entries at index. What's the index?
Current index equals ah dot interval in seconds equals the parsed interval in seconds.
And same thing for the command. the command equals the parsed command. And this sets on this array structure these values here. Although the command we I don't think we can just do an equals. We probably have to do a string copy.
Command. Yeah, let's do a string copy just to be sure. string copy because um we are we can't just set a stack local variable that only lives as long as this y loop lives um into this array here and expect it to keep its data. We have to properly copy the data from from the command into the target data structure like this. So this now copies the command into the chrome entries command field and this doesn't compile because current index doesn't exist.
Also we have to increment current index here after every iteration. And let's create an integer up here that is an int current index equals zero. Maybe this works. And then we do this. And now it still executes. That is already a good sign. And I expect now the chron entries here to contain all the data.
And now instead of printing the data from right after reading it, how about we print it after um after after having pass parsed everything just to make sure that it works. So we we loop up to the current index and we do a print print f and then string we print the oh no we print the interval in seconds equals a number like this new line interval in seconds and then we print f the command command but command and interval seconds are now extracted from chron entries at index current I no at index i this time same thing here chron entries index i do command And now let's see what happens.
And this looks good, right? Because the first loop here is still printing the line that was read by the by the parser here. So this is the first line. This is the second line that was read. And then our parse data structure here is printed in a separate loop. That's why it's only printed afterwards. And this is properly printing the interval and the command.
And maybe we can now edit the chron job configuration. And add yet another command. For example, every every 8 seconds. We want to echo this runs every 8th second.
Again, this can be arbitrarily complex.
All right. And if we now run this, let's just see it's properly parsing the data.
So, um I would say let's leave all this printing stuff inside just for debugging purposes, but technically you can remove it because you know it works.
And now what's remaining is we just need to retrieve we just need to recover our uh loop from earlier that I stored in my vim buffer here. There it is. So for every no as long as possible infinitely execute a command. Which command do we execute? Well, we execute a command for every single entry.
Oh, but now this is a bit more tricky because I want the first command to run every second, the other one every third second, and then this one every eighth second.
So, how do we do that?
Oh, this is not this is not trivial probably.
want to run the system command every second, the other command every third and the next one every eighth second. How can we run them independently of each other?
[clears throat] So every single second probably we need to check which commands are due to be executed.
So let's just sleep a single second here and probably we need to add something to this structure chron entry. Um so this is the original interval. This is the original command. And now we should also track um the seconds until new execution which are okay.
Seconds until new execution is always zero at first. This makes sense.
And then we check then we do the following.
We run now we we loop over all the chrome entries.
How many are there? There are current index chrome entries. This is basically the counter that we have created up here.
And then we do [sighs] chron entries at index i dot interval.
Oh wait or let's do the following seconds. This can be a seconds counter.
And this is the interval and this is the seconds counter. And then we do the following. We just check if oh this is really not trivial for me.
Chron entries at i.val interval is equal to chron entries i dot seconds counter then we execute. So if we start counting at zero and if this number executes in equals the interval then we execute the command using system chron entries at index i and then we execute the command.
After executing the command, we reset the chron entries seconds counter.
Mhm.
So we set it to zero and then it starts increasing again. So else we do um seconds counter chron entries seconds counter plus. So, we increment it or we should actually increment it every single second, not just in the else case.
And this might do it. I'm excited to see whether this works. If this works, I'm going to freak out. I didn't expect this project to have so much logic. So, we run the the checks every single second.
Yes, I know it's not ideal, but it's actually how the real chron job also works. The real chronop just runs every single minute. Every single minute is um completely sufficient for most use cases. In our in our case, it's every single second because for the sake of this video, I want to be able to quickly test and see if commands are properly executed in their designated intervals.
Okay, so let's see.
um count seconds and and execute. I'm just going to put a nice comment here.
So, let's do a compilation. This doesn't compile because I need to set a check interval seconds of course. Does it compile now?
No, not yet.
So error chron entries is a pointer. Did you mean to use the array notation?
Chron entries is a pointer. Which line is it? Chron entries is a pointer in line 67.
Line 67 here. Chron ah chron entries index. I of course we refer to a specific chron entry here.
Now we can compile this. Compiles without a warning. And let's see what happens if we execute this.
So it's printing hello, I'm Daniel every single second. It's printing the list command every 3 seconds.
And this one runs every 8 second. So it looks like it's correctly working. And this is a small working chronob implementation, guys. This is incredible.
Yes, this is crazy. So I didn't expect this to work and again this is just what what what have we even done here? We have created our own chronop executor with our own chronop configuration file here. Let's have a look at it again where we can configure intervals in seconds and arbitrary commands that are executed every time the interval ex expires and then and we have written all this logic to parse the file and to to get the executor to to run in the specified intervals. This is an exciting and nice project. I didn't expect it to be to turn out that nice in the end. So, this was really much fun. If you want to automatically run this chronob executor whenever you start up your computer, just do I just add it to your system uh startup applications for example down here custom command and just add it here. Um but this might might vary to due to the operating system or alternatively create a systemd service from it. That's but that's a complete working and nicely working crunchop executor. This was so much fun. This was a perfect project. Of of course there are some some small tweaks that you can make here. For example, the the parsing is is a bit hacky, but it works for most cases. For 99.999% of people on the on Earth, this will be completely sufficient.
If you want to learn C like I use it in my day-to-day projects, if you want to get real projects working yourself and start becoming a capable programmer, a capable C programmer, then have a look at the nononsense C programming channel and not channel, the nononsense C programming course, which is a course that I've created that teaches you C from ground up. It has awesome reviews.
It gets you going um just within three lessons from zero to creating graphical applications, real Unix style stuff, encryption, lots of p practical things comes with exercises and additional resources such as terminal tricks and hacks.
I just poured my heart into it. So get the nononsense seal programming course and see you next time. Bye-bye.
Related Videos

TOP 15 Data compression Interview Questions and Answers 2019 Part-2 | Data compression | Wisdom jobs
wisdomjobs
281 views•2019-06-28

CTS 158: 802.11w Management Frame Protection
ClearToSend
4K views•2019-02-04

NDSS 2019 Send Hardest Problems My Way: Probabilistic Path Prioritization for Hybrid Fuzzing
NDSSSymposium
496 views•2019-04-02

How realistic is Cities: Skylines?
CityBeautiful
159K views•2019-02-14

GUIs & TUIs: Choosing a User Interface for Your Python Project | Real Python Podcast
realpython
2K views•2025-04-04

The OSI Model - Explained by Example
hnasr
225K views•2019-05-12

Cloud Computing - Introduction
elithecomputerguy
98K views•2019-10-07

From Traveler's Dilemma to Dynamic Routing | Demystifying Networking
IITBombayJuly
5K views•2019-08-04
Trending

WOW! Judge TURNS THE TABLES on Trump in His OWN $10B LAWSUIT!!!
MeidasTouch
197K views•2026-07-23

Playstation NO DISC/NO BUY Fight Is Over...
DavidJaffeGames
4K views•2026-07-23

Steam and Xbox Just Dropped The Hammer On PlayStation
OhNoItsAlexx
9K views•2026-07-23

Americans Confused in Australia for 17 Minutes Straight
IWrocker
17K views•2026-07-23