Unix Command Line Interface for Beginners
Posted
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.
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.