Skip to content

Instantly share code, notes, and snippets.

@CodeSenpai4ever
Last active August 6, 2026 11:18
Show Gist options
  • Select an option

  • Save CodeSenpai4ever/cfc7063cce8ef633498b99cdde9c27a0 to your computer and use it in GitHub Desktop.

Select an option

Save CodeSenpai4ever/cfc7063cce8ef633498b99cdde9c27a0 to your computer and use it in GitHub Desktop.
Testing
#!/usr/bin/env bash
#
# csv_to_pki_json.sh
#
# Pure POSIX shell + awk implementation (NO python, NO jq, NO extra
# installs) - works in plain Git Bash / MINGW / any minimal shell that
# ships awk, sed, grep (i.e. basically everywhere).
#
# IMPORTANT: True .xlsx files are zip archives containing XML - a shell
# without unzip/python literally cannot decode that binary format. So
# this version takes a CSV export of your Excel sheet instead:
# Excel -> File -> Save As -> CSV (Comma delimited) (*.csv)
# Any spreadsheet (Excel, LibreOffice, Numbers, Google Sheets) can export
# CSV without installing anything extra.
#
# Usage:
# ./csv_to_pki_json.sh -i devices.csv -c "Common Name" \
# -s YOUR_STRUCT_KEY_ID -e your.group.mailbox@volkswagen.de \
# [-t vwpki/802x-client/p12] [-v 1y] [-d false] \
# [-o out_dir] [-n prefix] [--chunk-size 250] [--delim ,]
#
# -c accepts either the header name (case-insensitive, matched against
# row 1) or a 1-based column index (e.g. -c 3).
#
# Output:
# out_dir/prefix_001.json
# out_dir/prefix_002.json
# ...
set -eu
CERT_TYPE="vwpki/802x-client/p12"
VALIDITY="1y"
DRY_RUN="false"
CHUNK_SIZE=250
OUT_DIR="./output"
PREFIX="pki_request"
DELIM=","
INPUT_FILE=""
COLUMN=""
STRUCT_KEY_ID=""
CONTACT_EMAIL=""
usage() {
sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'
exit 1
}
while [ $# -gt 0 ]; do
case "$1" in
-i|--input) INPUT_FILE="$2"; shift 2 ;;
-c|--column) COLUMN="$2"; shift 2 ;;
-s|--struct-key-id) STRUCT_KEY_ID="$2"; shift 2 ;;
-e|--email) CONTACT_EMAIL="$2"; shift 2 ;;
-t|--cert-type) CERT_TYPE="$2"; shift 2 ;;
-v|--validity) VALIDITY="$2"; shift 2 ;;
-d|--dry-run) DRY_RUN="$2"; shift 2 ;;
-o|--out-dir) OUT_DIR="$2"; shift 2 ;;
-n|--prefix) PREFIX="$2"; shift 2 ;;
--chunk-size) CHUNK_SIZE="$2"; shift 2 ;;
--delim) DELIM="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo "Unknown argument: $1" >&2; usage ;;
esac
done
[ -z "$INPUT_FILE" ] && { echo "Error: -i/--input (CSV file) is required" >&2; exit 1; }
[ -z "$COLUMN" ] && { echo "Error: -c/--column (header name or index) is required" >&2; exit 1; }
[ -z "$STRUCT_KEY_ID" ] && { echo "Error: -s/--struct-key-id is required" >&2; exit 1; }
[ -z "$CONTACT_EMAIL" ] && { echo "Error: -e/--email is required" >&2; exit 1; }
[ ! -f "$INPUT_FILE" ] && { echo "Error: input file '$INPUT_FILE' not found" >&2; exit 1; }
case "$CHUNK_SIZE" in
''|*[!0-9]*) echo "Error: --chunk-size must be a positive integer" >&2; exit 1 ;;
esac
if [ "$CHUNK_SIZE" -lt 1 ] || [ "$CHUNK_SIZE" -gt 250 ]; then
echo "Error: --chunk-size must be between 1 and 250" >&2
exit 1
fi
case "$DRY_RUN" in
true|false) : ;;
*) echo "Error: -d/--dry-run must be true or false" >&2; exit 1 ;;
esac
mkdir -p "$OUT_DIR"
awk -v col="$COLUMN" \
-v delim="$DELIM" \
-v struct_key_id="$STRUCT_KEY_ID" \
-v contact_email="$CONTACT_EMAIL" \
-v cert_type="$CERT_TYPE" \
-v validity="$VALIDITY" \
-v dry_run="$DRY_RUN" \
-v chunk_size="$CHUNK_SIZE" \
-v out_dir="$OUT_DIR" \
-v prefix="$PREFIX" \
'
function jesc(s) {
gsub(/\\/, "\\\\", s)
gsub(/"/, "\\\"", s)
gsub(/\t/, "\\t", s)
gsub(/\r/, "", s)
return s
}
function write_header(fh) {
print "{" > fh
print " \"business_context\": {" > fh
print " \"struct_key_id\": \"" jesc(struct_key_id) "\"," > fh
print " \"contact_email\": \"" jesc(contact_email) "\"" > fh
print " }," > fh
print " \"certificate_type\": \"" jesc(cert_type) "\"," > fh
print " \"validity\": \"" jesc(validity) "\"," > fh
print " \"dry_run\": " dry_run "," > fh
print " \"subjects\": [" > fh
}
function write_footer(fh) {
print " ]" > fh
print "}" > fh
close(fh)
}
BEGIN {
FS = delim
col_idx = 0
if (col ~ /^[0-9]+$/) col_idx = col + 0
count = 0
file_index = 0
in_batch = 0
}
NR == 1 {
if (col_idx == 0) {
for (i = 1; i <= NF; i++) {
h = $i
gsub(/^[ \t\r"]+|[ \t\r"]+$/, "", h)
if (tolower(h) == tolower(col)) { col_idx = i; break }
}
if (col_idx == 0) {
print "Error: column \x27" col "\x27 not found in header" > "/dev/stderr"
exit 1
}
}
next
}
{
if (col_idx > NF) next
val = $col_idx
gsub(/^[ \t\r"]+|[ \t\r"]+$/, "", val)
if (val == "") next
if (count % chunk_size == 0) {
if (in_batch) {
print " }" > cur_file
write_footer(cur_file)
print "Wrote " cur_file " (" batch_n " subjects)" > "/dev/stderr"
}
file_index++
cur_file = sprintf("%s/%s_%03d.json", out_dir, prefix, file_index)
write_header(cur_file)
in_batch = 1
batch_n = 0
}
if (batch_n > 0) print " }," > cur_file
print " {" > cur_file
print " \"common_name\": \"" jesc(val) "\"" > cur_file
batch_n++
count++
}
END {
if (in_batch) {
print " }" > cur_file
write_footer(cur_file)
print "Wrote " cur_file " (" batch_n " subjects)" > "/dev/stderr"
}
if (count == 0) {
print "Error: no values found in column \x27" col "\x27" > "/dev/stderr"
exit 1
}
print "Done. Generated " file_index " file(s) in \x27" out_dir "\x27." > "/dev/stderr"
}
' "$INPUT_FILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment