Okay, welcome back. Today we're gonna cover a couple separate two main topics related to the shell. First, we're gonna do some kind of shell scripting, mainly related to bash, which is the shell that most of you will start in Mac, or like in most Linux systems, that's the default shell. And it's also kind of backward compatible through other shells like zsh, it's pretty nice. And then we're gonna cover some other shell tools that are really convenient, so you avoid doing really repetitive tasks, like looking for some piece of code or for some elusive file. And there are already really nice built-in commands that will really help you to do those things. So yesterday we already kind of introduced you to the shell and some of it's quirks, and like how you start executing commands, redirecting them. Today, we're going to kind of cover more about the syntax of the variables, the control flow, functions of the shell. So for example, once you drop into a shell, say you want to define a variable, which is one of the first things you learn to do in a programming language. Here you could do something like foo equals bar. And now we can access the value of foo by doing "$foo". And that's bar, perfect. One quirk that you need to be aware of is that spaces are really critical when you're dealing with bash. Mainly because spaces are reserved, and that will be for separating arguments. So, for example, something like foo equals bar won't work, and the shell is gonna tell you why it's not working. It's because the foo command is not working, like foo is non-existent. And here what is actually happening, we're not assigning foo to bar, what is happening is we're calling the foo program with the first argument "=" and the second argument "bar". And in general, whenever you are having some issues, like some files with spaces you will need to be careful about that. You need to be careful about quoting strings. So, going into that, how you do strings in bash. There are two ways that you can define a string: You can define strings using double quotes and you can define strings using single, sorry, using single quotes. However, for literal strings they are equivalent, but for the rest they are not equivalent. So, for example, if we do value is $foo, the $foo has been expanded like a string, substituted to the value of the foo variable in the shell. Whereas if we do this with a simple quote, we are just getting the $foo as it is and single quotes won't be replacing. Again, it's really easy to write a script, assume that this is kind of like Python, that you might be more familiar with, and not realize all that. And this is the way you will assign variables. Then bash also has control flow techniques that we'll see later, like for loops, while loops, and one main thing is you can define functions. We can access a function I have defined here. Here we have the MCD function, that has been defined, and the thing is so far, we have just kind of seen how to execute several commands by piping into them, kind of saw that briefly yesterday. But a lot of times you want to do first one thing and then another thing. And that's kind of like the sequential execution that we get here. Here, for example, we're calling the MCD function. We, first, are calling the makedir command, which is creating this directory. Here, $1 is like a special variable. This is the way that bash works, whereas in other scripting languages there will be like argv, the first item of the array argv will contain the argument. In bash it's $1. And in general, a lot of things in bash will be dollar something and will be reserved, we will be seeing more examples later. And once we have created the folder, we CD into that folder, which is kind of a fairly common pattern that you will see. We will actually type this directly into our shell, and it will work and it will define this function. But sometimes it's nicer to write things in a file. What we can do is we can source this. And that will execute this script in our shell and load it. So now it looks like nothing happened, but now the MCD function has been defined in our shell. So we can now for example do MCD test, and now we move from the tools directory to the test directory. We both created the folder and we moved into it. What else. So a result is... We can access the first argument with $1. There's a lot more reserved commands, for example $0 will be the name of the script, $2 through $9 will be the second through the ninth arguments that the bash script takes. Some of these reserved keywords can be directly used in the shell, so for example $? will get you the error code from the previous command, which I'll also explain briefly. But for example, $_ will get you the last argument of the previous command. So another way we could have done this is we could have said like "mkdir test" and instead of rewriting test, we can access that last argument as part of the (previous command), using $_ like, that will be replaced with test and now we go into test. There are a lot of them, you should familiarize with them. Another one I often use is called "bang bang" ("!!"), you will run into this whenever you, for example, are trying to create something and you don't have enough permissions. Then, you can do "sudo !!" and then that will replace the command in there and now you can just try doing that. And now it will prompt you for a password, because you have sudo permissions. Before, I mentioned the, kind of the error command. Yesterday we saw that, in general, there are different ways a process can communicate with other processes or commands. We mentioned the standard input, which also was like getting stuff through the standard input, putting stuff into the standard output. There are a couple more interesting things, there's also like a standard error, a stream where you write errors that happen with your program and you don't want to pollute the standard output. There's also the error code, which is like a general thing in a lot of programming languages, some way of reporting how the entire run of something went. So if we do something like echo hello and we query for the value, it's zero. And it's zero because everything went okay and there weren't any issues. And a zero exit code is the same as you will get in a language like C, like 0 means everything went fine, there were no errors. However, sometimes things won't work. Sometimes, like if we try to grep for foobar in our MCD script, and now we check for that value, it's 1. And that's because we tried to search for the foobar string in the MCD script and it wasn't there. So grep doesn't print anything, but let us know that things didn't work by giving us a 1 error code. There are some interesting commands like "true", for example, will always have a zero error code, and false will always have a one error code. Then there are like these logical operators that you can use to do some sort of conditionals. For example, one way... you also have IF's and ELSE's, that we will see later, but you can do something like "false", and echo "Oops fail". So here we have two commands connected by this OR operator. What bash is gonna do here, it's gonna execute the first one and if the first one didn't work, then it's gonna execute the second one. So here we get it, because it's gonna try to do a logical OR. If the first one didn't have a zero error code, it's gonna try to do the second one. Similarly, if we instead of use "false", we use something like "true", since true will have a zero error code, then the second one will be short-circuited and it won't be printed. Similarly, we have an AND operator which will only execute the second part if the first one ran without errors. And the same thing will happen. If the first one fails, then the second part of this thing won't be executed. Kind of not exactly related to that, but another thing that you will see is that no matter what you execute, then you can concatenate commands using a semicolon in the same line, and that will always print. Beyond that, what we haven't seen, for example, is how you go about getting the output of a command into a variable. And the way we can do that is doing something like this. What we're doing here is we're getting the output of the PWD command, which is just printing the present working directory where we are right now. And then we're storing that into the foo variable. So we do that and then we ask for foo, we view our string. More generally, we can do this thing called command substitution by putting it into any string. And since we're using double quotes instead of single quotes that thing will be expanded and it will tell us that we are in this working folder. Another interesting thing is, right now, what this is expanding to is a string instead of It's just expanding as a string. Another nifty and lesser known tool is called process substitution, which is kind of similar. What it will do... it will, here for example, the "<(", some command and another parenthesis, what that will do is: that will execute, that will get the output to kind of like a temporary file and it will give the file handle to the command. So here what we're doing is we're getting... we're LS'ing the directory, putting it into a temporary file, doing the same thing for the parent folder and then we're concatenating both files. And this will, may be really handy, because some commands instead of expecting the input coming from the stdin, they are expecting things to come from some file that is giving some of the arguments. So we get both things concatenated. I think so far there's been a lot of information, let's see a simple, an example script where we see a few of these things. So for example here we have a string and we have this $date. So $date is a program. Again there's a lot of programs in UNIX you will kind of slowly familiarize with a lot of them. Date just prints what the current date is and you can specify different formats. Then, we have these $0 here. $0 is the name of the script that we're running. Then we have $#, that's the number of arguments that we are giving to the command, and then $$ is the process ID of this command that is running. Again, there's a lot of these dollar things, they're not intuitive because they don't have like a mnemonic way of remembering, maybe, $#. But it can be... you will just be seeing them and getting familiar with them. Here we have this $@, and that will expand to all the arguments. So, instead of having to assume that, maybe say, we have three arguments and writing $1, $2, $3, if we don't know how many arguments we can put all those arguments there. And that has been given to a for loop. And the for loop will, in time, get the file variable and it will be giving each one of the arguments. So what we're doing is, for every one of the arguments we're giving. Then, in the next line we're running the grep command which is just search for a substring in some file and we're searching for the string foobar in the file. Here, we have put the variable that the file took, to expand. And yesterday we saw that if we care about the output of a program, we can redirect it to somewhere, to save it or to connect it to some other file. But sometimes you want the opposite. Sometimes, here for example, we care... we're gonna care about the error code. About this script, we're gonna care whether the grep ran successfully or it didn't. So we can actually discard entirely what the output... like both the standard output and the standard error of the grep command. And what we're doing is we're redirecting the output to /dev/null which is kind of like a special device in UNIX systems where you can like write and it will be discarded. Like you can write no matter how much you want, there, and it will be discarded. And here's the ">" symbol that we saw yesterday for redirecting output. Here you have a "2>" and, as some of you might have guessed by now, this is for redirecting the standard error, because those those two streams are separate, and you kind of have to tell bash what to do with each one of them. So here, we run, we check if the file has foobar, and if the file has foobar then it's going to have a zero code. If it doesn't have foobar, it's gonna have a nonzero error code. So that's exactly what we check. In this if part of the command we say "get me the error code". Again, this $? And then we have a comparison operator which is "-ne", for "non equal". And some other programming languages will have "==", "!=", these symbols. In bash there's like a reserved set of comparisons and it's mainly because there's a lot of things you might want to test for when you're in the shell. Here for example we're just checking for two values, two integer values, being the same. Or for example here, the "-F" check will let us know if a file exists, which is something that you will run into very, very commonly. I'm going back to the example. Then, what happens when we if the file did not have foobar, like there was a nonzero error code, then we print "this file doesn't have any foobar, we're going to add one". And what we do is we echo this "# foobar", hoping this is a comment to the file and then we're using the operator ">>" to append at the end of the file. Here since the file has been fed through the script, and we don't know it beforehand, we have to substitute the variable of the filename. We can actually run this. We already have correct permissions in this script and we can give a few examples. We have a few files in this folder, "mcd" is the one we saw at the beginning for the MCD function, some other "script" function and we can even feed the own script to itself to check if it has foobar in it. And we run it and first we can see that there's different variables that we saw, that have been successfully expanded. We have the date, that has been replaced to the current time, then we're running this program, with three arguments, this randomized PID, and then it's telling us MCD doesn't have any foobar, so we are adding a new one, and this script file doesn't have one. So now for example let's look at MCD and it has the comment that we were looking for. One other thing to know when you're executing scripts is that here we have like three completely different arguments but very commonly you will be giving arguments that can be more succinctly given in some way. So for example here if we wanted to refer to all the ".sh" scripts we could just do something like "ls *.sh" and this is a way of filename expansion that most shells have that's called "globbing". Here, as you might expect, this is gonna say anything that has any kind of sort of characters and ends up with "sh". Unsurprisingly, we get "example.sh" and "mcd.sh". We also have these "project1" and "project2", and if there were like a... we can do a "project42", for example And now if we just want to refer to the projects that have a single character, but not two characters afterwards, like any other characters, we can use the question mark. So "?" will expand to only a single one. And we get, LS'ing, first "project1" and then "project2". In general, globbing can be very powerful. You can also combine it. A common pattern is to use what is called curly braces. So let's say we have an image, that we have in this folder and we want to convert this image from PNG to JPG or we could maybe copy it, or... it's a really common pattern, to have two or more arguments that are fairly similar and you want to do something with them as arguments to some command. You could do it this way, or more succinctly, you can just do "image.{png,jpg}" And here, I'm getting some color feedback, but what this will do, is it'll expand into the line above. Actually, I can ask zsh to do that for me. And that what's happening here. This is really powerful. So for example you can do something like... we could do... "touch" on a bunch of foo's, and all of this will be expanded. You can also do it at several levels and you will do the Cartesian... if we have something like this, we have one group here, "{1,2}" and then here there's "{1,2,3}", and this is going to do the Cartesian product of these two expansions and it will expand into all these things, that we can quickly "touch". You can also combine the asterisk glob with the curly braces glob. You can even use kind of ranges. Like, we can do "mkdir" and we create the "foo" and the "bar" directories, and then we can do something along these lines. This is going to expand to "fooa", "foob"... like all these combinations, through "j", and then the same for "bar". I haven't really tested it... but yeah, we're getting all these combinations that we can "touch". And now, if we touch something that is different between these two [directories], we can again showcase the process substitution that we saw earlier. Say we want to check what files are different between these two folders. For us it's obvious, we just saw it, it's X and Y, but we can ask the shell to do this "diff" for us between the output of one LS and the other LS. Unsurprisingly we're getting: X is only in the first folder and Y is only in the second folder. What is more is, right now, we have only seen bash scripts. If you like other scripts, like for some tasks bash is probably not the best, it can be tricky. You can actually write scripts that interact with the shell implemented in a lot of different languages. So for example, let's see here a Python script that has a magic line at the beginning that I'm not explaining for now. Then we have "import sys", it's kind of like... Python is not, by default, trying to interact with the shell so you will have to import some library. And then we're doing a really silly thing of just iterating over "sys.argv[1:]". "sys.argv" is kind of similar to what in bash we're getting as $0, $1, &c. Like the vector of the arguments, we're printing it in the reversed order. And the magic line at the beginning is called a shebang and is the way that the shell will know how to run this program. You can always do something like "python script.py", and then "a b c" and that will work, always, like that. But what if we want to make this to be executable from the shell? The way the shell knows that it has to use python as the interpreter to run this file is using that first line. And that first line is giving it the path to where that thing lives. However, you might not know. Like, different machines will have probably different places where they put python and you might not want to assume where python is installed, or any other interpreter. So one thing that you can do is use the "env" command. You can also give arguments in the shebang, so what we're doing here is specifying run the "env" command, that is for pretty much every system, there are some exceptions, but like for pretty much every system it's is in "usr/bin", where a lot of binaries live, and then we're calling it with the argument "python". And then that will make use of the path environment variable that we saw in the first lecture. It's gonna search in that path for the Python binary and then it's gonna use that to interpret this file. And that will make this more portable so it can be run in my machine, and your machine and some other machine. Another thing is that the bash is not really like modern, it was developed a while ago. And sometimes it can be tricky to debug. By default, and the ways it will fail sometimes are intuitive like the way we saw before of like foo command not existing, sometimes it's not. So there's like a really nifty tool that we have linked in the lecture notes, which is called "shellcheck", that will kind of give you both warnings and syntactic errors and other things that you might not have quoted properly, or you might have misplaced spaces in your files. So for example for extremely simple "mcd.sh" file we're getting a couple of errors saying hey, surprisingly, we're missing a shebang, like this might not interpret it correctly if you're it at a different system. Also, this CD is taking a command and it might not expand properly so instead of using CD you might want to use something like CD and then an OR and then an "exit". We go back to what we explained earlier, what this will do is like if the CD doesn't end correctly, you cannot CD into the folder because either you don't have permissions, it doesn't exist... That will give a nonzero error command, so you will execute exit and that will stop the script instead of continue executing as if you were in a place that you are actually not in. And actually I haven't tested, but I think we can check for "example.sh" and here we're getting that we should be checking the exit code in a different way, because it's probably not the best way, doing it this way. One last remark I want to make is that when you're writing bash scripts or functions for that matter, there's kind of a difference between writing bash scripts in isolation like a thing that you're gonna run, and a thing that you're gonna load into your shell. We will see some of this in the command line environment lecture, where we will kind of be tooling with the bashrc and the sshrc. But in general, if you make changes to for example where you are, like if you CD into a bash script and you just execute that bash script, it won't CD into the shell are right now. But if you have loaded the code directly into your shell, for example you load... you source the function and then you execute the function then you will get those side effects. And the same goes for defining variables into the shell. Now I'm going to talk about some tools that I think are nifty when working with the shell. The first was also briefly introduced yesterday. How do you know what flags, or like what exact commands are. Like how I am supposed to know that LS minus L will list the files in a list format, or that if I do "move - i", it's gonna like prom me for stuff. For that what you have is the "man" command. And the man command will kind of have like a lot of information of how will you go about... so for example here it will explain for the "-i" flag, there are all these options you can do. That's actually pretty useful and it will work not only for really simple commands that come packaged with your OS but will also work with some tools that you install from the internet for example, if the person that did the installation made it so that the man package were also installed. So for example a tool that we're gonna cover in a bit which is called "ripgrep" and is called with RG, this didn't come with my system but it has installed its own man page and I have it here and I can access it. For some commands the man page is useful but sometimes it can be tricky to decipher because it's more kind of a documentation and a description of all the things the tool can do. Sometimes it will have examples but sometimes not, and sometimes the tool can do a lot of things so a couple of good tools that I use commonly are "convert" or "ffmpeg", which deal with images and video respectively and the man pages are like enormous. So there's one neat tool called "tldr" that you can install and you will have like some nice kind of explanatory examples of how you want to use this command. And you can always Google for this, but I find myself saving going into the browser, looking about some examples and coming back, whereas "tldr" are community contributed and they're fairly useful. Then, the one for "ffmpeg" has a lot of useful examples that are more nicely formatted (if you don't have a huge font size for recording). Or even simple commands like "tar", that have a lot of options that you are combining. So for example, here you can be combining 2, 3... different flags and it can not be obvious, when you want to combine different ones. That's how you would go about finding more about these tools. On the topic of finding, let's try learning how to find files. You can always go "ls", and like you can go like "ls project1", and keep LS'ing all the way through. But maybe, if we already know that we want to look for all the folders called "src", then there's probably a better command for doing that. And that's "find". Find is the tool that, pretty much comes with every UNIX system. And find, we're gonna give it... here we're saying we want to call find in the current folder, remember that "." stands for the current folder, and we want the name to be "src" and we want the type to be a directory. And by typing that it's gonna recursively go through the current directory and look for all these files, or folders in this case, that match this pattern. Find has a lot of useful flags. So for example, you can even test for the path to be in a way. Here we're saying we want some number of folders, we don't really care how many folders, and then we care about all the Python scripts, all the things with the extension ".py", that are within a test folder. And we're also checking, just in cases really but we're checking just that it's also a type F, which stands for file. We're getting all these files. You can also use different flags for things that are not the path or the name. You could check things that have been modified ("-mtime" is for the modification time), things that have been modified in the last day, which is gonna be pretty much everything. So this is gonna print a lot of the files we created and files that were already there. You can even use other things like size, the owner, permissions, you name it. What is even more powerful is, "find" can find stuff but it also can do stuff when you find those files. So we could look for all the files that have a TMP extension, which is a temporary extension, and then, we can tell "find" that for every one of those files, just execute the "rm" command for them. And that will just be calling "rm" with all these files. So let's first execute it without, and then we execute it with it. Again, as with the command line philosophy, it looks like nothing happened. But since we have a zero error code, something happened - just that everything went correct and everything is fine. And now, if we look for these files, they aren't there anymore. Another nice thing about the shell in general is that there are these tools, but people will keep finding new ways, so alternative ways of writing these tools. It's nice to know about it. So, for example find if you just want to match the things that end in "tmp" it can be sometimes weird to do this thing, it has a long command. There's things like "fd", for example, that is a shorter command that by default will use regex and will ignore your gitfiles, so you don't even search for them. It will color-code, it will have better Unicode support... It's nice to know about some of these tools. But, again, the main idea is that if you are aware that these tools exist, you can save yourself a lot of time from doing kind of menial and repetitive tasks. Another command to bear in mind is like "find". Some of you may be wondering, "find" is probably just actually going through a directory structure and looking for things but what if I'm doing a lot of "finds" a day? Wouldn't it be better, doing kind of a database approach and build an index first, and then use that index and update it in some way. Well, actually most Unix systems already do it and this is through the "locate" command and the way that the locate will be used... it will just look for paths in your file system that have the substring that you want. I actually don't know if it will work... Okay, it worked. Let me try to do something like "missing-semester". You're gonna take a while but it found all these files that are somewhere in my file system and since it has built an index already on them, it's much faster. And then, to keep it updated, using the "updatedb" command that is running through cron, to update this database. Finding files, again, is really useful. Sometimes you're actually concerned about, not the files themselves, but the content of the files. For that you can use the grep command that we have seen so far. So you could do something like grep foobar in MCD, it's there. What if you want to, again, recursively search through the current structure and look for more files, right? We don't want to do this manually. We could use "find", and the "-exec", but actually "grep" has the "-R" flag that will go through the entire directory, here. And it's telling us that oh we have the foobar line in example.sh at these three places and in this other two places in foobar. This can be really convenient. Mainly, the use case for this is you know you have written some code in some programming language, and you know it's somewhere in your file system but you actually don't know. But you can actually quickly search. So for example, I can quickly search for all the Python files that I have in my scratch folder where I used the request library. And if I run this, it's giving me through all these files, exactly in what line it has been found. And here instead of using grep, which is fine, you could also do this, I'm using "ripgrep", which is kind of the same idea but again trying to bring some more niceties like color coding or file processing and other things. It think it has, also, unicode support. It's also pretty fast so you are not paying like a trade-off on this being slower and there's a lot of useful flags. You can say, oh, I actually want to get some context around those results. So I want to get like five lines of context around that, so you can see where that import lives and see code around it. Here in the import it's not really useful but like if you're looking for where you use the function, for example, it will be very handy. We can also do things like we can search, for example here,. A more advanced use, we can say, "-u" is for don't ignore hidden files, sometimes you want to be ignoring hidden files, except if you want to search config files, that are by default hidden. Then, instead of printing the matches, we're asking to do something that would be kind of hard, I think, to do with grep, out of my head, which is "I want you to print all the files that don't match the pattern I'm giving you", which may be a weird thing to ask here but then we keep going... And this pattern here is a small regex which is saying at the beginning of the line I have a "#" and a "!", and that's a shebang. Like that, we're searching here for all the files that don't have a shebang and then we're giving it, here, a "-t sh" to only look for "sh" files, because maybe all your Python or text files are fine without a shebang. And here it's telling us "oh, MCD is obviously missing a shebang" We can even... It has like some nice flags, so for example if we include the "stats" flag it will get all these results but it will also tell us information about all the things that it searched. For example, the number of matches that it found, the lines, the file searched, the bytes that it printed, &c. Similar as with "fd", sometimes it's not as useful using one specific tool or another and in fact, as ripgrep, there are several other tools. Like "ack", is the original grep alternative that was written. Then the silver searcher, "ag", was another one... and they're all pretty much interchangeable so maybe you're at a system that has one and not the other, just knowing that you can use these things with these tools can be fairly useful. Lastly, I want to cover how you go about, not finding files or code, but how you go about finding commands that you already some time figured out. The first, obvious way is just using the up arrow, and slowly going through all your history, looking for these matches. This is actually not very efficient, as you probably guessed. So the bash has ways to do this more easily. There is the "history" command, that will print your history. Here I'm in zsh and it only prints some of my history, but if I say, I want you to print everything from the beginning of time, it will print everything from the beginning of whatever this history is. And since this is a lot of results, maybe we care about the ones where we use the "convert" command to go from some type of file to some other type of file. Some image, sorry. Then, we're getting all these results here, about all the ones that match this substring. Even more, pretty much all shells by default will link "Ctrl+R", the keybinding, to do backward search. Here we have backward search, where we can type "convert" and it's finding the command that we just typed. And if we just keep hitting "Ctrl+R", it will kind of go through these matches and it will let re-execute it in place. Another thing that you can do, related to that, is you can use this really nifty tool called "fzf", which is like a fuzzy finder, like it will... It will let you do kind of like an interactive grep. We could do for example this, where we can cat our example.sh command, that will print print to the standard output, and then we can pipe it through fzf. It's just getting all the lines and then we can interactively look for the string that we care about. And the nice thing about fzf is that, if you enable the default bindings, it will bind to your "Ctrl+R" shell execution and now you can quickly and dynamically like look for all the times you try to convert a favicon in your history. And it's also like fuzzy matching, whereas like by default in grep or these things you have to write a regex or some expression that will match within here. Here I'm just typing "convert" and "favicon" and it's just trying to do the best scan, doing the match in the lines it has. Lastly, a tool that probably you have already seen, that I've been using for not retyping these extremely long commands is this "history substring search", where as I type in my shell, and both F fail to mention but both face which I think was originally introduced, this concept, and then zsh has a really nice implementation) what it'll let you do is as you type the command, it will dynamically search back in your history to the same command that has a common prefix, and then, if you... it will change as the match list stops working and then as you do the right arrow you can select that command and then re-execute it. We've seen a bunch of stuff... I think I have a few minutes left so I'm going to cover a couple of tools to do really quick directory listing and directory navigation. So you can always use the "-R" to recursively list some directory structure, but that can be suboptimal, I cannot really make sense of this easily. There's tool called "tree" that will be the much more friendly form of printing all the stuff, it will also color code based on... here for example "foo" is blue because it's a directory and this is red because it has execute permissions. But we can go even further than that. There's really nice tools like a recent one called "broot" that will do the same thing but here for example instead of doing this thing of listing every single file, for example in bar we have these "a" through "j" files, it will say "oh there are more, unlisted here". I can actually start typing and it will again again facily match to the files that are there and I can quickly select them and navigate through them. So, again, it's good to know that these things exist so you don't lose a large amount of time going for these files. There are also, I think I have it installed also something more similar to what you would expect your OS to have, like Nautilus or one of the Mac finders that have like an interactive input where you can just use your navigation arrows and quickly explore. It might be overkill but you'll be surprised how quickly you can make sense of some directory structure by just navigating through it. And pretty much all of these tools will let you edit, copy files... if you just look for the options for them. The last addendum is kind of going places. We have "cd", and "cd" is nice, it will get you to a lot of places. But it's pretty handy if you can like quickly go places, either you have been to recently or that you go frequently. And you can do this in many ways there's probably... you can start thinking, oh I can make bookmarks, I can make... I can make aliases in the shell, that we will cover at some point, symlinks... But at this point, programmers have like built all these tools, so programmers have already figured out a really nice way of doing this. One way of doing this is using what is called "auto jump", which I think is not loaded here... Okay, don't worry. I will cover it in the command line environment. I think it's because I disabled the "Ctrl+R" and that also affected other parts of the script. I think at this point if anyone has any questions that are related to this, I'll be more than happy to answer them, if anything was left unclear. Otherwise, a there's a bunch of exercises that we wrote, kind of touching on these topics and we encourage you to try them and come to office hours, where we can help you figure out how to do them, or some bash quirks that are not clear.