COMP 3150Operating Systems

01 Introduction

Before graphical desktops, every interaction with a computer was textual. A user typed a command; the machine responded. That interface — the shell — is not a relic of an earlier era. It remains the most powerful and efficient way to interact with a UNIX-like system, and it is the primary interface you will use for every laboratory and programming assignment in this course.

The reason the shell endures is composability. Graphical applications are islands: to get data from one into another, you copy and paste, or you rely on a plugin that someone else wrote. Shell commands are small, focused tools that read from standard input and write to standard output, and the shell's redirection and pipeline mechanisms let you connect them in arbitrary ways. A pipeline of three commands can accomplish what would take a custom program in any other environment — often without writing a single line of code.

This lesson gives you the practical skills you need: moving around the file system, running and finding commands, managing files and directories, and combining tools with redirection, pipes, and simple scripts. The lessons that follow will build on this foundation to cover users, permissions, and the development environment.

02 Learning objectives

By the end of this lesson you will be able to

  • Navigate the file system.
  • Execute shell commands.
  • Manage files and directories.
  • Use redirection and pipes.
  • Write simple shell scripts.

This lesson contributes to U2.2 and to course learning outcome CLO6.

03 Required reading

  • Stevens & Rago — Advanced Programming in the UNIX Environment, 3rd ed., Sections 1.4–1.6 (files and directories, input and output, programs and processes). The classical presentation of how the shell interfaces with the kernel.
  • Shotts, W. — The Linux Command Line, 5th ed., Chapters 1–6 (navigation, file operations, redirection, and shell basics). Freely available online.
  • Man pages: man 1 bash, man 1 ls, man 1 cp, man 1 grep. The manual is the definitive reference for every command discussed in this lesson.

Reading strategy

Skim Shotts Chapters 1–6 for the practical material; read Stevens & Rago Sections 1.4–1.6 for the conceptual framework that ties the shell to the system-call interface from Lesson 2.1. Man pages are for reference; you do not need to read them cover to cover, but you should develop the habit of consulting them when a command does something unexpected.

04 Core content

4.1 What a shell is

A shell is a program that reads commands from the user, interprets them, and executes them. It is a user-space program — not part of the kernel — and it communicates with the kernel through the system-call interface. When you type ls, the shell:

  1. Reads the line from standard input.
  2. Parses it into a command name and arguments.
  3. Searches the PATH for an executable named ls.
  4. Calls fork to create a child process.
  5. Calls execve in the child to replace its image with the ls program.
  6. Calls wait to wait for the child to terminate.
  7. Prints the next prompt.

This is the fork–exec–wait sequence from Lesson 2.1 in action. Every command you type — from ls to gcc to a script — runs through this mechanism.

4.2 Interactive vs. non-interactive shells

A shell can run in two modes:

  • Interactive. A terminal is attached, the shell prints a prompt, and it reads commands from the user. This is what you get when you open a terminal or log in via SSH.
  • Non-interactive. The shell reads commands from a file (a script) or from standard input and executes them without prompting. This is how shell scripts work — a text file containing commands that the shell executes in sequence.

The syntax is identical in both modes; the difference is only whether a prompt is displayed and a terminal is attached.

4.3 Command structure and the PATH

A shell command has a simple form:

command [option]... [argument]...

For example:

ls -l /home/alice
#   │  │  └─ argument (path)
#   │  └─── option (long listing)
#   └───── command (list directory)

Options come in two forms:

  • Short options — single letter, prefixed with a single dash: -l, -a, -h. Multiple short options can be combined: ls -lah means ls -l -a -h.
  • Long options — a word, prefixed with two dashes: --all, --human-readable. Long options are usually more readable and easier to remember.

When you type a command, the shell searches for an executable file with that name in a list of directories called the PATH. You can see the current PATH:

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

# The directories are separated by colons.
# The shell searches them left to right and runs the first match.

The which command tells you which file the shell would run for a given command name:

$ which ls
/usr/bin/ls

$ which gcc
/usr/bin/gcc

$ which notacommand
(no output; the command was not found)

Why the PATH matters

If two programs have the same name in different directories on the PATH, the one in the earlier directory wins. This is why installing a program in /usr/local/bin can override the system version in /usr/bin. It is also why adding . (the current directory) to the PATH is considered a security risk — it would let a malicious file in the current directory shadow a system command.

4.4 File-system navigation

The file system is a tree, and every file has a unique path. There are two kinds of path:

  • Absolute — starts at the root, always begins with /. Example: /home/alice/notes.txt.
  • Relative — interpreted from the current working directory. Example: notes.txt or ../bob/README.

Two special directory names appear everywhere:

  • . — the current directory.
  • .. — the parent directory.
File-system tree and path resolution A small directory tree with root at the top. The current working directory is /home/alice. Two paths are highlighted: an absolute path (starting from root) and a relative path (starting from the current directory). Both resolve to the same file. FILE-SYSTEM TREE · PATH RESOLUTION / /etc /home /usr /var alice bob docs notes.txt .bashrc report.txt CURRENT WORKING DIRECTORY /home/alice "pwd" prints this TWO PATHS TO report.txt Absolute: /home/alice/docs/report.txt Relative: docs/report.txt
Figure 2.2 — A small file-system tree. The current working directory is /home/alice; docs/report.txt (relative) and /home/alice/docs/report.txt (absolute) refer to the same file.

Three commands are fundamental for navigation:

pwd — print working directory
Prints the absolute path of the current directory.
cd — change directory
Changes the current working directory. cd path moves to path; cd with no argument moves to your home directory; cd - returns to the previous directory.
ls — list directory contents
Lists the files in a directory. Common options: -l (long format), -a (include hidden files), -h (human-readable sizes), -t (sort by modification time), -R (recursive).
$ pwd
/home/alice

$ ls
docs  notes.txt

$ ls -la
total 24
drwxr-xr-x 4 alice alice 4096 Oct 15 10:22 .
drwxr-xr-x 3 root  root  4096 Oct 14 09:11 ..
-rw-r--r-- 1 alice alice  220 Oct 14 09:11 .bashrc
drwxr-xr-x 2 alice alice 4096 Oct 15 10:22 docs
-rw-r--r-- 1 alice alice   18 Oct 15 10:20 notes.txt

$ cd docs
$ pwd
/home/alice/docs

$ cd ..
$ pwd
/home/alice

4.5 File and directory management

The core file-management commands:

Essential file and directory commands
Command Purpose Example
touch Create an empty file, or update a file's timestamps touch notes.txt
mkdir Create a directory mkdir docs, mkdir -p a/b/c
cp Copy files or directories cp a.txt b.txt, cp -r src/ dst/
mv Move or rename files or directories mv old.txt new.txt
rm Remove files or directories rm file.txt, rm -r dir/
cat Print file contents to standard output cat /etc/hostname
less View a file one screen at a time less /var/log/syslog
head / tail Show the first or last lines of a file tail -f /var/log/syslog
find Search for files by name, size, or other attributes find . -name "*.c"
grep Search text for a pattern grep "error" logfile.txt
wc Count lines, words, and characters wc -l file.txt
man Read the manual page for a command man ls

rm is permanent

There is no recycle bin on the command line. rm file.txt removes the file immediately. Worse, rm -rf / (or any variation that reaches a wide directory with -r and -f) will recursively delete everything it can reach. Read commands carefully before pressing Enter, especially when they contain rm, wildcards, or a leading slash.

4.6 Getting help

There are several ways to find out what a command does and how to use it:

  • Man pages. man ls shows the reference manual for ls. The man page is divided into sections; section 1 is user commands, section 2 is system calls, section 3 is library functions, and so on.
  • --help. Many commands support a --help option that prints a short usage summary.
  • info. GNU commands often provide a longer, hyperlinked manual accessible with the info command.
  • apropos. Searches the short descriptions of all man pages for a keyword. Useful when you do not know the name of the command you need: apropos "copy file".

