Unix Command Line Interface for Beginners

Contents

Introduction

Once upon a time, at work, in the not so distant past, I noticed that people were copying commands into the terminal on macOS without knowing what they really mean. Some people were impressed by my knowledge of the command line, but I dismissed this as witchcraft that we learned back in the dark ages. Recently, while perusing the contents of my computer, I realized that I had created an entire class on basic Unix commands about twenty years ago for a local telecommunications company to help people moving to Unix-based systems from Windows ones. In this tutorial, I am going to explain a few things about using Unix-based shell commands. This is just some of the more common stuff.

The Unix-based operating systems

The Unix-based operating system consists of three parts: the kernel, the shell, and the programs. The kernel is the hub of the operating system. It allocates time and memory to programs and handles file storage and communication in response to system calls. System calls require services to be provided by the kernel.

For the purpose of this post, we are focusing on using the shell. The shell is the interface between the user and the kernel. The shell is the command line interpreter (CLI). It interprets the commands the user types in and arranges for them to be carried out. The commands themselves are programs. When the commands terminate, the shell gives the user a prompt.

There are numerous command line interpreters for Unix-based systems. Currently, the Bourne Again Shell (bash), the Z shell (zsh), and the Friendly interactive shell (fish) are popular, but there are other command line interpreters and other Unix shell variants.

We use the shell to run programs and interact with files. The files can contain documents, programs, or directories. The directories are organized into a tree. Each directory contains other files and other directories.

While writing this post, I primarily worked with zsh command line interpreter on macOS, but the commands will likely work for other Unix-like shells.

Understanding the tree

The files in an operating system are organized in a tree.

  flowchart TD
   root["/"]
   folder1["Users"]
   folder2["Library"]
   folder3["Volumes"]
   folder4["bin"]
   folder5["smiller"]
   root --> folder1
   root --> folder2
   root --> folder3
   root --> folder4
   folder1 --> folder5

The topmost directory above (/) is traditionally called root.

pwd (print working directory)

We can see where we are in the tree by typing pwd at the command line interface. When I open a new shell on macOS, and I type pwd, I can see that I am in the /Users/smiller directory also known as my home directory.

> pwd
/Users/smiller

ls (list)

We can see the files in the working directory by typing ls. This command prints a list of the files in a directory. For example, in the smiller directory, there is a rust_projects directory, and that directory contains the files hello_world.rs and hello_world. It also contains a directory called hello_cargo

  flowchart TD
  folder1["smiller"]
  folder2["rust_projects"]
  folder3["hello_cargo"]
  file1(["hello_world.rs"])
  file2(["hello_world"])
  folder1 --> folder2
  folder2 --> folder3
  folder2 --> file1
  folder2 --> file2

The ls command on its own lists most files in the working directory.

> ls
hello_cargo    hello_world    hello_world.rs

Some files and directories that start with a dot are not displayed. These files and directories usually contain important configuration information. If we use ls -a, the ls command followed by the -a flag, we can see two hidden dot directories: . and ..

> ls -a
.              hello_cargo    hello_world.rs
..             hello_world

This may not look particularly useful at first, but . represents the current directory and .. represents the parent of the current directory.

These two files are important for navigation.

cd (change directory)

We can now navigate to our new directory using cd rust_projects. If this command is completed successfully, we will be in our rust_projects directory. We can use pwd to confirm that we are in /Users/<OUR_USER_DIRECTORY>/rust_projects.

Go Home

From here, we can return to our home directory by doing a cd .. We mentioned that .. takes us up one directory. We can also do a cd which will take us back to our home directory, that is /Users/<OUR_USER_DIRECTORY>. We can also use ~ to reference our home directory. For example, assume that we are in the / Volumes directory. We can still see our rust_projects directory by using ls -d ~/rust_projects. We will explain what the -d flag does in the next section.

Illustration of navigating the tree

Altering the tree

mkdir (make directory)

Now that we understand how to observe the tree, we can make our own directories to navigate the tree. Use the mkdir rusty_projects to make a directory for a new project. This should create a directory called rusty_projects if you don’t already have one. If you do have a directory called rusty_projects, the CLI will let you know that the file already exists.

We can use the ls -d rusty_projects command to confirm that our new directory actually exists. The -d flag tells ls to treat directories like normal files and not return their contents recursively.

> ls -d rusty_projects
rusty_projects

rmdir (remove directory)

Now that we have returned to our home directory using cd, we can decide that we are not interested in working on rusty_projects. We can use rmdir rusty_projects to remove our rusty_projects directory. And we can use ls -d ~/rusty_projects to confirm that the directory is gone.

> ls -d ~/rusty_projects
ls -d ~/rusty_projects
ls: /Users/smiller/rusty_projects: No such file or directory

Manipulating files

Now that we understand the basics of navigation, we can manipulate files and directories. First, we need a new directory. mkdir new_unix_project to create a directory called “new unix project.”

We can cd new_unix_project to go into our new directory.

touch

We can touch grass in our new directory to create an empty file called “grass” in our new directory and can run ls to see that the “grass” file now exists in our new directory. The touch command creates an empty file. We can also touch flowers to create another empty file.

We can use ls -l to confirm that the flowers file is empty. In the fifth column, we see a 0 that represents the number of bytes in a file.

> ls -l
-rw-r--r--  1 smiller	staff    0 Mar 23 13:08 flowers

more/less

We can use more grass or less grass to see the contents of our files. Generally, we prefer “less” because it is more feature rich. We might see something like “grass (END)” showing us that the file is empty. Remember to use ‘q’ to leave the more or less program. It sure would be nice to have content in our file.

nano/pico/vi/emacs

There are numerous editors that we can use to edit text in a Unix-like system. Nano is one of the easier ones to use. We can use nano grass to edit the file and then use “Ctrl-O” to write out our changes and “Ctrl-X” to exit our editor. Now, when we try less grass, we can see the contents of our grass file. Pico, vi, and emacs are other editors that are beyond the scope of this tutorial, but if you are on a system that does not support nano, you might want to use vimtutor to learn about vi.

cp (copy)

Now, we can copy our grass file to another file. cp grass tall_grass will copy the contents of the “grass” file to the “tall grass” file. We can do less tall_grass to see that the contents of our new file are the same as the contents of our old file.

> less tall_grass

This grass is tall.
tall_grass (END)

head/tail

Suppose we had extremely detailed information about the various grasses of the midwest like little bluestem and switchgrass, but we put that information in poorly named files like grass1 and grass2. We could use commands like head -1 grass1 to read the first line of the grass1 file or tail -1 grass1 to read the last line of the grass1 file to figure out what our files were really about. The “-1” in this command is about the number of lines to read.

mv (move)

Now that we have several grass files, we want to put them into a directory. mkdir grasses to create a directory for our grasses. Use ls to make sure your new directory exists.

Now we can use mv *grass grasses which will move all files that end in the word grass to the “grasses” directory. We can do an ls to see that our files are gone from the current directory and then ls ./grasses to make sure that they all moved to the “grasses” directory.

cat (concatenate)

Now, we can move to the “grasses” directory using cd grasses. We can edit tall_grass using nano tall_grass and changing the file to say “This is tall grass.” We can also edit our grass file to say “This is grass.”

Now, we can print both files to the screen using cat grass tall_grass to get an output that says “This is grass.” on one line and “This is tall grass.” on the next line. This can be useful if we want to process the contents of many lines at once.

rm (remove)

From what we have learned so far, it would seem that we could cd .. to return to our parent directory and rmdir grasses to remove the grasses directory. However, if we try that, we get warned “rmdir: grasses: Directory not empty”. We cannot use the rmdir command to remove directories that are not empty.

We can use rm grasses/*grass to remove all the files that contain the word “grass” in the grasses directory. Using ls grass, we can now see that our “grasses” directory is empty.

Now, we can use rmdir grasses to remove the “grasses” directory.

It would be nice if we could have removed the “grasses” directory in a single step. We could have done that if we used rm -r grasses from the parent directory of the grasses directory.

Redirection

Now that we know how to create files and move them, we might want to engage in more complicated file manipulation. For example, what if we want to take information from the command line and put it into a file.

Redirecting output

We can do cat > flowers which will take flowers from the command line and put them in the flowers file.

If we run the command, we can list flowers on separate lines: crocuses tulips roses daisies

and then use Ctrl-D to close our file. Ctrl-D sends an “end of file” to the file.

Redirecting input

What if we need to organize our flowers by their names? We can sort flowers. That will print all the names of the flowers in order.

What if we need to organize the flowers by name and create a new shopping_list.

We can do a sort < flowers > shopping_list which will take the contents of the flowers file, sort them, and output them to a shopping_list file.

What if we forgot some flowers?

We can do cat >> flowers to add more flowers to our file.

cat >> flowers appends new content to the end of a file while cat > flowers creates a new file or overwrites an existing file.

Pipes

We can string together numerous Unix commands using pipes (|).

For example, we can do sort flowers | less and the output of the “sort” command is now displayed with “less.”

> less flowers

crocuses
tulips
roses
daisies
daffodils
flowers (END)

> sort flowers | less

crocuses
daffodils
daisies
roses
tulips
(END)

Wildcards

The * is a wildcard that matches zero or more characters. Earlier, when we discussed the mv command, we used mv *grass grasses and didn’t really explain the *. When we moved “*grass,” any file that ended in the word grass was moved to the grasses directory.

In addition to *, ? is another wildcard. It matches exactly one character. If we touch glowers in the directory that contains “flowers,” we can do ls ?lowers to see both “flowers” and “glowers.” If we had a third file called “zzlowers” in the directory, we could use “ls ?lowers” to see “flowers” and “glowers” and ignore “zzlowers.” We could use ls *lowers to see all three files.

When we create files in Unix, it is smart to avoid characters with special meanings such as / * & % in file names. Once, I accidentally created a file named “*”. I tried to remove it using rm *. This removed ALL the files in my directory, and I had to contact the system administration to see if we had backups for the directory. The correct way to remove a file named “*” is to rm '*'.

Read the manual

? This blog post shows only some of the most common Unix commands. As we continue our journey, we can use man <command_name> to learn about other Unix commands or to flesh in the details of existing ones. For example man ls will show us that ls can have many flags. My favorites are ls -al that will list files with information about their size and access permissions and ls -rtl that will list files in reverse time order.

Man pages can be overwhelmingly long, but you can use the forward slash to go to content of interest.

For example, when we “man ls” we can use “/The Long Format” followed by the return key to go to instances of the phrase “The Long Format.” The forward slash tells the shell to search forward in the document. After we have specified our phrase at least once, we can just use “/” to continue searching forward for the same phrase. We can also use “?” followed by the return key to go back to previous occurrences of that phrase.

TLDR

command name What it does
pwd (print working directory) print the name of the current directory
ls (list) list the files in a directory
cd (change directory) change the directory
mkdir (make directory) create a new directory
rmdir (remove directory) remove an empty directory
touch create a new file that is empty
more/less read the contents of a file and use q to quit
pico/vi/emacs text editors
cp (copy) copy the contents of one file to a new file
head/tail prints a few lines starting at either the beginning or end of a file
mv (move) move one or more files to a new location
cat (concatenate) print out the contents of multiple files
rm (remove) remove one or more files

Good luck on your journey. Please let me know if anything is confusing or if I should add other sections.