October 20, 2026
Shell Variables: My First Steps Into Treating the Terminal Like a Programming Language
A write-up of my completion of the Shell Variables module in pwn.collegeβs Linux Luminarium β including every mistake I made along the way.

By David Wairimu
4 min read
Before this module, I thought of the terminal as a place where you type commands and get output. That's it. Type ls, see files. Type cat, see text. Simple tool.
Then pwn.college's Linux Luminarium introduced me to a reframing I didn't expect:
The Linux command line interface is actually a sophisticated programming language with which you can write actual programs. Because the command line is colloquially referred to as a "shell," programs written in this language are referred to as "shell scripts." When you're using the command line, you are basically writing a shell script line by line.
That single idea changed how I approached everything that followed. The Shell Variables module is where it clicked β variables are the first real "programming" primitive, and learning them meant learning why the shell behaves the way it does.
Here's what I achieved, level by level, and the mistakes I hit.
1. Printing Variables
The first task: the flag was sitting in a variable called FLAG, and I just needed to print it.
My first mistakes:
bash
$ /challenge/run
You cannot solve this challenge using /challenge/run.$ /challenge/run
You cannot solve this challenge using /challenge/run.Right β the challenge explicitly tells you the flag is in the variable, not in the program's output. Then:
bash
$ echo $flag$ echo $flagA blank line. Why? Variables are case-sensitive. flag and FLAG are two completely different variables, and only FLAG was set.
The fix:
bash
$ echo $FLAG
pwn.college{Eo4LcOTxd-18oMU1Wr-Q6EPp9ZQ.QX3UTN0wyN0czMzIzW}$ echo $FLAG
pwn.college{Eo4LcOTxd-18oMU1Wr-Q6EPp9ZQ.QX3UTN0wyN0czMzIzW}Lesson: $VAR triggers variable expansion. And case matters β always.
2. Setting Variables
Now I had to write to a variable: set PWN to COLLEGE.
My mistakes:
bash
$ VAR $COLLEGE
bash: VAR: command not found$ VAR $COLLEGE
bash: VAR: command not foundI put a space where there shouldn't be one. The shell read VAR as a command to run, not an assignment. Worse, $COLLEGE was never set, so it expanded to nothing.
Then:
bash
$ echo $VAR PWN
PWN$ echo $VAR PWN
PWNI forgot the $ on PWN, so it printed the literal word. And I'd also set the wrong variable entirely:
bash
$ VAR=COLLEGE # correct syntax, wrong variable name$ VAR=COLLEGE # correct syntax, wrong variable nameThe fix:
bash
$ PWN=COLLEGE
You've set the PWN variable properly!
pwn.college{IqGEi2gm78feZSSJvGitNXu0uXX.QX5UTN0wyN0czMzIzW}$ PWN=COLLEGE
You've set the PWN variable properly!
pwn.college{IqGEi2gm78feZSSJvGitNXu0uXX.QX5UTN0wyN0czMzIzW}Lesson: No spaces around =. No $ on the left side of an assignment. And the name has to match exactly.
3. Multi-Word Variables
The challenge: set PWN to COLLEGE YEAH.
My mistakes β this one took me a few tries:
bash
$ PWN VAR="COLLEGE YEAH"
bash: PWN: command not found$ PWN VAR="COLLEGE YEAH"
bash: PWN: command not foundThe space after PWN turned it into a command. The challenge even gave me a helpful hint: "You put spaces around your '=' in your assignment!"
Then I set the right value but the wrong variable name:
bash
$ VAR="COLLEGE YEAH" # wrong name
$ PWNVAR="COLLEGE YEAH" # also wrong β that's a variable called PWNVAR
$ ECHO PWN # commands are lowercase, and missing the $$ VAR="COLLEGE YEAH" # wrong name
$ PWNVAR="COLLEGE YEAH" # also wrong β that's a variable called PWNVAR
$ ECHO PWN # commands are lowercase, and missing the $The fix:
bash
$ PWN="COLLEGE YEAH"
You've set the PWN variable properly!
pwn.college{MFh8kNfFplgCtitO96MwpfLmGwq.QXwYTN0wyN0czMzIzW}$ PWN="COLLEGE YEAH"
You've set the PWN variable properly!
pwn.college{MFh8kNfFplgCtitO96MwpfLmGwq.QXwYTN0wyN0czMzIzW}Lesson: Spaces break assignments, so quote multi-word values. "COLLEGE YEAH" is one token; COLLEGE YEAH is two.
4. Exporting Variables
This was the conceptual highlight of the module. Set PWN=COLLEGE and export it, while setting COLLEGE=PWN without exporting it. The child process should see the first but not the second.
I jumped the gun:
bash
$ /challenge/run
Incorrect... You have not exported the PWN variable.$ /challenge/run
Incorrect... You have not exported the PWN variable.Of course β I ran the program before setting or exporting anything.
The fix:
bash
$ PWN=COLLEGE
$ export PWN
$ COLLEGE=PWN
$ /challenge/run
CORRECT! Here is your flag:
pwn.college{kingn6OaYa5qCG5T3br4QvNIuLj.QXyYTN0wyN0czMzIzW}$ PWN=COLLEGE
$ export PWN
$ COLLEGE=PWN
$ /challenge/run
CORRECT! Here is your flag:
pwn.college{kingn6OaYa5qCG5T3br4QvNIuLj.QXyYTN0wyN0czMzIzW}Lesson: A plain shell variable lives only in your current shell. export marks it as an environment variable, so child processes inherit it. That distinction β shell variable vs. environment variable β is fundamental, and this level made it tangible.
5. Printing Exported Variables
Now I learned env, which dumps every exported variable. Since FLAG was exported for this level, a single command revealed it:
bash
$ env | grep FLAG
FLAG=pwn.college{wzlgWCR5IgrbD5Un6kI12o3T7OK.QX4UTN0wyN0czMzIzW}$ env | grep FLAG
FLAG=pwn.college{wzlgWCR5IgrbD5Un6kI12o3T7OK.QX4UTN0wyN0czMzIzW}Lesson: echo $VAR reads any shell variable; env only shows exported ones. grep turns a wall of output into one line.
6. Storing Command Output (Command Substitution)
This is where the shell really started feeling like a programming language. The task: capture the output of /challenge/run into PWN.
My mistakes:
bash
$ FLAG=$(cat /FLAG)
cat: /FLAG: No such file or directory$ FLAG=$(cat /FLAG)
cat: /FLAG: No such file or directoryI assumed a flag file existed. There wasn't one β the flag came from running the program. Then:
bash
$ ECHO $PWN
bash: ECHO: command not found$ ECHO $PWN
bash: ECHO: command not foundUppercase echo again. The shell is unforgiving about case.
The fix:
bash
$ PWN=$(/challenge/run)
$ echo $PWN
pwn.college{4wJyDEOo_XJdRJL9svc3DIQg58z.QX1cDN1wyN0czMzIzW}$ PWN=$(/challenge/run)
$ echo $PWN
pwn.college{4wJyDEOo_XJdRJL9svc3DIQg58z.QX1cDN1wyN0czMzIzW}Lesson: $(...) is command substitution β it runs the command and replaces itself with the output. The older backtick syntax works too, but $() is preferred, especially for nesting. As pwn.college puts it, you should use $(blah) instead of blah.
7. Reading Input
A breath of fresh air β this one worked first try. Use read to put user input into a variable:
bash
$ read PWN
COLLEGE
You've set the PWN variable properly!
pwn.college{AdU4kjWYTMTajk1tUNWHDUqKah1.QX4cTN0wyN0czMzIzW}$ read PWN
COLLEGE
You've set the PWN variable properly!
pwn.college{AdU4kjWYTMTajk1tUNWHDUqKah1.QX4cTN0wyN0czMzIzW}Lesson: read consumes a line from standard input. Add -p "prompt: " for a friendly prompt, and -s to hide what's typed (useful for passwords).
8. Reading Files Without cat
The final level taught me to avoid the "Useless Use of Cat." Instead of VAR=$(cat file), redirect the file straight into read.
My mistakes:
bash
$ read some_file
You invoked 'read', but it looks like you didn't specify the PWN variable!$ read some_file
You invoked 'read', but it looks like you didn't specify the PWN variable!I confused the destination variable with the input source. Then I tried to execute the file itself:
bash
$ /challenge/read_me
bash: /challenge/read_me: Permission denied$ /challenge/read_me
bash: /challenge/read_me: Permission deniedIt isn't executable β I was supposed to read it, not run it.
The fix:
bash
$ read PWN < /challenge/read_me
You've set the PWN variable properly!
pwn.college{oxklB2O7MzpGGWUbAxcQV1IeDZc.QXwIDO0wyN0czMzIzW}$ read PWN < /challenge/read_me
You've set the PWN variable properly!
pwn.college{oxklB2O7MzpGGWUbAxcQV1IeDZc.QXwIDO0wyN0czMzIzW}Lesson: < redirects a file into stdin. Combined with read VAR, it's a clean, shell-native way to load a file into a variable β no external program needed.
What I Took Away
Across these eight levels, most of my errors fell into a handful of recurring categories:
MistakeCauseFixecho $flag prints nothingCase sensitivityecho $FLAGVAR $COLLEGE β command not foundSpace in an assignmentVAR=$COLLEGEPWN VAR="..." β command not foundSpace after variable namePWN="..."Wrong variable setName mismatch (VAR vs PWN)Match the exact nameECHO β command not foundShell commands are lowercaseecho/challenge/read_me β permission deniedTried to execute a non-executable fileread PWN < filecat /FLAG β no such fileAssumed a file existedUse the program's actual output
The bigger picture: the shell is a programming language. Variables, expansion, assignment, quoting, scoping (export), command substitution, input redirection, and reading β these are the primitives. Every one of them has syntax rules, and the shell is brutally literal about enforcing them. A stray space or a capitalized command is enough to break everything.
That's not a bug, though. It's what makes the shell predictable once you understand it β and it's exactly why the Linux Luminarium teaches it through trial and error.
Onward to the next module.
Follow along as I work through pwn.college's Linux Luminarium β one module, and many mistakes, at a time.