Bash Scripting Fundamentals - Writing and Executing Scripts
Quick Answer: Create a Bash Script in Three Steps
To create a Bash script: (1) Start with #!/bin/bash as the first line, (2) Add your commands, (3) Make it executable with chmod +x script.sh. Then run it with ./script.sh.
Quick Comparison: Script Execution Methods
| Method | Syntax | Pros | Cons |
|---|---|---|---|
| Direct execution | ./script.sh | Fast, explicit intent | Needs execute permission |
| Bash interpreter | bash script.sh | Works without +x | Slower, less clear |
| Source/dot | . script.sh | Runs in current shell | Affects current environment |
| Scheduled (cron) | Via crontab | Automated, persistent | Needs proper setup |
Bottom line: Use chmod +x script.sh and ./script.sh for normal scripts. Use bash script.sh for debugging or when you canβt modify permissions.
What is a Bash Script?
A Bash script is simply a text file containing Bash commands that the system runs as a sequence. Instead of typing commands interactively one at a time, scripts let you save a series of commands and run them all at once, repeatedly, on a schedule, or pass them to others. Scripts turn ad-hoc commands into reusable, automated workflows.
Script vs Interactive Shell
| Aspect | Interactive Shell | Script |
|---|---|---|
| Execution | Command by command | All commands at once |
| Storage | Memory (lost on exit) | File (persistent) |
| Reusability | One session only | Unlimited |
| Automation | Manual | Automatic |
| Error handling | Interactive recovery | Predefined behavior |
Common Script Uses
- System administration: Backups, user management, updates
- Automation: Scheduled tasks via cron, deployment pipelines
- DevOps: Infrastructure provisioning, CI/CD integration
- Development: Build tools, testing scripts, environment setup
Script Structure and Anatomy
Minimal Script
#!/bin/bash
echo "Hello, Script!"
This three-line script demonstrates:
- Shebang (line 1): Tells the system to use Bash interpreter
- Command (line 3): The actual work
- Blank line: Organization (optional but recommended)
Typical Script Structure
#!/bin/bash
# ============================================================
# Script: backup.sh
# Purpose: Backup user directories
# Author: Admin
# Date: 2026-02-21
# ============================================================
# Configuration
BACKUP_DIR="/home/backups"
SOURCE_DIR="/home/user"
# Functions
create_backup() {
echo "Starting backup..."
tar -czf "$BACKUP_DIR/backup.tar.gz" "$SOURCE_DIR"
}
# Main
main() {
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
fi
create_backup
echo "Backup complete"
}
# Run main function
main "$@"
The Shebang (#!)
The shebang (also called hashbang) tells the system which interpreter to use for the script.
Shebang Syntax
#!/bin/bash
Common Shebangs
| Shebang | Interpreter | Use |
|---|---|---|
#!/bin/bash | Bash | Most common, full Bash features |
#!/bin/sh | POSIX shell | Maximum portability |
#!/usr/bin/env bash | Bash (via env) | Cross-platform compatibility |
#!/bin/zsh | Zsh | If you prefer Zsh |
Why Use /usr/bin/env bash?
#!/usr/bin/env bash
This is more portable because:
- Locates bash in your systemβs PATH
- Works even if bash is installed in non-standard location
- Better for cross-platform scripts
Location Check
Find where Bash is installed on your system:
which bash
# Output: /bin/bash
type -a bash
# Output: bash is /bin/bash
File Permissions
Scripts need execute permission to run directly. Without it, you must call bash script.sh explicitly.
Check Permissions
ls -l script.sh
# Output: -rw-r--r-- 1 user group 245 Feb 21 10:00 script.sh
The first part -rw-r--r-- shows permissions:
- First character
-= regular file (not directory) - Next three characters
rw-= owner can read/write (no execute) - Next three characters
r--= group can read only - Last three characters
r--= others can read only
Add Execute Permission
# Add execute for owner only
chmod u+x script.sh
# Add execute for owner, group, others
chmod +x script.sh
# Verify
ls -l script.sh
# Output: -rwxr-xr-x 1 user group 245 Feb 21 10:00 script.sh
The x now appears for the owner: rwx
Executing Scripts
There are several ways to run a Bash script.
Method 1: Direct Execution (Requires Shebang + Execute Permission)
./script.sh
The shebang tells the system to use Bash.
Method 2: Explicit Bash Interpreter
bash script.sh
This works even without a shebang or execute permission. The bash command is provided explicitly.
Method 3: Source the Script
source script.sh
# or
. script.sh
Runs the script in the current shell environment (not a subshell). Variables persist after execution.
Method 4: Using bash with -c Flag
bash -c 'echo "Hello from command string"'
Executes a bash command directly without a file.
Comparison
| Method | Shebang | Execute | Subshell | Use Case |
|---|---|---|---|---|
./script.sh | Required | Required | Yes | Standard execution |
bash script.sh | Not needed | Not needed | Yes | Portable, debugging |
source script.sh | Not needed | Not needed | No | Load functions/variables |
bash -c '...' | N/A | N/A | Yes | Quick commands |
Script Organization
Directory Layout
~/scripts/
βββ lib/
β βββ logging.sh # Logging functions
β βββ backup.sh # Backup functions
βββ bin/
β βββ daily-backup.sh # Main backup script
β βββ cleanup.sh # Cleanup script
βββ config/
βββ backup.conf # Configuration file
Sourcing Utilities
In main scripts, load utility functions:
#!/bin/bash
# Load utility functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/logging.sh"
source "$SCRIPT_DIR/lib/backup.sh"
# Use functions
log "Starting backup"
create_backup
log "Backup complete"
Comments and Documentation
Single-Line Comments
# This is a comment
echo "Hello" # Inline comment
Multi-Line Comments
# Comment block
# explaining what
# this section does
# Or using here-document
: << 'EOF'
This is a multi-line comment.
It doesn't execute.
It can span multiple lines.
EOF
Header Documentation
#!/bin/bash
# ============================================================
# Script: backup.sh
# Description: Automated daily backup of user directories
# Author: John Admin
# Created: 2026-02-21
# Modified: 2026-02-21
#
# Dependencies: tar, gzip, rsync
# Usage: ./backup.sh [source] [destination]
#
# Examples:
# ./backup.sh /home/user /backup
# ./backup.sh # Uses defaults from config
# ============================================================
Debugging Scripts
Enable Debug Output
# Method 1: Run with -x flag
bash -x script.sh
# Method 2: Enable inside script
set -x
echo "This will be traced"
set +x
echo "This won't be traced"
Output with -x:
+ echo 'Starting script'
Starting script
+ count=0
+ count=1
+ echo 'Count: 1'
Count: 1
Enable All Debugging Options
#!/bin/bash
# Exit on error
set -e
# Error on undefined variable
set -u
# Trace execution
set -x
# Pipe failures cause script failure
set -o pipefail
# Equivalent shorthand
set -euxo pipefail
Add Debug Output
#!/bin/bash
DEBUG=true
debug() {
if [ "$DEBUG" = true ]; then
echo "[DEBUG] $*" >&2
fi
}
debug "Script started"
count=0
debug "Count initialized to: $count"
Run with debug:
./script.sh # No debug output
DEBUG=true ./script.sh # Shows debug messages
Best Practices
1. Always Quote Variables
# Good
name="$1"
echo "Hello, $name"
# Bad (breaks with spaces)
echo Hello, $1
2. Use Functions for Organization
# Good
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}
backup() {
tar -czf "$BACKUP_FILE" "$SOURCE_DIR"
}
main() {
log "Starting backup"
backup
log "Backup complete"
}
main "$@"
3. Validate Input
#!/bin/bash
if [ $# -lt 2 ]; then
echo "Usage: $0 <source> <destination>"
exit 1
fi
source="$1"
destination="$2"
4. Use Set Options
#!/bin/bash
set -euo pipefail
# exit on error (e)
# error on undefined variables (u)
# pipe failures fail the script (o pipefail)
5. Handle Errors
#!/bin/bash
error() {
echo "ERROR: $*" >&2
exit 1
}
[ -f "$file" ] || error "File not found: $file"
Frequently Asked Questions
Q: Whatβs the difference between #!/bin/bash and #!/usr/bin/env bash?
A: #!/bin/bash assumes Bash is at /bin/bash. #!/usr/bin/env bash finds Bash in your PATH, making it more portable across systems where Bash might be in different locations.
Q: Can I run a script without execute permission?
A: Yes, if you call the Bash interpreter explicitly: bash script.sh. But for direct execution (./script.sh), you need execute permission and a shebang.
Q: Whatβs the difference between source and ./script.sh?
A: source runs the script in the current shell, so variables persist. ./script.sh runs in a subshell, so variables are isolated. Use source for loading functions, use ./script.sh for independent execution.
Q: How do I debug a script?
A: Use bash -x script.sh to trace execution, or add set -x inside the script. You can also add echo statements to print variable values.
Q: Should I use #!/bin/bash or #!/bin/sh?
A: Use #!/bin/bash for scripts you control. Use #!/bin/sh only if you need maximum portability to systems with minimal shells. Modern systems have Bash available.
Next Steps
Explore related topics:
- Bash Variables & Data Types - Master variable handling and scoping
- Bash Control Structures - Learn if/else, loops, and case statements
- Bash Functions - Write reusable code blocks
- Bash Error Handling - Robust error management
Now that you understand script fundamentals, youβre ready to write production-quality Bash scripts!