Shell and CLI

Shell and Bash Basics

Operating systems, shell commands, paths, streams, redirection, pipes, permissions, SSH, and Bash scripts.

Lecture File

slides/01_shell.pdf

Prerequisites

Comfort using a terminal is helpful, but the lecture builds from first principles.

Lecture Code

lecture_code/shell

1. Read Start with Big picture, then Deep study notes.
2. Trace Open the listed lecture-code files and follow the memory or stream state.
3. Check Use Pitfalls and Quick reference to catch common mistakes.
4. Practice Finish with the matching exam-practice deck.

01 Shell

Big Picture

  • This lecture treats the shell as a precise interface to the operating system, not as a magic text box that simply “runs commands.”
  • The key topics are command parsing, file paths, streams, permissions, executable lookup, and bash scripting.
  • The practical skill is prediction: before you press Enter, you should be able to explain what the shell will do, what the program will receive, and where output will go.

Core Model

  • The operating system manages access to hardware, provides a safe environment for running programs, and exposes the file system.
  • The shell is a text interface for asking the OS to do work.
  • A shell command is not one thing; it is a program name plus arguments plus shell syntax such as quoting, redirection, pipes, and substitutions.
  • Two commands that look similar can behave very differently because the shell is allowed to transform the command line before the program sees it.

What Happens When You Press Enter

  • The shell reads the line you typed.
  • It breaks the line into words, operators, and quoted sections.
  • It performs expansions in the order Bash defines.
  • It resolves redirections and pipes.
  • It finds the executable.
  • It launches the program with the chosen arguments and streams.

Why this order matters

  • The shell is not passing your raw text directly to the program.
  • It is preprocessing the line into a run-ready command description.
  • If you forget that, you will misdiagnose a lot of bugs.
  • Many “why did that happen?” shell questions are really “what did Bash do before my program started?” questions.

Reading Commands Precisely

  • In cat /usr/share/dict/words, the program name is cat.
  • /usr/share/dict/words is a command-line argument.
  • The operating system passes that string to the program.
  • Quoting the path, as in cat "/usr/share/dict/words", still gives exactly one argument.
  • The shell does not “understand” the argument semantically; it just decides where argument boundaries are.

Why quotes matter

  • Quotes control shell parsing, not program semantics.
  • If a path contains spaces, quotes stop the shell from splitting it into multiple arguments.
  • If you want literal text, quotes can also stop globbing and other expansions.
  • If you want a variable to expand but still remain one argument, use double quotes.

What the shell actually does

  • Bash first performs expansions and syntax processing.
  • It resolves quotes, variable references, command substitutions, globbing, and redirection.
  • Then it finds the program to execute.
  • Then it starts the program with a set of arguments and connected streams.
  • This order explains many confusing bugs: the shell may rewrite the line before your program ever runs.

Command-line arguments versus input

  • wc file.txt gives wc a filename argument.
  • wc < file.txt gives wc no filename argument and instead changes standard input.
  • The program sees different data, so the behavior is not identical.
  • This distinction matters everywhere in C and Python later in the course, because programs often have one path for arguments and another path for stdin.

Stream trace for wc

  • In wc file.txt, the shell does not open file.txt for the program. It simply tells wc the name of the file.
  • In wc < file.txt, the shell opens the file itself and connects it to stdin.
  • wc then reads from stdin as if the keyboard had been replaced by the file.
  • If the input comes from a pipe or redirect, the program may not know or care where it originally came from.

File Paths and Directory Thinking

  • An absolute path begins with / and starts at the root directory.
  • A relative path is interpreted from the current working directory.
  • . means the current directory.
  • .. means the parent directory.
  • pwd prints the current working directory.
  • cd changes the current working directory.

Tree model

  • The file system is naturally understood as a tree.
  • Directories are internal nodes.
  • Non-directory files are leaves.
  • A path is a route through that tree.
  • Thinking in trees helps with nested paths like /usr/share/dict/words and relative paths like ../../foo.txt.

