Tools Class Doesn't Teach You

The command line, SSH, Vim, tmux, and Git: the everyday tools most courses assume you already know.

Programming courses teach you a language. They rarely teach you the tools professional developers use around that language every day: the shell, remote access, a terminal editor, window management, version control. Those tools are what make the difference between fighting your computer and having it work for you.

Everything here is language-agnostic and free. Work through it in order if you’re starting out, or skip to whatever you need.

On this page

Getting a Linux shell

Nearly everything below assumes you have a Linux shell in front of you. You have several ways to get one, and none of them require giving up the operating system you already use.

Run Linux in a virtual machine. The safest option, and the easiest to undo. Your existing OS keeps running, and Linux runs in a window on top of it. If you break something, you delete the VM and make another one. See Containers & Virtual Machines for how to set this up.

Windows Subsystem for Linux (WSL). If you’re on Windows, this is the lowest friction path: a real Linux environment, no VM to manage, and it shares your filesystem. Open PowerShell as administrator and run wsl --install. Microsoft’s install guide covers the details. Nearly everything on this page works in WSL.

Dual boot. Install Linux alongside Windows and pick one at startup. You get full hardware performance and a real Linux desktop, at the cost of rebooting to switch and a partitioning step where mistakes are expensive. Back up first.

Install Linux as your only OS. What I run. Best experience if you’re ready for it, and the most disruptive if you’re not.

On a Mac? You already have a Unix shell. Open Terminal. It behaves very much like a Linux shell.

Mac users The primary difference is that the apt package manager is not an option. Use Homebrew instead, or do a web search for the equivalent command.

Want a thorough introduction? The Linux Foundation offers a free course: https://training.linuxfoundation.org/training/introduction-to-linux/

The Linux shell

Why this is worth your time

When you use a computer, you are probably looking at Windows or macOS. When your code runs, it is almost certainly running on Linux.

That is not an exaggeration. W3Techs measures the server operating system of the sites it surveys at roughly 92% Unix-like, overwhelmingly Linux, against about 8% Windows. Every major cloud provider defaults to Linux images. Android is built on the Linux kernel. Docker containers are Linux. Most continuous integration runners are Linux. Your home router, your car’s infotainment system, and the machines in the campus lab are running it too.

Here is what that means for you in practice. The moment you do anything beyond running code on your own laptop, you end up at a Linux prompt: deploying a web application, connecting to a lab machine, working out why a container will not start, or showing up on the first day of a job.

To be fair, you can avoid the shell for a surprisingly long time. AWS, Google Cloud, and Azure all have web consoles that will do most of what a terminal can do, and they are genuinely useful when you are learning what the pieces are. But clicking through a console is slow, it is hard to describe to a co-worker, and it is nearly impossible to repeat exactly. The shell is faster once you know it, and far more importantly, everything you do in it can be saved in a script, put in a repo, reviewed, and run again next month by someone else. That is the difference between doing a task and automating it, and automation turns out to be most of the job.

There is also a point past which the console runs out. Once you connect to the server itself, there is usually no graphical desktop installed on it, and you work with what the shell gives you.

The shell also earns its keep on your own machine:

One more argument, and to me it is the strongest. Frameworks and languages come and go. Bash has been in use since 1989, and the commands you learn this week will still work in twenty years, on hardware that does not exist yet. Very few things you can study give you that kind of return.

Bash also consistently ranks among the most widely used languages in the Stack Overflow developer survey, which is worth noticing given that nobody sets out to “learn Bash.”

Terminal emulators and customizing the terminal

One of the properties of a typical Linux distribution is that you can customize almost anything, and the terminal is no different. You can change the look and behavior of the terminal. This link shows instructions on how to customize the Linux terminal.

If the basic terminal that comes with your distribution is not adequate for your needs, you are not stuck with it. You can install a different terminal emulator and use it instead. Here is a list of terminal emulators. Before I switched to a tiling window manager, I liked using the Terminator terminal emulator.

SSH and remote machines

Secure Shell (SSH) is a method to securely remote login from one computer to another. All communications between the local and remote machine are encrypted. The most common implementation found on systems comes from the open source OpenSSH.

This is how you reach essentially every machine that is not sitting in front of you: a cloud server, a lab machine on campus, a Raspberry Pi in your closet.

A worked example

The basic form of the command is a username, an @, and the address of the machine you want to reach:

ssh dpouliot@server.example.edu

Read that as “log in to server.example.edu as the user dpouliot.” If your username on the remote machine is the same as the one on your own machine, you can leave off the dpouliot@ part and SSH will assume it.

The first time you connect to a machine, you will see something like this:

The authenticity of host 'server.example.edu (203.0.113.10)' can't be established.
ED25519 key fingerprint is SHA256:V1XoP0kCyEuAcTL2sBSNtpEgOMvhaRnjBNiCbEjPZ2s.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

This is worth understanding rather than reflexively typing yes. Every SSH server has its own key, and your machine has never seen this one before, so it is asking whether you trust it. Answering yes stores the key in ~/.ssh/known_hosts. From then on, your machine checks that the server presents the same key every time. If it ever changes, SSH refuses to connect and warns you loudly, because a changed key can mean someone is impersonating the server. In practice it usually means the server was rebuilt, but the whole point is that you get told rather than quietly connected to the wrong machine.

After that you are asked for a password, and then you are simply at a shell prompt on the other machine. Everything you type runs there, not on your laptop. When you are finished:

logout

You can also run a single command on the remote machine without staying logged in, which is the piece people usually discover late:

ssh dpouliot@server.example.edu 'df -h'

That connects, runs df -h to check disk space, prints the result on your screen, and disconnects. Commands like that are what shell scripts are made of.

Ports

Sometimes a server does not listen on the standard SSH port, which is 22. The -p flag says which port to use instead:

ssh -p 2220 bandit0@bandit.labs.overthewire.org

That is exactly the command you will use for the Bandit wargame later on this page. Now you know what the -p 2220 is doing.

Keys instead of passwords

Typing a password for every connection gets old, and passwords are the weaker option. The better approach is a key pair: a private key that stays on your machine and a public key that you install on the server. Generate one with:

ssh-keygen -t ed25519

Press Enter to accept the default location (~/.ssh/id_ed25519), and choose a passphrase when it asks. That produces two files: id_ed25519, the private key, and id_ed25519.pub, the public key. Then copy the public half to the server:

ssh-copy-id dpouliot@server.example.edu

It will ask for your password one last time. After that, ssh dpouliot@server.example.edu logs you in without one.

There is a lot more to say about SSH keys than fits here. For more information:

You can also read the manual page without leaving your terminal, which is the habit worth building:

man ssh-keygen

Never share the private key The .pub file is the one you hand out, and it is safe to publish. The file without the extension is your private key. It never leaves your machine, never gets emailed, and never goes in a Git repo. Anyone who obtains it can log in as you.

Copying files

scp copies files over the same connection, using the same user@host form with a colon before the remote path:

scp report.pdf dpouliot@server.example.edu:/home/dpouliot/
scp dpouliot@server.example.edu:/home/dpouliot/results.csv .

The first line sends a file up. The second brings one back, with . meaning “into the directory I am in right now.” For anything large or repeated, look at rsync, which transfers only what changed.

Saving your connections

Once you connect to the same machines regularly, put them in ~/.ssh/config:

Host lab
    HostName server.example.edu
    User dpouliot
    Port 22

Now ssh lab does the whole thing. scp file lab: works too. It is a small thing that removes a surprising amount of daily friction.

Try it You do not need a server of your own to practice. Generate a key pair with ssh-keygen, look at both files with cat, and notice that the public one is a single line you could safely paste anywhere. Then run ssh -p 2220 bandit0@bandit.labs.overthewire.org with the password bandit0 and read the host key prompt carefully before answering it.

Mosh

Mosh is a replacement for SSH. It was created to deal with poor connections between the local and remote machine. Visit the site and decide if you want to install Mosh on your machine.

A quick tour of the shell

Open up a Linux shell and follow along. Type the commands yourself. Reading them does not build the muscle memory.

To see where we are in the file system, let’s use the pwd command (print working directory):

david@david-HP-Z820-Workstation:~$ pwd
/home/david

This is the home directory for this user.

To see the files and directories located in the home directory, type in the ls command. This lists the files and directories, but may not include all of them. To see the hidden files, add the a flag:

ls -a

Now we will see even hidden files and folders.

Want to see what other flags you can use with ls? Use the man (manual) command:

man ls

You can also install programs from the command line using the package managers. For example, let’s install the cowsay program:

