Shell Scripting for Developer Productivity
In this tutorial, you'll learn shell scripting including variables, conditionals, loops, functions, error handling, and writing utility scripts that automate repetitive development tasks.
Why Shell Scripting Matters
Every developer faces repetitive tasks: renaming files, deploying code, backing up databases, processing logs, or setting up projects. Shell scripting turns these into one-command operations. A well-written Shell Script saves minutes every day and hours every week. Unlike higher-level languages, shell scripting gives you direct access to the operating system, pipelines, and all command-line tools.
By the end of this guide, you will write robust shell scripts that handle errors, accept arguments, and automate real development workflows.
What is Shell Scripting?
A Shell Script is a file containing a sequence of commands for a Unix shell (Bash, Zsh, etc.). Scripts automate multi-step tasks that would otherwise require manual command entry.
flowchart LR A[Shell Script] --> B[Variables] A --> C[Conditionals] A --> D[Loops] A --> E[Functions] B --> F[User Input] B --> G[Command Output] C --> H[if/else] C --> I[case] D --> J[for] D --> K[while] E --> L[Reusable Code Blocks]
Your First Script
#!/bin/bash
# hello.sh — A simple greeting script
echo "Hello, World!"
Making It Executable
chmod +x hello.sh
./hello.sh
Expected Output
Hello, World!
Variables
#!/bin/bash
# Defining variables (no spaces around =)
name="Alice"
greeting="Hello"
# Using variables with $
echo "$greeting, $name!"
# Command substitution
current_date=$(date)
echo "Today is: $current_date"
# Arithmetic
count=$((5 + 3))
echo "Count: $count"
Expected Output
Hello, Alice!
Today is: Mon Jun 22 09:15:00 UTC 2026
Count: 8
Variable Best Practices
# Use uppercase for environment variables
export PROJECT_DIR="$HOME/projects/myapp"
# Use quotes to handle spaces
filename="my file.txt"
cat "$filename" # Correct
cat $filename # Wrong — splits on space
# Default values
echo "${name:-World}" # Uses "World" if $name is unset
# Read-only variables
readonly VERSION="1.0.0"
Conditionals
#!/bin/bash
# if/elif/else
file="test.txt"
if [ -f "$file" ]; then
echo "$file exists"
elif [ -d "$file" ]; then
echo "$file is a directory"
else
echo "$file does not exist"
fi
# Numeric comparison
count=42
if [ "$count" -gt 10 ]; then
echo "Count is greater than 10"
fi
# String comparison
name="Alice"
if [ "$name" = "Alice" ]; then
echo "Hello, Alice!"
fi
File Test Operators
| Operator | True if |
|---|---|
-f |
File exists and is a regular file |
-d |
Directory exists |
-e |
File exists (any type) |
-r |
File is readable |
-w |
File is writable |
-x |
File is executable |
-s |
File exists and is not empty |
Loops
For Loop
#!/bin/bash
# Loop over files
for file in *.txt; do
echo "Processing $file"
wc -l "$file"
done
# Loop over a range
for i in {1..5}; do
echo "Iteration $i"
done
# Loop over command output
for user in $(cat /etc/passwd | cut -d: -f1); do
echo "User: $user"
done
While Loop
#!/bin/bash
# Read file line by line
while IFS= read -r line; do
echo "Line: $line"
done < input.txt
# Countdown
count=5
while [ "$count" -gt 0 ]; do
echo "$count..."
count=$((count - 1))
sleep 1
done
echo "Go!"
Functions
#!/bin/bash
# Define a function
greet() {
local name="$1"
echo "Hello, $name!"
}
# Function with return value
is_even() {
local num=$1
if [ $((num % 2)) -eq 0 ]; then
return 0 # Success
else
return 1 # Failure
fi
}
# Call functions
greet "Alice"
if is_even 42; then
echo "42 is even"
fi
Error Handling
#!/bin/bash
set -e # Exit on any error
set -u # Exit on undefined variable
set -o pipefail # Fail if any command in a pipe fails
# Or combine: set -euo pipefail
# Manual error checking
if ! mkdir -p "$OUTPUT_DIR"; then
echo "Error: Could not create directory $OUTPUT_DIR" >&2
exit 1
fi
# Trap errors
cleanup() {
echo "Cleaning up..."
rm -rf "$TEMP_DIR"
}
trap cleanup EXIT
TEMP_DIR=$(mktemp -d)
# ... do work ...
# cleanup runs automatically on exit
Argument Parsing
#!/bin/bash
usage() {
echo "Usage: $0 -n <name> -o <output> [-v]"
echo " -n Name (required)"
echo " -o Output directory (required)"
echo " -v Verbose mode"
exit 1
}
verbose=false
while getopts "n:o:v" opt; do
case $opt in
n) name="$OPTARG" ;;
o) output="$OPTARG" ;;
v) verbose=true ;;
*) usage ;;
esac
done
if [ -z "${name:-}" ] || [ -z "${output:-}" ]; then
usage
fi
if [ "$verbose" = true ]; then
echo "Name: $name"
echo "Output: $output"
fi
Real-World Script: Project Scaffolder
#!/bin/bash
# scaffold.sh — Create a new project structure
set -euo pipefail
PROJECT_NAME="${1:-my-project}"
PROJECT_DIR="$HOME/projects/$PROJECT_NAME"
create_project() {
local name="$1"
local dir="$2"
echo "Creating project: $name"
# Create directory structure
mkdir -p "$dir"/{src,test,docs,scripts}
mkdir -p "$dir/src"/{components,utils,styles}
# Create files
cat > "$dir/README.md" << EOF
# $name
Project description here.
EOF
cat > "$dir/.gitignore" << EOF
node_modules/
dist/
.env
*.log
EOF
cat > "$dir/package.json" << EOF
{
"name": "$name",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest"
}
}
EOF
# Initialize git
cd "$dir"
git init
git add .
git commit -m "Initial commit: scaffold $name"
echo "Project created at: $dir"
}
if [ -d "$PROJECT_DIR" ]; then
echo "Error: Directory $PROJECT_DIR already exists" >&2
exit 1
fi
create_project "$PROJECT_NAME" "$PROJECT_DIR"
Debugging Scripts
# Dry run (show commands without executing)
bash -n script.sh
# Trace execution
bash -x script.sh
# Debug sections in the script
set -x # Start debugging
# ... code to debug ...
set +x # Stop debugging
Common Errors
| Problem | Cause | Fix |
|---|---|---|
command not found |
Script not in PATH or not executable | Use ./script.sh or add to PATH |
[: too many arguments |
Unquoted variable with spaces | Always quote variables: [ "$var" = "value" ] |
syntax error: unexpected end of file |
Missing closing fi, done, or esac |
Check block terminator matching |
Permission denied |
File not executable | chmod +x script.sh |
| Line endings cause errors on Linux | Windows line endings (\r\n) |
Use dos2unix script.sh or set editor to LF |
Practice Questions
1. What is the shebang line and why is it used?
#!/bin/bash at the top tells the system which Interpreter to use.
2. How do you capture the output of a command into a variable?
variable=$(command).
3. What does set -euo pipefail do?
Exits on error, undefined variables, and any pipe failure.
4. How do you define a function in bash?
function_name() { ... } and call it as function_name.
5. What is the difference between > and >> in redirection?
> overwrites the file. >> appends to the file.
Challenge
Write a Shell Script called backup.sh that creates a timestamped backup of a specified directory, compresses it with gzip, stores backups in a ~/backups/ directory, keeps only the last 7 backups (deletes older ones), and logs all operations to a log file. Include error handling for missing directories and disk space issues.
Real-World Task
Write a Shell Script that automates your daily development setup. The script should: pull the latest changes from Git for a project, install any new dependencies, run database migrations, and start the development server. Make it accept an optional project name argument. Add verbose and help flags.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro