Shell Games

Install Z Shell

The first stop on our journey to improved productivity is the shell. The shell is the doorway to your system. It gives you access to the file system to create, read and write files, allows you to run programs, remembers your command history and even provides a programming language that can be used to build scripts to automate repetitive tasks.

Both macOS and Ubuntu use Bash as their default shell. Bash is perfectly usable, however it doesn’t have some newer features that will make your command line usage really fast. Z Shell was built to include features from bash, ksh and tcsh. It is the culmination of years of improvements and refinements on command line usage.

So, what are these improvements? Z Shell has far superior completion. You can not only use the <Tab> key to complete directory and file paths you can complete program options by entering the command followed by a dash (-) and a <Tab>. Z Shell will then show you a list of the available options to choose from. This alone is worth switching to Z Shell, it will save you a huge amount of time typing. Z Shell also has a robust command line editor which will allow you to re-use previous command lines and modify them to suit your needs easily. There are many other improvements, too many to adequately cover here. If you want to learn about Z Shell in depth take a look at the User Guide.

Installing Z Shell on both macOS and Ubuntu is straightforward. It is just a matter of using Homebrew or Apt respectively.

Ubuntu

$ sudo apt install zsh

macOS

Z Shell might already be installed on macOS. To find out open your terminal if it isn’t already and enter the following command.

$ zsh --version

If you see zsh 5.3 (x86_64-apple-darwin18.0) or something similar you are good to go. If you get an error message follow the install instructions below.

$ brew install zsh zsh-completions

Make Z Shell The Default

Now that Z Shell is installed we still need to make it the default shell. In other words, we want Z Shell to be the shell that is running when we open the Terminal. Right now if you look at the $SHELL environment variable it will respond with /bin/bash.

$ echo $SHELL

We will change that in this section. We can use the same method to change the default shell for both Ubuntu and macOS. Use the chsh (Change Shell) command as follows.

$ chsh -s $(which zsh)

The -s option signifies that you want to change your login shell. The which zsh part finds the full path to the zsh command. Wrapping it in $() makes the output of which zsh usable by the chsh command. It will report that it is changing the shell for your user name and prompt you for your password.

Close your terminal now and re-open it. If you see a message about not having any zsh startup files do not worry, we will be fixing that in the next section when Oh My Zsh is installed. Enter q to quit and do nothing.

Verify that zsh is now your default shell by displaying the $SHELL value again.

$ echo $SHELL

You should see the zsh command. On Ubuntu you might need to restart the machine before the default shell will be changed. If you still see /bin/bash when you echo the $SHELL variable try rebooting the machine.

Your shell prompt probably looks different as well. That is OK, we will be changing that as well when we pick a theme from Oh My Zsh!

Install Oh My Zsh

Oh My Zsh is a community driven framework for managing your Z Shell configuration. It includes over two hundred optional plugins, many themes to make your shell environment colorful and a auto update tool that keeps Oh My Zsh current for you.

I tend towards not using these kinds of large configuration projects. I’d rather build up my own environment from scratch. That way I have a better understanding of how it is built, and what capabilities have been included. However, the Oh My Zsh project is setup in such a way that you can start off with a small amount of useful settings and add new plugins as you need them. It doesn’t throw you into the deep end of the pool right away. You can add plugins as you need them.

You can find more information about Oh My Zsh at its Github repository, along with installation instructions. On Ubuntu you might need to install curl. You can use Apt to install it, if it is already available Apt will just report that fact and not install it.

$ sudo apt install curl

The command to install Oh My Zsh is shown below.

$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

That will download and run the Oh My Zsh installation script. It is actually a Git project, so the install is basically just cloning the project into your home directory and updating the Z Shell configuration file.

Installing Oh My Zsh
Installing Oh My Zsh

If you are using WSL you might see some strange characters in the Z Shell prompt after you install Oh My Zsh. The default Z Shell prompt is using some characters that are not available in the font being used. This will be fixed in the Z Shell Configuration section.

Update the Dotfiles Project

We can now update the dotfiles project with our Z Shell configuration.

Note: Typically, when making any changes in a Git repository, I would create a branch for the changes, stage and commit the changes to that branch when they are finished, push that branch to the remote host and make a pull request. That is the proper Git workflow. However it does seem like a bit of overkill for this particular Git repository. These are our own configuration files, no one else will be working on the project, and no one else is likely to ever use this project other than you. For the remainder of this book I will be simply making my changes on the master branch and pushing those changes to the remote master branch. Feel free to use the proper Git workflow for your dotfiles project, or not, its completely up to you.

