ipsort(1) "v__VERSION__" "ipsort DATE"
ipsort - sort IP addresses and CIDRs numerically
ipsort [OPTIONS] [ADDRESS...]
ipsort sorts IP addresses and CIDR blocks by their actual numeric value rather than lexicographically. It is designed to be flexible with input formats: it can sort plain lists of addresses, YAML or JSON list items, inline config values, and other mixed content while preserving surrounding decoration.
Input is read from stdin when no addresses are given, or from positional arguments otherwise. Use - as the sole argument to read explicitly from stdin.
IPv4 and IPv6 addresses are both supported. Mixed input is sorted with IPv4 first by default.
Bare IP addresses without a prefix length are accepted and treated as /32 (IPv4) or /128 (IPv6) for sorting purposes.
Lines containing no recognizable IP address act as block separators: each group of IP lines between separators is sorted independently, with separators preserved in place.
Because non-IP content is always preserved verbatim, ipsort can be given an entire YAML, TOML, or config file as input; only the IP addresses will be reordered, leaving comments, keys, blank lines, and all other content untouched.
-r, --reverse Reverse the sort order.
--ipv6-first Sort IPv6 addresses before IPv4 in mixed input. Default is IPv4 first.
-u, --unique Remove duplicate addresses. Comparison is by normalized CIDR (canonical network address plus prefix length). The first occurrence is kept.
For single-IP lines, duplicate lines are silently dropped. For multi-IP
lines in default mode, encountering a duplicate is an error: the ambiguity
of which IP to remove requires the user to clean up the input. When
*--ips-only* or *--ips-only-with-structure* is set, each IP on a multi-IP
line is checked independently and duplicates are removed without error.-i, --inline Collect all IP addresses from the entire input into one pool, sort globally, then redistribute sorted IPs back into their original positions. Decoration and surrounding content are preserved. Block separator logic does not apply (the entire input is treated as one sort scope).
Useful when IPs are spread across multiple lines within a single logical
unit, such as a multi-line YAML value.-n, --normalize Emit canonical network strings instead of the original token. Host bits are cleared (10.0.0.5/24 becomes 10.0.0.0/24) and bare IPs gain explicit prefix lengths (192.168.1.1 becomes 192.168.1.1/32). IPv6 hex digits are also lowercased (2001:DB8::FfAb becomes 2001:db8::ffab/128). Surrounding decoration is unaffected.
--ips-only Strip all non-IP content and emit one bare IP address per line. Each IP address in the input becomes one output line. Non-IP lines (comments, blank lines, etc.) are discarded entirely.
--ips-only-with-structure Like --ips-only, but preserves non-IP lines as block separators. Each IP address becomes one output line, decoration is stripped, and block separator logic applies normally.
*--ips-only* and *--ips-only-with-structure* are mutually exclusive.-c, --check Check whether the input is already in the state the current flags would produce. Exits 0 if no changes would be made, 1 otherwise. No output is written to stdout. The first out-of-order or unsatisfied line is reported to stderr on failure.
Combines naturally with other flags: *--check --unique* exits 1 if
duplicates exist; *--check --aggregate* exits 1 if any CIDRs can be
merged; *--check --normalize* exits 1 if any token is not in canonical
form.-a, --aggregate Merge adjacent CIDR blocks into their minimal supernet representation. For each group of CIDRs that can be combined, the first line in the group becomes the output line with the aggregated CIDR substituted in; remaining lines in the group are dropped. Surrounding decoration on the winning line is preserved.
Aggregation is applied per block. Use *--inline* for global aggregation
across the entire input.
For multi-IP lines involved in a cross-line aggregation, *--aggregate*
returns an error. Clean up the input and try again.
Subsumes *--unique*: if *--aggregate* is set, *--unique* has no additional
effect.ipsort tokenizes each line by splitting on any character that cannot appear in a valid CIDR address. This means it handles all common delimiter styles without configuration:
- 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
Tokens that look like CIDRs but fail to parse (e.g. 10.0.0.1/33) are treated as non-IP content and passed through with a warning to stderr.
CIDRs with host bits set (e.g. 10.0.0.5/24) are accepted. The host bits are cleared for sorting purposes and a warning is emitted to stderr. The original string is preserved in output unless --normalize is set.
By default, output mirrors the input format exactly. Decoration, delimiters, and surrounding content are preserved. Only the IP addresses are reordered.
For example, "192.168.0.0/16", "10.0.0.0/8" becomes "10.0.0.0/8", "192.168.0.0/16" (the quotes and comma are untouched).
For structured formats (JSON, YAML), the recommended pattern is to extract fields with jq(1) or yq(1), pipe through ipsort, and reconstruct downstream.
0 Success.
1 --check ran successfully and determined the input is not in the expected state.
2 Operational error: no input provided, no IP addresses found, --unique encountered an ambiguous duplicate on a multi-IP line, --aggregate encountered a multi-IP line involved in a cross-line aggregation, or invalid arguments (reported by the argument parser).
Sort a list of addresses from stdin:
$ 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 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(1):
$ jq '.networks[]' config.json | ipsortSort 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:
$ cat <<EOF | ipsort --unique
10.0.0.0/8
192.168.0.0/16
10.0.0.0/8
EOF
10.0.0.0/8
192.168.0.0/16Normalize output to canonical form:
$ cat <<EOF | ipsort --normalize
10.0.0.5/24
192.168.1.1
EOF
10.0.0.0/24
192.168.1.1/32Extract bare IPs from decorated input:
$ 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/24Sort in reverse order:
$ 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/8Check whether a file is already sorted:
$ ipsort --check < addresses.txt && echo "sorted" || echo "not sorted"Check whether a file has no duplicates:
$ ipsort --check --unique < addresses.txtAggregate subnets into their minimal supernet:
$ cat <<EOF | ipsort --aggregate
10.0.0.0/25
10.0.0.128/25
10.0.1.0/24
192.168.0.0/24
EOF
10.0.0.0/23
192.168.0.0/24Aggregate a YAML list, preserving decoration on the winning line:
$ 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/24Read from a file using shell redirection:
$ ipsort < addresses.txtSort a file in place using sponge(1) from moreutils:
$ ipsort < addresses.txt | sponge addresses.txtWithout sponge, redirecting output to the same file being read will truncate it before ipsort reads it. sponge buffers the full output before writing.
Sort an entire YAML file, leaving all structure intact:
$ 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 preserved exactly. IPs are sorted 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, sorting them globally while leaving everything else in place.
When an IP is removed from a multi-IP line by --unique in --ips-only or --ips-only-with-structure mode, adjacent delimiters (spaces, commas, etc.) that were between IPs may remain in the output. Users who need clean output in this case should use --ips-only to strip all decoration.
sort(1), jq(1), uniq(1), sponge(1)