LPIC-1 Module 5: Shells and Shell Scripting

Published On: 27 July 2025

Objective

In the world of Unix-like operating systems, the shell serves as the interface between users and the kernel. Whether you're issuing commands interactively or automating tasks with scripts, understanding how shells work is essential for any Linux system administrator or power user. A shell script is a file that contains a series of commands, enabling you to automate repetitive tasks, enforce policies, and configure environments. This blog explores the two major areas every Linux user must master: customizing and using the shell environment, and writing simple shell scripts. We will dive deep into these topics, discuss relevant commands and files, and offer practice MCQs to test your knowledge.

Topic 105.1: Customize and Use the Shell Environment

What Is the Shell Environment?

The shell environment refers to the runtime context in which the shell operates. It encompasses a set of environment variables, configuration files, and user-specific settings that control the behavior and appearance of the shell. When a user logs in or opens a terminal, the shell initializes using these settings to define things like the command prompt style, available paths for executable files, default editor, language settings, and more. At its core, the shell environment ensures that each user session is predictable and tailored to user or system needs. For instance, developers may customize their shell to include development tools in the PATH variable or define aliases for frequent commands. These configurations can be system-wide or user-specific, and they can be set for login shells, non-login shells, or interactive/non-interactive sessions.

The environment is built using shell startup files, such as .bashrc, .bash_profile, or /etc/profile. These files are executed when a new shell starts, setting up variables like PATH, defining functions and aliases, and initializing tools or services. Users can also alter these settings on the fly using commands like export, unset, or alias, giving the shell flexibility and control. Understanding the shell environment is crucial not just for ease of use, but also for system security, automation, and scripting. A misconfigured environment might result in command failures, insecure defaults, or unexpected script behaviors.

Key Topics:

a. Setting Environment Variables

Environment variables like PATH, HOME, USER, SHELL, and many others are key-value pairs that define and control the behavior of the shell and its child processes. These variables provide essential contextual data used by the operating system and the shell to determine how programs should execute and where system resources are located.

You can define environment variables in two ways:

Temporarily in the shell: These variables exist only for the current session or until the terminal is closed. They are set using the export command, which makes them available to all child processes started from the shell.

export PATH=$PATH:/opt/myapp/bin

In the above example, a new directory /opt/myapp/bin is appended to the existing PATH, allowing the shell to locate executables in that directory.

Permanently in configuration files: To ensure that environment variables persist across terminal sessions or reboots, they must be declared in startup configuration files such as ~/.bash_profile, ~/.bashrc, or /etc/profile. For example:

echo 'export PATH=$PATH:/opt/myapp/bin' >> ~/.bash_profile

This command appends the export statement to the .bash_profile, so the change is applied automatically the next time a login shell is started.

Environment variables can also be removed with unset, and viewed collectively using the env or printenv command. Managing them effectively is vital for scripting, user environment configuration, and application behavior tuning.

b. Bash Functions

Bash functions allow you to create reusable blocks of code within your shell session or scripts. These are essentially named sequences of commands that you can invoke just like any other command. Using functions makes your scripts and session management more modular, readable, and maintainable.

Definition Syntax:

greet_user() {
    echo "Welcome, $USER!"
}

This defines a function called greet_user. When this function is called, it prints a greeting message that includes the current user's login name (stored in the environment variable $USER).

Calling the Function:

greet_user

You can also pass arguments to functions:

say_hello() {
    echo "Hello, $1!"
}

say_hello Alice

Here, $1 represents the first argument passed to the function. Functions can include loops, conditional logic, and other scripting constructs, and they can return values using the return keyword or by echoing output.

Functions can be defined in .bashrc to be available in every interactive shell session.

c. Skeleton Directories

Skeleton directories provide a powerful mechanism for pre-configuring user environments in Linux. The /etc/skel/ directory acts as a template for new user accounts. Whenever a new user is created using tools like useradd, the contents of /etc/skel/ are automatically copied into the new user's home directory (typically /home/username). This is especially useful in multi-user environments where administrators want all users to begin with the same set of configuration files or settings. For instance, if an administrator wants every new user to start with a predefined .bashrc file that includes a set of aliases and environment variables, placing that file in /etc/skel/ ensures it's included by default.

You can place any configuration file here—.bash_profile, .bash_logout, .vimrc, or even custom scripts. These files are then owned by the new user and can be further personalized without affecting the template.

Example:

echo 'alias ll="ls -l"' > /etc/skel/.bashrc

This ensures that all newly created users will have ll as an alias for ls -l in their default shell environment.

By properly managing /etc/skel/, system administrators can save time and enforce uniformity across user environments.

d. Command Search Path