The Oh My Zsh installation mentioned adding an Oh My Zsh template to the ~/.zshrc file. That file holds most of the configuration options for Z Shell and it is the file we will move into the dotfiles repository so we can keep a history of our changes to it.

First make a directory for everything Z Shell related in your dotfiles repository.

$ mkdir ~/dotfiles/zsh

Now we will copy the existing Z Shell config file into the dotfiles repository and symlink to it. This way, when Z Shell starts it will find the dotfiles config file.

$ mv ~/.zshrc ~/dotfiles/zsh/.zshrc
  $ ln -s ~/dotfiles/zsh/.zshrc ~/.zshrc

Before you stage and commit those changes we will make some adjustments to the Z Shell configuration file. You will probably want to modify the PATH environment variable and pick a theme to use.

Z Shell Configuration

Take a look at the Z Shell configuration file that you just moved into the dotfiles repository.

$ less ~/dotfiles/zsh/.zshrc

The .zshrc configuration file is well documented. It allows you to easily export your PATH environment variable, set a theme or select from a set of random themes, set the number of days until Oh My Zsh updates itself or turn off auto updating completely. I’ll walk you through some basic settings changes below and explain each along the way.

Update The Path

We are going to update the PATH variable to also include the ~/dotfiles/scripts directory so the scripts we add in the future will be automatically available. Open the ~/dotfiles/zsh/.zshrc file with nano.

$ nano ~/dotfiles/zsh/.zshrc

The PATH variable is set right at the top of the file, but it is commented out at the moment. Uncomment the export PATH= line and add the scripts directory to the path as shown below.

~/dotfiles/zsh/.zshrc
  export PATH=$HOME/bin:$HOME/dotfiles/scripts:/usr/local/bin:$PATH

Each directory path is separated by a colon. Notice there is also a path for a bin directory in your home folder (~/bin). That is a handy place to put programs that you use but don’t need to be available to any other users on the machine. Use <C-o> to write the changes to the file, then <C-x> the exit nano.

Update The Theme

If you would like to stick to the book as closely as possible the Z Shell theme being used is the Candy theme. I like shell prompts that show a bit of information, but I also want to have as much space on the command line for typing out commands. The Candy theme helps in both cases. It shows the user name and machine name, the current time, the current path and Git repository information (if the directory you are currently in is a repository). That is a fair amount of information and can take up some space, so the command prompt is moved to the next line.

Find the ZSH_THEME line and change it to the following.

~/dotfiles/zsh/.zshrc
  ZSH_THEME="candy"

Optionally, if you do not like that theme, you can see a list of the themes that come with Oh My Zsh at the project website. Most of the listed themes have a screenshot of the theme in action. Simply replace "candy" with the name above the theme of your choice.

Update Case Sensitivity

This one is optional, but I find it to be very helpful. By default Z Shell completion is case insensitive. What does that mean? I’ll give an example that is directly related to this project. On macOS the user’s home directory has a folder named Documents and another folder named Downloads. We have also created a folder named dotfiles. When I want to change directory into the dotfiles directory to do some work I type:

$ cd do<Tab>

The <Tab> key triggers completion. Because Z Shell completion is case insensitive by default it shows a list of possible completions below the prompt.

$ cd do
    Documents/ Downloads/ dotfiles/

You can repeatedly press the <Tab> key to cycle through all of the options until you get to the one you want. This is handy, but it isn’t the behavior I want. If I wanted to change directory into one of the folders with an upper case ‘D’ I would have typed an upper case ‘D’. I specifically typed a lower case ‘d’ because I know the directory I’m looking for starts with a lower case ‘d’.

To change this behavior find the CASE_SENSITIVE line in the configuration file and uncomment it.

~/dotfiles/zsh/.zshrc
  CASE_SENSITIVE="true"

Now when you try to complete the dotfiles directory it will not show the other directories that begin with an upper case ‘D’. The Completion section will cover some other ways to use Z Shell’s tab completion capabilities.

Disable Title Updates

By default Z Shell will update the title of your Terminal window with the currently running command. I find this to be a bit distracting. Sometimes the command is very long and ugly and it doesn’t look nice in the title bar.

Another problem with automatically updating the title is that it will also affect our carefully crafted tmux window names. In the next chapter, when we start scripting our own development environments, we will assign specific names to each window. That will make it easier to move to the correct window when we need to see something specific. If we don’t disable the title updates then Z Shell will overwrite the window names. In the Z Shell configuration file find the DISABLE_AUTO_TITLE line. You only need to uncomment the line.

~/dotfiles/zsh/.zshrc
  DISABLE_AUTO_TITLE="true"

