Terminals and Shells

"When you type to UNIX, a gnome deep in the system
is gathering your characters and saving them in a secret place.
The characters will not be given to a program until you
type return (or new-line), as described above in Logging in."
Ken Thompson and Dennis Ritchie
UNIX Programming Manual, Sixth Edition
May, 1975

Though foreign to most users, the text-terminal is the original interface for interactive computing. As Dr. Brian Kerninghan put it, this interface forms a language unto itself, with files serving as nouns and commands as verbs.[1] Here we explore the theory behind this interface, and offer a glimpse of its power and utility. For a tutorial introduction, see Effective Shell by Dave Kerr or The Linux Command Line for Beginners by Canonical.

Terminals

and Terminal Emulators

A terminal is a computer peripheral similar to a typewriter. The earliest examples printed output onto paper. These terminals were produced well into the 1970's, and later electronic typewriters often supported terminal mode, allowing them to interface with computers via serial cable.

Ken Thompson (sitting) and Dennis Ritchie at PDP-11 (2876612463) Ken Thompson (sitting) and Dennis Ritchie at PDP-11, in Bell Labs' Unix Room[1]

As technology developed and interactive computing became more popular, hard-copy terminals were phased out in favor of screen-based entities. The resulting devices consisted of a monitor and keyboard; unlike modern monitors, they could only display text. Within Unix-like operating systems, terminals are represented by tty device files.

Terminal Emulators

Terminals persist today in the form of terminal emulators. A terminal emulator is a program that runs within the context of a desktop environment and provides the functionality of a physical terminal. On-screen, it looks sorta like:

GNOME Terminal Emulator

Some modern terminal emulators:

These emulators are "dumb" in the sense that they do nothing except display text and return text. Per tradition, their input and output is handled through device files.

See Also:

Shells

A shell is a program which interprets and executes our (arbitrary) commands. It is a "shell" in the sense that it is the outermost layer of the operating system, with which the user has direct contact.[2] The Unix architecture entertains the shell as an ordinary user-space process.

Aside:

Desktop environments serve a role analogous to shells.

Some modern shells:

A shell is launched for you when a terminal emulator window is opened. You can find out which shell is running by executing the ps command:

See Also:

Commands

and Arguments

A typical line of input has the following form:

$ command [arg1 arg2...]

This corresponds directly to, Find and execute command, and pass it arguments [arg ...]. There are a few places the shell looks for the command being executed. In order:

Aside:

Two tools to identify commands are type and which.

  1. User-defined functions - A way to group commands
  2. Shell builtin commands - Commands hard-coded into the shell
  3. External programs - Commands that exist as separate programs

Nearly all commands fall into the third category, external programs. For example, ls is an external program, and can be found in /usr/bin.

Arguments

Commands are the first part of a "typical line of input"; the other is arguments. Like commands, arguments are supplied by the user. They are passed to each function, builtin, or program as it begins execution, and it is up to that command to interpret them.

For example, echo responds to arguments very simply— It prints them back out:

$ echo one two three four
one two three four

Common arguments include:

$ command --help

which prints a terse help message; and,

$ command --version

which prints the version number of the software. See a command's manual page for more documentation.

See Also:

Working Directory

Recall that each process has a current working directory. Since the shell is a process, it too has a current working directory. At the command line, this is of particular importance: It represents where you are in the directory tree.

Aside:

Relative file names are resolved with respect to the current working directory.

The shell's current working directory may be printed with the print working directory command, pwd:

$ pwd
/home/josh

And we can change its value with the change directories command, cd:

$ cd Public
$ pwd
/home/josh/Public

Since each directory refers to its parent directory as .., we can always move up the directory tree with,

$ cd ..
$ pwd
/home/josh

Standard File Descriptors

The shell is connected to its terminal by three open files, known as the standard files:

Since open files are preserved across both fork() and exec(), each command inherits our keyboard and terminal screen at these file descriptors.

For example, the program /usr/bin/ls, which lists directory contents, prints to STDOUT, and our command

$ ls

runs that program in the context of the terminal.

Redirection

and Pipelines

One of the features that makes the Unix command-line interface exceptionally powerful is the ability to direct output away from the terminal. Since the terminal is represented as a collection of files, the operations on it and any other file are identical. Thus, we can replace the TTY file a command is going to write to with a regular file, and capture the command's output in it:

$ lscpu > cpu_stats

This is called redirection, and here we are redirecting STDOUT to the file cpu_stats (the file is created if it does not exist). STDERR has not been changed, and therefore the terminal remains the destination for any error messages.