One of the most critical components of the shell environment is the PATH variable. It defines a list of directories that the shell searches through to locate executable programs. When a user types a command like ls or vim, the shell doesn't know the exact location of that executable. Instead, it checks each directory listed in the PATH variable in the order they appear until it finds a match.

Viewing Your PATH

echo $PATH

This command displays a colon-separated list of directories. For example:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

If the shell doesn't find the command in any of these directories, it returns an error such as command not found.

Modifying the PATH Temporarily To add a new directory to the PATH for the current session:

export PATH=$PATH:/opt/tools/bin

This command appends /opt/tools/bin to the existing PATH variable.

Making PATH Changes Permanent To ensure that your PATH changes persist across sessions, you should add the export command to a shell initialization file such as ~/.bash_profile or ~/.bashrc:

echo 'export PATH=$PATH:/opt/tools/bin' >> ~/.bash_profile

After modifying this file, either log out and log back in or run:

source ~/.bash_profile

Best Practices

  • Always add directories to the end of PATH unless you have a specific reason to prioritize them.
  • Avoid placing . (current directory) in PATH for security reasons.
  • Use full absolute paths rather than relative paths to ensure reliability.

Properly managing your command search path can prevent script failures and make command-line work more efficient and predictable.

e. Important Configuration Files

File Purpose
/etc/profile Sets system-wide environment variables.
/etc/bash.bashrc Sets system-wide bash settings.
~/.bash_profile User-specific login shell settings.
~/.bashrc Non-login shell settings.
~/.bash_logout Commands to run when logout occurs.

f. Source and Dot Commands

In Bash, when you make changes to shell configuration files like .bashrc or .bash_profile, those changes don't take effect immediately. You must either log out and log back in or manually re-execute the file in the current shell session. This is where the source command (or its shorthand, .) comes in.

Usage:

source ~/.bashrc
# or
. ~/.bashrc

These commands execute the contents of .bashrc in the current shell environment, applying any changes like new environment variables, aliases, or functions.

This is crucial when:

  • You add new variables or functions and want them to take effect immediately.
  • You're testing configuration changes and don't want to restart your session.
  • You're debugging shell initialization behavior.

Using source ensures that your current shell is updated without needing to log out or restart the terminal.

g. Useful Built-in Commands

Command Purpose
env Displays environment variables.
export Makes variable available to child processes.
set Displays and sets shell options.
unset Removes variable from the environment.
alias Creates shortcut for a command.

Practice MCQs:

Q1. Which file is read when a Bash login shell is started?

A. ~/.bashrc
B. /etc/bash.bashrc
C. ~/.bash_profile
D. ~/.bash_logout

Answer: C

Q2. What is the purpose of the export command in Bash?

A. To unset a variable
B. To display current shell settings
C. To make a variable available to subprocesses
D. To delete a file

Answer: C

Topic 105.2: Customize or Write Simple Scripts

What Is Shell Scripting?

Shell scripting is the process of writing and storing a sequence of shell commands in a file commonly referred to as a script that the shell can interpret and execute in order. These scripts are essentially text files containing a list of commands that are executed sequentially, allowing users to automate routine tasks, configure systems, perform system administration duties, or even carry out complex operations involving decision-making logic and user input. Shell scripts are particularly powerful because they leverage existing command-line utilities and system tools, tying them together to form cohesive, repeatable processes. Common use cases include backup automation, system monitoring, log rotation, user management, and software deployment. By using constructs like variables, loops, conditionals, and functions, shell scripts can make administrative tasks much more efficient and consistent.

Moreover, shell scripting plays a vital role in the DevOps lifecycle by enabling configuration management, provisioning, and system orchestration. These scripts can be executed manually or scheduled via cron jobs or systemd timers, making them a critical component of Linux system automation and maintenance.

Key Topics:

a. Script Interpreters and Shebang

Every shell script should start with a shebang line, which is the very first line in the script and begins with #! followed by the path to the interpreter that should execute the script. For Bash scripts, the most common shebang is:

#!/bin/bash

This line tells the operating system to use the Bash shell to interpret the script. Without this line, the script may be interpreted using the default shell (which may not be Bash), leading to unexpected behavior or errors—especially if your script uses syntax specific to Bash. The shebang is critical in environments where multiple shell interpreters are present, such as Dash, Zsh, or Fish. It ensures portability and consistent behavior across different systems. For example, if a script is intended for POSIX compliance rather than Bash-specific features, the shebang can be:

#!/bin/sh

Scripts can also specify interpreters like Python or Perl:

#!/usr/bin/python3

In summary, the shebang:

  • Ensures your script uses the intended shell or language interpreter.
  • Is mandatory for scripts executed directly (e.g., ./myscript.sh).
  • Should point to a valid and correct path to the interpreter (can be checked with which bash, etc).

