[0:08] Well, it's that time of the week again. It's time for Chitchat Across the Pond. This is episode number 767 for May 13th, 2023, and I'm your host, Alison Sheridan. This week, our guest is Bart Bouchats, back with Programming by Stealth, installment 150. How are you doing today, Bart?
I'm doing fine. I'm getting awful deja vu, though. So there's two types of audiences here. There's the type of audience who downloads and listens to the podcast absolutely the instant it comes out, and then there's the other 98% of the people. If this is the first time you've heard Programming by Stealth 150, then just ignore the next minute or so of what I'm going to say.
But if you already heard Programming by Stealth 150, you will know that for the very first time in 150 episodes, we're recording it a second time because we didn't like it.
We didn't think it was our best work. Is that a good way to describe it, Bart?
[1:00] Yeah, and slightly because, as some of our most dedicated listeners discovered, because we didn't do a great job the first pass through, the challenge was very challenging.
Unsolvable? So challenging. Well, it was unsolvable with the work we had done. I mean, I know I like to say that people at this stage should be able to do some independent research, but some independent research. It took me three hours to find the missing piece of information.
And I'm literally paid to be a professional sysadmin-y person, or at least I was until I changed jobs.
Oh yeah, you're all rusty now, Bart. That's the thing. See, that's old news.
Do you know the senior sysadmin actually made fun of me for getting out of practice? I wasn't typing fast enough for his liking.
Confess about this, but I'd like to give a shout out to Ed Howland, because I think he might have been the first one to point it out. He was right on top of it, and he tooted you on Mastodon, and I didn't understand the subtlety because I hadn't tried yet, and I was like, oh, I'm not even going to try that if Ed can't do it.
So, but a few other people in Slack have been chatting about it, but the other thing is, in the first recording, I was really confused a lot, and that's usually a place where Bart and I know that there's something missing, that we've got some things out of rhythm.
I wasn't on my game, I'm not sure, but I believe we're gonna do a much better job in this episode. But the other thing is, oh, go ahead.
[2:30] Oh, so last time I put too much emphasis on the... I was afraid of retreading old work from Taming the Terminal, but I shouldn't have been, because A, that was quite a few years ago, and B, it was literally a different audience. So I didn't lay a foundation. Yeah, apart from that, it was a great idea. So I didn't actually lay a foundation, and then I proceeded to build a fairly tall skyscraper on top of it, and it didn't go well.
Toppled over in a heap, as they say. So the other thing we were going to say, we did say the first time recorded, is this is, programming by stealth 150. Yay! Yay!
So good, we're doing it twice. That's a lot of work. Right, right, right.
[3:12] Oh, and you have nearly completely rewritten the show notes.
I think I was 75% through reading when I started coming across something written the same way it was written last time we recorded two weeks ago.
So if you went through the show notes the first time and were confused, they are much, much clearer this time, I think.
I think the introduction is the same. I think the first paragraph stays the same.
And I think towards the very end, it starts to pick up and look similar.
But there was a lot of it that I looked at and went, man, I don't think I knew this the first time through. But now, everybody who heard it the first time, come on back.
We're going to lay the foundation and really kick into gear this time and understand it for real.
Indeed. So, actually, the people who heard it the first time can go back to sleep for the few minutes because we did start last time on this time by mentioning that there was a challenge in PBS 149.
And there is a solution to that included in the zip file for PBS 150.
Basically, the challenge was to take the existing breakfast menu script we have been slowly building up over a few installments at this stage, actually. I hadn't expected it to be a long-lasting one, but it's been with us for a while.
Well, it's been fun. We can all resonate. We got pancakes, we got waffles, we got sausage.
I mean, we're having a good time.
[4:32] Kind of are, yeah. They're the kind of things I like. So yeah, okay, that's why. So I asked you to add an optional argument, minus L, limit, to specify the maximum amount of things they can make up a breakfast, and an optional flag minus S to request some snark to go with your breakfast. I was sort of thinking the carrot weather approach to being helpful.
I think that's why we really liked this homework assignment. I saw a lot of people's solutions and said they were pretty funny.
Yeah. To be honest, I'm not going to dwell on the sample solution because it is extremely by the book. It is basically a copy and paste of code from installment 49. And I really struggled to find kind of anything in there that was worthy of a special mention, really.
It's just, yeah, it's just kind of by the book. So it's in there for you to have a look at. There's one paragraph in the show notes, but I'm just going to move us on to our new stuff, because we have a lot to talk about today.
Right, right. I would say, I mean, I think it was still a really valuable exercise, because just having to do it is the whole point, even though it was—there was a lot of inventive thinking to work on this one, but to go, okay, where does the colon go to say throughout the error, and where, you know, I'm going to write my own errors, and how I put the flags, and I I like to exercising the muscle.
That's exactly what I hope for in the homework.
[5:59] Yeah, and the syntax is a bit fiddly because you're using the case statement to go through the different possible flags and options.
That's a good way to put it, a very polite way to put it.
Yeah, the double semicolons are a particularly unique aspect to the whole concept.
Right. I did have one question when I was looking at it, if I might ask that. So in your case, you wrote colon L, colon S. Let me see if I can get this right. So the first colon means I'm going to write my own error message. The colon after the L means that it's optional argument.
[6:48] Yes. and then you've got an S and that's just a regular flag, no optional arguments. What if I have another optional argument?
Would it then be, if would it be colon S, colon, Sorry, colon L colon S.
B colons? Think of the colon as being a placeholder for the option.
But if there's another one, it gets another colon.
[7:13] Yes, exactly. So if we wanted to add another one called P, we could say S, no colon, because the S doesn't want anything, then P colon.
Good. That's what I was hoping. Okay.
Thank you. Yes. It's very compact syntax. I believe dense is the word for it.
A lot of meaning in that little bit special.
Yes. OK, now we start.
[7:40] So this is all about what I love calling terminal plumbing, because one of the actual technical terms from the documentation is pipelining.
So if you have pipelining, the other actual technical term is streams.
So we have pipelines and streams. Well, gosh darn it, that's plumbing to me.
So I always call it terminal plumbing.
And it's about taking inputs from one thing shoving them into outputs from something else and just rearranging where information flows.
And we spent a lot of time in Taming the Terminal learning about that concept from the point of view of a terminal user. So not writing scripts, but just using terminal commands that exist, and chaining them together in interesting ways. And I actually think that the content we have in Taming the Terminal is really good. It's in Solomons 15 and 16. So, the fact that we're doing this again does not mean we didn't do a good job last time. We're just doing it from a completely different point of view. So we need to learn it, frankly, in much more detail here. Because now we're going to be writing scripts.
I do want to keep mentioning, Taming the Terminal is a podcast that Bart and I did that's 40 some-odd episodes. And we've got links to it, he's got links to it in the show notes.
Indeed, and with the help of amazing listener Helma, it was turned into a book as well. Which- An e-book and- it.
[9:00] Which, by the way, I was, Bart has heard this already, but this tickles me so much, I got to tell the audience about it. I was doing a presentation to the CatMac user group, it's a user group back east somewhere, and when I got on the call, someone was asking a question about using the terminal, and she said, I really want to know more about learning the terminal, but I just don't know where to start. And of course, I had to chime in. I wasn't even supposed to be on the call yet, but I had to chime in and say, well, let me tell you about taming the terminal. And someone else in the call brought up, shared their screen, brought up Apple Books and had taming the terminal in their books.
[9:38] I mean, that just proves it, right? Yeah. And I have actually been stopped by computer scientists in the university who said, actually, I send my students to taming the terminal. I didn't didn't realize that was you.
Wow, that's so cool. All right, so what's the difference between the way you explained the terminal plumbing, entangling the terminal, versus what we're going to do today?
Well, when we're writing scripts, we actually do need—when you're using commands written by someone else, the someone else had to know the nitty-gritty detail.
We as the user didn't really care. But now we have the other hat on.
This is programming by stealth. So now we actually do need to care because we're writing our scripts so that they will play nice with the pipeline, so that they will play nice with all of these things.
So we need to learn it at a much more fundamental level.
And where we went wrong in take one is that we didn't actually pair it back to the fundamental concepts, which are not bash-specific.
The concepts are actually much bigger. And they come from the POSIX standard. So Linux, Unix, Mac.
[10:50] They all implement this POSIX standard, which is why so much stuff is the same, whether you're using Bash or ZSH or SH or KSH. They all have a whole bunch of stuff in common because they're all POSIX compliant. And the POSIX standard is actually where an awful lot of this is coming from. So much so that if you're a C programmer writing the cat command or writing the grep command, you would need to know pretty much everything we're going to talk about today, but you'd just be using C language syntax instead of Bash syntax. But what you'd be doing would be the same. So I think it's important to understand that we're going right back to the fundamentals, and then we're going to look at how Bash handles those fundamentals.
[11:29] So ultimately, there's three related concepts that are basically everything today is these three ideas. Streams, file descriptors, and files. And none of them really mean what you think they do, but anyway, let's go through them one by one.
So POSIX compliant operating systems, like the Mac, like Linux, etc. They represent flows of data into, out of, and between processes as streams. So they're just sequences of information.
They go into the stream in one sequence and they come out in the same sequence.
That's what makes it a stream. And the OS keeps a list of every stream that every process has access to. And processes use the operating system to tell them, you know, things. And one of the things the operating system does for the processes is it maintains a table of all of the streams the process has access to, and the process asks for a specific stream by number. So there's a unique numeric ID for each stream.
[12:39] And every process gets its own numbering. So if I have a cat command, inside that cat command, there is a stream zero.
If I have a grep command, inside that grep command, there is a stream zero, but they're not the same.
Each one has their own stream zero. So if you're running five or six scripts, they'll all have a stream zero.
[13:02] Now, a stream is a two-ended thing. There's an in and an out.
So the number doesn't actually attach to the whole thing.
The number attaches to an end of a. So when we say the stream has a number, that's me being hand-waving.
The ends of the stream can have number. And very often, only one of the ends is inside your script.
In fact, almost always only one of the ends is inside your script, because you're taking information from somewhere.
So the other end of the stream is coming from something you care about, and you're interested in your end of the stream.
Can I think about this like a wiring diagram, where I've got a circuit that's a little box and it's got an input and an output, and those things are numbered?
Yes, exactly. There may be a wire, but the other end of the wire is unknown, because it's not important to that script, to that circuit.
Yeah, you're in the sitting room, and there's a bunch of wires coming into you, and you can connect to them to do cool things, but you have no idea what's happening outside the room. But some are coming in and some are going out.
And some are going out, precisely. Okay. Exactly. That is it.
Exactly. Input streams and output streams. Okay. And so you refer to the stream by the end you have a hold of.
And the name we give that numeric ID for the end of the stream is a file descriptor.
[14:18] So file descriptors have nothing to do with files. They are the ends of streams.
Okay. That's nice of them. And it's very important to remember that the same stream, if I have a stream connecting two different commands.
The same stream will be known by different numbers inside each of the different commands.
Because again, they each have their own view of the universe. They just want to drive that point home. So the POSIX operating system gives every single process it starts three streams as standard, and they always get the same numbers. The first stream, which is numbered as zero.
I was going to try to catch it right before you started, because I'm going to stop you and make you go back a little bit.
In your show notes, you talk about readable and writable, and I don't think you just said that.
Is that important for us to state emphatically? I think I said it hand-wavingly, but you're right.
Let's focus in on it. So an input is a readable, and an output is a writable.
That makes sense. So you write to the output, and you read from the input.
[15:21] Precisely, so they're synonymous with each other. Input, read, output, write.
Okay, so starting again now, we have three standard file descriptors.
Yeah, so we get handed those three file descriptors every time we start as a new process, whether we're written in C, whether we're Bash, doesn't matter, we always get three of them from the operating system.
So the first one, numbered zero, is standard input, which is usually abbreviated stdin.
That's number zero, it's an input stream. And by default, that will be connected to the terminal window in which the thing is running.
So, when that terminal window has focus, that means the keyboard.
[16:03] Now, if you move away from that terminal window and go into a Word document or something, the keyboard is no longer talking to that script.
That script actually has nothing talking to it. It's going to sit there and wait until you focus the terminal window and then start typing again.
Okay. If it's trying to read. Third output then gets Numeric ID 1.
[16:23] It's usually called stdout, and this is a writable file descriptor, and it is connected to the terminal window. So whatever you put into standard output appears as text in the terminal window.
These are by default?
By default, yes. Unless we change them. Right. Which, of course, we will change them.
Unless something has been done. Of course we will change them, right?
But yes, by default. And then standard error is ID number two.
So file descriptor two is standard error, which is stderr. And that is also connected to the terminal.
And so by default, stdout and stder behave identically the same.
[17:02] But we can change all of them. And we can change out an error to different things while we're messing about with things, which is actually useful to be able to plumb error messages one way and healthy output or happy output another way.
So when we're doing our plumbing, we actually can distinguish.
So file descriptors are the ends of streams. And they have nothing to do with files.
They have nothing to do with files, because I said the three things were streams, file descriptors, and files. So streams, check. File descriptors, check.
Files then. Please tell me files have something to do with files.
Files, like you think of them, are a subset of files like the POSIX universe conceives them.
So a regular file is a file. So thankfully there is at least some sanity there, right?
A regular file, your Word document, your text file, your shell script itself, they are files. That is good.
Many, many, many, many, many more things are also files in POSIX's brain.
So in the POSIX universe, the concept of a file is used to represent pretty much anything, that takes data or gives data.
They pretty much all get represented as files.
[18:14] So basically, you have what you think of as a file, and what I think of as a file, is called a regular file in POSIX-speak, and everything else is a special file.
And there are lots of special files. The least special of them is the folder, which in POSIX-speak, because it's old, is still called a directory, because it's ancient.
But in modern parlance, we call them folders. We also have symbolic links and hard links, which you can think of as aliases or shortcuts.
Basically two file names end up getting you to the same actual piece of data. That's a symlink.
And a hard link is a slightly different version.
So, symlinks, hard links, folders, slash directories, those are all special files.
But they're still files. Those are all special files. Okay.
Yeah. But they get more special. Oh boy, do they get more special. Yay.
If your browser makes a connection to a website, there is a stream of information connecting the browser's process to the internet.
We think of it as a TCP connection.
POSIX says it's a file. The connection itself is a file? Yes, it is.
Not the destination? The connection is a file. Oh, the connection is a file.
[19:29] Yeah. If we have two apps on the same computer that talk to each other, say you have your web server and your database server sitting on the same physical server, the same operating system, they don't need to go through the internet to talk to each other. They can talk to each other true, something that's a bit like an internet connection, but it doesn't involve the network and it's called a socket. That's a file too. Your hard disk is a file slash dev slash hd, well it used to be hd01 in the olden days, but now they have really weird names.
In fact, there's a file that represents the raw disk. There's a file that represents the partition table. There's a file that represents the partitions inside the partition table.
There's a file that basically there's files to represent the CPU. There's files to represent your RAM. Every device that exists. How about my Ethernet card?
Yeah, definitely. It has a file too. It's a file? Okay. It is a file.
[20:28] It is represented as a file, as a special file in the file system.
Okay. And it will be under a slash dev. So So slash dev slash something is a special file.
I just went to slash dev. There's all kinds of stuff in there.
There's disks and yeah, all kinds of stuff.
Yeah, absolutely. So that is how POSIX operating systems represent devices which can take and push information.
They're in the slash dev folder and they're special files.
And some of them are actually really quite interesting to us. So.
[21:01] Three of them that just always catch my eye, slash dev slash null is a writable special file that will take any input you like and ignore it.
It is a black hole for data. If you have error output you want to just poof into the ether of existence, you write it to slash dev slash null and it just vanishes.
It is just a data destroyer. This helps us now understand there was an imaginary character that Leo Laporte played on cable TV a hundred years ago and his name was Dev Null.
That's how I get the joke.
[21:38] Excellent. Okay, so this is a place where everything goes away that goes there.
It's not trash. It is a black hole.
I believe in America you guys have these yolks in a sink that grind up the food and make it disappear.
Garbage disposal. We don't have them here. Yeah, we don't have them here and I'm really jealous of them.
That's basically what Dev Null is. it's the ultimate garbage disposal. It's really useful for making information go away.
If you just need a constant flow of zeros, let's say you're trying to blank out a disk, you can read infinitely many zeros from slash dev slash zero. It is a readable file descriptor that are a readable special file that will just give you zeros forever. And so you can redirect that flow of zeros to something else, like say a thumb drive and completely erase it or whatever.
So you can just get a flow... A firehose of zeros is dev zero.
[22:34] You can also get a firehose of random numbers. Now, they're actually low-entropy random numbers, but slash dev slash random will give you some low-entropy randomness on demand.
Which can be used... It's kind of fun.
Yeah, there's also a dev urandom which gives you high entropy randomness, but the thing is, urandom will wait until it has enough entropy. So you can read from dev random instantly, but if you're unfortunate and you read from urandom and there's no randomness available because there's no chatter on your network or whatever, you may end up waiting a long time until it's happy that you have enough entropy. Anyway, that's neither here nor there.
[23:06] So you have all of these special files. Most of these special files are universal, Right, every single process on your computer shares the same slash home slash allison.
Every single computer has the same slash etc slash hosts file. We all have the same slash dev slash null. We all have the same slash dev slash random. But the operating system is perfectly allowed to have locally scoped files that are different for every process. Why not?
And so, whatever the heck is connected to standard in is made available as a special file called slash dev slash stdin.
And when I read dev stdin in one script, I will get a different input than if I read it from a different script.
Because it belongs to the process. So every process sees something on dev stdin, but they're all different.
Slash dev slash std out, and slash dev slash std error.
I'm making a confused face. So you're saying, in slash dev, I can see it, I can see standard error, standard in and standard out.
What process do those have to do with, the ones I'm looking at in my directory of slash dev?
[24:24] Terminal or your finder.
Huh. Okay. But it's always stored there? Like, while I'm in the middle, I'm running a script, it's writing and rewriting that file in slash dev?
No, you're just saying the ones to do with the terminal are in there.
[24:40] No. Everything on your computer is a process. Every single process asks the operating system, show me the content of this folder. And the operating system just has a different answer for everyone. So everyone gets told, oh yeah, there's a dev STD in. But if you actually try to read from it, it's actually plumbed into something else. So everyone sees, a different copy of the universe. It's the multiverse.
It's the multiverse. Everyone sees that there is a dev STD in. But if you read a character from it, it will be a different character because it will be coming from wherever your STD in happens to to be plugged into.
I couldn't love this more. That's so obscure, it's awesome. Okay.
Isn't it wonderful? So you have three of them to match our three standard file descriptors.
So file descriptor zero is also accessible as slash dev slash scd, and they are effectively the same thing.
And scd is slash dev scd out, and it's dev scd here. So that brings us, we have streams, and we have file descriptors, which are the ends of streams, and we have files, which are basically anything that can take or send data.
So stream redirection is the POSIX... Basically, the POSIX API allows you to take one end of a stream and connect it to another.
Or take one end of a stream and connect it to a file.
[26:09] Whether that be a special file or a normal file, it doesn't matter, right?
So POSIX basically says streams can connect to streams, they plug into each other, and streams can connect to files, they plug into each other.
And therefore, you have a plumbing infrastructure. Okay.
Now, if you're writing in C, there's going to be a whole bunch of C functions for doing this.
If you're writing in a different language, you have different functions.
So this is the point where we go from generic POSIX to, and this is how Bash does it.
So now we're changing into, we're now entering Bash world.
Right? This is Bash's view of the underlying reality that is POSIX.
Okay. That make sense? Yeah. Yeah.
So now we're Bash all the way forward. So we have a collection of operators that tell Bash how to arrange the plumbing.
And the plumbing happens before the command executes. So if you make use of the pipeline or the less than sign or the greater than sign that that we're going to meet in a few minutes, that plumbing, that reconnection of all the different streams, that is done, and then the command executes.
So at the moment the command starts, stdin has already been rewired, stdout has already been rewired. So you do all of the plumbing, and then you say go.
Huh, okay. So it's not working sequentially through it? It all happens at... No.
[27:32] So plumb, then execute. Okay.
Okay, you'll have to explain to me, when we get into one of these, why that's important to know.
Well, it means that you can be guaranteed that the plumbing's connected and you won't spill water everywhere. That is the main reason you need to know. Basically, you can be sure that it's all ready for you. When your code runs, everything is connected, all the seals have been tightened, it's all set and ready to go. That's really the point I'm making.
[28:03] Okay, so stream redirection in Bash then. I will mention we're doing this in Bash 3 because we needed to work on the Mac, Linux, we needed to work everywhere. So we're not using the very, very, very latest Bash. There are more operators than we're going to meet.
Most of them were added after Bash 3. The vast majority of the new stuff are little shortcuts to do common things with fewer characters of typing. I would argue they are more difficult to read because it's not as if the terminal is particularly verbose. So shrinking it further, in my opinion, adds more confusion. We have a term for this kind of stuff where you just have some new syntax that gives you no new features. It just gives you a different way of writing it. It's called syntactic sugar. And so we should be going on a diet and we will not be having any syntactic sugar.
Oh, no sugary treats for us. Okay. Well, I'm over here having the bacon and sausage and an egg. So I'm good.
Now I'm going to reinforce our key pieces of learning here. So file descriptors are, numeric IDs representing the ends of streams. Files can be regular files or special files, including devices.
[29:19] Okay, so let's meet our first operator. The less than sign, which is an operator that takes a file and plums it into a readable file descriptor.
So in other words, into a stream.
[29:35] It's the input end of a stream. So take a file and connect it to the input end of a stream.
It's what the less than sign does. Which is a little confusing, because you put the thing that's going to shove in on the right.
And then it's a less-than symbol, so if you read from right to left, you can tell that it's flowing that direction.
[29:58] You have no idea how often I had to rewrite these show notes because it was the wrong way around. Ha ha ha!
Ah, anyway. In your show notes, you've got file descriptor less than file, and it's all cuddled together.
Yes, and that is the syntax. So it's file descriptor less than file.
Now, file descriptor is optional, so you'll very often just see less than file.
And the reason is because file description is optional with a really good default.
Zero. Standard input. So what you're doing is you're taking a file and connecting it to standard input. And that's almost always what you want. Okay, which makes sense because standard input was our keyboard in the terminal. But now that we're in the shell, we're saying we're going to shove a file in instead of having to type. Correct.
Standard input would be the obvious place to put it.
[30:46] Exactly. So you almost always just see less than some file. But it could be something else. If you get really advanced in the terminal, you can make your own streams, and they will get numbered 4, 5, 6, 7, 8.
Okay, no, no, no, no, no, no, no, stop. No, we're not going there. We are not going there. We are not going there. So there are reasons you might see number less than sign. But, nah, just less than sign file.
So an example would be wc-l, which is word count minus l for lines, less than sign, slash etc slash hosts. That will count the number of lines in slash etc slash hosts.
Okay, so the input file is slash etc slash hosts, and we've got a less than, so it's gonna shove it from the right to the left, and then do the command wc minus l.
[31:35] Yes, that's it exactly. And remember, the plumbing happens first. So by the time wc C minus L runs, it says what's my stdin and it's already the content of that file waiting for it to go.
Got it, got it. That's what I said. Okay, our next up is the opposite of less than.
It is the greater than sign, and it comes in two varieties. One of them and two of them.
[31:57] But regardless of whether it's one or two, they do the same job.
You have a writable file descriptor which gets connected to a file.
So a file descriptor that sends stuff gets connected to a file that will receive the stuff.
And the only difference between having one or two is that one of them replaces the content of a regular file, and two of them appends to a regular file.
And two greater than signs makes no sense on a special file.
That only makes sense on regular files.
Okay. I hate to throw a monkey wrench in here, but didn't we in a previous installment use three less-than symbols coming from a regular file to shove in like when we put our menu?
That's a here string, and what that was doing was taking the content... that was taking a string and shoving it as a standard input, which is not a stream redirection, that's a whole other strange thing.
Okay, so a single less than is the file to an input as an input stream.
[33:09] Pay no attention to the triple, because that's not terminal.
That's a here string.
[33:15] No, it's terminal. It's a whole other concept. It's a different section in the Bash docs.
[33:20] That's going to be hard for me to remember, because we've got a file here with a less than symbol, and it doesn't mean the same thing. Okay. Because this is going to a file with a script.
Bash does that a lot. Yeah. Bash does that a lot, by the way, right? Because it tries to make everything be one character, and there only are so many of them on my keyboard. It is a finite set.
So, but one greater than symbol and two greater than symbols, one is to take from the file descriptor into a file and overwrite it, two of them means to append.
[33:54] Correct. Or they're basically the same. So again, like with the less than sign, there is a default file descriptor. In this case, it's standard out. So by default, if you just see greater than sign file, it's standard out to a file.
And you'll see that a lot.
But you'll also see standard error to a file, often something called error.log, say.
And that means to arrow file, because to is the file description.
And Bart is saying the number two, not to greater than symbols.
Ah, so confusing. So his example says, some script.sh space the number two greater than error.log.
So you're saying take the standard error from some script.sh and shove it into error.log.
And then he's got another example with the number two and two greater than symbols, which means append to error.log.
[34:51] Exactly. So if you're running something, say, automated every hour in a cron job, if you want to see what was the output of the last time I ran the script, you would use one arrow, because that means every time, you're not getting confused by what happened yesterday.
This is a snapshot of what happened the last time it ran, the last time it ran, the last time it ran.
But if you're running it 100 times a day, and you're only going to check it once a week, well, maybe you actually want to be able to scroll back in time, in which case you choose to use two arrows so that your error log has a history.
So depending on the situation, one or the other is more likely to be useful.
Right. So that's two. I know that we've technically done three, but I'm calling it two.
The third one, then, is the pipeline. That's its official name, and its symbol is the vertical line symbol, which we call the pipe.
So it's wonderfully named. It makes up for all the other silly names in this entire episode.
The pipeline is the pipe. Yay, sense. And the pipeline, or the pipe, always has a command on the left and a command on the right because it is about connecting commands together.
So the other two were file descriptor and file in either direction.
This is command to command or process to process, if you're being more generic about it.
So what it does is it takes fileDescriptor 1 from whatever's on the left, standard out.
[36:19] And connects it to fileDescriptor 0 of whatever's on the right, i.e. standard in.
So the output of the command on the left becomes the input of the command on the right.
And that is how you flow information between commands on the terminal.
Or inside your shell scripts.
Do you want to know a secret, Bart?
The entire time we did Taming the Terminal, I never understood the pipe.
I was always kind of like, well, if I type exactly what he said, it does this.
I never used it in anger on my own, and somewhere in Programming by Stealth, it just started becoming part of my natural thing to type.
I actually piped to grep.
That's the one I really like to use. And I know that was one of the examples, but it never was, I don't know, it was something I could go look up and make it go, but I had to look it up every time to make it go.
And now I just, I used it today to do something just because I knew it.
I just, I just know it finally.
So it only took, what, four years of that, and four years. Another very common thing to pipe to is word count.
So WC minus L or whatever, because I, particularly with my work hat on, the question is often How many of these are there, and is it plausible?
I'm expecting it to be about 100,000.
So I pipe it to WC when a cell line says 9996 or something. I say, okay, that's plausible.
It's 96,000, close enough.
I cannot think of a single time I've needed to do that.
[37:49] That's interesting. This is where real-world IT kind of comes in, because you're interested in, I have a hundred thousand students who need to have accounts.
Do they all get provisioned? Let's do a word count on the list of users.
Okay, yeah, that's plausible.
[38:03] I would open up Excel, import the CSV, and do a count A. Yeah, well, you're over SSH into a server.
You're connecting it through the LDAP protocol. Not quite so straightforward sometimes. You can't always just hoof it into Excel.
I do a lot of stuff in Excel, and I've become really good at writing Excel formulae.
It is programming, but it makes Bash look really simple and really clear, and not at all confusing.
Interesting. Anyway, we're getting distracted. So as an example, we can say grep local slash etc slash host.
So grep will search for the word local in the file etc hosts, hosts. And then it will pipe, so the pipe symbol will then send all of the matching lines in etc hosts to the word count minus L. So it will tell us the amount of lines in etc hosts that contain the word local. I don't remember what it is off the top of my head, but you can run the command and figure it out.
Four. Four. It was four on mine when I ran it. I think mine was the same earlier.
So you're piping the grep command into the wc command. You're taking standard out from the grep command, and you make it standard in to the wc command.
[39:19] Bingo. That's it. Perfectly. Perfectly. Now, the last thing we need to talk about is file descriptor aliases.
So, I can take a file descriptor, which is the end of a stream, and I can shove it into a file, and that's very useful.
But I told you streams can be connected to files, and streams can be connected to streams. None of those three operators had a stream on both sides. How do I connect one to the other? The answer is, bash can turn, no not can turn.
For every file descriptor, there exists an alias that is a file. It is ampersand followed by the number. So if you need one end of standard in as a file descriptor, it's just 0. If, you need it as a file, if you need it to be on the other side, so that you can plumb it together into something else, you say ampersand zero. Now it's a file.
[40:25] I think I'm like Schrodinger's file descriptor. Yeah, I'm trying to think of an analogy. I've got a coax cable coming out, and I need to connect it to another coax cable.
And so I get one of those double-ended coax connectors and stick it in between. That's the ampersand?
It's a dongle. Turn the USB-C into USB-A. It's a dongle. The ampersand's a dongle. It makes the file descriptor a file.
It makes the HMI a VGA. Okay, now I hope before I lose that analogy, you'll tell me why I would not want to do this.
Okay, so if I need to... I can connect standard out to a file, and I can connect a file to standard in, and I can connect standard out from one command to standard in from another command with a pipe.
What if I want to take standard error and merge it into standard out so that both...
So if I have something that I need to grep, and I want to grep both standard error and standard out, I need to get the two of them into the grep.
[41:30] How do I do that plumbing? I'm missing a tool. We need a Y-connector.
We need a Y-connector, and the only way to do that is to be able to refer to the end of a stream as a file.
Because remember, it's file descriptor to file.
So I need my file descriptor to be a file, so I can...
Okay, okay. Can I use the ampersand on either one of the standard out or standard error?
Yes, you can.
Or is there a convention to only do one or the other? And does it mean something different? No, no, no. Okay.
No, no. So it can only ever be on the file side. So you have file descriptor, left or right arrow to a file. So the ampersand always goes after. So it can be ampersand zero, ampersand one, or ampersand two. It's fine. But it's always after. It's always on the right.
Right. Okay. But I'm going to be merging that with another one. So ampersand one.
[42:27] Greater than two? I don't know. Let me let you watch it.
The question is, where do you want the output to be, right? So if I take...
Because you don't end up with a new file descriptor, you end up with one of them containing both pieces of information. Got it. Okay, so it's either going to be in standard out or standard error when we merge them. Which one do you want it to be? Yes. Okay.
And you usually want it to be in standard out so that you can then pipe it into your word count minus L or whatever it is you want to do with it.
So you're normally taking standard error and smudging it into standard out so that you now have one thing which can be piped as the standard in of the next command.
Because you have two outputs, there's no error input.
But couldn't you merge standard out into standard error and then pipe it into word count?
You could. Okay, but that's just not often done. No, no, but you can't pipe, because the pipe takes standard out.
The pipe takes file descriptor one and connects it to file descriptor zero. That is all it does.
Only standard. Okay, I did not catch that. Yeah.
So it's always 1 to 0. So if you have error messages and you need to get them shoved into the next command in the pipeline, you have to get those error messages into standard out.
Okay, in the show notes you said, for example, the first command's file descriptor 1 becomes the second command's file descriptor 0. But it's not for example. It's always true.
[43:49] It's always the first commands file descriptor one becomes the second commands file descriptor one.
That's IE not EG. Yeah, but IE means for example.
No, that's EG. IE means therefore, means IE is... Do I have them backwards again? Then I messed up your show notes a lot.
[44:05] Ooh. IE means in other words. IE means in other words, EG means for example.
Oh man, and I just explained it to Sandy earlier today.
Oopsie. Did I really get it backwards again? You can check the diffs. No, no, I did. I did.
That's okay. I can fix them. Okay.
Okay, good. So it is always, always, always, always 1 to 0.
Okay. Right. So all of that is a really long way of saying you will see all over the place 2 arrow ampersand 1.
Take standard error, and connect it to the file version of standard out. In other words, merge standard error into standard out. Two arrow ampersand one. All over the place.
Okay, so two arrow ampersand one. So I'm saying take standard error and pipe it into the file standard out.
Yeah, because remember, the arrows need fileDescriptor to file.
So you need to be able to represent the fileDescriptor1 as a file, so you need a dongle.
And the dongle is your ampersand.
[45:30] It's weird, but it has to be a file on the right. And the way you turn a file descriptor into a file is with the ampersand.
So that is... I didn't design the language, but that is a syntax, so think of it as a dongle.
The next thing I should say is that you can have multiple redirects.
So if you have some command, and then you take the regular greater-than sign to send standard out to output.log, there's nothing to stop you also taking the error stream and sending it somewhere else.
So you can say, some script.sh greater than sign output.log space to greater than sign error.log.
Okay, so I'm going to stop you right here. When I read this in the show notes ahead of time, I was surprised.
So some script.sh, when we say greater than output.log, that means by default, only take standard out.
Now, that has nothing to do with the next command, which is to greater than error.log.
It's still looking back to that original file.
It's not taking something from output.log.
No. So those aren't strung together. They just both happen to be talking to sumscript.sh.
They are two redirects that apply to sumscript.sh. The only thing that connects commands together is the pipe.
Everything else is about setting up...
[46:58] It's jiggering the plumbing. So we are saying, when you run this script over here, we're going to rearrange the plumbing and then run the script.
So I'd like you to make two changes to the plumbing, then run the script.
So the first change I'd like you to do is take what would have been the terminal and shove that into output.log. And I'd also like to take you the error output, which also would have been the terminal, and shove that one over there.
So you can imagine the old switchboard.
But you can absolutely say it in the opposite order, two greater than error dot log and greater than output dot log.
[47:30] Correct, because the important point is the plumbing gets done, and then the command executes.
That's what I was going to come right back to, was that why that was important, was we set up all the plumbing, and they could be almost on like on two separate lines. They're two parallel commands.
They don't have anything to do with each other, and they're both taken to some script.
Okay, but when you do pipe, that's a left-to-right thing happening.
Yeah, pipe joins commands, whereas the arrows don't. They're just rearranging the plug-in, because they're files on the other end.
That's really interesting. And there's nothing to stop you having a less-than sign and rearranging standard in as well.
There's really no reason you couldn't have a third one on that line, just to confuse, the heck out of yourself.
Actually that makes me feel better, because remember I was confused.
I felt like it was writing from right to left, but it really isn't.
These are the things I want to set up, and they're all going to act on this.
Now, you had the less than side cuddled up against some script.sh, or the file, or whatever we had there before. And I remember pointing it out.
The cuddling is always on the right of the symbol, not on the left.
So if you scroll back up, the cuddling would have been... No, when you explain the less than operator, your example says, file descriptor cuddled up less than file.
[48:45] Ah, okay, so in the example here, the file descriptors are empty. Apart from the two, which is...
Oh, yeah, you dog. Yeah, yeah, yeah, yeah, yeah. So that's not a file. It's a file descriptor?
There's an invisible one, right? I could write some script sh space one arrow output dot log two arrow error dot log, but the one is the default, so you generally omit it.
Yeah, yeah. it's confusing enough as it is, let's not throw wounds about all the places as well.
Oh, 100%, I did not understand this on take one of 150.
[49:22] It's not straightforward. Everyone copies and pastes it, and everyone gets muscle memory for it. Very few people understand it. Very, very few.
Was it hard to explain it? To write it and explain it? Very. Very hard to explain it. And I can promise you there are sysadmins with 20 years' experience who don't understand it. They just know that if I put this magic incantation here, the right magic happens.
You should bookmark this one to send them. Do you know what that means?
Me! I'll be bookmarking it for me! and test.
If I were viewing sysadmins, I would absolutely ask them to explain, for example, this next example is perfect. So the pipeline connects processes together, whereas the less than and greater than sign rearrange the plumbing. So if you need to rearrange the plumbing before you connect the process to the other, you do the rearranging before the pipe. So this, is where the order does matter. So it's very, very common to want to take the error and standard out of a script or something and send both of them to the next item in the pipeline.
And that means you have to have the two arrow ampersand one before the pipe. Because otherwise, when some script executes, it hasn't been plumbed in. And then the standard error goes out to your terminal. The other script then plumbs in the standard error from some other script.
[50:45] Only it's too late now because what you wanted is gone. You already flushed that down the toilet over there.
Or out the terminal, but yeah.
[50:54] Okay, so his code says, some script.sh, 2 greater than &1. So that takes standard out, we're going to merge it in with the file.
Sorry. 2 is error. Standard error. We're going to merge it with the file, standard out.
Standard out. And then we're going to pipe that over to some other script.sh.
Okay, so we're merging the two together, yeah.
Yeah. And the pipe always takes one to zero, so standard out, standard in.
Therefore, when the pipe happens, it now has both our errors and our not errors, and they get shoved into some other script, which can then...
Okay, so that's that example again, where you couldn't say one greater than ampersand two. Well, you could, but the pipe wouldn't have anything to take.
Yeah. The pipe would then miss everything. It would all miss the pipe.
You'd spill it all out across, all the bits that go wrong.
I'll probably do that, okay. What a lot of people do because they think the redirect is always after the command, they think it must be on the end of the line, and they shove it on the very end, and that is one of the most common sysadmin mistakes, and that is why I'd put it on the interview.
Because that will really catch someone who knows their stuff, who's experienced.
Right. I've done a lot of talking.
[52:15] Let's do some doing.
So, no new concepts for the next while, we're just going to see them happen.
So my standard, my challenge solution seems like as good a script as any to play with.
Pbs149-challenge-solution.sh sitting in the zip file.
So the first thing I'm going to do is I'm going to take a file and shove it into standard in of my script.
And the file is also sitting in the zip file, it's called favorite-order.txt.
And it's a fascinating file that contains 3 lines of text. 2, 3, 1.
Now if you run my script without any sort of chicanery, you'll notice that 2 is pancakes, 3 is waffles and 1 is done.
So if we now pipe that file into our script, then instead of reading from the keyboard every time it asks us what we'd like for breakfast, it's reading from that file one line at a the time. So we will order Pancakes Waffles and then we will finish. So if you say ./.pbs149-channelinsolution.sh space less than sign favoriteorder.txt, it will just never ask you because the keyboard has been replaced with the file, and it will go two, three, one. Pancakes Waffles, done.
Just see it happening. You just see it on screen. Right. Yay.
[53:40] So that is redirection. That makes perfect sense.
The next thing we can do is take our output and shove it somewhere else.
But of course, we're still going to take our input from favoriteOrder, but we're now going to send our output to transcript.txt.
So ./.pbs149channelSolution, less than sign favoriteOrder, greater than sign transcript.txt.
So we're taking standard out, and we are shoving it to transcript.txt.
And what do you see if you do a cat of transcript.txt? You'll see it says, choose your breakfast, as many items as you like, added pancakes to your order, added waffles to your order, you ordered the following two items, pancakes, waffles.
Now, what you will have noticed is that the output was... Before you jump to the part about the output, because that is the most important thing, but I've been meaning to ask you this.
When I do that, when I shove the favorite order in, but I take standard out and I send it to transcript.txt, it shows me the menu, but then it writes hash question mark, hash question mark, hash question mark percent.
Those are the prompts that you were not seeing yourself typing into because they're coming from the file. Okay, okay.
[55:02] Interesting. Okay, so I'd have to go back and look at your code to see why it was...
So you just had a question mark?
Well, it's the select. There's a bit of... Because we're only seeing part of what's going on with the select, the prompting is having some debris on your screen.
Okay, okay, good. So keep going. But all I saw in transcript.txt, I've got the breakfast, I ordered and the process of ordering, but I still saw the select list on my screen.
[55:34] Yes. Now, some things you saw on your screen, and some things have ended up in the file, but nothing has ended up in both places. The output has been bifurcated. Some of it went left and some of it went right. And the reason for this is now clear to us since we understand there is standard error and standard out.
So the greater than symbol, on its own, without a number before it, means take standard out.
So everything that was on standard out went to transcript.txt, which means everything we saw on screen went to standard error.
And if you read the docs for the select command, it says, "...the set of expanded words is printed on standard error output stream.", Oh, interesting.
That's why you still saw it. So you might actually have to, when you're using this, if something unexpected happens, go look at the command you're using and where does the output go?
Yes, and you can re-plumb it. Right, right. But you might look at it and go, well, where'd that come from?
[56:37] Right. Usually it means, oh, standard error. That's usually how it works, right? You have your arrow to send it to your file, and then you still see it on screen. It's like, hang on a second. Oh, standard error. Okay.
That's where you copy-paste. Two arrow ampersand, yeah. Yeah, two arrow ampersand one, and away you go.
So let us now fix our code so it's at least consistent, because you'll notice that one One of the dumbest things is it says, select your food.
Was on standard out, therefore it went to the transcript. But the list of options to pick from went to standard error. So it was on screen, but the actual prompt wasn't. So let's update the code so that it actually goes to standard error as well. At least we'll be consistent.
Okay. So you will see there is another version of my sample solution called pbs150a-menu.sh.
And what it has done is it has added arrow &2 to the end of a bunch of echo statements, the ones that I want to go to standard error.
So noFileDescriptor means take standard out, send the 2 &2, in other words the file version of standard error.
So echo will send the standard out, we're saying nah nah nah, over to standard error with you.
So you want those things to go to the screen. I want to go to standard error, which for the moment we're still connecting to the screen.
[58:05] So now when you run, you know, pbs150a, inside arrow, favorite order, outside arrow, transcript.txt, now you get a more sane output.
The only thing in your transcript is now you ordered the following two items, pancakes and waffles. And all the rest went to standard error and ended up on your screen.
Hang on, so am I in the right spot?
[58:32] So you're saying the line that says ./.PBS150A-menu.sh greater than transcript.txt? No, before that.
One up, one ahead of me. Ah, okay. Okay, good. So we're still saying take the input, favorite order.txt, and then the sound it out goes to transcript.txt.
So we're still automating the order, we're still having it enter in the 231.
Yeah, so it's showing me you added pancakes to your order, you added waffles to your order, but then it just stops magically. But if I look at transcript, it says, you ordered the following two items, pancakes and waffles.
Yeah. So basically, the conversation was on standard error, and the results are in standard out. And we now have a same division of these two things.
Now, if we take out the less than sign favorite order.txt and we make it truly interactive again, it now works interactively, because we can actually have the conversation with it and choose some muesli and I don't know what else I put in there that was vaguely healthy like a banana.
I don't know if there was a banana in there, actually, now that I think about it.
There should have been.
So you can now interact with this. And then when you're finished, your order goes to your transcript.
Got you, got you. Okay.
[59:49] So think carefully about which of the two output streams you use for what, right? There's no right answer. You're the script writer. You have a tool, and you choose to use the tool however it makes sense. But choose.
So you can't really control... Well, can you... Let's see. Yeah, I guess you did. So some of the comments were going to standard out and thus ended up in transcript.txt by echoing those to, ampersand... By redirecting.
Redirecting them to ampersand greater than two. Did I say it the right way?
No, two arrow ampersand one.
Okay, arrow... Sorry, no, sorry, sorry. I just said it exactly backwards. I said it exactly backwards.
It's greater than ampersand two.
Yes. So greater than without a number. that stopped that from going to standard out and said, I want to put that out to the screen, which is where standard error is right now.
Correct. Correct, correct. More correct than me. You got it wrong.
I had to read it. Okay, so we can take the standard error or the standard out of whatever's going on and direct it wherever we want it to go.
[1:01:07] Correct. So really it's about being consistent so that related output goes to the same place.
And it may go there by default, or you may have to be a bit more proactive about it.
But either way, you as a programmer, you have work to do. You need to actually think about it.
What am I trying to achieve here?
Right. So another thing we can of course do is we can pipeline information into our script.
So we can say echo minus e 2 slash n 3 slash n 1 pipe dot slash pbs158 dash menu dot sh.
Okay, so instead of creating a file, you're just saying, here's where I want you to input.
Exactly. So echo minus e interprets the slash n's as actual newline characters, that's what the minus e does. Otherwise it actually prints backslash n, which is not useful.
I learned that one on my own when it didn't work in my script.
I've had to find that out on my own a few too many times. I think it may finally have sunk in. So in this case, echo writes the standard out, we then pipe that to become standard in, therefore it becomes the actions in our menu.
We could redirect that to something else. So we could basically send that to wc-l to count how many things there were in that order. Or how many lines there were. Which is a heading and then three outputs.
So anyway, there we have us.
[1:02:34] Ooh, I can't spell oblivion. I'll fix that. Hit save. So we can also send...
You didn't actually... You did have a lead into the next section. So the answer that came out was four?
Yes. Sorry, but the output to standard error is coming from our script is confusing things.
Yes, thank you. That was my setup. Okay. So in our command, we have our echo minus e, to rscript piped to wc-l. So standard out hasn't gone to transcript.txt this time, it's gone to another process, wordcount-l. But all of the, like, you know, choose your breakfast, all of that glop still went onto your screen, because standard error is still connected to your terminal. Now, we're automating the menu choice by piping standard in, and we're sending sending the output to the word count, so all of that glop is 100% useless. It's not achieving our goal at all. We're telling it, I'll choose this and then count. I don't need to see that.
Okay, so you're talking about the stuff that's showing up on screen, the standard out.
[1:03:42] Yeah, no, it is standard error, right? Because standard error is going to work at minus L.
So it's very common to want to get rid of all of that glop in these kind of situations.
And we do that by redirecting standard error, which is 2, to the magic black hole that is dev null.
So if you go echo when I see 2 slash n3 slash n1, pipe that to our script, and then space 2 arrow slash dev slash null, pipe wc minus l, which means that in the middle step, we take all of standard error in that middle bit of the pipeline, and we shove it into the black hole.
And then only standard out will continue to the word count. Good. Now, everybody else has probably figured this out, but when you're saying arrow, you mean greater than, but it's a faster way of saying, just say arrow.
Everyone does. And it doesn't make any sense. No, it's fine. It's kind of like an arrow.
Say and shove it that way, where the pointy end goes.
Exactly. Okay. Okay. So let's now look at some considerations when we're writing our own scripts.
So it's kind of obvious, but I'm going to say it explicitly so it's not implicit.
Whatever the standard in, standard out and standard error were for your script, every command you start within your script inherits those by default.
[1:05:07] You can change them, but by default if your script has been sent to a file, all of the things in your script are going there too unless you change them.
If you think about it, we use the echo command inside our script.
So if the echo command hadn't inherited our redirections, it would never have ended up in transcript.txt.
So I'm restating the obvious here, but if your script is redirected, everything in your script is redirected.
If your script calls another script, calls another script, it's all inherited.
[1:05:41] Okay. Yeah, I guess that seems intuitive. Yeah.
Again, I'm making explicitly implicit. Otherwise, things would be really weird.
Sort of imagine a universe where that wasn't true and you couldn't actually do anything useful.
The other thing that we kind of sort of know already is that reading a stream is destructive.
Think of it like a river.
If you take a bucket of water out of the river, it's out of the river.
So a stream is a sequence of information coming at us and we use a read command to take one line of it, or we can use another command to take it all, but when we take it out of the stream, it's gone from the stream.
Now, don't worry if I turn a regular file into a stream.
Have I deleted my file? No, you haven't.
So what's actually happened is that a copy of the file has been turned into the stream.
And so you read the copy and as you read it, it gets destroyed.
[1:06:38] So if you need to use information later, you need to save it somewhere because the stream is gone, right?
So, we actually do have to capture standard in if we want to use it later.
So we have a couple of ways we can capture standard in. So the read command is a way of reading one line at a time from standard in.
What it's really doing under the hood is it's reading one ifs at a time.
So the $IFS, special posix variable, is the input field separator.
And what it's basically saying is keep reading standard in until you meet one of these.
And then it stops.
[1:07:20] So that's why changing ifs to the blank string makes read empty the stream.
Because I don't care how many Uline characters you give me, I'm going to keep reading because I don't have an input file separator. So if you want to read the entire content of a stream in one go with the read command, you set ifs to the empty string and it just keeps reading forever until the stream is empty and then it stops because the stream is empty. Now I don't like doing that. Because if I change the value of ifs, and then I forget to put it back how I found it, I have now made spooky action at a distance, and that could cause...
So I used $ifs. I changed $ifs to something to solve a problem of not being able to find the new lines. And when I showed you that, you said, ooh, and you got the spooky action at a distance thing.
But if you have discipline, isn't that what it's for?
It's its whole job is to be changed and then put back. Yes, but it's a very crude technique that dates back to the very early days of...
It's inherited from Shell as opposed to being a good way to do things, like in the 70s before we knew better.
Okay, so you're weak sauce if you have to resort to it. It means you just didn't figure out the right way to do it.
[1:08:39] Is that harsh? That's a little bit too harsh. There are times where, like, there are times you have to actually genuinely write assembly language, but you shouldn't do it unless you have to, because it's kind of dangerous and the ifs is kind of like that.
Most of the time there is a better way, but I am not going to tell you that it's always wrong because that's too strong.
I did figure out the right way to do it and didn't have to use it.
But boy, it was offered as the solution all over the internet.
Told me it was the right way to do it, Bart.
[1:09:13] The internet is awfully sticky sometimes. Because what happened is someone asked their sysadmin friend from the 70s how you do this, and their sysadmin friend from the 70s said this, and it worked.
And that is now stuck on the internet forever.
And ever and ever and ever, amen. And no one really understands that, so they just copy and paste it.
And it just spreads.
Okay. That's how it works.
Now, I prefer to read all of STDN at once. commands, using a technique called slurping, which I love. And we can use the cat command with no arguments. Because if you open the man page for cat, it says by default it reads the standard in and sends it the standard out.
Now we know that $openRoundBracket someCommand closeRoundBracket takes whatever the output of that command is and shoves it into a variable. It's the value of that command, is how I've been describing it to you, but it's actually standard out becomes the variable's value, is really what's going on. So if I say some variable equals dollar open bracket cat close bracket, I have just slurped all of standard in into the variable, whatever I called it, in one go.
But we don't know what standard in is.
[1:10:26] It's just, this is a script that will... Whatever it is. a standard in.
There's always one, yeah. If it's going to run, it's going to have to get a standard in from the keyboard or from a file or...
Exactly, right? So if I need to read my input, I don't care where it came from, I'm just saying, you will send me some input, I will accept that input, and then I will do something.
I can just take all of the input I was given with $, open, round bracket, cat, close round bracket. Okay, so it is literally cat. You could use any other command.
It is cat that takes all of standard in, it just slurps it in. Yes. Okay. Yes.
So if you say man cat, it says default behavior, take standard in, send the standard out.
So its default behavior is to do some very simple plumbing.
Okay. And then standard out is being, because we gave it the dollar symbol, it's saying shove that into this variable name.
Yes. Ah, okay. I read this four times, did not understand it until you said it. Got it.
Okay. by the terminal can be very...
[1:11:29] I say a lot, but very little characters. Yeah, yeah. Very few. Yeah.
So we have an example script called PBS 150 B Naive Shouter.
That is H, which takes input, turns it into all caps and then outputs it again. So it shouts at you.
So it's just the input becomes equal to dollar open bracket.
Sorry, open parens, cat, close parens.
Then it says the output becomes equal to $echo the input piped to tr and then some special regular expressions in fact.
The tr command stands for translate and it takes a string of characters and it maps them to another string of characters. So in other words, this is going to take lowercase a and map it to uppercase a, lowercase b and map it to uppercase b. It's a translation from lowercase to uppercase, is what it actually is.
Okay. It's a strangely powerful command, tor. Again, it's quite dense.
Man, tor will tell you all about it, and you'll get very confused, and then you'll go to Google to get a real answer. That may be how I got where I needed to be.
And then I just say echo the output.
[1:12:40] So we read from standard in, we make it over case, and we output it to standard out.
That's all the script does.
So as I say, it's sitting there in the zip file.
So we can run it.
We can say echo hello world pipe dot slash PBS 150 be naive shouter and it will shout Hello world!
But what happened? Can I describe what happened then? So we echoed hello world, we piped it into the Naive Shouter, and so the first thing the Naive Shouter did was it took standard in, which was hello world, and it slurped the whole thing in and shoved it into a variable called the input.
And then we ran this command that changes it all to uppercase, and then you had to echo dollar the output, which meant shove it to standard out, and that was why it comes out in caps.
[1:13:37] Correct. Perfectly described. So this worked brilliantly because Standard In had some information.
We slurped the output of our echo command.
Therefore, it worked perfectly. We had something to slurp.
What happens if I run that script, or you run that script, without a pipe? Just ./.name of script.
Well, standard in exists. It's connected to your keyboard. But we're using the cat command, not the read command. So it's going to keep reading until it meets end of file. So you can type away and hit enter, and type away and hit enter, and type away and hit enter, and it's going to keep wanting more. And more, and more, and more, and more. How do I tell it, I'm done! What it's really waiting for is the end of file character, which is not on your keyboard. But it is available to you by the magical combination of CTRL plus D.
So when you're finally done being frustrated, hit CTRL D and it will stop.
So it not only stops, it does something very odd. So I typed in ABC in all lowercase. Then And I hit control D, and it wrote capital A D, capital B, capital C.
Why did the—where did the D come from?
[1:15:07] I must have been your control D unless you maybe right but it after the capital A.
Maybe i can't i can't tell i don't remember that happening but.
Yeah i tried it twice just to think a site you know fumble fingered it and it does so it takes it does the capital A and then it goes oh i got the D.
And then made it a candle even though it was a control D not a regular D that's that's spooky action at a distance airborne.
Yeah it is, but basically you shouldn't end up in a situation where your users have to know the type of control D.
Okay good. That's why we call it a naive shouter. So we actually need to be a little bit intelligent when we slurp Standard In.
Now my first thought was, well there must be a way to just ask Bash, is there something there?
Like, is standard in empty, or is there some information in it for me to take out?
And that's actually impossible if you'd like your script to work everywhere.
It sounds like it should be easy. Tell me, is there something in the stream?
Is the stream empty? You know, it's like, does this river have water in it?
It should be an answerable question.
But it is not, unless you use bash five, when you can use the bizarre incantation read minus T zero, Which will then have a success code that tells you whether or not there was something there to read.
[1:16:35] It reads nothing but tells you whether or not it would have succeeded if it had.
It doesn't work on the Mac, so it's not cross-platform. By the way, your show notes say, never on a Mac.
There is a reason Apple doesn't ship the latest version of Bash.
It's not that it's behind, it's that they have a... And Apple disagrees with the license for Bash 4 and above, correct?
Yes, it is all such a philosophical argument. But the GPL version 3 is different to the GPL version 2, and some people have issue with it.
Including Linus Torvalds. So it's not a particularly niche position.
But Bash 4 is GPL 3. Therefore it is not on the Mac.
Okay. I just want to make sure people didn't think it was just old and stupid.
[1:17:30] No. It's just... Yeah, oh, it's a nerd fight. Okay, so we still have to have a way to know whether it's standard in has content to slurp.
Is there a workaround?
Yeah, and there is. The workaround is to copy what everyone else does.
Basically be like everyone else. So Cat and Grep are under the same restrictions as my script and your script.
So how do they handle it?
The answer is, they don't detect. They make you say, take it from standard in.
[1:18:08] So they make you tell it to go slurp. And there's a convention.
It's pretty explicit.
Yeah, so it's a two-rule algorithm. If there are no files specified, so when I just say cat with no arguments, it says, oh, you must have meant standard in.
You didn't tell me anything else, so what else could you have possibly meant?
I'll go slurp. The grep command is kind of the same behavior. If you say grep space one, argument, that means grep pattern, and that should be followed by where to search. But, if you just say grep pattern, well, you didn't say where, so grep goes, well, I assume you meant standard in then. If you tell me nothing, I'll assume standard in. So that's the first part of the algorithm, is if you didn't tell me somewhere...
Here. Don't all commands assume standard in? All commands have a standard in.
Oh, but cat and grep are different.
No, whether you choose to slurp them is your choice, right? Every command has a file descriptor called zero connected to something.
Do they choose to read from it?
[1:19:19] Okay, let me see if I can say that then. So Cat and Grep do have a file descriptor zero.
So they have a standard in, but you need to tell them whether or not to read from that explicitly.
Right, because if you read from it when it's empty, it'll wait for you to hit Command-D.
Control-D, yeah. Control-D. Right, right.
Okay. Right. So the first rule is, if you didn't tell me where to look, I'm just going to read from standard in because, well, why else did you call me? Why are you asking me to search for a pattern? Why are you asking me to print something out? Why am I here? Right? It's sort of an existential question. So it assumes if he didn't tell me anything at all, read, from standard in.
The second thing is if you do give me a list of files to go and cat or grep, I will check Check each one. If the file name is the symbol "-", I'm going to read standard-in, and basically I'm going to use "- to represent standard-in. If you give me anything other than a "-", I'm going to go read that file. So if you... cat- is the same as cat.
Okay, good. That's what I was going to say. Why do you need to say a "- if cat doesn't require it?
Okay, so cat can take arbitrarily many files and concatenate them together.
[1:20:45] So you could have header.txt minus footer.txt. That would take the content of standard in and stick it between header.txt and footer.txt.
That was the top of my head going off. No, let me try to do it again, because I might be able to get there.
So the minus is standard in, but it can be one of a list of things that can be the input.
And it doesn't stop it from talking to taking an input file.
When it runs into one of those, it's not like it's got a finish line on it or anything.
It wouldn't exit or anything. Okay. Yeah.
All right. It's just a code word. It's like, if I say the sparrows are nesting in the winter, that means I'm a Russian spy.
If I say minus, that means go read Standard In.
[1:21:42] Okay, I didn't follow the Russian spy part, but okay, I think I've got it.
Isn't that a thing in spy novels where you sit down next to someone in a coffee shop and say some silly innocuous sentence and it means I'm the Russian agent who is here to meet you?
Have I been reading too much James Bond? Maybe I've been reading too much James Bond.
Anyway, it's just a code name for go read Standard In.
Right. This is a lot easier in human language conversation than it is reading the show notes because It just says, I'm not complaining about the show notes.
It's the only way you could write it. But it says it checks each file path to see if it's the string minus comma, and if it is comma, read from standard. It's like, wait, what? Wait, what?
But now I understand what you're saying.
[1:22:26] Okay. Yeah. And the good news is that even though it's a minus dangling on its own, getOpts does not get confused.
So it will actually work with getOpts. So it's not looking for a flag or anything.
Yeah, because it's just a minus all by itself. And because so many things implement this convention that a minus on its own will be interpreted as standard in, right? It doesn't actually mean it. It's just we've all agreed that we'll call it minus. It's a convention.
Really interesting. So make sure you cuddle your minuses when you do want it to be a flag.
Because it means something else. Well, otherwise, it's always going to be two arguments. Yeah. OK.
So we have a shouter that is not naive, so pbs150c-shouter. And this implements what I just said in English, is here in Bash.
So we've also added an optional minus-b flag to prove it works with GetOpt.
So the minus-b is an optional flag that stands for bang. So if you put a minus B, it should shove an exclamation mark after it shouts at you.
So we take the input, we shout it, and then we shove an exclamation point on the end if you said minus B. I just needed something to prove to you that it works with getups.
OK. So our new shouter will behave like a cat.
[1:23:53] So we start off and we do our usual getups thing, and we basically say, if you gave me the optional flag b, the variable bang becomes equal to the exclamation point.
So all of that is just our standard getup stuff. Mm-hmm.
So now we come to the bit where we have the algorithm. So what text is going to be shouted?
Well, I'm going to start by saying, text becomes equal to the empty string.
So I'm going to start by saying, I have nothing to shout.
So then I said, the first point of my two-point algorithm them was IF zero files are specified, read from standard in. So I say IF...
$octothorpe, $$, whatever other aliases we call this bloody symbol.
[1:24:40] Is equal to zero. Then text becomes equal to our slurp command, $openParents, $cat, $closeParents.
Okay, what does $hash mean?
So we're going back four weeks now to when we talked about special variables. $octothorpe is the number of arguments.
Okay. Thank you. It's one of the strange ones that actually makes sense, because $1 is the first argument, $2 is the second argument, $hash is how many were there?
Okay. Okay, good.
Yeah. So if there are no arguments, read standard in. So that's point one of our little algorithm.
So point two was, go through all the other files, and if it's a minus, read from standard in, otherwise read from the file.
So for path in $at. So what was $at? Also going back four weeks.
That's all of them.
Correct. So we're looping over our arguments. So every time we go through the loop, path will be the next argument.
[1:25:44] So in our loop, the first thing we say is, is path equal to minus? If it is, slurp.
ARP text plus equals $cat $open parents, cat, close parents, ELSE read the bloody file so text plus equals $cat, $path close our parents Done.
[1:26:11] Got ya. Okay. Then we convert where shouted text becomes equal to our tr stuff again and then we echo out our converted text.
So now let us prove that my script does what I say it does.
[1:26:29] So if I echo hello world and pipe it to our shouter it will shout hello world in all caps.
Right. So that means there are no arguments. So it went through and it got as far as if $octoswarp equals 0.
Yeah, it does. Okay then, I will read from standard in.
Wait a minute, wait a minute. There are no arguments. Oh, because you didn't put the B on.
Well, even if I did put the B on, remember that's a flag that we make go away with the shift. So what argument are you saying we haven't given it?
Well, none. We didn't give it an argument. What argument is it looking for?
Well, it's looping through all of the arguments to see if they're files or not, but all of the arguments is none of them, so the for loop never happens.
So the echo hello world is not an argument.
That's just pipe...
Right. What is the echo hello world? That's one command. No, echo $world is command one.
Type command2 as our script. So standard out from command1 becomes standard in to command2.
They are two entirely separate commands joined together with a pipeline.
And standard in is not an argument?
[1:27:51] No. It is a stream.
Everything after the pipe is a whole separate command. So the entire command is .pbs150cshoutout.sh.
That is the command. Okay. Okay.
Okay. Which has no argument. I somehow was feeling like it was an argument, but I see what you mean.
Yeah. We can do the same thing with the minus B and it still works.
Because after we do our get opt stance, we have the copy and paste shift echo dollar opt in minus one, that thing we learned about four weeks ago that I told you to just copy and paste that will make the optional arguments disappear.
This ringing a bell? Yes.
[1:28:39] Okay. Phew. Because I'd hate to have to do that again, because I think I'd probably get it wrong.
So that's proving point one.
If you don't tell me where to go shout, then standard in. So now let's run the script with an argument.
./.pbs150cshouter menu.txt So we now have one argument, menu.txt.
So our if statement, if the number of arguments is 0, well that never happens, cause the number of arguments is 1, menu.txt.
So now the for loop happens, because $at contains one thing, menu.txt. So we'll go through the loop once. Is menu.txt equal to minus? Nope. Therefore we skip over the slurp. We do not read standard in. What we do instead is we cat the file we were passed, and we add that, into our text.
So I think a better example would be if you said echo hello world, pipe the script, then, menu.txt, because then there is a standard in.
You haven't given it a standard in, but doing that proves that it's going to ignore that standard in.
[1:30:02] Okay, but in the real world, you would never pipe something to it and then intentionally not use standard in.
Right, but what I'm saying is an artificial example of showing that it had a standard in. Because with the example of just saying the script menu.txt, it had no standard in to look for. Well, I guess it proves it doesn't wait at the keyboard.
Right, which is actually the problem to be solved. Okay, gotcha. It does look like I understood it, though.
Yeah, exactly. So we have safely piped when it made sense to pipe, we've safely slurped when it made sense to slurp, and we've safely not slurped when it didn't make sense to slurp, which is our problem to be solved. No ctrl-d shenanigans here. It actually did the right thing. And we can also...
[1:30:48] Use the minus to have the menu.txt get shouted, and to have standard in get shouted.
So if we say echo minus c slash n slash n hello world piped to dot slash pbs150c blah dee blah dee blah minus b space menu.txt space minus. There's a lot happening here.
So we've sent the string newline newline hello world as standard in. So that's sitting for our script as standard in. We've set the minus b flag to say add an exclamation point on the end. That's also sitting in our script now.
Then we have two arguments. So let's look at our code. Is the number of arguments equal to zero? Definitely not. We have minus and we have menu.txt. So that never happens. We.
[1:31:40] Come into the for loop. Dollar at has two values. Let me make sure I get this right.
So menu.txt is the first argument, and then the second time in the loop will be the minus.
So the first time through the loop, it says, is menu.txt equal to minus? No it isn't. Therefore we will read menu.txt and shove that into our variable.
Second time through the loop, is minus equal to minus? Why, yes it is. Therefore I shall read standard in. What is in standard in? Oh, it's this newline newline hello world.
So that gets added into our variable after our menu.
[1:32:18] I completely understand this. Yay! Good!
I mean, because we were so not here last time. No, no, no, no.
This is sort of like upside-down world from the last time.
Not even close. And again, the value of how well you write this and how well you explain it, because I read the show notes, but I got to this part and I went, I don't know what he's talking about.
But now when you explained it and I'm looking at it, the two together, chef's kiss, it's working.
Excellent. So what actually gets printed out is our menu, followed by two new line characters. So the first one, basically it ends up with a new paragraph that then says, hello world. And then because we said minus B, oh shouted at you. And because of the minus B, there's an exclamation point on the end, just for a little final finesse. So proving that it understood what standard in was with the minus, that it wouldn't got the echo, it understood the flag minus B, it didn't get confused on that, and it knew to take the input of the text file first.
[1:33:19] Perfect. Yeah. Got it. And our script behaves just like cat. It behaves just like grep. We have written, a script that is a native... It behaves like the big boys. We are capable of replicating the functionality of the core features of the terminal. We can write shell scripts that are first-class citizens of the terminal.
They behave properly in the pipeline.
That's really useful. Because now our scripts can sit there and be used by everyone who's used to using the terminal.
And they will play nicely with all the other utilities we love.
And if you write five or six scripts to do five or six things, you can chain them all together because they're behaving properly.
They're behaving like every other terminal command does.
[1:34:10] And that is very powerful. Now, this, folks who are listening to episode take one, this is new stuff here. I need to
/dev/tty Direct Access to the Keyboard and Terminal
[1:34:21] give you one more piece of very important information, which is a, really darn useful.
So darn useful, you will need it to do the challenge. Only I didn't realise that you would need it to do the challenge.
So we know that out of the box, whenever the terminal starts a new command for us, a new process, a new script, it gives us a standard in, a standard out, and a standard error.
And unless we've done some plumbing, it's the keyboard and it's the terminal.
[1:34:56] Twice. And that's great. But what if we need to have our cake and eat it?
What if we need to take stuff from standard in, and we need to write stuff to standard out, and we also need to absolutely, positively, always get something from the keyboard in the middle?
So the most common example of this is you can use the ssh command to run a command on a remote server, and you can pipe the command you want to run to ssh, and you might want to take the result of that remote command and do something else with it. So you want to pipe the output of SSH to something else.
But in the middle, you need to get a password. SSH needs to say to you, I need your password, please type it in.
And you don't type that in your script, by the way. Oh, heck no, you don't. Yeah. Very good.
Okay, put this on the test too.
[1:35:56] Oh yeah, that is very high on the test. Anyway, so standard out needs to go to whatever you're trying to do with the result of running the remote command. And standard in has to be the remote command.
But you need to tell them to type and to read what they type.
How? The answer is, there's another magic file that I haven't told you about yet.
It's in slash dev. So it's another one of those files that's different for every process.
So when the terminal started the ssh command, it gave that command a copy of, it's own private copy of, slash dev slash tty.
And slash dev slash tty is a file representing the terminal that you can read from and write to.
[1:36:51] Both? Both. It's a twofer. It's a two and a one. It's bi-directional. It is a read and a write.
And you can use it anywhere you can use a file. So that means that you can say less than sign slash dev slash tty to read from the tty. And you can say greater than slash dev slash tty to write to the terminal. So you can echo please enter your password space greater than sign slash dev slash tty and it will come onto the screen and not go to standard out. Because it's not going to standard out it's going to dev tty which is your terminal.
[1:37:36] Okay. So all of your plumbing is still in place you haven't broken the pipes you've, You've just written somewhere else completely, which is connected to your terminal still.
Is that like a time-based permanent sort of thing? Like I am now in dev tty and I need to now get back out?
Or is it just for that one thing you're doing? Like any other redirection, so if you have my script and then less than sign dev tty, everything inside your script inherits all of the standard descriptors it was given.
So inside that command, standard out is now dev tty, because it's inherited all the way down. Now in this case, you're saying echo. So the echo command is just so echo less than sign dev tty means it only is the echo command, right? Because the echo command doesn't have anything inside it, it's just the echo command.
But it could be myscript, arrow, dev, tty, and then everything in myscript would be using DevTTY.
[1:38:39] But the next line of your script is not affected by any plumbing you did on the echo command, right?
Yeah, right, right, right.
Just like when we said echo, echo, arrow, ampersand 2, we only wrote the standard error for the one echo command.
Because that was all we re-plumbed.
I just wanted to make sure. Yeah, no, it's very important.
Yeah, okay. So we can use the less than sign and the greater than sign with dev tty to get from the keyboard, and always the keyboard, and to write to the terminal, and always the terminal, regardless of any plumbing that's gone on around us.
So we can have our cake and eat it. And this was the missing piece for the challenge?
This was the missing piece for the challenge. Okay. All right.
So let's have a look at the challenge then, shall we?
I would like you to update your menu script.
[1:39:32] I would like it to default to reading the menu from a file named menu.txt in the same folder as the script, i.e. default to doing what it does now.
If you pass the optional argument "-m minus", then I would like you to read the menu from standard in.
If you pass the optional argument "-m name of file", I would like you to read the menu from name of file. This should sound familiar to you. This is yet another way of saying our convention. Only I'm saying by default read from menu.txt. Otherwise, minus m minus means standard in, minus m something else means something else.
Regardless of where the menu came from, when you present the options to the user, always present the options in the terminal and ALWAYS read the answers from the keyboard.
[1:40:36] So, you're saying that we're going to have it read from a file, but then ignore that and always make them do it interactively? No, no, the menu.
No, no, the menu comes from the standard in or the file. Your choices are always interactive, like the password to SSH is always interactive.
Oh, oh, okay.
The menu is what you choose from, not what you have chosen. What is path-to-file.txt?
If you would like to use a custom menu.
So by default, the menu is read from menu.txt. But what if you'd like to use a different menu?
Then you can say, "-m different menu." Oh, so like, this is our specials today.
Exactly. Okay. Or you can accept the menu from standard in. So you could have a database query and then pipe the result of the database query to standard out, you know, to standard in, and then have that become your menu.
If you're going to get really fancy paths. OK, no.
I'm barely hanging on a thread of what this means. I think I understand. If not, I'll ask Ed.
[1:41:52] He's my go to now, man. He's got it all going on. Now, there's a lot going on here.
So if you'd like to make your code easier to read, you may, if you choose to, get rid of your minus S for snark flag.
Okay, but you can keep the menu, everything, snarky. You just don't give them the option, is what you're saying.
Well, to be honest, that's optional, right? If you'd like to simplify your code, you can take out those if statements.
Or you can leave them in. Or you can be permanently snarky. I would like you not to get distracted.
Oh, okay, good, good, good.
Okay. This sounds fun. And I actually understand the lesson now.
That's even more important.
Excellent. I knew it was going to be a long one, which we both did. But hey, I'm glad we've arrived in the right place at the end of it all. But I have run out of steam very much. Thank goodness we're here. Okay. Well, had you chopped this up into two, it absolutely wouldn't have worked.
This had to be one big piece. Yeah. No, I did look for a place and I was like, no.
[1:42:56] There's no place. We're doing it. We're doing it. You have final thoughts on this?
Okay, so we are, we are really making hay here. Like we have really done a lot. We've, a few loose ends to tie up. So most of our loose ends are actually revisiting things we've done, but explaining them properly. So we have seen most of what we need, but we haven't given it all the right names, which makes it more confusing than it needs to be.
So I'm going to do a bunch of cleaning up, particularly this concept we have seen but never named called expansions. But before we do that, I need to teach you something really practical.
There's a better way to give output than echo. It's printf and it's way more powerful.
So the first thing we're going to do is better output with printf.
And then we're going to circle back.
We also need to have a little bit of a better look at the scope of things, which will make it safer to mess around with $IFS and its ilk.
Okay, we learned a little bit more about how to do that in a controlled way. And then I definitely want to talk just finish up with a good explanation of what all the different brackets mean. And what I mean by these expansions, because you have round brackets, you have two round brackets, you have curly brackets, a lot of different brackets going on here.
And actually, they do make sense.
[1:44:17] But we've met them, higgledy-piggledy, a bit here and a bit there.
So we should pull it together. And one of the reasons I'm keen to have the pull it together episode is it will be a quick reference.
Yeah, I would love that. For me, as much as for anyone else.
Instead of, okay, go over to this lesson and then that lesson, and I'm using the PBS index from Dorothy and asking Dorothy, where did that go? And she tells me where to go find things.
Probably be better to have it all in one wrapped up place. This has been a great series, and I'm glad we're not done yet, done yet, but it's been super, super fun. I've mentioned a few times here that we have a Slack community, and you get to that by going to podfeed.com slash slack, where you can meet people from all of the NoSilicast podcast, but there's a programming by Stealth Channel in there. It says PBS, and that's where all the really nerdy people hang out. And Bart occasionally drops in when he has time and answers a few questions or makes his own little snarky comments as well. So So I highly encourage you to do it.
Or use a little love heart. Yeah. I often love heart things.
That's true. There's a little thumbs up. He's definitely very positive about everything in there.
But the entire trick to joining is going to podfeed.com slash slack and joining.
There is no, there are no hoops to jump through. We never have any spam.
We've never had any bots. We don't, don't tell anybody, but it's secretly awesome.
[1:45:39] Yes, exactly. And that's because we say it, not type it, I guess.
But yeah. type it too. I don't know why. You get the community you deserve, Tom Merritt always says.
[1:45:50] Which is one of the things I love most about working with you, because you curate an amazing, community. It's one of the funnest things about being involved with the Nocilla cast is all the Nocilla castaways. Such a group of rocking people. Right. Okay. Well, folks, lots and lots and lots of happy computing until we reconvene for the penultimate bashing.
So anyway, talk soon, and happy computing!
If you learn as much from Bart each week as I do, I'd like you to go over to lets-talk.ie and press one of the buttons over there to help support him.
He does 98% of the work here, I'm just the stooge that listens to him and asks the dumb questions.
If you go over to lets-talk.ie, you can support him on Patreon, you can donate via PayPal, or you can use one of his referral links.
I really hope you'll go over and help him out.
In the meantime, you can contact me at pogfeet.com.