sudo apt install cowsay

The sudo command tells the OS to run this as the root user. apt install installs the program using the aptitude package manager.

Let’s try running the cowsay program:

cowsay hello

You might get an error message like this:

$ cowsay hello
Command 'cowsay' is available in '/usr/games/cowsay'
The command could not be located because '/usr/games' is not included in the PATH environment variable.
cowsay: command not found

If you received this error message, this happened because the operating system does not know where the cowsay program is installed. (You might not have gotten this error.) Let’s find its installation location using the find command:

find / -name 'cowsay'

In the command above, find is the command, / is an argument telling which directory to start the search. / is the root directory. -name 'cowsay' searches for the name cowsay.

After running the find command, we see that the cowsay program is installed at /usr/games/cowsay. Now we can run the program using its full path:

$ /usr/games/cowsay hello
 _______
< hello >
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

This can get tedious using the full path. Fortunately Linux systems have a PATH variable. The PATH variable is an environment variable that contains an ordered list of paths that Linux will search for executables when running a command. Using these paths means that we do not have to specify an absolute path when running a command.

To see what is in the current PATH variable, let’s use the echo command:

echo $PATH

The $ is bash syntax for a variable. The echo command displays to the terminal screen. So this command is telling the terminal to print what is in the PATH variable to the screen.

To add the cowsay program to our path, we can simply update the variable:

PATH="$PATH:/usr/games/"

This command takes the current PATH variable and appends :/usr/games/ to it. Now if we check the PATH variable again, we can see :/usr/games is added to it.

However this will only last as long as the current shell session. When you close your terminal, it will revert to the previous version of the PATH variable. To make this change persistent, we need to modify the .bashrc file. To do this, we first go to our home directory:

cd ~

Then let’s make sure the .bashrc file is here:

ls -a

Let’s open a text editor inside our shell to edit this file:

nano .bashrc

Inside the nano text editor, add this line to the bottom of the file: export PATH="$PATH:/usr/games"

Save the file using Ctrl+O, then exit with Ctrl+X. To get the terminal to read the updated file, type in:

source ~/.bashrc

This reloads this file and updates the PATH variable. Now we don’t need to use the full path, and our change is persistent if we close the terminal and open a new one.

Try it

Get the cow to think your name:

 _______________
< David Pouliot >
 ---------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Then read the man page for cowsay and work out how to:

  1. Change the cow’s eyes to any characters you want.
  2. Use a different animal. There are many to choose from.
 _______________
< David Pouliot >
 ---------------
 \     /\  ___  /\
  \   // \/   \/ \\
     ((    O O    ))
      \\ /     \ //
       \/  | |  \/
        |  | |  |
        |  | |  |
        |   o   |
        | |   | |
        |m|   |m|

Reading a man page to answer your own question is the actual skill here. The cow is just an excuse to practice it.

Practice: Bandit on OverTheWire

The Bandit game at OverTheWire is a capture-the-flag game that gives a good introduction to the Linux shell. In each level, you have to beat the level to find the password to the next level. To start, visit the page to read the instructions, then in a terminal, ssh into level 0:

ssh -p 2220 bandit0@bandit.labs.overthewire.org

Note the use of the -p 2220. This is telling the ssh program to use port 2220 instead of the standard SSH port.

Use password bandit0 for the first level. For all the other levels, it is up to you to find the password. To exit from an ssh connection, use the logout command.

Try it Solve the levels up through Bandit Level 11 → Level 12. When you’re logged into bandit12, your terminal should look something like this:

Terminal logged into bandit12

Bandit is also the best first step if you’re interested in security work. See Getting Started in Cybersecurity.

Some other useful shell tutorials:

Shell tools and scripting

Once you’re comfortable running commands, the next step is stringing them together into scripts so the computer repeats the work instead of you.

Read this introduction to shell tools and scripting from MIT’s Missing Semester.

Try it Do exercises 1, 2, and 3 at the bottom of that page. They cover shell scripting, finding files, and searching text, three things you will use constantly.

Vim

Read this tutorial on using Vim.

Vimtutor is a built-in tool to teach you how to use Vim. From a Linux or Mac command line (if you have a Windows computer, use WSL or a VM) start up vimtutor:

vimtutor

Try it Complete all of the steps in vimtutor. To save your work, press Esc, then type :w myVimtutor.txt to save the file. Keep it. It is a useful reference for the commands you just learned.

