How Do I Configure My Shell Environment?
Environment variables, PATH, aliases, and setting up your C development toolchain
After this lesson, you will be able to:
- Explain what the
PATHvariable is and how the shell uses it to find commands - View and set environment variables with
echo,export, andenv - Modify your shell configuration in
~/.bashrcand apply changes withsource - Understand the difference between shell variables and environment variables
- Add directories to
PATHand make changes persistent across sessions
Why Does ./myprogram Work but myprogram Doesn’t?
You compile your first C program:
gcc -Wall -o hello hello.c
You try to run it:
hello
Command 'hello' not found
But this works:
./hello
Hello, world!
The program is right there. Why does the shell find it with ./ but not without? The answer involves something called the PATH — an environment variable that controls where the shell looks for programs. Understanding PATH (and environment variables in general) is the key to making your shell work the way you want.
hello.c and the executable hello is in your current directory. Why does hello give "command not found" but ./hello works?hello, the shell searches each directory in your PATH variable (like /usr/bin, /usr/local/bin) but NOT the current directory. This is a security feature — it prevents accidentally running a malicious program in the current directory. ./hello explicitly says "run the hello in this directory."
Variables, PATH, and Customization
Environment Variables
The shell maintains a collection of variables that store configuration information. Programs can read these to adapt their behavior.
See them all:
printenv | head -10
Or look at specific ones:
echo $HOME # /home/student
echo $USER # student
echo $SHELL # /bin/bash
echo $PWD # Current working directory
From Java: In CSCD 210, Gradle handled build configuration through
build.gradle. Environment variables are Unix’s equivalent — they configure how programs behave, where to find things, and what defaults to use. In C, you’ll read them withgetenv("HOME").
Creating Variables
Shell variables are local to your current session:
MY_NAME="Jessica Doner"
echo $MY_NAME
Environment variables are visible to child processes (programs you launch):
export GREETING="Hello from the shell"
echo $GREETING
The difference matters: a shell variable is only visible in your current shell. An exported environment variable is passed to every program you run from that shell.
Common Pitfall: Variable assignment has NO SPACES around
=.X=5works.X = 5does NOT — bash interprets it as a commandXwith arguments=and5. This is the #1 syntax error for shell beginners.
CC=gcc in your shell and then run a Makefile that references $(CC). The Makefile can't find the variable. What did you forget?export, a variable is local to your shell session — child processes (like make) don't inherit it. export promotes a shell variable to an environment variable, making it visible to every program launched from that shell. This is the same distinction C's getenv() relies on.
The PATH Variable
PATH is a colon-separated list of directories where the shell searches for commands:
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
When you type gcc, the shell searches these directories left to right:
- Check
/usr/local/sbin— not there - Check
/usr/local/bin— not there - Check
/usr/sbin— not there - Check
/usr/bin— found it! → runs/usr/bin/gcc
which gcc # /usr/bin/gcc — shows where it was found
Why ./hello Is Required
The current directory (.) is NOT in your PATH by default. This is a security feature.
Imagine someone put a malicious program named ls in a shared directory. If . were in PATH, running ls there would run the malicious version instead of /usr/bin/ls. By requiring ./hello, you’re explicitly saying “run the program here, not from PATH.”
Key Insight: The
./prefix explicitly tells the shell “look in the current directory.” Without it, the shell only searches the PATH directories. This is intentional security — never add.to your PATH on a shared system.
Why does this matter?
Every single lab in this course requires running your compiled program with ./programname. If you don’t understand why the ./ is necessary, you’ll waste time troubleshooting “command not found” errors on perfectly good executables. This is also your first taste of how Unix treats security as a default, not an afterthought.
/usr/local/bin:/usr/bin:/bin. You type python3 and it runs /usr/bin/python3. Then you install a newer Python to /usr/local/bin/python3. Which version runs when you type python3 now?/usr/local/bin comes before /usr/bin in the PATH, the newer installation is found first. This is exactly why /usr/local/bin is listed first — it's where user-installed programs go, and they should take priority over system defaults. Use which python3 to verify which one the shell finds.
Shell Expansion
Before your command runs, the shell performs several expansions. Understanding these helps you write more efficient commands.
Brace expansion — generates sequences:
echo {a,b,c} # a b c
echo file{1,2,3}.txt # file1.txt file2.txt file3.txt
echo {01..05} # 01 02 03 04 05
mkdir -p project/{src,test,docs} # Creates 3 directories at once
Tilde expansion:
echo ~ # /home/student
echo ~/cscd240 # /home/student/cscd240
Arithmetic expansion:
echo $((2 + 3)) # 5
echo $((100 / 7)) # 14 (integer division)
echo $((2 ** 10)) # 1024
Command substitution — use a command’s output as text:
echo "Today is $(date)"
file_count=$(ls | wc -l)
echo "Found $file_count files"
Quoting: Controlling Expansion
Double quotes — allow variable and command substitution, suppress wildcards:
echo "My home is $HOME" # My home is /home/student
echo "Files: *.txt" # Files: *.txt (no glob expansion)
echo "Today is $(date +%A)" # Today is Friday
Single quotes — suppress ALL expansion:
echo 'My home is $HOME' # My home is $HOME (literal)
echo 'Today is $(date)' # Today is $(date) (literal)
Key Insight: Double quotes allow
$variableand$(command)expansion but suppress filename globbing. Single quotes suppress everything — what you see is what you get. When in doubt, use double quotes.
echo "There are $(ls | wc -l) files in $PWD" print?$PWD becomes the current directory path) and command substitution ($(ls | wc -l) runs the pipeline and substitutes the result). If this were in single quotes, you'd get option A — everything printed literally. Pipes work fine inside $() because the subshell processes the entire pipeline.
Deep dive: Shell expansion order — what happens before your command runs
The shell performs expansions in a specific order. Understanding this order explains why some combinations work and others don’t:
- Brace expansion —
{a,b,c}→a b c(happens first, before any variable substitution) - Tilde expansion —
~→/home/student - Parameter/variable expansion —
$HOME→/home/student - Command substitution —
$(date)→Fri Mar 21 ... - Arithmetic expansion —
$((2+3))→5 - Word splitting — results are split on spaces/tabs/newlines
- Pathname expansion (globbing) —
*.c→main.c hello.c
This order explains some surprising behaviors:
# Braces expand BEFORE variables, so this doesn't work:
FILES="a,b,c"
echo {$FILES} # Prints: {a,b,c} — brace expansion already happened
# But this works because the variable is used in globbing (step 7):
EXT="c"
ls *.$EXT # Lists all .c files — variable expands at step 3, glob at step 7
# Double quotes suppress steps 6 and 7 (word splitting and globbing):
echo "$HOME" # /home/student — variable expanded, no splitting
echo '$HOME' # $HOME — single quotes suppress ALL expansion
The key takeaway: double quotes let variables and commands expand but prevent accidental word splitting and globbing. Single quotes suppress everything. When you’re unsure, double-quote your variables: "$variable".
Escaping — the backslash suppresses the next character’s special meaning:
echo "The price is \$5.00" # The price is $5.00
Aliases: Command Shortcuts
Aliases let you create shortcut names for commands:
alias ll='ls -la'
alias rm='rm -i' # Always ask before deleting
alias compile='gcc -Wall -o'
Now ll runs ls -la, and rm asks for confirmation. Check your aliases:
alias # List all aliases
type ll # ll is aliased to 'ls -la'
unalias ll # Remove alias
But there’s a problem: aliases defined at the command line disappear when you close the terminal.
Making It Permanent: ~/.bashrc
The file ~/.bashrc runs every time you open a new terminal. This is where you put your customizations:
nano ~/.bashrc
Scroll to the bottom and add:
# My CSCD 240 customizations
alias ll='ls -la'
alias rm='rm -i'
alias compile='gcc -Wall -Wextra -std=c17 -o'
export EDITOR=nano
export CC=gcc
export CFLAGS="-Wall -Wextra -std=c17"
Save and reload:
source ~/.bashrc
The Trick:
source ~/.bashrcre-reads your config file without opening a new terminal. You’ll use this every time you add a new alias or variable. It’s the equivalent of restarting your IDE after changing settings — except it takes less than a second.
Now those aliases and variables are available in every future terminal session.
Shell Startup Files
There are two main config files:
~/.bash_profile— runs on login (SSH sessions, first terminal)~/.bashrc— runs when opening a new terminal window
On Ubuntu, ~/.bashrc is what matters for daily customization. If you’re confused about which to edit, edit ~/.bashrc.
From Java: In CSCD 210, Gradle’s
build.gradleconfigured your project. In C development, your shell environment is your build configuration. SettingCC=gccandCFLAGS="-Wall -Wextra"is equivalent to configuring compiler settings in your build tool. Your~/.bashrcis your personal build configuration file.
Setting Up for C Development
Here’s a practical ~/.bashrc setup for this course:
# Compiler settings
export CC=gcc
export CFLAGS="-Wall -Wextra -std=c17"
# Convenient aliases
alias ll='ls -la'
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
# Quick compile shortcut
alias ccompile='gcc -Wall -Wextra -std=c17 -o'
With these in place, your C development workflow becomes:
cd ~/cscd240/labs/lab4
nano water_bill.c # Edit
ccompile water_bill water_bill.c # Compile with all warnings
./water_bill # Run
Quick Check: What's the difference between MY_VAR="hello" and export MY_VAR="hello"?
Without export, MY_VAR is a shell variable — only visible in the current shell. With export, it becomes an environment variable — visible to any program you launch from that shell. In C, getenv("MY_VAR") only sees exported variables.
Quick Check: Why is the current directory (.) not in PATH by default?
Security. If . were in PATH, a malicious program with the same name as a common command (like ls) in any directory you visit could hijack that command. Requiring ./program is explicit and intentional.
Quick Check: What does source ~/.bashrc do?
It re-reads and applies your ~/.bashrc configuration file in the current shell session. Without it, changes to ~/.bashrc only take effect when you open a new terminal window.
Quick Check: What's the output of echo 'Home: $HOME'?
Literally Home: $HOME. Single quotes suppress all variable expansion. To see the actual home directory, use double quotes: echo "Home: $HOME".
Your Shell Is Now Yours
You’ve gone from a blank terminal to a personalized development environment. Your ~/.bashrc has aliases that save typing, compiler variables that enforce good habits (-Wall), and safety nets (rm -i). This is how every professional Unix developer works — they customize their shell once and carry those settings everywhere.
And with that, Series 1: Unix Foundations is complete. You can navigate the file system, read documentation, control permissions, redirect I/O, build pipelines, and configure your environment. That’s the entire Unix toolkit you need to start writing C.
In Series 2: C Foundations, you’ll write your first C program, learn how gcc turns source code into an executable, and start translating your Java knowledge into C. The shell you’ve been configuring is about to become your daily driver.
Ready to test your Unix skills? The CTF Challenges start with shell and file system puzzles that use everything from this series.
Big Picture: The six skills from this series — navigation, documentation, permissions, redirection, pipes, and environment — are the foundation for every lab in this course. When a lab says “compile with warnings enabled,” you know that means
gcc -Wall. When it says “redirect output to a file,” you know>. When something breaks, you know how to read man pages and check permissions. These aren’t just Unix trivia — they’re your development workflow for the next nine weeks.