CCATP_2023_05_27

An audio podcast where Bart Busschots is teaching the audience to program. Associated tutorial shownotes are available at https://pbs.bartificer.net.

2021, Allison Sheridan
Chit Chat Across the Pond
https://podfeet.com

Edit Transcript Remove Highlighting Add Audio File
Export... ?

Transcript


[0:00] Music.

[0:08] Well, it's that time of the week again. It's time for Chitchat Across the Pond. This is episode number 768 for May 27th, 2023. And I'm your host, Alison Sheridan. This week our guest is Bart Bouchats with programming by Stealth. Installment 151. Not quite the ring of 150, but we're marching on.
Well, you could argue it's 152-ish. What's 152? Is that something?
Well, no, it's a rounder number. Okay, you're just being silly now. What are we going to do this week, Bart?
We are going to have a slight change of plan because our listeners rock. So we had, I think, an excellent episode last time all about redirecting the various POSIX streams. I'm really happy we redid it.
And I got a lovely message from listener Jill of Kent, who was very insistent the of Kent to be added since there are now two amazing listener Jill's. So we need to be sure we credit the right listener, Jill.
That's right, right. So we talked about the fact that slash dev slash TTY represents the terminal.
And we also said, unfortunately, correctly, that you can't tell if there is or isn't something waiting for you in standard in in a cross platform way, you can do it in bash five, which means it won't work on the Mac.
But you can't do it cross platform. And that remains true, unfortunately.

[1:31] But Jill did have a fantastic little tip. So if you remember back to when we first met conditionals, we learned about the square bracket square bracket built-in function for evaluating a Boolean.
And I mentioned that there were commonly used operators like minus f for is it a file, and minus e for does it exist, and minus d for is it a directory.
And I said there's loads more, but you don't need the other ones, we'll just stick to the more important ones. There is one which has just become interesting. Minus t can tell you whether or not a stream is the terminal.

[2:12] Ooh, are we going to learn about that this week? We are. I'm getting ahead of myself every day when I hear, yeah, I'll save my powder.
That was me turning Burt back to the order of the show notes.
Yes. So then we are going to do the first half of what I had actually promised to do today, which is printf.
And printf is a fantastic life scale. I'm going to make the argument that printf is a life scale, a bit like regular expressions. So Perl gave the world regular expressions.
You know how to write regular expressions. You don't know how to write a line of Perl.
What kind of you do? Because the regular expression syntax you know is FROM Perl.
Well, the C programming language gifted the world a very common way of representing format strings.
And Bash just took them wholesale.
So yeah, okay, why reinvent the wheel? We'll just take printf from C.
PHP stole it from C. Java stole it from C.
Lots and lots of languages stole it from C. So once you learn printf here for us in Bash, you now have a life skill that will actually you'll notice it cropping up all over the place.

[3:11] Now, once you recognize it, so we're going to have lots of fun with that.
But of course, we have some homework to do before we do anything else.
So I guess that's where we should get kicked in. So we have been playing around with a little script to read a menu.
Initially, it was from a text file, present you with a list of options, ask you to enter your order and then tell you what you ordered.
And when last we left our little script, I had asked you to update it so that it would default to reading the menu from a file named menu.txt in the same folder as the script.
But if you wanted to use a different file, you could use the "-m"- optional parameter to specify a filename.
Or if you would like to read from standard in, then you could use "-m "- to say actually I'd like you to read the menu from standard in.
And then I did say that we had added a minus S for snark flag last time when I told you if you want to make your code a bit smaller, you can lose the snark and you said no.

[4:16] So you're fine, your snark stayed, mine went away because I wanted to have my sample solution.
Basically, I wanted the important things for this week to not be cluttered.
So I felt it was better for clarity that I remove it, even if you didn't.
And then the other thing was... Let me hold you for one quick second here.
No, you did say that. I misinterpreted something. Okay. I now understand the assignment better. All right.
Okay. And then the last thing I'd said was to make sure that no matter which way the pipes are flowing, I always wanted the interaction with the user to be through the terminal.
So whether or not there's a menu being piped into standard in, when it comes to asking the user for their order, they should always be asked from the terminal, from the keyboard, not from standard in, which would be what happened by default. So you had a little bit of work to do to read and write to the terminal explicitly, which is the last thing we learned in the previous installment.
So basically, you will find the full solution as always in the installment zip, pbs150-challenge-solution.sh.
I am so boring in my naming convention, but hey, I'll call it consistent.
No, that's turned out to be very handy because Ed Howland is organizing your solutions into the PBS student challenges.
So we all have our own little folders in our organization for PBS, and we created one for you, and he's populating it, and he's depending on you to keep consistent with that naming convention.
That's a good thing. I'm a very boring person. Stay boring.

[5:45] No, no, you're consistent and reliable and repeatable. That is not boring.
That is what you really want. Fair enough. Okay, I will keep doing it now. That's good to know.
Yeah. So I'm going to draw your attention to a few things, but the full solution is sitting in the zip file. thing I want to draw your attention to is the use of slash dev slash TTY, which is the.

[6:05] Special file that represents the terminal. And so whenever we need to send a message that will always go to the terminal, like, please enter your order, we need to redirect standard out to slash dev slash tty.
So that way, no matter whether or not the order is being piped into a file or piped over to wc minus l to count the lines, no matter what you're doing with the plumbing, you always want to ask the user in the terminal what you want to eat.
Therefore, we need to print all of those messages to the terminal.
Therefore, we use the greater-than sign slash dev slash tty after the echo statements to, make sure we're printing to the terminal.
So I'm going to stop you because I think it's possible I will never understand this.
I know I've been through PBS 150 an entire twice, and I looked at the homework, but I didn't come close to figuring out what I was supposed to do with slash dev slash tty.
I'm confused, because we always saw the menu in the terminal, and we always saw the output in the terminal.
Because it was going to standard error.

[7:14] Right. But you just said we have to put echo to greater than slash dev slash DTY to be able to see it.
Well, to be able to always see it. Because if you redirect standard error to dev null, then all of a sudden your menu goes away.
How would it get sent to dev null? How would it standard...
The person calling your script is free to redirect anything to standard in, and they're free to redirect standard out or standard error anywhere, and you have no control as a script author, right? That is how your script is used.
That happens at use time.

[7:53] So do you want your script to always behave no matter how it's being used, is the question.
Yeah, I guess this is where our silly example makes me not even think of that as an option.
Like why would anybody ask for a menu and not want to be able to see it?
Why would they ever do that? That would have never occurred to me, but this is just a silly example, right?
Yeah, I'm trying to make the line so that it's silly and fun. No, no, that's good.
That's good though, but that's opening me up to someone might tell it.
So I don't think in your examples, actually, do you go through and say to send it to dev null and prove that it still comes out?
I don't do dev null to prove it still comes out, but I do actually in the example pipe the actual output of what you ordered to a text file to prove that we actually have what we want, which is the interaction is through the terminal.
And the only thing that ends up in the transcript file is your order.
So the conversation with you about your order doesn't go to standard out, but what you ordered does go to standard out, so it can be redirected.
Therefore, the script behaves very, very friendly with pipes.

