Skip to main content

Shells and Terminals

ยท 14 min read
Pere Pages
Server Side Rendering

A shell is a command-line interpreter that provides a user interface for the Unix/Linux operating system. The shell interprets the commands you enter in the terminal and tells the operating system to execute them.

Analogy

Think of the terminal as a movie theater and the shell as the movie being played. The theater (terminal) provides the environment and tools (screen, seats, speakers) to experience the movie, while the movie (shell) is the actual content or the interpreter that you interact with.

Terminalโ€‹

Historical Contextโ€‹

terminal

Originally, a terminal was a physical device (like a teletype machine) used to interact with computers. It included a keyboard for input and a printer or screen for output.

Modern Contextโ€‹

emulator

Today, when we talk about a terminal, we're usually referring to a terminal emulator. This is a software application that emulates the old physical terminals within a graphical user interface (GUI).

Functionalityโ€‹

Command Line Interface (CLI): A terminal provides a CLI where you can type commands to perform tasks such as file manipulation, program execution, and system monitoring.

Interpreting Commands: The terminal sends your commands to a shell, like Bash or Zsh, which then interprets and executes them, with the results displayed back in the terminal.

๐Ÿ—“๏ธ Chronology of Cli shellsโ€‹

Here's a chronological overview of the major Unix command-line shells:

early 1970sโ€‹

๐Ÿ–ฅ๏ธ Thompson Shell (sh)โ€‹

Developed by Ken Thompson in the early 1970s, the Thompson Shell (sh) was one of the earliest Unix command-line shells. It provided basic functionality for interacting with the operating system and executing commands.

late 1970sโ€‹

๐Ÿ–ฅ๏ธ Bourne Shell (Bourne sh)โ€‹

Created by Stephen Bourne in the late 1970s, the Bourne Shell expanded upon the Thompson Shell, introducing significant improvements and becoming the default shell for early versions of Unix. It offered a more versatile scripting language and introduced features like variables, loops, conditionals, and control structures.

late 1970sโ€‹

๐Ÿ–ฅ๏ธ C Shell (csh)โ€‹

Developed by Bill Joy in the late 1970s, the C Shell (csh) introduced a C-like syntax and interactive features like command-line editing, command history, and job control. It aimed to enhance the user experience and make the shell more intuitive and user-friendly.

early 1980sโ€‹

๐Ÿ–ฅ๏ธ KornShell (ksh)โ€‹

Created by David Korn in the early 1980s, the KornShell (ksh) combined features from the Bourne Shell (sh) and the C Shell (csh), offering a powerful scripting language with improved interactive features. It added command-line editing, history expansion, and advanced flow control constructs.

late 1980sโ€‹

๐Ÿ–ฅ๏ธ Bourne Again SHell (bash)โ€‹

Developed by Brian Fox and Chet Ramey in the late 1980s, Bash (Bourne Again SHell) was designed as an enhanced version of the Bourne Shell (sh). Bash aimed to maintain compatibility with sh while introducing new features and improvements. It became the default shell for many Linux distributions and offered advanced features like command-line editing, history manipulation, job control, and extensive scripting capabilities.

early 1990sโ€‹

๐Ÿ–ฅ๏ธ Z Shell (zsh)โ€‹

Developed by Paul Falstad in the early 1990s, the Z Shell (zsh) aimed to provide even more advanced features and customization options. It included powerful tab-completion, spelling correction, advanced globbing, and improved scripting capabilities. Zsh gained popularity among power users and developers.

late 2000sโ€‹

๐Ÿ–ฅ๏ธ Friendly Interactive SHell (fish)โ€‹

Created by Axel Liljencrantz in the late 2000s, the Friendly Interactive SHell (fish) prioritized simplicity, user-friendliness, and an intuitive command-line interface. It offered features like syntax highlighting, autosuggestions, and easy-to-use command-line editing. Fish aimed to provide an improved interactive experience for both beginners and advanced users.

  1. Bash (Bourne Again SHell): Bash is the default shell for many Linux distributions and is widely used due to its extensive features, compatibility, and scripting capabilities. Bash is by far the most widely adopted command-line shell in Unix-based systems. It is the default shell for many Linux distributions, and it has a strong presence in both server and desktop environments. It is estimated that Bash is used by over 70% of Unix users.
  2. Zsh (Z Shell): Zsh is an extended version of the Bourne shell (sh) with additional features and customization options. It offers advanced tab-completion, spelling correction, and powerful scripting capabilities. Zsh has gained significant popularity in recent years due to its powerful features and customization options. It has a passionate user community and is often favored by advanced users and developers. While there is no precise data on adoption percentages, Zsh is commonly considered the second most popular shell after Bash.
  3. Fish (Friendly Interactive SHell): Fish is designed to be user-friendly and interactive, with features like syntax highlighting, autosuggestions, and easy-to-use command-line editing. It focuses on ease of use for both interactive sessions and scripting.
Fish

