
For a look beyond what I mention in the introduction for this part of the course, take a look a the course repository to see where we are headed.
Your Mac is littered with hidden files called "dotfiles." Let's take a look at a few with Finder and with the Command Line.
ZSH is the new default interactive shell on macOS. Let's make sure we are using the correct shell.
Configuring your computer is usually a tedious, manual process, with a few gotchas along the way.
Watch out for "analysis paralysis" when it comes to all the information out there regarding Dotfiles.
Let's create a zshrc file, which ZSH will run each time a new shell process starts.
Commands and their options can be hard to remember. Sometimes it's appropriate to create an alias.
Let's change our prompt so it's more appropriate for this course by adding some spacing and removing some non-essential items.
We can combine multiple pieces of functionality into a single function.
The Command Line Tools are a requirement for a few of the tools we'll be using. Let's install them.
Did you know that GitHub provides a "no-reply" email address? Let's configure git locally with that address, so we can keep our personal information private.
Here we'll create a pair of public and private SSH keys.
Now we'll add our keys to our local SSH Agent and create an SSH config file.
Finally, we can add our public key to our GitHub account and test our connection.
We need to start backing up our work in the zshrc file, so we'll create a repository on GitHub and clone it to our machine.
Let's make a few changes to our code locally, and make sure we can push those changes to the remote repository on GitHub.
Our zshrc file isn't being backed up in the Home directory, so let's move it to our local repository.
Manually symlinking our dotfiles will become more tedious as we install more tools. We'll use Dotbot to help manage this process.
With Dotbot installed, let's configure it to symlink a couple of our dotfiles.
Dotbot can run shell scripts. As we progress, we'll use this functionality to organize our code into separate files.
Homebrew is "The Missing Package Manager for macOS." We'll use it to install software in a way that each install can be tracked in our repository.
We'll also take a quick look at just how easy it is to thank the Homebrew team by sponsoring them on GitHub.
Since we'll be making many related code changes under a new subject, let's create a sandboxed location in Git, called a Branch.
Let's start installing software with Homebrew. We'll start with a command line tool, and cURL replacement, called HTTPIE.
We can upgrade or replace many command line tools. This time, we'll look at bat, an alternative for cat.
With two packages installed, let's take a look at the installation output to see how Homebrew works, and learn how it handles dependencies.
Now let's look the the installation output of the actual Homebrew installation.
Homebrew was installed by downloading a long script file. Let's combine the use our our two new tools to take a look.
Homebrew doesn't just install packages for the command line, it also installs applications, or casks. Let's install Google Chrome.
Tracking application installs in our repo will be a big part of restoring our complete computer setup.
We've been getting by with Apple's Terminal application and Nano as our editor. Now we can improve that situation by installing VS Code.
Since we deleted the "artifact" that came along with the VS Code install, let's reinstall it with the Graphical User Interface of VS Code.
Using the GUI to install the code binary is too manual of a process. Instead, let's add its location to our PATH variable.
We're not ready to resolve this code-binary business. We can use the "Issues" feature of GitHub to put this idea on the shelf for a later time.
That strange command we copied from the VS Code website uses something called a "heredoc." Let's use the circuits of time to learn how it works.
In the Git & Symlinks section, we learned that Dotbot can run script files. Let's start organizing our Homebrew code into its own file.
After we install an application, we have to approve its first opening. To avoid this annoyance, let's disable the Gatekeeper during our Homebrew installs.
The Homebrew "bundle" tap offers a better way to manage and track our installs. Let's use it to create a Brewfile.
Now that we're using the Brewfile, can we still avoid the Gatekeeper? Also, manually editing our Brewfile might not be the best idea.
In this lesson, I install GitKraken which is a nice GUI for Git.
Instead of LS, we can use a more colorful alternative, called exa.
Will our Dotfiles repo be the right place to store the configuration of every single application? Let's keep that in mind while looking at Dropbox and Alfred.
Alfred is cool enough to take a quick detour so we can learn about its features.
Let's take a look at Alfred's preferences to see why Dropbox is preferred over our repository.
Some applications are only distributed through the Mac App Store. Let's install another CLI tool so we can install these applications.
Note: It looks like Snappy has become abandonware. Let me know if you have a good alternative.
We just installed Snappy with mas-cli. Let's take it for a spin and make a change to System Preferences so we can use it.
Note: It looks like Snappy has become abandonware. Let me know if you have a good alternative.
Phew! Let's review what we learned in the Homebrew section and merge our branch back into master with a fast-forward merge.
As time passes, Homebrew will automatically update the information in the repositories that you've tapped. In this lesson we look at how to upgrade software when updates are available.
Not all packages are popular enough to be in the core Homebrew repository. In this lesson we learn how to tap a third-party repository, install `bat-extras`, and take a look at its features.
After creating a new branch, we have a brief introduction to the Cultivate Skills section, and look at our GitHub issue about syntax highlighting in bat.
So far, we've only symlinked files, but sometimes it makes sense to symlink a directory. By doing this, all of the contents of the directory will in effect by symlinked as well. Doing this in Dotbot is slightly more involved than symlinking files.
Learn how to Map Syntaxes with Bat so our dot-prefix-lacking files get the highlighting they should.
When we make a commit in Git, we can reference an issue that we made previously. This will add a comment to the issue which links back to the commit. Use this to keep track of your thoughts, as well as your files, over time.
Each time we run our Dotbot install script, Homebrew is reinstalled. Since this isn't necessary and wastes a bit of time, we add an "exists" function to our zshrc that checks whether our shell can find a given command. This function appears to work in our Line Editor.
Since we want our "exists" function to work in the Homebrew Setup script, we first create a test file and find that our function can't be found. The shell runs scripts in Non-Interactive mode which doesn't source the zshrc file.
To run the "exists" function in Non-Interactive shells, we create a "zshenv" file, which is our second startup file.
With our "exists" function working in our new startup file, we add a conditional to the setup file which only installs Homebrew if the "brew" command cannot be found.
If we remove the shebang from our test file, we discover that we are running "sh" which is a shell we haven't yet come across.
While zsh is the default interactive shell, sh is the default non-interactive shell. Currently, sh is just a stand-in for bash.
After getting over our frustration with some strange looking code, we learn that `command -v` is similar to the which command.
Function arguments in shell scripting don't have names like you see in other languages. Rather, they are just numbered, so `$1` is the first parameter.
We can redirect Standard Output from other commands and we are emphasizing Output because later on we'll learn about Standard Error.
We are redirecting the result of standard output to `/dev/null` which is a strange location on our system. The result of this redirection, oddly, is to discard the output of our function.
A File Descriptor is a number that uniquely identifies a file or other input/output resources. We've been using Standard Output which is one of these resources and we learn that Standard Error is another.
In addition to redirecting Standard Output, we can also redirect Standard Error, and we use the File Descriptors we just learned about to do so.
We can rewrite the "exists" function more explicitly to better understand it. But now that we understand the individual pieces of the function, it's still not clear how it works because it discards both Standard Output and Error.
It's OK that our function throws out its results, because our scripts can instead use a hidden result of the function, which is called an Exit Code.
We've been using Homebrew to add new functionality with things like "exa" and "bat," but we can also update the software that came with our computer. Here we update nano and start looking at the various software locations on our machine. Homebrew installs nano to the usual Cellar location rather than replacing the pre-installed version.
The command `brew bundle dump --force --describe` is a bit long and hard to remember, so we create an alias. To test it, we upgrade less (another pre-installed tool) which improves our scrolling functionality when using bat.
Because nano isn't looking as fancy as our other tools, we add to our bat syntax highlighting issue by adding a comment, and we make it more general by changing the title.
Because the $PATH variable is difficult to read, we add an alias called `trail` that lists out its locations. We'll figure out how it works later, but we put it to use immediately to investigate where software is found on our machines.
We know that our aliases intercept commands and run something else. If the name of a function conflicts with a command, the same thing will happen. It's also possible to overwrite your entire $PATH which will cause more issues.
In addition to the uppercase version of the $PATH variable, ZSH provides a lowercase $path variable which is an array. We use this version in our trail alias because it is easier to manipulate, and we start exploring this with Parameter Expansion.
There are more complicated ways to manipulate Parameter Expansion. The F option that is used in our alias breaks items of an array out into separate lines.
The alias also uses a one-line version of heredoc which is called hereword. This defaults to showing its output in cat, but we can change this to bat using NULLCMD.
The various environment variables ($0, $SHELL, $ZSH_VERSION) and commands (which -a zsh, zsh --version) that we've used before to show the location and version of our shells also show us that updating ZSH with Homebrew isn't enough to change it to the default shell on the system.
Here we create our second setup file and run it with Dotbot, but the chsh -s command still isn't enough to change shells, as the Homebrew-installed version is "non-standard."
Adding the Homebrew-installed ZSH location to /etc/shells, along with logging out and back in, makes our new shell "acceptable" and changes the default system shell.
After this lesson, our new setup file will have two commands that each require a password; one is our superuser password and the other is our user password.
The list of acceptable shells, /etc/shells, is a file owned by root, so the simple redirection and appending that we've used before with echo and >> doesn't work. Here we figure out how our new mystery tee command works, and we add redirection to /dev/null so it has the same result.
We also compare the tee command option against another command that you'll find in other online examples. We don't like this one because it gives a shell sudo privileges.
Even though ZSH is the new default interactive shell, Bash is still the default non-interactive shell. While it would be perfectly fine to leave it as is, let's try to change it to our Homebrew-installed version of ZSH.
The Homebrew-installed version of ZSH is an "unrecognized" shell (I think because it isn't owned by root). Let's throw in the towel on that change and try changing the symlink to point to the pre-installed version.
Our new conditionals have fixed the idempotency and repetition issues found in our setup file. We'll make a couple of commits and link them together by adding the commit hash for the first to the message of the second.
Let's review this section and merge the new code back into the master branch.
Node is a technology many, if not most, developers will use, regardless of whether they are backend JavaScript developers. While a single instance can be installed, switching among versions is a common requirement. We'll first install N, which is a Version Manager for Node.
Rather than using an install script that makes the necessary changes for us, we can add Environment Variables and adjust the $PATH on our own. Then we'll install a few versions of Node and see how easy we can switch among them with N.
Instead of installing Node versions manually in the Line Editor, we'll create another setup file, test the conditional contained therein, and link the new file with Dotbot.
Node comes with its own Package Manager, NPM, which is typically used to install project dependencies. NPM can also install system-wide tools like we've done with Homebrew. But instead of maintaining something similar to a Brewfile, we'll install global NPM packages from the setup file.
Like we've seen elsewhere, tools installed with NPM like to add hidden files to our home directory, and we need to decide whether these belong in our Dotfiles repository.
In the last lesson, we installed NPM packages for a specific tech stack, but there are also general-use tools as well. Let's install trash-cli which will make deleting files a bit safer. Again, we have to remember to manually add these installs to our setup file.
Apparently we don't have enough package managers because--in addition to Homebrew and NPM--we are going to install Yarn. This will create a problem whose solution will be applicable to other technologies, like Ruby, Java, and others.
Each time we look at our path, we see duplicate locations. Let's take a quick detour and start solving this problem, part of which has to do with the inner workings of VS Code.
By installing Yarn the way we did*, we lost the ability to use N, our version manager for Node. To regain the use of N, we can move the N_PREFIX to the start of the $PATH variable.
* If you really need Yarn, you should install it with NPM, not with Homebrew.
We no longer see duplicates in our $PATH when in a root shell, but we still see duplicates in subshells and after sourcing our zshrc file. The lowercase array version of the $path was useful in our trail alias, and it will be useful again.
The "incantation" of typeset -U looks promising, but we don't know how arrays work in ZSH. Let's play around with them before trying to fix our path.
The lowercase $path makes our code easier to read and typeset -U prevents duplicates. This is the second time we've seen ZSH offer a benefit over Bash.
Let's conclude the Node section and merge its branch back into master.
We've covered a lot of ground in Part 1 of this course. Let's go over it once more and see what the next installments have in store.
Our Macs* are littered with hidden "dotfiles" which maintain system and application configuration information. In this course, we will:
learn exactly what "dotfiles" are,
backup these files in a Git repository,
start using tools to automate the bootstrapping process,
use Homebrew so our application installs are recorded in a remote repository,
learn many things about our shell, including Redirection, Standard Input / Output / Error, File Descriptors, HereDoc / HereWord, Error Codes, and more.
and see now Node, NPM, and Yarn fit into our Dotfiles setup (an exercise that will be similar to setting up Ruby, Java, etc.)
Along the way, we will learn how to use the Command Line and several related tools. We will start customizing our shell (with aliases, functions, variables, etc.) and replace built-in tools with projects from the open source community that improve our shell experience. Each step we take will be tied back into the larger Dotfiles concept and recorded in our repository.
This subject is big and I have many more hours of content that I'd like to create. In addition to what's currently available, I'm hoping to make two more installments; Part 2 would be about ZSH and Part 3 would be where we put it all together by restoring our setup on a new OS. As new content will likely come with price increases, now is the time to enroll. But whether I can continue adding material depends on the success of the current material, so please help get the word out.
* All the work in this course is done on macOS. Much of the content will be relevant to those on Unix-like systems, and less so on Windows systems.