Worked path trace

  • Suppose the current working directory is /usr/home/rob/cmput275/slides.
  • foo.txt means “the file named foo.txt in the current directory.”
  • ./foo.txt means the same thing.
  • ../foo.txt means the foo.txt file in the parent directory.
  • ../../././foo.txt still means the same as ../../foo.txt; the redundant . entries do not change the location.

Streams: stdin, stdout, stderr

  • Every program starts with three standard streams.
  • stdin is the input stream.
  • stdout is the normal output stream.
  • stderr is the error output stream.
  • On Unix, Ctrl+D signals end of input from the keyboard.

Why streams matter

  • Streams let programs work with text input and output without caring whether the data came from the keyboard, a file, a pipe, or a redirect.
  • Good command-line tools keep regular output on stdout and diagnostics on stderr.
  • That separation lets you save useful output while still seeing errors.

Redirection

  • > redirects stdout to a file.
  • < redirects stdin from a file.
  • 2> redirects stderr to a file.
  • > overwrites the target file if it already exists.

Worked stream traces

  • printOut.py > out.txt
  • stdout is redirected into out.txt.
  • stderr, if any, still goes to the terminal.
  • printErr.py 2> err.txt
  • stderr is redirected into err.txt.
  • stdout still goes to the terminal.
  • printBoth.py > out.txt 2> err.txt
  • stdout and stderr are split into separate files.
  • This is a clean way to prove the streams are independent.

What the lecture code is showing

  • printOut.py writes only to stdout, so > captures everything it produces.
  • printErr.py writes only to stderr, so 2> captures everything it produces.
  • printBoth.py makes the distinction visible in one run.
  • readInput.py demonstrates that stdin is just a stream; when no file is given, it can read what is typed or what another command sends through a pipe.

Input redirection versus filename arguments

  • wc file.txt
  • the program receives file.txt as an argument.
  • wc decides how to open and read it.
  • wc < file.txt
  • the shell opens file.txt and connects its contents to stdin.
  • wc just reads stdin.
  • This is a recurring exam idea: an argument is not the same thing as a stream.

Exit Status

  • Programs return exit statuses to the OS.
  • Zero usually means success.
  • Nonzero usually means failure.
  • The exact meaning is program-defined.
  • The shell stores the last exit status in $?.

Why exit status matters in shell work

  • Scripts can stop early when a command fails.
  • A pipeline or command chain can react differently depending on success.
  • $? is how you inspect the result of the most recent command after it finishes.
  • The course examples treat exit status as the program’s way of reporting success, failure, or custom meaning.

Why exit status matters

  • Exit status is how programs signal success or failure to the shell and to other scripts.
  • A script can branch on exit status to decide what to do next.
  • Later in the course, this becomes part of defensive shell scripting and automated testing.

Bash Variables, Quoting, and Expansion

  • Assignment uses x=10 with no spaces around =.
  • Bash variables are strings.
  • Reading a variable uses $x or ${x}.
  • Arithmetic uses $((...)).
  • $? stores the exit status of the last command.

The string model

  • Bash variables are not typed numeric containers the way C variables are.
  • The shell stores text and expands it when needed.
  • Arithmetic expansion is a special case that asks Bash to interpret the text as a number.
  • That is why quoting and expansion rules matter so much in shell code.

Mental model

  • Bash variables are not typed like C variables.
  • The shell stores text and expands it when needed.
  • Arithmetic expansion is a separate step; it is not the default meaning of +.

Quoting rules

  • Single quotes suppress shell expansion.
  • Double quotes suppress globbing but still allow most expansions.
  • If you want literal text, single quotes are the safest default.
  • If you want variables to expand but do not want word splitting or globbing, use double quotes.

Worked examples

  • echo "$x275" can be surprising if the shell tries to interpret x275 as a variable name.
  • echo "${x}275" makes the boundary explicit.
  • * on its own expands to matching filenames.
  • echo "*" prints a literal asterisk because the quotes suppress globbing.