[9:04] Okay, maybe we should keep going through this and I'll understand that when you get there.
Okay, so we took the echoes and we greater than symboled them into dev slash dev slash DTY.
Yes. To make sure that no matter what, the echo statements would always be printed to the screen.
Yes, which is the echo statement to say please choose your order and the echo statement to say you have just added whatever to your order. So the two echo statements relating to taking your order.
The other thing we have to do is make sure that the SELECT statement is interacting with the terminal. Because the SELECT statement, by default, will read your answers from standard in, which could be a menu, which would really make a mess if it tried to use your menu as its options.

[9:49] If you said minus m space minus, then standard in has just been used as the menu, so how can it be used now to take option one, option two, option three? It can't. Okay.
So we need to make sure that the SELECT statement always reads from the terminal.
So that means we need to redirect the terminal into the SELECT, which means that after the DONE, we have a less than sign, dev tty.
The DONE in the SELECT you're talking about?
The DONE that ends the SELECT. So the end of the SELECT is the DONE keyword.
So when I say at the end of the SELECT, that is where it goes.
It's a little confusing, but hey, that's Bash. That was the rules.
Well, that's where we were shoving a file in before. It is. You're correct, actually, yes.
Now we're just doing dev slash dev slash tty.
Yes. Okay, and that doesn't keep us from shoving in menu dot txt?
No, because that's going to be sitting in standard in, which we're going to have used elsewhere that I'm going to talk to you about in a moment.

[10:46] Okay, okay. So we're saying that no matter what happens, when the Select menu comes up saying, what would you like to eat?
Your answer is always coming from the keyboard.

[10:56] Doesn't matter what else has been done. Okay. You're guaranteeing it.
And we also need to guarantee that when it asks us, when it shows us the list of options.

[11:05] It should be showing those to the terminal always.
So the Select statement also has to be told, you always talk to the terminal.
So after the done, we also have a greater than slash dev slash tty.
So after the don't we have two redirections because we need standard in and standard out to be plumbed to the terminal, for the select statement. Gotcha. And that then works. So that is that is the first piece of important change. And then the next thing is how do we load the menu? So in the past we just sort of read the menu from the file and that was that was a very straightforward. We always was to set it from the file.
But now we actually have a little bit of logic to apply. And so I decided that the easiest way to apply logic was to default.
So to set a variable at the top of my script, to default it to taking the menu from the TXT file.
So I have menu source becomes equal to $dirname bash source slash menu.txt, which is the file menu.txt in the same folder as the script. So we learned about that.
So that was the thing that we used to shove in, but now you're putting it into a variable, so you can use it.
Yeah, so it's a variable that now has a default value of, what I basically said was, if they don't tell me anything else, I'm going to assume this.
So let's actually just make that assumption into a variable assignment.
And then, we then later on down the script need to actually slurp in.

[12:31] I probably should have included in the little snippets also.
So in my getOpts, I obviously added an m for the minus m optional argument, and whatever you pass to minus m gets stored into menuSource.
So in other words, menuSource defaults to menu.txt, but if you pass a minus m, then whatever you pass is now in menuSource.
So then when it comes to reading the menu, I can basically say, well my menu string, I'm going to default it to an empty string, so menuString becomes equal to single quote single quote.
And then I say, if my menuSource is equal to the minus symbol, then we want to slurp standard in, which we can do with just $cat.

[13:17] Otherwise, I want to read from whatever file is in menuSource, it'll either be my menu.txt or whatever was passed with the minus m.
So then I say menu string becomes equal to $cat menu. So I thought that it was minus, that it was minus m and then minus, as two separate things we were supposed to do.
And so I didn't catch that part of the assignment. I went back and double checked.
And yes, you were very clear in your instructions that that's what you said to do.
But I don't understand how the minus m and the minus still go together.
I still don't get it.
Abstract it back. So, if I add a parameter named l, and I expect you to pass me a length, then there are two arguments, "-l", and then 4. Or "-l", and then 22. It's minus name of parameter, value.
So minus m, space, value. And the value can either be minus, or the value can be a filename.
So if the value is a filename.

[14:24] I understand what it does, and you're saying if it's minus, then it's going to cat whatever text string you shoved in at the front, which is standard in?
Yes. Okay. So I tested your code by just saying, you know, ./. the shell script name space minus, and it goes out and it gets the menu.txt file.
Correct, because you passed it, an unnamed argument, which the script completely ignores. So $1 contains the value minus, and, my script does nothing there.
Okay, because it only knows what minus is if it was preceded by the optional argument minus M. Okay, okay. The fact that it ignored it made me think it was doing something different than what it was doing.

[15:17] Yeah, so it is just $1, if it doesn't have... If it didn't name it, it's just $1.
Okay, okay. I think I've caught up.
Okay, so where was I? We've done that bit. Okay, and we've done that bit. So then, once we have our menu in a string, then we can loop through it as if it was coming from a so the triple arrow is our here string, which is how we read a string into standard in.
So, triple arrow, bleh, triple arrow, triple arrow is some sort of very soft flower, triple arrow dollar menu string will read the value of that variable as standard in.
So we can use that in our while loop to just process our menu.
It doesn't really matter where it came from now, because we've shoved it into the variable.
Yeah, yeah, yeah, that part I understood.
And that saved me duplicating my code, because I don't like duplicating code that makes me cranky.
It is a bad smell. So at this stage we can verify that the code does what I think. So you will find in the zip file an extra file called menu-montypython.txt, which offers a shockingly interesting menu. It contains one item and one item only. Spam.

[16:38] I suppose you could have made 10 items and they're all spam.
I could have, or I could have had spam, egg, bacon, and spam.
There you go. I did try to be clever with mine, even though I stole mostly copied off your paper. I made mine called Twitter.txt and all it had in it was a poop emoji.
Oh, that's too good. For anyone who doesn't know, that's what Apple or what, not Apple, that's what Twitter PR responds with if you write to it, is a poop emoji. So then I got excited and I made a fail whale and then I made a rocket with an explosion thing. So, you know, at least I I had three to test with. Oh, that's great fun. And of course, you can even use emoji anywhere because they're just UTF-8. It's great. Yeah. I thought you'd be excited that I used some emoji. I do like that. Even though I totally copied off your paper. Hey, it's all about learning. You can learn off someone else in the class. That's fine. So we can call a script with minus M space menu dash Monty Python dot txt and it will let us choose our spam. Or, we can echo bacon newline eggs newline toast, pipe that into our script, and then call our script with minus m minus, and then redirect to order.txt. And that proves the whole thing out, because now what happens is, standard-in is now bacon eggs toast, which.

[18:00] We're going to read as the menu. And yet, even though standard-in is used, when it comes up to ask you what you'd like to eat, you're still getting the menu options, like you should.

[18:10] When you're typing stuff in and it's still interacting with you perfectly. And then when you're finished, it doesn't tell you your order because that's been redirected to order.txt.
But if you have a look in order.txt, it contains only what you ordered and not the chit-chatter you had to make your order.

[18:27] I have a very important question on this one. When I ran this command, it showed me four options.
Not just bacon, eggs, and toast.
One of the options was done.
Where did done come from? Okay, so if you look at... Where is the loop?
Wherever the select loop is...
Where is the select loop? So the select loop... Select items in done.
Yes. As in, there is an option... Yes, as in, there is an option...
Go ahead. Yeah, so the SELECT loop takes, you know, whatever in, and then a list of things.
So my first thing in the list is just done, followed by the explosion of my array.

