← All scripts

// Bash scripts

Split a VLAN range into subnets

Calculates the matching subnets (prefix length configurable, default /27) from a range of VLAN IDs, based on a fixed VLAN-to-subnet mapping.

Network
Version2.1.3
Updated2026-07-11
Tested onTested on a Arch Linux x86_64
LicenceMIT
Requiresbash 5
Size185 lines · 5.8 KB
SHA256 2f99d7a8a9f45d171758187d639d26220c41cee851698fab25163ba9e275b2b2 Verify after downloading: echo "2f99d7a8a9f45d171758187d639d26220c41cee851698fab25163ba9e275b2b2 netdivide.sh" | sha256sum -c

Read it first, then run it. The complete source is further down on this page. There is deliberately no curl … | bash one-liner here.

Use at your own risk — no liability for data loss or system damage. See the terms of use.

What the script is for

In some networks, every VLAN ID maps to its own subnet — a convention that saves you from looking things up in docs or a spreadsheet, but still means doing math in your head once you're dealing with a whole range of VLANs or with different subnet sizes. The script does that conversion in one step.

What it does

  • Calculates the matching subnets for a VLAN range (START-VLAN END-VLAN).
  • Shows the start network, end network, and how many /24-sized units are needed.
  • Base network (--base), anchor VLAN (--anchor, the VLAN ID that maps to

x.x.0.0/PREFIX), and prefix length (--prefix, 8 to 30, default 27) are all configurable.

  • Warns if the range overflows the third octet (base network or prefix too small).
  • Provides Bash autocompletion for its options via --completion (enable it using source <(netdivide.sh --completion) or save it permanently in /etc/bash_completion.d/).

How it works

Instead of a lookup table, the script computes the position purely arithmetically. Each VLAN ID gets an index relative to the anchor VLAN, and that index maps directly to a position in the address space via integer division and modulo — depending on whether the subnet is smaller or larger than a /24:

local block_size=$(( 2 ** (32 - prefix) ))

if [ "$prefix" -ge 24 ]; then
    local per_24=$(( 256 / block_size ))
    s_third=$(( s_idx / per_24 ))
    s_fourth=$(( (s_idx % per_24) * block_size ))
else
    local step3=$(( block_size / 256 ))
    s_third=$(( s_idx * step3 ))
    s_fourth=0
fi

From /24 upward, the subnet fits entirely into the fourth octet, just like classic /27 or /26 user subnets. If the prefix is smaller than /24 (e.g. /23 or /22), the subnet is larger than a /24 and spans multiple units in the third octet — the fourth octet then stays at 0, and the step size in the third octet grows accordingly. Both cases boil down to the same idea: don't maintain a table, derive the position from the definition of the anchor VLAN and the block size.

Why I automated this

I’ve often found myself having to calculate multiple /27 (or other) subnets because each VLAN required its own interface. Since using a calculator for this felt too tedious and confusing, I eventually put together a quick script to simplify the process and have been expanding it ever since.

Usage

bash netdivide.sh --help
bash netdivide.sh 1001 1008
bash netdivide.sh --prefix 26 1001 1008
source <(bash netdivide.sh --completion)   # load bash autocompletion

Source

netdivide.sh
#!/usr/bin/env bash
#
#  netdivide.sh - teilt eine VLAN-Range in gleich grosse Subnetze auf
#
#  Autor:   Matthias Meister — https://scripts.web.mm-core.de
#  Version: 2.0.0
#
#  Lizenz:  MIT — Copyright (c) 2026 Matthias Meister
#           Volltext: https://scripts.web.mm-core.de/lizenz/
#
set -euo pipefail

readonly VERSION="2.0.0"
readonly MIN_PREFIX=8
readonly MAX_PREFIX=30

usage() {
    cat <<'EOF'
netdivide.sh - teilt eine VLAN-Range in gleich grosse Subnetze auf

Nutzung:
  netdivide.sh [OPTIONEN] START-VLAN END-VLAN

Optionen:
  -b, --base   NETZ   Basis-Netz (erste zwei Oktette). Default: 10.100
  -a, --anchor VLAN   VLAN-ID, die auf das erste Subnetz (x.x.0.0/PRAEFIX)
                      zeigt. Default: 1001
  -p, --prefix N      CIDR-Praefixlaenge des Subnetzes je VLAN. Default: 27
                      Erlaubt: 8-30
  -h, --help          Diese Hilfe anzeigen
  -V, --version       Versionsnummer anzeigen

Logik:
  Jede VLAN-ID bekommt genau ein Subnetz der Groesse 2^(32-Praefix).

  Bei Praefix 24-30 passt das Subnetz ins vierte Oktett:
      Index      = VLAN - Anchor
      pro /24    = 256 / Blockgroesse
      3. Oktett  = Index / pro_24
      4. Oktett  = (Index % pro_24) * Blockgroesse

  Bei Praefix 8-23 ist das Subnetz groesser als ein /24 und belegt mehrere
  Einheiten im dritten Oktett:
      Schrittweite = Blockgroesse / 256
      3. Oktett    = Index * Schrittweite
      4. Oktett    = 0

Beispiele:
  netdivide.sh 1001 1008
      -> /27-Netze (Default), 10.100.0.0/27 bis 10.100.0.224/27
  netdivide.sh --prefix 26 1001 1008
      -> /26-Netze, je 64 Adressen
  netdivide.sh --prefix 23 --base 10.200 --anchor 2001 2001 2010
      -> /23-Netze, je 512 Adressen ueber zwei Einheiten im 3. Oktett

Exit-Codes:
  0  alles gut
  1  inhaltliches Problem (z. B. END-VLAN < START-VLAN)
  2  Aufruffehler (falsche/fehlende Argumente)
EOF
}

