Lesson 02 / 20
What Is a Shell
The prompt, splitting the command line into words, performing expansions before the command runs, option forms, builtins, and exit status.
Contents
The previous lesson defined the shell as a replaceable program that interprets commands. That definition does not yet explain anything: what exactly does the shell do with a typed line?
The answer also explains the command line’s most common mistakes. Why a filename with a space splits into two separate arguments, who actually interprets the asterisk, how it is known whether a command succeeded — all of these are the result of a single parsing process.
The Prompt
The shell signals that it is waiting for a command with a prompt. The prompt’s content is configurable; the username, machine name, and current directory are commonly shown. In this course’s examples, the prompt is shown as a single character:
$ echo Hello Hello
Lines starting with $ are the typed command; the lines below them are the
command’s output. Documentation carries an established convention: the
ordinary user prompt is shown as $, the superuser prompt as #. If a
command starting with # is seen in a document, it is understood that the
command requires administrative privilege.
The Line Is Split Into Words
The shell first splits the line into words. The split is done on whitespace characters, and consecutive spaces count as a single separator. The first word is the name of the program to run; the remaining words are passed to it as arguments.
$ echo Hello world Hello world
The output has one space instead of four. This is not because the echo
command compresses spaces: echo received two arguments — Hello and
world — and printed them joined by a single space. The spaces between them
were gone before echo even ran.
The argument count can be counted directly:
$ file="measurement 01.csv" $ set -- $file; echo $# 2 $ set -- "$file"; echo $# 1
$# gives the argument count. When the variable’s value is written without
quotes, the shell split it into two words; written inside quotes, it left it
as a single word. This is why filenames with spaces must be written in
quotes; otherwise the command looks for two files that do not exist.
The same difference can be shown with a print format that makes word boundaries visible:
$ printf '[%s]\n' $file [measurement] [01.csv] $ printf '[%s]\n' "$file" [measurement 01.csv]
Expansions Happen Before the Command
Splitting into words is not the only operation. Before the program runs, the
shell applies several expansions to the words. Three are used in this
course: variable expansion ($name), filename expansion (*, ?), and
home directory expansion (~).
The decisive point is this: the shell performs the expansion, not the program that runs. The program sees only the result of the expansion.
$ echo /usr/bin/zc* /usr/bin/zcat /usr/bin/zcmp $ ls /usr/bin/zc* /usr/bin/zcat /usr/bin/zcmp
Both commands produced the same two filenames. The * character never
reached the ls command; the shell replaced the pattern with matching
filenames, and ls received two ready-made arguments. The only difference
between echo’s output and ls’s is that ls performs column alignment.
This has two consequences. First, if the pattern matches no file, the shell
leaves it unchanged and the program receives a meaningless name containing
*. Second, and more important, what a command will do can be seen in
advance: putting echo in front of a command shows the argument list the
shell will produce, without running it. This will become a safety habit in
the deletion lesson.
Quoting also stops expansion:
$ echo "/usr/bin/zc*" /usr/bin/zc*
Double quotes block filename expansion and word splitting, but not variable expansion. Single quotes block all of them: text inside single quotes is preserved literally.
Options and Arguments
A command line’s arguments are of two kinds. An option changes the command’s behavior and, by convention, starts with a dash; the rest are the objects the command will operate on.
Options have three written forms, and all three give the same result:
$ ls -l -a /home total 16 drwxr-xr-x 1 root root 4096 Jul 26 18:53 . drwxr-xr-x 1 root root 4096 Jul 26 18:46 .. drwxr-x--- 3 student student 4096 Jul 26 18:49 student $ ls -la /home total 16 drwxr-xr-x 1 root root 4096 Jul 26 18:53 . drwxr-xr-x 1 root root 4096 Jul 26 18:46 .. drwxr-x--- 3 student student 4096 Jul 26 18:49 student
Single-letter options are written with a single dash and can be combined:
-l -a is the same as -la. Long options are written with two dashes
(--all) and cannot be combined; each is a separate word. The long-option
form is not defined in POSIX; it is a GNU tool family addition, so the
single-letter form is preferred wherever portability matters.
If a filename starts with a dash, trouble follows: the command mistakes
it for an option. This is what the -- separator is for. Everything after
the word -- is not interpreted as an option. Why this separator is a
security matter will be seen in the deletion lesson.
Builtins and External Programs
Not every command is a separate program. Some commands are implemented inside the shell itself; these are called builtins. Which one a name is can be asked directly:
$ type -a cd pwd echo cd is a shell builtin pwd is a shell builtin pwd is /usr/bin/pwd pwd is /bin/pwd echo is a shell builtin echo is /usr/bin/echo echo is /bin/echo
Three different situations appear. cd is only a builtin. pwd and echo
are both a builtin and exist as external programs; in that case, the
builtin wins.
cd being a builtin is not a preference but a necessity. A program’s
working directory belongs to its own process; a cd running as a separate
program would change its own directory and exit, leaving the shell’s
directory where it was. To change directory, the command must run inside
the shell’s own process.
echo being both a builtin and an external program is for portability: it
needs to be callable even in environments without a shell. The behavior of
the two implementations is not exactly identical; this is why printf is
preferred over echo for formatted output.
Exit Status
Every command leaves behind an integer when it ends: the exit status. The convention runs in reverse — zero reports success, and every nonzero value reports a failure. This direction was chosen because there is one way to succeed and countless ways to fail.
The last command’s status is held in the $? variable:
$ true; echo $? 0 $ false; echo $? 1 $ ls /missing; echo $? ls: cannot access '/missing': No such file or directory 2
ls reports its failure both with an error message and with a nonzero
status. The message is for humans; the status code is for the scripts that
chain commands together.
Two values come from the shell itself and need to be told apart:
$ lss -l bash: lss: command not found $ echo $? 127
127 reports that the command was not found; 126 that it was found but could not be run. The first is a search problem, the second a permission problem. The difference between these two numbers shows exactly what the next lesson and the permissions topic each solve.
Where error messages are written to is a separate matter: standard output and standard error are two separate streams and can be redirected independently. Using this distinction belongs to the Shell Programming course.
Summary
- The shell first splits the line into words; the first word is the program name, the rest are arguments.
- Variable, filename, and home directory expansions happen in the shell before the program runs; the program sees only the result.
- Double quotes stop word splitting and filename expansion but not variable expansion; single quotes stop all of them.
- Single-letter options can be combined, long options cannot; the
--separator ends option interpretation. - Builtins run in the shell’s own process; this is why
cdhas to be a builtin. - In exit status, zero shows success; 127 reports the command was not found, 126 that it could not be run.
Next Step
Commands now need files to operate on. But files are not placed at random: every branch of the hierarchy starting from the root directory has a specific meaning. The next lesson introduces this layout, establishes the distinction between absolute and relative paths, and builds the project tree that will be developed throughout the course.
To keep your progress and take notes, Log in
My notes
Log in to take notes.