Last active
August 6, 2026 11:18
-
-
Save CodeSenpai4ever/cfc7063cce8ef633498b99cdde9c27a0 to your computer and use it in GitHub Desktop.
Testing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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