[19:11] So that means that as far as the SELECT statement is concerned, it has done, and then whatever the array exploded into.

[19:17] Ah. Good. Okay. You're working down my list of questions. You've got three down. Ooh.
Oh, I'm not even halfway. Go me.
Okay, so that is the sample solution. I hope that works. Well, no, I know it works. I hope it makes sense. So now, now I get to go to saying nice things about Jill of Kent. So what we had left off is that we unfortunately can't tell whether or not there is something in, in any particular stream actually waiting for us to read it. So we can't avoid blocking Yeah, we talked about this last time, let's not rehash that.
We can detect whether or not any stream is the terminal. And this is something that's perplexed me for a while.
Because there's a whole bunch of terminal commands that appear to do magic.
And whenever something appears to do magic, I know it's not magic.
It's something Bart doesn't know.
Well, Bart now does know how this is done. So you may have noticed that the git commands are an example of this.
The git command, if you say git log, it will list every single git commit in a repo.
So for PBS, the git log is quite long.
We have done a lot of commits. And if you do it on the terminal, it will pass it through less, and it will stop you after every page and make you hit the spacebar.

[20:43] But if you take that same thing and pipe it to a file, all of it just goes into the file.

[20:51] There was no one hitting a spacebar there. So it knew, if I'm talking to the terminal, I will do one behavior, but if I'm not talking to the terminal, I will do a different behavior.

[21:06] So how does it know whether or not it's talking to the terminal?
How did it know whether or not it was being piped?
And the answer is because of this minus-t test for inside our square bracket square bracket operator. So if you say minus- We didn't type a minus T. We didn't type any square brackets.

[21:25] Okay, if we want our script to be able to work in the same intelligent way as the git commands, then in our script, we can detect whether or not something is the terminal by writing an if statement to say, basically, if we detect that something is the terminal, do one thing, else do another thing. So that is how jake is doing it. Okay. So the minus t operator takes just one argument on its right, which is a number, which is the identifier of the file handle.
So zero for standard in, one for standard out, and two for standard error. So if you say if square bracket square bracket minus t zero, then you know that whatever code for if if standardin is a terminal, else whatever code for when standardin is not a terminal.
So I have a wonderfully exciting script called pbs151a-terminal-test.sh which just has three if statements.
If minus T0 then echo standardin is a terminal, else echo standardin is not a terminal.
And in both cases I am explicitly doing my echo //dev//tty because I know I'm about to to start messing around with my streams.
So I want this script to always tell the terminal what's going on, because otherwise, if I'm doing redirections, I'll lose my output.

[22:54] So, I'm being easy. I have another one. Are you going to start putting greater than slash dev slash DTY on the end of every echo statement?
Well, no, because then anything you do that to can't be piped into a file.
So you're only doing that when you want to be absolutely certain it's going to a terminal.

[23:11] If you do that all the time, people are going to be really cranky at you because then your script can't be saved to, the output of your script can't be saved to a file.
Okay. Okay. All right.
Uh, so I, I, the same if statement basically three times for T for minus T zero minus T one and minus T two with standard in, standard out and standard, error. So if you try that at the terminal, if you just run the script with, without any piping, it will tell you that all three of them are the terminal, which is the default, right? Standard in is the terminal, standard out is the terminal and standard in, or is the terminal?
So you did that by just running the script with no arguments?
Yes, and in particular, no redirections is what matters here, because the script ignores arguments. But the important thing is there's no terminal plumbing.
We're just running the script. If we do a bit of terminal plumbing, so if we say echo pancakes pipe and then send that to our script, now standard in contains pancakes.
Not connected to the terminal, it's connected to the other side of the pipe.
So now when we run it, we will see that it says that standard-in is not the terminal.

[24:18] Okay, even though we just finished typing echo space quote pancakes unquote with the terminal, it's saying it's not coming from the terminal.
Correct, because standard in is not connected to your keyboard.
If you read from standard in, you are going to read pancakes.
You're going to read the output of the echo command. Remember, the pipe symbol...
I just typed it with my... I typed it in the terminal with the keyboard.
That's how it got there.
How is that? That's how it got there. That's not where it is.
So remember, the pipe connects two commands together. So the echo command we typed, and the echo command outputted pancakes.
Yeah?
Okay, to standard in. Got you. Okay. To standard out. It outputted to standard out.
And then the pipe symbol took standard out.
But it became standard in, I mean, to the script. Yes, exactly. And plumbed into standard in on the script.
So the script is seeing standard in as being the output of a pipe, which contains pancakes, which it's going to ignore, but it's just going to tell us, no, that's not a terminal.
I'm plumbed into something.

[25:16] Okay. If we redirect standard out to dev null, by saying name of script forward slash slash dev slash null, it will tell us that standard out has now been redirected.
If we redirect standard error... So it's not a terminal. It is not a terminal because it is dev null. Okay.
If we do to greater than sign dev null, then we're redirecting standard error to dev null.
It will then tell us that standard error is not a terminal.
Or, we can go for the full whack, and we can echo pancakes, pipe it into the script, arrow dev null, and then send to arrow &1.
So arrow dev null says take standard out, and make it be dev null.
And then the 2 greater than &1 says take standard error, and make it standard out.
Standard out is already dev null. So what we're saying there is, make standard error be dev null as well.
So now NOTHING is the terminal.

[26:14] Okay. Now, I get why it was useful to have git log go to less so we could hit the spacebar and the man pages do the same thing. But what are the use cases that you see for this that you wish you had known before?
Automatic paging is probably the most common use case. You will see that used in a whole bunch of the standard Red Hat, in fact, the SystemD commands that Red Hat uses for managing stuff.
Another place that's used by Git, as well as by a lot of modern commands, is you can make the terminal do pretty colors by adding in really weird escape characters.
And you don't want those escape characters ending up in your output if you pipe it somewhere.
Like, you really don't want square, bracket, squiggly, whatever ending up.
They're really horrible escape characters.
So when you see a command like git, which uses colors to show you what's new and stuff, if you pipe that git command to a file, the color's gone.

[27:18] Again, it's detecting whether or not it's on a terminal, and it's only giving you color, if it's on a terminal. If it's not on a terminal, it doesn't fill your output with GLOP.
So how would we use any of these minus D0, minus D1, minus D2 for the three different standards?
How would we use it to make it go to less? You could say, if not, obviously you'd want to invert.
You might have a variable that contains your output.
Call it $mystuff. So you'd say, if minus T1, standard out. If standard out is a terminal, we say less space...
Sorry, we take echo whatever pipe less.
So echo $theoutput pipe less. Else echo $theoutput fee.

[28:12] Okay. How would it be going somewhere other than the terminal?
We would have had to have code that was saying to do something else with it under certain circumstances?
Not code. No, no. Remember, it's about how the script is used.
So if you write this script and someone puts a greater-than sign myoutput.txt, then your script will respond by having a different branch of that if statement.
Gotcha. Gotcha.
Okay. Okay. That's fine. So it's about how your script is used. Yes.
So your script is detecting at runtime what's around me. Your script is aware of its surroundings.
You can make your script behave differently depending on how it was called.

[28:54] All right, very fun. It is very fun. Thank you, Jill of Kent.
I was very pleased, very pleased.
OK, so now we're going to move into conceptually less confusing territory.
We are going to make our string outputs nicer.

[29:11] At the moment, when I have outputted a variable as part of our output, we've just said echo and then we've used an interpolated quote.
So a double quote. And then we just put the name of the variable in.
In there with a dollar sign in front of it. And it's taken whatever was there and just splatted it into the middle of the string.
And outputted it. Which is fine a lot of the time.
But maybe we want a teensy weensy bit more control.

[29:37] Like maybe we want to always round it up to having two decimal places.
Or maybe we want to always pad out a string so that they line up nicely.
There's all sorts of things you want to do to format output more than just splat the variable completely unchanged straight into the output, which is all we can do with echo.
And that is why printf, which stands for printFormatted, that's why that C function was written.
And the C language was basically written to write Unix.
So it's probably not a surprise that the shell scripts inherited from C, given that C lives in the same place, it's a Unix-y thing.
And like I said before, printf has made its way all over the place.
It's become one of those de facto standards. If I want to do string formatting, we'll do it that way.
I know Objective-C has it.
Do you remember there was a nasty bug with Apple's routers that if you named your Wi-Fi network something with a percentage sign in it, your network exploded?
That was printf. Because the special character to say this something special in a printf is percent.

[30:47] Oh, so you will recognize it really did leak in somewhere. It really did leak in somewhere.
So the printf command is kind of fun. It's really, really easy to just get going with it.
But it's really, really powerful. So it will probably take you the entirety of the rest of your life to learn everything.
But it's fine because no one person needs everything.
So you can learn what you need 90 percent of the time really quickly.
And if you need to learn something else, you can do a quick Google and you'll very quickly find the format string to do the weirdo thing you needed to do and you can pop it in.
So we're just going to do a spoiler here. I have read ahead and I think this is really fun. I imagine you're going to have way too much fun with the bits for your formatting tables. I can just see you having way too much fun with that. Yep, I was already experimenting.
Good, good, good, good. That is what I want. So we're going to focus on just the bits I I think are important.
And if you ever need more, know that there is more.
And then at this stage, we're 150 something episodes in. So at this stage, I'm hoping people are comfortable ending up on Stack Overflow and having a read.
Or pop it open the documentation and having a read.

[32:01] So the basic structure for the printf command is printf. The first argument is the format string.
So this is- What is a format string?
Because you don't define that in the show notes. That was question number four.
You never say what a format string is.

[32:19] Okay, I guess I need to rephrase my show notes. I do actually have a title.
Printf format strings is H2, but I guess that isn't clear enough.
No, it says better string formatting with printf.
Yeah, that's the H1. I even checked with Dorothy to see if you had already told me this so that you wouldn't say, Alison, I already told you what a format string was.
So tell us, Bart, what is a format string?
Well, I'm actually going to put you on pause on that. I promise you it's coming one page down in the scroll bar.

[32:50] So you can't use the phrase until you get to that, then? Well, no, but I need to tell you what the arguments are.
I need to... Here is the landscape, right? This is a country. It has a couple of cities.
We'll look at each city in a moment.
So the printf command, the first argument is this very important format string, which is going to take up most of our time today.
And then after that are zero or more additional arguments.
And so, the format string contains placeholders, and the additional arguments are the values that are going to get shoved into those placeholders.
So if your format string contains 4 placeholders, you need to have 4 more arguments to give, you the four values for your placeholders.

[33:33] Okay, it's funny that they define that new terminology that's annoying, because you could have called them variables, but they're not called variables.
But they're a value that they're...
If it's something that has a symbol and you've got a value you're going to shove into it, that's a variable. It's just another name for it, but I don't know why they didn't do that.
But it's inside a string. So if you use the word variable, people get really confused, because it's inside a string, so...
You put variables inside strings all the time, with dollar parenthesis the variable name.
Right, but that's a Bash thing. This is not a Bash thing. As far as Bash is concerned, a format string is just an ordinary string that contains the percentage sign. It's only the printf function that takes that ordinary Bash string, and the printf function is doing the magic. So from Bash's point of view, they are absolutely not variables. They are just, the percentage sign, and then another character.

[34:31] It's a subtlety, right? But the printf function is where the magic happens.
So the printf function takes, as its first argument, a string.
And then whatever amount of extra arguments you need to fill up all the placeholders in that string.
And that will depend entirely on what you're trying to achieve.
So the number of arguments will vary over time.
By default, printf writes to standard out. So you can use it in place of echo. You can, just substitute in printfs instead of echos. But what if you actually want to take 3 or 4 pieces of information, format them nicely, but save it to a variable? That's not an unreasonable thing to want to do.

[35:15] So printf does support that with a named optional argument, v for variable I guess. So you can say printf minus v nameOfVariable, then your format string, and then whatever values you want to shove into that format string. And then instead of the output going to stdout, it goes into the variable.
Okay. So. We can, as I said, there's examples in the show. So the first example is a very basic printf. So printf, the first argument is the string i like 2 have %s %d like 2. So we can s %d times a week !newlinecharacter, And then we pass a second argument of pancakes and a third argument of 5.
And when you run that it says I like to have pancakes 5 times a week.
Because the %s is our first placeholder, and that becomes pancakes.
And the %d is our second placeholder, which becomes 5. And again we'll look at why they're s and d in a moment.
The basic structure is printf string value, value.

[36:23] So far, so good. Yeah, I want you to tell me what a format string is pretty soon.
— You're using examples of it now. — Two lines. That apparently was a format string that you just read to us.
Correct. It was, yes.
We have another example serving into a variable dessert, and then we can echo our variable dessert, and you can see that instead of it going to the screen, our formatted string was saved into the variable.
— So... — Yeah, reading this, it makes perfect sense. It's really simple syntax.

[36:56] — Devilishly simple is the phrase I'm going to use. — Yeah. Oh, because it's a single digit.
— Yeah, and a single string, and we're not doing any... We're not doing anything clever with it, right?
We're just taking a simple value and shoving it out. So, the format string, that first argument, that is where the power and the complexity are hiding in plain sight.
So really, for the rest of this installment, we are going to talk about those format strings, right?
So, at first...

[37:28] What's a format string, Bert? Is the quote I like to have, percent S, percent D, times a week, unquote, is that the format string?
Yes, the first argument is the format string.
So, a format string is what? It is a string that contains these things I'm not allowed to call variables.
Percent S and percent D. Placeholders.
Yes.
Okay, so any string that has those placeholders in it that come after a printf, that's what a format string is.
Yes. Okay.
Yeah, so printf will treat whatever you give it as its first argument as the thing I'm supposed to transform.
So printf... Okay, so what was confusing me is I thought maybe the percent S was the format string.

[38:20] That's why I've been trying to get you to tell us what it is, because I didn't know which it was. It's the whole string that happens to contain those placeholders.
Yes, so the first argument, the whole first argument, is the format string.
No, it's not the first argument, because you've got printf minus v dessert.
Okay, so the minus v dessert is an optional argument which gets stripped away by optargs? Argops? Ah, getops!
Optarg. Getops. Right? Right, so even there, when the minus-v dessert is processed, the first actual argument is still the string.
Our optional argument is the minus-v dessert, so the first real argument is still the format string.
In both examples, the first argument is the format string.

[39:05] Hm. Okay, you can have an optional argument that is not the first argument.
Which is how, if I say to you that the first argument of ls is the folder you want to list, If you say LS minus L, you don't then say to me, well, no Bart, the first argument is There's no minus L.
If I say ls space minus l tilde, you're not going to argue with me that tilde isn't the first argument?

[39:29] I would have, until you've just— Apparently it's not.
Optional arguments are not considered— You're saying because it's not $0.
Yes, exactly. Precisely. Okay. Okay. Okay. That makes sense.
Okay. So everything really from now on is about that string, right? It's all about that string.
And in that string, it... So that string is made up of three things, right? So inside that string, there are three possible things that can exist. There can be escape sequences.
There can be format specifications, which is a terrible name I didn't make up. Fortunately, I'm reading from the manual.
I did. Good, because I would have yelled at you because it doesn't make any sense.
It doesn't make any sense. But okay. I went to... I checked the manual to make sure I was using the right words. And I was using way sensibler words. And the manual told me, They are format specifications. I abbreviated the spec.
Okay, so escape sequences, format specifications, and then what's the third thing?
Plain text. If you're not one of those two, you're just text.
And what printf does with just text is it just prints it.
So unless you're an escape sequence or a format spec, you're just going clean through.
You're just getting whatever you are in, that's what you are out. So you just get past it. Okay.
So let's start with the escape sequences because they're easy.

[40:47] They start with a backslash, and then as far as we're concerned there's only really three of them that matter to us, and there's two of them that matter to us, and there's a third one we need to be aware of.
So backslash enf is for a new line, backslash t is for a tab, they are the two most important ones. And should you need to have a backslash in the actual output, then you have to do backslash backslash.
Because otherwise it doesn't work.
So I have a very very silly example which shows that Bash is actually quite clever in its printf.
So if you're only using slash t and slash n, you can use an interpolated or an uninterpolated, string, so single or double quotes, and it will work fine.
So if you printf ho slash t ho slash n ho slash t hum slash n, or if you did the same thing in double quotes it will print out ho tab ho newline ho tab hum And it will be properly lined up. And it will do it correctly.
Which makes a lot more sense if you're looking at the text. I bet nobody could hear that, but I know what you mean because I'm looking at it.
So it works fine whether you have double or single quotes, which is a relief.

[42:02] Because within a double quote, slash n actually does have a meaning as does slash t, but basically the printf is like, well, you've given me a literal tab, I'll interpret that the same as a backslash t, and I'm happy.
Oh, you've given me a literal newline character, I'm happy with that.
So it's fine with slash t and slash n in either type of string.
That cannot be said about backslash backslash. If you want a backslash in a single quoted string, it's just backslash backslash.

[42:29] If you want a backslash in a double quoted string, the double quotes are going to interpret everything once.
So backslash backslash gets collapsed to backslash.
So if you want what gets as far as printf to be two backslashes, you need four of them to start with. This does not please me.
Okay, I'm never going to need a backslash. I'm going to make sure no matter what happens, I'm never going to use one.
My advice is always use a single quoted string and the off chance you need a backslash.
Thankfully, they're rare. I think they're used as the escape sequence because they're rare.
Again, in life, you generally type the forward slash unless you're a Windows person. Right. Yeah.

[43:08] Yeah, I don't know how you keep track of when single and double quotes, it's mysterious to me. If something's wrong with my code, I change it to the other one. I just start randomly going through single quotes and changing to doubles because I don't know, I can't keep track. I need like a cheat sheet.
Well single quote means exactly this, double quote means I'm going to have a think about, it and stuff will change. So inside the double quotes, it's like it takes two passes at it if you want to try to remember it that way.
So a single... Well, but the place I got stuck on was the spaces in when I wrote more bacon, as one of the things you could choose from my menu.
And if it wasn't double quotes, it would interpret that space and make that a separate name.
So I would have a choice of more and a choice of bacon. Single and double would work the same there.
The important thing is that there be quotes.

[44:08] Oh, maybe that's what it was. Yeah, maybe there weren't quotes at all. Yeah.
Yeah. Okay. Well, see, I didn't even remember that correctly.
Practice. Quotes are going to be mysterious for life for me, I think.
But okay, now I like single ones and I'm never using backslashes. it.

[44:24] Inside format strings, that's a good idea. Remember that in a single quote, dollar variable name does not expand, right?
Because a single quote is literal.
So if you type, if you have a variable called dollar X, if you stick single quote, dollar X, single quote, the output will be the dollar symbol and the X symbol.
If you do that in here, Bart. I mean, that is the difference.
You are getting one. Me one. No, we are working towards a cheat sheet.
Remember, that is my promised finale. My grand finale for all this bash stuff is a giant big cheat sheet for, well, frankly.

[44:56] Me.

[44:57] Oh, I did not know that. I don't know that you told us that.
Okay, great. Great. No, you already gave me great praise about the idea last time.
So I like the fact that you don't remember you've praised me before.
This is good. You get to do it again. Anyway, yes, that is where we are headed to a nice big cheat sheet, which is selfish of me because I need one.
And I figure if I need one, we all do.
Okay, so where was I? Yes. Okay, so that all works fine.
Sequences out of the way, plain text out of the way. Right. The meat. These format specification thingamabobs. So they start with a percentage symbol. So if you're using printf and you see a percentage symbol, that's what I'm going to call a placeholder. The official name is a format specification. And a format specification contains up to four parts. Actually, there's a fifth part I'm completely ignoring for this series. So remember I said this thing can do more than than we're going to talk about. So we have the option of having up to four things after the percent. We can have zero or more flags. We can have a minimum width. We can have a period followed by a level of precision. We must have a type. So the types are, as far As far as we're concerned, the subset we care about are D for a whole number, which I think of as digits, right?
So 10 is a D.
F for a floating point number.

[46:26] So 2.145.

[46:28] S for a string and percent for a percent, because we have the same problems we have with the backslashes, right?
The percent means we're starting off a format spec.
So what if I actually want to print out 100 percent?
Well then I actually need to have %% I'm going to have %D for the number number percent percent.

[46:49] Okay. Okay. So, the simplest format specs are just a percentage symbol and the letter D, F, or S. All we're using is the percent and the type.
But we have these other three things which we can use when we need them.
And I could explain them all up front, but I'm going to show you instead of tell you.
I'm going to show you why we need them by having some simple stuff that's not nice, and then we'll make it nicer.
So we're going to start with a very simple format string. %d %s %cost $%f newlinecharacter In my show notes, I mentioned in giant big bold text, printf does not shove a newline character onto the end.
This means you can use 5 or 6 printf statements to make one line of output, which is convenient.
With echo, every time you do an echo, you get a new line, so you can't build your string up in pieces. But it does mean you have to remember that if you want a new line, you have to tell it, I would like a new line.

[47:58] But I can see the value of that if it would get really hard to read if you just had one giant long printf statement.
Exactly, exactly. So it is actually very valuable to be able to break them apart. But I do often find myself with my bash prompt suddenly shoved off way to the side. It's like, oh, I forgot to slash n, didn't I? Okay, well, let's rewrite the code and see.
You'll know right away. You'll know right, exactly. You'll know right away. So then we have three arguments after So, five pancakes, 5.5.
And what a print is— Okay, so let's say it again, because you said a whole bunch of words in between.
In his string, he's got %d is his first—wait, I'm going to get the word correct—format specification.
It's just %d. Then he's got %s, cost, and then he's got %f, which is going to be one of those floating points, but he's put a dollar symbol in front of it, which should just be passed through as a dollar symbol. And then he gives himself a new line.
And then he's got the three—what do we call those? Values?
Sure, let's call them values. Five pancakes, 5.55.

[49:07] Yes, which prints out, five pancakes cost $5.55000. Okay, so the five took the %d, because it's a digit, it's a whole number, and then you had to use a %s to put in pancakes.
So five pancakes, because it's a string, and then you wanted a floating point for how much it costs, so you put in the dollar symbol and the floating point ampersand F to get a 5.55000.
Yeah, now, it decided that floating point numbers should have six decimal places, six digits of precision. That's its default, it just decided. I don't want six digits of precision.
I want two.
Not on money. So how do I control... Not on money. Yeah, exactly.
How do I control precision?
Well, if we scroll back up, one of the four things that we can put in there is precision, which is period symbol followed by a number.

[50:06] So we can simply... Oh, I like that. That's good syntax. Yeah.
So it's dot precision value. You're going to hate lots of other things, but that one.
Okay, let's stick with what I like. Don't ruin my budget.
This one's good. This one I actually remember. So let's update our format specifier so that we have, instead of just saying %f, we're going to make it %.2f. So if we do exactly the same thing, but after the last one we now have %.2f, now we get 5 pancakes cost $5.55. Which is much better.
So now let's do some bigger numbers. Let's break it again. So earthDiameter becomes equal to 40075.017 which is the number of kilometers that the earth is around its equator, I checked.
So let's printf that. So the first argument to printf is the giant big string the space earth space is space percent f km space around space the equator period slash n and then end my string.
And then I'm just going to pass it one placeholder, or one value, which is $earthDiameter.

[51:17] Don't call it a placeholder, you already aren't allowed to use that word.
Yeah, okay, one more argument which is the value that's going to get shoved into my placeholder.
There's the right word.
I think for the audience you don't need to keep saying the spaces because it's a little harder to follow it. So it says the earth is %f, so that's going to be a floating point number, is %f km.
And you can just smash it right together because the KM is just going to pass right through around the equator. And then you put a new line and you're shoving in the value as dollar earth diameter, which you just finished defining in the previous line.
Correct. Which is going to output the earth is 4.0 whatever to 6 decimal places, kilometers around the equator. So now we have two problems.
The precision is too high.

[52:09] And where's the commas? How am I supposed to read this big wall of numbers?
So first off, we actually don't care about the size of the earth.
How's about zero decimal places?
Well, you'll be happy to know that 0.0 just means lop em off.
Don't care. That's a precision of no decimal places, so that's easy enough to do.
Great, so now we're down to 40075 km around the equator. That's still not right.
We want our separators. Now this broke my head for a bit, because when you read the documentation on wikipedia, it looks like the optional flag for the separator is the comma symbol. Which would be nice.
It's not. It's the single quote. Which is not nice. I imagine it as a floating comma.
It's the only way my brain is ever going to store this information.
Up in the air. It's an up in the air comma. That helps.
So if we go back up and we have a look, we can have % symbol followed by optional flags.
Ah, ok, so the optional flags go before the precision.
So that means that it's %'.2f.

[53:13] Sorry, .0f, because we want our zero. Now this is where string interpolation gets in our way.
Because if we use single quoted strings, then the single quote will end the string.
So now we've got to use double-quoted strings. Thank goodness we don't need a backslash.
If you ever need a backslash and the comma separator, you have a mess. You then have to do all sorts of escaping and stuff. So this time I'm changing to double-quoted.
I have a really annoying question. Go on. In France, they use the comma for the decimal.

[53:52] So how would the French just have to use English syntax? Nope. No, this is the magic. So the printf statement doesn't interpret it as insert a comma, it interprets it as insert a thousand separator. And it doesn't insert a precision as insert a period, it inserts it as insert a decimal separator. So when you're in a French version of Mac OS X... Yes! If you're in French Bash, it will do the French thing. Or if you're in in belgian bash. That's localization. So you'll be happy. There you go. A little bit of happy joy.
So if we now have our format string as %'.0f, that will give us a floating point number with all of the decimal precision gone and the thousand separator.
So finally we get the Earth is 40,075 km around the equator, which I can tell you is 40,000, because now I can see that it's 40,000 and not a wall of numbers.

[54:54] Right, right, right. That's fun. It's not that hard. Now that I can think of it as a floating comma, I'm okay.
Yes, it still makes me cranky. Why not a comma? I'm sure there was a really good reason somewhere somehow. It makes me cranky. Anyway. So the next thing that you can do, you can actually use printf to make tables on the terminal. So you've probably noticed that there are terminal commands that output tabular data. And they do that, they're probably written in C, so they're probably using the C version of printf, but they're doing it with printf because printf can take, and remember, so we have, if we scroll back up again, we have our four things, percentage sign, optional flags, optional minimum width, optional precision type.
So the minimum width is the key to giving us spacing so that things line up correctly.
Now it is a minimum width.
If you say I want this to be 10 wide and you give it 11 characters worth of stuff, it will not truncate it.
It will go, well, it doesn't fit. So it does treat the width as a minimum.
If you say this is 10 wide and you give it 12, it will print 12.

[56:03] Am I making sense? Yeah, it'll override the minimum you gave because you gave it, okay.
Yeah, it will overrun basically.
So the official documentation just calls it width and then it tells you that it will overflow.
So I, in the show notes, called it minimum width because a width that overflows is a minimum width.
It's not a maximum width, right? Right.
So by default, if you give something a width, obviously, if I say, I want you to print the a string boo, a width of 10. Well, boo is 3 letters, and I've said make it a width of 10. So then the question is, what do I do with the other 7? Do I have 7 spaces and then the boo? Or do we have the boo and then the 7 spaces? In other words, where do I put the value in the width I've just given it? And the answer is, the default is that if you specify a width, everything gets right-aligned. Not left-aligned, right-aligned, and is padded with spaces. So, 7 spaces and then boo, if I say I want a width of 10 and I pass it the value boo. So it's right-aligned, padded with spaces.

[57:19] So it's not... Okay, if you said the width was 10, is it 10 to the edge of the right-aligned or up to, I'm losing where the 10 is.
So the 10 is the full, okay, so it is, there will be 10 characters printed.
And the last three, the last characters will be what you passed it, and anything it needs to pad it will be padding before.
So if you say, I want this to be 10 wide, and you give it four characters, it will print six spaces and then your four characters.
By default.

[57:57] 13, 14, 15, 16, 17, 18, 19, 20. Okay, sorry, I was using Bart's example and testing that.
Now I understand. I wish I'd used a number smaller than 20. I made you count a lot there.

[58:09] So the fact that it's right-aligned is interesting. Your example.
Okay, so in my example, I'm gonna make a variable to hold our format string because we're gonna use the same format string for multiple rows of text.
So imagine if you're printing a table, you could end up using the same format a thousand times.
If you have a thousand pieces of data to print into your table.
So instead of copying and pasting a thousand times or whatever, you can save it as a variable because it's just the first argument.
So I'm just going to save my format as the string %20s %8.2f n.

