LFCS 6 ๐ง The Shell โ How It Works
The shell is the interface between the user and the kernel. Every command typed at a terminal is read, parsed, expanded, and executed by the shell. The LFCS exam is taken in a shell, and nearly every task on the exam is performed by typing commands into it. Understanding what the shell does with those commandsโhow it interprets them, expands them, and hands them to the kernelโis the foundation for everything else.
This chapter covers the shell’s operation: the interactive loop, the command execution path, the environment, the startup files, and the features that the exam relies on. You will learn what happens between pressing Enter and seeing output, how the shell finds the program to run, how the environment and variables work, and how the shell’s configuration files are loaded.
Key point: The shell is a program, not part of the kernel. It reads a line, performs expansions, resolves the command to a file, forks a child process, executes the command in the child, waits for it to finish, and prints the prompt again. The environment is a set of name-value pairs passed to every child process. Variables that are exported become part of the environment; variables that are not exported are shell-local.
Why understanding the shell matters
The exam problem. The LFCS is performance-based. Every task is performed in a shell. If the shell does not behave as expected, the task fails. A PATH that does not include a directory, a variable that is not exported, a startup file that overrides a settingโthese are shell-level problems that look like command-level problems.
The debugging problem. When a command “does not work,” the failure is often in the shell’s parsing, not in the command itself. A glob that expands to the wrong files, a variable that expands to an empty string, a quote that is missingโthese are shell issues. Understanding the shell’s expansion order is understanding where to look.
The configuration problem. The shell reads configuration files at login and at startup. Which files are read depends on whether the shell is a login shell or an interactive shell, and whether it is bash or zsh. The LFCS includes tasks that require setting an environment variable persistently, and the answer depends on which file the shell reads.
The scripting problem. Every shell command is a mini-program. A script combines them. The shell’s exit codes, conditionals, loops, and functions are the building blocks of the scripts that the exam expects you to read and write.
The environment problem. The environment is the set of variables that programs inherit. PATH, HOME, USER, LANG, EDITORโthese are read by programs to determine their behavior. Setting them correctly, and persisting the settings, is a routine task.
a. The shell’s execution loop
The shell is a loop: read, parse, expand, execute, wait, repeat.
$ ls -l /etc/hosts
-rw-r--r-- 1 root root 221 Oct 8 10:00 /etc/hosts
$
When the user types ls -l /etc/hosts and presses Enter:
- The shell reads the line from the terminal.
- It tokenizes the line into words:
ls,-l,/etc/hosts. - It performs expansions: no variables, no globs, no command substitution.
- It resolves the command:
lsis found in a directory inPATH. - It forks a child process.
- The child executes
/bin/lswith the arguments-land/etc/hosts. - The parent waits for the child to exit.
- The shell prints the prompt again.
The ls command is an external program. Some commands are built into the shellโcd, echo, export, read, set. They are executed by the shell itself because they affect the shell’s state. An external program runs in a child process and cannot change the parent’s state.
$ cd /tmp
$ pwd
/tmp
The cd command is a built-in. If it were an external program, it would change the child’s directory and exit, leaving the parent’s directory unchanged. The built-in runs in the shell, so the change persists.
To see whether a command is a built-in, use type.
$ type cd
cd is a shell builtin
$ type ls
ls is /usr/bin/ls
$ type -a echo
echo is a shell builtin
echo is /usr/bin/echo
The type -a shows all the places a command is found. The shell checks built-ins first, then functions, then aliases, then PATH.
The PATH variable is a colon-separated list of directories. The shell searches them in order when it does not find the command as a built-in.
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
The first matching executable is the one that runs. To see which file would run, use which.
$ which ls
/usr/bin/ls
$ which python3
/usr/bin/python3
The which command searches PATH and prints the first match. It does not check built-ins or aliases. Use type for the full picture.
b. Variables, the environment, and export
A shell variable is a name-value pair. It is created by assignment.
$ name="Alice"
$ echo "$name"
Alice
The variable name holds the string Alice. The $name is expanded by the shell before the echo command runs. The echo command receives the string Alice as its argument, not the variable name.
The distinction between a shell variable and an environment variable is export. A shell variable is local to the shell. An environment variable is passed to every child process.
$ name="Alice"
$ bash -c 'echo $name' # empty, not exported
$ export name
$ bash -c 'echo $name' # Alice
The bash -c runs a child shell. The child inherits the environment, not the shell’s local variables. The export promotes the variable to the environment, so the child sees it.
The env command prints the environment.
$ env
SHELL=/bin/bash
USER=alice
HOME=/home/alice
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
...
The env command runs a program with a modified environment. It is used in scripts to set variables for a single command.
$ env EDITOR=vim git commit
The EDITOR variable is set for the git commit command only. It does not affect the current shell.
To unset a variable, use unset.
$ unset name
$ echo "$name"
The variable is removed. The echo prints an empty line.
To see the value of a variable with a default, use ${var:-default}.
$ echo "${name:-Bob}"
Bob
$ name="Alice"
$ echo "${name:-Bob}"
Alice
The ${name:-Bob} expands to the value of name if it is set and non-empty, and to Bob otherwise. The ${name:=Bob} assigns the default if the variable is unset. The ${name:?error} prints an error and exits if the variable is unset.
The shell has special variables. $? is the exit code of the last command. $$ is the PID of the shell. $! is the PID of the last background process. $0 is the name of the script. $1, $2, … are the positional parameters. $# is the number of positional parameters. $@ and $* are all the positional parameters.
$ ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
$ echo $?
2
The exit code is 2, which indicates an error. The convention is that 0 means success and any non-zero value means failure.
$ true
$ echo $?
0
$ false
$ echo $?
1
The true command always exits 0. The false command always exits 1.
c. Shell startup files and options
The shell reads configuration files when it starts. Which files are read depends on the shell and how it is invoked.
For bash:
| Shell type | Files read |
|---|---|
| Login shell | /etc/profile, ~/.bash_profile, ~/.bash_login, ~/.profile |
| Interactive non-login | /etc/bash.bashrc, ~/.bashrc |
| Non-interactive | The file named by BASH_ENV |
A login shell is started when the user logs inโat the console, over SSH, or with bash --login. It reads the profile files. A non-login interactive shell is started when a terminal is opened in a graphical session or when bash is run without --login. It reads the bashrc files. A non-interactive shell is used to run a script. It reads neither, unless BASH_ENV is set.
For zsh, the files are /etc/zsh/zshenv, ~/.zshenv, /etc/zsh/zprofile, ~/.zprofile, /etc/zsh/zshrc, ~/.zshrc, /etc/zsh/zlogin, ~/.zlogin. The order is different, and the files serve different purposes. ~/.zshenv is read for every shell. ~/.zshrc is read for interactive shells.
The LFCS uses bash on both Ubuntu and CentOS. The startup files that matter for persistence are ~/.bashrc and ~/.bash_profile.
# ~/.bashrc
export EDITOR=vim
alias ll='ls -la'
The ~/.bashrc is read by interactive non-login shells. On Ubuntu, ~/.bash_profile sources ~/.bashrc if it exists. On CentOS, the same pattern is common. The result is that ~/.bashrc is read in most interactive contexts.
To make an environment variable available to all processes, it must be exported and set in a file that the shell reads. For a single user, ~/.bashrc or ~/.bash_profile. For all users, /etc/profile or /etc/profile.d/*.sh.
# /etc/profile.d/custom.sh
export EDITOR=vim
The /etc/profile.d/ directory contains scripts that are sourced by /etc/profile. This is the standard way to set system-wide environment variables.
Shell options change the shell’s behavior. The set command lists and sets them.
$ set -o
allexport off
braceexpand on
emacs on
errexit off
...
The set -e enables errexit, which causes the shell to exit when a command fails. The set -u treats unset variables as an error. The set -x prints each command before executing it, which is useful for debugging scripts. The set -o pipefail makes a pipeline fail if any command in it fails.
#!/bin/bash
set -euo pipefail
The shebang line and set -euo pipefail are the standard preamble for a robust bash script. They cause the script to exit on error, treat unset variables as errors, and fail pipelines when any command fails.
Complete Example Session
# ============================================
# PART 1: SHELL EXECUTION
# ============================================
$ ls -l /etc/hosts
$ type ls
ls is /usr/bin/ls
$ type cd
cd is a shell builtin
$ which ls
/usr/bin/ls
# ============================================
# PART 2: PATH
# ============================================
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
$ PATH="$HOME/bin:$PATH"
$ export PATH
# ============================================
# PART 3: VARIABLES
# ============================================
$ name="Alice"
$ echo "$name"
Alice
$ unset name
$ echo "$name"
# ============================================
# PART 4: EXPORT
# ============================================
$ name="Alice"
$ bash -c 'echo $name'
$ export name
$ bash -c 'echo $name'
Alice
# ============================================
# PART 5: SPECIAL VARIABLES
# ============================================
$ echo $?
0
$ echo $$
12345
$ echo $HOME
/home/alice
# ============================================
# PART 6: EXIT CODES
# ============================================
$ true
$ echo $?
0
$ false
$ echo $?
1
$ ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
$ echo $?
2
# ============================================
# PART 7: DEFAULT VALUES
# ============================================
$ echo "${name:-Bob}"
Bob
$ name="Alice"
$ echo "${name:-Bob}"
Alice
# ============================================
# PART 8: STARTUP FILES
# ============================================
$ cat ~/.bashrc
export EDITOR=vim
alias ll='ls -la'
$ source ~/.bashrc
$ echo $EDITOR
vim
# ============================================
# PART 9: SYSTEM-WIDE ENVIRONMENT
# ============================================
$ cat /etc/profile.d/custom.sh
export EDITOR=vim
$ echo $EDITOR
vim
# ============================================
# PART 10: SHELL OPTIONS
# ============================================
$ set -e
$ set -u
$ set -o pipefail
$ set -x
+ ls -l
The ten parts covered shell execution, PATH, variables, export, special variables, exit codes, default values, startup files, system-wide environment, and shell options.
Quick Reference
Command Types
| Type | Runs In | Example |
|---|---|---|
| Built-in | The shell | cd, echo, export |
| External | Child process | ls, grep, find |
| Function | The shell | User-defined |
| Alias | The shell | ll='ls -la' |
Variable Operations
| Operation | Syntax |
|---|---|
| Assign | name="value" |
| Read | $name or ${name} |
| Export | export name |
| Unset | unset name |
| Default | ${name:-default} |
| Assign default | ${name:=default} |
| Error if unset | ${name:?error} |
Special Variables
| Variable | Value |
|---|---|
$? | Exit code of last command |
$$ | PID of the shell |
$! | PID of last background process |
$0 | Script name |
$1โ$9 | Positional parameters |
$# | Number of positional parameters |
$@ | All positional parameters |
$HOME | Home directory |
$PATH | Command search path |
$USER | Current user |
Bash Startup Files
| Shell Type | Files |
|---|---|
| Login | /etc/profile, ~/.bash_profile |
| Interactive non-login | /etc/bash.bashrc, ~/.bashrc |
| Non-interactive | $BASH_ENV |
Shell Options
| Option | Effect |
|---|---|
set -e | Exit on error |
set -u | Error on unset variable |
set -x | Print commands |
set -o pipefail | Fail on pipeline error |
Best Practices
โ Do This:
# Use type to find what a command is
type ls # โ
# Export variables that programs need
export EDITOR=vim # โ
# Use ${var:-default} for safe expansion
echo "${name:-Bob}" # โ
# Check exit codes in scripts
if ! command; then echo "failed"; fi # โ
# Use set -euo pipefail in scripts
#!/bin/bash
set -euo pipefail # โ
# Put environment variables in ~/.bashrc
echo 'export EDITOR=vim' >> ~/.bashrc # โ
# Put system-wide variables in /etc/profile.d/
echo 'export EDITOR=vim' > /etc/profile.d/custom.sh # โ
โ Don’t Do This:
# Don't forget to export variables for child processes
name="Alice" # child does not see it # โ
# Don't use $name without quotes
echo $name # word splitting, globbing # โ ๏ธ
# Don't assume the shell reads ~/.bash_profile
# Interactive non-login shells read ~/.bashrc # โ ๏ธ
# Don't edit PATH without preserving it
PATH="/new/path" # loses the old PATH # โ
# Don't use set -e without understanding it
set -e # can exit unexpectedly on non-zero returns # โ ๏ธ
# Don't put aliases in scripts
alias ll='ls -la' # aliases are not expanded in scripts # โ ๏ธ
# Don't forget that cd is a built-in
# An external program cannot change the shell's directory # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Variable empty in child | Not exported | export name |
| Command not found | Not in PATH | Add to PATH or use full path |
~/.bashrc not read | Login shell reads profile | Source it or edit the profile |
| Environment not persistent | Set in the current shell only | Add to ~/.bashrc |
cd in script has no effect | Runs in a subshell | Source the script or use builtin cd |
| Exit code ignored | Not checking $? | Check $? or use && |
PATH overwritten | Assigned without $PATH | PATH="$HOME/bin:$PATH" |
Real-World Examples
1. Find a Command
type -a python3
which python3
2. Export a Variable
export EDITOR=vim
echo $EDITOR
3. Persist a Variable
echo 'export EDITOR=vim' >> ~/.bashrc
source ~/.bashrc
4. System-Wide Variable
sudo tee /etc/profile.d/custom.sh << 'EOF'
export EDITOR=vim
EOF
5. Check Exit Code
command
if [ $? -eq 0 ]; then echo "success"; fi
6. Default Value
echo "${EDITOR:-nano}"
7. Add to PATH
export PATH="$HOME/bin:$PATH"
8. Alias
alias ll='ls -la'
ll
9. Debug a Script
bash -x script.sh
10. Robust Script Preamble
#!/bin/bash
set -euo pipefail
Visual
Shell Execution Loop
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ USER TYPES: ls -l /etc/hosts โ
โ โ โ
โ โผ โ
โ 1. READ the line from the terminal โ
โ โ โ
โ โผ โ
โ 2. TOKENIZE into words: ls, -l, /etc/hosts โ
โ โ โ
โ โผ โ
โ 3. EXPAND variables, globs, command substitution โ
โ โ โ
โ โผ โ
โ 4. RESOLVE the command: built-in? alias? PATH? โ
โ โ โ
โ โผ โ
โ 5. FORK a child process โ
โ โ โ
โ โผ โ
โ 6. EXEC the command in the child โ
โ โ โ
โ โผ โ
โ 7. WAIT for the child to exit โ
โ โ โ
โ โผ โ
โ 8. PRINT the prompt โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Shell vs Environment Variable
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SHELL VARIABLE โ
โ โ
โ name="Alice" โ
โ Visible only to the current shell โ
โ Not passed to child processes โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ ENVIRONMENT VARIABLE โ
โ โ
โ export name="Alice" โ
โ Visible to the current shell and all child processes โ
โ Passed through fork and exec โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ PARENT SHELL โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ name="Alice" (shell variable) โ โ
โ โ export PATH (environment variable) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ fork + exec โ
โ โผ โ
โ CHILD PROCESS โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ $name โ empty (not exported) โ โ
โ โ $PATH โ inherited โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Bash Startup Files
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LOGIN SHELL (ssh, console, bash --login) โ
โ โ
โ /etc/profile โ
โ โโโ /etc/profile.d/*.sh โ
โ ~/.bash_profile โ
โ โโโ sources ~/.bashrc (common pattern) โ
โ ~/.bash_login (if no .bash_profile) โ
โ ~/.profile (if no .bash_profile or .bash_login) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ INTERACTIVE NON-LOGIN SHELL (terminal in GUI) โ
โ โ
โ /etc/bash.bashrc โ
โ ~/.bashrc โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ NON-INTERACTIVE SHELL (script) โ
โ โ
โ $BASH_ENV (if set) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Exit Codes
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ EXIT CODE CONVENTION โ
โ โ
โ 0 โ Success โ
โ 1 โ General error โ
โ 2 โ Misuse of shell built-in โ
โ 126 โ Command cannot execute โ
โ 127 โ Command not found โ
โ 128+ โ Fatal signal (128 + signal number) โ
โ โ
โ Check with: echo $? โ
โ Chain with: cmd1 && cmd2 (cmd2 runs if cmd1 succeeds) โ
โ Chain with: cmd1 || cmd2 (cmd2 runs if cmd1 fails) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Shell | Program that reads and executes commands |
| Built-in | Runs in the shell (cd, echo) |
| External | Runs in a child process (ls, grep) |
PATH | Colon-separated command search path |
type | Show how a command resolves |
which | Show the PATH location |
| Shell variable | Local to the shell |
| Environment variable | Exported, passed to children |
export | Promote to environment |
$? | Exit code of last command |
| Login shell files | /etc/profile, ~/.bash_profile |
| Interactive files | ~/.bashrc |
| System-wide | /etc/profile.d/*.sh |
Key takeaways:
- The shell is a program, not part of the kernel. It reads a line, parses it, expands it, resolves the command, forks a child, executes the command, waits, and prints the prompt. The loop is the interactive session.
- Built-ins run in the shell; external commands run in a child process.
cdmust be a built-in because it changes the shell’s state.lsis external because it does not. Usetypeto find out which a command is. - The
PATHvariable determines where external commands are found. The shell searches the directories inPATHin order. The first match runs. Adding a directory toPATHmakes its commands available. - Shell variables are local; environment variables are inherited. The
exportcommand promotes a shell variable to the environment. Child processes see exported variables, not shell-local ones. - The startup files depend on the shell type. A login shell reads
/etc/profileand~/.bash_profile. An interactive non-login shell reads~/.bashrc. A script reads neither, unlessBASH_ENVis set. - Exit codes indicate success or failure. 0 means success. Non-zero means failure. The
$?variable holds the exit code of the last command. Scripts use exit codes for conditionals. set -euo pipefailis the robust script preamble. It exits on error, treats unset variables as errors, and fails pipelines when any command fails.
Remember: The shell is the interface to the system, and the LFCS is taken entirely in the shell. Understanding what the shell does with a commandโhow it parses, expands, resolves, forks, and executesโis understanding where things go wrong when they do. The PATH determines what runs. The environment determines what programs see. The startup files determine what is configured. The exit codes determine what happens next. These are the fundamentals, and every task on the exam is built on them.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!