[0:07] Well, it's that time of the week again. It's time for Chit Chat Across the Pond. This is episode number 765 for April 15th, 2023, and I'm your host, Alison Sheridan. This week our guest is Bart Bouchats with another installment of Programming by Stealth. How are you doing today, Bart?
I am doing good. A little bit more rained on than I wanted to be, but yeah.
[0:27] Well, I'm going to use a quote from you. We have this big strange yellow thing in the sky and nobody knows what it is. It's the first time it's like we've awakened to sun in months.
I awakened the sun too, in fairness. The weatherman said, as the day goes on, cloud will build and rain will arrive by, what did he say, late evening, which apparently is four o'clock.
I grumble with his definition of late evening. Anyway, before we get stuck in, we wanted to make an announcement here to make sure that everyone knows that there's a link to the show notes if you're listening for every episode in the podcast feed. So if you're listening to this, look in your podcatcher.
There's a link to be able to take you to Bart's fabulous show notes. It's a little bit convoluted because it sort of goes to my site and then back out to Bart's, but you can get there and you can get all of the tutorial show notes. The other thing is that we want to make sure you know that you can join in the conversation with us and other Programming by Stealth learners by joining the podfeet slack. Now, it sounds kind of funny to get there through Programming by Stealth, but work with us here. There's a channel called hashtag PBS, and that's where all the little nerdlets that live and chat about programming stuff. And it's a lot of fun in there. And so if you want to talk to Bart and me and other people, Bart dips in and out, but I'm in there pretty regularly. And there's a lot of people in there smarter than me. So that's real easy to get to. It's podfee.com slash slack.
[1:50] Excellent. Yes. And it's a good community with good people. And yeah, it's got quite lively. So nice to see.
Yeah, and the reason we're bringing this up is because we've had a lot more focus on this show lately.
A lot of people writing to us and getting excited, and a bunch of people we've never talked to before joining the PBS Learners that we set up, the organization in GitHub.
But a lot of people are saying, but how do we talk to other people?
And how do we talk to you? and we're like, oh, we don't ever say that on this show.
[2:25] And it's kind of strange, but it seems that the two topics that have really set this series on fire are Git and Bash.
And Git I was expecting some traction on. Bash is a surprise, but it's getting a lot of traction.
People really are tickled by it. We're getting lots of comments, lots of questions, people sharing notes together.
I am finding what I've found in almost every part of this is most of the people listening already know how to do this subject, but are learning more because of you.
And Ed Howland is a recent joiner of this, and he said that.
He said, I've been using Bash for years, but I cannot believe how much I've learned from Bart. And Bart, you had a great response to that.
Well, I'm basically about five minutes ahead of the class, because I too have been writing Bash scripts for years.
But you're always trying to do something.
And so your goal is to get the something done. And so you go to Stack Overflow and you copy and paste it in.
And it looks semi sane. So you go, give it a go. Oh yeah, it does what I want. Great.
[3:25] But if you say to me, why is it dollar star, not dollar at, or why is that quoted? Or why is that not quoted? A lot of the time I go, the internet said so, it works.
[3:36] But you can't do that in this series, right? You, your job here is to be the every person and you're very bloody good at it, which means I am terrified of arriving for an episode without being able to answer the obvious question, why?
Oh, okay. Right? If I type it and I can't say why, problem.
And so you have to study that and then you end up explaining to us and then we get to know that. That's really cool.
Well, that's fun. Yeah, so I'm learning so much. So much. Today's a perfect example.
[4:07] Speaking of being five minutes ahead of the class, I have not done the last installment's a bonus homework because at 2.59 p.m.
Bart, I started at three. At 2.59 p.m., I figured out why my homework from the week before, two weeks before, had not been working.
And I have, it's not that I haven't been working on it. I've had my code, your code, side by side by side.
And we actually talked about it the last time that I had to use that IFS thing.
I had to change IFS and change it back. And you said that was a bad smell.
I have been searching for why for, what is that? three weeks, trying to figure out why. And at 2.59, I realized, when you do this select, if you don't put the name of the array in quotes, then items in the array that are two words, or have a space as a separator, become two separate elements.
Yes. When they expand out, that is, yeah, the quotes are important.
I could test the array, and it would tell me, yeah, more bacon was one thing. But as soon as it did the select it would be more and bacon and I have been fighting that till $2.59 today. Please sign.
[5:15] And the same would happen with a for loop. So if you did a for in, it would have come up as once through the loop for...
Was it smoky bacon or something? So once for smoky, once for bacon.
[5:25] Well, I had a completely different way of doing it, an if-then-blah thing going on, and it didn't work there. So I changed it to a while loop the way you did it, and it still didn't work. Finally, I went through and I used your code with my menu to see if it would work, and of course it worked just fine.
So there was a subtlety. Yeah, but I finally saw it at $259. So I have not yet done this challenge, but that challenge looks pretty easy. So I may even figure it out today.
Well, to be honest, that one was kind of a layup for the one you're getting at the end of this. So you can skip it and go straight to the one at the end of this, to be honest, because this is really where I wanted you to go.
I love the challenges. I love them. They get me fired up. I want to play. I wake up thinking I'm going to work on this and then I go, oh, first I got to do that and that and that and that and that and that. And then it's Thursday. You might recognize yourself in this one.
[6:16] Part of the challenge is so that I know I'll make you smile.
I added in a little joke that you will have far too much fun with.
Oh, I like it. I like it. I do want to say that you have completely dragged me into your madness.
I posted in our Slack at podfee.com slash Slack that the other day I needed to create text file with something in it, and I opened the terminal to do it, Bart.
Yay! This is a good sign. I approve this message.
I guess. But I was just like, what's happened to me? It was the easiest way to do it.
[6:58] Yeah, exactly. Right, well, what we are spending our entire installment on today is getting better at doing command line arguments.
And along the way, I'm going to do one of those things I do where I've told you some stuff but I've tiptoed around certain words in my descriptions.
I haven't told you an untruth, but I have definitely not told you the whole truth.
So I'm about to reveal that without knowing it, you have been using standard POSIX variables.
So POSIX is like a superset of a whole bunch of shell scripts, because POSIX is the attempt at having it not become a complete mess. So the POSIX standard tries to say that the hope was that every single Unix and Linux would be POSIX compliant, which means that if you stick to POSIX, your shell script will work universally.
[7:54] Okay. And I'm not sure we've arrived exactly there because the only major operating system that's POSIX certified is the Mac.
Unless you're living in the banking sector where stuff like, oh, what's the...
Some of the big... The HP UX, HP Unix and stuff, where those things are still in use, they're certified POSIX.
So you have certified POSIX, you have verified POSIX, which is basically it passes all the tests. And then you have sorta-kinda-posix. And I think a lot of people are probably cranky that Linux is sorta-kinda-posix, so it's like mostly posix. It's a bit like HTML3, or sorry, HTML5. It's like every browser does most of HTML5, but not quite do it all, and posix is a bit like that. But anyway. So being mostly posix, does that mean if we write something posix-compliant on the Mac, it might not run on Linux? Or does it mean the other way around?
Or yes. If you're in POSIX land, you're on the safest ground you can be. But at the end of the day, these computers are built by humans and they all have their foibles.
[9:02] OK. Well, we got to try, though, right? We got to try. But a bunch of the stuff we're doing today is different to how it would be if Bash did it. Not because Bash is schizophrenic, but because Bash has to do the POSIX stuff to be POSIX compliant. And so we're going to learn about some variables that don't know if they're an array or a variable. They're like a quantum variable. If you ask them a stringy question, there'll be a string. And if you ask them an array-y question, there'll be an array. But they're kind of both and they're kind of neither, which is a bit odd.
But that's the POSIX standard for you. So that's why two of these variables are weird.
It's not Bash's fault. It's POSIX.
But anyway, we'll get to that. So it is all about arguments. That is our biggest thing today is we want to make our shell scripts behave like terminal commands. They should do things the terminally way so that they can slot into your own and other people's terminal shenanigans. If you do things the same way as everything else, it's just easier.
Don't get confused. Your stuff just works. It's just, you know, when in Rome, do as the Romans.
[10:16] First thing I should say is you will find in the zip file for the show notes the solution to the challenge. And to be honest, it's extremely by the book, because all I asked you to do was to add an optional argument to the existing menu script. And the argument would be a rate limit. So if you If you don't pass the limit, you should keep taking items for the order until the user chooses to say they're finished.
There is a limit, then you should cut them off when their virtual tray is full.
I think you're sort of describing the requirement, including the how you did it, because I don't think you were that specific.
So I said, but you didn't say that you had to set an argument.
It says update your solution to, well, wait a minute. No, maybe it did, except an optional argument limiting the number of items.
[11:15] That I've pretty much copied and pasted from one to the other.
So do you mean that the user of this shell script has to set the argument in an interactive way? Is that what you were asking us to do?
Well, so basically when you run the shell script, you can optionally pass it an argument that is a number.
Okay, okay. I misunderstood. I thought it was just saying, I can set this thing that you're only allowed to have three things for breakfast, and then the response comes back and says, hey, quit eating so much, or something like that.
Well, I guess you could be snarky, right? I didn't tell you what to do when people reach the limit. Okay. Okay. I understand better now. Okay.
Yeah. Like I said, I finished the other homework at 2.59, so I just read this very quickly.
[11:59] Yeah. I don't use anything in my sample solution that we haven't seen quite a few times already.
So it's extremely by the book. And there is an infinity of ways of doing this, right?
Because at the end of the day, you're going to default your limit to some value that represents unlimited. Then you're going to check to see if there was an argument. You're going to check to see if the argument is sane or not. And then if it is, you're going to change the limit from your default, that means there is no limit, to whatever the user said. And then in your loop, where you're going through your select over and over and over again, you're going to have to add a conditional to check, should I be applying a limit? And if I should be applying a limit now or not yet.
So it's basically a bunch of if statements is what it comes down to.
So the main thing I sort of wanted to draw your attention to in my sample solution is that.
[12:55] I chose to represent no limit by an empty string. So I set my limit to an empty string to represent there being no limit.
And the reason for doing that is it means that you can use the minus z and minus n tests in your conditionals. So minus z means zero length and minus n means not zero length.
[13:17] So if you want to check is there a limit then you can just say if minus n, dollar one or dollar limit. And if you want to check if there is no limit, then you use minus z, because if, the limit is z or zero, nothingness, then there is no limit. So depending on which is easier to write your code, which will make the most human friendly code, because you could always use not, of course, to switch everything around. But I like to write my code so that I can say it and it's written the way I would say it. It just, it makes code easier to maintain.
So you're comfortable saying minus z as a human word?
Yeah, because in my brain it's like empty, right? If the limit is empty, if the limit is not empty.
Yeah, I'm just barely getting to where when I see minus z I don't go, what the heck is that? So I'm definitely not at the, that looks like a human-friendly thing yet. I'm getting there.
I guess looking like isn't quite right, but logically being structured as. It's easier to read it says, if the limit is empty, is easier to say than if the limit is not not empty. If you end up writing your code with nots and stuff all over the place, when you're saying it, it's like not not, not or, not yet.
But it's actually the other way around. It's if empty limit.
But it depends on what you're trying to do. It's not if limit is empty.
[14:36] I think you may be digging more deeply than I'm intending you to. You can end up writing your code with nots and stuff, or you can write your code where you're at least having the operator that makes it the straightest path so that you don't tie yourself into big circles trying to debug your code later.
If you can avoid writing if not this or that and all these, if you can make your conditions as simple to express as possible, in the future you will be thankful because you won't tie yourself in knots.
I guess that's the point I was trying to make. But there's an infinity of ways of doing it.
And then the only very small other subtlety is... What did I say? Actually no, that's it. That kind of is all I figured I could even say. It's all varied by the book. Varied by the book.
Good. Okay, so that then brings us on to our friends the special posix variables.
[15:33] We've already seen some of these. So I've written up a table in the show notes of the most commonly used of these.
The first one is $PATH in all caps. That's part of the POSIX standard.
So the PATH is a list of directories that your terminal goes and searches for when you hit a command. So when you type the command GIT, where does it go to find GIT? It checks everything in the PATH.
[16:00] $home will always be your home directory. So in bash, we can use tilde, but that's a bash thing.
In any shell script that's POSIX compliant, you can use $home.
That is a POSIX thing.
[16:12] So let me back you up a little bit. Okay. We're calling those variables.
We were talking about POSIX standards when you started. So when you say a variable, you mean it's a standard existing variable that I can always use everywhere, in any shell script, or at the command line, and everybody knows what it means?
If your shell is POSIX-compliant, it must provide these variables.
These variables are mandated by the standard.
You mean match these variables. These variables must mean this.
Like $PASS, $HOME must be the correct user's home directory.
Yeah, so the specification says you must make these variables and they must do this.
So that means that whether you're in SH or Bash or in Linux or Unix or anywhere that's POSIX, you can use $PATH and it will be where are the executables hiding? They're somewhere in your path. Where is my home folder? It's in $HOME. Now again, bash people can take a shortcode with a tilde character, but that's a bash thing.
The other one then is the input field separator or $IFS. And you have encountered this strange beast. And I have warned you away from it. Because this little fella subtly affects how other terminal commands work by effectively changing the definition of what is a space.
[17:35] Basically what it does. And if you set ifs to a non-standard value at the top of your script, then 100 lines later, something could utterly break. Because it will have switched from defaulting to I split on spaces to Oh actually I split on tildes now. Or the other common one is you'll see terrible code for I want to process a CSV file. Change ifs to comma. Great, that one one line of code will now work.
You will now be breaking apart your CSV on commas. But the read line later down your file has just gone completely nuts because instead of breaking at the end of a line, it's now breaking whenever the author puts a comma in the text.
[18:15] But warning me off of it seems like an odd thing because, I mean, there was a better solution to my problem.
What I was doing was I was telling it, don't make ifs be a space just for a minute here because I need these things to stay together and not get separated.
My more and my bacon needed to stay together.
And I was immediately turning it back off right afterwards. And that was a... sounds like what it's for.
And by the way, the internet says to use it. No, there are people on the internet who are wrong about many things.
The internet tells you all sorts of terrible things.
It's a pretty common answer to the question, because I kept searching and searching and searching and searching.
I kept coming up with that answer every time. And if you use it and then you undo it, if you put it right back to space, like if you wrap your...
You have to remember to do that or you have to put it inside the subshell where its effect is contained.
It's like a radioactive source. They can be safely used, but you do need to know that it is a radioactive source and that it does have side effects.
Mark Watney, whatever you do, don't dig up this radioactive source.
Right, exactly. It is...
Like, and especially when I know that the answer is just to quote the thing.
[19:31] Don't go near the radar, Kirsten Stewart. Oh, you know that. But that took me three weeks.
I know, but I didn't want to say, yes, Alison, you have found the answer, because I really wanted you to find the quoting solution, because that is actually the better answer by far.
Don't break it. It kept me out of the bars for weeks, so it was a good thing to not tell me.
Now, I think we will cover, we will probably look at ifs later when we have looked in more detail at subshells, subshells, because that is, there's a special keyword in Bash called local that basically says I want to shrink the scope of my actions to the tiniest, tiniest thing possible. And local and ifs are very good friends, because it avoids spooky action at a distance by basically saying make my spooky action right here. And then you can use it because you've basically lead shielded it. We're making this analogy far too... We're stretching it out, but you You get the idea.
Okay. We've also seen the next of the special variables, $?. It is the exit code from the previously executed command.
So we use $? to see what was the exit code of the last command.
If you say echo $?, you will see the zero or the one from whatever came before.
Oh, that's right.
[20:42] What we haven't seen before, but you will find that on the Googles quite a lot, is $$, which is basically my process ID.
So if you need your script to find out what process it's running as, it will be $$. If your script starts another script as a background process with the ampersand symbol, you may want to then kill that background script you started before it finishes, because you've decided if it runs for more than 30 seconds, something's gone wrong.
How do I get to it? Well, its ID will be in $! So you can then say kill minus kill $!
But your notes say it's the most recently started background task.
I could have had my backup start running and be the most recently started background task.
No, because it wouldn't be started by you.
All of this is from the point of view of the script, right? So ifs is for your script, $? is for what's in your script, right? It's local to you. It's not system-wide.
Because otherwise it would be pot luck. Seriously pot luck. That would be spooky.
[21:50] Spooky action at a distance of a very different kind. So if your script starts something with the ampersand symbol, so you've backgrounded it, then you want to be able to kill it.
But if you background five things and you don't save the value of $! into a variable of your own, you have lost it the next time you use the ampersand to background something else.
Take me to the terminal we looked at backgrounding with ampersand.
It's not something we've done here because we have never had to write a script that we want to do two things at once.
Because that is how you do multitasking in the one script, right?
You could have the one script start... Oh, no, so many ways of...
Running in parallel. That's what's worse than radioactive. That is how you get bugs. That is how you break things. That's how you get race conditions. That's how security falls apart. Handle with great care, that one.
We already met $1 to $9 as the positional arguments, but they have a very logical friend, $0. It's the name of the script itself. So on the terminal, you type...
Does that include the path? Does it include the path or no?
It's exactly what you typed in the terminal. So if you type .slash, then $0 will contain .slash.
If you typed the full path, then $0 will contain the full path.
It's literally whatever you typed before the first argument is the zeroth argument.
How was I invoked?
[23:14] OK, it's potentially useful and also potentially dangerous, but not dangerous, potentially misleading, potentially not as useful as you'd hope.
Usually potentially not as useful as you'd hope, actually.
And then we come on to the three I really care about for today.
Dollar, octothorpe, pound, hash, whatever we're calling it this week.
Dollar star and dollar at.
And they are all to do with our arguments. They tell us things about our arguments.
The first one is the very easiest one. How many arguments are there? That is stored in $octosrp, $pound, $hash, $shebang, $... I think that's all the names we use.
$hash. Didn't we decide on $hash?
$hash. The one with the two lines and the other two lines. So if you want to know, did they pass four arguments, if open square bracket, open square bracket, $pound minus eq4, they passed four arguments. It's a number. It's very straightforward.
I have written a demo script, should you feel like it. pbs149a numargssh. It says echo you passed $args. So if you run that script and pass it no args it'll say 0. If you run that script and pass it 42 as one arg and then space single quote life universe everything single quote, it will correctly determine that it's two arguments, even though they have spaces, because you quoted it. Leave the quotes out, it'll tell you you passed 4.
It's very straightforward.
[24:42] And then we get to our two friends And our two friends... These are the guys I warned you about that don't know if they're a variable or an array They don't really know what they are Officially they're called expansions So they behave more like star.txt than anything else, They will expand out to become the arguments of the script But they will do so in subtly different ways And you can make dollar star behave not just subtly different, but dramatically different by messing around with ifs, because ifs will actually control how dollar star behaves.
So by default, dollar star breaks everything apart on spaces.
[25:28] But if you make ifs be something else and dollar star will break it apart on something else.
It's actually really difficult to explain in English how they're different.
So I have written a script so I can show you how they're different.
Let me guess, you had to do this to get it straight in your head too?
I tried to read it, and then I tried to understand it, and then I tried to replicate it, and I was like, nope, I'm just going to do a script.
So you will find pbs149b-args-expansions.sh, and what it does is it echoes, just to show, how many arguments to read, youpass$octosrp args.
Then it runs a for loop across $ with no quotes, so it's an unquoted version of $.
Wait, wait, wait, stop, stop. For a in $star, I don't have any... you're saying for whatever in this expansion variable, but I don't know what that means.
Well, I'm going to show you. When you run the script, you're going to see what it means.
Expands out to represent your arguments in a certain way.
[26:37] And you will see what it does when we run it. But you're acting like $ is an array.
Correct. You're asking an array-like question, so it's behaving like one.
Okay. I told you they're weird. It expands out to become the arguments.
And you will see how it does the expansion when we run this code.
It will produce a number of items that the for loop is going to loop over.
And we're just going to print them out.
[27:04] Okay. So it's four for loops. What happens if I expand $ star without quoting it? What happens if I expand $ at without quoting it? What happens if I expand $ star with quotes? And what happens if I expand $ at with quotes?
What do you mean with and without quotes?
Okay, so you can see it says for a in $ star, no quotes. Then we have for a in $ at, no, quotes. Then we have for a in quote, dollar star quote. That is a quoted dollar star.
And then we have for a in quote, dollar at quote. So like you can quote any variable, you can quote these weirdo variables, and if you quote them or don't quote them, they will behave differently if they contain spaces. So if, If you have arguments with spaces, there are four possible things that can happen.
And rather than try to explain it, which is impossible, we're going to run this code with different arguments.
[28:12] Oh, great, I pasted the code twice and the results... I think you've got the results in there, but I've been sitting there looking at that, and that was part of why I was super confused.
Because he's got the code in twice. I've got the code in twice.
Oh, phew, I've only got the code in twice. Oh, phew, I've only got the code in twice.
Right, but I think the other part's fine. The other part is fine. I thought I'd left out the other part, and that would have really ruined this description. So we're going to run... Ah, I see what's missing. What's missing is the bit where I say, run the code with name of script, and then the two arguments are 42, and then inside single quotes, life space universe space everything. So that's what's missing there. I will fix the showdown.
You see why I was a little stuck.
I can see why you're a little confused. Yeah. All right, so we're going to run this script with two arguments. So we're going to say ./. the name of the script 42 life, the universe, and everything, not in quotes.
No, we're going to quote the argument. We're going to quote the argument so that we have a normal argument, which is 42. Exactly. Exactly. So we want a normal argument, which is 42, and then we want a quoted argument, which is life, universe, everything. I didn't bother with the and. I figured he'd get the joke. Okay. I'm gonna do it but I'll just do it with something a little bit easier just so I can follow along. Life, the universe.
I'll just stop right there.
[29:35] So you will notice if you don't quote them, they behave the same, right? Unquoted dollar star becomes four things. 42, life, universe, everything. So because we didn't quote it, the spaces are all, you know, as far as the for loop is concerned, the spaces are there, so the for loop sees four things, because we didn't quote it.
So could I remember that as dollar star without the quotes doesn't keep that array as just two entries, it breaks it up at the spaces?
[30:16] And dollar at is exactly the same thing. Okay.
That kind of helps me remember it at least. I didn't have quotes around it, so it did look at it as separate things.
It broke apart. Okay.
[30:30] Now, if you're an advanced programmer, you can use ifs to make $ star exploded on something else. So you could make it split on every i by setting $ifs to i and then you'll get like 42l and then ife.
No, we can't let Allison play with the ifs. That's dangerous.
Yeah, precisely. And whereas $rat will ignore ifs, $rat will just do the spaces. So $rat is your reliable, I will break it on spaces. It's very straightforward. But then the question what happens if we quote it? Well, $star takes everything and says, oh, you want it all in quotes, do you? All right then. One giant big thing, 42 space, life space, universe space, everything. It quotes the lot. Oh, okay. Okay. So it takes the two arguments, slaps them together, puts spaces everywhere and says, great.
Or you could think about it as star for all. It's all arguments as one string.
$at, when you quote it, says, well, I'm going to give these back like they were given to me.
I got two things. I will give you back two things.
So quoting $at is 99.999% of the time what you actually wanted.
I was just about to say, I'm not learning any of the other ones, Bart. I'm only learning that one.
[31:54] So what you want is double quote $at close to the double quote, and that will give you your arguments. Like a sane person. I would like them the way the user wanted them.
Hey, how about we do what the user asked? I'm sure there are valid reasons for mushing them altogether. I could say $quoted seems vaguely useful. Gimme everything, just in one big string. Okay. But unquoted, they're both pretty useless in my opinion.
Right. Now, these things are behaving sort of kind of like arrays. Could you have an, actual real array, please? Well, thankfully you can, because you can combine the quoted version of $at with the array syntax and just make a new array named args by saying args args becomes equal to, open a round bracket, quote, dollar a, close the quote, close the round bracket. Ta-da! A normal array named args that contains your arguments.
Okay. You like to do that just because it's human readable now?
It means that I can then... Yeah, so if you have a thing where by default you do one thing, otherwise you do whatever was in the arguments, well, you're going to have an array that you're you're going to default to some stuff.
Well, what do you do afterwards?
You could have an if statement that says if it was an array, you write this code, else write this other weirdo code with those expansions.
[33:17] Or you could turn the expansion into an array and just do the same thing.
Or if you want to add some default values before and after the arguments.
Right. Sometimes it's nice to have a real array. Think you can do things with real arrays, right?
Not something just masquerading as an array.
[33:35] Not some sort of quantum mechanical thing that doesn't know whether it's up or down.
So yeah, if you want a real array, and you'll see that a lot on results, you'll see open round bracket quoted $at. And what it's doing is just saying, give me an array, no messing about, just give me a real array. And it makes everything easier.
[33:52] Okay, so there is an example in the show notes, which proves to you that it is just an array, and it uses all the same syntax we use in the previous installments with the $open curly bracket octosorp args open square bracket at close square bracket to give us the number of items in the array. And we can loop over it by having the quoted dollar open curly bracket args square bracket at close square bracket. All of that syntax you hated. It all works fine with this new array we've made.
You know, I don't actually hate it because I've gotten used to thinking it through each time. It's certainly not native.
I think it's true as well. Look how it's in quotes, Bart.
This is probably the thing I will never forget.
Everything's going to be in quotes, whether it's right or wrong from now on.
If you think about it this way, quoting by default is safe.
[34:44] Yeah, it seems like it. Pretty much, because then spaces don't break everything.
Right, so all of this is in preparation for the main event of the day.
So this allows us to deal with normal arguments, like space, 42, some string or whatever. But remember I said we want it to be like those big boy terminal commands, like ls? And what do we know about ls? Well it does things like ls minus l. Or you can specify funny colours with minus c for the different colours. You can do all sorts of cool things. Like our terminal commands have these minus blah flags all over the place. How do they do that? Well they could do it in an infinity number of ways, but it's become a sort of a pseudo standard, and the way they do it is with the...
SH and Bash both do it with a built in feature of the language called getOpts.
[35:39] And we are going to learn how to do that. Now, I'm going to give you a little warning.
If you go googling for getOpts, you may get sent down the garden path because there's a completely different thing that works completely differently called getopt without the s. And that is a terminal command, not a bash language feature. It is a very old terminal command that predates bash and could be argued has features getopt doesn't, but is so complicated it may as well have no features because you'll just go insane.
It will just break you. So getOpts.
The S is for super. Or whatever, I don't know. Plural is better.
More is better. Anyway, getOpts. You want to be careful you're looking for the documentation for getOpts with the S on the end.
[36:33] So, when you look around at the terminal, there are actually two distinct… Okay, so the universal term in the jargon for these minus-blah things are options.
And options come in two flavors. You have a boolean option, basically it's there or it isn't.
And we euphemistically call those flags.
Because like a flag on a mast, there either is a flag or there isn't.
So there either is a minus L or there isn't.
Oh, so that's why they're considered Boolean? Yeah. They're either there or they're not.
Yes, no, in this case, present, absent.
And they're referred to colloquially as flags. So you just flag the fact that...
Boolean options sounds funny. Boolean to me is and or or, right?
Well, Boolean means two-valued.
So Boolean... So decimal means ten-valued. we happen to use the symbols 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
It could be donkey, banana, pear, whatever. As long as there's 10 of them, it's decimal.
Boolean is on, off, true, false, yes, no, one, zero. They're all Boolean.
[37:46] They're common pairs, and they obey Boolean algebra. They're Boolean.
But the meaning of them is yes, no, what usually we use the word Boolean.
That's what it is here, too, right? Either the flag is here or it isn't.
Because if they do it or don't, well, yeah. Yeah, but the variable is here or it isn't.
It's not called a Boolean variable.
It is, I've seen it. Thank you.
[38:10] The argument, you're giving an argument to a script, and it's optional, doesn't make it a Boolean argument. It's just an argument. I very likely don't understand this yet.
In formal computer science, it is absolutely a Boolean because the flag is either there or not.
[38:31] That is Boolean, true-false. Did they specify minus L? Did they not?
The minus L is there or not.
The minus L is present or absent. So there's a test running to see if it's there or not.
It's true of any Boolean variable. If you don't use a Boolean, what point is it?
That's what I'm trying to...
No, I'm trying to back into why it's a Boolean option, is because there's something that's going to look to see is there a flag or not. It has to be a 1 or a 0.
Okay. Yeah, it's either there or there's not. Okay. Okay. In contrast, an optional argument is "-someletter value".
So the ssh command will assume you want port 22. What if you don't?
You say "-p 2222", if you want to go to port 2222, which is the default alternative.
So the "-p says I'm going to tell you in the next argument I want a different port.
Next argument, the different port." Interesting. So optional arguments always require a value. I never noticed that.
That's what makes them different. That is what defines being an optional argument versus a flag. If I need a value, I'm an optional argument. If I'm just there or not, then I'm a flag.
[39:53] So do I need a piece of paper, or am I just a thing I stick up in the air, or I don't stick up in the air?
Okay. I think I'm with you. Okay. It's actually hard to say in words. It's kind of more logical looking. So just to remind us.
[40:07] If you've done taming the terminal, you've seen a lot of flag-type arguments, right? So the one I always go back to by default is ls, because everyone knows to type ls minus l. If you don't...
Hang on. I'm going to tell people something really quickly. If you're new to this series, Taming the Terminal is another podcast that Bart and I did. It's 42? I don't know.
I think it was 43. 43, which makes me slightly sad because it's not Douglas Adamsy.
Shouldn't have done that. But anyway, it's a podcast about in the mid-40s of episodes teaching how to use the terminal, in case you haven't seen it. That's what he's referring to.
Okay, so go back to minus L.
[40:46] Yeah, so the LS command by default gives you a multi-column list of files, right? If your your directory has 10 files, it will list a few of them and then a space and then a few of them and then a space and then a few of them. So it'll be in parallel columns.
If you want to see the details, you want a listing. And the way you tell LS, turn on your listing mode is with the minus L flag. So you just say LS space minus L and then it outputs this nice detailed listing instead of the columns. There's another flag minus a, which tells ls that you want to see all files, even the hidden ones.
So what you've probably also seen is ls-al. That's called cuddling your flags. And it literally is the same as ls-a space minus l.
And getops implements this convention, so with getops we can cuddle our flags.
[41:41] So there we go. The other apps let's us do that. Why is minus L not the default?
Whoever wants to see it in those annoying columns. I mean, if there's like three things, maybe.
But since there's more than three, I can't read it.
I never use LS without minus one or minus L. So minus one puts them all under each other without the detail, which is great if you're putting the result of LS as a pipe into another command.
If you want to count something, it's a nice way to count things.
Or if you want to grep for all files that look like something, or I want to see the details. So I either want ls-1 or ls-l. A plain old ls is just annoying. You can't copy and paste it. What do you do with it?
[42:22] I can't even read it. Rant over. Exactly. Useless. I can't read alphabetically poorly. Yeah.
Oh, God, yeah. Yeah, terrible. Okay. So to illustrate the optional args, the most common example is your minus p for the port number on, say, ssh. So ssh minus p space 2222, some user at some dot server. Now, there are people who will tell you that minus p is a flag.
Those people don't actually understand what a flag is. That is not a flag, that is an optional argument.
But I do now. Yes, because it requires a value. The two, two, two, two. And it makes no sense, right? I mean, I want a custom port. If you, don't tell the custom port, that is nonsense, right? What is the point in raising a flag to say, I want to do something different and then fail to tell poor SSH what the something is. It doesn't make sense. So they're logically different concepts. And so it will be obvious to you which you need.
[43:22] Now, there are some limitations to getOps. GetOps is old. It actually comes from the Bourne shell, from SH. So there's another convention that has come up because terminal commands have gotten complicated.
So there are 52 possible flags, slash optional arguments, with this minus whatever scheme.
There's minus a to z and minus capital a to z and then you're out.
[43:48] Oh, good point. So you can have archive and add with minus little a and minus big A, but what if you want archive, add, and append.
You're going to have to call one of them minus P for app end or something, right? You start doing these weird things like grep uses minus V for invert because minus I was needed for case insensitive.
You just use minus one. Okay, that's a fair point. Don't you use zero and a nine also?
That's true. You do also get minus... Well, no, can you have those? No, you can have those.
You're right. You're right. You can have those. Okay, so 62. But the point being, when you If you try to make them have logical, memorable names, you'll run out.
And so there's a new convention called long options, which is minus minus and then a name name and getopt does not do long options.
So we are stuck in the past. We can't have minus minus track like we can use in bash, or not in git.
We'd have to use minus uppercase T or something.
[44:48] The other thing that getopt is very limited on is getopt doesn't let you mix things.
So when you use ls-al, you still give it a normal argument, which is what would you like me to list. So if you leave it blank, it'll list the current directory. But you can list, any other directory from anywhere. If you say ls-l tilde, it will list your home directory no matter where you are. And I'm pretty sure ls is smart enough that if you say ls tilde and then minus l, it will still be happy. And there are many terminal commands where you can... like with ssh, it won't care if you do the minus p at the beginning or the you can say ssh user at server minus p443 or ssh minus p443 user at server it doesn't care mix them and match them, it doesn't care, getOpts is nowhere near that forgiving, first come the options then come the arguments, can't mix them.
[45:41] So you have to do minus whatever, minus whatever, minus whatever and then the regular arguments, it's not a huge problem, it's just a little bit less flexible, So the user has to do it in the right order, which is fine.
Okay, so now, I spent so long trying to write this bit of the show notes.
GetOpts is really simple and it does make sense, but I think you need to be on mind-altering drugs to notice.
So it is designed to be used in a loop.
So what getOpts does is it starts at the start of your list of arguments And it does one flag, it saves some information in some variables, and then it exits with an exit code The exit code is success if it's not finished, and it would like to do another argument at some when you feel like it, or failure when it's finished In other words, it's designed to go in a while loop, So you will always see, while get out.
[46:54] You just lost me. I'm going to be using getops in while loops?
I thought getops was the name for this description you've been giving us this whole time of using flags and optional arguments.
It is a command that will process those flags for you, right?
So your script will be designed to accept flags.
Well, you need to write code to react to those flags, to process those arguments.
Those flags are just arguments.
Your script has been called with arguments. Some of those arguments have a name starting with a minus.
[47:36] Which makes them an option.
I think I'm lost at the very beginning still, Bart. I thought getops, like I said, I thought you were describing getops was this collection of this concept of optional arguments and flags.
But you're saying I'm going to use GitOps to use those terminal commands inside of my code? No. My script?
So that your script can accept flags of your choice.
So if you are writing a script and you want your script to be able to have a minus v for verbose, use, then you use $1 to get the first argument, and you use $2 to get the second argument.
So that's how your script reacts to arguments. But how do you react to options? So how do you react to these special arguments that start with a minus? You could write your own code.
How are the special arguments going to get into my code?
Okay, so you could write your own code, then loop through every argument, check if it starts with a minus symbol, and then you do all of the work, of figuring out that it's one of these options.
[48:55] But I must have written my shell script to want to see a minus v.
Right, exactly. What you would have to do all the work of figuring out, is this a minus v?
[49:08] There's a lot of processing, because if you say, my script to minus v, some arguments, well, the minus v is optional, so sometimes some arguments is in third place, and sometimes it's in second place, or whatever, right?
[49:24] So dealing with that complexity would involve a lot of code.
You could do it from first principles and you would end up writing lots and lots of code, that you would then have to replicate in every script you write.
So GetOps does it for you.
GetOps understands these minus-style options.
GetOps does the heavy lifting. In a concise sentence, getOps allows you to take in arguments that have flags or optional, arguments.
Yes. It processes them for you. Now I know why we're talking about getOps.
I did not get that at all. Okay. So it is the chainsaw for cutting that particular plank of wood.
It is the tool you will use to help you do that task. And it does it one at a time.
So it starts at the start of the list of arguments.
And it looks to see, are you a flag? Oh, you are a flag. I will capture your details, and I will store them.
And then I will stop. And I will also check to see if there's another one to come.
If there's another one to come, I will exit with success.
If I'm finished, there are no more flags, sorry, there are no more options, then I will exit with fail.
[50:47] Which means that in a loop, as long as there's more work to be done, the loop will go round and round and round because the loop will keep going until it gets a fail.
So this is how readline works, right? It's give me a line and I will exit with success or fail depending on whether there's another line to come.
So I will exit with success or fail depending on whether there's another option to come.
And so you will always see while getOpts.
Because otherwise you're just going to get the first option and that'll be that.
So you're going to loop them.
Okay. Now, I've done a bit of hand-waving there. It puts some stuff in some variables.
I may need to make that jump a little bit higher at you. So it actually uses three variables.
And one of them, it lets you name yourself.
And usually letting you name things yourself makes your life easier.
But in this case it probably makes life more confusing because it means everyone's example on the internet is different. Because everyone has picked a different name.
[51:45] So the first argument that can have any name you like, I am always going to call it opt.
Because what getopt does is it puts the letter of the option that it found into this variable.
So if it found a minus v, the variable of your choosing will contain the value v. If, it found a minus d, the variable of your choosing will contain the letter d. If it found a minus h, it will contain h. Okay.
Am I making sense? Yeah, I don't...
Not yet. How we're ever going to use this, but... That is a worked example on the way, because that is the only way to make this make sense.
But I do need to say it, confuse the crap out of you, and then we can un-confuse you with an example.
Okay, so we're just going to use opt as our variable name, but we could use it, it could be pancakes.
It could be pancakes or waffles or anything we like, exactly.
But it will contain the option we found, right? So getOpt goes and looks to see, is there an option here?
And if there is an option, it will put the name the option into the variable of your choosing.
It will also fill or not fill the magic variable OPTARG in all caps.
Because if it's a minus p port number, where does the port number go?
[53:08] If it's an optional argument, then the value of the argument goes into optarg.
So it's always optarg, but it's not always opt. It's always optarg. Yeah, exactly. Why? Either pick them all for us or pick none of them for us.
This I don't get. Okay.
[53:24] Now, I said that it steps forward step by step by step. How does it know where it left off?
The next time it comes around the while loop, how does it know where it was?
It uses the variable optInd, which is the index of where it is in the list of arguments.
And that's really useful, because when it's finished, it tells us where the dividing line is between the minus-y stuff and the normal arguments.
So depending on how many minuses there were, the normal arguments are going to be in different places. So $1 isn't $1.
So we need to actually, when we're done dealing with the minuses, we need to clean house so, that all the other stuff is available to us as $1, $2, $3, $4, etc.
And we can do that, but only because opt-int will tell us how much detritus we have to clean away.
So if I've said LS minus AL in there, it would have opt-int would be two?
Or let me guess, one? Correct. It starts at zero? Of course it does.
No, not because it starts at zero.
If you think about it from the terminal point of view, "-al is one argument. to say, yeah.
[54:40] Now, opt arg understands that that's two options, so it will go around its loop twice, but the value opt int will be two, because the next thing is the second argument.
So opt int always points at what's next.
What would I look at next? I would look at the thing after what I've already processed.
So opt int is always one in the future.
Don't worry. Okay. paste one liner that does all the cleanup.
All the cleanup is a copy paste.
But the point is, Opt-in makes it possible to tell the difference between the minusy stuff and the other stuff.
Because we need to get our other stuff, right? We can't lose the other stuff because then we suddenly lost an amazing ability to just have plain old arguments, right?
The easy stuff would become impossible, which would be so silly.
We'd have these weird arguments, but no normal ones.
I don't want that.
So anyway, we have our three variables. Now, what arguments does getopt want? So getopt is.
[55:49] Effectively a terminal command. So while getopt, getopt what? The first thing you have to do is you have to tell getopt what flags and optional arguments you are expecting or are valid.
And you do that with a string, and because they're all one-letter names, you just put the names you want in the string.
So if you want to accept a minus A and a minus B, you just put in the string AB.
And they will be two flags, A and B.
So it's AB, not A space B. Just AB. No, just AB.
Okay. Well, I guess that works because you would know it's always going to be one letter, so So it chops it at every letter.
Exactly. Precisely. If you would like A to be an optional argument, it's A colon. If.
[56:42] You would like B to be an optional argument, it's B colon. So the colon after the letter just means... It means it's like a minus P 443.
So no colon flag with colon, I want a value.
So a colon, it's waiting for the thing on the other side of the colon.
Okay, all right. Yeah. That's how it knows whether I take one argument or two.
How big of a bite am I supposed to take here? If I see minus p, getopt needs to know it needs the next argument, too.
[57:17] If I see a minus L to LS, it knows I'm done. This one doesn't have any more. I'm done. I can go on and do the next thing.
So that is how you specify it. And then, if you ever invent the time machine, I promise you you're going to use it to go back to the developers of GetOpt.
Because A, you're already cranky at them for making you name one variable but not the other two.
And B, you're going to kill them for reusing the colon for a completely different meaning in the same string.
Oh come on. I'm not making this up. I couldn't invent a dumber thing.
If the colon goes first, it means I'll write the error messages. Thank you very much. Not you.
By default, getOpts will put very, very generic error messages that are of no assistance to anyone.
If you would like to have an actual error message that means something, you want to write the error message yourself.
And the way you tell getups to be quiet is by starting the string with a colon.
So before you put any letters in, you put a colon in that basically says, shh.
And then you put your letters in. So colon a colon would be an optional argument where I'm going to write the error message.
Yeah, and colon a without the second colon means a flag, and I'm going to write the error message.
[58:41] So do you write the error messages by flags and optional arguments?
So would you end up with like colon A colon B colon...
No, because the... The first colon is either present or absent.
So either the string doesn't start with a colon. And after that, it's letter, option E followed by a colon, letter, option E followed by a colon, letter, option E followed by a colon until you're done.
It could be colon A, colon B, C, D.
[59:14] That would mean two of those who had optional arguments, and I'm going to write all the error messages. Wow.
Okay. Yeah, that leading colon is just the... that has gone down in history as one of the silliest decisions that a human being has ever made.
Because it makes the whole thing really hard to read.
Like, really hard to read.
Okay, so the first argument to getups is this string that tells it what flags, what options, whether they be flags or optional arguments, what are we expecting?
If you use a flag or an optional argument you haven't defined, then it will actually come to you as question marks.
Remember I was saying that you name a variable and then that variable will contain whether you did a minus A or a minus B. If you tell OptArgs that you only want a minus A or a minus B and someone gives you a minus C, they won't get C in the variable, they'll get question mark.
If you give anything that wasn't supposed to be there, you get ? and that's how you know to shout at people. Oh.
[1:00:23] If ? you did this wrong? Yes.
So it's basically, huh? So getUpt is kind of like the idea of getUpt going, what?
I was told to look for this and this, and you gave what?
So ?. Okay. argument to getOpt is the name of that variable that you have to give a name to. So we're always going to say getOpt some string with some colons in it, opt. So we're always going to do that.
So we're going to have while getOpt a string opt. So what do we do inside the while loop?
But inside the while loop, the value of opt will be whatever the letter of the flag that was found.
So it's processing your arguments, looking for flags.
The first time you go into the loop, getOpt finds a flag.
It will put the letter into the variable you gave it, which is $opt for us.
So we have to check what is the current value of $opt and then we have to react to it.
[1:01:41] Now, you're on mute, by the way. I certainly am. I've been saying okay.
It's fine. I can lip-read really well.
It really wasn't a problem, but I figured it would become a problem at some stage.
But at some point, it might be. So, this is why we do video.
So, we could do a bunch of if statements, right?
If $optEQ a, else if $optEQ b, else if, which would work perfectly.
However, it's really messy code. So remember we learned about the case statement a few installments ago, which is basically a really efficient way of doing an if-else?
[1:02:21] That is why every example on the internet will be while getopt case. Those three commands go together. Like love, marriage, and something. Kids, I guess. So you'll always see that particular while getOpts case. And then inside the case statement is where you deal with your flags and optional arguments.
So this is all very hand-waving. So I'm going to stop waving my hands and I'm going to show you some code.
So we're going to write a script which is going to take two options, a flag and an optional argument.
So this is hello world, but we're giving it a spin.
So the flag you can give it is an Allison-friendly flag, minus S for be snarky.
[1:03:12] Okay. That's good. And the optional argument is an adjective to describe the world.
So hello what world? Wonderful world, wicked world, wobbly world, wibbly wobbly timey wimey world, right? But you get this put and an adjective in front of world.
And that is the optional argument. So that gives us minus s for whether or not to be snarky, which is a flag, so there either is or there isn't a minus s. or minus w and then your world adjective.
[1:03:46] Okay. I'm looking at your code and it doesn't...
I don't see how we know that's a minus W.
Okay, well, we will get there. So what it says is bsnarky equals quote.
And he says assume minus S is not passed.
So wouldn't it be snarky? Right, so we need to set some defaults for ourselves before we actually process the args, right?
So the first thing we do in our script is we set up the default behavior.
What do we do if they don't pass us an option?
So I'm going to start by saying bsnarky is set to off, which I'm just representing as bsnarky is empty.
So my code basically assumes... It's starting assumption in live, before it checks its arguments, is not to be snarky.
It's starting assumption before it goes and checks its args is that the adjective will be wonderful.
[1:04:40] Then we use getOpts to figure out what the user actually asked us.
So then we say while getOpts, and then the string we use is colon sw colon.
Okay, so let me say this. We put it in quotes because...
It's a string and it has funny characters, so let's just quote it to keep it safe.
Sounds good. Okay, and there's a colon at the front because that means I'm going to write my error messages.
And we've got two flags, S and W. Actually, I'm sorry, hang on, let me say it, don't catch it.
S, W colon means that S is a flag, and W colon means it's an optional argument.
Bing, bing, bing, 10 out of 10. And we're going to put it into OPT.
We don't put it into $OPT because we call it $OPT. Later it's OPT here because we're defining it.
[1:05:31] Precisely, so we're saying it will be OPT, and we get the value of OPT with the $ operator, which is give me the value of.
Right, okay. Right. So while getopt, our string, opt, do. Wait, be clear and say getopts.
Yeah, good catch. Because I've been hearing getopt.
I'm afraid we should do a command F in the show notes for getopt, because I'm sure I typoed it somewhere. I did my absolute darnedest not to, but you know me. There's a good chance of at least one. Anyway. I don't see any.
Yay! Go me! There's probably one somewhere that's... there should be one somewhere that says don't use the wrong one. There should be a match of one. Anyway, so then inside the first thing, and the only thing inside our while loop, is the case statement. So that goes from case to esac, which is case backwards. We snarked about that last time. So case $opt in. So in other words, we're saying we want to look at every possible value, or we want to look at the different values of $opt, and then we say the first value we're interested in is S.
If we found an S, so everything between the S bracket and the two semicolons is what happens if $opt contains s.
[1:06:46] Okay, I'm forgetting this syntax because case was not last week, it was quite a while ago.
It says S, roundy bracket, and ends in double semicolons?
Yes. So between... And that's the normal case syntax?
That is the very weird normal case syntax. It means that everything from the bracket to the two semicolons is what we do if it's an S. So if $opt is equal to S... It's very clean.
[1:07:12] That's why it was written this way. Yeah, exactly. I always have to go look it up because it's a bit weird, but it is very clean.
So if at any point through the loop we meet the s flag, what do we do?
Well, just to make it clear what we're doing, I am echoing a debug statement.
In the real world, you would not echo a debug statement. So I'm just saying echo debug enabling snark.
And then I'm doing the actual work. B snarky becomes equal to one.
So they gave the minus s. therefore, we shall be snarky.
[1:07:44] Okay, and that was the variable that you defined up front. Correct.
That you set to a default value of not snarky, which, that's a little weird, but okay.
Yeah, I hummed and hollered, but anyway. Then we have the case for w.
So w bracket as far as the two semicolons. So what do we do if it's w?
Well again, we have a debug statement saving the custom magnet of $optarg.
Because optarg, we didn't get to name it, the value for an optional argument will always be in optarg. So what do we do? Well, we have to save it, because the next time we're in the loop, optarg will have a different value. So we say world.adj becomes equal to optarg.
And we quote it, because what if they put a space in? Perfectly valid to have wibbly-wobbly-timey-wimey.
[1:08:33] World. Right? That is sort of an adjective. And then we need to check for question mark, because question mark is getOpt's way of saying, it wasn't an S, it wasn't a W, but it was a minus. So we have to have a case for... So basically, you'll notice Linux and Unix commands do this. They say usage colon, and then they print out the name of the command, and then they list which arguments there are in square brackets if they're optional.
Now we could write the name of the script, but it's way nicer to show you the $0 in use in combination with the basename command we learned about in the previous installment that takes away everything apart from the filename. So .slash whatever becomes just whatever. Or slash whatever whatever whatever dot, you know. Basically it becomes just the file. Everything before the name of the file gets thrown away with basename.
[1:09:28] And the file in this case is the name of the shell script? It's whatever you typed to run the shell script.
So if you type ./. the name of the script, then it will be ./. the name of the script, but base name will take the ./. away. If you typed slash home slash Allison.
[1:09:44] Slash documents. Right, right. Okay. Got it, got it. Yeah. $0 will contain it all, and base name will clean it up for you, and leave it at just the file name.
Okay. So basically saying, give me my file name, and then minus S minus W adjective.
Got you. Let me say that one more time. So $0 is whatever you typed in, which may have been the full path, and base name is just going to grab the base name.
Including the file extension, right?
Okay. So everything that's not a folder.
OK. So there we are. And then we just, oh yeah, exit one.
Yeah, because when you get an error, you really should sud off. Yeah, exactly.
[1:10:25] And then we end the escape, sorry, we end the case with an isac, and then we end the while loop with a done.
So what do we do? OK, so we have now processed our arguments, right?
That took zero amount of time through the loop if they gave us no minuses.
If they gave us both a minus s and a minus w, then we took two goes around the loop.
And if they gave us something weird, goodness knows how many, well, at least one extra time around the loop at most, because then we would have hit the exit one and sodded off.
Right. So, you know, that loop will have happened a number of times, which means we will have dealt with our different options.
So now we're ready to actually do our greeting. So I'm going to assemble the greeting in the variable named greeting. I'm going to start with an empty string, and then I'm going to say, if bsnarky is empty, sorry, if bsnarky is not empty, then the greeting is, oh, yo.
Otherwise, the greeting is, hello. I was trying to be snarky without swearing.
All you. So minus n is not? Minus n is not empty.
[1:11:24] So if bsnarky is not empty, then we shall be snarky. So all you. Otherwise, hello.
And then we say the greeting is plus equals space dollar word adjective space world.
And then the exclamation point is in a separate little string because otherwise it will break everything because you can't have an exclamation point in a double-quoted string because it has a meaning, as we've discovered quite a few times in this series.
And then we echo. Why is it greeting plus equals or you in quotes?
Well, because I'm building it up, right?
I started with an empty string and I'm building it up. Yeah, okay, so the first ones we could get away with just equals because the string was empty, but it's a really good habit to build it up because what if we want to prefix it with like an emoji or something in the future.
[1:12:12] Okay. This way my code is robust. Okay. I always write like this.
I didn't catch that you were building it up.
Okay. Yeah, so we start by saying greeting is an empty string, and then we build it up with the snark, and then we finish it out by adding in the adjective, followed by world, followed by the exclamation point, and then we echo it out.
So if you run that script without the minus W or the minus S, it will just say, hello world.
It with just the minus S, it will say, oh you, world. Or if you run it with a minus W without the minus S, it will say, hello, beautiful world, or whatever. And you can do both, and it will say, oh you, beautiful world. Probably won't, it'll probably be stinky. You'll probably use minus W, stinky, or something. Like, oh you, stinky world.
And if you put in a different minus flag, you will get the usage error.
[1:13:06] So that is... I think I follow it, yeah.
So we are almost done, but we've left out one teeny tiny piece.
So what about the ordinary arguments? Remember I said this little hand-waving thing about, we'll just clean up after ourselves.
We'll just use this $opt int for the option index. They just clean up. We'll just clean up.
How do we just clean up?
Well, there is a wonderful command that exists in bash and many other shells called shift.
And the shift command takes the first argument and makes it vanish.
And it updates $1 to 9, $ star and $ at.
[1:13:54] As if the first argument had never existed. And if you shift again, another one disappears.
$123, etc., and $at and $star, they all update. So they shift one to the left?
They shift one to the left. And if you give shift an argument, they will shift that amount to the left.
That number, okay. So we will know that one more that we want to get rid of will be contained in the variable named $optint.
So we need to shift... One more because it's always looking to the next one.
Because it's always pointing at where it's going next. So we need to do the math.
Now at the moment, based on our knowledge of bash so far, we only know how to do mathematics using the bc for basic calculator command. So for us, we echo $optind-1 to bc and it will do the math for us, then we take the result of that and pass it to shift.
So for us, it's shift $open roundy bracket for run this command for me, echo $optind-1 BC. In other words, we take the string, whatever optint is, minus 1, we shove that into the calculator we take the result and we give that to shift.
[1:15:08] Okay. That is because we don't know that there is a bash way of doing math, because that's on our agenda for very shortly.
So if you prefer to skip ahead and just make a text expander shortcut now, you will make that shortcut for shift, open quote, dollar bracket bracket.
Notice dollar bracket bracket, we have never seen before.
Opt in minus one, bracket bracket. That will do the math properly.
But you don't know why that works, because I haven't told you yet.
But you can make the text expander a snippet now and just get into the habit of doing it right.
[1:15:42] I like the one with the calculator in it, pardon me. The one with the calculator makes sense to me.
I feel like so much of this is because of the limitations of the number of characters.
It's like, well, we're going to have like 14 roundy brackets.
You call them brackets and that never sounds like a parenthesis to me.
Parenthesis, I should be saying parenthesis, yeah. Yeah, yeah.
But I have a feeling we're going to have 42 parentheses around something at some point.
Because they've only got so much vocabulary. That is true. Now I'm happy to say there was nothing that...
I'm not aware of any that triplicate.
But there are... Yeah, they do duplicate.
Like you say, run out of symbols. So we can update our example.
Give one final example, pbs149e, where instead of saying, hello world, we're going to say hello to a person.
So we expect one argument always, which is going to be the name to say hello to.
And we are going to use the "-s", for snark But instead of a "-w", for an adjective to describe the world I'm just going to say "-a", for an adjective to describe the person, So in this case we're going to have a "-s", as a flag And a "-a", as an option As an optional argument, And we are always going to want, the name as a normal, plain old argument.
[1:16:58] So we have a bit of both. I'm not quite caught up on why we need to do this whole getting rid of, getting opt-in set back to nothing, but I'm hoping I'm going to understand this now.
We'll step through this code, and then we will describe at what point in time the code would collapse in a heap if we didn't clean up.
And it would collapse in a heap.
[1:17:17] So, very similar, bsnarky becomes equal to an empty string. And this time I've renamed my variable from world.adj to just adj.
And I'm defaulting it to brilliant, because I think I'd like to be brilliant today.
So we're defaulting it to no snark and brilliance.
Because there are multiple ways things could go wrong, I'm going to save that usage string into a variable called usage instead of copying and pasting it all over the place.
So that is the same syntax we've seen before.
But this time, name is a third argument on the back, which has no minus flag in front of it and no square brackets.
Square brackets is how you traditionally symbolize optionality. And the name is not optional, so it's no square brackets. So we're saying, you can give me a minus s, you can give me a minus a with a value, and you must give me a name. That's what we're saying.
Okay, so we're doing our standard stuff here while getOpts, and then we pass it the string colon s a colon. So we want a flag s and an optional argument a. We then choose the name of where we're going to store these options when they're found. We're calling it opt.
[1:18:30] Do case $optIn s echo enabling snark bsnarky equals 1 echo saving the custom adjective optarg, adjective becomes equal to optarg question mark sorry this is insane echo $usage exit 1.
So all the usual stuff.
Now imagine we skip the next line that says clean up after ourselves.
We now say name becomes equal to $1.
If they ran the script without the minus s and without the minus w, then $1 is indeed the name.
If they ran the script with a minus s, $1 is minus s.
[1:19:17] If they ran the script with a minus w but no minus s, then $1 is minus w.
Sorry, minus a today.
If they use a flag, $1 will be whatever flag they happen to use first. CK. Right. Right.
CK. That's why we have to clean up after ourselves. Otherwise we can't… How do we get at the name? It could be… CK. So we don't know whether it's going to be one or two in this case.
CK. It could be one or three. Well, no. So if they passed only a minus s, then it will… If they passed nothing, it would be at one. If they passed a minus s only, it would be at two. If they passed a minus w only, because it takes a value, it would be at three. And Wait, wait, wait, wait.
Sorry, minus A, minus A, minus A.
Okay, so backing up, if they do nothing, it would be $1?
Yes. The name would be $1. If they did minus S, it would be $2.
If it did minus A, it would be $3 or would it be $4?
It would be $3, because you have minus A followed by the adjective.
So minus A stinky, or whatever. So it would be 4?
— No, no, it would be 3. — Okay, so minus A stinky only takes up 1.
No, no, minus A stinky takes up 2.
[1:20:36] — But I've already got a minus S there. — No, no, okay, I'm saying, no, no, if you only passed at a minus A, then it would be at 3. If you passed at a minus S and a minus A, it would be at 4.
Okay, good, okay, that makes sense. So it could be anywhere between 1 and 4, inclusive.
Right, okay. So we do need to clean up, because otherwise we can't get our name in a sensible way.
So we clean up by doing our shift, optind minus one.
So if you just passed me the s, then optind will be two. So we want to throw away just the minus s.
So we want to throw away two minus one. If you only pass me the minus a, followed by the adjective, then optind will be at three. We want to throw away two, so three minus one is two.
Okay. You're seeing how this is working.
So that can be my text expander snippet, is that shift $echo $optin-1 going to the calculator. Okay.
Yeah. Because that will just work. It doesn't matter how many they passed, that will restore sanity.
And then whatever normal arguments there were after the options will now be left as $1234, etc. Okay.
And $at and $star have also been updated, so if we want to take the arguments into an array, it's there for us.
And then we can just carry on as before, we can build up our greeting and we can echo Do it oaf!
[1:22:03] And I have a note to myself saying, trying to decide whether or not I tell you that you can cuddle optional arguments on one condition.
[1:22:14] They have to be the last thing in the cuddle. So you can have minus S A, but you can't have minus A S.
Because the A needs a value as the next argument. So you can't cuddle on the right of an optional argument, but you can cuddle some flags on that's left. So you can say "-sa".
And you know this without knowing it, because how many times have you done a git commit "-am", followed by a message?
So that's "-a", flag, for commit everything, "-a", for all, and the "-m", optional argument for a message.
So that's "-a", "-m", message. But you do "-am", the message. So that's you cuddling, a flag with an optional argument. So that is it. Now, can you see why I spent so long today trying to write these notes? At the end of the day, the sample code is not complicated.
But answering the question, why? Why is it like this, Bart? Not easy.
Now I really think you should have tried to work the, what is it, at, wait, what was it, dollar star and dollar at into your last example.
[1:23:38] I'm glad I'm doing video, Bart's head just exploded all over the room.
Now I do have an optional challenge for you.
[1:23:48] Good. So taking our menu, I would like you to add a "-l", optional argument for the limit.
Because that makes a lot more sense than just having it as a bare number stuck on the end as a normal argument.
It should be a "-l", for limit. I would also like you to add a support a snarky flag.
I think you can manage being snarky in your menu. I'm definitely going to do it when the limit command comes up.
There was no question I'd already planned that, right?
There you go. So that is your challenge. So that will give you an opportunity to play around with a flag and an optional argument.
That sounds fun.
[1:24:27] Well, Bart, we have a new ending. Well, we do, but before that, let's just take a moment and look at the forest.
So I am saying to you, I want you to write shell scripts that are completely at home in the terminal. And we are darn close, because we can now do our little flags, and they can even be cuddled up together. We're doing really well here. What we're still missing is having our script behave properly inside a pipeline.
So piping something to R script and having R script send its output to another command through a pipe.
So, bloody, you know, grep something or other, pipe R script.
R script should be able to accept a barf of information from another command.
Like a grep command or whatever, right? So we're not yet ready to play ball inside a chain of pipes.
So we need to learn how to do the plumbing. Or rather, we need to learn how to...
The terminal will plumb in the input, but our script has to actually consume that input. Right, right.
[1:25:34] You can lead a horse to water, you can give a shell script input, but the shell script's code has to actually consume that input. And so that is where we need to go next, so that we can fit into a chain of pipes so we can do our terminal plumbing properly.
And yes, so I would like to remind everyone what we said at the top of the show. There is a community of amazing people. They are hiding over in Allison's Slack, and Allison is very good at running a Slack. So pbs.com forward slash slack. Everything good begins with podfeed.com. This I have learned. So if you go to the Slack, you sign up, there will be a channel called Pantson or Hash or Octosort PBS. And in there are lots of really cool people talking about all of this stuff and you can ask your questions and they're all friendly and stuff and there is no such thing as a silly question. And they will jump over each other to help you.
Yeah, and there's no gate to get into this slack. You just go there and you get in. And we haven't had anybody misbehave. It's just the nicest place. It's very happy and pleasant in there.
The community rocks. This episode proves what Bart always says. He never knows when one's going to go long and he never knows when it's going to go short. He said, this could be a short episode.
We're at an hour and a half.
And only a third of that was because of me being dense. So...
[1:26:52] Well, no, you're doing your job. Absolutely right, because I think it was a lot of people shouting at their iPods or iPhones or whatever we listen on these days.
So anyway, with all of that said, between doing your homework and playing in our community, remember, folks, there is lots of opportunities so you have lots of happy computing.
[1:27:11] 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.