Skip to content

Terminal and Shell

If you have never used a terminal before, this page will get you oriented. If you have, use it as a quick reference. This is not a comprehensive guide; it covers enough to be productive in this course.

Unless noted otherwise, path and log file examples below assume a Linux server rather than macOS.

A terminal (or terminal emulator) is the window you type commands into. A shell is the program running inside that window that interprets your commands and executes them.

Common shells:

  • bash (Bourne Again Shell): the default on most Linux servers and older macOS versions.
  • zsh (Z Shell): the default on modern macOS. Nearly identical to bash for basic usage.

When you open Terminal on macOS or a WSL window on Windows, you are running a shell inside a terminal. The prompt (something like ubuntu@ip-172-31-0-5:~$ or ulbrical %) tells you who you are, where you are, and that the shell is ready for input. The final prompt character is a useful clue: $ usually means a regular user, # usually means the root user, and % is common in zsh’s default prompt for a regular user. Prompts are customizable, so you may see other formats.

In Unix-like systems, most things you interact with are represented as files or directories. The filesystem also includes other file types, such as symbolic links, devices, and sockets. Paths use forward slashes (/). The root of the filesystem is /.

CommandWhat it doesExample
pwdPrint the current directorypwd
lsList files in the current directoryls -la (all files, detailed)
cdChange directorycd /var/log
cd ..Go up one directorycd ..
cd ~Go to your home directorycd ~
cd -Go to the previous directorycd -

Absolute paths start with / (e.g., /etc/apache2/apache2.conf). Relative paths start from your current directory (e.g., ./config.txt or ../other-dir/).

CommandWhat it doesExample
catPrint a file’s contentscat /etc/hostname
lessView a file with scrolling (press q to quit)less /var/log/syslog
head / tailShow the first/last linestail -20 /var/log/syslog
cpCopy a file or directorycp file.txt backup.txt
mvMove or rename a filemv old.txt new.txt
rmRemove a file (no undo)rm temp.txt
rm -rRemove a directory and its contentsrm -r old-dir/
mkdirCreate a directorymkdir -p logs/2026
touchCreate an empty file or update a timestamptouch notes.txt

grep searches files or command output for lines matching a pattern. It is one of the most-used commands in system administration.

FlagWhat it doesExample
(none)Search for a literal stringgrep "error" /var/log/syslog
-rSearch recursively through a directorygrep -r "DB_HOST" /var/www/
-iCase-insensitive matchgrep -i "warning" log.txt
-vInvert: show lines that do NOT matchgrep -v "^#" config.txt
-nShow line numbersgrep -n "failed" auth.log
-cCount matching lines instead of printing themgrep -c "error" syslog
-EExtended regular expressions (enables +, ?, alternation, {n})grep -E "errors?" syslog
-rnRecursive with line numbers (common combination)grep -rn "TODO" /var/www/

For alternation with extended regular expressions, use |:

Terminal window
grep -E "error|warn" syslog

find locates files by name, type, or other attributes:

Terminal window
find /etc -name "*.conf" # all .conf files under /etc
find /var/log -name "*.log" -mtime -1 # log files modified in the last day
find /tmp -type f -size +10M # files larger than 10 MB in /tmp

which tells you where the shell would find a command:

Terminal window
which python3 # /usr/bin/python3

Every Unix program connects to three standard streams by default:

  • stdin (standard input, file descriptor 0): where the program reads input. By default, the keyboard.
  • stdout (standard output, file descriptor 1): where the program sends its normal output. By default, the terminal.
  • stderr (standard error, file descriptor 2): where the program sends error messages. Also the terminal by default, but kept separate from stdout so that errors do not corrupt piped output.

The distinction between stdout and stderr matters when you redirect output to a file or pipe it to another program. Only stdout flows through a pipe by default. Error messages still appear on the terminal even when normal output is redirected elsewhere, which is usually what you want.

The pipe operator | connects one command’s stdout directly to the next command’s stdin. Each program does one thing; the shell arranges the plumbing:

Terminal window
grep "error" /var/log/syslog | wc -l # count lines containing "error"
du -ah /var | sort -rh | head -10 # 10 largest items in /var
ps aux | grep nginx | grep -v grep # find nginx processes

Redirection sends a stream to a file instead of the terminal, or takes file content as input instead of the keyboard:

Terminal window
echo "hello" > file.txt # write stdout to file (overwrites)
echo "world" >> file.txt # append stdout to file
command 2> errors.log # redirect stderr only to a file
command > out.log 2>&1 # redirect both stdout and stderr to the same file
command >> out.log 2>&1 # append both (the standard pattern for cron job logs)

2>&1 means “send file descriptor 2 (stderr) to wherever file descriptor 1 (stdout) is currently going.” Order matters: write > file 2>&1, not 2>&1 > file. The second form redirects stderr to the terminal first and then moves stdout to the file, which is almost certainly not what you intend.

echo prints a string followed by a newline. It is sufficient for simple messages:

Terminal window
echo "Backup complete"
echo "Error: file not found" >&2 # send to stderr instead of stdout

printf gives precise control over formatting. It does not append a newline automatically, so you add \n explicitly:

Terminal window
printf "%-20s %s\n" "Directory" "Status"
printf "%-20s %s\n" "/var/www/html" "OK"
printf "%d files processed\n" 42

The read builtin captures a line from stdin and stores it in a variable. Use -r to disable backslash interpretation (almost always what you want). Prompt flags differ between shells, so the most portable pattern is to print the prompt separately:

Terminal window
printf "Enter the destination path: "
read -r DEST
echo "You entered: $DEST"