Custom Folder Location

Oh My Zsh lets you override settings by loading files with a .zsh extension from a folder named custom. The default location for the custom directory is in the ~/.oh-my-zsh directory, but you can override that setting as well.

We will change the location of the custom directory to our dotfiles directory so that any configuration changes we make will be tracked along with our other configuration files.

Find the ZSH_CUSTOM line in the .zshrc file and uncomment it. Then update the path as shown below.

~/dotfiles/zsh/.zshrc
  ZSH_CUSTOM=$HOME/dotfiles/zsh/custom

Save the file and exit nano with <C-o> and <C-x>. Now create the custom directory and make a file to hold some basic changes.

$ mkdir ~/dotfiles/zsh/custom
  $ touch ~/dotfiles/zsh/custom/lib_patches.zsh

The file name is not important, Oh My Zsh loads any file in the custom directory with a .zsh extension.

As an example override, Oh My Zsh has many helpful aliases defined, one of which is ll, which is a long form of the ls command. Their version of ll is ls -lh, which doesn’t show files or directories that begin with a dot. I prefer to see those when I am viewing a directory, so I have an override in my lib_patches.zsh file.

~/dotfiles/zsh/custom/lib_patches.zsh
  # My Preferred ll alias.
  alias ll='ls -GAFl'

The G option enables color output. The A option shows all entries except the . and .. directories. The F option appends file type information and finally the l option shows the long format which includes the owner and group, timestamp and so on.

Save and Restart

That is all of the Z Shell configuration changes we’ll be making for now. We will probably be enabling some plugins in the future, but that can wait. Save the .zshrc file changes by pressing <C-o>, then close nano by pressing <C-x>.

You will need to quit the terminal and restart it for the Z Shell configuration changes to kick in, so do that now. When you restart the terminal you should see the new theme you selected and you can verify the PATH by entering:

$ echo $PATH

Make sure it has the dotfiles/scripts directory listed.

Those are all of the configuration settings we need to make for Z Shell at the moment. Feel free to push the changes to your remote repository.

Shell Basics

Now the fun part. In this section I will show you some of the ways the Z Shell will speed up your development. Think of it as a mini Z Shell tutorial. I can’t cover every aspect of Z Shell, that would take an entire book on its own, but this should be enough to get you going and help you increase your productivity.

This first section on shell basics is going to cover very simple shell usage. If you are already comfortable with using a shell feel free to skip to the next section on shell Completion.

Running Commands

Shells are similar to interpreted programming languages. You type a command at a shell prompt and the shell reads your input, parses it and executes the command. Any output or error text generated by your command is then displayed in the terminal. This is a very simplified version of what happens, but that is the gist of it.

Let’s get our hands dirty. Open your terminal and enter the date command.

$ date
  Mon Feb 25 12:13:14 PST 2019

The shell read the input, which just consisted of the string ‘date’. Internally it parsed the string and determined it was a command. It then ran the command and displayed the output.

If you enter a string that the shell doesn’t understand it will display an error message instead. Enter ‘dtae’ instead and see what happens.

$ dtae
  zsh: command not found: dtae

This brings us to the PATH. The shell has to know where to find the commands you run on the command line. There are two main ways that you can tell the shell this information. You can supply the full path to the command every time you use it, or you can specify a number of places to look for commands in the PATH environment variable.

Using the full path is only really used when there is a conflict. Suppose you were building a new and improved version of the date command. If you wanted to test your version of the command you couldn’t simply enter date on the command line. The shell would find the default system version of the command and run it. You would have to supply a path to your version of the command. Supplying a path to a command tells the shell to skip searching the PATH locations and just run the command I specified.

In the Z Shell Configuration section you updated the PATH environment variable to include the ~/dotfiles/scripts directory. This is the reason for that change. The shell will now look in that directory for commands or scripts to run when you type them on the command line.

Many commands accept additional arguments that change their behavior. The ls command, for example, displays a listing of the files and directories.

$ ls
  Applications  Downloads  Library ...

Without any arguments it shows the contents of the current directory in a compact format and does not show certain files. You can change it to show a long format by using the -l (long) argument.

$ ls -l
  total 0
  drwx------@  4 dave  staff   128 Jul  8  2017 Applications
  drwxrwxr-x@  3 dave  staff    96 Feb 22 18:28 Creative Cloud Files
  drwx------+ 18 dave  staff   576 Feb 25 13:10 Desktop
  drwx------+ 19 dave  staff   608 Dec 23 20:37 Documents

That shows much more information about each file and directory including the permissions, user and group that owns it, the size and so on. You can also show the listing of a directory that is not the current directory by adding the path to the command line.