4.7 Redirection

Every process started by the shell has three standard streams:

Standard input (stdin, fd 0)
Where the process reads its input. By default, the terminal keyboard.
Standard output (stdout, fd 1)
Where the process writes normal output. By default, the terminal display.
Standard error (stderr, fd 2)
Where the process writes error messages. By default, also the terminal display — but a separate stream.

The shell can redirect these streams before starting a process:

  • > file — redirect stdout to a file, overwriting its contents.
  • >> file — redirect stdout to a file, appending to it.
  • < file — redirect stdin from a file.
  • 2> file — redirect stderr to a file.
  • 2>&1 — redirect stderr to wherever stdout is currently going. Combined with >, this captures both streams in one file: cmd > out.txt 2>&1.
  • > /dev/null — discard output by redirecting it to the special "null device" that throws away everything written to it.
# Save the listing of /etc to a file
$ ls /etc > etc-contents.txt

# Count the lines in that file
$ wc -l etc-contents.txt
142 etc-contents.txt

# Append another listing
$ ls /usr >> etc-contents.txt
$ wc -l etc-contents.txt
158 etc-contents.txt

# Save only the errors from a build
$ gcc broken.c 2> errors.txt

# Save both the output and the errors, interleaved
$ gcc -Wall broken.c > build.log 2>&1

4.8 Pipes

A pipe, written |, connects the stdout of one command to the stdin of the next. This is the mechanism that makes the shell truly powerful: rather than writing a program to do everything, you chain small tools that each do one thing well.

# Count how many files are in /etc
$ ls /etc | wc -l
142

# Find all lines in a log file that contain "error",
# then count how many there are
$ grep "error" /var/log/syslog | wc -l
37

# Sort the list of users on the system, remove duplicates, and page through it
$ cut -d: -f1 /etc/passwd | sort | uniq | less

# Show the ten largest files under /var in human-readable sizes
$ du -ah /var 2>/dev/null | sort -rh | head -10

Each command in a pipeline runs as a separate process, connected by a kernel-managed pipe (a buffer the kernel provides for interprocess communication). The commands run concurrently: as soon as the producer writes to the pipe, the consumer can begin reading, without waiting for the producer to finish. This streaming behaviour is what makes pipelines efficient even on large data.

The UNIX philosophy in a single pipe

"Write programs that do one thing and do it well. Write programs to work together." Pipes are the mechanism that makes this philosophy work. Every command in a pipeline is a small, focused tool; the shell combines them into something larger than any one would be alone.

4.9 Command substitution and variable expansion

The shell expands certain patterns before running a command:

  • Variable expansion. $VAR is replaced by the value of the variable. $PATH, $HOME, $USER are common examples. Use ${VAR} when the variable name would otherwise be ambiguous.
  • Command substitution. $(command) runs command and substitutes its output. For example, mkdir backup-$(date +%F) creates a directory named backup-2024-10-15.
  • Arithmetic expansion. $(( expression )) evaluates an arithmetic expression: echo $(( 3 + 4 )) prints 7.
  • Globbing. * matches any sequence of characters, ? matches any single character, and [abc] matches any character in the set. For example, rm *.tmp removes every file ending in .tmp. Note that globbing happens in the shell, not in the command — the shell expands *.tmp into the matching filenames before running rm.

4.10 Shell scripts

A shell script is a text file containing a sequence of shell commands. To run it:

  1. Write the commands to a file.
  2. Make the file executable: chmod +x script.sh.
  3. Run it: ./script.sh or bash script.sh.

A minimal script:

#!/bin/bash
# The first line is the "shebang": it tells the kernel
# which program should interpret this script.

echo "Hello from a shell script"
echo "Today is $(date +%F)"
echo "The current user is $USER"