while Fish offers a very user-friendly and feature-rich experience, its divergence from traditional shell paradigms, combined with the entrenched user base and script compatibility of Bash and Zsh, has limited its widespread adoption.

Enterprise Unix serversโ€‹

servers

The standard command-line shell for enterprise Unix servers can vary depending on the specific organization, their preferences, and the Unix distribution they are using. However, in many enterprise Unix server environments, the default shell tends to be Bash (Bourne Again SHell).

Bash has widespread adoption in Unix-based systems, including Linux distributions commonly used in enterprise environments. It offers a rich set of features, strong compatibility with POSIX standards, extensive scripting capabilities, and a large user base, which makes it a popular choice for both interactive shell usage and scripting tasks.

Bash's popularity and compatibility with the Bourne shell (sh) mean that it can execute existing sh scripts without modifications, making it convenient for enterprises with legacy scripts or applications.

That being said, there are cases where other shells like Zsh or KornShell (ksh) might be preferred or explicitly chosen by organizations based on specific requirements, user preferences, or compatibility needs with existing scripts or applications. It is always important to consider the specific context and requirements of the enterprise when determining the most suitable command-line shell for their Unix servers.

Bashโ€‹

Bash (Bourne Again Shell) is a command-line shell and scripting language commonly used in Unix-based operating systems. It is the default shell for many Linux distributions and is available for other Unix-like systems as well.

bash

The main difference between sh (Bourne shell) and bash (Bourne Again SHell) lies in their features and capabilities. While both are command-line shells and scripting languages, bash is an extended version of sh with additional features and improvements.

bash vs sh

bash is an extended version of sh with additional features and improvements.

POSIX and shell scriptingโ€‹

posix

POSIX, which stands for "Portable Operating System Interface for Unix," refers to a set of standards and specifications that aim to ensure compatibility and portability across Unix-like operating systems. POSIX defines various APIs (Application Programming Interfaces) and command line interfaces for software developers to create applications that can run on different Unix-based platforms.

There's also the concept of "POSIX shell scripting." A POSIX-compliant shell script should ideally run on any POSIX-compliant shell, providing a level of standardization.

sh

The original Bourne Shell (sh) is often treated as the baseline for POSIX shell scripting.

In summary, the term "shell script" in Unix can refer to scripts written for any of these shells. The specific features and syntax might vary, but the fundamental concept of automating tasks via command-line instructions is a common thread. For broader compatibility, especially in diverse environments, writing POSIX-compliant shell scripts is often recommended.

Shell on macOSโ€‹

Default Shell: Zshโ€‹

As of macOS Catalina, the default shell is zsh (Z Shell). Prior to Catalina, Bash (Bourne Again SHell) was the default. Both are command-line interpreters.

Customization and Scriptsโ€‹

Shells on macOS, like zsh, are highly customizable. You can write scripts to automate tasks, customize the command prompt, and even install additional frameworks like Oh My Zsh for further customization.

Common Developer Stack in macOSโ€‹

  1. You open your Terminal Emulator (like iTerm2).
  2. The terminal starts the Zsh shell.
  3. Oh My Zsh runs within Zsh, providing themes, plugins, and customizations.
  4. You interact with Oh My Zsh-enhanced Zsh through the terminal, leveraging its advanced features for your development tasks.
Stack
1. Zsh
2. Oh My Zsh
3. iTerm2

Oh My Zshโ€‹

omzlogo

Oh My Zsh is a delightful, open source, community-driven framework for managing your Zsh configuration. It comes bundled with thousands of helpful functions, helpers, plugins, themes, and a few things that make you shout...

iTerm2โ€‹

iTerm2

iTerm2 is a replacement for Terminal and the successor to iTerm. It works on Macs with macOS 10.14 or newer. iTerm2 brings the terminal into the modern age with features you never knew you always wanted.

Python vs Bashโ€‹

bash vs python

While Python has gained popularity and widespread usage in the Unix ecosystem, it wouldn't be accurate to say that it has completely replaced Bash as the de facto shell scripting language. Python and Bash serve different purposes and have their own strengths in the Unix environment.

Bash, being the default shell for most Unix-like systems, is well-suited for simple and quick scripting tasks that involve interacting with the operating system, executing commands, and performing basic file operations. It excels in its ability to handle shell-specific operations, such as variable expansions, process piping, and command substitution.

Python, on the other hand, is a versatile general-purpose programming language that offers a rich ecosystem of libraries, frameworks, and tools. It is often preferred for more complex scripting tasks, automation, system administration, web development, data processing, and machine learning. Python's extensive standard library and third-party packages make it an attractive choice for a wide range of tasks beyond traditional shell scripting.

In some cases, Python can indeed be used as a replacement for Bash, especially for more advanced scripting needs or when leveraging Python's additional capabilities and libraries. However, Bash's simplicity and lightweight nature still make it a popular choice for quick one-liners, simple scripts, and shell-specific operations.

