
The shebang (#!/bin/bash)
The shebang #!/bin/bash is a special construct that plays a crucial role in how shell scripts are executed in Linux.
Definition and Purpose
◦ The #! character sequence, known as the shebang, is a special construct that must be the first line of every shell script.
◦ Its primary purpose is to tell the kernel the name of the interpreter that should be used to execute the script that follows.
◦ In the case of #!/bin/bash, it explicitly designates the Bourne Again SHell (BASH), located at /bin/bash, as the interpreter for the script. BASH is the most popular and default shell on most Linux distributions
Without a shebang, the shell assumes the script is written for itself (e.g., Bash). The shebang allows you to write scripts for other interpreters like Python, Perl, or even run text files as binaries.
Common Shebang Examples:
Shebang Interpreter Use Case
#!/bin/bash The Bash shell Standard Bash scripts.
#!/usr/bin/python3 Python 3 Python scripts.
#!/usr/bin/env bash Bash (via env) Portable Bash scripts.
#!/bin/sh The system shell (often Bash) Scripts aiming for POSIX sh compatibility.
#!/usr/bin/env python3 Python 3 (via env) Portable Python scripts.
Making a script executable
You’ve written a brilliant shell script and added the perfect shebang. You try to run it with ./my_script.sh and get this frustrating error:
bash: ./my_script.sh: Permission denied
Why? Because, by default, a new file is created with read and write permissions for the owner, but not execute permission. The system is protecting you from accidentally running a text file as a program.
To turn your text file into a command you can run, you need to give it the execute permission.
The Tool: chmod (Change Mode)
We use the chmod command, which we learned about in the File Permissions module, to add the execute bit.
Remember the symbolic notation:
u - user (owner)
g - group
o - others
a - all (user + group + others)
+ - add a permission
x - execute permission
—
How to Make a Script Executable
Step 1: Check the Current Permissions
Always a good first step. Use ls -l.
$ ls -l my_script.sh
-rw-r--r-- 1 user user 123 Jan 16 11:30 my_script.sh
The first string -rw-r--r-- shows the permissions. The lack of an x anywhere means no one can execute it.
Step 2: Add the Execute Permission
You have two main choices:
1. Make it executable for yourself (the owner):
$ chmod u+x my_script.sh
u+x means “add (+) execute (x) permission for the user/owner (u).”
2. Make it executable for everyone:
$ chmod a+x my_script.sh
# or simply...
$ chmod +x my_script.sh
a+x means “add execute permission for all (a).”
+x is a common shortcut that defaults to affecting all classes.
Step 3: Verify the Change
Use ls -l again to see the difference.
$ ls -l my_script.sh
-rwxr--r-- 1 user user 123 Jan 16 11:30 my_script.sh
# OR if you used 'a+x'
-rwxr-xr-x 1 user user 123 Jan 16 11:30 my_script.sh
Notice the x in the user’s permission set (rwx). The script is now executable.
Step 4: Run It!
Now you can execute your script directly.
# Run it from its current directory
./my_script.sh
# If the script is in a directory listed in your $PATH (like ~/bin),
my_script.sh
Variables and Command Substitution
Why Use Variables?
Variables are like labeled boxes where you can store information (text, numbers, filenames) for your script to use later. They are essential for:
- Avoiding repetition: Store a value once, use it many times.
- Making scripts configurable: Use variables for values that might change.
- Capturing output: Store the result of a command to use in your script.
Using Variables in Bash
1. Setting a Variable (The Box Label)
To create a variable, just choose a name and assign a value with an = sign. No spaces around the = are allowed.
bash
# Store text (a string)
name="Alice"
filename="report.txt"
# Store a number
count=42
# Store the output of a command (see next section!)
current_date=$(date)
2. Using a Variable (Looking in the Box)
To access the value inside the variable, prefix its name with a $ sign.
bash
# Print the variable's value
echo "Hello, $name"
# Use it in a command
cp $filename /backup/$filename
You can also use curly braces {} to clearly separate the variable name from surrounding text. This is called parameter expansion and is a best practice.
bash
# Without curly braces - is the variable 'file' or 'filename'?
echo "The file is $filename_backup" # This won't work!
# With curly braces - it's clear!
echo "The file is ${filename}_backup"
3. Important Rules
- Variable names are case-sensitive.
$name and $NAME are different.
- By convention, variable names are uppercase for constants and lowercase for script variables.
- No spaces around the
= sign. count=42 is correct; count = 42 will cause an error.
Command Substitution: $(command)
Command substitution is a superpower in shell scripting. It allows you to capture the standard output of a command and store it directly in a variable.
Think of it as putting a command inside a variable box.
Syntax:
variable_name=$(command_to_run)
Example:
bash
# Store the current date and time
current_time=$(date)echo "The script started at $current_time"
# Store the system's hostname
server_name=$(hostname)echo "Running on server: $server_name"
# Store the number of lines in a file
line_count=$(wc -l < /etc/passwd)echo "There are $line_count users on the system."
# Store the path to a file found by another command
config_file=$(find /etc -name "nginx.conf"2>/dev/null | head -1)echo "Found config at: $config_file"
Control Structures
What are Control Structures?
So far, our scripts have run commands in a straight line. Control structures change this flow. They allow your scripts to make decisions, repeat tasks, and choose different paths based on conditions. This is what transforms a simple list of commands into a powerful, intelligent program.
The two fundamental types of control structures are:
- Conditionals: Choose to run code based on a condition (
if, case).
- Loops: Repeat code multiple times (
for, while, until).
1. Conditional Execution: The if Statement
The if statement is the primary tool for decision-making. Its basic logic is: “If this is true, then do that.”
Basic Syntax:
if [ condition ]; then
# commands to run if the condition is TRUE
fi
The if-else Syntax:
if [ condition ]; then
# commands to run if the condition is TRUE
else
# commands to run if the condition is FALSE
fi
The if-elif-else Syntax:
if [ condition1 ]; then
# commands if condition1 is TRUE
elif [ condition2 ]; then
# commands if condition2 is TRUE
else
# commands if ALL previous conditions are FALSE
fi
Example: Safe File Deletion
#!/bin/bashfilename="$1"
if [ -f "$filename" ]; then
echo "File found. Deleting $filename..."
rm "$filename"
else
echo "Error: File '$filename' does not exist."
fi
2. The case Statement
The case statement is like a multi-choice if statement. It’s perfect for matching a variable against multiple patterns.
Syntax:
bash
case $variable in
pattern1)
# commands for pattern1
;;
pattern2)
# commands for pattern2
;;
*)
# default commands if no patterns match
;;
esac
Example: Simple Menu System
bash
#!/bin/bashecho "Select an option: start|stop|status"
read choice
case $choice in
start)
echo "Starting the service..."
systemctl start my-service
;;
stop)
echo "Stopping the service..."
systemctl stop my-service
;;
status)
echo "Checking status..."
systemctl status my-service
;;
*) # This is the default case
echo "Error: Invalid option. Use start, stop, or status."
exit 1
;;
esac
3. Loops: Automating Repetition
A. The for Loop
Ideal for iterating over a known list of items (files, numbers, usernames).
Syntax:
bash
for item in list_of_items; do
# commands to run for each $item
done
Examples:
#!/bin/bash
# This script greets a predefined list of friends.
for name in Alice Bob Charlie; do
echo "Hello, $name! How are you today?"
done
Output:
Hello, Alice! How are you today?
Hello, Bob! How are you today?
Hello, Charlie! How are you today?
B. The while Loop
Runs a block of code while a condition is true. Great for reading files line-by-line or creating persistent prompts.
Syntax:
bash
while [ condition ]; do
# commands to run while the condition is TRUE
done
Example: Countdown Timer
bash
#!/bin/bash
# This script counts down from a specified number.
count=5
while [ $count -gt 0 ]; do
echo "T-minus $count..."
sleep 1 # Wait for 1 second
count=$((count - 1)) # Decrease the count by 1
done
Output:
T-minus 5...
T-minus 4...
T-minus 3...
T-minus 2...
T-minus 1...
C. The until Loop
The opposite of while. It runs a block of code until a condition becomes true. (Runs while the condition is false).
Syntax:
bash
until [ condition ]; do
# commands to run until the condition becomes TRUE
done
Example : Wait for a Website to Respond
bash
#!/bin/bash# This script pings a website until it gets a response.
echo "Waiting for example.com to come online..."
until ping -c 1 example.com &> /dev/null; do
echo "Site is unreachable. Retrying in 3 seconds..."
sleep 3
done
echo "SUCCESS: example.com is now reachable!"
Output:
text
Waiting for example.com to come online…
Site is unreachable. Retrying in 3 seconds…
Site is unreachable. Retrying in 3 seconds…
… (this repeats until a ping is successful)
SUCCESS: example.com is now reachable!