$ ls -l ~/dotfiles/zsh
  total 0
  drwxr-xr-x  3 dave  staff  96 Dec 14 12:17 custom

That doesn’t show all of the files and directories though. What happened to the .zshrc file? On Linux based systems files that start with a dot are considered system files and are not displayed by default. You can add the -A (all) argument to the ls command to force them to be shown.

$ ls -lA ~/dotfiles/zsh
  total 8
  -rw-r--r--  1 dave  staff  3475 Feb 12 23:55 .zshrc
  drwxr-xr-x  3 dave  staff    96 Dec 14 12:17 custom

That is starting to be a little too much typing just to see a directory listing. Typing that out once or twice wouldn’t be too bad, but over the course of a day or a week it would get old. Shells have a number of ways to reduce that typing. You can create Aliases for commands that you use regularly, and you can use Completion to help fill out parts of command lines. Completion will be covered in the next section.

An Alias is just a short name for a longer command. In the Z Shell Configuration section you replaced an existing Z Shell alias with a modified version. To create an alias you use alias new-name="full-command". Just replace new-name with the short name you would like to use and full-command with the actual command line. If you wanted to make an alias for the ls command above you could use:

alias la="ls -lA"

If that was in your Z Shell configuration somewhere when you typed la on the command line it would actually run ls -lA.

You can find out much more about the arguments that a particular command has by using the built in help documentation provided by the man command. The man command gives you access to Manual Pages. Most commands available have Manual Pages to describe their use, arguments, known bugs and so on. Enter man ls to see the documentation for the ls command. You can use the <Up> and <Down> arrow keys to scroll through the document and q to exit it.

Redirection

The concept of input and output is especially meaningful in the shell. The programs that run in the shell can accept input and write output to many different sources. In the ls examples above the arguments and options to ls were input to the program, and the display was the output.

There are three special sources for input and output that are always available in the shell. Standard output, also known as stdout or file descriptor 1, is where any program output is written. Standard error, also known as stderr or file descriptor 2, is where any error messages generated by the program are written. By default stdout and stderr are shown in your terminal window. Standard input, also known as stdin or file descriptor 0, is where programs read input from. By default stdin is read from your keyboard.

All three of those IO channels have defaults, which implies that they can be changed. This is where Redirection comes in. If you wished to store a directory listing to a file for later use you could redirect the output from ls to a file.

$ ls -lA ~/dotfiles/zsh > file_list.txt

The greater-than operator instructs the shell to redirect the output of the ls command into the file named file_list.txt. You can see the file will exist in your current directory after you run this command. View the contents of the file with cat and you will see that the output is the exact output you would expect.

$ cat file_list.txt
  total 8
  -rw-r--r--  1 dave  staff  3475 Feb 12 23:55 .zshrc
  drwxr-xr-x  3 dave  staff    96 Dec 14 12:17 custom

It is important to note that the greater-than operator only redirects the output from stdout, not stderr. If there had been an error in the original ls command, for example if the directory didn’t exist, you would see the error output in the terminal window as normal, and the file_list.txt file would be empty.

Also note that if you redirect the output of a different command to the same file it will overwrite any previous content in the file. If you wish to append to an existing file you can use the double greater-than operator, >>.

You can redirect input as well with the less-than operator, <, though this is not used as ofter as output redirection.

Pipes

A shell pipe is a way of chaining programs together to perform a larger task. You create a shell pipe by joining two or more commands with the pipe operator, which is the | (vertical bar) character. Doing this sends stdout from the command on the left to stdin for the command on the right. The simplest way to see this work is to pipe the output of ls to a pager like less. If you list the contents of a directory with a large number of files and directories the output will scroll past quickly and you won’t be able to read it all. Pager programs, like less and more will show a screen worth of information and stop, allowing you to slowly scroll through the content as you need. Try it with the /usr/bin directory.

$ ls -lA /usr/bin | less

You will see a full screen out output. Pressing <Space> will advance the output to the next page. Pressing <Return> will advance the output a single line. At the bottom of the screen is a colon prompt. You can enter additional commands at this prompt, including searching for patterns or jumping to specific locations. Press the h key to see help. Press the q key to quit less.

Job Control

When you run a command in the shell the command will run until it completes and then you will see another shell prompt, at which point you can run another command. Some commands run for a long time though, or run until the user exits them. Sometimes you will need to get back to a shell prompt while a command is running. This is where the shells job control features come in.

