Shell and Terminal on Mac

Abbreviated Fundamentals of the Command Line Interface

What’s what

Most users of personal computers today are intimately familiar with the graphical user interface (GUI) where interactions with the computer typically involve using a mouse (and sometimes the keyboard) to click and navigate through windows, icons, menus, and buttons.

Before GUIs became common, computers were often operated through a text-based method called a command-line interface (CLI). And this approach is sill widely used today.

In the “old days,” most computers would boot into the CLI because there was no graphical alternative. Today, every major operating system still includes a way to access the command line through an application called a terminal emulator (or just terminal for short). On macOS, the app is unceremoniously named “Terminal” and can be found in the /Applications/Utilities directory. But you can download other terminal applications, including Alacritty, Ghostty, iTerm2, Kitty, Warp, WezTerm or others.

Every terminal application has a text-based input field called the command line that begins with a prompt (usually > or $). Many users customize their prompts to include information about the username or hostname, current directory, git branch or git status, or even the execution time or exit code of the prior command. Common frameworks that assist with prompt customization include Starship, Oh-my-posh, and [(https://github.com/romkatv/powerlevel10k).

When you type and submit a command, the Shell Command Line Interpreter (or just Shell) parses and interprets the text you have typed, carrying out your instructions and possibly returning information to you in the terminal. This Read-Eval-Print loop (REPL) is common when computer programs allow “live” interaction between the user and computer (as opposed to writing all your commands in a script file and executing the whole file).

The default shell on macOS at the time of writing is zsh (pronounced the “Z shell”) which replaced bash (the “Bourne Again SHell”) as the macOS default starting with the release of macOS Catalina in 2019. bash, as evidenced by its name, was an evolution of another shell called the Bourne shell (with the short name sh). To round out the list, the only other popular shells today are fish (the “friendly” shell) and nushell (which emphasizes structured tabular text when possible).

Shell Basics

To determine what shell you are using, open your terminal application and type echo $SHELL at the command prompt. If the response is /bin/zsh then its zsh.

Most commands tend to have the following structure

[command] [options] [arguments]

You’ll need help. Often. The commands run-help and man (short for manual) following by a command name will bring up the command’s documentation using the terminal pager less. For navigation, arrow keys move one line at a time, page up/down and the spacebar move one page at a time, and q exits. For more succinct documentation, visit https://tldr.sh/ in a browser, which can be used interactively or downloaded.

Moving around

Navigating the hierarchy of directories and performing simple operations on files are fundamental to working from the command line. Note that many of the following tools take the verbose -v option, which prints a confirmation message in the terminal and is useful to “see” what these commands do, especially while learning them.

  • pwd prints the current (working) directory
  • cd changes the working directory
  • ls list the contents of a directory
  • .. denote the parent directory
  • . denotes the current directory (useful, eg, when copying)

Creating, moving, copying, and deleting files and directories are common operations:

  • touch updates the access and modification times of files, but also creates a file if it does not yet exist
  • mv move (or rename) one or more files or directories
  • cp copy one or more files or directories
  • rm remove (ie, permanently delete) file(s). A good alternative is to “trash” them with mv file_name ~/.Trash
  • mkdir create a new directory
  • rmdir removes directories (use with caution!) A good alternative is to “trash” them by installing the trash command with brew install trash and then running trash dir_name

It is often useful to use wildcards to return a “group” of files. Common wildcards include:

  • * denotes zero or more characters
  • ? a single character
  • [abc] a list of permited characters
  • [a-z] a set of characters

Editing Text at the Command Line

When working in a terminal, you’ll often need to edit what you’ve typed, e.g., to fix a typo, move back a few words, delete a chunk of text, or recall and modify a previous command. While you can do this with arrow keys and backspace, you’ll become dramatically faster once you learn a handful of editing shortcuts.

Most shells (including zsh and bash) provide “line editing,” meaning you can edit the current command before you run it. These editing features come from a library called readline (used by bash) or a similar system called zle (the Zsh Line Editor). Both support two classic styles of keybindings: Emacs-style editing (the default in most shells) and Vi-style editing (popular among Vim users). These styles trace back to two influential text editors from the 1970s: Emacs and vi, which shaped how programmers interact with text even to this day.

Emacs-style keybindings (default)

In Emacs mode, you use Control key combinations to move and edit efficiently. These are widely supported across shells and many terminal programs. Here are the most useful ones to memorize. Note that on the macOS Terminal, “Alt” is often the Option key.

  • Ctrl-A jump to the beginning of the line
  • Ctrl-E jump to the end of the line
  • Ctrl-B move back one letter
  • Ctrl-F move forward one letter
  • Alt-B or Esc-B move back one word
  • Alt-F or Esc-F move forward one word
  • Ctrl-L clear the screen
  • Ctrl-U delete from cursor to the beginning of the line (“undo this whole front part”)
  • Ctrl-K delete from cursor to the end of the line (“kill to end”)
  • Ctrl-W delete the word behind the cursor
  • Ctrl-P previous command (same as up arrow)
  • Ctrl-N next command (same as down arrow)
  • Ctrl-R reverse search through history (Start typing part of a previous command and press Ctrl-R repeatedly to cycle matches.)

Vi-style keybindings (modal editing)

Vi mode is “modal,” meaning you switch between modes: “Insert” mode where you type normally, or “Normal” mode where keys perform editing commands. This feels strange at first, but you can become extremely fast once it clicks. Vi’s reputation is that you can move mountains in a few keystrokes, but with the cost that even simple operations also require a few keystrokes.

To change from the default emacs-style key binding to vi-style for the current shell session, run bindkey -v or set -o vi. To enable vi-style keybinding every time you start an interactive shell, add one of those commands to your shell configuration file (typically ~/.zshrc for zsh or ~/.bashrc for bash). Then either restart your terminal or reload the configuration with source ~/.zshrc.

Once enabled, when you’re typing a command you’re usually in insert mode. Press:

  • Esc switch to normal mode
  • i return to insert mode at the cursor
  • a return to insert mode after the cursor

In normal mode, a typical operation folows the pattern count-operator-motion/object. For example, 3dw deletes forward three words. Omitting the count is typica, for example ci( changes the text inside the current set of parentheses. Common commands include:

Movement of cursor

  • h/l left/right
  • 0 beginning of line
  • $ end of line
  • w forward one word
  • b back one word

Editing (normal mode)

  • x delete character under cursor
  • dw delete a word
  • dd delete the entire line
  • cw change a word (delete word and enter insert mode)
  • D delete from cursor to end of line

History search

  • j/k up/down
  • /text search backward in history for “text”
  • n repeat the last search

A common workflow looks like: type a command; hit Esc; quickly edit with w, b, dw, etc.; press i to insert again; run.

Combining tools

The Unix philosophy is that command line tools are designed to do one thing and to do it well. Therefore, complex operations are created through a composition of tools. This is made possible by managing the communication streams of these tools. Each has 3 standard communication streams: standard input (stdin), standard output (stdout), and standard error (stderr). Often, the keyboard will not be the source of input, but rather it will be from the output generated by other tools and contents of files.

  • < takes input from a file (eg, < file.txt wc)
  • > directs output to a file and overwrites (eg, ls > dir_contents.txt)
  • >> directs output to a file and appends
  • | pipes output from one command to input of another (eg, ls | less)

Common tools for inspecting files, or for gathering and summarizing their contents include:

  • cat for concatenating files; can be used to print file contents in the terminal for short files
  • find for recursively searching a directory hierarchy returning names of files and/or directories
  • wc for counting characters, words, and lines
  • head for obtaining the first few lines
  • tail for obtaining the last few lines
  • sort for sorting the contents of a file
  • uniq for removing adjacent duplicates

More-advanced tools that deserve their own section and explanation

  • grep for matching a Regular Expression pattern in one or more files, see here
  • sed a stream editor for modifying data
  • awk a data processing language named after its creators

Sections to add

  • File permissions (read/write/execute)
  • A whole section on each of grep/sed/awk
  • Connecting: ssh/ftp/curl
  • Environment variables (incl. customizing the prompt)
  • Writing programs (incl. if/for/while)