-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgrepcidr.py
More file actions
executable file
·170 lines (139 loc) · 4.22 KB
/
Copy pathgrepcidr.py
File metadata and controls
executable file
·170 lines (139 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python3
import sys
import argparse
import ipaddress
def parse_args():
p = argparse.ArgumentParser(
description="Filter lines by whether an IP field matches one or more CIDRs."
)
p.add_argument(
"-e", "--expr",
action="append",
default=[],
help="CIDR expression to match; can be given multiple times"
)
p.add_argument(
"-C", "--cidr-file",
help="File containing CIDRs, one per line"
)
p.add_argument(
"-v", "--invert-match",
action="store_true",
help="Select non-matching lines"
)
p.add_argument(
"-c", "--count",
action="store_true",
help="Print only the number of selected lines"
)
p.add_argument(
"-o", "--only-matching",
action="store_true",
help="Print only the selected IP field"
)
p.add_argument(
"-f", "--field",
type=int,
default=1,
help="1-based field number containing the IP (default: 1)"
)
# Trick:
# We allow remaining positional args to contain:
# - one or more CIDRs
# - optionally the input filename as the last arg
p.add_argument(
"args",
nargs="*",
help="CIDRs followed optionally by input file"
)
return p.parse_args()
def load_cidrs(args):
cidr_strings = []
# from -e / --expr
cidr_strings.extend(args.expr)
# from file
if args.cidr_file:
try:
with open(args.cidr_file, "r", encoding="utf-8", errors="ignore") as fh:
for line in fh:
line = line.strip()
if not line or line.startswith("#"):
continue
cidr_strings.append(line)
except OSError as e:
print(f"Cannot open CIDR file '{args.cidr_file}': {e}", file=sys.stderr)
sys.exit(3)
# from positional args:
# interpret all but last as CIDRs if last is not a CIDR
positional = list(args.args)
infile = None
if positional:
maybe_last = positional[-1]
try:
ipaddress.ip_network(maybe_last, strict=False)
# last arg is also a CIDR -> all positional args are CIDRs, input is stdin
cidr_strings.extend(positional)
except ValueError:
# last arg is probably a filename
infile = maybe_last
cidr_strings.extend(positional[:-1])
if not cidr_strings:
print("No CIDRs provided.", file=sys.stderr)
sys.exit(2)
networks = []
for cidr in cidr_strings:
try:
networks.append(ipaddress.ip_network(cidr, strict=False))
except ValueError as e:
print(f"Invalid CIDR: {cidr} ({e})", file=sys.stderr)
sys.exit(2)
return networks, infile
def ip_matches_any(ip_s, networks):
try:
ip = ipaddress.ip_address(ip_s)
except ValueError:
return False
for net in networks:
if ip.version == net.version and ip in net:
return True
return False
def main():
args = parse_args()
if args.field < 1:
print("Field number must be >= 1", file=sys.stderr)
sys.exit(2)
networks, infile = load_cidrs(args)
fh = sys.stdin
if infile:
try:
fh = open(infile, "r", encoding="utf-8", errors="ignore")
except OSError as e:
print(f"Cannot open file '{infile}': {e}", file=sys.stderr)
sys.exit(3)
selected = 0
field_index = args.field - 1
try:
for line in fh:
if not line.strip():
continue
parts = line.split()
if field_index >= len(parts):
continue
ip_s = parts[field_index]
matched = ip_matches_any(ip_s, networks)
if args.invert_match:
matched = not matched
if matched:
selected += 1
if not args.count:
if args.only_matching:
print(ip_s)
else:
print(line, end="")
finally:
if fh is not sys.stdin:
fh.close()
if args.count:
print(selected)
if __name__ == "__main__":
main()