The #!/bin/bash line is essential. When you run ./script.sh, the kernel reads the first two bytes; if they are #!, it treats the rest of the line as the path to the interpreter. The kernel then runs /bin/bash with the script path as its argument.

Scripts can use control flow, variables, and conditionals:

#!/bin/bash

# Arguments
NAME="$1"
if [ -z "$NAME" ]; then
    echo "Usage: $0 <name>" >&2
    exit 1
fi

# Loops
for FILE in *.txt; do
    if [ -f "$FILE" ]; then
        LINES=$(wc -l < "$FILE")
        echo "$FILE: $LINES lines"
    fi
done

This script uses:

  • Positional parameters. $1 is the first argument, $0 is the script name, $@ is all arguments.
  • Test expressions. [ -z "$NAME" ] tests whether NAME is empty; [ -f "$FILE" ] tests whether FILE exists and is a regular file.
  • Exit codes. Every command returns an exit code; 0 means success, non-zero means failure. exit 1 ends the script with code 1.
  • Loops and conditionals. for, if, while are the standard control-flow constructs.

Shell scripts are covered in far more depth in the next lesson and in Lab 1. For now, the goal is to recognise the syntax and be able to read a simple script when you encounter one.

4.11 Scripts in this course

You will write shell scripts for at least three purposes in this course:

  • Lab automation. Each laboratory has a set of commands to run and outputs to collect; wrapping them in a script makes the workflow reproducible.
  • Test harnesses. When you implement a scheduler, a memory allocator, or a file-system simulator, you will want a script that runs a set of test cases and reports the results.
  • Experiment automation. When you measure context-switch cost or page-fault rates, a script that runs the experiment across a range of parameters and collects the results in a consistent format saves an enormous amount of manual work.

The best way to learn is to type commands by hand. Every command you run at the prompt is a line you could put in a script later.

05 Worked example

To see the power of composition in action, let's build a small but realistic pipeline from scratch.

Task: find the ten most common words in a file

Suppose we have a text file and want to know which words appear most frequently. In a graphical environment, we might write a program. In the shell, we chain together a few small tools.

  1. Get all words, one per line. The tr command translates characters; here we replace every non-letter with a newline.
    tr -cs '[:alpha:]' '\n' < input.txt
  2. Normalise case. Pipe through tr again to convert to lowercase, so "The" and "the" are counted together.
    tr -cs '[:alpha:]' '\n' < input.txt | tr '[:upper:]' '[:lower:]'
  3. Sort the words. Now that all instances of the same word are adjacent, we can count them.
    tr -cs '[:alpha:]' '\n' < input.txt | tr '[:upper:]' '[:lower:]' | sort
  4. Count adjacent duplicates. uniq -c collapses runs of identical lines and prefixes each with a count.
    ... | sort | uniq -c
  5. Sort by count. sort -rn sorts numerically in reverse order, putting the highest counts first.
    ... | uniq -c | sort -rn
  6. Take the top ten.
    ... | sort -rn | head -10

The full pipeline:

tr -cs '[:alpha:]' '\n' < input.txt \
  | tr '[:upper:]' '[:lower:]' \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -10

Sample output on a small corpus:

    892 the
    521 and
    489 of
    418 to
    389 a
    312 in
    287 is
    265 that
    231 it
    208 for

Six small commands, none of which knows anything about word frequency, combined to solve a problem that would otherwise require writing a custom program. This is the pattern that makes the shell so effective.

Turning it into a script. To reuse the pipeline, wrap it in a script:

#!/bin/bash
# topwords — print the N most common words in a file
# Usage: topwords FILE [N]

FILE="$1"
N="${2:-10}"   # default to 10 if not given

if [ ! -f "$FILE" ]; then
    echo "topwords: no such file: $FILE" >&2
    exit 1
fi

tr -cs '[:alpha:]' '\n' < "$FILE" \
  | tr '[:upper:]' '[:lower:]' \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -n "$N"

