Bash Scripting for Hackers: Automation Basics (2026 Complete Guide)
Introduction
In the world of penetration testing and ethical hacking, bash scripting is one of the most powerful skills you can develop. While manual commands work for one-off tasks, automation through bash scripts enables you to perform complex operations efficiently, consistently, and at scale.
Whether you're conducting reconnaissance, analyzing logs, brute-forcing credentials, or enumerating targets, bash scripting transforms repetitive tasks into executable workflows. This tutorial will teach you the fundamentals of bash scripting specifically tailored for cybersecurity professionals and ethical hackers.
Prerequisites: Basic Linux terminal knowledge and familiarity with essential Linux commands.
Table of Contents
- Why Bash Scripting for Penetration Testers
- Bash Script Anatomy
- Variables in Bash
- Conditionals (if/else/case)
- Loops (for/while/until)
- Functions
- Arrays
- String Manipulation
- File I/O Operations
- Command Substitution
- Practical Pentesting Scripts
- Error Handling
- Scheduling with Cron
- Manual vs Scripted Workflow Comparison
- FAQ
Why Bash Scripting for Penetration Testers? {#why-bash-scripting}
The Power of Automation
Bash scripting enables penetration testers to:
✅ Automate Repetitive Tasks — Eliminate manual execution of the same commands across multiple targets
✅ Increase Efficiency — Run complex multi-stage attacks with a single command
✅ Ensure Consistency — Execute tests the same way every time, reducing human error
✅ Scale Operations — Process hundreds or thousands of targets simultaneously
✅ Create Custom Tools — Build specialized utilities tailored to your specific needs
✅ Chain Multiple Tools — Integrate Nmap, Metasploit, Burp Suite, and other tools seamlessly
✅ Schedule Reconnaissance — Set up automated cron jobs for continuous monitoring
Real-World Use Cases
- Automated port scanning across entire network ranges
- Subdomain enumeration with multiple tools and consolidation
- Log parsing and analysis to identify attack patterns
- Credential validation against multiple services
- Report generation with formatted output
- Continuous vulnerability monitoring of target infrastructure
Every professional penetration tester uses bash scripting daily. It's the glue that connects individual tools into powerful automated workflows.
Bash Script Anatomy {#bash-script-anatomy}
The Shebang Line
Every bash script starts with a shebang (#!) that tells the system which interpreter to use:
#!/bin/bash
This line must be the first line of your script. It specifies the absolute path to the bash interpreter.
Alternative shebangs:
#!/usr/bin/env bash # More portable, finds bash in PATH
#!/bin/sh # POSIX shell (less features)
Basic Script Structure
#!/bin/bash
# Script: basic_template.sh
# Description: Template for bash scripts
# Author: Andrax Pentester
# Date: 2026
# Variables
TARGET="192.168.1.1"
PORT=80
# Main logic
echo "Scanning target: $TARGET"
echo "Port: $PORT"
# Exit with status code
exit 0
Making Scripts Executable
After creating a script, make it executable:
chmod +x script.sh
./script.sh
Variables in Bash {#variables-in-bash}
Local Variables
Variables store data for use throughout your script.
#!/bin/bash
# Variable assignment (no spaces around =)
target="example.com"
port=443
protocol="https"
# Using variables (prefix with $)
echo "Target: $target"
echo "URL: ${protocol}://${target}:${port}"
Best practices:
- No spaces around the
=sign - Use
${variable}for clarity and to prevent ambiguity - Quote variables to handle spaces:
"$variable"
Environment Variables
Environment variables are available system-wide:
#!/bin/bash
# Read environment variables
echo "Current user: $USER"
echo "Home directory: $HOME"
echo "Current path: $PATH"
# Set environment variable for child processes
export API_KEY="your-api-key-here"
Positional Parameters (Command-Line Arguments)
Capture arguments passed to your script:
#!/bin/bash
# $0 = script name
# $1, $2, $3... = arguments
# $# = number of arguments
# $@ = all arguments as separate words
# $* = all arguments as a single word
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Total arguments: $#"
echo "All arguments: $@"
Usage:
./script.sh 192.168.1.1 80 tcp
# $1 = 192.168.1.1
# $2 = 80
# $3 = tcp
Special Variables
$? # Exit status of last command (0 = success)
$$ # Process ID of current script
$! # Process ID of last background command
Conditionals (if/else/case) {#conditionals}
if/elif/else Statements
#!/bin/bash
port=$1
if [ -z "$port" ]; then
echo "Error: No port specified"
exit 1
elif [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
echo "Error: Port must be between 1-65535"
exit 1
else
echo "Valid port: $port"
fi
Test Operators
Numeric comparisons:
-eq # Equal to
-ne # Not equal to
-lt # Less than
-le # Less than or equal to
-gt # Greater than
-ge # Greater than or equal to
String comparisons:
= # Equal to
!= # Not equal to
-z # String is empty
-n # String is not empty
File tests:
-e # File exists
-f # File is a regular file
-d # File is a directory
-r # File is readable
-w # File is writable
-x # File is executable
-s # File is not empty
case Statements
For multiple conditions:
#!/bin/bash
service=$1
case $service in
ssh)
port=22
;;
http)
port=80
;;
https)
port=443
;;
mysql)
port=3306
;;
*)
echo "Unknown service: $service"
exit 1
;;
esac
echo "$service runs on port $port"
Loops (for/while/until) {#loops}
for Loops
Iterate over a list:
#!/bin/bash
# Loop through IP addresses
for ip in 192.168.1.1 192.168.1.2 192.168.1.3; do
echo "Scanning $ip"
ping -c 1 "$ip" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "$ip is alive"
fi
done
Range-based loop:
#!/bin/bash
# Scan ports 1-100
for port in {1..100}; do
timeout 1 bash -c "echo > /dev/tcp/192.168.1.1/$port" 2>/dev/null
if [ $? -eq 0 ]; then
echo "Port $port is open"
fi
done
C-style loop:
#!/bin/bash
for ((i=1; i<=10; i++)); do
echo "Attempt $i"
done
while Loops
Execute while condition is true:
#!/bin/bash
count=1
while [ $count -le 5 ]; do
echo "Try $count"
((count++))
done
Reading files line by line:
#!/bin/bash
while IFS= read -r line; do
echo "Processing: $line"
done < targets.txt
until Loops
Execute until condition becomes true:
#!/bin/bash
count=1
until [ $count -gt 5 ]; do
echo "Count: $count"
((count++))
done
Functions {#functions}
Functions organize reusable code blocks.
Basic Function Syntax
#!/bin/bash
# Define function
check_host() {
local host=$1
ping -c 1 "$host" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "$host is reachable"
return 0
else
echo "$host is unreachable"
return 1
fi
}
# Call function
check_host "google.com"
check_host "192.168.1.1"
Functions with Return Values
#!/bin/bash
port_scan() {
local target=$1
local port=$2
timeout 1 bash -c "echo > /dev/tcp/$target/$port" 2>/dev/null
return $?
}
if port_scan "192.168.1.1" 80; then
echo "Port 80 is open"
else
echo "Port 80 is closed"
fi
Arrays {#arrays}
Arrays store multiple values in a single variable.
Indexed Arrays
#!/bin/bash
# Declare array
targets=("192.168.1.1" "192.168.1.2" "192.168.1.3")
# Access elements
echo "First target: ${targets[0]}"
echo "Second target: ${targets[1]}"
# Array length
echo "Total targets: ${#targets[@]}"
# Iterate over array
for target in "${targets[@]}"; do
echo "Scanning $target"
done
# Add element
targets+=("192.168.1.4")
# All elements
echo "All targets: ${targets[@]}"
Associative Arrays (Bash 4.0+)
#!/bin/bash
# Declare associative array
declare -A ports
ports[ssh]=22
ports[http]=80
ports[https]=443
ports[mysql]=3306
# Access by key
echo "SSH port: ${ports[ssh]}"
# Iterate over keys
for service in "${!ports[@]}"; do
echo "$service runs on port ${ports[$service]}"
done
String Manipulation {#string-manipulation}
String Length
string="example.com"
echo ${#string} # Output: 11
Substring Extraction
url="https://example.com/path"
echo ${url:8:11} # Output: example.com (start at position 8, length 11)
Replace Substring
path="/var/log/apache2/access.log"
echo ${path/apache2/nginx} # Replace first occurrence
echo ${path//\//-} # Replace all occurrences of / with -
Remove Prefix/Suffix
filename="report.txt"
# Remove extension
echo ${filename%.txt} # Output: report
# Extract extension
echo ${filename##*.} # Output: txt
Case Conversion (Bash 4.0+)
string="Example Domain"
echo ${string,,} # Lowercase: example domain
echo ${string^^} # Uppercase: EXAMPLE DOMAIN
File I/O Operations {#file-io-operations}
Reading Files
#!/bin/bash
# Read entire file
content=$(cat targets.txt)
# Read line by line
while IFS= read -r line; do
echo "Processing: $line"
done < targets.txt
Writing to Files
#!/bin/bash
# Overwrite file
echo "New content" > output.txt
# Append to file
echo "Additional line" >> output.txt
# Write multiple lines
cat > results.txt << EOF
Scan Results
============
Target: example.com
Status: Complete
EOF
File Existence Checks
#!/bin/bash
if [ -f "targets.txt" ]; then
echo "File exists"
while read -r target; do
echo "Target: $target"
done < targets.txt
else
echo "Error: targets.txt not found"
exit 1
fi
Command Substitution {#command-substitution}
Capture command output and store it in variables.
Syntax
# Modern syntax (preferred)
result=$(command)
# Old syntax (deprecated)
result=`command`
Examples
#!/bin/bash
# Get current date
today=$(date +%Y-%m-%d)
echo "Today: $today"
# Count files
file_count=$(ls -1 | wc -l)
echo "Files: $file_count"
# Store command output
open_ports=$(nmap -p- --open 192.168.1.1 | grep "open")
echo "$open_ports"
# Use in conditionals
if [ $(whoami) = "root" ]; then
echo "Running as root"
else
echo "Not root"
fi
Practical Pentesting Scripts {#practical-pentesting-scripts}
1. TCP Port Scanner
#!/bin/bash
# File: port_scanner.sh
# Description: Simple TCP port scanner using /dev/tcp
if [ $# -ne 3 ]; then
echo "Usage: $0 <target> <start_port> <end_port>"
exit 1
fi
target=$1
start_port=$2
end_port=$3
echo "[*] Scanning $target from port $start_port to $end_port"
echo "[*] Started at $(date)"
echo ""
for ((port=start_port; port<=end_port; port++)); do
timeout 1 bash -c "echo > /dev/tcp/$target/$port" 2>/dev/null
if [ $? -eq 0 ]; then
echo "[+] Port $port is OPEN"
fi
done
echo ""
echo "[*] Scan completed at $(date)"
Usage:
chmod +x port_scanner.sh
./port_scanner.sh 192.168.1.1 1 1000
2. Subdomain Enumerator
#!/bin/bash
# File: subdomain_enum.sh
# Description: Enumerate subdomains using wordlist
if [ $# -ne 2 ]; then
echo "Usage: $0 <domain> <wordlist>"
exit 1
fi
domain=$1
wordlist=$2
output="subdomains_${domain}.txt"
if [ ! -f "$wordlist" ]; then
echo "Error: Wordlist $wordlist not found"
exit 1
fi
echo "[*] Enumerating subdomains for $domain"
echo "[*] Using wordlist: $wordlist"
echo ""
> "$output" # Clear output file
while IFS= read -r subdomain; do
full_domain="${subdomain}.${domain}"
# Try DNS resolution
if host "$full_domain" > /dev/null 2>&1; then
echo "[+] Found: $full_domain"
echo "$full_domain" >> "$output"
fi
done < "$wordlist"
echo ""
echo "[*] Results saved to $output"
echo "[*] Total found: $(wc -l < "$output")"
Usage:
./subdomain_enum.sh example.com /usr/share/wordlists/subdomains.txt
3. Log Parser for Failed SSH Attempts
#!/bin/bash
# File: ssh_log_parser.sh
# Description: Parse SSH logs for failed login attempts
log_file="/var/log/auth.log"
output="failed_ssh_attempts.txt"
if [ ! -r "$log_file" ]; then
echo "Error: Cannot read $log_file (run as root)"
exit 1
fi
echo "[*] Parsing SSH failed login attempts"
echo ""
# Extract failed password attempts
grep "Failed password" "$log_file" | \
awk '{print $1, $2, $3, $11, $13}' | \
sort | uniq -c | sort -rn > "$output"
echo "[*] Top 10 attacking IPs:"
echo ""
head -10 "$output"
echo ""
echo "[*] Full report saved to $output"
echo "[*] Total unique attempts: $(wc -l < "$output")"
Usage:
sudo ./ssh_log_parser.sh
4. Credential Checker
#!/bin/bash
# File: credential_checker.sh
# Description: Test credentials against SSH service
if [ $# -ne 3 ]; then
echo "Usage: $0 <target> <username_file> <password_file>"
exit 1
fi
target=$1
username_file=$2
password_file=$3
if [ ! -f "$username_file" ] || [ ! -f "$password_file" ]; then
echo "Error: Username or password file not found"
exit 1
fi
echo "[*] Testing credentials against $target"
echo "[*] WARNING: Use only with permission!"
echo ""
while IFS= read -r username; do
while IFS= read -r password; do
echo -n "[*] Testing ${username}:${password}... "
# Use sshpass with timeout
timeout 5 sshpass -p "$password" ssh -o StrictHostKeyChecking=no \
-o ConnectTimeout=3 "${username}@${target}" "exit" 2>/dev/null
if [ $? -eq 0 ]; then
echo "SUCCESS!"
echo "[+] Valid credentials: ${username}:${password}" >> valid_creds.txt
else
echo "Failed"
fi
sleep 1 # Rate limiting
done < "$password_file"
done < "$username_file"
echo ""
echo "[*] Scan complete. Check valid_creds.txt for results."
Note: This is for educational purposes only. Always obtain proper authorization before testing.
Error Handling {#error-handling}
Exit Codes
#!/bin/bash
# Exit immediately on error
set -e
# Exit on undefined variable
set -u
# Pipe failure detection
set -o pipefail
# Combined
set -euo pipefail
Error Checking
#!/bin/bash
command_with_error() {
nmap -sV 192.168.1.1 -oN scan.txt
if [ $? -ne 0 ]; then
echo "Error: Nmap scan failed"
return 1
fi
return 0
}
if command_with_error; then
echo "Success"
else
echo "Failed"
exit 1
fi
Trap for Cleanup
#!/bin/bash
# Cleanup function
cleanup() {
echo "Cleaning up temporary files..."
rm -f /tmp/scan_results_*.txt
echo "Done"
}
# Execute cleanup on exit
trap cleanup EXIT
# Main script
echo "Running scan..."
nmap 192.168.1.1 > /tmp/scan_results_$(date +%s).txt
Scheduling with Cron {#scheduling-with-cron}
Cron Syntax
* * * * * command
│ │ │ │ │
│ │ │ │ └─── Day of week (0-7, Sunday=0 or 7)
│ │ │ └───── Month (1-12)
│ │ └─────── Day of month (1-31)
│ └───────── Hour (0-23)
└─────────── Minute (0-59)
Scheduling Examples
# Edit crontab
crontab -e
# Run script every day at 2 AM
0 2 * * * /root/scripts/daily_scan.sh
# Run every hour
0 * * * * /root/scripts/hourly_check.sh
# Run every 15 minutes
*/15 * * * * /root/scripts/quick_scan.sh
# Run Monday-Friday at 9 AM
0 9 * * 1-5 /root/scripts/weekday_scan.sh
# Run on the 1st of every month
0 0 1 * * /root/scripts/monthly_report.sh
Automated Reconnaissance Script
#!/bin/bash
# File: automated_recon.sh
# Description: Daily reconnaissance automation
target="example.com"
date_stamp=$(date +%Y-%m-%d)
output_dir="/root/recon/$target/$date_stamp"
mkdir -p "$output_dir"
echo "[*] Starting automated recon for $target"
# Subdomain enumeration
subfinder -d "$target" -o "$output_dir/subdomains.txt" 2>&1
# Port scanning
nmap -p- --open "$target" -oN "$output_dir/nmap_scan.txt" 2>&1
# Technology detection
whatweb "$target" > "$output_dir/whatweb.txt" 2>&1
echo "[*] Recon complete. Results in $output_dir"
Add to crontab:
0 3 * * * /root/scripts/automated_recon.sh >> /var/log/recon.log 2>&1
Manual vs Scripted Workflow Comparison {#comparison-table}
| Task | Manual Approach | Scripted Approach | Time Saved |
|---|---|---|---|
| Port Scan 10 hosts | Run nmap 10 times, type each IP | for ip in hosts; do nmap $ip; done | 90% |
| Subdomain Enumeration | Test each subdomain manually | Automated wordlist iteration | 95% |
| Log Analysis | Read logs line by line | Grep + awk parsing with sorting | 98% |
| Credential Testing | Type each user/pass combo | Loop through credential files | 99% |
| Daily Reconnaissance | Login daily and run commands | Cron-scheduled automation | 100% |
| Multi-tool Workflow | Run each tool separately | Chain tools with pipes and substitution | 85% |
| Report Generation | Copy/paste results manually | Automated formatting and file output | 90% |
| Error Handling | Notice and fix errors manually | Automatic error detection and logging | 80% |
Key Insight: Scripting transforms hours of manual work into seconds of automated execution, allowing you to focus on analysis rather than repetitive tasks.
Frequently Asked Questions {#faq}
1. What's the difference between #!/bin/bash and #!/bin/sh?
#!/bin/bash— Uses the full-featured Bash shell with arrays, functions, and advanced features#!/bin/sh— Uses the minimal POSIX shell with limited functionality but better portability
For penetration testing scripts, use #!/bin/bash for access to all features. If you need maximum portability across different Unix systems, use #!/bin/sh or #!/usr/bin/env bash.
2. How do I debug bash scripts?
Use these debugging techniques:
# Enable debug mode (print each command)
bash -x script.sh
# Or add to script:
set -x # Enable debugging
set +x # Disable debugging
# Verbose mode
set -v
# Check syntax without running
bash -n script.sh
ShellCheck is an excellent static analysis tool:
sudo apt install shellcheck
shellcheck script.sh
3. Can bash scripts replace Python for pentesting?
Both have their place:
Use Bash when:
- Chaining existing tools (Nmap, Metasploit, etc.)
- File and text processing
- Quick automation tasks
- System administration
- Running on minimal systems without Python
Use Python when:
- Complex data structures needed
- Network protocol implementation
- GUI applications
- Cross-platform compatibility
- Machine learning / advanced algorithms
Most professional pentesters use both: Bash for glue scripts and Python for custom tools.
4. How can I make my bash scripts faster?
Optimization techniques:
# Use GNU Parallel for concurrent execution
cat targets.txt | parallel -j 10 nmap -Pn {}
# Minimize subshells (slow)
count=$(cat file.txt | wc -l) # Bad
count=$(wc -l < file.txt) # Good
# Use built-in commands over external
echo ${#string} # Good
echo $string | wc -c # Bad (spawns process)
# Background processes for parallel tasks
for target in $targets; do
scan_target "$target" &
done
wait # Wait for all background jobs
5. How do I securely handle credentials in scripts?
Best practices:
# Never hardcode credentials
# BAD:
password="admin123"
# GOOD: Read from secure file
if [ -f "$HOME/.credentials" ]; then
source "$HOME/.credentials"
chmod 600 "$HOME/.credentials" # Restrict permissions
fi
# Or prompt user
read -sp "Enter password: " password
echo
# Or use environment variables
export API_KEY="your-key"
# Clear sensitive variables after use
unset password
unset API_KEY
Never commit credentials to git repositories!
Conclusion
Bash scripting is an essential skill for every penetration tester and ethical hacker. By mastering variables, conditionals, loops, functions, and file operations, you can automate complex workflows and dramatically increase your efficiency.
This tutorial covered:
✅ Bash script fundamentals (shebang, variables, arguments)
✅ Control structures (if/else, case, loops)
✅ Functions and arrays for organized code
✅ String manipulation and file I/O
✅ Command substitution for integrating tools
✅ Practical pentesting automation scripts
✅ Error handling and scheduling
Next Steps:
- Practice writing your own automation scripts
- Explore Kali Linux setup and configuration
- Integrate bash scripts with Nmap workflows
- Study the GNU Bash manual
- Check official Kali documentation
Remember: Automation is the key to scaling your penetration testing operations. Start small, build your script library, and continuously refine your workflows.
Happy scripting, and stay ethical!
Tutorial 16 of 105 in the Kali Linux Complete Security Series
Author: Andrax Pentester / Syed Abrar
Last Updated: 2026
Estimated Reading Time: 18 minutes
Related Tutorials:
- Linux Terminal Mastery: Command Line for Beginners
- 50 Essential Linux Commands for Cybersecurity
- Kali Linux Setup: Essential Post-Installation Steps
- Nmap Cheat Sheet: Complete Command Reference
Follow Andrax Pentester for more cybersecurity tutorials, tools, and ethical hacking guides.