 Well, it's that time of the week again. It's time for Chitchat Across the Pond. This is episode number 774 for July 29, 2023. And I'm your host, Alison Sheridan. This week, our guest is Bart Bouchotte, with Programming Myself 153. And we're going to talk about my favorite subject, Scope. Well, I think this is an episode I'm really looking forward to because after, I thought this was going to be a three-episode arc, but we're finally getting to the end of our journey in terms of learning new stuff. So we are going to come back and do a sort of a farewell episode where we put everything into context. And what I'm hoping is the farewell episode would be like a quick reference for me and I'm assuming many others. Good idea. Wrapping in a bow where we know how to get back to all the goodness. This has been a great series. I'll actually be sad to see it go. I've really enjoyed it. I think I like these smaller bite-sized pieces of something that's not. JavaScript was a long haul. What was that, two and a half years or something? Right. And it was also your very first language. So that makes it a longer haul. Our two more unexpected ones were Gate and Bash. And they've both ended up really good. People are writing to us now going, hey, I just found this Git series. This is really cool. So very cool. I use my own show notes a lot. One of the things I'm really excited about is I have started opening the terminal when I'm coding or when I'm working on the show notes so that I can do my Git stuff right from the command line. And that makes me feel really, really happy. I mean, I still bring out the big guns with a GUI when things get weird. But I mean, I set up a text expander snippet, Gitcom. And that's my Git commit message with Git commit-am. So I'm just ready to go doing it from the command line. It makes me happy. Excellent. Well, with my work hat on, I do a lot of Git stuff over SSH at which point. You don't have a client, right? So you're in on the terminal at the time. And it really is very liberating, empowering even. Yeah. To be able to do it on the command line. I think that's a really great word to use for the terminal itself. It's liberating. It's powerful. It gives you something you don't have. It seems like it's more limited, but almost its limitations are at strength in some ways. I don't know. It can do pretty much everything. Yeah. Finding the right magic word. All right, so as I say, this one is kind of bittersweet. This is our last bit of new content. But it's actually rather important because as you start to write bigger things, you find yourself copying and pasting the same piece of code multiple times in your script. And that sets off all of your, this is a bad smell from a software engineering point of view thing. And in JavaScript, you would have immediately gone, this is either a function or a class. And I have been finding it ever more difficult in examples and in sample homework and stuff, not to use functions. Because I find myself doing the same thing multiple times. And then it's like, this is wrong. I should do it right, but I can't do it right because we haven't talked about functions and we can't talk about functions unless we talk about scope and scope and bash is weird. And I've been a little bit afraid of it. I'm kind of glad I had a couple of extra weeks on this one. So I'm much more comfortable now. In my talk at Macstock this last week, it was about how I learned by teaching and I used you as an example of how you say that you're often one step ahead of the audience, you know, one week ahead as you're learning stuff. So you've learned a lot in this sequence too. I have, I absolutely have. So today we're going to learn how to use POSIX functions because POSIX is a super set of bash and bash has its own special syntax that only works in bash or we can use the POSIX syntax that will work in bash and SH and ZSH and lots of other places too. So I was like, well, I'm only going to learn one or I'll confuse myself. So let us learn the POSIX way so that our knowledge is portable. And then the other thing is, yeah, we'll get to lexical versus dynamic scope a little bit later, but let's start with functions but before we even do that, I did set you a challenge last time, which is a bit of a cheat of a challenge because the challenge was to take your existing sample solution for our multiplication table printer script and to rewrite it to make use of XRX and or arithmetic expressions as appropriate to your particular code. But of course I used my example to show why XRX is great. So I'd already done the XRX part of the assignment. Cheater. Cheater a bit, yeah. So all I ended up doing, now I have included my full sample solution in the zip file PBS152 dash challenge solution that SH. So for me, it was about changing the mathematics into arithmetic expression. So instead of writing, of echoing a string and piping it to the basic calculator BC, which is a bit messy, instead I was just using the dollar per end per end and then writing out the mathematical equation properly and then catching the value that way, which does make nicer code, let's be honest. Right, right. So the other thing that that let me do in the challenge solution was to revisit one of those little things I told you to turn into a text expander snippet and not to think about too much, which is that every time we're finished with opt args, sorry, with getting optional arguments, yeah, with opt args, sorry, get opts. Why does my show not say opt args? It should say get opts. Oh, fix that. Yes, that's interesting. So every time we use get opts, we have to finish afterwards with this little bit of boilerplate that basically says take all of the optional arguments that may or may not exist and take them off the front of the array of arguments so that they're out of our way and then continue the script so that dollar one and dollar two are the normal arguments. And so we've been doing that with the shift keyword and then we've been telling the shift keywords to use the value which we compute by echoing opt ind, which is the index for get opt minus one and piping that to the basic calculator, which is really weird looking code. We can replace that with the much nicer shift dollar per end per end opt in minus one per end per end. I think you were really mean to teach us that basic calculator first and then show us, yeah, the arithmetic's here. Well, you were already cranky that there were so many brackets and they meant so many things. I was like, no, I'm gonna keep this one on the back bowler for a bit. There is too much confusion happening here. I stand by that decision. Sure. So let us jump into functions. And the first thing I'm going to do is say that all of us are at a disadvantage because we know how to program in other languages and we think we know what the function is because we've come from JavaScript or PHP or all these kinds of languages. And in those languages, a function takes its input through arguments and gives its output as a value that is returned. And we use the return keyword to return that value. But in Bash, functions don't really work like that. Think of them as a script within a script or think of them as a custom terminal command. They want their input primarily via standard in. They want their output via standard out and they do return something. They return an exit code. They don't return a value, they return an exit code. And so you use the return keyword not to return your value and that's gonna drive you nuts. Okay. So the normal way to return your value is just to echo it. So it goes to standard out. And then whoever's using your function will use the standard terminal plumbing to take standard out. And yeah, what do they wanna do? Is this the middle of a pipeline? Do we want this to go to a file? Do we want this to go to a variable? You don't know. And arguably the function shouldn't care. The function's job is to make some data exist and put it on standard out. Okay. Now, just like terminal commands do have arguments, bash functions do have arguments, but don't think you have to send everything into an argument, right? Bash function, the terminal commands really like to use standard in. So so should your functions. So think of your function as a custom terminal command. I wish there was a terminal command to do blah, blah, blah. Let me make one. Function. Okay. So that's how you should think of it. They're custom terminal commands. Okay. So we're gonna learn the POSIX syntax and there's another thing we're at a disadvantage of. So the POSIX syntax is name of function, space, openParents, closeParents, space, open and curly bracket, all of the commands that make up your function close your curly bracket. Those two parents to you and me and to everyone else who's on JavaScript or C or Java or Perl or PHP or I could list a million more languages, we think that the names of our arguments go in the parents, right? That's what we're used to. Think of those parentheses as one single character that means nothing more than this is a function. It is just a marker to say this is a function. Nothing goes in there. They are purely ornamental. They serve no function other than to say I'm a function. I have a little confession to make. It was a very long time at a programming by stealth before I actually ever put anything inside the parentheses. I sort of always thought of it as just a, this is just to tell me that that's a function. So Dorothy was always telling stuff inside her parentheses and I was always like, well, I'm afraid to do that. I don't know what that means. Well, I guess if you go back to your old self, you'll be just fine because in Bash land or in POSIX land, they are just there to demarcate this thing I'm about to make as a function. And so it's basically name of function, the two parans and then KERDI brackets to contain our function. At least it's KERDI brackets. At least that much makes sense to us. Yeah, yeah, no, that's good. So the easiest way is to show. So let us start with a very simple function that, what am I, yeah, so it's a simple function to do hello world because I think that's compulsory. Yeah, there are laws. We must obey all laws. There are laws. So our function is going to simply print out hello world on one line and then on the next line it's gonna print out inside parans from inside a POSIX function. Just so that our function isn't too tidy. So the script itself, you will find in pbs153a-fn.sh and the script itself will call our function three times by default or if we pass our script a number it will call our function that many times. So we can see our function do its thing multiple times. And I say in the show notes I'm going to do it once by default and then I think I wrote my code to do it three times. I fixed it in the show notes. You had it in the show notes as, you must be missing one poll because it does say function counter equals one and that's what it said in the file it actually says one. Yes, okay, so is it the file of the show notes because it should say one. So I'm wondering. They both say one now. Oh, good, good, good, good. Okay, I did actually pull. Yeah, I'm curious. We might be, we'll have to double check that. I'll make a note for us to check that after the show but yeah, it's a three and I was like. Okay, good, good, the sample output. I was like, how is it three? But it says one, but I fixed it. Yeah, it is one. Yeah. And the sample output in the show notes also clearly shows it as one, so yeah. Yeah. I mean, okay. So looking then at the definition for our function is simply hello w, which is what I named my function, space our two parents, space curly bracket and then echo hello world, echo from inside the POSIX function. And that's it. At the call the function, we just treat it like any other terminal command on planet Earth. We just say the name of the function and it will call the function. Okay. And that really is all there is to it. Okay. Well, that looks like it makes perfect sense. Yeah. Sorry, I can't multitask. I was just sending Bart a little note telling him that something's dinging in his room heard it a couple of times, so. Which it shouldn't be because I have personal focus on but I guess I'll switch you to do not disturb. Okay. Which is even more restrictive. Good. Personal focus. Right. Oh, didn't mean to put my screen saver on. That definitely doesn't help. The audience is like, what are they doing? These clowns, have they never done this before? Yeah, you think not. Anyway, interesting. Okay. So we just call the function by just saying the name of it. There's no parenthesis. Just say the name of it. No two parentheses for that. Just. Nope, because again, it's a custom terminal command. Just think of it as I wrote a terminal command. Okay. So like cat doesn't have parentheses on it. You know, LS doesn't have parentheses. One of the things. Exactly. I was telling Bart before we started, he named his function hello with a W, which I thought was hilarious because I always misspell hello and my fingers just want to put a W on the end of it. So I thought he had done that and I started fixing it all over the place. Then I realized, no, it was hello world. It's going to make my brain even hurt even more. So that'll be even harder. Yeah. The other thing just to draw your attention to, since it's new in our exploration of bash is I am making a point in my examples, this installment to make heavy use of arithmetic expressions to help embed them in. So when I decrement my counter inside my while loop, I'm using double round per ends without the dollar sign because I don't want the value of, I just want it to do the action. So take my counter and decrement it. Good. That actually helped when I saw it again, when I was pre-reading the show notes to see that you're using the arithmetic expressions. I like it. Excellent. And just a reminder that if you put the dollar in front of the arithmetic expression, you get the answer of the math. And if you don't put the dollar, you get the exit code. So that means we can use the arithmetic expressions for conditionals, like for example, only printing an empty line unless we're at the last hello world. So it says round per end, round per end, FN counter greater than zero, round per end, round per end and echo blank string. So that means take the, if the function counter is still greater than zero and it started say at three, it goes three, two, one, it's gonna echo it in as soon as it gets to zero. So we're not getting the, we are getting the execution of the function. So what we're getting there, because we didn't put a dollar front, but we're basically getting true or false or rather successor fail. Oh, oh, wait a minute. From the greater than. Oh, from the greater than, but function counter is a value that's getting decremented. So it's just a variable. Well, that's the line above it. It's a variable. Yeah. It's just a variable. Correct, yes, function counter is a variable. Okay, got it. Yes, because also as a reminder, inside an arithmetic expression, you don't give functions the dollar sign because it's like, this is math. If it isn't numbers, it's a variable. Right. Because the only thing allowed inside your two parentheses is math. So in math universe, you don't need to say dollar X, you just say X. Right, right. Again, it's batch trying to be helpful, but it does have the side effect of sometimes being confusing, which is why I'm making a point of repeating it. Okay, so every function is like a script within a script, I said, or like a custom terminal command. Well, we know that our script gets its very own copy of standard in, standard out, and standard error. And every time our script calls another script or calls a command on a terminal command, that command gets its own copy of standard in, standard out, and standard error, which is why we can use the pipes to manipulate these things, right? The standard out from the cat command is piped into the standard in of the grep command or whatever, so they each have their own. Okay. Functions are like terminal commands. So every time you run a function, that running copy of the function gets its very own standard in, standard out, and standard error. And what they're connected to completely depends on how you call your function. So if you put your function into the middle of a pipe, then your function standard in will be whatever the pipe has just connected to it. If you, like if you just call a command without doing any terminal plumbing, then the standard in for the command is your standard in, the same is true with the function. If you just use standard in without a pipe, then the function has the same standard in your script hat. So it just inherits it, but it does have its own one. So that's just, you know, to note. And also, it can write the standard out, and it's its own standard out. So you can use it inside a pipe just like you can on any other terminal command. So it basically, it behaves like you expect, is what I'm trying to say in very convoluted ways. It works like it should. So the next example I'm going to give is a little function. So you're gonna find that in PBS135B, FNStreams, where we're going to write a bash function that uses the streams, that uses standard in and standard out to be terminal like. And we're gonna write a function that is a terminal command called pal, which is short for palindrome. That's gonna take some input from standard in and it's gonna print it the right way around once and then immediately follow with it backwards. So it becomes a palindrome because you've printed forward and then backward. So you're on mute, Alison. I hope that's intended. I was moving my chair a minute ago and didn't wanna make a racket. No, good, okay. So our function is going to, is gonna take its input from standard in and then write its output to standard out. So inside the function, again, we just say pal, open friends, close friends, curly. So we're saying make me a new function, name pal, and all the rest is just window dressing. So the first thing we do inside our function is we read from whatever happens to be the function standard input, which will be different every time you call the function. Sometimes you might call it inside a pipe. Sometimes you might call it inside a script. The function doesn't know what it will be. It just knows it will have a standard input. And we wanna take that standard input and save it in a variable, which I'm choosing to call stor for string, because it really could be anything. Right, we're just gonna palundromize. So we're using the cat function, the cat command, which by default, reads standard in and prints the standard out and we're catching it with the dollar friends syntax. So we're basically saying the output of the cat command, save it in this variable called stor. And then we're gonna print that variable as is without a trailing new line. So echo minus n for minus no new line. And then we have to reverse it. So there is a terminal command called rev, which reverses things. So we're going to echo our string and pipe it to rev. Why does that exist? That seems like such an odd thing. I mean, it's perfect for this little example, but why does it exist? I generally find that if something might be useful somewhere, so the terminal thinks of itself as a bunch of Lego bricks and you can build any hassle you want. So one thought that would be an idea. So there it is. So that's our entire function. So it reads from standard in and it just uses the echo command for its output. So you'd be used to in JavaScript, creating a string, building up the string and then returning the string. But here the primary output mechanism is standard out. So just echo the stuff, just echo it. That is how you give output from your functions in the normal course of things. Different, not difficult, but different. So how do we use our function? Well, the easiest way to use it is to pipe something to it. So we're going to say echo your palandromic username and then we're going to send, we're going to... So the variable dollar user in all caps is one of those variables that exist in every Unixy system, which is your username. So we're going to take the result of running the command echo dollar user pipe pal and then print that out after the words your palandromic username. So basically the bit that's actually going to pal is echo dollar user pipe pal. So we're going to send your username to our new function. Okay. And it will write to it standard out. The dollar per ends is going to capture that and print it out as a string, a part of the echo command that came before it. So when you run this script, you will see that it does what it says in the tin now. We also then give another example of using our function within a pipeline. So it's not at the end of a pipe, it's in the middle of a pipe. And so we're going to write the host name in all caps. So we're going to take the host name command, which we'll just print your host name. We're going to pipe that to our pal function. So our pal function receives our host name. It will write to it standard out, which we're now going to pipe to the transliterate command, tr, which is going to take all the lowercase a to z and transliterate them to uppercase a to z. So in other words, convert them to uppercase. And so when you echo that out, pal host, which is the variable we're saving all this in two is going to be the uppercase shouty version of our computer's name. So if you run that script, I guess yours is going to say Allison Slyats whatever Allison backwards is. Oh, come on. You know what Allison backwards is? Oh, Nocella. Ha ha ha ha ha ha ha. Duh. There probably, isn't anybody else whose name you absolutely should know what it is backwards. Yeah, so it says Allison Nocella, and then it writes my computer name and then runs it right into itself backwards and the whole thing's in all caps. Yeah. So I mean, it's not an exciting function. No, I like it as an example, but it does just what you would expect it to do. Precisely, so we have made a terminal command, right? Whenever we're writing functions, we're making terminal commands and we're being all terminally and we are using the streams. Yeah. So the other very terminally thing is exit codes, right? If you need to communicate success or failure, you do that through an exit code. An exit code of zero means all good and any other exit code means not happy. So we're going to, a very common thing you might wanna customize your exit codes on is a function to test something. Is this a, duh duh duh duh duh duh. Well, that's a very much a success, fail sort of answer. So exit codes are a fantastic mechanism for communicating that. So our third example, 153cfnreturn is going to show how we can use the return keyword to do two things. It stops the function in its tracks and ends the function with the return code we give it. So it's exactly like the exit command for a script, but instead of ending the whole script, it just ends the function. Okay, okay. So if there were other code further down, it would never happen, right? So you might say, if some condition return zero and that will just stop executing the function, like in JavaScript, you can at any point, say, return my string and that will stop the function running. But it's not gonna stop the script, it's just gonna stop this function for a minute. Correct, correct. So the exit command would stop the whole script, the return command stops the function with the appropriate exit code. So we're gonna write a very boring function called is underscore int, which is going to check if something is an integer. So we are going to take our input from standard in and we're not going to write anything to standard out. We're just simply going to return the exit code zero if it is an integer or one if it is not an integer. So again, is in space, per end, per end, space curly. So we're gonna start by taking whatever is in standard in, which is what the cat command does if you don't give it any arguments, and we're going to pipe that to eGREP, which we're going to tell to be quiet. So minus Q for just be quiet eGREP, just give me a return code, don't print anything to the screen. And the pattern we'd like it to match is starts with an optional minus sign and then one or more digits. Okay. And then we're going to do that ampersand ampersand thing and then return zero. So if the eGREP evaluates to true, then we will go ahead and return zero, which means all is okay because in bash world, everything's upside down. Yeah, that was what that pause was because you're going, wait a minute, it should be okay to run. So zero is true, one is false. Yeah, so zero is success. I like to say success in my head. Okay, good. Yeah, that's easier. Or no error. Zero is absence of error. That's actually where it comes from. Okay, that's, that's, and then. So assuming the grep matches, our function will cease at that point. It will say, I'm happy and it will stop running. Oh, because you did a return. Because I did a return. Okay. If that has not happened, I am not happy. Then the second line in the function happens, return one. But it still stops. Yes, there is an error. It still stops. It stops no matter what. It's done now. Okay. Yes. And in fact, it would stop even if we didn't do anything else, but you know. Oh, okay. I see, I was a little confused by just the way this was formatted. It looked like this was all what, everything that was in the script, but actually that's two, you're doing, you're running it. No, is that all in the script? So the function, so the whole script is what's in the show notes, which is just a tiny little four line function followed by yours actually using the function. And that's, that's what's coming next. So check some values. It's also using the function. That's inside the script. Inside the script? Yes, not inside the function. Okay. Okay. Oh, right, right, right. So the function has stopped, but the script can have more stuff in it. In case this is gonna be the input. That's odd. You did the input stuff afterwards. Well, right, you define a function, then you use it. Yeah, okay. All we've done here is define the function. So now it exists, or you can't use a function until it exists. JavaScript does clever things like it, JavaScript will read all of your file and then pretend you'd written the function definitions to the top, it's called hoisting. So when it goes to run your code, all of your functions are at the top of your file. Doesn't matter where you wrote them, when JavaScript runs under all at the top of the file. But in Bash, you better define them before you use them, or Bash is gonna go, oh no. What are you talking about? Is int, never heard of that. Okay, but you haven't walked through the second half of this, but you don't ever use the function. I do, I do, many times. Every time you see is underscore int for the rest of the file, we're calling our function. I don't see is underscore int in the second half of this block of code that we're looking at. So inside the do, on the first line inside the do, we use it. Oh, there it is. Yeah, that's right. Okay, all right, good, it is there. You can see it. Yeah, again, you're looking for parentheses. I am, I am, yeah, you're right, that's what's wrong. Okay. Yeah, so you gotta get into that, but we're making custom terminal commands here. Custom terminal command, custom terminal command. So in order to test our function, we should run it against some values, and I got lazy of copying and pasting. So I made an array with my test values, and then I looped through my array. So my array contains the values 42, which is an integer, 4.5, which is not an integer, minus one, which is an integer, and waffles, which is most certainly not an integer. But I would like an integer of waffles. One, please. No minus one, no waffles for you. No. So we then loop over it with a standard for loop. So for val in, and then that syntax we all love so much to get up all the values in an array into a loop. A dollar, parentheses, name of array, open square bracket, add close square bracket, close or curly brackets, it's a lovely syntax. I can't walk across the keyboard syntax, okay. Pretty much. So then we say inside our for loop, we're saying if, and then we're piping our value into our isint function. So if echo dollar val pipe isint, so we're making standard in for our function b, our variable that we're testing, and then we're echoing out either the value is an integer, else the value is not an integer. So that will then run four times, and when you run it, you will see that it says that 42 is an integer, 4.5 is not an integer, minus one is an integer, and waffles is not an integer. Good. So there we go. So that is an example of us using exit codes. Again, we're behaving just like a terminal command because that's what we're doing here. We're making our own terminal commands. Now, you and I both know that terminal commands can take arguments. Sure, they work on standard in for a lot of their work and they work on standard out for most of their output, but they can take arguments. LS most certainly can take an argument of what folder would you like me to list? If you don't give it anything, it will do something sensible, but it can take arguments. You can give it flags, optional arguments or flags like minus a for show me everything, minus l for give me a long listing. So we know we can have arguments. So of course our functions can do the same. And the way it works is that as well as each of our functions getting their own standard in, standard out and standard error, they also get their own dollar octo slope for how many arguments was I given, dollar at for all of my arguments and dollar one, dollar two, dollar three, et cetera. So all of those special variables we've been using as the arguments to our script, every function gets their own copy of that self same set of arguments. Wait a minute, are you saying that these functions are like we've written our own terminal commands? Hmm. They are. You should also think of your scripts the same. Ha, ha, ha, ha, it's terminal commands all the way down folks. So that actually means we already know how to use our arguments as we've done it before. So let us now make our isn't function a little bit more clever. So just like the cat command will either print whatever's on standard in to standard out or if we give the cat command some arguments it will use those arguments instead of standard in. So why don't we have our isn't function say did I get any arguments? If I got arguments, then I should check if they're all integers. And if they're all integers, then I'm going to be happy. So I'm gonna return success. If even one of them is not, I'm going to return error. Okay, so let us start by first off. So our function is a little bit longer this time because we're making it do some things here. So for the first time, our function actually stretches to about 15 or 20 lines here. So the first thing we do inside isn't is we make a variable named int or e for int regular expression so that I don't have to keep copying and pasting that lovely bit of gibberish that means starts with an option minus followed by one or more digits. Please tell me that's a text expand or snippet by now. Do you know what isn't because I typed, I am so fluent in regex language that it's easier for me to type with and remember an abbreviation. I don't know if that's something to be proud of or ashamed of, but I really, I have a t-shirt that has to be or not to be as a regular expression. It's stupendously nerdy. I love it, I love it. I use it as like a one t-shirt test and so far, four people have laughed. I like you, you're my people. Anyway, I have saved my regular expression. This is again, avoid code reuse, because if I made a mistake, if I copied and pasted that instead of making it a variable, I would have to find everywhere I made the same mistake where as by saving it as a variable, I only make the mistake, I have one place to correct my unavailable mistakes. It's good practice. Anyway, so we save our regular expression and the first thing we need to do is figure out do we have more than zero arguments? If we have zero, we wanna read from standard A and otherwise we wanna do something else. So the first thing is we say if and then inside of our square brackets so we can test a condition, we're going to test if dollar octo-thorpe or dollar pound or dollar hash or whatever we're calling it this week is greater than zero. So the greater than is minus gt if we're inside our test command. So the square brackets are our test command. And which one is dollar octo-thorpe? What does that mean again? Is that all of the arguments? And it's the number of. The number of arguments. No, all of them. It's gotta, oh good, it's got a number symbol. Yes, one I can remember. So if that is true, we're going to do something new. So put a pin in that for about 15 seconds. Else we wanna do exactly what we did before which is process standard in. So I copied and pasted from the previous, I didn't copy and paste the previous script I used as my starting point and these are the two lines that remain. And all the rest was built around those two lines. So they are exactly what we had before because processing standard in is just like it always was. So the other part of the if is new. Okay, so we did have an argument. There is something to process in the arguments. What shall we do? We shall loop over the arguments. So for val in open single, oh, sorry, open double quote, dollar at close double quote, which is our syntax for exploding our arguments into the loop. So each time we go through the loop, val, the variable dollar val will contain the argument we're processing at the moment. And so we're gonna take the current value we're testing, we're gonna pipe it to eGREP, we're gonna shove it through our regular expression and now we're going to invert our logic. So previously you said and and return zero because there was only one thing to test so we could just do that. I was like, well, this matches our expression. Great, we're done. Well, if I wanna say all the arguments have to be integers then I can't just say, I can't declare victory when the first one checks out. You know, I've won the battle but not the war, right? So I need to just flip my logic around here. And so what I'm saying here is I am only going to exit the function if I fail because if even one of them fails, we're done, right? They can't all be integers if one of them isn't. The first, second, third, fourth, it doesn't matter. As soon as you hit one, return one. Precisely. So now instead of saying ampersand, ampersand return zero, I'm saying, or return one. So I've inverted my logic. I've done a little Boolean trick there. Now, if we make it the whole way to the end of the loop and we have not exited the function, then they must all have passed. They must all have been integers. So therefore I now end that part of my F statement with return zero. Okay. I just thought of something. Your else is taking the form of processing standard in that you did in your previous example. And in that case, I forget, did you have it be that they all had to be? No, your previous example, they didn't all have to be integers. Right, because standard in is one thing. Standard in has a value, so it was simpler. We just had a value to test. Well, now we could have one arc or five arcs or a million arcs. So that's why it's more complicated. Okay, but you're gonna get two different answers from this. If you sent it the same four values, your 42 minus one pancakes and whatever the other one, 4.5, if you send it those as arguments, you would get false. You would get no, there's no integers. But the second test, within the same function, if you are the same script, I'm sorry, if you don't give it any arguments, it's gonna actually to answer you four times. It's not gonna take the collective. Only if you call it four times. Well, you're giving it four. Right, but we did it in a loop. So each time we called it last time, we gave, we called it with one value. We piped a single value into standard in. If we pipe all the values into standard in, that'll arrive as one big string, which is gobbledygook. So it's actually gonna behave exactly like it did last time. I know, I'm saying it is gonna behave exactly like last time. But the first half of your function, if you get, you're gonna get two different ways of looking at the problem. You could give it the same four arguments. If you gave it as arguments, it'll come back false. It'll say you failed. But if you don't give it any arguments, it'll give you one success and three fails. Even though they were the same four values. Not quite. So if you, so last time we called the function four times with one value each time. If we do the same thing and call the function four times with one argument each time, we will get exactly the same output. But if we're interested in answering a bigger question. So basically what we have now done is we've made our script more powerful. It can answer the question, is this one thing an int? Or it can answer a new question it couldn't answer before. Are these all integers? Which is a different, which is a more powerful question. Yeah, yeah, I'm just, I'm showing off that I understand how this works. By saying that you're getting two different answers from the same set of data. Given those four inputs. Fair. If you give them as arguments, you'll get one answer. But if you don't give them as arguments, you're gonna get four separate answers. That is completely true. And it is also true that if you give them as an argument one at a time, you'll also get the four separate answers. True, true. Okay. Interesting. So, yes. Okay, so we can use our self same for loop as last time. But this time we're gonna say instead of piping and stuff, we're just gonna call our function directly. So the code is much cleaner. If isn't dollar val. Wow, that's a lot easier than saying if echo dollar val pipe isn't, that's faffin' about, we just say if isn't dollar val. Well, hey, that's much more Englishy. Then echo yes, else echo no. Okay, it's nice and clean. We can also show it multiple values now. So we can say if isn't 42 minus one, then echo all integers, else echo not all integers. And we can also send it 11 and waffles. And then echo all integers or not all integers. So when you run that code, what you will get is the nice clean answers basically that the first two are all in the first time. Sorry, let me say all that again. The first loop through, we get exactly what we had before. Yes, no, yes, no. The second time when we call it with 42 and minus one, we get, yep, they're all integers. And then we call it with 11 and waffles. We get, nope, not all integers because waffles is not an integer. Okay. So what I sort of wanted to draw your attention to is that the same function isn't within that example there is called in three different ways, right? We can say if echo dollar val pipe isn't and it works just fine because we're taking standard in and our function handles that. Or we can say if isn't a single argument and it works just fine. Or we can say if isn't with two arguments and it works just fine. So we're now starting to write something which behaves awfully like a real world terminal command, right? Yeah, yeah. Okay, so now I need to bring us to variable scope. The cause. We have unwittingly created spooky action at a distance. We have been using variables inside our functions with gay abandon. And I have had to be stupendously careful in these examples. These examples work despite the fact that I have not taken into account how Bash's scope works. This is where you want to back up and give that description that we inserted at the beginning. This is where I'm leading into that description in about 30 seconds. Okay. So at the moment our code works because I have been really careful to use unique variable names everywhere and never have a clash because all those variables are global. All of them, they're all globals. So they would all stump on each other because Bash by default makes every variable a global variable which is sort of the big picture view of a mechanism for handling scope that is known as dynamic scope which is nothing we have used before uses dynamic scope. We have lived in a universe of, well, I don't think I've ever told you we use lexical scope or static scope as it's also known. No. But we have been. And the reason I haven't said it is A because very few computer scientists even speak that kind of jargon and almost every language that you're ever going to come across with the sole exception to my knowledge of shell script is lexically scoped because that's how our humans tend to work. So what is lexically scoped? What's that definition? Lexical means wordy things, right? A lexical analysis, you're analyzing the word. So a lexical scope where in the code your variable is defined determines the variable's scope. Okay. So you're used to me saying to you if I define a variable inside the function it comes into being at the opening curly bracket and ceases to exist at the closing curly bracket. Right. So it is lexically scoped in your source code within its containing curly brackets. Okay. And no matter how deep you nest the variable the curly brackets are always what's giving it its lexical scope. So the line on the file tells you the scope. So lexical and slash static which are synonyms in this case static is the opposite of dynamic but neither one of those sound like where I define it matters versus it doesn't matter where I define it. So what is the other word of you? I'm gonna have to come back to the definition every time because those words don't mean that. I know. They do mean that. But again, they're hard to define. Yeah, yeah. It's hard to, I don't like the names but I can't think of a better one. Yeah. Okay. So let me see if I can say it again because we've been talking a lot about it. Static slash lexical is where like JavaScript where you define it that defines its scope but dynamic means it's always global. Yes, and. Okay. There's a very important and here. So what makes dynamic scope dynamic is that the scope is figured out by how your function is called. So if you have a variable called waffles and that variable is inside a function inside a script. Depending on how you call that function waffles may have a different value. So you could have another function called pancakes and pancakes could set the variable waffles to 42 and then call the function waffles. And then waffles will see, I've mixed up my names here horribly. That's okay. I lost you in the waffles anyway. Yeah. We're gonna say the variable is called waffles and the functions are called function one and function two. Okay. How's that? All right. So we have a variable called waffles and if I say in my script waffles becomes equal to 42. And then inside function one I print out the value of waffles. It will get the 42 because it was set in the function before I, because it was set in the script before I called the function. Okay. If in function two, I have a line that says waffles becomes equal to minus one. And then the next line is called function one. Then the value is actually going to be minus one. Oh, come on. The code hasn't changed. Where I call the code affects the value it sees. That's horrible. It's horrible until you think about how shell scripts work and then it makes perfect sense because we have been using this dynamical scope all along. We just haven't known it. So by default, everything is global but it doesn't have to be. You can expressly say, I want to make these variables local to me. So inside your function you say to bash these variables are mine. I want my own copy of these but you then create a new universe. So every function or terminal command you call inside your function sees your value. So it's like you're casting a shadow, right? So there was a global value and you made a new value when you're casting a shadow on everything you call. Okay. And if something you call changes the value it can make another littler scope for everything it calls and cast a shadow inside the shadow inside the shadow. So you're projecting down. So now think about how variables like the input file separator work, ifs. So I have told you to be really wary of messing with ifs because ifs is global. I haven't told you about functions. If you would like to use ifs within a function you are 100% safe to do so as long as you will localize it. So if you say local ifs which we're gonna get to the syntax in a second but if you basically say I want to make my own ifs then your spooky action at a distance is gone. Oh. Because now your change only applies inside your function and everything you call from your function. So if you want to make the input separator be the comma instead of the new line character if you do it inside a function and you've expressly say make it a local variable spooky action at a distance evaporates because you haven't messed with all the rest of your script, right? Okay. It's only being constrained by your function. So when you're writing functions that use variables you always have to ask yourself global or do I want my own? Okay. So probably be safer to do local unless you're sure you want a global. Bing, bing, bing. So by default you should get into the habit of starting every function with a line that localizes your variables. So the syntax to say this one is local is the keyword local followed by the names of one or more variables. And just like with a for loop we don't say we don't say for dollar val, we say for val in you say local var one, var two not dollar, var one, dollar, var two it's just the names of the variables. And so we can use those to localize our variables. Now I have jumped to go in slightly on the show notes because it just was being if that flowed really well. Okay. It did and I wasn't going to stop myself just because my show notes were a different word. I noticed that and then I went, no, it's working. So now I'm going to, I have told you what I want to tell you now I'm going to show you that I'm not talking poop. So if you were unaware of this reality of how bash works and you naively wrote a function to say add an optional argument to or isn't, right? So the get ops command manipulates dollar one, dollar two, et cetera. Well, every function gets its own dollar one and dollar two. So you would immediately assume that get ops will work just fine within a function and it does but it won't work just fine without spooky action at a distance if you don't remember to localize things. So we're going to do it without localizing things so we break everything. Okay. So we are going to make our isn't function take one optional argument, a flag minus a and if that flag is set, then we will accept any integer instead of all integers. So in our previous one, if you gave it three values they all had to be integers. If you say minus a then any can be an integer. So effectively an or instead of an and. Okay. Seems like a reasonable thing to do. So if we just take the syntax we're used to and we shove it in our function before we do the test for whether or not we have any arguments we do the normal get ops stuff. So I'm still saving my regular expression to a variable called int or e because I still want that. I'm making another variable called any okay and I'm setting it to the empty string. That's just going to be my variable to know whether or not we're in anything or everything mode. It's just my variable to hold that fact. Then we're going to use get ops and the pattern is colon ooh, colon a, no colon, typo Bart. What should it be? Just a because a doesn't need a value. And no colon after? No, so colon a for I want my own error messages but a is a flag that doesn't take a value. Yeah. So just colon a. Okay. So while get ops a so while get ops our pattern is colon a and then we're going to save the current value in a variable called opt. Then we're going to do our usual case opt in. If it's an a we set any okay to be one. Otherwise we print out our error message and instead of saying exit one, we say return one because we don't want to kill the whole script just because they use the function wrong. We just want to end the function. So that's the only thing that's changed here from what we're used to. And then we have our code like before for the most part but we've added in an extra if statement to say if any is okay we need to do a third type of logic. So we're still going to have a loop but this time we say if we find any one that is a valid integer we can immediately jump to success without testing anymore. Oh, okay. So we now have a third different convoluted way of having this logic, right? So we have if statements inside of statements but it's just logic, right? It's nothing new to us. And if we run this function, the first time we call the function everything is fine. It does exactly what we want. So we call the function with multiple arguments we don't give it a minus a and it quite correctly gives us the answer we expect. And the second time we call it with the minus a and it still works fine. It still says, yeah, there's at least one interjournalist at test values. And then the third time we call it without the minus a with exactly the same input we used before and now it barfs. It gives us a dumb error message. What? Why? How? The answer is because the way getOpt works is it uses a variable called opt in to count where it is in the list of arguments. How far have I gotten looking for my optional flags? That's how we can do that little opt in minus one trick at the end. Right. So the opt in does counting. It's a global variable. Okay. So the first time we call our function, opt in starts at zero and moves forward until it meets the minus a. Right. The second time we call our function there is no minus so opt in doesn't move but it's not at zero. It's still sitting out here in the middle. The third time we call our function opt in is not at the start of our argument list it's in the middle of our argument list and the first thing it meets is minus one which is our test value. And it goes what minus one? That's not in my list. Error. So because it's a global variable. Okay. I think I lost you at how opt in is being used as an input to this function. Not to this function to get opt. Get opts uses opt in opt in's belongs to get opts. Okay. But we never. So when you say while get opts. Right. Get opts is using a get opts says start opt in at zero. So get opts the first time get opts runs opt in is at its default value of no value. So get opts says oh there is no opt in great make it zero. Okay. And it uses opt in to remember how far it's gotten on the list of arguments inside that while loop. We say while get opts. Okay. It's using opt in. Let me see if I get it. So when you're shifting it you're doing that while it's also changing. The reason we can shift is because get opts has been changing the value of opt in. So why is it barfing again? So the first time we call our function everything is as opt as get opts expect. Mm-hmm. Right. So we call our function three times the first time with a minus a. So the first time with a minus a opt get opt does everything it normally would. So it's slowly counts opt in and it goes how far do I get? I get as far as wherever minus a appears in the argument list. And so the value of opt in is going to be two I think. Okay. It doesn't matter. The value of opt in is not zero. It is wherever minus a appeared in the argument list and we shift by that amount minus one. Right. The second time we call our function we don't give it a minus a. So get opts doesn't actually move opt in because there is nothing there are no minus this process. Sorry, just got it. Okay. That's where the- The third time. Okay. Yeah. The third time it goes through the loop. Oopsie daisies. We now start to look for our options and the first option we find is minus one. Okay. Which is garbage. Right. It goes all wrong. So do we have to localize opt in? Yes, we do. Huh. We have to localize every variable we use inside this function. Okay. We have to localize into re every single variable, right? We don't want any global variables leaking out of this function, right? Like you say, unless you explicitly want it to be global unless there's a reason you want to address something global you shouldn't. So by default, localize all your variables unless you intentionally want to project something into the global scope. Okay. So basically you go through your code and every time you find a variable you localize it. So the working version of this script which doesn't do any weird spooky action at a distance is identical to that version but this very first line inside the script is simply local space and then all the variable names we use which is int or e any okay opt opt in opt arg and val. Okay. Now every time the function is called it gets an entirely fresh into re and entirely fresh any okay and entirely fresh opt and entirely fresh opt in and entirely fresh opt arg and entirely fresh val. So I'm struggling with we have a lot of the same code being replicated throughout this as you're doing these buildup of examples. Where am I looking for where you actually use the syntax of local? So in the final version of this file which is pbs 153 f opt args but I'm in the show notes at the very first line of the function in the function in the show notes is just I just copied the one line rather than copying the entire function again cause I thought it'll be too confusing. So it says localize the appropriate variables local int or e any okay just it's the two paragraphs above reusing functions with source. Okay. I'm looking for localize the and it's not in here a spooky action a distance. I scroll up and down and I'm lost. I'm lost. We're passed to use look. Okay. Oh, got it. Okay. Oh, and I know you said this for the audience but I was busy trying to find it. So local and then you just list all the variables int or e any okay opt opt in opt arg and val. Okay. Yeah, just list them and that's it. All the spooky action and distance just evaporates because now we have a proper scope on our function. So do you start by localizing everything you're doing and then when you realize you need something global then you change it or you remove it from the local list. Okay. Yeah. So when I start a function I put the word local and just leave it there and then every time I go to use a variable I scroll up, type my variable name up there and then I use it. I pretend bash needs me to declare variables. It doesn't. But if you pretend it does then you'll get it right. Oh, okay. So I just pretend that local is declaring my variables which it sort of is, it's declaring them as local. So the final piece of the puzzle for today is I've been saying that the reason we want functions is to avoid code reuse, which is very true. But if you're working in a very specific script that does something really one off you're gonna write a function that has no meaning anywhere ever again, right? It does this one weird thing and you need it in this one script and that's great. But that's not how coding often goes, right? You find yourself doing something quite generic that you would like to use in 20 scripts. Do you write a function in your first script then say, oh great, that one works and then copy and paste it into the other 19 scripts and then three weeks later you find that you've made a typo and then you have to remember the other 19 scripts. No, you don't of course, or you don't want to. So you can pull the content of one bash file into another script as if you had typed it there. Oh really? Basically you import it as if you had just typed it there and the command is source followed by the file name to go get. And so you can give it a relative path and it will be relative to the current working directory which is dangerous because if you have your script sitting in your home directory and you're off in some other directory then your dot is not your home directory and so a relative path would be dangerous. So you can use the trick of Dear Name $0 which we used when we were doing our menu example which is probably what you want to do 99% of the time because that will basically say include this file next to my script. Not next to where I am, next to where my script is. So it gives you a relative path relative to your script. Or the other way to do it is to have a single folder in your home directory where you put all of your utility scripts and then use the full path. Actually you could use tilde, you could say source tilde slash. Sure, but having a relative path lets you know that you're where you are, yeah. Exactly, so I tend to go with the relative path. The point is source followed by a file path will suck all of the code in as if you had typed it at that point in the file. So it's just import basically. So if you have all of your functions sitting in a folder and you just give them sensible names then you can just say source, get check-in to whatever and then it'll pull in your check-in function or whatever and you just build up a little library of useful things. I like it, do you have a lot of these? I do have a few, I do have a few, yes. And I keep them all in sync across my Macs using Shea Maw. I didn't tell you a couple of days ago or I was just doing, oh, I was working with you on making some changes to my git config and then I realized, oh man, I have to go over to my, now that I have two laptops, I had to go over to my other laptop and change it. I was, man, I wish there was some way to like version control this, but no, oh, he's gonna spank me if I say that out loud. Well, that's an easy answer. So if you picked up a series while we were just on bash, you might wanna go back and take a look at the Shea Maw series. Indeed, yes. Which will get you into the gist rabbit hole because Shea Maw is very giddy, which is a good rabbit hole to be in. Exactly. So we have now arrived at a point where we really have covered not just the bash basics. I had intended this to be a very superficial view of bash, but you know something, we've actually covered more bash than most sysadmins will ever use. Because most sysadmins use other people's bash and they don't quite know what it does. We now have truly covered all of the important features of bash. And like I said, we're gonna tie it all together in a neat little package in one final installment. But in terms of new stuff, in terms of ideas and concepts, we actually have a really good grasp of shell scripting now. And we have all of the means we need to do this as the English expression goes in anger, because we can use functions and the source command to break apart our reusable pieces and stick them in such a place that we really can build up a library of shell scripts to do powerful things for us. And you know, you could then combine that with something like TextExpander to call your various little scripts and things, but you know, we're building up really powerful stuff here and we've learned a lot of very powerful tools here. So I think the big takeaway from today is that we know how to make functions, we know how scope works. And finally, I can stop saying, never mess with ifs. I can change my advice to be only mess with the localized copies of ifs. So once you localize it, you're not doing spooky action at a distance anymore, you're doing very controlled action at a distance because you are deciding that you need the inputs everywhere to be the comma for your purposes within this defined piece of your code base. That is powerful, not spooky, that is good. So three months ago when I tried to use ifs and you said, no, now we know the answer why. Now we know why I was very, I didn't want to really go into too much detail of why it was dangerous, but I definitely said it could cause spooky action that this instead of might bite you. And I worked very hard in my examples to avoid it, which sometimes was a bit circuitous. Right. I was like, I know there's a right way to do this, it's the local keyword, but I can't say it yet. No, I can. So I always like when we get to this stage of a series because I can stop remembering what it is I'm lying by omission about cause I never tell you an untruth, right? I work really hard never to give an untruth. I just very judiciously don't mention the things that would be confusing until we're ready. And I'm done, they're all gone. I don't have any more hidden little mustn't say, mustn't say, and I love that cause then I can just do stuff. That's a lot of mental work you've been carrying. Yep. Well, it's always the case. And it's like I say, I never want to lie, but I do want to not confuse, which is difficult. So we're going to wrap it up in a bow next time. If you would like a challenge, I'm afraid I wasn't very imaginative of this time, but I can promise you that your code for printing out your multiplication table reuses some logic in a few places. It must have some bad smells of code reuse. Find them, replace them with functions. There we go. Easy enough. Well, this, I've got to give you a tip of the hat. I know there have been a lot of times I've struggled with trying to understand things. These show notes are amazing this week. These are, not that they aren't always, but the clarity and the simplicity of your examples really helped that they weren't big complex things. They were small bite-sized pieces that I could go, okay, I know what, check him to see if something is an integer. Okay, let's just do that little tiny thing. So I appreciate that. Thank you. I know it takes extra work to be simpler than it does to be complex sometimes. I'm just really happy we by accident had quite a few weeks to write these notes because they went through a few iterations. Okay, good. But I'm really happy where they ended up actually. I went out for my cycle in a very good mood and even though I got a puncture, I still came home in a good mood. Good, good. All right. Okay, well until we convene for one more bit of bashing, happy computing. If you learn as much from Bard each week as I do, I'd like you to go over to let's dash talk dot 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 let's dash talk dot IE, you can support him on Patreon. You can donate via PayPal or you can use one of his referral links. I really hope you'll go over and help him out. In the meantime, you can contact me at Podfeet or check out all of the shows we do over there over at podfeed.com. Thanks for listening and stay subscribed.