Learn › Linux Foundations › Bash Shell Environment
Final test: Chapter 8, Bash Shell Environment. Twenty-five objectives on a real Debian 12 server covering both parts: shell variables, env, printenv, export, unset, PATH, which and type, then alias, unalias, .bash_aliases, source, brace and tilde expansion and quoting. Fifty minutes, graded from what the programs record, from the files you create, and from the commands you run.
An alias works in one shell, then seems to disappear in the next. Find where this server loads aliases, make the definition available in the current shell, and check a new login. The same shell also expands braces, ~, variables, quotes and command output before a program receives its arguments.
Route: alias and startup files first, then expansions and quoting, then the chapter lab.
This is Part 2 of 2. Part 1 covered Sections 8.1 to 8.10, the values a shell holds and the way it finds a program. This part covers Sections 8.11 to 8.18: naming your own commands, the files that make a setting survive a login, the rewrites the shell performs on your line before anything runs, and command substitution, which puts the output of one command into the line of another. It ends in the chapter lab, which covers Sections 8.1 to 8.17.
What you already know: Part 1 showed that the shell rewrites your line before running it, replacing $NAME with a value. Every section here is another rewrite the shell does, or a way to control it. Chapter 5 read files with cat and head, and Chapter 6 copied them with cp; both tools appear again here.
Objectives
After this part you can:
Before you start a machine, each exercise shows a rehearsal terminal with recorded output. It checks the requested command but does not change Linux files. If your Part 1 practice machine is still running, continue on it; otherwise start a practice machine here. Commands run there, so an unintended command can still change it even if the lesson does not mark the exercise complete.
While the machine is running, files you created remain on it, but a new connection may start a fresh shell. Check which aliases and variables are present before using them; Section 8.14 teaches how to load installed aliases into the shell you are using. The final Test Room starts a separate prepared server and checks its objectives automatically. It covers Sections 8.1 to 8.17, while command substitution in Section 8.18 is lesson practice; completing a rehearsal exercise is not a Test Room result.
Five questions before the material. A wrong answer costs nothing: the explanation states the fact and names the section that teaches it.
>>> An alias is held by the running shell. Another shell, and the next login, know nothing about it until a file defines it again. Sections 8.11 and 8.13.
>>> A login Bash reads ~/.profile on this prepared server. That file is configured to read ~/.bashrc and then ~/.bash_aliases, so the alias is defined at each new login. Bash does not automatically read ~/.bash_aliases in every kind of new shell. Section 8.13.
>>> Running a file starts a separate process, and its definitions die with it. source reads the file into the running shell, so what it defines stays. Section 8.14.
>>> Brace expansion happens in the shell, before the command runs. The command receives three separate arguments and never sees a brace. Section 8.15.
>>> Single quotes hand every character through untouched. Double quotes still let a dollar name be replaced, and with no quotes the shell replaces it too. Section 8.17.
An alias is a word you define as a stand-in for a longer command. When the word is the first thing on your line, the shell replaces it with its definition, then runs the result. The definition goes in quotes so the shell stores it instead of running it now. An alias lives in the shell, not on disk, so this works wherever you stand. Define ll as ls -l:
prompt: admin@server1:~$ answer: alias ll='ls -l' ||| alias ll="ls -l" output: hint: Section 8.11. The word alias, the new name ll, an equals sign, then ls -l inside single quotes.
Silent. The alias exists in this shell from now until you remove it or the shell ends. Nothing was written to disk.
alias with a name and no equals sign prints that definition back. Ask for ll:
prompt: admin@server1:~$ answer: alias ll output: alias ll='ls -l' hint: Section 8.11. The same word as Exercise 8-34, alias, then just the name ll.
The shell prints the definition in the form you could type back, with single quotes around the value whichever kind of quotes you used.
Anything you type after the alias is added to the end of the definition, so ll takes the same arguments ls does. List ~/logs through it; the home-rooted path works from any directory:
prompt: admin@server1:~$ answer: ll ~/logs ||| ll logs ||| ll /home/admin/logs ||| ll ~/logs/ output: total 1036 -rw-r--r-- 1 admin admin 791424 Jan 15 2026 access.log -rw-r--r-- 1 admin admin 49980 Jan 15 2026 auth.log -rw-r--r-- 1 admin admin 17000 Jan 15 2026 cron.log -rw-r--r-- 1 admin admin 192090 Jan 15 2026 error.log hint: Section 8.11. The short name you defined, ll, then the directory ~/logs.
The long listing from Section 2.4, reached by two letters. The shell replaced ll with ls -l and added your argument, so the command that ran was ls -l ~/logs. An alias is a text replacement of the first word, nothing more.
Ask what kind of word ll is now that the alias exists:
prompt: admin@server1:~$ answer: type ll output: ll is aliased to `ls -l' hint: Section 8.10. The command that reports what kind of word a name is, then the name ll.
type reports an alias as an alias and shows what it stands for. which would find nothing, because an alias is not a file. This is why type is the tool to reach for when a command does something you did not expect.
unalias takes the definition back. The name becomes an ordinary word again, and the shell looks for a program with that name as usual. Remove ll:
prompt: admin@server1:~$ answer: unalias ll output: hint: Section 8.12. The word unalias, then the name of the alias, ll.
Silent. From here the word ll means nothing to the shell, and typing it produces the same command not found message Part 1 met in Exercise 8-19. The step Errors you will meet quotes it.
Every setting so far lived in one shell and ended with it. Bash reads files of commands when it starts, and whatever those files contain happens by itself. Which file it reads depends on the kind of shell.
A login shell is the shell a session starts with, the kind Section 7.5 defined. It is what a connection over SSH gives you. Bash reads /etc/profile first, then the first one of your own that exists out of ~/.bash_profile, ~/.bash_login and ~/.profile. It stops at the first one it finds, so creating ~/.bash_profile on a machine that has ~/.profile means ~/.profile is never read again.
An interactive shell is one you type into. An interactive shell that is not a login shell, such as one you start by typing bash inside a session you already have, reads ~/.bashrc.
Debian joins the two. The ~/.profile it ships reads ~/.bashrc, so a login shell on this server reaches that file as well. The file begins by naming the case it was written for. Read the first five lines of ~/.bashrc; the path is home-rooted, so it reads the same from any directory:
prompt: admin@server1:~$ answer: head -5 ~/.bashrc ||| head -n 5 ~/.bashrc ||| head -5 /home/admin/.bashrc output:
hint: Section 5.3. The command that prints the first lines of a file, its count option with 5, then the path ~/.bashrc.
The first line names the non-login case, and Debian's ~/.profile is what brings the file into a login shell as well. The fifth line is the other half of the rule: the file stops early when the shell is not interactive, so a script that runs bash gets none of it.
The rest is ordinary shell commands, run before your first prompt. Bash does not read ~/.bash_aliases by itself; a startup file must source it. Debian's ~/.bashrc has a line that reads that file when it exists. On this prepared server, ~/.profile reads ~/.bashrc and then sources ~/.bash_aliases, so the aliases are defined at each new login. A new interactive non-login Bash reads ~/.bashrc but not ~/.profile; do not assume it has the same aliases. Use source ~/.bash_aliases to load them into the shell you are already using.
The tools directory holds a file of alias definitions, ready to be installed under that name. You are in your home directory. Read tools/aliases.sh:
prompt: admin@server1:~$ answer: cat tools/aliases.sh ||| cat ~/tools/aliases.sh output:
alias ll='ls -l' alias la='ls -la' alias releases='ls ~/website/releases' alias backups='ls -lh ~/backups' hint: Section 5.1. The printing command, then the path tools/aliases.sh (or ~/tools/aliases.sh from anywhere).
Two comment lines, which the shell ignores, and four alias definitions in the form of Exercise 8-34. A file like this is the normal way an engineer carries a working environment from machine to machine.
You are in your home directory. Look for ~/.bash_aliases, the file the startup files read:
prompt: admin@server1:~$ answer: ls ~/.bash_aliases ||| ls /home/admin/.bash_aliases output: ls: cannot access '/home/admin/.bash_aliases': No such file or directory hint: Section 2.2. The listing command, then the path ~/.bash_aliases, written with the tilde so it names the same file from anywhere.
The message names the path the tilde stood for, which is one way to see what the shell did to your line before ls ran. Nothing reads ~/.bash_aliases yet, because there is nothing to read.
Section 8.14 covers source, the builtin that reads a file into the shell you are typing in. Ask it to read ~/.bash_aliases now, before the file exists, and the message names the path:
prompt: admin@server1:~$ answer: source ~/.bash_aliases ||| . ~/.bash_aliases ||| source /home/admin/.bash_aliases output: bash: /home/admin/.bash_aliases: No such file or directory hint: Section 8.14. The word source, then the path ~/.bash_aliases.
The shell reports it, because source is part of the shell. It reads a file; it does not create one.
You are in your home directory. Copy tools/aliases.sh to ~/.bash_aliases:
prompt: admin@server1:~$ answer: cp tools/aliases.sh ~/.bash_aliases ||| cp ~/tools/aliases.sh ~/.bash_aliases ||| cp tools/aliases.sh /home/admin/.bash_aliases output: hint: Section 6.7. The copying command, the source tools/aliases.sh (or ~/tools/aliases.sh from anywhere), then the destination ~/.bash_aliases.
Silent, as cp is. The file now exists under the name the startup files look for, so every login from now on defines those four aliases. The shell you are in was started before the file existed, and knows nothing about it yet.
source is a builtin: it reads a file and runs every line of it in the shell you are typing in, rather than in a separate process. That is what makes its definitions stay.
Running bash ~/.bash_aliases instead is a different act. Bash starts a separate shell, that shell reads the lines and defines aliases only for itself, then exits and takes those definitions with it. This form asks Bash to read the file, so the file does not need execute permission. Nothing the separate shell defined reaches your current shell. Read ~/.bash_aliases into this shell:
prompt: admin@server1:~$ answer: source ~/.bash_aliases ||| . ~/.bash_aliases ||| source /home/admin/.bash_aliases output: hint: Section 8.14. The same command as Exercise 8-42, source and the path ~/.bash_aliases, now that the file is there.
Silent. Every line of the file ran in this shell, so the four aliases exist here now. A single dot is the same builtin under an older name, and . ~/.bash_aliases does exactly the same thing.
List every alias the shell holds. The word takes no arguments, so it answers the same wherever you stand:
prompt: admin@server1:~$ answer: alias output: alias backups='ls -lh ~/backups' alias la='ls -la' alias ll='ls -l' alias releases='ls ~/website/releases' hint: Section 8.11. The word alias on its own, with no name after it.
Four definitions in name order, including ll, which the file defined again after you removed it in Exercise 8-38.
Run one of them. releases lists ~/website/releases, so it works from any directory:
prompt: admin@server1:~$ answer: releases output: 2026-06-01 2026-07-15 2026-08-01 hint: Section 8.14. One word: releases, the name the file defined for the listing of ~/website/releases.
The three dated release directories from Chapter 2. The word releases is not a program anywhere on this machine; the shell replaced it with its definition and ran that.
Ask what kind of word releases is:
prompt: admin@server1:~$ answer: type releases output: releases is aliased to `ls ~/website/releases' hint: Section 8.10. The command that reports what kind of word a name is, then the name releases.
The definition the file installed. A name that behaves unexpectedly on a machine you did not set up is often an alias, and this is the command that shows it.
Brace expansion turns one word into several. The shell repeats the text around the braces once for each item inside them, separated by commas. It happens before the command runs, so the command receives ordinary separate arguments. Nothing is read from disk, so it works wherever you stand. echo shows what the shell built from report-{a,b,c}.txt:
prompt: admin@server1:~$ answer: echo report-{a,b,c}.txt output: report-a.txt report-b.txt report-c.txt hint: Section 8.15. The printing command, then one word made of the fixed start report-, the three letters a,b,c inside braces separated by commas, and the fixed ending .txt.
Three words from one. The text before the braces and the text after them is repeated for every item. No file was touched: this is the shell rewriting your line, and echo simply printed the result.
Two dots between a first and a last value make a range instead of a list. Expand {1..5}:
prompt: admin@server1:~$ answer: echo {1..5} output: 1 2 3 4 5 hint: Section 8.15. The printing command, then 1, two dots and 5 inside braces.
Five words. Ranges work on letters too, and a range of dates or numbered directories is the usual reason to reach for one.
Anything the shell builds this way can be handed to a real command. You are in your home directory, and tools/reports is empty. Create 2026-01.txt, 2026-02.txt and 2026-03.txt in it with one command:
prompt: admin@server1:~$ answer: touch tools/reports/2026-0{1,2,3}.txt ||| touch ~/tools/reports/2026-0{1,2,3}.txt ||| touch tools/reports/2026-{01,02,03}.txt ||| touch ~/tools/reports/2026-{01,02,03}.txt output: hint: Section 8.15. The file-creating command from Section 6.1, then tools/reports/2026-0{1,2,3}.txt (or ~/tools/reports/2026-0{1,2,3}.txt from anywhere).
Silent, as touch is. The shell handed touch three separate paths, so touch created three files without knowing a brace was involved.
You are in your home directory. List tools/reports:
prompt: admin@server1:~$ answer: ls tools/reports ||| ls ~/tools/reports ||| ls tools/reports/ output: 2026-01.txt 2026-02.txt 2026-03.txt hint: Section 2.2. The listing command, then the path tools/reports (or ~/tools/reports from anywhere).
Three files, one command. Typing them out one at a time would have produced exactly the same result and three chances to make a typing mistake.
A range builds directories the same way. You are in your home directory. Create q1, q2 and q3 inside tools/reports with one command:
prompt: admin@server1:~$ answer: mkdir tools/reports/q{1..3} ||| mkdir ~/tools/reports/q{1..3} output: hint: Section 6.2. The directory-making command, then tools/reports/q{1..3} (or ~/tools/reports/q{1..3} from anywhe
Practice Chapter 8, Part 2. Bash Shell Environment: alias, startup files, source, expansion and quoting in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.