-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparse_usb_ids.sh
More file actions
141 lines (123 loc) · 3.56 KB
/
parse_usb_ids.sh
File metadata and controls
141 lines (123 loc) · 3.56 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
#!/usr/bin/env bash
# parse_usb_ids.sh
# Parses http://www.linux-usb.org/usb.ids (or a local file) into CSV files:
# vendors.csv, products.csv, subsystems.csv, devices_flat.csv
#
# Usage:
# ./parse_usb_ids.sh
# ./parse_usb_ids.sh --file=/path/to/usb.ids
# ./parse_usb_ids.sh --url=https://example.com/usb.ids
set -euo pipefail
DEFAULT_URL="http://www.linux-usb.org/usb.ids"
usage() {
cat <<EOF
Usage:
$0 [--file=PATH | --url=URL]
If no option is given, downloads from ${DEFAULT_URL}.
Outputs:
vendors.csv, products.csv, subsystems.csv, devices_flat.csv
EOF
exit 1
}
INPUT=""
MODE=""
# --- Parse flags ---
for arg in "$@"; do
case $arg in
--file=*|-f=*)
INPUT="${arg#*=}"
MODE="file"
;;
--url=*|-u=*)
INPUT="${arg#*=}"
MODE="url"
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $arg" >&2
usage
;;
esac
done
if [[ -z "$INPUT" ]]; then
MODE="url"
INPUT="$DEFAULT_URL"
fi
TMPFILE="$(mktemp --suffix=.usbids)"
cleanup() { rm -f "$TMPFILE"; }
trap cleanup EXIT
# --- Fetch or copy ---
if [[ "$MODE" == "url" ]]; then
echo "Downloading from: $INPUT"
if ! command -v curl >/dev/null 2>&1; then
echo "Error: curl is required but not installed" >&2
exit 1
fi
curl -fsSL "$INPUT" -o "$TMPFILE"
else
echo "Using local file: $INPUT"
if [[ ! -f "$INPUT" ]]; then
echo "File not found: $INPUT" >&2
exit 1
fi
cp "$INPUT" "$TMPFILE"
fi
# --- Output files ---
OUT_VENDORS="vendors.csv"
OUT_PRODUCTS="products.csv"
OUT_SUBSYS="subsystems.csv"
OUT_FLAT="devices_flat.csv"
# Clear and add headers
echo 'vendor_id,vendor_name' > "$OUT_VENDORS"
echo 'vendor_id,product_id,product_name' > "$OUT_PRODUCTS"
echo 'vendor_id,product_id,subsys_vendor_id,subsys_product_id,subsys_name' > "$OUT_SUBSYS"
echo 'vendor_id,vendor_name,product_id,product_name,subsys_vendor_id,subsys_product_id,subsys_name' > "$OUT_FLAT"
# --- Parse file ---
awk -v OUT_VENDORS="$OUT_VENDORS" \
-v OUT_PRODUCTS="$OUT_PRODUCTS" \
-v OUT_SUBSYS="$OUT_SUBSYS" \
-v OUT_FLAT="$OUT_FLAT" \
-v OFS=',' '
function q(s){
gsub(/^[ \t\r\n]+|[ \t\r\n]+$/, "", s)
gsub(/"/, "\"\"", s)
return "\"" s "\""
}
/^[[:space:]]*#/ || /^[[:space:]]*$/ { next } # skip comments/blank lines
/^[0-9A-Fa-f]{4}[[:space:]]+/ {
vendor_id = tolower(substr($0, 1, 4))
vendor_name = substr($0, 6)
print vendor_id, q(vendor_name) >> OUT_VENDORS
cur_vendor = vendor_id
cur_vendor_name = vendor_name
next
}
/^[[:space:]]+[0-9A-Fa-f]{4}[[:space:]]+/ && !/^[[:space:]]{2,}/ {
sub(/^[[:space:]]+/, "", $0)
prod_id = tolower(substr($0, 1, 4))
prod_name = substr($0, 6)
if (cur_vendor == "") next
print cur_vendor, prod_id, q(prod_name) >> OUT_PRODUCTS
print cur_vendor, q(cur_vendor_name), prod_id, q(prod_name), "", "", "" >> OUT_FLAT
cur_product = prod_id
cur_product_name = prod_name
next
}
/^[[:space:]]{2,}[0-9A-Fa-f]{4}[[:space:]]+[0-9A-Fa-f]{4}[[:space:]]+/ {
sub(/^[[:space:]]+/, "", $0)
split($0, parts, /[[:space:]]+/)
subsys_vid = tolower(parts[1])
subsys_pid = tolower(parts[2])
subsys_name = substr($0, index($0, parts[3]))
if (cur_vendor == "" || cur_product == "") next
print cur_vendor, cur_product, subsys_vid, subsys_pid, q(subsys_name) >> OUT_SUBSYS
print cur_vendor, q(cur_vendor_name), cur_product, q(cur_product_name), \
subsys_vid, subsys_pid, q(subsys_name) >> OUT_FLAT
next
}
' "$TMPFILE"
echo ""
echo "✅ Done. Generated CSV files:"
ls -lh "$OUT_VENDORS" "$OUT_PRODUCTS" "$OUT_SUBSYS" "$OUT_FLAT"