Using the right shebang is essential for compatibility, clarity, and control over how your scripts execute.

b. Standard Syntax and Loops

Loops and conditions make scripts dynamic and capable of decision-making.

For Loop

Executes a block of commands for each item in a list.

for item in apple banana orange; do
    echo "Fruit: $item"
done

While Loop

Repeats a block of code while a condition is true.

counter=1
while [ $counter -le 5 ]; do
    echo "Counter: $counter"
    counter=$((counter + 1))
done

If Condition

Executes commands only if a condition is true.

if [ -f /etc/passwd ]; then
    echo "Password file exists"
else
    echo "File not found"
fi

Test Command

Used to evaluate expressions.

test -d /home && echo "Directory exists"

Or its modern equivalent:

[ -d /home ] && echo "Directory exists"

c. Command Substitution

What Is Command Substitution?

Command substitution in shell scripting allows you to run a command and use its output as part of another command. It is extremely powerful when you need to dynamically insert the result of one command into another.

Syntax:

There are two main forms:

$(command)  # Preferred modern syntax
`command`   # Older backtick syntax (less readable, more error-prone)

Example Explained:

current_date=$(date)
echo "Today is $current_date"

Here's what happens step-by-step:

  1. Command Executed: date is a command that prints the current date and time, e.g., Tue Jun 10 14:15:36 IST 2025
  2. Substitution Happens: The output of date is captured and substituted into the variable current_date.
  3. Variable Used: The echo statement then outputs: Today is Tue Jun 10 14:15:36 IST 2025

This technique is particularly useful in scripting for:

  • Dynamically generating filenames (log_$(date +%F).txt)
  • Capturing output for use in logic (if statements)
  • Embedding results into strings

Common Use Cases:

Save command output to a variable:

hostname=$(hostname)
echo "Running on $hostname"

Use output as argument to another command:

ls -l $(which bash)

Why Use $(...) Instead of Backticks?

  • Easier to read
  • Nestable:
echo "Current user is $(whoami), home is $(eval echo ~$USER)"

d. Return Values and Exit Status

Every time you run a command in a Linux shell, that command generates an exit status (also called a return value). This exit status is a number that communicates whether the command succeeded or failed, and sometimes provides information about the type of failure.

Key Concept: Exit Status

  • The exit status is a numeric code returned by the command to the shell after it finishes running.
  • It can be accessed using the special variable $?, which stores the exit status of the most recently executed command.

Understanding the Output

command
echo $?

In this example:

  • command is any Linux command you run (e.g., ls, mkdir, ping, etc.).
  • echo $? prints the exit status of that command.

Exit Code Values and Their Meaning

Exit Code Meaning
0 Success. The command completed without errors.
1–255 Failure. The command encountered an error. The exact number can give insight into what went wrong.

Note: By convention, 0 always means success, while non-zero values indicate some kind of error or special condition.

Why This Matters in Scripting

Exit statuses are crucial in scripting because they allow you to write conditional logic based on whether a command succeeded. For example:

cp /file1 /backup/
if [ $? -eq 0 ]; then
    echo "Backup successful"
else
    echo "Backup failed"
fi

Or, more efficiently using command chaining:

cp /file1 /backup/ && echo "Backup successful" || echo "Backup failed"

This logic ensures that your script reacts appropriately—logging errors, sending alerts, or trying alternative actions based on what happened.

Exit Codes in Custom Scripts

You can also define custom exit statuses in your own scripts using the exit command:

#!/bin/bash

if [ ! -f "$1" ]; then
    echo "File not found!"
    exit 2
fi

echo "Processing file $1"
exit 0

