Transcript
[0:00] Music.
[0:07] Well, it's that time of the week again. It's time for Chitchat Across the Pond. This is episode number 771 for June 24th, 2023. And I'm your host, Alison Sheridan. This week, our guest is Bart Bouchat's back with another installment of Programming by Stealth, number 152. How are you doing today, Bart?
I'm doing mostly okay. I'm mildly cranky at the universe because a piece of glass made my day unpleasant.
Oh, and the bike wheel, bike tire? Yeah. And it turns out that I have three spare tires for the mountain bike and zero spare tires for the road bike. I thought I had two in one. I did not have what I thought.
So did you carry your bike home?
[0:45] I was very close to home. So I sort of limped home on it, but it's ruined. Like I thought, I'll just fix this puncture. Oh no. Oh no. So Amazon Prime is going to get one to me as quick as possible.
Oh, I bet. Steve said something funny when he heard you had a piece of glass. He goes, Oh man, don't you wish you did?
You know why? Because I have a slow leak that they can't find.
I'm losing one PSI per day. So help it along. Yeah, yeah.
If it just had a big gouge in it, but they can't figure out what's wrong with it.
But it just we were gone for five days and it lost five PSI.
So I can't keep pumping it up every day.
So anyway. Yeah, they're hard to pump up Tesla tires. Some of our garages aren't strong enough, I've discovered.
We've got just we got a little home unit.
It's real easy. It's just plug it in. It's just a regular tire.
Yeah, it takes as much as a heavier vehicle tire does. Maybe all your pumps are stronger than ours, but here I have to be careful. I'm holding my hand up. It's like the size of a loaf of bread, my pump that I use at my house. I plug it in the wall. It's got these... And it does it. It's that real quick.
The Tessa needs 45 psi and a lot of our stuff only goes to 30.
[1:57] Really? I'm used to tires going to that. I guess you're a little bitty adorable car. We all did that.
Dinky European cars. Anyway. Well, anyway, we should probably get started because we have what I think is going to be a challenging lesson. I have pre-read the notes and I have lots of questions, so we better dig in.
Yeah, and it's been a month, so that isn't helping things. So we are in the stage of our story where we're very close to being done, which means we've basically covered an entire language almost. I mean, we are 90% through and we've done a lot more bash than I thought we would, because it turns out the community love it. I really got into it.
Everyone's kind of got into it, even though it's such a strange language. So this installment or this challenge gave us a lot of room to reuse pieces we've learned and to assemble them in perhaps slightly different ways. So the same Lego bricks, but we built a different house, if you'll excuse the analogy. And I don't know how long it's going to take us to describe this and how many questions you have, but you're you're in your most important role here. You are standing in for the audience. And given where we are in the series, now is the perfect time to stop and and ask the questions.
[3:17] Okay, so before you start, I want the audience to know what I told you before we started.
A lot of these commands look familiar, so I know you've told me them before, but I feel like they're so fuzzy, I dreamt them. So I may stop you more on things that's like, you're not allowed to say, Alison, I told you that, because I know you told me these things, I'm sure.
Right. Let's take that as a given. Apart from the point where I say, and this is new, everyone understand that these are our old friends of Lego bricks. But there's a couple of things going on here. So I've thrown a lot of bricks at you in the last couple of installments, and I don't know of very many languages that are more dense than shell.
[3:57] Well, yeah, I'm looking at the commands going, yeah, %d. I knew what that was a couple of weeks ago. I've gone back and read and brought up a cheat sheet while we're talking, but it's so concise that there's not a lot of clues to what it is.
A lot of document reading, right? When I'm writing shell script for real, in the real world, I have the docs open a lot because I have to remind myself what the different operators are a lot because, like I say, it's dense and it's like, you know, percent, single quote D. What was that again?
Oh yeah, the single quote. I sat there going, what the hell? And all of a sudden I went, it's a comma, yay, I got one.
It's the anti-gravity comma as I try to make it funny. Yeah, exactly. But you need to go read it. And that's not a failing, right? The skill of being a software developer is to not feel bad about reading the docs and hopefully to get to the point where you can get to the right bit of the docs more quickly.
[4:56] I don't remember, and you're not allowed to tell me you told me, unless I ask you directly, did you tell us where the docs are other than just man?
I ate.
I'm pretty sure I linked to them. I generally stick Bash documentation into Google.
It's a horrible site because it looks like it dates back to the age of Bash.
It's barely formatted, black text in a white background with no sidebars or nothing.
It is the most primitive looking thing you've ever seen.
You're like, is this the official documentation?
Oh, so it is.
[5:32] OK, well, as long as it's the language that's dense, not me.
Right. And like I say, it is normal to need to go read the docs.
That is that is not a bug. That is that is how this language works.
So our challenge a month ago, right. That also doesn't help.
Our challenge a month ago was to write a script that would print out a pretty version of the multiplication table.
So we had done multiplication tables when we learned about looping.
And the point of the exercise then was to learn about loops because they were new to us.
Whereas now, the point of the exercise was to practice string formatting using printf.
And so this is a bit pretty, right?
We've done just print the numbers. This is a bit pretty.
And I kind of went to town on pretty in my sample solution, and I use it as an opportunity to try to bring in lots of pieces from the past.
So I sort of intentionally went, this is a bells, whistles, and maybe even a cherry solution.
So what you needed to do was to write a script that would take one required argument, which was a number.
So if it was 3, then you wanted the 3 times tables. If it was a 4, you wanted the 4 times table.
[6:45] By default, it should go from 1 to 10, like you would have done in school, but you could optionally give it a minus S for start or a minus E for end, although I believe I very foolishly gave them terrible names in the actual challenge, minus lowercase m and minus uppercase M, and I literally started showing us by saying I was an idiot.
So my sample solution used a start and end.
I actually didn't care which they gave me, whether they were min or max in my solution, But lowercase m was min and uppercase m was max.
You wanted start and end, and that's fine. But it does mean that the solution won't look anything...
It does have different things in it than ours.
But I think we can run with that.
Yeah, and like I say, it was after I started writing the code, I realized that I don't actually care whether we count up or count down, and therefore min and max are silly, whereas start and end are actually what we're doing.
And your solution... I did a test to see whether they gave me the first...
The minimum was less than the maximum. And if it wasn't, I just did it in the other order.
Oh, that's another way to do it. Yeah, flip them. Isn't that how USB-C works? It basically checks the wires and sees, am I plugged in the other way around? I'll flip myself, instead of the old USB-A way of making you flip it.
There you go.
[8:00] The other thing I... Yes. And so it could be 1 to 10 by default, or to the values you specified.
Now, this code took me a long time to write.
So that's another important thing to say. The sample solution here was not something I threw together in five minutes.
And while I was writing it, I pulled my hair out a few times, and I don't have much of it left.
And so one of the things I would do in the real world is give my code a debug mode where it would tell me what it was doing under the hood.
And so I decided to show instead of tell.
Well. So my sample solution accepts an optional flag –d which makes it chatty. It tells you what it's doing. But so as not to pollute my output, I made it chatty to standard error.
That way you can pipe it to different places.
[8:49] Depending on what you want. Okay. And if you don't put the minus D, you don't add a pivot to see all that garbage.
Precisely. So basically, if you know about the minus D, you can see what it's doing, which is really useful when you're building up format strings and things.
Because like I said, the syntax is a bit, you know, dense.
[9:08] So you'll find the full solution in pbs151-challenge-solution.sh in the installment zip. And it's a long one. Now I have, I pasted it all into the show notes because I figured that way people don't have to download the zip file to see everything.
But ultimately, big picture wise, we start by making sure you've given the correct arguments using the getOpts that we've now encountered a few times.
And so we basically, we do our little dance to make sure that they've given numbers and then we save the values for use later.
I'm saving the number, the thing we want to do the table for as n because I just couldn't think of a better name for a number.
The n times tables? I don't know. I was very low on imagination.
Can I ask you a dumb question? No, no, just think.
Where in your code do you assign the value that they're going to multiply to n?
I literally couldn't find it.
[10:10] Okay, so you see we have a while getopt do and then a whole bunch of stuff, and then we have a done. And none of them are n.
Correct, because the n is not minus n. It's just the first normal argument.
So after we're done with the optional ones, we do that shift copy paste thingy to make them poof away.
Therefore, what's left is dollar one. So then we say n becomes equal to $1.
[10:39] Okay, gotcha. I see it now. Okay. And so at that point, I was already getting things wrong.
So at that point, I put in my first debug statement. So what I chose to do was make a variable named doDebug so that my code would read nicer. So if minus n doDebug, then my various debug statements.
So I'm going to do one of my first— I'm sure you've told me this, but so it says if, and then it's got the double brackets.
This is going to be the test. It says "-n $doDebug". So $doDebug is going to be a function?
[11:18] It's a variable. No, it's a variable. It has a value of one if they gave it to us. Correct.
Okay, but what has "-n got to do with that? The n is the number they're giving us.
No, okay, so $n is our variable. We're now inside square bracket universe, right? We're in the test, so square bracket, square bracket means we're doing a test. And all of the...
Right, square bracket, square bracket means test. So minus eq for equals, minus gt for greater than...
What does minus n mean? I'm getting there, I'm getting there. So minus z is for empty string, and the opposite of minus z is minus n for not empty string. So it was one of the ones that I made that I basically said you're going to hate this one because it's not obvious. So minus n is not empty string. So if...
So of all the variables you could have chosen for the input number to be n and then use n there, and I mentioned this before, you use n two more times below where it doesn't mean the same thing.
So there's four different... the letter n means four different things in this code.
Maybe back to the fact that the language is dense, but I don't think I would pick N.
I would not pick N because N has four meanings here.
[12:33] Okay, in my mind, dollar n is a thing. The variable n is dollar n. And in other places, it's whatever it is. So minus n is a test. Minus anything is a test, I guess, is sort of the demonic.
[12:49] Because you tell me when the n is the number, and then I'll learn all the other things it means as we go. Because I know you use it two more times. I'm curious where else I use it.
I'm not... I don't remember where, but there are 150 lines of code here, so...
So this says, if doDebug is not empty, in other words, if it's one, then we're going to do this set of code?
Yes. Okay. Which is just print out. Now, I prefix my debug statements with DEBUG in all caps and a colon so that when I'm looking at the output, I can easily tell when I'm just telling myself things versus when it's part of the program.
You might like this. In Visual Studio Code, there's a plugin called ToDoTree, and you can have it format different kinds of things you write in different ways.
So I have one that says debug, and it highlights it in like bright yellow.
So when I'm done with my code, and I've got everything working, I can find them all.
[13:53] Oh, that is actually really nice. In this case, you want them in there, but okay.
So now, the do debug is...
So we say, we're printing out strings inside double quotation marks, which makes them interpolated strings.
So in that case, we're printing out the letters D-E-B-U-G colon space N space space space equal sign dollar N.
[14:20] Okay. That is the letter N. Okay, but I'm showing the value of my variable, so I give the name of the variable equals the value of the variable.
I mean, the point of the print statement is to show the value of n.
If I named it anything but n, it would make the world's worst debug statement.
If I'm telling myself n has this value, I need to say n equals $n.
Yeah, you know that. I'm just thinking, when teaching somebody who doesn't know what all these things mean, N was a choice that makes things harder, but I see what it means, because then you say echo debug start equals dollar start and equals dollar end.
And then you do the greater than ampersand two, which I think is send it to standard error?
No, standard out. Which one? Error?
Okay, so before the greater than sign is the default output.
So that's standard out. And then we are saying make standard out go to number two, which is standard error.
So we're redirecting.
[15:27] So echo would normally go to standard out. And we're saying, no, no, no, don't go to standard out.
Go to standard error instead.
Got it. Okay. I'm with you. Okay. So we make sure everyone has given us everything we want. We're quite a few lines of code in, and so far all we have done is ask for all of the different variables from everyone, saved them for ourselves into three variables, $end, $start, and $end. And if they've given us garbage, we have been cranky at them.
It is generally speaking good practice, if your script can be cranky for multiple reasons, to use different exit codes for each reason. Because you have 255 of them at your disposal.
So that gives you 255 different ways to be cranky. So it actually can be useful.
The one thing is, you should write a comment at the top of your script that actually says, what your exit codes mean.
So you'll find at the very very top I have listed my two exit codes.
Exit code 1 is for missing a required argument or using an unsupported flag or optional argument.
Error code 2 means you gave me the right arguments but they have silly values.
So I needed one argument, you gave me one, but you gave me pancakes and really I wanted a number. And yes you gave me a minus e but you gave me wobbly and what I actually wanted was a number. So that's the difference in 1 and 2.
[16:52] And so you'll see in my various bits of code, I say exit one or exit two, depending on why I'm cranky at the user.
[17:00] Right. Okay. Let me ask you a procedural question. I'm scrolling up and down and up and down right now. Am I supposed to be down where you're explaining it in text or am I supposed to be looking at the raw code?
I've decided I'm just going to walk through. I'm walking through the raw code because I say the same things.
Okay. Because I think we should talk about the bigger structure.
So at this stage of the code, we have basically gotten our input.
We now have three variables. All of that was to make sure we absolutely, positively, definitely have three numbers.
So then it's about starting to make our pretty strings.
And I'm trying to print my table out so that it's always the same width, and I have framed it with the ASCII characters for a table.
Because if people can remember to the days when there was no GUI, you would have these really pretty menus with perfect little frames around them and stuff.
And that was all done with ASCII characters that are not. Yeah, they're not the pipe symbol.
They're a little bit taller. They touch the very, very top of the character.
You can still get to them if you go into the BIOS on Windows, I think. Oh, actually, yes.
You can see exactly what you're talking about. Yes, yes. Yeah, that's exactly it. And the Mac OS X character viewer will show them to you.
And so I basically went in and I found the ones for top corner.
[18:16] The four corners, horizontal and vertical, and basically stuck them in my clipboard.
And then I use them in my format string. So my table gets printed out with this very pretty ASCII table around it. But if you're going to print a pretty table, it's really important that every row is always printed out the right length, because otherwise your table, the illusion breaks immediately if one of your rows is the wrong length. So again, we're talking about making things pretty. So we actually have to calculate the biggest possible width of each piece. So we're printing a table of, you know, our number multiplied by the one in the sequence equals a a value. So there's actually three columns there that could be different widths depending on whether you call the script with the three times tables or the 3,000 times tables.
And the fact that we have thousand separators, because we're now pretty printing strings using printf, it means that we can't just say if it's a thousand it's four wide, if it's a hundred it's three wide, because the commas and stuff.
So I thought about trying to figure out an algorithm.
And then I went, no.
[19:25] I'm going to run each of my numbers through printf, and then through wordCount-c for character, and just ask it how long they are.
And then whichever is the longest one gets saved? So I have a variable called maxMLen for the maximum length of the multiplier, and maxPLen for the maximum length of the product.
And I have a loop that goes through all of my numbers, And it just says, if the current one is longer than what I thought was the longest before, update the longest before. And at the end of the loop, my max will be correct.
So I think I just figured out another confusion I had reading the code.
The product is the answer.
Yes. 7 times 3 equals 21. 21 is the product. Yes.
[20:12] The multiplier is what changes. So... Oh, okay.
Okay. Well, we'll get to it in the code, but you never ask for the length of the, or store the length of the number that was put in there that I could find.
I didn't see it in the calculation. It doesn't change. You must count it somewhere then.
I do. There is a little one-liner, but it's, yes, actually here.
So nLen equals the character length for the number, right?
So it's the very first one I do. Calculate the maximum length of each column when nicely formatted.
NLen becomes equal to.
So nlen is the length of n. I'm going to do something very mean, Alison.
So remember I said, everything apart from one thing is not new.
[20:57] The one thing... Wait, I have too many negatives in that sentence, haven't I? One thing is new.
One thing is... Yeah. Thank you. There's one thing new.
It is a command which is simultaneously trivially simple, and yet the most difficult thing to explain that makes most people's heads explode.
[21:17] And I don't understand how it can be simultaneously simple and confusing, but it absolutely is. And I was in that boat. It took me years to not run away from it.
I've run away from it for years.
It's actually really powerful and really useful. It's the command xr.
We are going to learn about it in detail. For now, I'm going to say, stick a pin in that line of code, because that line of code is actually illustrative.
What I can tell you— —And the line of code for the listeners is the line of code that calculates how long the length of the variables?
Yes. Now, the vast majority of it is the pieces are, you know, you have a printf percent d with the single quote, which we now know is the anti-gravity comma. So we're saying we want to print a whole number with a thousand separator, and then we pass it $n. So $n will get printed with its comma separator. We pipe that to wc-c. So wc is the word count command, and minus c says give me the character count.
And if we stop listening right there, we're fine. Yes, we should know what you just said. The comment tells you the important piece. The actual length of the number in characters is stored in nlen.
[22:35] Oh, when formatted, because you've put the anti-gravity comma in there.
Correct, exactly. So if you wanted to do the 3000 times tables, it will correctly say that it is five characters long.
Which is one more than you might have thought. And so then we just assume, we just make a guess, max mlen one, max plen one.
Say again, max mlen and max plen again. So the maximum length of the multiplier, so we have our three times tables, it would be 3 times 1, so then 1 is the multiplier.
3 times 2, then 2 is the multiplier. 3 times 3, 3 is the multiplier.
Okay.
[23:12] Because that changes, right? And every row in your table, that one changes.
And so does the answer.
So those two have to be calculated. Therefore, we have to have two variables.
Okay, so maxPLAN is the length of the product, the max length of any product in the table.
In the table, yeah. And we're going to define them as one, not really assume.
We're going to define them as one. Yeah. They're starting checks. Okay. Yeah.
And so then we have a for loop, so a good example of a typical shell script for loop, for m in, and then we use the dollar. So that m is not minimum.
That's not the minimum we were... What would be in our code?
It's... We're looping over the multiplier. We're looping over the multiplier here, right?
So we're going to go from three times one, then three times two, then three times three.
So that M is indeed the multiplier.
[24:03] Okay, but that's not the... No, it is the multiplier. That's not even the variable names that you defined.
Okay, this is a new variable we're defining here, for m. Right, because we got to go through them from the start to the end.
So we're saying sec is the sequence command, dollar start, dollar end.
So m is going to go from the start to the end, one by one.
Each time through the loop. So we're simulating the table, but we're not going to print anything.
So we're effectively going through our table in stealth, right? We're going from start to finish, in stealth, and each time we're calculating the length. So MLen becomes equal to printf, with its upside down comma, pipe it to word count minus c, and then hocus pocus, and out comes the, length stored in MLen. And then we have to decide, is what we got now bigger than our previous biggest. And so one of the things we learned early on, that I wanted, I intentionally did this to reinforce old knowledge, is you can use the ampersand ampersand, which is the and statement, to work as a really quick if. Because remember I said Bash does lazy evaluation?
[25:21] — Sure. — Okay, so with lazy evaluation... — No, no, sure I remember is what I mean.
— I believe you. — Okay.
Well, do you want me to explain lazy evaluation or not?
— I guess that's what I'm asking. — I want you to explain this line, and if you need to explain lazy evaluation, then yes.
Okay. So the...
For something to be... For the final result of an AND to be true, both sides have to be true.
Okay, as it always is.
[25:49] So, if the first side of the and is false, then lazy evaluation says, don't execute the second side.
[26:00] Which is why it works like an if. Because if the condition mlen is minus gt greater than max mlen, so if the length we've just calculated is greater than the maximum length, only when that's true does the second statement happen.
Oh, okay. I had forgotten that. And then the second statement is, now take our maximum length of the multiplier. We're just doing the multiplier right now. The maximum length of the multiplier, update it with what you just finished calculating, MLen.
Precisely. The current value for the length of the multiplier. Okay?
So, and then we do the same trick again, but we have a little bit more work to do because to get the length of the product, we have to actually calculate the product, right?
I can't get you the length of 3 x 10 unless I do 3 x 10 first.
So the first thing I do is I print $n$m, which is whatever our n is multiplied by m, and I pipe that into our basic calculator, the bc command for doing actual math.
Then I run it through printf with a little bit more hocus pocus that we're absolutely going to learn about in the future, and then I count its length.
So multiply it, make it pretty, then count its length.
[27:29] So you're calculating it, actually you're calculating it, you're echoing it, you're sending that to the basic calculator, which will run the math, and now it knows what that value is, and then you run printf to find out how long to print that value out, and then you can pipe that to wordcount-c, which counts it, with two hocus pocuses on this line.
Yeah, this line teaches you everything in the world of an exerg. This line is...
This line will be how I teach exerg.
OK. This line is the line. This is the line of the entire script.
This is it. This is the line.
OK, but we're not going to talk about it yet.
We will after we put it in its bigger context.
OK.
[28:19] So we do the same trick again, right? We check to see whether what we found is bigger than what we had.
And if it is, then we update our biggest.
OK. So now we can do the math to figure out how long is the top of my pretty table, which I'm calling the cap length because it's the cap on the table.
I wasn't sure what else to call it.
OK, OK, that's what you meant by that. All right.
So it's basically, it's eight characters because of the various spaces, times the maximum length of n, sorry, eight plus the maximum length of the number plus the maximum, sorry, eight plus the length of the number, plus the maximum length of the multiplier, plus the maximum length of the product.
Calculate that, and that is the length of the middle piece. I counted the spaces, because it's...
So when I look at the table that you create, You only have six spaces.
[29:10] There's an extra space hiding, I think, either side of the table.
[29:15] It is right. That's what you say in your code.
And I went back and looked at the code, and I can't account for that extra space.
But that's interesting. Well, I know that it's needed because I ran it with my debug statements, and until I did it that way, it was broken. We'll get there.
I'll point you where I'm going. I don't see it, but okay. All right, good.
I'll go to the dark way.
So now we know the length of the cap, and you care about that because that's going to tell you how many, I'm going to call them, dashes you're going to need and two corner pieces.
Correct. That's it, exactly. Okay.
So at that stage, we have another debug statement to print out all of these lengths I've just calculated, and that took me a long time to get that right because that has all the exergs and stuff up there.
That was really hard work to get this far in the code.
So now I do myself a little bit of work and I need to make my formatting string for each row in the table.
[30:10] So we have a space, followed by the character for the side of a table, followed by another space, followed by a placeholder for a number, which we will look at in more detail in a moment, followed by the space, followed by the character x, because that's how we humans right, multiply, we're not computers, we don't use a star, followed by a space, followed by a percent, and then add some stuff, and a D that we'll get to in a moment, followed by a space, the equal sign, a space, another placeholder, a space, the edge of our table, a space, and then our new line character.
[30:48] So that's where you start out by saying that there's a space before that first, I'm going to call it a pipe, but it's not that vertical symbol.
There isn't one in the code. It says fstring equals quote pipe.
There's no space in between. And I kept looking at it going, where is he saying there's a space?
[31:09] Sorry, the space is after the pipe, it's before the percent.
I'm sorry, I'm stepping through it here with my cursor and the shift key. Okay.
Sorry, that's where the space is. Okay, because I think the notes below say that there's a space before that vertical line, and that's where I've...
I couldn't understand other stuff, so I spent my time counting spaces.
And yeah, that character actually is really wide, because when you select that character, so it goes blue, that's really wide, so it looks like there's a space there, but there isn't.
Is after the table edge. Sorry about that. So one of the things that Bash does that is confusing to people is that if you want to concatenate two strings, you don't use an operator, you just slam them together. And I remember using the word slam them together many, many, many months ago to try and make it funny and therefore more memorable. So.
[32:06] The first thing we get here is a string from the first double quote to the second double quote is just a string. And then we get the value of nlen, so that is going to be a number.
So let's pretend it's 3. So what we're actually getting there is percent, our upside-down comma, 3d. So if we go back to last week, if you put a number inside your %d, what you're saying is the minimum length for this number is three, and it's going to be right aligned.
[32:43] So if you can imagine... Right. I forget, which caused it to be right aligned?
Doing nothing makes it right aligned. You have to go out of your way to make it go the other way.
So what I'm looking at as Bart is talking is percent, single quote, double quote, dollar, capital L-E-N, double quote D. And that does like 700 things right there. Okay, we know it's a whole number. Dollar n lang, that's our variable, so that's like three we're saying, and it's in quotes because we have to do that.
It's not in quotes, right? The quote starts, f string becomes equal to quote, pipe space percent single quote end quote. That's an end quote.
[33:31] Oh, good. Wait, right? Yeah, so the quote starts... Okay, after the equal symbol, the quote opens.
Okay. That quote ends before the dollar end, then. Okay, and then we start another one.
Right, so we're smashing our strings together because that's how... In any other language, like JavaScript, we would have a plus in there, but if we put a plus in there, bash will go nuts.
In Bash, you smash them together to stick them together. So they're like magnets. So it's like we have our string from the first double quote to the double quote, and then magnetically 3, or whatever the value of the variable is, and then we start a new quote, which goes all the way to the next variable, which is magnetically connected to it. So this is Bash being very, very dense.
Yeah. I believe you, Bart. And I know we've learned every atomic piece of that, but boy is that nasty to look at.
Right, but that is what happens with printf. I was so excited I thought I knew what it was doing and I don't. I didn't, I should say. Okay.
But when you piece it together, what you end up with is percent, single quote, and number d, which is percent d means print me a whole number. The single quote means put in the 1000 separators, and the number means make it so long. So it does collapse down to cents.
But it's a dense language.
[35:01] Yeah, it sure is. And again, this caused me so much trouble that I threw a debug statement in there to print out my bloody string. Because it took me a while to get that string not to be garbage.
So that's why there's a debug statement there. You can tell where Bart got stressed is where where Bard put debug statements in. Okay. All right.
So now what we need to do is make a middle piece for the top and the bottom of our table.
So we're going to have our corner pieces, and then we have a slab that is the door frame, the top and bottom door frame of our table. Right?
So it's not a bunch of dashes? It's a bunch of dashes. That's exactly what it is.
Oh, it is. Okay. But it's a different length every time, so I need to build it with a loop.
So you're calling it a cap insert.
— Right, because it's used twice. — But you called it cap-mid.
Right, because at the top of the table, it's a downward-pointing end on each end, and at the bottom of the table, it's the same middle piece.
— Okay. — But its start and finish characters are the opposite of each other.
— So I used the string twice. — Okay, you called it cap-insert, but then you had cap-mid, and I thought that was something different.
So cap-mid means cap-insert.
[36:09] Uh, yes, because this is the first time I actually built a variable.
Yeah, you're right. I should have.
Okay. And you did 48 things on one line here with semicolons.
Yes. So this is, again, me finding an excuse to show things.
So normally when I do loops and stuff and if statements, I do something that Google results, when you go to stack overflow, doesn't do.
I space it out. For something, new line, do. New line, some stuff. New line, done.
If you go to Stack Overflow, you will generally see that collapsed, with 4m whatever semicolon do at the very least.
And so, if I don't tell you about this, the more you google stuff, the more you're going to think I've let you down. Because the google results are going to be full of semicolons.
And all a semicolon means is, pretend it's a new line.
It. It just tells Bash, treat this like it's on a new line. And so if I want to read your code, I can take this and just take out these semicolons and make them new lines?
Correct. And then you're left with a for loop that just plus equalses the dash into cap mid.
[37:31] Okay. So you're going to do that, but how long are you going to do that?
One to the length of the cap. Sec one dollar Kaplan.
Okay. I thought the cap length was just the middle piece, but you're saying it's the...
It's two more than the length. It's the length of the middle piece. No, no. I mean, we're building the middle piece, right?
So it is the length of the middle piece. So I just need to... I need a string. If the middle piece is 10 more, then I need 10. length len is the length of the entire thing, not just the middle piece?
No, no, no. It's just the middle piece. So this is just a loop to make my middle piece.
I need ten dashes. And then I'll put a little corner on them. It is literally just a loop to go from one to how many dashes I need.
Okay.
You.
[38:26] I follow you. Again, the variable names are killing me, but I'm trying to keep up. Okay.
Okay. So now, the next step of the code says print the table. So I'm going to stick my table into a variable, which I have named table. Do I get points on that one?
Yes. Okay. Well, I don't know what table is yet. We'll see whether I like it when we get there.
Well, it's an empty string, right? Table becomes equal to an empty string.
I don't know. We'll find out. The variable to build a table into is the table. So the first thing we print is the top, right? So render top cap row. So we have our brackety symbol. So we're using printf.
So what we... Now this is very new to us, right? This is reinforcing last time's knowledge.
This is not something you've known for ages. So printf by default writes the standard out, But if you use "-v", it writes to a variable.
And the variable will be the... Printf minus v. Yeah, the variable comes next.
So the variable here is called row. So we're saying printf minus v row.
So render this string pretty into the variable row. Then we give our pattern...
Pattern. road before now.
[39:44] Effectively, we're defining it now. Yes. Okay. I think I remember that. Okay.
Yeah. All right. So we have our string that is the pattern for printf, which is open, which is a funny character for the corner of a table, %s is a placeholder for a string, then we have the other side of the funny character for the other side of the table, a newline character, and then we pass in one argument, which is cap mid, which is that number of dashes we've just made a loop.
And that gets substituted in between. It's going to get squirted in for the string in between the two.
Yes. Okay, I do remember that. Correct.
Okay. So we have now made an appropriately length top of the table.
Okay. And then we say table plus equals dollar row. So our table has now, has one row in it.
It's the top of the table.
Okay. So then we do our loop for m in our sequence from start to end.
[40:35] So each m is our multiplier. We finally get to calculate the product? Yeah.
Calculate the product. P becomes equal to, and then we use the basic calculator again to calculate $n star $m.
And then we need to print it into our pretty string.
So again, printf minus v row. So again we're saving it to $row.
Our string is the horrible string that we built in that horrible line of code we both hated with all of those percents and quotes and stuff.
That's our pattern, so we're just using it now. Now we pass, it has three placeholders, so we give it three values, n, m, and p, the, number, the multiplier, and the product, and they get dropped into the three percent d's.
[41:18] So we already used the variable row, and we defined it with the printf minus v command, and then we plus equaled it into the table, and now you're defining it again.
Is it writing over it? Or did it cease to exist?
[41:39] Is that a philosophical question? It's still...
No, I mean, you know what I mean, like we're always worried about scope.
So does it not exist at that point so you can...
It's stomping on it. No, it's the same variable, it's stomping on it.
Yeah, so effectively I'm using it as a carrier to get from the printf into my table.
Okay. I do a lot of tabular stuff in my Time Adder thing, and the word row exists at least 700 in there with different things in front of it, because I keep coming up with new names, but I like stomping on it.
That's good. Okay.
Yeah, so we're just saying, I want a row sticking in the table.
I want a row sticking in the table. I want a row sticking in the table.
So we printf our row, we table plus equals $row, done. Right? So that gets us the body of our table.
So now that we've done all the hard work above, the actual printing of the table is trivially simple code, right?
We have a pattern and we give it three substitutions. Great.
Yeah. Then we invert our logic from the top. We do the same thing again to make a bottom cap, but this time we use the other corners of the table, %s, and then we send our midcap in again.
And then one last time, table plus equals row. So now we have our full table.
So the variable table now contains everything we want.
[42:57] And then what I said to get bonus credit was that if the output was a terminal, we should put our tables through the less command so that it will, if we say give me the 1000 times tables from 1 to 100, it should give us a page, then we hit the spacebar, it gives us the next page, we hit the spacebar, it gives us the next page, like happens with commands in git. If you do a git log on a repository with a lot of things, it will page it for you nicely. But if you pipe that same output to a file, it doesn't do that. It sends everything to the file. And the way it does that is by detecting, is standard out a terminal, or is standard out anything else? Okay, so that's why we have an if statement here. That's the if statements job. Are we a terminal or are we not a terminal? And so what Jill told us, which I then included in the show notes last time, because Jill is cool.
Jill loves Kent.
[44:00] Jill of Kent, yes. Sorry, we need to say which Jill. Kent is in the UK by the way, folks.
So "-t is the test for whether or not something is a terminal, and it demands to be told what stream it should go and check. And so standard out is the stream 1. So "-t 1 means is standard out a terminal. Then, we echo the table and pipe it to less. Else, we just echo the table.
[44:30] Now, Less has two optional flags shoved on the end of it, because Less's default behaviour is annoying.
If you just pipe something to Less that's less than a page long, Less doesn't just print it out and then exit. Less locks up your entire terminal, prints the thing you want at the bottom of the page, and waits for you to hit Q.
[44:57] That's annoying. So the minus minus quit if one screen takes care of that problem.
The other thing less does is when you finish less, so if you, like the man pages use less.
If you go into a man page and you hit Q, what happens to the man page you were looking at?
It vaporizes. It vaporizes. Now do you want your table to vaporize when you got to the bottom?
No. The minus minus no minus init stops the vaporizing.
Oh, interesting. Can you man?
You can man less. Minus minus no dash init? Well, when you man less and you start looking for...
Basically start reading through how to make it not suck.
I basically read the whole man page for this. I mean, can you make the man pages be minus minus no dash init?
I don't know. And do you know how you'd find out? Man man.
Try it. No, man man, because man has a man page. I like it. I like it.
There's an odd side effect I discovered from what you did there, and I'm wondering whether you didn't notice it.
I gave it a big number. I told it to go from 3 to minus 17, and then I made my terminal happen to be very short in height.
Mm-hmm. And it drew the table three times, and it wrote colon, dot, dot, dot, skipping, dot, dot, dot, in between each one.
[46:20] Why does it do that? Dad, I didn't see that. That must be some behavior of less.
Yeah, and it appears to be related to this quit if one screen.
Because if I make it tall enough, if I send the exact same command, and it just prints it once.
My terminal didn't do that. So give it minus S3 minus E minus 17 minus D space 7.
[47:00] So you're saying, minus S? 3.
3. Minus E, minus 17. You make it 20. Minus 17. Okay. And then I did minus D, 7.
[47:14] Okay, see, oh, you put it in debug mode, then the...
Yeah, okay, so, I'm not gonna put it in debug mode. Yeah try it without the debug mode and it should behave properly.
Let's see, I've got to make it short enough. You've got to make the table...
Or, well, the other way is to go to end on 170, and then I'll definitely do it.
No, it still does it without debug. It's if the window is shorter than one table height.
So if it's scrolling partially off screen, then it causes it. If it's tall enough, it doesn't do it.
But it... Skipping! Okay, I've just tried to reproduce that with a tiny little terminal window, and it's not doing it for me, it's behaving perfectly.
Well, I can send you a scrolling screenshot of it if you would like, but it's 100% predictable.
Actually, you know what it does? It gets down to minus 15, that's what it does, and it never, so it doesn't get to minus 17, which would be the last number, and it cuts it off, it doesn't get the bottom cap, and it says skipping, and it did it again, minus 16, it skipped it again, minus 17, yeah, it doesn't.
How are you paging? Are you hitting the spacebar? Enter.
[48:33] Well, I can show Bart after the fact, but it's a... Yeah, no, I cannot recreate this, because if I hit enter, I get it one line at a time.
Until I get to the end. And then I hit Q to exit out of less. Interesting.
Yeah, no, mine is behaving perfectly. Mine is refusing to misbehave.
That's exactly what I'm typing, and if I do a... the screen height has to be smaller than the height of the table.
I made mine like three letters tall and it still worked. And you've scrolled back and you don't see this skipping thing?
What did I... oh, hang on. I didn't try scrolling, I just did my lesson, it went through.
So you're saying if, no, my scroll is working, but I'm using warp, so maybe warp is a nicer term.
You.
[49:27] This is as much as I can get on screen. Woohoo! Hello.
This might not be the most interesting thing for everybody else, but I just sent you a screenshot of what I see. That's as tall as I can...
It keeps going past that.
If you scroll, it's... So the scroll... So the weird stuff is if you scroll...
It's after the command is finished.
When I scroll back up, I can see all this skipping, skipping, skipping stuff.
And the table's getting printed longer and longer each time.
That's the Mac terminal handling its history in a strange way.
[50:03] I don't know. It should page the table perfectly for you as you're going, right?
I'm using the default terminal. I'm not in warp. I'm in terminal.
Right, sure, sure, sure. But I mean, less is doing its thing, but as you're looking at it, you're getting it line by line, and you can move up and down, and it's behaving like less should.
It just prints all this all out to the screen.
But you're saying your screen was set to three characters high.
Ah, you did three characters high. I've got a shorter than possible screen, and then I increase the height.
And that's when I can see, and it pops out of the less command as I increase the height.
So I didn't know I was supposed to be hitting the space bar.
Right, so it's, yes. So paging means, yeah, so paging is that standard scrolly behavior that you get from less or more so yeah yeah don't don't hit the space bar just heighten your when it's partially drawn i that's when i lengthened i was still inside less and i lengthened it and that's what created it ah okay yeah yeah okay that's yeah yeah i didn't notice i was inside i didn't know i was inside less.
[51:19] Yeah because i was trying to understand how the script works Okay, right. No, that explains it. Yeah, Les gets very confused if the universe changes midway.
And it's like, but I was making it this size and then. Okay, well, I'm glad we covered that. All right, where do we go from here, Bert?
I was going to say, I need to find my show note window again after all the telegram windows and stuff. Okay, so at this stage, the script is at its end. Now I'm just going to check my own notes to make sure I have said everything to you that I wanted to say.
Pretty ASCII tables, check. Calculating the widths, check. Building our format string, check. Command separator, check. Conditional paging of the output, check.
And then, yeah, so this is a decision point, Alison. So either we get into XARGs now, or we save ourselves and we make a part two where we get into XRX.
[52:20] So, it is 52 minutes long. It's your call. I've got the time, but it sort of feels like we should move on. We should do it next time. What do you think?
Well, how about we keep ex-args till next time, because ex-args is going to take a lot of mental energy. And how about we skip on to what I put in as a second topic, which is arithmetic, because it's easy.
Okay, good. That's the only part I didn't read in the show notes, because I was tired when I got down to that far. Okay, good.
[52:52] Okay, so sorry, I'm bad at picking up signals. So is that good, yes? Good, good, or good?
Yes, that's good. Let's do some arithmetic.
Let's do some arithmetic. So you have probably noticed throughout our code that we have been doing math indirectly. We have been making a string using echo and then making a string and then piping the string to the basic calculator, which is BC. BC stands for Basic Calculator.
And then we've been using the $() subshell to capture standard out and shove it into a variable.
So to calculate the product, we say p becomes equal to $()$n$m$pipeBC, which is not short or elegant, or frankly that easy to read. So debugging that code 6 months from now is unpleasant, especially compared to other languages that we've met along the way.
[53:55] So, you're probably saying, but there must be a better way, and there is, but I've intentionally avoided it because I didn't want to flood you with too many brackets at once.
So just a reminder. In other words, now you're going to flood us with more brackets.
Okay, I'm going to give you some more, but a little recap of where we got to.
So we learned that single square brackets are the old style SH, so pre-bash tests, and we should never use them.
We're going to see them in Google search results, but we know better.
So we never use single square brackets.
[54:30] The double square brackets are our modern test command. So they evaluate to true or false.
They will become an executive zero for success, anything else for failure.
And we use them as Booleans.
So they're the square brackets and inside those square brackets, you have minus all, sorts of things. EQ for equals, GT for greater than, N for not an empty string, Z for an empty string.
There's loads of them. So that's our square brackets, two of them.
We also know that we can group multiple commands together inside roundy brackets.
So whenever you should have one command but you want more than one, you just pop them in brackets and hey presto, now they're together.
And what I may never have explicitly said is that when you do the roundy brackets without the dollar symbol, the output is the exit code.
So if you saved the output of a roundy bracket with no dollar, it's the exit code you'd end up saving, which is probably not what you want. Which is why we almost always say $ roundy bracket and then we capture standard out into the variable.
[55:34] So if we go back up to our example, p becomes equal to $ roundy bracket, echo whatever to bc. So bc writes the answer to standard out, the $ roundy bracket shoves standard out into p. That's how it works.
So there are all the brackets we've met already, and that's quite a few. Now I'm going to to introduce you to double round brackets.
So double round brackets without a dollar sign will do the arithmetic, and the output will be the exit code of whatever the arithmetic is. And the arithmetic can include conditions, so the exit code could actually be useful.
A dollar sign means, no, no, I want the answer from the math.
[56:21] So it sort of works the same way as single round brackets. Yes, there's logic to its very denseness, but there's the world of difference between run me a sequence of commands, which is single bracket, or do some math, which is double bracket.
Okay.
So it is important. I like the way you built this up because Boolean tests are more like an arithmetic thing than not an arithmetic thing.
Precisely. No, with those two together, I'm screenshotting this with my mind, hoping Dorothy puts a pointer to it in the PBS index. She will now.
So like inside the double square brackets, the rules are different.
So remember we said that inside the double square brackets, you can do things like minus F or does this file exist?
And that doesn't work outside of square brackets, right? The square brackets are like a universe where there's different syntax.
The roundy brackets put us into a math universe.
So we enter into Mathland. And in Mathland, the universe is a wee bit different.
So the first thing is, we do not prefix our variable names with a dollar.
So if I want to access a variable named p, it's just p.
[57:32] Which if you think about every bit of calculus you've ever seen in your life, it's just the letters.
So, okay, I freaked out when you said that we're not going to use dollar symbols with our variable names, but only when they're inside double roundy brackets.
Correct. That is only in math universe. Okay. This is sacred ground.
It's a sacred ground. The only thing in here is variable names and numbers, right?
And the various, you know, operators that do math. No strings.
No formatting. Can't have strings in here. None of that, because this is math universe.
No formatting. None of that. Okay. Okay. Everything's numbers, right?
[58:06] It's all numbers. So, our variables do not get prefixed with the dollar symbol, and if you're looking in the documentation, this is called automatic variable expansion.
That is what they call that fact.
We can also use the assignment operator without having to cuddle it.
So in normal bash universe, you have to say p equals a value, no spaces or the whole thing explodes.
Inside round you bracket universes, you can space your code out and it's all fine.
[58:37] Can you cuddle it if you don't want to be scared and maybe make a mistake?
By all means. He won't care. Okay. By all means. You don't have to cuddle it.
I'll probably space it, but they'll start screwing up the other ones. Okay.
All right. Yeah. We can also use a whole bunch of mathematical operators that have their purely mathematical meaning in here. They have no other meaning. So plus doesn't mean concatenate.
It means mathematically add these things together. means do the actual arithmetic, slash means divide, star means multiply. We also have our plus plus and our minus minus for incrementing and decrementing, and we have our percentage symbol for modulus, just like in JavaScript. And we have something that not all languages have. Star star means raised to the power of. So 2 star star 8 is 2 to the power of 8, which can be useful.
We can also do comparisons in here in arithmetic land.
So double equals is a numeric comparison. Are they numerically equal?
[59:39] So, exclamation point equals is not equals, is are they numerically not equal? Less than is numerical comparison, greater than is numerical comparison. We have greater than or equal to and less than or equal to.
[59:51] And... So just like normal. Just like normal, exactly. In here, it's like any other language we're used to, right? Inside the double brackets, things are a lot simpler.
We can also assign valuables to variables in here. So we can use equals for a simple assignment, plus equals for increment assignment, minus equals for a decrement assignment, star equals slash equals, and even, I've never in my life used it but it does exist, modulus equals, percent equals.
What would that do?
So it is the equivalent of saying x equals x modulus the number. So if you say x percent equals three, that is the same as saying x equals x modulus three.
Okay. I plan on never using that. Okay. So far I've gotten through life without using it, so...
And then the last thing, our friend, the ternary operator from JavaScript, makes its first appearance here on Bash land. It can only do numbers.
It's very annoying. I wish it could do strings. But no, it can only do numbers.
So we can say the variable t becomes equal to 1 if...
[1:01:05] Oh, let me say all that again. I made a complete mess of that example.
Basically it's condition?answer1, answer2. So the outcome of $?p?equals42?1, 0 will be 1 if p is 42, or 0 if p isn't 42. Okay. Good. Sorry I said that terribly, but yeah.
So, all of this means that we can take a horrible statement like p becomes equal to dollar open round you bracket echo quote dollar n star dollar m close quote pipe bc. We can replace all of that with bracket bracket p equals n star m bracket bracket.
[1:01:49] Nice. That is so much more readable. Like nature intended, Bart.
Yes. Now, because when we don't use a dollar, the result is the exit code, we can use it in if statements. So if roundy bracket roundy bracket p double equals 42 close our roundy brackets then echo life the universe and everything. V to end our if statement.
Okay, okay, so just a darn second here, we can't replace p equals dollar round bracket all that glop with round bracket, round bracket, p equals n times m.
Those are not the same thing. The one with the single roundy brackets is that's the actual value, not the, it's not the exit code.
So those two statements are not the same.
[1:02:49] Sorry, I'm not with you at all anymore. Okay, you wrote, this means we can rewrite a statement like p equals dollar, round bracket, and you said we can replace that with round bracket, round bracket, p equals n times m, round bracket, round bracket. Those are not the same.
Yes. You would have to have a dollar sign in front of the second one for it to be the same.
That is not correct, because the end result of the first statement is that the variable named p will contain the result of our math.
Correct. But the one with the two roundy brackets is the exit code.
So they're not the same. Ah!
No, no. of the round, you brackets.
[1:03:31] The result of the round-U brackets is being completely ignored in the shorter example.
It is not something becomes equal to a round-U bracket, round-U bracket, it's just the round-U brackets. So the exit code is ignored. We don't use the exit code. Inside round-U bracket universe, we do the assignment. P becomes equal to N star M. It's all inside the brackets.
[1:03:57] Why did you bother telling us that without the dollar sign? Because if we wanted to use the result of the math straight away, we need the dollar to say give me the value, but we don't want the outcome of this. We just want to be in math universe to do the assignment. Everything we need is happening inside Mathland, so we don't use the output. The output from this statement is irrelevant.
So if you've got rowdy bracket, rowdy bracket, p equals n times m, if the next line said echo $p, would it be the value of n times m?
Yes, it would. Or would it be the exit code?
No, no, it will be the value, because inside the brackets, the assignment has happened.
You have made p equal to n. That's it right. I'm not buying it. I'm not buying it.
You said it had to be the... But copy and paste it into the terminal.
Well then the explanation doesn't make any sense. Okay. If you said q becomes equal to roundU bracket roundU bracket p equals n star m, then q will have the exit code, and p will have n star m.
[1:05:11] Oh, so the value in P would still exist, even though assigning it to something tells it just the exit code. That is very weird.
So the output of the round-u brackets is the exit code, but inside those round-u brackets the thing we did was assign a value to P. So that's done, and then the exit code happens.
But we don't give a bleep about the exit code. We're not doing anything with it. We wanted to get p to become equal to n times m without faffing about with pipes.
Do we have to initialise p beforehand or does it just come into being because this state here? to being there because you said p becomes equal to.
Mmkay.
Okay, that's not any chance I'm going to remember that one, but I believe you right now.
Well, practice, because practice is going to help you there because you're going to do math a lot.
[1:06:06] Yeah, I need lots of homework to do practice though, Bart. Remember, I don't have any reason to use this, so weeks go by. It's funny, but you are going to get your homework, even though you skipped over Exile, you're still going to get your homework.
Alright, good. Now, so we then say that we can use this inside conditional statements, right? So because the result of the roundly brackets is the exit code, you can use the roundly brackets in if statements. Because if statements need exit codes.
So if roundly bracket roundly bracket p double equals 42, so that's a comparison not an assignment.
Single equals is becomes equal to, double equals is is equal to.
So that gives us the exit code, which is what the if needs, and it can correctly print life, the universe, and everything, only when p has the value 42.
We can also use the same syntax in our little shortcut with the ampersand ampersand.
Because the exit code is a true-false value, therefore if that is true, then the second statement happens, echo life, the universe, and everything. So that is a shorter version of the same if statement.
Yeah, I like that. Okay, good. Now, by using the dollar, we can get the answer from the math. So to get the sequence from 1 to whatever n times m is, we can say sec for the sequence command. The first argument is 1. The second argument is the value of n times m.
[1:07:35] Wait, what's 1? So the sequence command takes two arguments, where you want the sequence from and where want the sequence 2. So to get a sequence from 1 to n times m, the first argument is 1. Oh, okay.
[1:07:50] Okay. And the second argument is the value of the math. Okay. Well, I like it. I do hope you use it to cement it.
Well, your challenge is to redo your table using round bracket math.
[1:08:07] So that means I have to have a reason to use math. Mine didn't have any math.
Yours had math. You must have had math. You multiplied. Oh, I'm sorry. I'm sorry.
It's hard to just do the multiplication, yeah. Okay.
Yeah, so yeah, whatever math is in your solution, do it with the value bracket land. And it, should make your code cleaner, I hope. That is the intention.
So in the next- It's been more than four weeks since I did my homework, so that's why I forgot I do actually do math. I remember it being nearly as complicated as what you're doing, though.
Yeah, mine just said, echo rangemin times the number, pipe it to bc, and then print the answer. Print it out. That's all it said.
[1:09:00] Okay, well anyway, I'll change it. Change it, exactly. So in the next time, our part two of this is going to be that one line of code where we have the two x-arrays. And it is going to take us probably 45 minutes to explain that line of code.
Really? Okay, good. Well, that explains why it took me close to two hours to read the the show notes earlier today.
[1:09:28] And it's important. XARGs is very powerful. So I don't want to rush XARGs.
Good, good. I'm glad we did the arithmetic. That was fun. You were right.
Hey, okay. Well, with that, I think we have certainly given people enough to digest for a while. So let us hope they have lots and lots of happy computing between now and next time.
But first, Bart, where should they go to talk to other people who are doing the programming by stealth work?
They should go to podfee.com forward slash slack, where all the cool people hang out.
[1:10:00] There you go. And there's a PBS channel if you haven't been there.
There is no gatekeeping to enter this, and I don't understand why, but nobody mean or nasty has gone into our slack.
Knock on wood. If you're still listening after the, how are we even recording this or more, you're our people.
Exactly. It's a filter. It works. It's a filter, it works.
Alright, now you're allowed to tell them to happily compute, Bart.
Good, good, good. Okay, folks, well there's lots there for you to digest, so until next time, happy computing.
If you learn as much from Bart each week as I do, I'd like you to go over to lets-talk.ie and press one of the buttons over there to help support him.
He does 98% of the work here, I'm just the stooge that listens to him and asks the dumb questions.
If you go over to lets-talk.ie, you can support him on Patreon, you can donate via PayPal, or you can use one of his referral links.
I really hope you'll go over and help him out. In the meantime, you can contact me at Podfeet, or check out all of the shows we do over there over at podfeet.com. Thanks for listening and listening.
[1:11:06] Music.