[58:47] So that's %20s. We know the first thing is the, well, we gotta have the percent.
The second thing is the flags, but it looks like we don't have any. Correct.
Because we're not doing any commas with apostrophes or anything.
So you're saying 20, so that's the minimum width, and then you're gonna have a string, and then you've got percent 8.2f, which means it's gonna be eight characters, right justified, but it's gonna be a floating point, with 0.2 precision followed by a new line.
Perfect. 10 out of 10. Okay.
So then, in order to not muck up your terminal, I'm going to use the semicolon to have two terminal commands on one line.
So it's just a printf, semicolon, another printf. That's all that's going on here, because otherwise, when I copied and pasted, I had my terminal reappear, and my table wasn't pretty.
So it's printf, and then we are quoting our format string, string because our format string contains spaces.

[59:51] So it's printf space $rowformat. And then the first argument is waffles.
Wait, printf quote $rowformat?
Yes. And then our first value is waffles, and our second value is 4.5. Then we have our semicolon, printf, our rowformat again, pancakes 5.4. And when it prints out, we get a right justified pancakes, followed by 4.50, and then a right just of, sorry, waffles 4.50, pancakes 5.40.
And on a terminal with fixed width font, they line up perfectly. And the first column is 20 wide, and the second column is eight wide.
I think it's nine. One, two, three, four, five, six, seven, eight, nine.
Well, there's a space between the two columns. So there should be, it should.
Yeah, why is there a space? Because the %8 is not...
I intentionally put a space between the S and the %8.
If I wanted there not to be a space, I would bash them together.
No, you can't do that. Of course you can.

[1:01:00] Wait a minute. So right now it says %20S %8.2F. You're saying you would write %20S %8.2F? Yes.
Yes. Oh, because that space is just it's just a string character being passed through.
It's just plain text.
Oh, I'm glad I counted because that could matter. Now, something you will see done a lot is the pipe symbol used to separate your columns.

[1:01:27] Because then you really do get a table, and then if you use dashes for some horizontal rows, a few echo statements with dashes.
So not piping. Okay, I was a little worried there that I was going to be piping one of these format strings into the other.
It's not a format string. One of these, shoot. Well, the whole thing is a format string, right?
So you could say you... No, but that's not what I meant.
I thought we were going to be piping one format specification into another format specification, But it's just a string at this point.
It is just a character pipe. Yeah, precisely, precisely, precisely.
Context is everything. Context is king.

[1:02:07] So that's what it's doing by default. That's not necessarily what you want for your default.
So how about we left align our descriptions?
The flag for left align, and I have no idea how you're going to remember this, it's minus.
So, if we change our row format to be %-20s $%8.2f, the minus now means that our waffles and pancakes are going to be left aligned inside their 20 wide space.
So now when we print it out, we get waffles space space space space space space space dollar sign.
And then after the dollar sign, the eight width of the floating point begins.
Then we have our pancakes, dollar sign, and then the eight width of that one begins.

[1:03:15] Okay. And notice it is eight in total, so the period counts as a character.
If it was a negative number, it would count as well, right?
So it really is a very dumb eight characters. It's not like eight digits to the right of the period.
It's not thinking numbers. It's just thinking characters, right?
It's just there is room here for eight things to go on the screen.
So eight things. I wouldn't call that dumb at all, that you require that when you're aligning things, a period should be a character. That takes up the space.
Yeah, that's fair. There's a minus. I can kind of feel it. 20 means shift it over to the right, but if you want to shift to the left, you've got to go backwards. Minus 20, maybe.
If it works. It's not horrible. It's not like a floating comma for crying out loud, Bart.
Anything that works for you.
The other thing you sometimes want to do with numeric data is, instead of padding it with spaces, you might want to pad it with zeros. Because sometimes you actually do want to to have, like if you're building up file names, you might want to have 001, 002 or whatever.
So you can do that with the flag zero.

[1:04:22] Hmm. That one actually does make sense. So if we have the row format percent zero three D, the first zero is the flag because the width has to be one or more.
So so a zero will never be interpreted as a width because a zero is the flag.
Widths are one, two, three, four, five, six, seven, eight. That bothers me a little bit.
I know it looks a bit weird, doesn't it?
But it does work because if you print out one, 20 and 300, we get them perfectly lined up as 001, 020, and 300.
And you did that with percent 03D. Yes.
So it's a digit, it's going to be three wide. You could have made it five wide.
I could have, and then I would have had two more zeros padded in front of each one.

[1:05:10] Wait, why? Because if you say five, and we're padding with zeros, then there will be...
Oh, you're telling me 03 is saying how many zeros to pad with? No. No, it's just... Zero means pad with zeros, three means how wide. So zero five... But width is not how many digits you have, width is how wide the column is. We just finished saying that. Correct. And instead of padding with spaces, we're padding with zeros.
I don't like this at all. So is it minus...
How do I make it be a width of 20 and right justified? Then you would just say 0, 2, 0. 20, 0...

[1:06:01] And if you want to write justified, it's what you get by default, so...
No, but I think I don't understand. So it says %03d.
You're saying the zero says we're going to pad with zeros and we're going to make it three characters wide. Not the column, not the column width, but the number will be three digits.
The placeholder shall hold three characters on the screen, and the...
There's no such thing as a placeholder.
That's not a word we're allowed to use, right? The format spec shall be three characters wide.
The output from the format spec... But it's not that minimum width.
But that's not minimum width. Yes it is.
But we said minimum width was the width of the column, not of the number of digits in what you're looking at. Maybe it's different with numbers?
No, there are exactly three characters. 001, that is exactly three. The width is three.

[1:07:09] The only thing that's changed is that the spaces are now zeros. If you take the zero, it would be space 1, space 2, 0, 3, 0, 0.
Okay, but what would the syntax be to make it be right justified?
It is right justified.
We want the column width to be 20. Okay.

[1:07:32] So you're saying that in order to have this padding of zeros, they have to be part of the width. Okay, so when you make something 20 wide, the default is we fill in the gap with spaces. The zero just means don't fill in with spaces, fill in with zeros. Okay, so that's different than just saying there's a leading zero in the number. These aren't really numbers, these are padded places to make up the width. That's a little different.
I was thinking a lot of times you want numbers to, you know, to look correct, to have the same number of digits. But in this case, they also define the width. It's a little, just a little subtlety. Yeah. Okay. Yeah. Okay. I understand it now. I don't like it, but I understand it. Now, if you mix positive and negative numbers, things can get out of alignment again. So what do you do if you have a mixture of minus and positive numbers?
Well, one of the things you can do is explicitly prefix the positive numbers with a plus. I, think that looks ugly, but I'm sure there's a reason. I'm sure there are places where you would want to do that.
So you can have the flag plus to say prefix my positive numbers with pluses.

[1:08:46] So then if you do that, so you say percent plus D and then you ask it to print out minus one zero one one, you get minus one plus zero plus one plus zero, I don't want to irritate the other.
The other deal would get cranky at the plus zero. But anyway.

[1:09:03] Or a nicer solution might be to pad with spaces.
Wait, it's not the other Jill, it's still Jill of Kent. It's the same Jill.
Oh, so it is, yes. Apologies, Jill.
Keep our Jills correct.

