Developer Productivity, v2

Bash Environment Setup Script

ThePrimeagen
terminal
Developer Productivity, v2

Check out a free preview of the full Developer Productivity, v2 course

The "Bash Environment Setup Script" Lesson is part of the full, Developer Productivity, v2 course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen demonstrates how to create a script called "run" that can run scripts from a subdirectory called "runs". He demonstrates using the "find" command to find all executable files in the "runs" directory and store them in a variable. He then loops through the scripts, filters out any scripts that don't match the filter argument, and executes the remaining scripts.

Preview
Close

Transcript from the "Bash Environment Setup Script" Lesson

[00:00:00]
>> ThePrimeagen: All right, so the idea is we're gonna create a script called run, and we're gonna pass in some arguments, and the script is gonna run zero or more scripts. And I want to make sure that these scripts are easily organized, easy to understand, and so they're gonna be in a subdirectory called runs.

[00:00:15]
All right,, pretty straightforward, and I think we can all do that. Let's see if you want to see the full version of what I've done, I here's the link out, it's called primeGen/devonGitHub, it's where I have all my stuff. I actually think that I'm writing this one a bit better than the one that I currently have, because this one is me practicing for a course.

[00:00:32]
Realizing going through it a few times, versus that one's the one I wrote in a flurry, in a theory one evening, being angry at my environment and answer accessible. So they're kinda a little bit different, so the first thing we're gonna kinda start with is just a very little bit of bashing.

[00:00:46]
We're gonna kinda slowly build up this script, so let's just start right here, we're gonna run the actual, let's see all we need to write the actual script. So we're gonna start first by creating, let's see, we're just gonna create the dev environment first, so the dev environment, again, is everything surrounding your development.

[00:01:01]
So I want to be able to effectively create a way to be able to build NeoVim from source or build anything I want or install anything I want. All right so you can create this project directory anywhere you deem fit, we're gonna create a run script or we're gonna create a script called run.

[00:01:16]
You technically can name it whatever you want you wanna make that script executable, and you can open up your favorite text editor, which is obviously NeoVim, maybe it's something else. And so we're going to start simple, let's code up something that allows us to get the current directory and take in one argument for filtering tasks, like that's it, that's all we're going to start with.

[00:01:34]
So what I'm gonna do here is I'm gonna go back to my personal here and I'm gonna make der, I'm gonna just call it, Dev-n, all right. And we're gonna do a little Dev-n and in here we're gonna start building out this whole process. So I'm gonna create a script called run, and in here you do the classic, so, if you don't know what this does, this just tells your this just tells.

[00:02:00]
Whenever you execute this, it says, hey, here's the program, you kinda, that is gonna be run right here, so this is called the Shebang, right? I have it backwards, there we go, it's a shebang, this is now a bash script, when I run it, it'll run. If you don't know how to make something executable on your system, if you're, on the old Linux side, and I believe this also works perfectly fine on Mac as well, you just do a chmod.

[00:02:26]
It's like a Michael Jackson move, you just chmod it and boom, chmod your script, now, it runs, okay, fantastic. And so if I go in here and go echo "hello", now that I have this thing ran, I can go run, it runs, so just Chmod plus x run script, that's all you gotta do, it now runs.

[00:02:45]
And you always have to usually do this because it's not in your path, you're just executing it from this directory, all right. I just assume that everybody knows things like path, is everyone here fine with the word path? Like, when I say that, no one here is confused, okay, good.

[00:02:59]
Because that's one of those things where there's a lot of assumed knowledge, and if you don't know what it is, it could be problematic. So for those that don't know, just in case the path your computer, even Windows has this. It's just a series of places that it looks for a series of directories that it looks into to see if there's anything that's executable.

[00:03:16]
So when you have something like this that's runnable, say, dev, commit, or dev end. That's because somewhere on my system I said, hey, this directory contains executables, and in that directory contained something called dev end. And when I run it, it can run all right, so let's program this thing up.

[00:03:33]
So getting the current directory in bash is a bit awkward, and I often have to look this one up over and over and over again, cuz it's just so bizarre. But what you're gonna wanna do is you're gonna wanna get the dirname of, let's see, what is it?

[00:03:48]
It's dirname of BASH_SOURCE[0], yeah, classic, right? So you're gonna change directory into the bash source zero, and then get the present working directory, bam, first try, every try. It's just like a little trick that is a part of it, I know it's like one of these one liners and bash again, this is the 400 you know, sharp edges that bash exists with.

[00:04:15]
You just memorize them and when you memorize them, they become pretty easy to kind of work with this is one of those, and it kinda makes sense, right? There's this thing called Bash 0 kind of like ARGs V in a node program that tells you where you're at.

