Bash Scripting for Hackers
Automate recon and glue your tools together
- › Write a runnable bash script with variables, conditionals, and loops
- › Read command exit codes and branch on success or failure
- › Loop over hosts, ports, or wordlist entries to automate repetitive work
- › Turn a manual recon routine into one reusable script
From typing commands to automating them
You already compose one-liners at the prompt. A script is just those commands saved in a file, with the logic — repeat this, only do that if this succeeded — made explicit. For a hacker, bash is the glue: it drives nmap across a subnet, hammers a login form with a wordlist, parses tool output, and chains ten tools into one command you run once. You will not write beautiful software in bash. You will write fast, ugly, effective automation, which is exactly what the job needs.
The skeleton
Every script starts the same way and must be made executable before it will run:
1#!/bin/bash2# ^ the "shebang": tells the OS to run this file with /bin/bash3 4target="10.10.14.7" # a variable — NO spaces around the =5echo "Scanning $target" # $target expands to its value6 7# run it:8# chmod +x hello.sh (make it executable — recall the x bit from Linux I)9# ./hello.shx="1" is assignment; x = "1" is an error (bash thinks x is a command). Inside tests, [ "$x" = "1" ] needs spaces *inside* the brackets. And always quote your variables — "$target" not $target — or a value with a space or empty value will silently break the command. Quoting bugs are the number-one source of "why doesn't my script work."Exit codes: how a script knows if something worked
Every command, when it finishes, sets a hidden number: $?, its exit code. 0 means success, anything else means failure. This is how your script makes decisions — not by parsing output, but by asking "did that succeed?"
1ping -c1 -W1 "$target" > /dev/null 2>&1 # silence it; we only want the exit code2if [ $? -eq 0 ]; then3 echo "$target is UP"4else5 echo "$target is down"6fi7 8# the idiomatic short form — && runs on success, || runs on failure:9ping -c1 -W1 "$target" >/dev/null 2>&1 && echo "UP" || echo "down"Why this is the core idea
ping returns 0 if the host replied, non-zero if it didn't. You never had to read ping's text — you asked $?. That pattern (run a command, branch on its exit code) is how you build tools that *react*: is the port open? did the login succeed? did the exploit land? The && / || short form is the same logic compressed: A && B means "do B only if A succeeded," A || B means "do B only if A failed."
Loops: the reason to automate at all
Recon is repetitive by nature — the same check across many hosts, ports, or words. A loop turns "do this 254 times by hand" into three lines. The for loop walks a list; the shell's brace expansion {1..254} generates one.
1#!/bin/bash2subnet="192.168.56"3for i in {1..254}; do4 ip="$subnet.$i"5 # background each ping so all 254 fire nearly at once, not one-by-one:6 ping -c1 -W1 "$ip" >/dev/null 2>&1 && echo "$ip is up" &7done8wait # block until all the backgrounded pings finish1#!/bin/bash2# try each word from a file as input to some check3while read -r word; do4 echo "trying: $word"5 # e.g. hydra/curl/ssh attempt goes here, branching on its exit code6done < wordlist.txtThe subnet sweep shows two techniques worth naming. for i in {1..254} loops the numbers 1 to 254. Appending & to the ping backgrounds each one so all 254 run concurrently instead of taking a slow second each; wait then pauses the script until they've all reported. That single trick — fan out with &, collect with wait — turns a four-minute serial scan into a four-second parallel one. The while read loop is the skeleton of every password/subdomain/directory brute-forcer you'll ever write: read a line, try it, react.
Functions: naming a routine
When a block of logic repeats, wrap it in a function. Arguments arrive as $1, $2, and so on. Functions turn a sprawling script into named steps you can reuse and reason about.
1#!/bin/bash2scan_port() { # $1 = host, $2 = port3 # bash can open a TCP socket itself via /dev/tcp — no nmap needed4 (echo > "/dev/tcp/$1/$2") >/dev/null 2>&1 \5 && echo " [$2] open" \6 || echo " [$2] closed"7}8 9host="$1"10echo "Scanning $host"11for port in 21 22 80 139 443 445 3306 8080; do12 scan_port "$host" "$port"13done/dev/tcp/host/port opens a TCP connection, and its success or failure (the exit code again) tells you whether the port is open. This matters on a stripped-down victim box where nmap isn't installed but bash always is. You'll reach for exactly this when you land somewhere bare.That is the whole toolkit: variables, exit codes, if/&&/||, for and while loops, and functions. It is not much, and it is enough. Everything from a subnet sweeper to a login brute-forcer to a "grab all the interesting files and tar them up" looter is these pieces arranged for a purpose. Bash won't make you a software engineer; it will make you fast, and speed is leverage.
Write a recon script
Write recon.sh that takes a target IP as its argument ($1) and does three things: (1) checks the host is up with a single ping and prints UP/DOWN, branching on the exit code; (2) if it's up, scans a list of common ports using bash's /dev/tcp (no nmap) and prints which are open; (3) writes the open ports to a file named <ip>.ports. Make it executable and run it against a box in your lab. Then extend it: accept a whole /24 (loop 1..254, backgrounded) and only port-scan the hosts that answered.
$ chmod +x recon.sh$ ./recon.sh 192.168.56.102$ cat 192.168.56.102.ports- › The script branches on $? (or && / ||), not on parsing text
- › Port scanning works with /dev/tcp and no external tools
- › Variables are quoted ("$1", "$ip") throughout
- › The subnet version fans out with & and collects with wait