Table of Contents
- What is Shell Scripting?
- 1.1 What is a Shell?
- 1.2 What is a Shell Script?
- 1.3 Why Learn Shell Scripting?
- Getting Started with Shell Scripting
- 2.1 The Shebang Line
- 2.2 Your First Shell Script
- 2.3 Making Scripts Executable
- 2.4 Running Shell Scripts
- Core Concepts
- 3.1 Variables
- 3.2 Control Structures (Conditionals and Loops)
- 3.3 Functions
- 3.4 Input/Output Handling
- Common Practices for Effective Scripting
- 4.1 Commenting and Documentation
- 4.2 Error Handling
- 4.3 Logging
- Best Practices
- 5.1 Quoting Variables to Avoid Pitfalls
- 5.2 Using Modern Syntax (
[[ ]]Over[ ]) - 5.3 Avoiding Reserved Names and Uppercase Variables
- 5.4 Validating Dependencies and Inputs
- Advanced Tips for Mastery
- 6.1 Arrays
- 6.2 Arithmetic Operations
- 6.3 Debugging Techniques
- 6.4 Sourcing Scripts
- Conclusion
- References
1. What is Shell Scripting?
1.1 What is a Shell?
A shell is a command-line interpreter that acts as an interface between the user and the operating system kernel. It reads commands from the user (or a script) and executes them. Popular shells include:
- Bash (Bourne-Again Shell): The most common shell, default on Linux and macOS.
- Zsh (Z Shell): Extends Bash with features like auto-completion and themes.
- sh (Bourne Shell): The original Unix shell (less feature-rich than Bash).
This guide focuses on Bash, as it is widely adopted and compatible with most systems.
1.2 What is a Shell Script?
A shell script is a text file containing a sequence of shell commands. Instead of typing commands manually, you can write them in a script and execute the file to run all commands at once. Scripts support logic (e.g., if-else), loops, variables, and functions, making them ideal for automation.
1.3 Why Learn Shell Scripting?
- Automation: Automate repetitive tasks (e.g., backups, log rotation, file cleanup).
- System Administration: Manage users, services, and configurations at scale.
- DevOps: Integrate with tools like
git,docker, or CI/CD pipelines. - Productivity: Replace manual, error-prone workflows with reusable scripts.
2. Getting Started with Shell Scripting
2.1 The Shebang Line
Every shell script starts with a shebang line (#!), which tells the system which interpreter to use. For Bash scripts, the shebang line is:
#!/bin/bash
This ensures the script runs with Bash, even if another shell is default.
2.2 Your First Shell Script
Let’s create a simple “Hello World” script. Open a text editor (e.g., nano, vim) and paste:
#!/bin/bash
# This is a comment (ignored by the shell)
echo "Hello, World!" # Print text to the terminal
Save the file as hello.sh.
2.3 Making Scripts Executable
By default, text files are not executable. Use chmod to grant execution permissions:
chmod +x hello.sh
2.4 Running Shell Scripts
Execute the script with:
./hello.sh # Runs the script in the current directory
Output:
Hello, World!
3. Core Concepts
3.1 Variables
Variables store data for reuse. In Bash, declare variables without spaces around =:
#!/bin/bash
name="Alice" # Local variable (no $ when assigning)
echo "Hello, $name!" # Use $ to access the variable
Key Variable Types:
- Local Variables: Defined in the script (e.g.,
name="Alice"). - Environment Variables: Global variables (e.g.,
$HOME,$PATH; avoid overwriting these). - Special Variables: Predefined by Bash for script metadata:
$0: Script name.$1,$2, …: Positional arguments (e.g.,./script.sh arg1 arg2→$1=arg1).$#: Number of arguments.$?: Exit code of the last command (0 = success, non-zero = error).
3.2 Control Structures
Control structures add logic to scripts.
If-Else Statements
Check conditions with if-else. Use [[ ]] (modern) or [ ] (POSIX-compliant) for comparisons:
#!/bin/bash
age=20
if [[ $age -ge 18 ]]; then # -ge = greater than or equal
echo "Adult"
elif [[ $age -ge 13 ]]; then
echo "Teenager"
else
echo "Child"
fi
Loops
Repeat commands with for, while, or until loops.
For Loop (Iterate Over Items):
#!/bin/bash
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do # Loop through array elements
echo "I like $fruit"
done
While Loop (Run Until Condition Fails):
#!/bin/bash
count=1
while [[ $count -le 5 ]]; do
echo "Count: $count"
((count++)) # Increment count
done
3.3 Functions
Reuse code with functions. Define them with function name() { ... } or name() { ... }:
#!/bin/bash
greet() {
local name=$1 # $1 = first argument to the function
echo "Hello, $name!"
}
greet "Bob" # Call the function with "Bob" as an argument
Output:
Hello, Bob!
3.4 Input/Output Handling
Read User Input
Use read to capture input from the user:
#!/bin/bash
echo "Enter your name:"
read -r name # -r prevents backslash escapes
echo "Hello, $name!"
Redirect Output
>: Overwrite a file (e.g.,echo "Hi" > output.txt).>>: Append to a file (e.g.,echo "Again" >> output.txt).|: Pipe output of one command to another (e.g.,ls -l | grep ".txt").
4. Common Practices for Effective Scripting
4.1 Commenting and Documentation
Add comments to explain why (not just what) the code does:
#!/bin/bash
# Backup log files to /tmp/backups (runs daily at 2 AM via cron)
LOG_DIR="/var/log"
BACKUP_DIR="/tmp/backups"
mkdir -p "$BACKUP_DIR" # Create backup dir if it doesn't exist
cp "$LOG_DIR"/*.log "$BACKUP_DIR" # Copy logs to backup
4.2 Error Handling
Prevent silent failures with:
set -e: Exit the script if any command fails (non-zero exit code).set -u: Treat unset variables as errors.trap: Run commands on script exit (e.g., clean up temporary files).
Example:
#!/bin/bash
set -euo pipefail # Exit on error, unset var, or pipe failure
# Cleanup temporary files on exit
cleanup() {
rm -f /tmp/tempfile.txt
echo "Cleanup done."
}
trap cleanup EXIT # Run cleanup when script exits
# Simulate an error (script will exit here)
false # This command fails (exit code 1)
echo "This line will never run."
4.3 Logging
Log messages to a file for debugging and auditing:
#!/bin/bash
LOG_FILE="/var/log/myscript.log"
log() {
local message="$1"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $message" >> "$LOG_FILE"
}
log "Script started"
log "User $USER ran the script"
5. Best Practices
5.1 Quote Variables to Avoid Word Splitting
Unquoted variables with spaces cause “word splitting.” Always quote variables with "$var" to preserve spaces:
#!/bin/bash
name="Alice Smith"
echo "Hello, $name" # Good: Outputs "Hello, Alice Smith"
echo "Hello, " $name # Bad: Outputs "Hello, Alice Smith" (accidental, but risky)
echo "Files: $HOME/*" # Bad: Expands to filenames; use "$HOME"/* instead
5.2 Use [[ ]] Instead of [ ]
[[ ]] (Bash-specific) supports regex, globbing, and avoids edge cases (e.g., empty variables):
#!/bin/bash
name=""
# Bad: [ ] fails if variable is empty (needs quotes)
if [ "$name" = "Alice" ]; then echo "Match"; fi
# Good: [[ ]] handles empty variables safely
if [[ $name = "Alice" ]]; then echo "Match"; fi
5.3 Avoid Reserved Names and Uppercase Variables
- Reserved Names: Don’t name variables/functions after commands (e.g.,
ls,cd). - Uppercase Variables: Reserve for environment variables (e.g.,
PATH). Use lowercase for local variables.
5.4 Validate Dependencies and Inputs
Check if required commands or files exist before running:
#!/bin/bash
# Check if "git" is installed
if ! command -v git &> /dev/null; then
echo "Error: git is not installed."
exit 1
fi
# Check if input file exists
input_file="data.txt"
if [[ ! -f "$input_file" ]]; then
echo "Error: $input_file not found."
exit 1
fi
6. Advanced Tips for Mastery
6.1 Arrays
Store lists of data with arrays:
#!/bin/bash
colors=("red" "green" "blue")
echo "First color: ${colors[0]}" # Access by index (0-based)
echo "All colors: ${colors[@]}" # Expand all elements
6.2 Arithmetic Operations
Use $(( ... )) for arithmetic:
#!/bin/bash
a=5
b=3
sum=$((a + b))
echo "5 + 3 = $sum" # Output: 5 + 3 = 8
6.3 Debugging Techniques
set -x: Print commands as they run (debug mode).bash -x script.sh: Run the script with debugging without editing it.
Example:
#!/bin/bash
set -x # Enable debugging
name="Alice"
echo "Hello, $name"
set +x # Disable debugging
6.4 Sourcing Scripts
Use source script.sh or . script.sh to run a script in the current shell (import variables/functions):
# In utils.sh
greet() { echo "Hello, $1"; }
# In main.sh
source ./utils.sh # Import greet()
greet "Charlie" # Output: Hello, Charlie
7. Conclusion
Shell scripting is a foundational skill for automation and system management. By mastering variables, control structures, and best practices like error handling and quoting, you can write scripts that are robust, maintainable, and efficient.
Start small (e.g., automate file backups), then gradually tackle complex tasks. Refer to the Bash manual and experiment—practice is key to mastery!
8. References
- GNU Bash Manual
- ShellCheck (Linter for shell scripts)
- Bash Hackers Wiki
- Advanced Bash-Scripting Guide
- The Linux Command Line (Book) (Free online)