1. Introduction
When diagnosing Wi‑Fi congestion or planning coverage, a quick snapshot of nearby networks by band (2.4/5/6 GHz) is invaluable.
This script scans the air and reports visible BSSIDs total, a breakdown per 2.4 GHz / 5 GHz / 6 GHz, and the count of unique channels observed.
Example Use Cases:
- Detect neighborhood density before picking channels
- Track Wi‑Fi “noisiness” over time per band
- Validate 6 GHz availability/visibility on upgraded sites
2. Overview of the Script
This Bash script:
-
Attempts an
iw
scan with multiple fallbacks (best‑practice order):scan ap-force passive
→scan passive
→ plainscan
(active)
-
Parses the scan results with portable
awk
to:- Count visible BSSIDs overall and per band (2.4/5/6 GHz)
- Compute unique channel count across all bands
-
Emits zeroes if scanning isn’t possible (no interface/permissions/driver support)
Environment/Overrides:
IF
(defaultwlan0
) – Wi‑Fi interfaceIW
(default/sbin/iw
) – path toiw
SUDO
(defaultsudo
) – privilege escalation (can set to empty if not needed)
Prerequisites:
- Linux-based NetBeez agent with a Wi‑Fi NIC
iw
installed and permission to scan (may requiresudo
)- Bash +
awk
; locale forced toC
for stable parsing
Expected Output:
-
Five key metrics in
key=value
lines:scan_visible_bssids_total
scan_visible_bssids_24g
scan_visible_bssids_5g
scan_visible_bssids_6g
scan_visible_channels_total
3. Script Code
#!/usr/bin/env bash
# Wi‑Fi neighborhood counters (POSIX sh, BusyBox-friendly)
# Emits:
# scan_visible_bssids_total=
# scan_visible_bssids_24g=
# scan_visible_bssids_5g=
# scan_visible_bssids_6g=
# scan_visible_channels_total=
IF="${IF:-wlan0}"
IW="${IW:-/sbin/iw}"
SUDO="${SUDO:-sudo}"
export LC_ALL=C
run_scan() {
# Try correct ordering first, then fallbacks. Return scan text or empty.
# 1) ap-force passive (correct order)
out="$($SUDO "$IW" dev "$IF" scan ap-force passive 2>/dev/null)"; rc=$?
case "$rc:$out" in
0:*Usage*) rc=1;; # usage banner means bad combo on this build
esac
[ $rc -eq 0 ] && { printf '%s\n' "$out"; return; }
# 2) passive only
out="$($SUDO "$IW" dev "$IF" scan passive 2>/dev/null)"; rc=$?
case "$rc:$out" in
0:*Usage*) rc=1;;
esac
[ $rc -eq 0 ] && { printf '%s\n' "$out"; return; }
# 3) plain scan (active)
out="$($SUDO "$IW" dev "$IF" scan 2>/dev/null)"; rc=$?
case "$rc:$out" in
0:*Usage*) rc=1;;
esac
[ $rc -eq 0 ] && { printf '%s\n' "$out"; return; }
# 4) give up
printf '%s' ""
}
SCAN_OUT="$(run_scan)"
# If scan failed or returned nothing, emit zeros and exit cleanly
if [ -z "$SCAN_OUT" ]; then
echo "scan_visible_bssids_total=0"
echo "scan_visible_bssids_24g=0"
echo "scan_visible_bssids_5g=0"
echo "scan_visible_bssids_6g=0"
echo "scan_visible_channels_total=0"
exit 0
fi
# Parse with portable awk
printf '%s\n' "$SCAN_OUT" | awk '
BEGIN{
vb=0; v24=0; v5=0; v6=0
}
function band_of_freq(f){
if (f>=2400 && f<2500) return 24
if (f>=5000 && f<6000) return 5
if (f>=5925 && f<7125) return 6
return 0
}
function ch2g_from_freq(f){
if (f==2484) return 14
if (f>=2412 && f<=2472) return int((f-2412)/5)+1
return 0
}
/^BSS[[:space:]]/{
vb++; freq_seen=0; next
}
/^[[:space:]]+freq:[[:space:]]*/{
if (freq_seen) next
freq_seen=1
f=$0; gsub(/[^0-9]/,"",f); f+=0
b=band_of_freq(f)
if (b==24){
v24++
ch=ch2g_from_freq(f); if (ch>0) ch24[ch]=1
} else if (b==5){
v5++; ch5[f]=1 # freq key for 5 GHz
} else if (b==6){
v6++; ch6[f]=1 # freq key for 6 GHz
}
next
}
END{
visible_channels_total=0
for (k in ch24) visible_channels_total++
for (k in ch5) visible_channels_total++
for (k in ch6) visible_channels_total++
print "scan_visible_bssids_total=" vb
print "scan_visible_bssids_24g=" v24
print "scan_visible_bssids_5g=" v5
print "scan_visible_bssids_6g=" v6
print "scan_visible_channels_total=" visible_channels_total
}
'
4. Sample Output
scan_visible_bssids_total=42
scan_visible_bssids_24g=18
scan_visible_bssids_5g=23
scan_visible_bssids_6g=1
scan_visible_channels_total=21
5. Closing Remarks
Use this script to baseline Wi‑Fi density and band adoption around agents, or to verify improvements after channel/Tx‑power changes.
It’s resilient across differing iw
builds and cleanly degrades to zeroed metrics if scanning isn’t possible.
Extensions you could try:
- Emit unique channel counts per band (e.g.,
visible_channels_24g
) - Add RSSI/quality buckets (e.g.,
>-70 dBm
) for interference severity - Output JSON for easier ingestion and historical trending
- Accept interface via
IF
env var:IF=wlp2s0 SUDO= ./wifi_scan.sh
(skip sudo when not needed)