In this script:

  • exit 2 indicates an error (e.g., file doesn't exist).
  • exit 0 signals success.

e. Chained Commands

Chained Commands in Bash

Chained commands let you link multiple commands together so they execute in sequence under certain conditions. This is a very powerful technique in shell scripting and interactive command-line use, especially for automation and conditional operations.

1. Using && (Logical AND)

The && operator allows you to run a second command only if the first command succeeds (i.e., exits with a status code 0).

Example:

mkdir testdir && cd testdir
  • mkdir testdir: This command tries to create a directory named testdir.
  • &&: Bash checks if mkdir was successful (exit code 0).
  • cd testdir: Only runs if the previous mkdir command succeeded.

Use Case: Ideal for performing a task only if a prerequisite action succeeds. For instance, you may want to compile code only if source code download was successful:

wget http://example.com/source.tar.gz && tar -xzf source.tar.gz

2. Using || (Logical OR)

The || operator runs the second command only if the first command fails (i.e., exits with a status code other than 0).

Example:

cd /nonexistent || echo "Directory does not exist"
  • cd /nonexistent: Attempts to change to a non-existent directory.
  • ||: Bash checks that this command failed (exit code not 0).
  • echo "Directory does not exist": Executes only because cd failed.

Use Case: Useful for error handling or fallback behaviors. For example:

grep "Error" logfile.txt || echo "No errors found"

This tells the shell to search for the word "Error" in a log file, and if it's not found (i.e., grep fails), it prints a friendly message.

Combining && and ||

You can even combine both for more complex logic, resembling an if...then...else construct:

mkdir myfolder && echo "Created!" || echo "Failed to create folder!"

This works like:

  • If mkdir succeeds → prints "Created!"
  • If mkdir fails → skips the success message and prints "Failed to create folder!"

However, be cautious: Bash will not execute the || part if the echo "Created!" succeeds—even if mkdir failed. So for safer if...else behavior, use proper conditionals (if, then, else) in scripts.

These operators are essential for creating clean, conditional command chains in Bash.

f. User Input and Read Command

One of the most important interactive features of shell scripting is the ability to accept user input. This allows your script to behave dynamically, adapting its behavior based on what the user provides during execution. The read command in Bash is used precisely for this purpose.

Syntax:

read [options] variable_name

Example with -p flag:

read -p "Enter your name: " username
echo "Hello, $username!"
  • -p: This flag allows you to prompt the user with a custom message before accepting input.
  • username: This is the variable where the input provided by the user will be stored.
  • echo "Hello, $username!": This prints a message using the input value stored in the variable.

How it Works:

  1. When the script reaches the read command, it pauses and waits for user input.
  2. Whatever the user types is stored in the specified variable (username in this case).
  3. You can then use that variable later in your script—for display, logic, or any conditional processing.

Multiple Inputs:

You can also read multiple values at once:

read -p "Enter your first and last name: " first last
echo "Hello, $first $last!"

Silent Input (-s) – For passwords:

To hide input (useful for passwords):

read -sp "Enter your password: " password
echo
echo "Password entered."

Timeout (-t) – Automatic timeout if no input:

read -t 10 -p "Enter your choice in 10 seconds: " choice

If no input is given in 10 seconds, the command fails.

Default Variable – REPLY:

If no variable name is given, Bash uses a default variable called REPLY:

read -p "Enter something: "
echo "You entered: $REPLY"

g. Mail on Condition

if ! ping -c 1 google.com; then
    echo "Ping failed!" | mail -s "Ping Error" root
fi

What This Script Does:

This is a simple Bash script that monitors the network connectivity by pinging google.com. If the ping fails, it sends an email alert to the root user with a message.

Line-by-Line Breakdown:

if ! ping -c 1 google.com; then
  • ping -c 1 google.com: Sends 1 ICMP echo request (i.e., ping) to google.com to check if the site is reachable.
  • !: The exclamation mark negates the result. It means "if the ping fails" (i.e., if the command returns a non-zero exit status).
  • if ...; then: Standard Bash conditional syntax. If the ping fails, the code inside the block will be executed.
echo "Ping failed!" | mail -s "Ping Error" root
  • echo "Ping failed!": Outputs a simple message stating the failure.
  • | mail -s "Ping Error" root: Pipes that message into the mail command, which sends an email with:
    • -s "Ping Error": Subject of the email.
    • root: The recipient (local root user).

So, this line sends an email to the root user with the subject "Ping Error" and body "Ping failed!".

fi

Ends the if block.

h. Permissions and Ownership

Ensure script has execution rights:

chmod +x myscript.sh

To run:

./myscript.sh

Setuid is generally avoided for scripts due to security issues, but in compiled binaries it allows temporary privilege escalation.

Practice MCQs:

Q1. What does the following command do?

mkdir newdir && cd newdir

A. Creates a directory and always changes into it
B. Changes directory only if mkdir fails
C. Changes directory only if mkdir succeeds
D. Fails if newdir already exists

Answer: C

Q2. Which of the following lines is used to define a script interpreter?

A. #! /bin/bash
B. /bin/bash
C. bash -x
D. chmod +x script.sh

Answer: A

Conclusion

Understanding shells and shell scripting is not just an academic requirement it's a practical necessity for any Linux administrator, DevOps engineer, or developer. By customizing your shell environment, you can improve your workflow and system consistency. With shell scripting, repetitive tasks are simplified, and system management becomes more efficient and reliable. If you are preparing for certifications like LPIC-1 or RHCSA, mastering these foundational skills will not only help you pass your exams but also excel in real-world Linux administration. Take the LPIC-1 Shells and Shell Scripting Quiz Now