Suppose you are looking at the paged output from the ls example above. The less command takes over the screen and runs until you exit it. If you need to get back to the shell prompt, perhaps you need to see the man page for one of the commands, you can enter <C-z>. That tells the current foreground process to stop, or suspend itself, and you will be returned to the shell prompt. You will see some information about the process and a job number.

$ ls -lA /usr/bin | less
  [1]  + 16075 done       ls -lA /usr/bin |
         16076 suspended  less

The number in square brackets, [1], is the job number. You can see that it split the two commands between the pipe operator onto separate lines. The ls command is done, and the less command is suspended. Below that output is the shell prompt. You can now use the shell to run any other commands you need to.

To return to the suspended ls command you use the fg (foreground) command along with the job number preceded by a percent sign.

$ fg %1

That will restore the suspended less command. You can continue to page through the information and quit the command when you are done.

If you have suspended a number of commands and can’t remember the job numbers you can get a list of all suspended processes with the jobs command.

It is also possible to run a command and automatically put it into the background. It just runs behind the scenes and allows you to still enter other commands at the shell prompt. In order to start a command in the background you add an ampersand, &, at the end of the command line. You can try it with the top command.

$ top &
  [1] 16634
  [1]  + 16634 suspended (tty output)  top

The top command shows the memory usage, CPU usage and processes running on you machine. Adding the & at the end of the command line automatically suspended the command. To see its output you would need to use the fg command as discussed above. Once the top program output is displayed you can use q to quit it. Running a command in the background like this is typically done when starting long running GUI programs. You can start up a GUI program, such as a browser, and still have access to your shell prompt while the program is running.

Completion

One of the best features of Z Shell is its command line completion capabilities. It goes far beyond simply filling in directory and file names. I’ll start with those first, however, just to get the ball rolling.

Directory and File Names

Z Shell completion for directories and file names is more than simply saving you some typing. It does save a lot of typing, but it can also be used to navigate and find your way through a directory hierarchy that you don’t know. Lets say you are looking for a directory that you know is somewhere in the /usr directory or one of its sub-directories.

$ ls -al /u<Tab>

That will complete the /usr directory, now your command line will look like:

$ ls -al /usr/

Press <Tab> again and you will see a list of files and directories inside the /usr directory.

$ ls -al /usr/<Tab>
  bin/   lib/   libexec/   local/   sbin/   share/   standalone/

As you can see all of the options shown are sub-directories. At this point if you press <Tab> again the bin/ option will be highlighted and the command line will be updated to ls -al /usr/bin/. Continuing to press <Tab> will cycle through all of the options, filling in the selected option on the command line. If you press <Tab> while the standalone/ directory is highlighted it will return back to the beginning of the list and highlight bin/. Pressing <Shift><Tab> will cycle through the list of options in the reverse order. If you are tabbing quickly and pass the option you want, you can simply <Shift><Tab> back to the option you passed rather than cycling through the entire list again.

Now that you see the directories within the /usr directory you are pretty sure the file you are looking for is in local/. You <Tab> forward through the options until local/ is highlighted and now the command line is:

$ ls -al /usr/local/
  bin/   lib/   libexec/   local/   sbin/   share/   standalone/

The problem is, if you press <Tab> again it is going to highlight the next option in the completion list. How do you make it show the list of files and directories in /usr/local/? With local/ highlighted in the completion list press the <Return> key. This will not run the command yet, it will simply confirm that you want the local/ option to be used. Now the completion list will be gone and you can <Tab> again to see what is in the /usr/local/ directory. .command}

You can continue this process until you find the file or directory you are looking for.

Name Collisions

Another way Completion can help you out is with similarly named files or directories. Lets say you have two files in your current directory named this_is_file_one.txt and this_is_file_two.txt. A bit of a contrived example, sure, but you will run into similar file naming patterns all over the place. Log files, for example, tend to have names like system.log.0.gz, system.log.1.gz and so on. Now assume you want to copy one of the files somewhere.

$ cp th<Tab>

Completion will fill in as much as it can and stop. After pressing <Tab> above the command line will be:

$ cp this_is_file_

If you press <Tab> again at this point you will see the possible matches.

$ cp this_is_file_<Tab>
  this_is_file_one.txt   this_is_file_two.txt

At this point you can proceed in one of two ways. You can type additional characters so the shell will know which file you mean then press <Tab> again to continue the completion process.

$ cp this_is_file_t<Tab>

Or you can just press <Tab> again to cycle through the list of matches and highlight the desired item in the list like we did with the directories above. Once you have the correct file highlighted press <Return> to select it.

Command Arguments

Another form of completion that Z Shell provides is command argument completion. Completion can fill in a partially typed argument name. Suppose you want to see the version of git you have installed.

