Table of Contents
- What is a Shell Script?
- Setting Up Your Environment
- Basic Shell Script Structure
- Variables and Data Types
- Control Structures
- Input/Output Handling
- Functions
- Common Practices
- Best Practices
- Sample Script: Putting It All Together
- Conclusion
- References
What is a Shell Script?
A shell script is a plain text file that contains a series of commands interpreted and executed by a shell. The shell acts as an intermediary between the user and the operating system, translating commands into actions.
Common shells include:
- Bash (Bourne Again SHell): The most popular shell, default on Linux and macOS.
- Zsh (Z Shell): An extended version of Bash with more features.
- Sh (Bourne Shell): The original Unix shell (less common today).
Shell scripts are used for:
- Automating repetitive tasks (e.g., renaming files, backups).
- System administration (e.g., monitoring logs, managing users).
- Running sequences of commands with conditional logic.
Setting Up Your Environment
Before writing scripts, ensure your environment is ready:
1. Check Your Shell
Most systems use Bash by default. Verify with:
echo $SHELL # Output: /bin/bash (or /bin/zsh, etc.)
2. Text Editor
Use a simple editor like nano, vim, or VS Code. For beginners, nano is user-friendly:
nano my_script.sh # Creates/opens "my_script.sh"
3. Make Scripts Executable
Scripts are not executable by default. Use chmod to set permissions:
chmod +x my_script.sh # Grants execute permission
4. Run the Script
Execute with:
./my_script.sh # Runs the script (if in current directory)
# Or specify the shell explicitly:
bash my_script.sh
Basic Shell Script Structure
A minimal shell script has two key components: the shebang line and commands.
Shebang Line
The first line of a script, #!/bin/bash, tells the system which shell to use. Always include this to avoid ambiguity:
#!/bin/bash
Comments
Use # for comments (ignored by the shell). Explain why (not what) the code does:
#!/bin/bash
# This script prints a greeting
echo "Hello, World!" # Output: Hello, World!
Example: “Hello World” Script
Create hello_world.sh:
#!/bin/bash
# My first shell script
echo "Hello, World!"
Run it:
chmod +x hello_world.sh
./hello_world.sh # Output: Hello, World!
Variables and Data Types
Variables store data for reuse. Shell scripting has loose typing (no need to declare types).
Declaring Variables
Use VAR_NAME=value (no spaces around =):
name="Alice"
age=30
Accessing Variables
Prefix with $ or ${} (curly braces avoid ambiguity):
echo "Name: $name" # Output: Name: Alice
echo "Age: ${age} years" # Output: Age: 30 years
Special Variables
Shells provide built-in variables for common tasks:
$0: Script name (e.g.,my_script.sh).$1, $2...: Command-line arguments (e.g.,./script.sh arg1 arg2→$1=arg1).$#: Number of arguments.$?: Exit status of the last command (0 = success, non-zero = error).$HOME: User’s home directory (e.g.,/home/alice).
Example with command-line arguments:
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Number of arguments: $#"
Run with:
./script.sh apple banana
# Output:
# Script name: ./script.sh
# First argument: apple
# Number of arguments: 2
Environment Variables
Set variables globally with export:
export PATH="$PATH:/new/directory" # Adds a directory to PATH
Control Structures
Control structures (conditionals, loops) add logic to scripts.
If-Else Statements
Check conditions and execute code accordingly. Syntax:
if [ condition ]; then
# Code if true
elif [ another_condition ]; then
# Code if first condition false, second true
else
# Code if all conditions false
fi
Common Conditions:
-eq: Equal (numbers, e.g.,5 -eq 5).-ne: Not equal.-gt: Greater than.-lt: Less than.-f "file": File exists.-z "string": String is empty.
Example: Check if a number is positive:
#!/bin/bash
read -p "Enter a number: " num # Read user input
if [ $num -gt 0 ]; then
echo "$num is positive"
elif [ $num -lt 0 ]; then
echo "$num is negative"
else
echo "$num is zero"
fi
Loops (For and While)
For Loops
Iterate over a list of items:
# Syntax: for item in list; do ... done
for fruit in apple banana cherry; do
echo "I like $fruit"
done
Output:
I like apple
I like banana
I like cherry
Loop through numbers with {start..end}:
for i in {1..5}; do
echo "Count: $i"
done
While Loops
Repeat while a condition is true:
# Syntax: while [ condition ]; do ... done
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$((count + 1)) # Increment count (arithmetic expansion)
done
Input/Output Handling
Read User Input
Use read to capture input from the user:
#!/bin/bash
read -p "Enter your name: " name # -p adds a prompt
echo "Hello, $name!"
Redirect Output
>: Overwrite a file (e.g.,echo "Hi" > output.txt).>>: Append to a file (e.g.,echo "Again" >> output.txt).2>: Redirect errors (e.g.,ls non_existent_file 2> error.log).
Pipes (|)
Pass output of one command to another:
ls -l | grep ".txt" # List files, then filter for .txt
Functions
Functions group reusable code. Syntax:
function_name() {
# Code here
# Use $1, $2... for parameters
}
Example: Greet a user:
#!/bin/bash
greet() {
local name=$1 # "local" limits variable scope to the function
echo "Hello, $name!"
}
greet "Bob" # Output: Hello, Bob!
Return values: Use echo to return a value and capture with $(function):
add() {
echo $(( $1 + $2 ))
}
sum=$(add 3 5)
echo "Sum: $sum" # Output: Sum: 8
Common Practices
Test Scripts First
- Use
bash -n script.shto check for syntax errors (dry run). - Use
bash -x script.shto debug (prints each command as it runs).
Handle Errors Gracefully
Check if commands succeed before proceeding:
if ! cp file.txt backup/; then # "!" inverts success status
echo "Error: Failed to copy file"
exit 1 # Exit with non-zero code to indicate failure
fi
Best Practices
1. Use the Shebang Line
Always start with #!/bin/bash (or your shell) to avoid relying on the system default.
2. Quote Variables
Quoting prevents issues with spaces in filenames/strings:
filename="my file.txt"
echo "$filename" # Correct: preserves spaces
# echo $filename # Incorrect: splits into "my" and "file.txt"
3. Use set -euo pipefail
Add this at the top to make scripts robust:
-e: Exit on any command failure.-u: Treat undefined variables as errors.-o pipefail: Exit if any command in a pipe fails.
#!/bin/bash
set -euo pipefail # Strict error checking
4. Limit Scope with local Variables
In functions, use local to avoid polluting the global namespace:
my_func() {
local var="local only" # Not accessible outside the function
}
5. Avoid Hard-Coded Paths
Use variables for paths to make scripts portable:
BACKUP_DIR="$HOME/backups" # Instead of "/home/alice/backups"
mkdir -p "$BACKUP_DIR" # "-p" creates parent dirs if needed
Sample Script: Putting It All Together
Let’s write a script that backs up .txt files from a directory to a backup folder, logs the action, and counts the files backed up.
#!/bin/bash
set -euo pipefail
# Configuration
SOURCE_DIR="$1" # First command-line argument: source directory
BACKUP_DIR="$HOME/backups"
LOG_FILE="$BACKUP_DIR/backup_log.txt"
# Check if source directory is provided
if [ $# -eq 0 ]; then
echo "Error: Please provide a source directory."
echo "Usage: $0 <source_directory>"
exit 1
fi
# Check if source directory exists
if [ ! -d "$SOURCE_DIR" ]; then
echo "Error: Directory '$SOURCE_DIR' does not exist."
exit 1
fi
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"
# Count .txt files
FILE_COUNT=$(find "$SOURCE_DIR" -maxdepth 1 -type f -name "*.txt" | wc -l)
# Backup .txt files
echo "Backing up $FILE_COUNT .txt files from $SOURCE_DIR to $BACKUP_DIR..."
cp "$SOURCE_DIR"/*.txt "$BACKUP_DIR"/
# Log the action
timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$timestamp] Backed up $FILE_COUNT files from $SOURCE_DIR" >> "$LOG_FILE"
echo "Backup completed successfully. Logged to $LOG_FILE"
exit 0
Run it with:
./backup_script.sh /path/to/txt_files
Conclusion
Shell scripting is a powerful tool for automating tasks and simplifying system administration. By mastering the basics—variables, control structures, input/output, and functions—you can write scripts to save time and reduce errors.
Start small: automate a daily task (e.g., organizing downloads), then gradually add complexity. Remember to follow best practices like quoting variables and handling errors to write robust scripts.
Happy scripting!
References
- GNU Bash Manual
- Bash Guide for Beginners
- ShellCheck (Tool to lint shell scripts)
- DigitalOcean Bash Scripting Tutorial