main() {
    local base_net="10.100"
    local anchor_vlan=1001
    local prefix=27
    local positional=()

    while [ "$#" -gt 0 ]; do
        case "$1" in
            -h|--help)
                usage
                return 0
                ;;
            -V|--version)
                echo "netdivide.sh ${VERSION}"
                return 0
                ;;
            -b|--base)
                [ "$#" -ge 2 ] || { echo "FEHLER: --base benoetigt ein Argument." >&2; return 2; }
                base_net="$2"
                shift 2
                ;;
            -a|--anchor)
                [ "$#" -ge 2 ] || { echo "FEHLER: --anchor benoetigt ein Argument." >&2; return 2; }
                anchor_vlan="$2"
                shift 2
                ;;
            -p|--prefix)
                [ "$#" -ge 2 ] || { echo "FEHLER: --prefix benoetigt ein Argument." >&2; return 2; }
                prefix="$2"
                shift 2
                ;;
            --)
                shift
                while [ "$#" -gt 0 ]; do
                    positional+=("$1")
                    shift
                done
                ;;
            -*)
                echo "FEHLER: Unbekannte Option '$1'" >&2
                echo "Hilfe:  netdivide.sh --help" >&2
                return 2
                ;;
            *)
                positional+=("$1")
                shift
                ;;
        esac
    done
    set -- "${positional[@]}"

    if [ "$#" -ne 2 ]; then
        echo "FEHLER: Bitte START- und END-VLAN angeben." >&2
        echo "Hilfe:  netdivide.sh --help" >&2
        return 2
    fi

    local start_vlan="$1" end_vlan="$2"

    if ! [[ "$start_vlan" =~ ^[0-9]+$ && "$end_vlan" =~ ^[0-9]+$ && "$anchor_vlan" =~ ^[0-9]+$ ]]; then
        echo "FEHLER: VLAN-IDs und Anchor-VLAN muessen ganze Zahlen sein." >&2
        return 2
    fi
    if ! [[ "$prefix" =~ ^[0-9]+$ ]] || [ "$prefix" -lt "$MIN_PREFIX" ] || [ "$prefix" -gt "$MAX_PREFIX" ]; then
        echo "FEHLER: --prefix muss eine ganze Zahl zwischen ${MIN_PREFIX} und ${MAX_PREFIX} sein." >&2
        return 2
    fi
    if [ "$end_vlan" -lt "$start_vlan" ]; then
        echo "FEHLER: END-VLAN (${end_vlan}) ist kleiner als START-VLAN (${start_vlan})." >&2
        return 1
    fi
    if [ "$start_vlan" -lt "$anchor_vlan" ]; then
        echo "FEHLER: START-VLAN (${start_vlan}) liegt unter der Anchor-VLAN (${anchor_vlan})." >&2
        return 1
    fi

    local s_idx=$(( start_vlan - anchor_vlan ))
    local e_idx=$(( end_vlan - anchor_vlan ))
    local block_size=$(( 2 ** (32 - prefix) ))

    local s_third s_fourth e_third e_fourth

    if [ "$prefix" -ge 24 ]; then
        local per_24=$(( 256 / block_size ))
        s_third=$(( s_idx / per_24 ))
        s_fourth=$(( (s_idx % per_24) * block_size ))
        e_third=$(( e_idx / per_24 ))
        e_fourth=$(( (e_idx % per_24) * block_size ))
    else
        local step3=$(( block_size / 256 ))
        s_third=$(( s_idx * step3 ))
        s_fourth=0
        e_third=$(( e_idx * step3 ))
        e_fourth=0
    fi

    if [ "$e_third" -gt 255 ]; then
        echo "WARNUNG: Range ueberschreitet das 3. Oktett (>255). Basis-Netz oder Praefix zu klein." >&2
    fi

    local start_net="${base_net}.${s_third}.${s_fourth}/${prefix}"
    local end_net="${base_net}.${e_third}.${e_fourth}/${prefix}"
    local count_vlans=$(( end_vlan - start_vlan + 1 ))
    local count24=$(( e_third - s_third + 1 ))

    echo
    echo "  Basis-Netz:   ${base_net}.x.x   (Anchor-VLAN ${anchor_vlan} = ${base_net}.0.0/${prefix})"
    echo "  VLAN-Range:   ${start_vlan} - ${end_vlan}"
    echo "  Praefix:      /${prefix}   (${block_size} Adressen je Subnetz)"
    echo
    echo "  Benoetigte Subnetze: ${count_vlans}   (verteilt auf ${count24} x /24)"
    echo
    echo "  Start-Netz:   ${start_net}   (VLAN ${start_vlan})"
    echo "  End-Netz:     ${end_net}   (VLAN ${end_vlan})"
    echo
    echo "  --------------------------------------------"
    echo "  Gesamt-Range: ${start_net} - ${end_net}"
    echo

    return 0
}

main "$@"
Ad