A useful debugging habit

  • If a variable-based command behaves strangely, print the variable first.
  • Then print the command with quoting preserved.
  • Then decide whether the bug is expansion, globbing, or plain bad data.
  • Many shell bugs disappear when you check the actual value before the shell processes it.

Combining Commands

  • $(...) runs a command and substitutes its output inline.
  • mktemp creates a temporary filename and the file itself.
  • Pipes send stdout from the command on the left into stdin on the command on the right.
  • Unix tools are designed to be composed.

What command substitution really does

  • The shell runs the inner command first.
  • It captures the command’s stdout.
  • It substitutes that text into the outer command line.
  • This is why $(mktemp) can be embedded inside a larger command that needs a fresh path.

Worked examples

  • $(mktemp)
  • the shell runs mktemp.
  • it substitutes the resulting filename into the surrounding command.
  • this is useful when a command needs a unique temporary path.
  • cmd1 | cmd2
  • stdout from cmd1 becomes stdin for cmd2.
  • no temporary file is necessary.

Debugging habit

  • When a pipeline fails, test each stage by itself.
  • Then connect them one at a time.
  • This isolates whether the bug is in the producer, the consumer, or the stream connection.

Practical pipeline reasoning

  • The left command should emit the format the right command expects.
  • If the consumer expects lines and the producer emits one giant string, the pipeline may still run but produce nonsense.
  • If the producer prints diagnostics to stderr, a pipe will not carry them.
  • That separation is often useful when debugging because errors stay visible.

Executables, PATH, and Permissions

  • Bash searches directories listed in PATH when you run a command by name.
  • If a program is not in PATH, you must name it with a path such as ./myProgram.
  • ./ means “in the current directory.”
  • ls -l shows file permissions.
  • chmod changes permissions.
  • A file still needs execute permission before the kernel will run it as a program.

What the shell and kernel are doing

  • The shell decides which file should be executed.
  • The kernel decides whether the file may be executed.
  • If the file is a script, the shebang selects the interpreter.
  • If the file is a binary, the kernel loads it directly.

PATH failure mode

  • If you type programName and the shell says command not found, one possibility is that the executable exists but is not on PATH.
  • Another is that you forgot ./ for a local executable.
  • Another is that the file does not have execute permission.
  • These are different problems even if the error message feels similar.

What the machine is doing

  • The shell finds the executable file.
  • The kernel checks whether the file is executable.
  • If the file is a script, the shebang tells Unix which interpreter to launch.
  • If the file is a binary, the kernel loads and executes it directly.

Worked example

  • gcc produces binaries with the execute bit already set.
  • That is why you can run the result right away with ./a.out.
  • If you compile to hello_world, the shell still will not search the current directory automatically, so ./hello_world is the safe form.

Permissions thinking

  • r means read.
  • w means write.
  • x means execute.
  • chmod u+rw file adds read and write permission for the owner.
  • If you add a shebang but forget x, the script still will not run as a program.

Scripts and Remote Shells

  • A bash script is just a text file containing bash commands.
  • #!/bin/bash marks it for bash execution.
  • $1, $2, $#, and $0 expose script arguments and counts.
  • if, for, while, and functions let scripts express control flow.
  • ssh username@serverAddress opens a shell on a remote machine.
  • Temporary files on shared systems should be deleted promptly.

Worked trace: simplefor.sh

  • The loop iterates over One, Two, and Three.
  • On each iteration, x is set to the next word.
  • echo prints the current value.
  • The loop body is small, but it demonstrates the full control-flow structure of a bash script.

Worked trace: simpleWhile.sh

  • x=0 initializes the counter.
  • The test [ $x -le 10 ] checks whether the loop should continue.
  • x=$((x+1)) increments the counter with arithmetic expansion.
  • The loop stops when the condition becomes false.