Redirection operators <, >, and 2> redirect STDIN, STDOUT, and STDERR, respectively. Redirections are interpreted by the shell before the command executes, and are not passed as arguments to the command.

Pipelines

We have a few other tricks up our sleeve: The pipe operator, |, allows us to direct the output (STDOUT) of one command to the input (STDIN) of another. The result is called a pipeline, and allows us to manipulate program output as a stream of text.

For example, Section 7 of the manual pages contains many interesting articles, and we can get a single line summary of each using apropos:

$ apropos -s 7 '.*' 
bpf-helpers (7) - list of eBPF helper functions
gnupg (7) - The GNU Privacy Guard suite of programs
RAND (7ssl) - the OpenSSL random generator
Standards (7) - X Window System Standards and Specifications
UPower (7) - System-wide Power Management
utf-8 (7) - an ASCII compatible multibyte Unicode encoding
address_families (7) - socket address families (domains)
aio (7) - POSIX asynchronous I/O overview
armscii-8 (7) - Armenian character set encoded in octal, decimal, and ...
arp (7) - Linux ARP kernel module.
[...]

By piping this output into the sort command, we can alphabatize the results, forming something closer to an index:

$ apropos -s 7 '.*' | sort 
address_families (7) - socket address families (domains)
aio (7) - POSIX asynchronous I/O overview
armscii-8 (7) - Armenian character set encoded in octal, decimal, and ...
arp (7) - Linux ARP kernel module.
ascii (7) - ASCII character set encoded in octal, decimal, and hex...
asymmetric-key (7) - Kernel key type for holding asymmetric keys
attributes (7) - POSIX safety concepts
audit.rules (7) - a set of rules loaded in the kernel audit system
backend (7) - cups backend transmission interfaces
bio (7ssl) - Basic I/O abstraction
[...]

We can continue in this fashion: If we only want the first few lines, we can pipe the results into head:

$ apropos -s 7 '.*' | sort | head

If we want only the last few lines, we can use tail:

$ apropos -s 7 '.*' | sort | tail

And if we want to page through the results, we can pipe into less:

$ apropos -s 7 '.*' | sort | less

See Also

Scripting

Note:

Scripts must be stored as executable files. One can change a file's mode bits using chmod:

$ chmod +x file

The last trick up our sleeve is saving a sequence of commands into a file, then executing the file as a program. Such programs are called scripts, and lend the command-line a great deal of power: Rather than repeating ourselves, we can simply save commands into a file, then execute the file.

The first line of a script must begin with the characters #!. This line is referred to as shebang, and designates the program that will interpret the script.

For example, if we had a file named hello with the following contents,

#!/usr/bin/sh

echo Hello World!

we could execute it as an external program with,

Aside:

Here we are using ./ to execute a program in the current working directory.

$ ./hello
Hello world!

See Also:

Closing Remarks:

fork()

The earliest versions of UNIX entertained exactly two processes: One for each of two terminals connected to the machine.[3] The shell, sh, existed as a user-space process, but fork() had not been introduced. To execute a command, the shell replaced itself with the requested program; the program, on calling exit(), replaced itself with the shell once again.[3]

In particular, change directories was implemented as a separate command. When invoked, it inherited the shell's current working directory, changed it, and then the shell inherited the resulting working directory. With the inclusion of the fork() system call, though, the chdir command broke:

"There was much reading of code and anxious introspection about how the addition of fork could have broken the chdir call. Finally the truth dawned: in the old system chdir was an ordinary command; it adjusted the current directory of the (unique) process attached to the terminal. Under the new system, the chdir command correctly changed the current directory of the process created to execute it, but this process promptly terminated and had no effect whatsoever on its parent shell! It was necessary to make chdir a special command, executed internally within the shell. It turns out that several command-like functions have the same property, for example login."[3]

And so, builtins began.

See Also:

Suggested Reading:

Missing Semester

Lecture 1: Course Overview + The Shell (2020)

References

  1. Kernighan, Brian. Unix: A History and a Memoir. Published 2020, by Brian W. Kernighan via Kindle Direct Publishing.
  2. Kernel Definition. From The Linux Information Project. (2005, May 31) Retrieved April 10, 2021, from http://www.linfo.org/kernel.html
  3. Ritchie, D. M. (1984).The Evolution of the Unix Time-sharing System. AT&T Bell Laboratories Technical Journal, 63(6), 2nd ser., 1577-1593.