$ git --ve<Tab>

Will fill in the rest of the argument.

$ git --version

If you have a hard time remembering rarely used command arguments type a dash character then press <Tab>. Z Shell will show you a list of arguments and descriptions of what the argument does.

$ ls -<Tab>

Will show you all of the available arguments for the ls command.

$ ls -
  -1 -- single column output
  -A -- list all except . and ..
  -B -- print octal escapes for control characters
  ...

Use <Tab> and <Shift><Tab> to move through the list of options and <Return> to select an option. You can then type another dash character to show the argument list again. You will notice that the argument you already selected is no longer in the list.

There is much more that can be done with Completion, far too much to fit into this book. It you are typing out a command line and get stuck try pressing <Tab>. It will do something useful most of the time. If you are interested in finding out more I suggest looking at A User’s Guide to the Z-Shell, and Chapter 6: Completion, old and new in particular.

Command Line Editing

Most of the work done on the command line is simply typing out and running commands. Many of those commands are short and easy to type. Some are much longer, involving long file or directory paths and many command arguments. Sometimes you might forget a command argument or option, or mis-type part of a file name. This is where Line Editing comes into play. The Z Shell line editing features will allow you to slice and dice command lines, fix typos and re-run previous commands.

Just The Basics

You can break a long command line into multiple lines by entering a backslash  character then pressing <Return>. You can then continue on with the command on the next line.

$ cp ~/very_long_directory_name_containing_files/source.txt \<Return>
  > ~/a_different_directory_name/destination.txt<Return>

Z Shell notifies you that the command is not finished by showing the > character on the continuation line. Pressing <Return> at the end of the line will run the command as usual.

If you have not closed a single or double quote when pressing return Z Shell will notify you and give you a chance to close the quoted text.

$ echo "This is a long
  dquote> string spanning two lines"<Return>

You can delete characters behind the cursor with the <Backspace> key and delete characters in front of the cursor with the <Delete> key (above the arrow keys).

The left and right arrow keys will move the cursor backwards or forwards on the command line respectively. And the up and down arrow keys will move you backwards or forwards through the command history.

Doing Better

I have already talked about the benefits of touch typing and keeping your hands on the home row. Much of the speed improvements you will gain by reading this book will be about keeping your hands on the home row. Guess where the arrow keys aren’t.

Fortunately there are other, faster ways to move your cursor around on the command line and access the command history than using the arrow keys. Before I get to the specifics you have to make a choice.

Pick Your Poison

Z Shell offers two options for command line editing. Emacs mode and Vi mode. Emacs mode is set by default. Emacs mode is probably easier to pick up if you don’t already have some experience with Vim. Its key combinations are fairly standard modifier key plus character sequences to perform an action. Vi mode works in modes just like the Vim and Neovim editors. When in Insert mode you can type out your command as normal. Press the <Esc> key to exit Insert mode and normal keys allow you to move the cursor or go to the start of the line, etc.

I will discuss the basics of both modes so it isn’t terribly important to pick the ‘right’ option now. Later in the book I will be covering Vim, so if you want to get a bit of a head start you could try out Vi mode now. If you don’t plan on spending much time with Vim you might want to just stick with Emacs mode.

Emacs mode

Emacs mode is the default line editing mode in Z Shell. It uses <Ctrl> and <Esc> sequences to move the cursor around the command line. The basic cursor movement commands are:

Keystroke Action
<Backspace> Delete the previous character
<Delete> Delete the next character
<C-d> Delete the next character
<C-b> Move the cursor backward one character
<C-f> Move the cursor forward one character
<C-v> Insert next character literally

The <C-v> command requires a little explanation. It allows you to enter a character that would otherwise perform some action in the shell. For example, if you would like to enter a <Tab> character in the command line you would type <C-v><Tab>. Otherwise the <Tab> would trigger completion.

Now on to larger movements.

Keystroke Action
<Esc>b Move the cursor backward one word
<Esc>f Move the cursor forward one word
<C-a> Move the cursor to the start of the line
<C-e> Move the cursor to the end of the line

Just a reminder about the keystroke notation. For the <Esc> commands above the additional character is outside of the brackets. This signifies that you press the <Esc> key, release it, then press the other specified key.

Working with command history is also covered with line editing commands.

Keystroke Action
<Up> Move back one command in the history list
<C-p> Move back one command in the history list
<Down> Move forward one command in the history list
<C-n> Move forward one command in the history list
<C-r> Search incrementally backwards through command history
<C-s> Search incrementally forwards through command history
<Esc>< Go to the start of command history
<Esc>> Go to the end of command history

