Unix Command Line Crash Course

Learn about Unix shell, commands, and environment variables.

Hopefully, you have seen a command line interface, CLI, before. You can find them on nearly every system: Windows, Linux, macOS, and even my old Amiga had a CLI. Each of them work slightly different, but there is one standard which has come to dominate, which is based on the Unix operating system. Both Linux and macOS derive from the Unix operating system. When interacting with a CLI on Linux or Mac, you will use mostly the same Unix commands. Unix command line interfaces have also become popular on Windows the last few years through Windows Subsystem for Linux (WSL).

You interact with the Unix command line through a program, usually called terminal or console. Here is an example of using iTerm2 on my Mac. In this particular example, I am issuing commands to look the contents of the Godot directory, where I have stored some games I have developed.

Listing contents of Godot directory using Terminal application

If you use a graphical file manager such as Finder, this Godot directory would have looked as follows:

Finder file manager showing Godot directory

Why should you learn how to use a Unix command line interface? Isn’t it much easier to just click around in a file manager? Indeed, graphical user interfaces tend to be easier to explore and learn. But text-based interfaces have numerous advantages which are hard to replicate in graphical user interfaces:

  1. They often prove a lot more power and flexibility.
  2. Complex graphical interfaces are harder to learn than complex text-based interfaces.
  3. Text-based interfaces naturally lend themselves to automation, which can be a big time saver.

The second point requires some justification. It is true that it is easier to understand a graphical user interface, but only up to a point. Large and complex graphical applications can be hard to learn. What to do is not obvious. Finding the right buttons to click or dialogs to open may require a video or series of images with careful step-by-step instructions.

Text-based interfaces in contrast are straightforward to explain in articles such as this one. Complex operations which you easily forget how to do, so you can easily copy to a notebook and write an explanation. These notes can later easily be retrieved using search tools. Searching images of a graphical UI is much harder.

In this article, I will teach you how to do the following things:

  • Navigate around the file system just as you would do with a graphical file manager.
  • Look at what files are in a directory and filter out files or directories you are not interested in.
  • Do operations on files.
  • Explain the binary search path. It is used by the terminal to locate the command you write.
  • What are environment variables, and what can you use them for?

Work with Files and Directories

When you start the Unix command line, you will get some kind of greeting. It will differ depending on the system and configuration you have. Mine looks like this:

Last login: Thu Jun 23 22:10:30 on ttys001
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish

~
❯

You will have a cursor next to a prompt, indicating that the CLI is waiting for you to type a command and press enter. The prompt at the start of the line is usually one of the symbols $, # or . Mine is a because I am using the Starship prompt. It adds several cool features, but you don’t have to use it.

Let us look at the most basic commands ls, cd, pwd, man, cat, touch and file.

ls - List directory contents

With the ls command, you can see what files and directories are in a directory, just like you can with a file manager.

