Chapter 4
Managing Processes: A Deconstructed Shell

In this chapter, we’ll “deconstruct” a UNIX shell. What does that mean? In UNIX, the shell—the command-line interface you use to type commands, move files around, and run programs—is just another user-space program. Although it makes intense use of APIs provided by the OS, the shell itself is not part of the OS, which means we can reverse engineer a shell like bash or zsh and replace it as we please.

Our goal isn’t to create a full-featured bash replacement, however. bash and all other descendants of the UNIX sh have a bias toward interactive use—they have a variety of features designed to support a human at a keyboard typing commands in real time.

However, UNIX shells can be surprisingly weak and brittle when used for automation, especially within the sort of large and complex distributed systems in which Scala is often deployed. Thus, our goal is to design the basic components of a Scala library for shell-like automation tasks. Done right, this will let us leverage Scala Native’s ahead-of-time compilation to replace bash scripts with compact binaries, and open up our automation tasks to Scala’s impressive powers of abstraction.

What are the critical features of a shell for these use cases? Our goal is to:

And we’ll do all of this in straightforward, idiomatic Scala. However, we’ll stay clear of some of the more advanced syntactical features common to DSLs, or domain-specific languages, in Scala, such as implicit conversions, custom string interpolators, and custom symbolic infix operators; instead, our library will be designed for use in straightforward, everyday Scala code.

Let’s start by learning more about how a shell invokes a program.