The <C-r> search command is particularly helpful. Moving backward one or two commands to find a recent one is very easy, but what if the command you want is thirty or forty commands back in the history? This is where <C-r> comes in. After you press <C-r> you will see a bck-i-search: prompt below the command prompt. Start typing parts of the command you remember. After each letter you type Z Shell will display the found command. When you have found the command you are looking for simply press <Return> to execute it.

Finally some cut and past commands.

Keystroke Action
<Esc><Backspace> Kill the previous word
<Esc>d Kill the next word
<C-k> Kill to the end of the line
<C-u> Kill the entire line
<C-y> Yank the last cut text
<Esc>y Cycle through up to ten recent cuts

I’m using the Unixy terms here to avoid confusion if you look at any documentation. Cut is called Kill and Paste is called Yank. This same naming convention exists in Vim as well as other commands.

When you Kill text it is stored in a Kill Ring. Up to ten items are stored in the Kill Ring. You can access those ten items using the <Esc>y command repeatedly. If you keep pressing <Esc>y you will eventually return to the start of the Kill Ring. The <C-y> command can also be used repeatedly, but it will duplicate the last Killed text rather than cycle through the Kill Ring.

There are some more command line editing commands, but their usefulness is a bit limited. You can read the full documentation for the Z Shell Line Editor (ZLE) at the Z Shell User’s Guide.

Vi Mode

If you are being adventurous and want to use Vi Mode for line editing you will need to make a change to your Z Shell configuration file to turn the mode on. Oh My Zsh sets Emacs Mode line editing in ~/.oh-my-zsh/lib/key-bindings.zsh, so we are actually customizing the Oh My Zsh behavior. We will add the override in our dotfiles project. Open the Z Shell custom patches configuration file and add the following lines.

~/dotfiles/zsh/custom/lib_patches.zsh
  # Use Vi Mode for line editing.
  bindkey -v

Save the file, exit and restart your terminal. You are now in Vi Mode. You probably won’t notice much difference at first. You can type commands just like normal. This is the main difference between Vi Mode line editing and the actual Vim editor. Vi Mode line editing starts out in what is called Insert Mode, whereas in Vim you start out in Normal Mode.

When in Insert Mode your keyboard acts like it normally does. When you press the ‘a’ key an ‘a’ character is echoed to the screen. You type and text is created.

In Normal Mode the same keys you normally type with become commands that move the cursor around, move through the command history and other actions. To exit Insert Mode and access Normal Mode you press the <Esc> key. A short example will probably help to clarify Vi Mode. With Vi Mode enabled enter the following command.

$ cd ~/dotfiles/zsh/custom

You can use <Tab> completion as normal to complete the directory names. This all takes place in Insert Mode. You can type out commands just as you are accustomed to. Press <Return> once the command is entered to execute it and so that it will be entered into your command history. Now press the <Esc> key to exit Insert Mode and enter Normal Mode. Now your regular keys will perform different actions. Press the k key and you will move backwards one line in the command history. The change directory command you just entered will show up at the prompt. The cursor will be on the last character.

$ cd ~/dotfiles/zsh/custom

You can press the k key again to move one more command backwards in the command history. Go ahead and press k a couple times. Now, to move forwards through the command history press the j key. You will move back through the commands towards the change directory command we entered earlier. Keep going forward with j until you get back to that command.

There are a number of ways to get back to Insert Mode. The easiest is to just press the i key. That will put you back into Insert Mode with the insertion point behind where the cursor is. In our example it would end up as follows.

$ cd ~/dotfiles/zsh/custo|m

This isn’t usually what you want. You probably want to add something to the end of the line. Press the <Esc> key to exit Insert Mode and press the $ key to make sure the cursor is positioned at the end of the line. Now press the a key. That puts you into Insert Mode with the cursor positioned after the last character.

$ cd ~/dotfiles/zsh/custom|

Those are the basics of switching between Insert and Normal modes. Press <Esc> when in Insert Mode to go to Normal Mode. Go back to Insert Mode by using one of the editing commands. The i key inserts before the cursor. The a inserts after the cursor.

Some more Normal Mode commands for moving the cursor are as follows.

Keystroke Action
h Move the cursor one character left
j Move forward one command in the history list
k Move backward one command in the history list
l Move the cursor one character right
w Move the cursor one word right
b Move the cursor one word left
0 (Zero, not O) Move the cursor to the start of the line
$ Move the cursor to the end of the line

The h , j , k and l keys are similar to the arrow keys. In Vim j and k move the cursor down or up one line respectively, but in the shell, where there is only one line being acted upon at any given time they were repurposed to cycle through the command history.

