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:
- Command Executed: date is a command that prints the current date and time, e.g., Tue Jun 10 14:15:36 IST 2025
- Substitution Happens: The output of date is captured and substituted into the variable current_date.
- 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:
- When the script reaches the read command, it pauses and waits for user input.
- Whatever the user types is stored in the specified variable (username in this case).
- 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