[00:04:28]
The first two arguments is the thing is where you're at and I believe your first executable, which is node. And so then this one just says hey, this is where I'm at, so then I just change, I get the name of it and then I change to it and then I get the present working directory.

[00:04:42]
So pretty easy, awesome, and then I also wanna go like this filter equals, let's just go $1, and so this is the first argument that's passed into the script, okay, awesome. And let's go like this echo, just to really make sure we get this all scripter, and of course, a filter, all right, we're gonna just start off easy.

[00:05:02]
If I execute run, you'll see there it is, I'm in home/primeagen/personal/dev-env, perfectly, that's exactly what I wanted. And if I go foobarbaz, you see first argument foo bar and baz, okay, fantastic, we're looking good. This is everything we want, somehow we're actually gonna build something that hydrates your entire environment starting from here.

[00:05:21]
I know a lot of you, I know there's some doubters, I can feel the doubters going, no, I don't know if we can do this, all right. So there we go, look at that practically the exact same code, awesome. So let's see, but how do we run our set of scripts, right?

[00:05:33]
So now it's time to actually do the running side of things, so this is actually a pretty simple script to do. So what I'm gonna do is I'm gonna do the following, is that there is a, we can do, we're gonna use the command find, as if everyone's not familiar with find you provide it a directory and some arguments.

[00:05:50]
And it will grab everything out of that directory and recursively, and it will hand you back a list of items separated by new line effectively, is how you can think about it. And so, it's pretty straightforward to use, so what I'm gonna do is I'm actually gonna change into my scripts directory.

[00:06:05]
Remember when you execute something from in here, it doesn't actually change your directory, it starts a new process. That process has a new directory, so you don't have to worry about that, if you were to source this file, it would change your directory. So don't source it, obviously just execute it, there we go, so I'm gonna change into this directory, and I'm gonna find on runs.

[00:06:24]
And what I'm gonna do is I'm gonna find everything inside this runs directory that is an executable, and then I'm also gonna not allow it to search recursively deep. So all I have to do is go in here and go like this, max depth, if you're not familiar with find, it's actually pretty nice, max depth one, min depth one, I know, it's kinda weird.

[00:06:42]
If you don't do min depth one, you'll also get like dots because the dot directory is also a directory which is yourself. So, min depth max depth one and then do executable and type file, there we go. So this will find every single thing that is a file that's executable within the runs directory and what I'm gonna do is I'm gonna go like this, scripts equals, and I'm just gonna surround it.

[00:07:04]
There we go, so now we have all the scripts, fantastic, for script in scripts, this is that fantastic batch language, and I actually like it, I actually prefer this with words. I actually like the do and done, as opposed to square by brace, call me old fashioned, but I actually like, I like that style, I like tokens that are forced to require start and stops.

[00:07:27]
I think it looks nicer, no, this old fashioned me, right? Okay, so now all we have to do is filter out anything we don't want to execute, and then we have to execute the things that aren't filtered out should be pretty straight forward. So if you're not familiar with the old grep, we can use that as well.

[00:07:43]
So what we can do is we can say, hey, we're gonna echo filter, we're gonna echo out our filter command right here. And then we're gonna want to or we're gonna echo, sorry, we're gonna echo out the script, and we're gonna check to see, does grep contain it.

[00:07:56]
Q means quiet, it's not gonna print anything, V means Everything it's gonna inverse match it. So instead of trying to find this match, it's gonna try to not find the match. Do you not contain this value. So does this script not contain this filter? If it does not contain the filter, then we should be able to go in here, forgot the little semicolon.

[00:08:20]
Going right there, right here. We can go and continue. And just to make sure we understand, I'll go like this echo filtering script, fantastic. And if we have it, then we'll go script. We'll just execute the script, it'll just run, awesome, right? We got everything we needed done right here.

[00:08:37]
And you know what? We probably would like to do is we probably want to actually create this directory now and maybe create at least one or two test scripts, right? That probably be a good next plan. So what I'm gonna do is I'm gonna jump over, by the way, if you, if you feel like you wanna catch up with the code.

[00:08:52]
I also have it on the course website. I'm gonna take some, potentially some unconventional approaches to this runs and And I'm gonna go neovim. And in here I'll do the old shebang, a bang, user, bin and bash. There we go. I'm gonna make it executable, and I'm just gonna go echo hello from neovim for now.

[00:09:13]
Keep it pretty simple. All right, good. All right, so now, if we've done this correctly, it should only find one script, which should be neovim, because that's the only executable script inside the runs directory and it and we should be able to check for our filters. And if that's that, then we should be good to go.

[00:09:33]
So I should be able to go like this. If I run foobarbaz, you'll notice that it filtered neovim. It did not, in fact, run it. Found it but it filtered it. If I remove that it should say hello from neovim because I ran the script. Okay fantastic, hopefully people are seeing where this is going.