In bash, read -rp "Enter the destination path: " DEST combines those two steps.

Inside a script, read is commonly used in a while loop to process a file or command output line by line.

Process substitution (<(command)) makes the output of a command appear as a file path. It is useful when a command expects file arguments rather than piped input:

Terminal window
diff <(ls /etc/nginx/conf.d) <(ls /etc/nginx/sites-enabled)

Both ls commands run concurrently and diff compares their outputs as if they were two files. Process substitution is supported by shells such as bash, zsh, and ksh, but it is not available in plain POSIX sh scripts.

Every file has an owner, a group, and permissions for read (r), write (w), and execute (x). In ls -l output, the first character shows the file type.

Terminal window
ls -l script.sh
# -rwxr-xr-- 1 ubuntu www-data 512 Mar 15 script.sh
# ^ file type (- = regular file, d = directory, l = symbolic link)
# ^^^ owner permissions (rwx)
# ^^^ group permissions (r-x)
# ^^^ others permissions (r--)
CommandWhat it doesExample
chmodChange file permissionschmod +x script.sh
chmod 755Set specific permissions (owner rwx, group r-x, others r-x)chmod 755 script.sh
chownChange file ownersudo chown www-data:www-data index.html

The numeric form (like 755) encodes permissions as three digits: owner, group, others. Each digit is the sum of read (4), write (2), and execute (1). So 755 means rwxr-xr-x.

sudo runs a single command as the superuser (root). Most system administration tasks require it.

Terminal window
sudo apt update # Update package lists
sudo systemctl restart nginx # Restart a service
sudo vim /etc/hosts # Edit a system file

Use sudo only when necessary. Running everything as root is a security risk because any mistake, or any compromised command, has unrestricted access to the system.

su (substitute user) switches your entire session to another user rather than running a single command. su - (with a hyphen) switches to root and loads a full login environment, including root’s PATH and profile:

Terminal window
su - # become root (requires root's password)
su - deploy # become the "deploy" user
exit # return to your original user

On Ubuntu and some other distributions, the root account is locked by default, so su - to root may fail unless a root password has been set.

On most modern Linux servers, sudo is preferred over su - because it leaves an audit trail of who ran what and does not require sharing the root password. su is more common in environments where sudo is not configured.

Package managers install, update, and remove software. The package manager depends on your distribution.

DistributionManagerInstall example
Ubuntu/Debianaptsudo apt install nginx
Amazon Linux/RHELdnf / yumsudo dnf install nginx
macOSbrewbrew install wget

On Debian and Ubuntu, run sudo apt update before installing packages to refresh the package list. Other package managers handle repository metadata differently.

Environment variables store configuration that programs and scripts can read. They are written in ALL_CAPS by convention. Every process has its own set of environment variables, inherited from the process that started it. When you open a terminal, your shell inherits variables set by the system and by your shell configuration files. When you run a command, that command inherits your shell’s environment.

Terminal window
echo $HOME # your home directory
echo $USER # your username
echo $SHELL # your current shell
printenv # print all environment variables

A plain assignment creates a shell variable, local to the current shell session:

Terminal window
MY_VAR="hello"
echo $MY_VAR # hello
bash -c 'echo $MY_VAR' # (empty — child process does not see it)

Adding export promotes it to an environment variable, which child processes inherit:

Terminal window
export MY_VAR="hello"
bash -c 'echo $MY_VAR' # hello

Scripts you run in the terminal, CLI tools, and programs launched from the shell all run as child processes. They see exported variables but not plain shell variables.

Variables set in a terminal session disappear when you close the terminal. To make them permanent, add the export line to your shell configuration file:

  • bash: ~/.bashrc (interactive non-login shells, the common case on Linux servers) or ~/.bash_profile (login shells).
  • zsh: ~/.zshrc.

After editing the file, reload it in your current session:

Terminal window
source ~/.bashrc # bash
source ~/.zshrc # zsh

PATH is a colon-separated list of directories the shell searches when you type a command. The shell tries each directory in order and runs the first match:

/sbin
echo $PATH
which python3 # shows which python3 the shell would run

To add a directory to PATH for the current session:

Terminal window
export PATH="$HOME/.local/bin:$PATH"

Place the new directory before $PATH (at the start) to make it take priority over system-installed versions. Add the same line to ~/.bashrc or ~/.zshrc to make it permanent.

SSH (Secure Shell) connects you to a remote machine over an encrypted channel. You will use it constantly in this course.

Terminal window
# Connect to a remote server
ssh -i ~/Downloads/cs312-key.pem ubuntu@54.123.45.67
# Copy a file to a remote server
scp file.txt ubuntu@54.123.45.67:/home/ubuntu/
# Copy a file from a remote server
scp ubuntu@54.123.45.67:/var/log/syslog ./syslog-copy.txt

If SSH says your private key permissions are too open and refuses to use it, fix the permissions:

Terminal window
chmod 400 ~/Downloads/cs312-key.pem

When you encounter an unfamiliar command, these are the fastest ways to learn what it does:

Terminal window
man ls # Open the manual page for "ls" (press q to quit)
type cd # Ask the shell what "cd" is (builtin, alias, function, or executable)
tldr tar # Community-maintained cheat sheets

Reading manual pages (man) is a core skill. The format is dense at first but becomes natural with practice. Many commands also support -h or --help, but those flags are not standardized and can differ between Linux and macOS. If you want TLDR pages, install a client for your platform first. For example, Homebrew currently packages tlrc on macOS, while Linux distributions may package tldr, tealdeer, or another client.