Why bother with Vim? Nothing requires you to use Vim. You can write code in any editor you wish. So why learn it? Becoming competent with Vim can make you more productive for the rest of your career.

It is also a good skill to be at least familiar with a terminal text editor. Sometimes you have to SSH into a machine and edit a text file in the terminal, and no graphical editor is available.

If you do decide to make the switch to Vim, understand that it will take a little time to get comfortable with it. For the first two weeks I used Vim, everything seemed to take longer and I was tempted to switch back to my IDE. After two weeks, I had enough of the useful commands memorized and never looked back. Your mileage will vary with this.

Customizing Vim

One of the features that makes Vim so powerful and productive is the ability to customize it. Without these customizations, I don’t think Vim would be nearly as productive. Customization involves editing a configuration file (.vimrc) or installing a plugin.

vimrc

There are two versions of this file. One is the system-wide configuration file; the location of this depends on which OS distribution you are using. There is also a file associated with the user located at ~/.vimrc.

To create or edit this file, open it in Vim:

vim ~/.vimrc

Comments in the .vimrc file start with the " character.

My favorite addition to the vimrc file is mapping the jk keys to the escape key. It is so much faster to hit both of those keys to change modes in Vim than to reach for the escape key:

inoremap jk <Esc>

I like to see the line numbers, so I add:

set number

Enable syntax highlighting:

syntax on

You can change the default color scheme (change it to whatever you prefer):

colorscheme elflord

Set the tab size:

set tabstop=4

Replace tabs with whitespace. I love this for Python coding:

set expandtab

Highlight lines longer than 80 characters:

let w:m80=matchadd('ErrorMsg', '\%>80v.\+', -1)
set textwidth=80

If you do a web search for Vim customizations, you will find more sources than you have time to read. A lot of professional developers post their .vimrc file on GitHub also. Feel free to browse those. But don’t simply copy their file. Make sure you understand what each line does and decide if you want that as part of your .vimrc file.

Tabs and split screen

Vim has tabs and split screen features so you can work with multiple files in one terminal.

Split screen

You can split the screen either horizontally or vertically. There are keyboard shortcuts for this:

I find that hitting Ctrl+w is not very comfortable. I prefer instead, while in command mode, to type :winc. Thus to split the screen, use :winc v. Another option is to remap these shortcuts in your .vimrc file.

Note that splitting the screen shows the same file in all of the screen splits. Why would you want to do this? Once your file becomes longer than what will fit on a screen, it is often useful to view one part of the file while working on another.

For more tips on using splits, see https://vim.fandom.com/wiki/Switch_between_Vim_window_splits_easily

Tabs

Vim has a tab feature, very similar to tabs in a browser or text editor.

To open a tab, use the command :tabnew. This will open a new empty buffer. To open a tab with a file, use :tabnew filename.

This page https://vim.fandom.com/wiki/Using_tab_pages lists many options for tab navigation and an option for customizing the shortcuts.

Other tips

The Vim Fandom site has more tips than you have time to read. It has a nice search feature, so if there is anything you want Vim to do, you might find it here. One example: when programming C and C++, you often find yourself switching between the source code (.c or .cpp) file and the header file. They have many different solutions to automate this.

Plugins

To use Vim plugins, it is useful to have a plugin manager. I use Vundle, but others use vim-plug. To install Vundle:

git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim

Then add the following to your .vimrc file:

set nocompatible

" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

" let Vundle manage Vundle, required
Plugin 'VundleVim/Vundle.vim'

" put all of your plugins here
Plugin 'Syntastic'

" All of your Plugins must be added before the following line
call vundle#end()            " required
filetype plugin indent on    " required

After you have added the plugins to your .vimrc file, to install them open Vim and run:

:PluginInstall

There are plugins like syntax highlighters (now Vim is starting to have IDE-like features):

Plugin 'Syntastic'

NERDTree is a file system explorer for Vim that is very popular:

Plugin 'scrooloose/nerdtree'

If you like the autocomplete feature that many IDEs have, there is a Vim plugin for that:

Plugin 'ycm-core/YouCompleteMe'

There are numerous plugins to help you with specific tasks, like web programming and plugins for specific programming languages. A web search will reveal many.