[00:09:47]
If I go neovim, of course neovim is inside of neovim so it will match that. It's kind of just like a fuzzy slight finder. So there we go so we have kind of like the basic framework set up for us to be able to run any script right here, and it can run an entire directory of scripts.

[00:10:05]
So that means every single thing we want to install that has basic configuration, whatever we can actually set up a series of those scripts for each one of them. So my neovim installation process, though, what was it? Seven, eight lines that are required to. To do install neovim.

[00:10:19]
It can have its own script that I know just works, and it will just run. And I can even get clever and use like environment variables to select the version. You could go nuts with all these things for now, I'm just not gonna do all those things in here, fantastic.

[00:10:35]
So let's just jump back down here. So looks like I've pretty much Written the exact same code. All right, awesome, my echo statements not as nice. Quick notion for those that don't know the difference between these, you probably see single bracket comparisons in bash, double bracket comparisons in bash, and then just no bracket comparisons in bash.

[00:10:54]
Why does all this happen? Well, the single bracket one is very it's called post compliance testing. It has its own set of syntax. You'll notice it doesn't use things like greater than. I don't know the exact reason for all, like why this one, it's an older style one.

[00:11:08]
It has more edge cases. This one is a little bit nicer. I just assume it's because it's meant to be written right on the command line. I believe there's something called test in which you can pass this in and it'll do a test on it and return one or zero as the exit code if it fails or passes.

[00:11:24]
The double bracket is more modern, so you can use something like equal equal, but it only works in bash environments. And then, of course, this one right here, which actually just executes that, and depending on the X code, is how if the if statement passes or not. So a X code of zero means, yay, we can go into this if statement.

[00:11:43]
X code of not zero means we're not gonna go into it because there was an error. So pretty straightforward with those things. All right, back to the program. What if we want to be able to run our installation scripts? But we don't want, maybe we want to test to make sure we're not crazy, right?

[00:12:00]
Why? Even if we have to do some deleting and all that, we don't want to just destroy ourselves, we probably want like, a dry run feature, right? All right, this is pretty easy to do, so let's just hop back up here and we're gonna do a little bit of changing.

[00:12:11]
I'm gonna set my filter to nothing. I'm gonna create a dry that's equal to one. And we're gonna do a little while our, what is it? While our amount of arguments, dollar sign pound, dollar sign number count, right, dollar sign argument count is greater than zero. We're gonna go in here and we're gonna do like a little bit of checking, okay?

[00:12:35]
You have to par, you have to parse your own command line arguments. It's not that bad to really do long as you don't have very complex arguments, but you can imagine I can do this. If the first argument equals dry, well, then I can make, oopsies, dry equals one.

[00:12:54]
We definitely want that to be zero. We can then say dry equals one. There we go. Dry is now true when this happens, fantastic. Else, if it's not that I can just say filter equals one. We're gonna keep it simple. We only allow a singular filtered term. I'm not gonna create an array of filtered terms.

[00:13:14]
There you go, right, let's just start simply, you can fix those things later, they are not that hard, right? And then just do a shift. All shifts does is simply removes the first argument from the argument list. So we're just gonna go through here and pop off every single argument, look through it.

[00:13:27]
That way you don't have to provide dry as like the first argument, it could be the second argument, you don't have to be clever with any of these things. We just take them, parse them, bada bing bada boom. Next, let's build out a function called log. Now I like doing this just because, especially with dry runs, I don't want to do this, dance of trying to like do a bunch of if statements with, this thing's dry or not dry, run.

[00:13:49]
I don't know which one. So I try to remove all of that and instead just run it through a couple functions. So this one's pretty easy if dry equals 1.
>> ThePrimeagen: Then I want to go echo dry run, and then I'll do this. That means print out everything that was passed to me.

[00:14:11]
Dollar sign at is all of them. Else we can just print out all of them, right? So that is now, it just kind of operates like echo. We go down here, I can now use log. If it is dry, it'll say a dry run. If it's not dry, it will not be a dry run, fantastic, right?

[00:14:28]
Now let's do the exact same thing, except for I'm gonna have execute if it's execute or, let's see. Let's go like this. First we're gonna go log and then we'll do execute with everything. So we'll log to let you know you're trying to execute something. And then if it's a dry, we'll go return else.

[00:14:46]
Or we can take that, bring that down. Else we'll just execute everything, fantastic. Yeah, there we go. So we have these two little wrapper functions to kind of help. If you're not familiar with this, that's okay. Okay, they're unusual, they're weird. These are like one of those bashisms, again, that once you learn a couple bashisms, it just helps you.

[00:15:05]
And so this just simply will get it so that I can have protective logging and protective execute based on this dry flag, yes or no. So now I can simply log that, change the directories, find all this, echo that script, I can log that out, and I can execute this.