Save this as topwords, run chmod +x topwords, and use it as ./topwords input.txt 20. The script handles missing arguments, validates its input, and behaves like any other command on the system.

06 Interactive activity

Activity · work through the shell on your VM

Open a terminal and perform the following. Record what you observe for each step. The goal is to build muscle memory before the laboratories assume these skills.

  1. Explore your home directory. Run pwd, then ls -la. What hidden files (starting with a dot) exist? The dotfiles configure your shell and other programs.
  2. Create a workspace.
    mkdir -p ~/comp3150/lab1
    cd ~/comp3150/lab1
    pwd
    Where are you now? Confirm with pwd.
  3. Create files.
    touch file1.txt file2.txt file3.txt
    ls -l
    What are the permissions and sizes of the new files?
  4. Write to a file.
    echo "first line" > file1.txt
    echo "second line" >> file1.txt
    cat file1.txt
    What does the file contain after each command?
  5. Use a pipeline.
    ls /usr/bin | wc -l
    How many commands are in /usr/bin? Now count only the ones containing "sh":
    ls /usr/bin | grep sh | wc -l
  6. Redirect errors.
    cat /nonexistent 2> err.txt
    cat err.txt
    What did the error message say? Note that 2> captured it while leaving stdout untouched.
  7. Use command substitution.
    echo "Today is $(date)"
    echo "Files here: $(ls | wc -l)"
    Note that the inner command runs first, and its output is substituted into the outer command's argument.
  8. Write a small script.
    cat > hello.sh << 'EOF'
    #!/bin/bash
    echo "Hello, $USER"
    echo "You are in $(pwd)"
    echo "There are $(ls | wc -l) files here"
    EOF
    
    chmod +x hello.sh
    ./hello.sh
    Run it a few times, including after changing directory, to see the values change.

Reflect: which of the commands you used made use of standard input, standard output, or standard error? Which relied on the shell expanding something (a variable, a command, a glob) before running the command itself?

07 Knowledge check

Check your understanding

Question 1

What is the difference between > and >>?

Show answer
> redirects standard output to a file, overwriting whatever the file contained. >> also redirects standard output but appends to the file instead of overwriting it. The distinction is easy to forget and can cause accidental data loss: cmd > log.txt destroys the previous contents of log.txt.

Question 2

What is the difference between 2> and 2>&1?

Show answer
2> redirects standard error to a file, leaving standard output going to the terminal. 2>&1 redirects standard error to wherever standard output is currently going — so combining them, as in cmd > out.txt 2>&1, sends both streams to the same place. The order matters: cmd 2>&1 > out.txt does something different (it duplicates stderr to the current stdout first, then redirects stdout — the stderr copy still goes to the terminal).

Question 3

What does the PATH environment variable do, and what happens when a command name is not in any of the directories listed there?

Show answer
PATH is a colon-separated list of directories the shell searches when looking for an executable file. When you type a command name, the shell scans the directories in order and runs the first matching executable it finds. If no directory on the PATH contains a matching executable, the shell reports "command not found" and returns a non-zero exit status. To run a program that is not on the PATH, you can give a path explicitly: ./myprogram or /opt/tools/myprogram.

Question 4

What is the purpose of the #!/bin/bash line at the top of a shell script?

Show answer
It is called the shebang, and it tells the kernel which interpreter to use to run the script. When the script is executed directly (e.g., ./script.sh), the kernel reads the first two bytes of the file. If they are #!, the rest of the line is treated as the path to the interpreter, and the kernel runs that interpreter with the script path as its first argument. Without a shebang, the kernel does not know how to execute the file directly; it must be invoked through an explicit interpreter, such as bash script.sh.

Question 5

Given a file data.txt, write a one-line command that prints the number of lines containing the word "warning".

Show answer
grep "warning" data.txt | wc -l
Or, using grep -c, which counts matching lines directly:
grep -c "warning" data.txt
Both approaches count matching lines, but the second is more efficient because it avoids the extra process and the pipe. In general, when a single command can do the job, prefer it over a pipeline.

