cloverleaf-larry/lib/table-to-csv.sh
Bryan Johnson 3eb88f86c8 v0.4.1: each / each-site / len2nl / csv-to-table / table-to-csv
Five small Unix-style loop & format helpers, fully offline:

lib/each.sh
  - replaces v1 `each`
  - run a CMD per item: args, stdin lines, or {}-placeholder substitution
  - example: tbn adt | awk '{print $2}' | each.sh 'route_test {}'

lib/each-site.sh
  - replaces v1 each_site / each_site_hdr / each_site_tcl patterns
  - iterates every site under $HCIROOT with HCISITE/HCISITEDIR auto-exported
  - --filter REGEX limits which sites; --hdr prints a header before each

lib/len2nl.sh
  - replaces v1 `len2nl`
  - strict superset: handles length-prefixed (digits before MSH),
    MLLP (\x0b...\x1c\x0d), and segment CRs (→ LF)
  - works as stdin filter or with file arg

lib/csv-to-table.sh
  - 2-column CSV → Cloverleaf .tbl format
  - emits proper prologue (who, date, bidir, type, version)
  - --has-header --default VALUE --bidir 0|1 --in-delim CHAR --user NAME --out PATH

lib/table-to-csv.sh
  - reverse: .tbl → CSV
  - --with-header --delim CHAR --include-meta
  - confirmed clean round-trip: CSV → table → CSV byte-identical for the data rows

All 5 are pipeable, have --help, zero external deps beyond bash+awk+sed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:05:19 -07:00

71 lines
2.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# table-to-csv.sh — convert a Cloverleaf .tbl file to CSV.
#
# Reverses csv-to-table.sh. Skips the prologue, dflt= line, encoded= meta,
# comment lines, and #-separator lines; emits "input,output" rows.
#
# Usage:
# table-to-csv.sh [FILE] # file or stdin
# table-to-csv.sh --with-header [FILE] # emit "input,output" header
# table-to-csv.sh --delim ';' [FILE] # output delimiter (default ',')
# table-to-csv.sh --include-meta [FILE] # also emit prologue/default rows
set -o pipefail
usage() { sed -n '2,12p' "$0"; exit 0; }
INPUT=""
WITH_HEADER=0
DELIM=','
INCLUDE_META=0
while [ $# -gt 0 ]; do
case "$1" in
--with-header) WITH_HEADER=1 ;;
--delim) shift; DELIM="$1" ;;
--include-meta) INCLUDE_META=1 ;;
-h|--help) usage ;;
-*) echo "table-to-csv: unknown flag: $1" >&2; exit 2 ;;
*) INPUT="$1" ;;
esac
shift
done
[ -z "$INPUT" ] || [ -f "$INPUT" ] || { echo "table-to-csv: no such file: $INPUT" >&2; exit 2; }
[ "$WITH_HEADER" = "1" ] && printf 'input%soutput\n' "$DELIM"
awk -v D="$DELIM" -v META="$INCLUDE_META" '
function csv_escape(s, needs_q) {
needs_q = (index(s, D) > 0 || index(s, "\"") > 0 || index(s, "\n") > 0)
gsub(/"/, "\"\"", s)
if (needs_q) return "\"" s "\""
return s
}
/^[[:space:]]*$/ { next }
/^[[:space:]]*#/ { next }
/^prologue$/ { in_prologue=1; next }
/^end_prologue$/ { in_prologue=0; next }
in_prologue { next }
/^dflt=/ {
if (META == "1") {
val = $0; sub(/^dflt=/, "", val)
print "__dflt__" D csv_escape(val)
}
next
}
/^encoded=/ { next }
{
line = $0
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
if (line == "") next
if (waiting_for_output) {
print csv_escape(pending_input) D csv_escape(line)
pending_input = ""
waiting_for_output = 0
} else {
pending_input = line
waiting_for_output = 1
}
}
' "${INPUT:-/dev/stdin}"