ipsort sorts IP addresses and CIDR blocks by their actual numeric value rather
than lexicographically. It handles plain lists, YAML and JSON list items, inline
config values, and other mixed content, preserving surrounding decoration while
reordering only the addresses.
- Sorts IPv4 and IPv6 addresses numerically
- Preserves decoration: YAML list markers, JSON brackets, inline keys, punctuation
- Block separator model: non-IP lines (comments, blank lines) divide input into independently sorted groups
--inlinemode for sorting IPs spread across multiple lines within a single logical unit--aggregateto merge adjacent CIDRs into their minimal supernet representation--uniquededuplication by normalized CIDR--normalizefor canonical network string output--ips-onlyand--ips-only-with-structurefor extracting bare addresses- Composable in shell pipelines
Download the binary for your platform from the
releases page, make it
executable, and place it somewhere on your PATH:
chmod +x ipsort
mv ipsort ~/.local/bin/Shell completions and the manpage are included in the release archive.
Requires Rust and scdoc (for the manpage).
git clone https://github.com/mfinelli/ipsort.git
cd ipsort
makeThe compiled binary, shell completions, and manpage are written to
target/release/. To build and install to your system:
sudo make install # installs to /usr/local by default
sudo make uninstall # removes installed filesipsort [OPTIONS] [ADDRESS...]
Read from stdin when no addresses are given. Use - to read explicitly from
stdin.
| Flag | Short | Description |
|---|---|---|
--reverse |
-r |
Reverse the sort order |
--ipv6-first |
Sort IPv6 before IPv4 in mixed input | |
--unique |
-u |
Deduplicate by normalized CIDR, keeping first occurrence |
--aggregate |
-a |
Merge adjacent CIDRs into their minimal supernet representation |
--check |
-c |
Exit 0 if input is already sorted/aggregated/unique, 1 otherwise |
--inline |
-i |
Sort all IPs globally across the entire input |
--normalize |
-n |
Emit canonical network strings (clears host bits, adds /32//128) |
--ips-only |
Strip decoration, emit one bare IP per line, discard non-IP lines | |
--ips-only-with-structure |
Strip decoration, emit one bare IP per line, preserve non-IP lines |
--ips-only and --ips-only-with-structure are mutually exclusive.
| Code | Meaning |
|---|---|
0 |
Success |
1 |
--check ran successfully; input is not in the expected state |
2 |
Operational error (no input, no IPs found, conflicting flag usage) |
Sort a list from stdin:
$ printf '192.168.1.0/24\n10.0.0.0/8\n172.16.0.0/12\n' | ipsort
10.0.0.0/8
172.16.0.0/12
192.168.1.0/24Sort positional arguments:
$ ipsort 192.168.1.0/24 10.0.0.0/8 172.16.0.0/12
10.0.0.0/8
172.16.0.0/12
192.168.1.0/24Sort a YAML list, preserving list decoration:
$ cat <<EOF | ipsort
- 192.168.1.0/24
- 10.0.0.0/8
- 172.16.0.0/12
EOF
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.1.0/24Sort with block separators preserved:
$ cat <<EOF | ipsort
# internal ranges
192.168.1.0/24
10.0.0.0/8
# dmz
172.16.2.0/24
172.16.1.0/24
EOF
# internal ranges
10.0.0.0/8
192.168.1.0/24
# dmz
172.16.1.0/24
172.16.2.0/24Sort IPs from a JSON field using jq:
$ jq '.networks[]' config.json | ipsortRead from a file using shell redirection:
$ ipsort < addresses.txtSort a file in place using sponge from moreutils:
$ ipsort < addresses.txt | sponge addresses.txtWithout sponge, redirecting to the same file you are reading from will
truncate it before ipsort reads it. sponge buffers the output and writes it
only after the input is fully read.
Sort an entire YAML file, leaving all structure intact:
$ cat firewall.yml
# firewall rules
allowed_sources:
- 192.168.1.0/24
- 10.0.0.0/8
- 172.16.5.0/24
denied_sources:
- 10.99.0.0/16
- 192.168.99.0/24
$ ipsort < firewall.yml
# firewall rules
allowed_sources:
- 10.0.0.0/8
- 172.16.5.0/24
- 192.168.1.0/24
denied_sources:
- 10.99.0.0/16
- 192.168.99.0/24Comments, blank lines, and YAML keys are all preserved exactly. Only the IP addresses are reordered, independently within each block separated by blank lines.
Sort IPs in a Kubernetes ingress annotation with --inline:
$ ipsort --inline < ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-service
annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: >-
10.0.0.0/8,
10.99.0.0/16,
172.16.5.0/24,
192.168.1.0/24
spec:
rules:
- host: my-service.example.comThe entire YAML structure is preserved. --inline treats all IPs across the
file as one pool, which is what you want when a list spans multiple lines within
a single annotation value.
Sort a multi-line YAML value with --inline:
$ cat <<EOF | ipsort --inline
allowed_ips: 192.168.1.0/24 10.0.0.0/8
172.16.2.0/24 172.16.1.0/24
EOF
allowed_ips: 10.0.0.0/8 172.16.1.0/24
172.16.2.0/24 192.168.1.0/24Deduplicate a list:
$ printf '10.0.0.0/8\n192.168.0.0/16\n10.0.0.0/8\n' | ipsort --unique
10.0.0.0/8
192.168.0.0/16Aggregate subnets into their minimal supernet:
$ cat <<EOF | ipsort --aggregate
- 10.0.0.0/25
- 10.0.0.128/25
- 192.168.0.0/24
EOF
- 10.0.0.0/24
- 192.168.0.0/24Normalize to canonical form:
$ printf '10.0.0.5/24\n192.168.1.1\n' | ipsort --normalize
10.0.0.0/24
192.168.1.1/32Extract bare IPs, discarding all decoration and structure:
$ cat <<EOF | ipsort --ips-only
# group one
- 192.168.1.0/24
- 10.0.0.0/8
# group two
- 172.16.0.0/12
EOF
10.0.0.0/8
172.16.0.0/12
192.168.1.0/24Check whether a file is already sorted (useful in CI):
$ ipsort --check < addresses.txt && echo "sorted" || echo "not sorted"With other flags, checks whether the input satisfies those conditions:
$ ipsort --check --unique < addresses.txt # exits 1 if duplicates exist
$ ipsort --check --aggregate < addresses.txt # exits 1 if CIDRs can be mergedSort in reverse:
$ ipsort --reverse 10.0.0.0/8 172.16.0.0/12 192.168.1.0/24
192.168.1.0/24
172.16.0.0/12
10.0.0.0/8ipsort tokenizes each line by splitting on any character that cannot appear in
a valid CIDR address, so it handles all common delimiter styles without
configuration:
| Input style | Example |
|---|---|
| Space-separated | 10.0.0.0/8 192.168.0.0/16 |
| Comma-separated | 10.0.0.0/8,192.168.0.0/16 |
| Quoted JSON-style | "10.0.0.0/8", "192.168.0.0/16" |
| YAML list items | - 10.0.0.0/8 |
| Inline config values | network: 10.0.0.0/8 |
Bare IP addresses (without a prefix length) are accepted and treated as /32 or
/128 for sorting. CIDRs with host bits set (e.g. 10.0.0.5/24) are accepted
with a warning to stderr (host bits are cleared for sorting), and the original
string is preserved in output unless --normalize is set.
Contributions are welcome. The codebase is structured as a library
(src/lib.rs) with a thin CLI binary (src/main.rs). The library modules are:
parse: token-level CIDR parsingclassify: line spanification and classificationsort: sort comparator and optionsblocks: block-level and inline sorting, deduplicationoutput: output rendering
For a full account of the design decisions, requirements, and tradeoffs behind
the implementation, see DESIGN.md.
Run the test suite with:
cargo testLicensed under the GNU GPL version 3.0 or later.
ipsort: versitile ip address sorting tool
Copyright 2026 Mario Finelli
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.