Reading args.py, pyCat.py, and sillyAdder.py

  • args.py prints the raw argument list so you can see how Bash passed the command line into Python.
  • pyCat.py reads filenames from sys.argv[1:] and falls back to stdin when no filenames are present.
  • sillyAdder.py shows what a robust command-line program should do when it expects exactly two integers.
  • These scripts mirror the lecture’s shell concepts because they expose the program side of the shell/program boundary.

Lecture code connections

  • lecture_code/shell/args.py
  • prints sys.argv so you can see how Python receives shell arguments.
  • this is the direct analog of how C receives argc and argv.
  • lecture_code/shell/pyCat.py
  • reads files listed in sys.argv[1:].
  • if there are no arguments, it falls back to stdin.
  • this mirrors the shell distinction between filename arguments and redirection.
  • lecture_code/shell/sillyAdder.py
  • checks argument count before doing any work.
  • it is the right pattern when a tool expects exactly two numeric inputs.
  • lecture_code/shell/readInput.py
  • reads from stdin line by line.
  • it shows the program side of a pipeline.
  • lecture_code/shell/outVsErr/printOut.py
  • prints only to stdout.
  • lecture_code/shell/outVsErr/printErr.py
  • prints only to stderr.
  • lecture_code/shell/errPrints/printBoth.py
  • prints one line to each stream.
  • lecture_code/shell/shell_scripts/simplefor.sh
  • shows a fixed for loop.
  • lecture_code/shell/shell_scripts/simpleWhile.sh
  • shows a while loop and arithmetic expansion.

Common Failure Narratives

  • A student thinks wc < file.txt passes a filename. It does not; it changes stdin.
  • A student runs myProgram from the current directory and gets “command not found” because . is not searched automatically.
  • A student uses > when they meant to inspect output and accidentally overwrites a file.
  • A student uses single quotes when they intended variable expansion, or double quotes when they intended literal text.
  • A student writes a script that assumes a fixed number of arguments and then fails when it is called with the wrong count.
  • A student forgets that ssh moves them onto another machine, then edits the wrong copy of a file.

Exam Solution Patterns

  • If the question involves a command, split it into program, arguments, redirections, and expansions.
  • If the question involves a file path, decide whether it is absolute or relative and what directory the shell is currently in.
  • If the question involves output, ask which stream is being used.
  • If the question involves a script, check argument count, quoting, permissions, and interpreter selection.
  • If the question compares two similar commands, identify whether the difference is in the program’s arguments or in the shell’s handling of input and output.

Quick Reference

  • Current directory: pwd
  • Change directory: cd path
  • Print file: cat file
  • Stdout redirection: cmd > file
  • Stdin redirection: cmd < file
  • Stderr redirection: cmd 2> file
  • Pipe: cmd1 | cmd2
  • Temporary file: mktemp
  • Inline command substitution: $(cmd)
  • Bash variable assignment: x=10
  • Bash arithmetic: $((x + 1))
  • Last exit status: $?
  • Remote shell: ssh user@host
  • Make executable: chmod +x file
  • Show permissions: ls -l

Exam Questions

  • Q: What is the difference between wc file.txt and wc < file.txt?

A: The first passes a filename argument; the second feeds the file contents into stdin.

  • Q: What does 2> redirect?

A: Standard error.

  • Q: Why might ./program be required instead of program?

A: Because the current directory is not automatically searched through PATH.

  • Q: What does a pipe do?

A: It sends stdout from the command on the left to stdin of the command on the right.

  • Q: What does $(mktemp) do?

A: It runs mktemp and substitutes the resulting filename into the command.

  • Q: Why can printBoth.py > out.txt 2> err.txt separate the two outputs cleanly?

A: Because stdout and stderr are independent streams.

  • Q: What is the purpose of a shebang line?

A: It tells Unix which interpreter to use when the file is executed as a program.

  • Q: Why is echo "${x}275" not always the same as echo "$x275"?

A: Braces make the variable boundary explicit, so Bash does not try to interpret the combined text as a different variable name.

Built from summaries/01_shell.md and reviewed against slides/01_shell.pdf plus matching files in lecture_code/.