[00:15:20]
Pretty nice, right? That feels pretty nice. It should let me know everything's going on. So now when I rerun this and I add the word dry, you can see, okay, we were gonna do this and we were gonna do this, but notice that it didn't actually run the hello from neovim.

[00:15:32]
It just said we're gonna do it. So it's like, nice. Okay, so now I can make sure that my scripts don't accidently destructively destroy myself. I can see what they're gonna do, and then I can run it. It's always really fantastic to plan for that, because or else, life kind of sucks.

[00:15:46]
There you go. Hello from the event. So I always try to build that in as one of the first things I do. Now we've effectively created something that you can now bring in every piece of software you need on your system with a single x. Execution, obviously, bringing this down.

[00:16:01]
You could do a bunch of different ways, you could bring this down. You can put this up on Git, and you're just gonna have to manually get cloned down this directory, and then just run, run, and then it will start installing everything. It's not that hard to get your system up and running, but I assume you can type for at least two minutes to get to the point of getting everything installed.

[00:16:17]
And I think the thing that I really like about this, and the reason why I like it this way is, a, it's super simple to understand. What are we at? 44 lines? This is not a lot of bash, there's not a lot to it, it's pretty straightforward stuff.

[00:16:29]
You could make it more clever if you really, really wanted to. But if I had another thing, let's just pretend, I don't know, we had something else, foobar, right? It doesn't really matter what the other thing is. If I go in here and I have bin/env bash, and I echo "hello from foobar", you'll notice that when I run this, we should be able to just do, did I make it executable?

[00:16:55]
I didn't make it executable, there we go. Look at that, protecting myself. You can even see it's filtering for foobar, it's gonna run that, it's gonna run neovim. If I simply just run, you'll notice that it runs both hello from Neovim, hello from foobar. It's pretty simple, it's a very easy way to be able to have each one of your installation scripts for each one of your little libraries that you want.

[00:17:14]
You can have your dev set that's just like pseudo app, install, jq, CMake, build-essentials, all the crap that you want, Python, all that, it can just be right there. So I just really appreciate this way cuz it's very understandable for me. And it's something that if I want to make different, I can make it different easily.

[00:17:32]
And so there we go, there is step one of why I think I like mine better than yours. Cuz I don't have any configuration in there. I simply have one simple convention. Put an executable script in a single directory, it will now work every single time. If I wanna filter it, I filter it.

[00:17:46]
There we go. Did I write very similar code? I think I wrote identical code. It's like I've done it before, high five. All right, awesome, boom, now we have this, we can easily set up our environment, it's fairly extensible. You can add as many scripts as you'd like, you can filter them.

[00:18:02]
You can even edit the script to be exactly the way you want. Maybe you want multiple filters, you, Tapper, you could. I do. I'm not even gonna say you can't, you could do that, it's just I don't want that. So I'm just not gonna do it. All right, so some of the cons, obviously, bash kinda sucks.

[00:18:16]
Not gonna lie to you. What is this? This just feels funny coming from any programming language. Why is a string of $@ making things execute? I don't know, it's bash, right? There's some bashes and that's like, I'm gonna take that now, execute it, because it's in the right order.

[00:18:32]
I don't really get that. I haven't spent enough time learning all the ins and out of bash. I just know how to use it like a sword, right? Keeping things up to date is easy to forget. And so make sure that as you install things, if you like what they are, that's why I put a filter in there.

[00:18:46]
Is that, actually, anytime I add something new to my system that I want around for a while, I go right to this directory, add a new script, and it's like, hey, I now want jq. Here's how I wanna install it, this is why I wanna install it, boom, now it's part of that.

[00:18:59]
And then I always run things through this. I just keeps me in the habit of keeping everything the same, it's always the same. I can just see with the glance of an eye all the things that I like to have personally installed on my system. Making it OS independent, again, still pain in the ass though, probably not as easy as Ansible in that sense.

[00:19:17]
But at the same time, I could just make run Mac and bada bing, bada boom, slightly different, and it all works out. Because most of those things will probably be very similar. Like, installing Neovim is gonna probably be identical between Mac and Linux, maybe. Maybe it's not, maybe it blows up.

[00:19:32]
Maybe you can just use GitHub to download the tarball and then just don't have to worry about it at all, and life's easy. Other thoughts on dry run, you could technically, if you really want to make your script as extensible as possible, you could export dry run and then run those scripts.

[00:19:46]
And that way, the things underneath have access to this variable called dry run that now equals 1. So they can just show you what you do inside of each subscript. I find that to be extremely cumbersome, big pain in practice, so I didn't wanna go that way. I just wanted to say, hey, I would have done this, but I didn't do it.

[00:20:03]
I don't wanna have to have every single script be filled with all these if statements and everything. That just sounds like a lot of work and it's not work I wanna do.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now