In summary, while Python has gained popularity in the Unix ecosystem and offers powerful features, it hasn't entirely replaced Bash as the primary shell scripting language. The choice between Python and Bash ultimately depends on the specific requirements and nature of the scripting task at hand.

$SHELLโ€‹

Which is my shell?
echo $SHELL
# outputs: /bin/zsh

In Unix-like operating systems, the variable $SHELL is typically set by the system during the login process. It is usually set by the system's login shell, which is responsible for starting the user session. The login shell reads the user's login information and sets up the environment variables, including $SHELL.

The value of $SHELL represents the user's default shell, which is the command interpreter that is used when the user interacts with the system. It determines the behavior and features available in the user's shell session.

The system administrator or the user can change the default shell for a user account using the chsh command (short for "change shell"). This command allows the user to specify a different shell, and upon the next login, the system will set the $SHELL variable accordingly.

In summary, the system's login shell is responsible for setting the $SHELL variable during the login process, and the user or system administrator can change it using the chsh command.

Shebangโ€‹

shebang

Shell and Shebang: Shell scripting involves writing a script using a shell language, such as Bash. The first line of your script should start with a shebang (#!) followed by the path to the Bash interpreter (#!/bin/bash). This informs the system which interpreter to use for executing the script.

#!/Users/john.doe/.nvm/versions/node/v18.12.0/bin/node
console.log("hello world");
#!/bin/bash
echo 'hello world!'

Other Topicsโ€‹

Unit testing bashโ€‹

there are several popular tools available for unit testing Bash scripts. Here are a few commonly used ones:

  1. Bats: Bats (Bash Automated Testing System) is a popular testing framework for Bash. It provides a simple syntax for writing test cases and assertions. Bats is widely used and has a rich ecosystem of plugins and extensions.
  2. shunit2: shunit2 is a unit testing framework for Bash scripts inspired by JUnit. It allows you to write test cases using a simple syntax and provides various assertion functions. shunit2 has been around for a while and is well-documented.
  3. BashUnit: BashUnit is another lightweight testing framework specifically designed for Bash scripts. It offers basic testing features, including assertions and test case organization. BashUnit is easy to use and suitable for simple testing needs.
  4. BashSpec: BashSpec is a behavior-driven development (BDD)-style testing framework for Bash scripts. It allows you to define tests using natural language-like syntax and provides reporting features. BashSpec focuses on readability and offers descriptive test outputs.

These tools provide different features and syntax styles, so you can choose the one that aligns best with your preferences and project requirements. They generally allow you to write test cases, perform assertions, and execute tests in an automated manner.

Commentsโ€‹

Use comments to add explanations or notes within your script. In Bash, comments start with the # character, and they are ignored during script execution.

# This is a comment
echo 'hello world!';

Variablesโ€‹

You can assign values to variables in Bash using the syntax variable_name=value. Remember not to include spaces around the = sign. To access the value of a variable, prefix the variable name with a $ sign, such as $variable_name.

#!/bin/bash
my_name='John Doe';
echo $my_name;

Command Executionโ€‹

#!/bin/bash

mkdir myself
echo "Name: John Doe
Age: 33
Phone: +34640556233 " >> ./myself/data.txt
cat ./myself/data.txt
rm -rf ./myself

You can execute commands within a Bash script using backticks (`) or the $() syntax. For example, output=`command` or output=$(command).

result=$(ls -l)

or

result=`ls -l`

Conditional Statementsโ€‹

Use conditional statements to make decisions in your script. The if statement is commonly used with various conditions (-eq, -lt, -gt, -ne, etc.) and logical operators (&&, ||, !).

[[ ! -d tmp ]] && mkdir tmp
if [[ ! -d tmp ]]; then
mkdir tmp
fi

Loopsโ€‹

Loops allow you to repeat a block of code. Common loop types in Bash include for, while, and until. These loops can be controlled with conditions and iteration over a range or a list of values.

#!/bin/bash

folder_path=$1

for file_path in "$folder_path"/*.txt; do
if [ -f "$file_path" ]; then
echo "File: $file_path"
cat "$file_path"
echo -e "\n------------------------"
fi
done

Input/Outputโ€‹

You can read user input using the read command and display output using the echo command. You can also redirect input and output using operators like <, >, >>, and pipes (|) to pass output from one command as input to another.

read -p "Enter your name: " name
echo "Hello, $name! Nice to meet you."

echo $name > name.txt

Functionsโ€‹

Bash supports defining and using functions. You can create reusable blocks of code by defining functions and calling them within your script.

A file called read_files.sh:

#!/bin/bash

print_txt_files() {
folder_path=$1
for file_path in "$folder_path"/*.txt; do
if [ -f "$file_path" ]; then
echo "File: $file_path"
cat "$file_path"
echo -e "\n------------------------"
fi
done
}

print_txt_files $1
./read_files.sh ./txts

Exit Statusโ€‹

Every command executed in Bash returns an exit status. You can access the exit status of the previous command using the special variable $?. Typically, an exit status of 0 indicates success, while non-zero values indicate different types of failures.