08 Unit discussion

Discussion prompt

The UNIX shell was designed in an era when every interaction with a computer was textual. Today's environments are dominated by graphical interfaces and web-based tools, yet the shell remains the primary interface for system administration, software development, and cloud operations. Even web-based cloud consoles ultimately translate their clicks into API calls that map back to shell commands.

Why does the shell persist? Consider what properties of the command line make it resistant to replacement: composability, automation, scriptability, the low bandwidth required to transmit commands, and the fact that a shell session is inherently automatable. Are there contexts where the shell is genuinely inappropriate, or has it proven adaptable to every use case?

Post your response to the Unit 2 discussion forum and reply to at least two classmates. This activity supports CLO6 and CLO7.

09 Practice exercise

This ungraded exercise consolidates the lesson. Aim to spend about 45–60 minutes on it.

  1. Build a directory tree by hand. Using only mkdir, cd, and touch, create the following structure inside your home directory:
    comp3150/
    ├── lab1/
    │   ├── src/
    │   └── tests/
    ├── lab2/
    │   └── docs/
    └── notes.txt
    Then run find comp3150 -print to verify the structure.
  2. Use every redirection operator once. For each of >, >>, 2>, 2>&1, and <, write a small command that uses it and explain in one sentence what the operator did.
  3. Build a pipeline from memory. Without consulting the lesson, write a pipeline that lists every file in /usr/bin, filters to only those that contain the letter "g", and prints the count. Then check your answer against the pattern shown in the lesson.
  4. Write a script that summarises a directory. The script should take a directory path as its argument and print:
    • The number of files
    • The number of subdirectories
    • The total size in human-readable form
    Handle the case where no argument is supplied by printing a usage message and exiting with a non-zero status.
  5. Automate a measurement. Write a script that runs ls -l /usr/bin | wc -l five times, prints each result, and then prints the average. This is the shape of the measurement scripts you will write in later laboratories.
  6. Read a man page carefully. Run man find. Find the option that searches by file size. Write a command that finds every file under /var that is larger than 10 MB.

10 Summary

  • A shell is a user-space program that reads commands, interprets them, and executes them by forking child processes and calling execve.
  • The file system is a tree; paths are either absolute (starting from /) or relative (starting from the current working directory). . and .. refer to the current and parent directories.
  • Commands are found by searching the directories listed in the PATH environment variable.
  • The core file-management commands are ls, cd, pwd, touch, mkdir, cp, mv, and rm.
  • Standard input, standard output, and standard error are three separate streams. Redirection (>, >>, <, 2>, 2>&1) lets you control where each goes.
  • Pipes (|) connect the output of one command to the input of the next, allowing small tools to be composed into larger data-processing workflows.
  • The shell expands variables ($VAR), command substitutions ($(cmd)), arithmetic ($(( ... ))), and globs (*, ?, [...]) before running a command.
  • A shell script is a text file containing commands; the #!/bin/bash line identifies the interpreter, and the script becomes executable with chmod +x.

Looking ahead

Lesson 2.3 turns to users and groups — how Linux identifies who is using the system and how it groups users for the purpose of access control. Understanding users and groups is a prerequisite for understanding permissions in Lesson 2.4.

11 References

  • Stevens, W. R., & Rago, S. A. Advanced Programming in the UNIX Environment, 3rd ed., Sections 1.4–1.6. Addison-Wesley, 2013.
  • Shotts, W. The Linux Command Line, 5th ed., Chapters 1–6. No Starch Press, 2019. Freely available at linuxcommand.org.
  • Newham, C., & Rosenblatt, B. Learning the bash Shell, 3rd ed. O'Reilly, 2005.
  • Robbins, A., & Beebe, N. H. F. Classic Shell Scripting. O'Reilly, 2005.
  • Linux man pages: man 1 bash, man 1 ls, man 1 cp, man 1 grep, man 1 find, man 7 glob.
👤