Searching through the command history can also be accomplished in Vi Mode.

Keystroke Action
/ Search backward through command history
? Search forward through command history

These actually work backward from how they do in Vim. Normally the / key will start a search forward from where the cursor is and ? will search backward from where the cursor is. I suppose it was switched in Z Shell because you almost never search the command history forward and the / search is very natural for most Vim users.

Keystroke Action
i Insert to the left of the cursor
a Insert to the right of the cursor
I Insert at the start of the line
A Insert at the end of the line

The i and a commands were covered briefly above. Their uppercase counterparts are very handy. Regardless of where the cursor is on the command line, if you want to enter Insert Mode at the beginning of the line use I , or at the end of the line use A .

Keystroke Action
x Delete a character under the cursor
db Delete the previous word
dw Delete the next word
d$ Delete from the cursor to the end of the line
d0 Delete from the cursor to the start of the line

Now we get to some combinations. The x command is a lone command, it just deletes the current character. The other commands all start with a d which in Vim stands for delete. The following character gives the delete command context. It should all make sense because the context characters act the same way they do when used alone. The b command moves backward a word, but paired with d it deletes that word instead. The same holds for the other delete commands.

That should be enough to get you going with Vi Mode line editing. If you already have some Vim experience you should know that most of what you already know will translate to the command line. Feel free to experiment.

Plugins

The final Z Shell feature, or really Oh My Zsh feature, I will be covering is Plugins. Oh My Zsh has many plugins available that add aliases, completion capabilities and other extensions to the shell that make a particular program easier and more enjoyable to work with.

If you take a look at your ~/dotfiles/zsh/.zshrc configuration file, near the bottom, you will see a plugins= line and the git plugin is enabled by default. The actual plugin code is in the ~/.oh-my-zsh/plugins directory. List that directory and you will see all of the available plugins. There are plugins for AWS, Bundler, Cargo, Docker, Emacs, Github, NPM, Rails, the list goes on.

To enable a plugin simply add it to the list of plugins in your ~/dotfiles/zsh/.zshrc file. For example, if you wanted to enable the tmux plugin edit your .zshrc file as shown. (We will be adding this plugin later in the next chapter, so if you would like to take care of the change now feel free to do so. Just don’t forget to commit your changes and push them to your remote host.)

~/dotfiles/zsh/.zshrc
  plugins=(
    git
    tmux
  )

Save the file and restart your terminal. Once the terminal is restarted the new plugin will be available.

You can find a list of plugins and documentation for them on the Oh My Zsh Wiki. Take a look at the Git plugin for a list of the aliases it provides.

Git

I will go over some of the Git plugin functionality here, and cover some other plugins in their respective sections.

You may have noticed already that your command prompt behaves differently when you are inside a Git repository. To see what I mean open your terminal, if it isn’t already, and change directory to your home directory. Your prompt should look like the following. (As long as you used the same theme as I used previously.)

dave@Terminal-Velocity [12:14:11] [~]

It shows your user name, host name, the current time and the current directory. Now change directory into your ~/dotfiles directory. The prompt should now look something like this.

dave@Terminal-Velocity [12:15:52] [~/dotfiles] [master]

It added the Git branch name to the end of the prompt. This isn’t the only trick. If you check out a different branch the new branch will be shown. If there are un-staged changes you will see a red asterisk after the branch name. This provides an easy to notice visual queue to the current Git status.

Aliases

There are fifty or more aliases defined for the Git plugin. One for almost any situation you can think of. These are the ones I use on a regular basis.

Alias Action
gst git status
gaa git add –all
gcb git checkout -b
gc git checkout
gcmsg git commit -m
gfo git fetch origin
ggp git push origin $(current_branch)

Some aliases are standalone. The gst alias, for instance, doesn’t require any additional arguments. Some do require additional arguments. The gcb and gc aliases require the branch name.

$ gcb new-feature

That would translate to git checkout -b new-feature, which would create a new branch named new-feature and immediately check it out.

Summary

In this chapter you installed Z Shell and Oh My Zsh. You modified your system to make Z Shell the default shell used when you start your terminal. You updated your dotfiles project with your Z Shell configuration files and setup a custom file to override existing Oh My Zsh settings. Finally, you learned a bit about how to use Z Shell effectively.

There is so much more to learn about Z Shell and shells in general. I haven’t touched on pattern matching, shell variables, shell scripting, etc. There are many books written just about command shells. I can not do the topic justice in this book. The things I’ve covered, I hope, will help you be a bit more productive with Z Shell, but you should continue to learn about the other features of Z Shell.