❯ ls /bin
[		expr		pwd
bash		hostname	rm
cat		kill		rmdir
chmod		ksh		sh
cp		launchctl	sleep
csh		link		stty
dash		ln		sync
date		ls		tcsh
dd		mkdir		test
df		mv		unlink
echo		pax		wait4path
ed		ps		zsh

❯

This shows the contents of the bin directory on a Unix system. That is where Unix keeps binaries for the most common commands. We call a file containing a program you can run for binary.

The ls command is smarter than your file manager. You can do many neat tricks. Characters such as * and ? get interpreted in a special way. They form what we call glob patterns. You can use glob patterns to filter what files are show. The * matches any number of letters while ? matches a single letter. So a? would match files named ab, ab, ad, ae and so on, while *.txt would match files named foo.txt, bar.txt, baz,txt and so on. What is up with the foo and bar names? Those a just silly names we tend to use in programming and Unix circles. As soon as you see the words foo, bar, qux or baz you should know that they are placeholders. The specific word used isn’t important. It could be anything.

cd - Change working directory

To try out the ls command, we will use the cd and pwd commands. Just like with a graphical terminal, there is a current working directory. It is the directory your commands will apply changes to. You can change to a working directory with the cd command. If you have forgotten where you are, you can use the pwd command. pwd is sort for print working directory.

Let us jump a bit around and look at some directories on a macOS system (will be similar on Linux).

❯ cd /bin

❯ ls
[         df        ln        sh
bash      echo      ls        sleep
cat       ed        mkdir     stty
chmod     expr      mv        sync
cp        hostname  pax       tcsh
csh       kill      ps        test
dash      ksh       pwd       unlink
date      launchctl rm        wait4path
dd        link      rmdir     zsh

❯ pwd
/bin

❯ cd /System
❯ ls
Applications DriverKit    Volumes
Developer    Library      iOSSupport

❯ pwd
/System

touch and mkdir - Make files and directories

To be able to experiment a bit more with ls we will make our own directory and fill it with bogus files. We will go to our home directory. If you just write cd you will jump to the home directory for your user. Here we will make a directory called animals to experiment with ls.

From now on, I will show above the prompt what my current directory is and files will have a * as an ending, while directories have the ending /. You can get trailing slash on your system by writing ls -p instead of just ls. The -p is called a switch. Every Unix command comes with several switches affecting how they run.

Okay, let us make some dummy files with the touch command.

~
❯ mkdir animals

~
❯ cd animals 

~/animals
❯ touch bird.png dog.jpg cat.jpg monkey.gif

~/animals
❯ ls
bird.png    dog.jpg
cat.jpg     monkey.gif

~/animals
❯ touch horse.png whale.png fox.gif

The mkdir command makes a directory with a given name, while touch can be used to make one or more empty files. With these files, we will look at how we can use glob pattern with ls to show only a subset of the files you have in the current directory.

~/animals
❯ ls
bird.png    fox.gif     whale.png
cat.jpg     horse.png
dog.jpg     monkey.gif

If you want to only see the .png files in the animals directory you can use the *.png glob pattern which means we don’t care what the name of the file is as long as it ends with .png

❯ ls *.png
bird.png   horse.png  whale.png

~/animals
❯ ls *.gif
fox.gif     monkey.gif

You can find all three letter filenames with an o in the middle using the question mark ? which matches a single letter.

~/animals
❯ ls ?o?.*
dog.jpg  fox.gif

man - Manual - Get help

Once you know the basics of Unix commands you want to be able to look at specific detailed information which does not make sense to cover in a blog post. We can look at the manual for each command using the man command. man ls will give us the manual page for the ls (list) command. It will drop you into a pager program. A lot of beginners immediately ask: How do I get out of this program?! Whether you are inside the vim program, less, or man you press q to quit. In the vim editor you may have to press ESC key and then colon : before you can hit q to exit.

Here is an overview of the most useful commands for navigating inside a man documentation page:

  • and arrow keys for going up and down one line.
  • Ctrl-U and Ctrl-D for going up and down a whole page at a time.
  • /foobar searches for the text foobar in the manual page. You just write a slash / and then type the text you are interested in finding. (foobar is a nonsense word).
  • q quit the manual page viewing.

See if you can find the manual entry for the -l switch. It prints info about directory contents on long form. You can search for it by writing /-l. The entry should look like this:

-l   (The lowercase letter “ell”.) List files in the long
      format, as described in the The Long Format
      subsection below.

We can try the -l switch to see how we get more info:

~/animals
❯ ls -l
total 0
-rw-r--r--  1 erikengheim  staff  0 Jun 24 14:11 bird.png
-rw-r--r--  1 erikengheim  staff  0 Jun 24 14:11 cat.jpg
-rw-r--r--  1 erikengheim  staff  0 Jun 24 14:11 dog.jpg
-rw-r--r--  1 erikengheim  staff  0 Jun 24 14:16 fox.gif
-rw-r--r--  1 erikengheim  staff  0 Jun 24 14:16 horse.png
-rw-r--r--  1 erikengheim  staff  0 Jun 24 14:11 monkey.gif
-rw-r--r--  1 erikengheim  staff  0 Jun 24 14:16 whale.png

This shows a whole bunch of information about each file. Each column is for the following:

  1. Whether you can read r, write w or execute x the file. There is a triplet for the user, group file belongs to and everybody else.
  2. Number of links to file. What appears as different files in different locations on your hard drive can all point to the same file.
  3. Owner of the file, erikengheim in this example.
  4. Group the file belongs to. staff in this example.
  5. Number of bytes in file. It is zero because we made empty files.

The last columns are for when the file was last modified and finally the name of the file.

cat - Concatenate - Join files and show contents

Our files are empty, so to be able to test the cat command used to show contents we have to actually edit the files and add some content. You can use any text editor storing text as UTF-8 encoding, or you can use the echo command to add text to files.

~/animals
❯ echo blue bird > bird.png

~/animals
❯ echo black cat > cat.jpg

~/animals
❯ echo red fox > fox.gif

You can use cat to look at the contents of individual files or concatenate the contents of multiple files and show it.

~/animals
❯ cat bird.png cat.jpg
blue bird
black cat

~/animals
❯ cat bird.png cat.jpg fox.gif
blue bird
black cat
red fox

echo - Display text

In many cases we need to display some text to the user. We can use echo for this purpose:

❯ echo Hello world
Hello world

This example many look at bit pointless but it makes sense when writing longer scripts doing multiple things where you want to give feedback to the user. You can also use it to show the contents of environment variables. We can use named variables to store text which can later be shown:

❯ NAME=Joe
❯ echo hello $NAME
hello Joe

Why do we need the dollar sign $? Because if you only wrote NAME the system would not be able to realize you want the contents of a variable named NAME. It would think you wanted to write the text NAME:

❯ echo hello NAME
hello NAME

So while you don’t need the dollars sign $ when setting an environment variable, you will need it when using it.

How Commands are Located with Binary Search Path

Most commands such as ls, cat, cp and rm are actual small programs which are loaded and executed by the operating system. When you type a command the operating system will look for that command in different locations. You can find out where the command is found by the operating system by using the which command:

❯ which ls
/bin/ls

❯ which cat
/bin/cat

❯ which vim
/usr/bin/vim

How does the operating system know to look in the /bin directory for ls and cat while the vim editor is in the /usr/bin directory? That is done using the binary search path. It is stored in the environment variable called PATH. We can look at the contents of binary search path by using the echo command.

❯ echo $PATH
/usr/local/bin /usr/bin /bin /usr/sbin /sbin

But maybe we have programs in other locations. How do we make the operating system find them? Say we have binaries in the /opt/bin directory which we want to use. Each search path is separate with a colon : and we can simply insert the existing path when redefining what the PATH variable should be:

❯ PATH=/opt/bin:$PATH

There is however one problem with what we just wrote. As soon as you close the terminal window the path will be forgotten. That is why you need to add this line to the .profile or .zshrc file in your home directory. Which file you add to depends on whether you use the bash or zsh shell. You also have to add the export keyword otherwise the environment variable will not be known outside the shell configuration file .profile or .zshrc. So you write:

export PATH=/opt/bin:$PATH

Because a path may contain spaces, we tend to use quotation marks " around text strings to show that it is one contiguous block of text. Hence we usually write:

export PATH="/opt/bin:$PATH"

In this example we put /opt/bin first, but what if we wrote it like this instead?

export PATH="$PATH:/opt/bin"

That is perfectly valid and means that when looking for a Unix command the system will first look in one of the paths already defined in PATH before looking in /opt/bin. That may matter in case you have commands with the same name in multiple locations. For instance your operating system may come installed with the python language but then you install your own newer version to /opt/bin. Since you want your newer version to be used by default you put the /opt/bindirectory first, so it gets priority.

Environment Variables

Environment variables can have many uses. We just saw the PATH environment variable used to specify the binary search path. Various Unix tools require a text editor. For instance the Git version control system will sometimes open an editor to let to edit commit messages. How is you operating system supposed to know what editor to open? You can specify that with the EDITOR environment variable. Although sometimes you want different editors for different programs. For instance I don’t want to use my editor in the same way for Git as with the Julia programming language. Fortunately Julia allows me to specify an editor in the JULIA_EDITOR environment variable which takes precedence over what the EDITOR environment variable says. Let me show you:

export EDITOR="mate -w"
export JULIA_EDITOR=mate

For Git I used the -w switch because it will cause the calling program (Git) to halt until I close the editor. That is not desired behavior for me when using Julia.

The text editor I use, TextMate allows you to set environment variables in a preference panel. You can see variables such as BOARD, SERIALDEV and TM_JULIA. This is useful because any program started by TextMate will have access to these environment variables.

Exported environment variables are inherited by child processes. What do you mean? A running program is called a process. Processes have parent-child relationships. The terminal window is a process and when run a command such as ls, man or touch you are creating a child process of this window, or more specifically the shell process running inside the window.

Any running program could launch a child process, and define environment variables which should be inherited by the child process. TextMate allows you to write shell scripts, Python, Ruby or Julia scripts which could read these environment variables.

Thus the use of environment variables is not limited to text-mode applications. You can use environment variables to pass data from and to any program.

comments powered by Disqus