Try it Build your own .vimrc. Start with the settings above, keep the ones you like, and add one thing you found yourself. The point is that every line in it is there because you chose it.

Automate it with a shell script

To use your favorite Vim configuration on any system, it is helpful to create a Git repo with your .vimrc file and a script to install it. Here is an example of my install script:

#!/bin/bash

ln -s `pwd`/vimrc $HOME/.vimrc
mkdir -p $HOME/.vim/bundle
cd $HOME/.vim/bundle
git clone https://github.com/VundleVim/Vundle.vim.git
vim +PluginInstall +qall

This script creates a symbolic link between the ~/.vimrc file and the vimrc file in the repo directory. Then it installs Vundle and runs the PluginInstall command in Vim.

The payoff Once you have a Git repo with your .vimrc file and your shell script, installing your Vim setup on a brand new computer is:

tmux and tiling window managers

It is often very useful to have multiple terminal windows open when coding. You might have one or more open with the source code and another to run commands (compile, make, run the program, run tests, and so on). Some terminal emulators will handle this for you. A better way is to use either tmux or a tiling window manager.

tmux

tmux is a terminal multiplexer. There are several guides for installing and using tmux:

Optional, like Vim Nothing requires you to use tmux. It is another tool that can make you more efficient if you take the time to learn it.

If you do decide to customize tmux, add the .tmux.conf file to your repo and modify your installation script:

#!/bin/bash

ln -s `pwd`/tmux.conf $HOME/.tmux.conf
ln -s `pwd`/vimrc $HOME/.vimrc
mkdir -p $HOME/.vim/bundle
cd $HOME/.vim/bundle
git clone https://github.com/VundleVim/Vundle.vim.git
vim +PluginInstall +qall

Now you have one script to configure Vim and tmux on any new machine you work on.

Tiling window managers

A tiling window manager organizes windows in a similar fashion to tmux, but it does this for all the windows, not just terminal windows. For Linux distributions, you will have many choices of different tiling window managers. For Mac and Windows, the choices are more limited, but there are options.

I have used the i3 window manager for years and love it. This manager is easy to customize (just edit a config text file), and there is plenty of online support to help. The biggest downside at first was not having the normal menus to open programs (I had to get used to dmenu), and many GNOME desktop programs are not available when in i3 mode. Also, some programs like Zoom sometimes behave oddly using i3.

There is a Linux distribution called Regolith Linux that comes with i3 pre-installed and pre-configured. One of its best features is that it provides access to all the GNOME programs that weren’t available in the basic i3 setup.

There are numerous tiling window managers to choose from:

And many more. Each has different features and keyboard shortcuts.

Which is better, tmux or a tiling window manager? It depends. tmux is available on almost any system, but only manages shell windows. A tiling window manager requires installation permissions, sometimes as root, so on a lab or work machine you may not have the option. Which is better (if either) depends on your situation.

Git

Git is an open source version control system. It is the most common version control system used in industry and academics. Learning Git is a critical skill for your career in computer science.

Git servers

Git itself is simply software. When we think about Git, though, we often think about specific websites, like GitHub. You can run your own Git server instead of using these websites. There are numerous websites that host Git for you. The three most popular are:

Installing Git on your local machine

To use any Git server, you also need the Git software installed on your local machine. These instructions will show you how to install Git on your personal computer.

Learning and using Git

The Git commands are issued in a terminal command line interface, though Windows does have a GUI version and the Git commands are integrated into various IDEs. For example, here are the instructions for Git integration with VS Code.

Learn the commands first Even if your code editor has Git integrations, I strongly suggest that you use the terminal command line at first, so you learn the commands. When the GUI does something confusing (and it will), knowing what it is actually running is what gets you unstuck.

Basic Git commands

The following are the basic commands commonly used with Git repos:

Git tutorials

Several sites already have good tutorials, so I will simply provide links:

Try it This tutorial from GitHub is a good hands-on introduction to most of the Git commands you need to learn: https://github.com/skills/introduction-to-github

Going further: continuous integration

Slightly more advanced usage of a Git server involves continuous integration: https://github.com/skills/continuous-integration

Continuous integration usually involves automation. For example, a common automated task is running tests. These can be unit tests or other tests that you create. Any time code is pushed, the CI will automatically run them.


Credit Portions of this page are adapted from MIT’s The Missing Semester of Your CS Education, used and shared under CC BY-NC-SA.