[1:09:17] Something that's more practical to want to do is just to have a space in front of the positive numbers so that they line up. So they line up.
Yeah, and you can do that with the flag. Oh, you're going to love this. You can use the space as a flag.
Oh, come on. I know! So %space means... %spaceD means I want the digit and the flag is the space.
And it looks like you're separating it, but it's not.
The percent space D is all, because it goes as far as the type, which is D, it makes my head hurt.
It knows, and it knows not to apply that space to the minus numbers, the negative numbers.
Yes, because that's what the syntax, so that's the meaning of the flag, is pad positive numbers with a space.
When you see your command and you see the output right afterwards, it makes sense at that instant in time. But if I was looking at that without your output, I'd be going, what? My advice when using printf in your own code is to comment generously, so that future you knows what you're doing. And if you're going to read someone else's code that uses printf, have the manual open. Just have the docs open. You're going to need them.

[1:10:31] I promise you, you will need them, because you'll be looking at it going, percent, space, what, what, what, what? Because you can have as many flags as you like. As many flags as makes make sense, you can shove them all together. So as a final example we have the wonderful format %singlequote.2f.
So %space means I want to prefix my negative numbers with a space. The single quote means I want 1000 separators. The .2 means I want a decimal precision of 2. And the f means floating point. So if I run that with minus one two three four point five six seven eight and nine eight seven six point five four three two I get minus one comma three two four point five seven. So I've truncated by two. I have my comma and on the next line I have space, nine comma eight seven six five six point five four again truncated to two decimal places with with a space and my comma.
Not truncated, rounded.

[1:11:41] Yes. Fair. And also rounded down. So it was negative 1234.5678, so they rounded to a smaller number.
They rounded down. They went in the down negative direction.
Yes, and actually the 57 was rounded as well in the upward direction.
That's the one I'm talking about.
I'm saying it went more negative.
That is important. Oh, sorry. Yes, yes, yes.
Time adder application, that threw me because it didn't round, doesn't round down in JavaScript.

[1:12:22] So it was getting bigger. It was like, wait, what? That took me a long time to find that one.
Oh, that is weird. Yeah, I had to do some jiggery-pokery to fix it. I hope I said that correctly, but it was one way or the other. It was doing it the opposite of what I thought it should.
Interesting. Bad interesting, but interesting. And then one final little tip. So the precision operator obviously makes sense with decibel numbers. And I did tell you that you specify a minimum width. What if you actually need to make some text not break out? What if you want your text not to smash through? You use the precision operator to truncate strings.
So if you want to have a string be exactly three characters, you would say 3.3s. 3.3s.

[1:13:17] So a minimum length of three, and then a precision of three, which means if it's longer than three, chop it off.
And that will give you exactly three. That's so annoying because now you're taking decimal syntax and applying it to strings.
To strings. Wow. But I just have a word. So this is a highly efficient language you're working with, as you said.
It's information dense. There are very few characters in these format specs, but there's a lot of meaning in those very few characters.
Like a regular expression, it says a lot with very little, which is powerful and very confusing when it's other people's code.
Yeah, yeah, definitely. Those three letters and those digits, they're doing a lot of work.
They really are doing a lot of work. So the final example in the show notes, we have %-3.3S %2D and we're giving it Monday 5, Tuesday 11. And that ends up printing out a nice little table of M O N space 5 Q 11 and it's all nicely aligned. The first row left aligned not that it really matters when you truncate it and the second right aligned.

[1:14:40] As I say, you can get so carried away with this, so carried away with this.
Usually, to be honest, the way it normally works in real world is you start with just percent S and percent D or percent F and then you run your script and it won't look right and then you go, OK, what do I need to change?
And then you'll start to throw in your flags and your precision until you get what you want.
That is generally how I would advise doing it.
Oh, you're on mute, Alison.

[1:15:12] I am. I took a drink of my water. My ice makes noise.
One of the things I did to help with this was I used my screenshot tool that I'm testing right now called Shotter to take a screenshot of the syntax that you gave us, so I've had it floating.
So no matter how much I scrolled through the show notes, I keep going, okay, flags are first, then min-width, then precision, then type.
Got it. Yeah. Yeah. And that is it.
Final thoughts, Jill's wonderful message serves as a fantastic reminder that the community drive this series. And the further we've gotten into it, the more into the hundreds we've gotten, the more the community is affecting the flow of things. The things the community find interesting, I spend extra time on. When the community get confused about something, I loop back. And when the community have a great idea, I will very often take it on board, as this entire episode took a whole different turn than what I'd planned because of Jill's message. So, potfeat.com forward slash slack is where you can hang out and send me messages of your own. Or Allison.
Very good. Lots of people in there having great fun. Yeah, the Programming by Stealth channel is probably the most active other than Delete Me where we just goof around.
We throw random stuff, yeah. And then the second thing I'm hoping you appreciate is is just how powerful printf is.
It really is.
It makes it possible to write some amazing outputs to the terminal.
And like Perl's regular expression syntax is a digital life skill.

[1:16:39] These percentage symbol things, they show up all over the place.
Now that you know about them, look out for them.
And just like when you buy your first TV, you'll suddenly see EVs everywhere.
Once you know these percent symbols, you're going to start seeing them all over the place, including in security alerts for Apple routers from time to time.
So anyway, that is, that is hopefully a new life skill. And I'm going to end with a challenge.
So I had to think long and hard about this. And I've teleported us back in time a bit.
And we're going to set our menu aside.
And way early in our bash adventure, we did loops.
And the tradition for making loops and setting homework is printing an X times table.
Well, tabular data sounds like something that maybe we could have some x times data in.
So, I would like you to write a new shell script for printing out a nicely formatted multiplication table.

[1:17:34] I'd like your script to require one argument, which is going to be the number whose table we print out.
By default, I'd like you to multiply that number by 1, 2, 3, 4, 5, all the way up to 10.
I would then like you to accept two named optional flags. Minus lowercase m for a different minimum.
Instead of it being from 1 to something, be minus m to something and an uppercase m for a maximum because minimum and maximum start with the same bloody letter so I figured minus m minus m, so minus lowercase minus uppercase to replace the 10 for the uppercase for the upper limit so that way you can make your table go from you know minus 4 to 5 million or whatever you like, Then, as some bonus credit, if you have the time and the inclination, if and only if standard out is connected to a terminal, I would like you to send your output through less.
Oh, there it is. So if you do the 100 times tables from minus 1000 to plus 1000, I think we should have that with a space to go page to page.

[1:18:50] Okay. Okay. So, there you go. You get to practice everything we talked about, I hope.
Yeah. Yeah. That sounds like a fun one. Now, I assume we're allowed to go use our previous homework as a starting point.
Oh, yes. I certainly will be. I like that assignment. I was good at that one.
I didn't do so good this week, but I'll be back in it. I think this will be a lot of fun.
This looks... I like this alignment stuff. You know, I like tables.
I like organization. The more I wrote these show notes, the more I thought, Alison's going to like this one.
I also knew you were going to hate some of the syntax, so I preemptively sort of nipped it in the bud.
But I knew you wouldn't like things like the single quote, because I don't like it.
Silly. Right, right. But I didn't get the design, and I just have to tell you.
Right. Well, that is it.
So, until next time, lots of 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...

[1:20:09] Music.