
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!