Basis aus 2nd-brain für LPIC-Prüfungsvorbereitung übernommen
Regeln, Standards, Kontextstruktur und Werkzeuge übernommen; 2nd-brain- Inhalte (Stacks, Ansible-Inventar, Architektur, Knowledge) entfernt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
7f6b8ebe07
70 files changed
+2816
No files matched your search
@@ -0,0 +1,21 @@
|
||||
# .env – Einstellungen für die Scripts in diesem Verzeichnis
|
||||
# Wird von b_init gesourct (Bash-Syntax). Echte .env nie ins Git – nur .env.example.
|
||||
# Vorrang: Default < .env < Umgebung (export / VAR=… ./script.sh) < Kommandozeile
|
||||
# Relative Pfade gelten relativ zum Script-Verzeichnis, nicht zum aufrufenden $PWD.
|
||||
|
||||
## Logging ---------------------------------------------------------------------
|
||||
B_LOG_LEVEL=info # debug | info | warn | error (-d setzt debug)
|
||||
B_LOG_CONSOLE=1 # 0 = still (-q)
|
||||
B_LOG_FILE= # z. B. "log/${SCRIPT_NAME%.sh}.log"; leer = keine Datei (-l DATEI)
|
||||
B_LOG_FILE_LEVEL= # leer = wie B_LOG_LEVEL; z. B. debug für ausführliche Logdatei
|
||||
B_LOG_COLOR=auto # auto | 1 | 0
|
||||
|
||||
## Optionen vorbelegen (überschreibt den Default aus der Definition) ------------
|
||||
#OPTION_DRY_RUN=1
|
||||
|
||||
## Zusätzliche Optionen für alle Scripts dieses Verzeichnisses – immer mit += ---
|
||||
# Format: "kurz|lang|typ[:META]|default|Beschreibung" typ = flag | value
|
||||
#B_OPTS+=( "k|keep|flag|0|Zwischendateien behalten" )
|
||||
|
||||
## Projektwerte – mit ${VAR:-default}, damit die Umgebung gewinnt ---------------
|
||||
#DIR_BACKUP="${DIR_BACKUP:-/srv/backup}"
|
||||
@@ -0,0 +1,11 @@
|
||||
# bin/ – Werkzeuge der AI-Basis
|
||||
|
||||
> Selbst nach [`ai/common/bash.md`](../ai/common/bash.md) gebaut – zugleich Referenzbeispiele für den Standard.
|
||||
> Alle: `-h` Hilfe · `-n` Trockenlauf · `-d` Debug · `-q` still · `-l DATEI` Logdatei.
|
||||
|
||||
| Script | Zweck |
|
||||
|---|---|
|
||||
| `kb_index.sh [--check]` | Knowledge-`INDEX.md` erzeugen, Pflichtfelder/doppelte ids prüfen |
|
||||
| `bashdoc.sh <script.sh>… [-o datei] [-p]` | Markdown-Doku aus `##`-Kommentaren |
|
||||
| `_functions.sh` | gemeinsame Basis – **Kopie** von `ai/common/templates/bash/_functions.sh`, nicht lokal ändern |
|
||||
| `.env.example` | Vorlage für lokale Einstellungen (`cp .env.example .env`) |
|
||||
@@ -0,0 +1,415 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------
|
||||
## @file _functions.sh
|
||||
## @brief Gemeinsame Bash-Basis: .env laden, Optionen, Logging, Usage, Trap
|
||||
## @author <AUTOR – siehe ai/context/responsible.md>
|
||||
## @owner <VERANTWORTLICH – siehe ai/context/responsible.md>
|
||||
## @version 1.0.0 (07.10.2026)
|
||||
##
|
||||
## @usage source "${SCRIPT_DIR}/_functions.sh"; B_OPTS+=(…); b_init "$@"; set -- "${ARGS[@]}"
|
||||
## @note Nur sourcen, nicht ausführen. Benötigt bash >= 4.4.
|
||||
## Kopien NICHT lokal ändern – Projektspezifisches gehört in
|
||||
## _functions.local.sh (wird automatisch geladen, falls vorhanden).
|
||||
## Standard: ai/common/bash.md
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
echo "_functions.sh wird gesourct, nicht ausgeführt." >&2
|
||||
exit 2
|
||||
fi
|
||||
[[ -n "${_B_LOADED:-}" ]] && return 0
|
||||
if (( BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then
|
||||
echo "FEHLER: bash >= 4.4 nötig (gefunden: ${BASH_VERSION})." >&2
|
||||
return 1
|
||||
fi
|
||||
_B_LOADED=1
|
||||
B_FUNCTIONS_VERSION="1.0.0"
|
||||
|
||||
# --- Pfade (pfadsicher, unabhängig vom aufrufenden $PWD) ----------------------
|
||||
# Das Script setzt SCRIPT_PATH/DIR/NAME selbst; hier nur Fallback.
|
||||
: "${SCRIPT_PATH:=$(readlink -f -- "${BASH_SOURCE[1]:-$0}")}"
|
||||
: "${SCRIPT_DIR:=${SCRIPT_PATH%/*}}"
|
||||
: "${SCRIPT_NAME:=${SCRIPT_PATH##*/}}"
|
||||
B_LIB_DIR="$(readlink -f -- "${BASH_SOURCE[0]}")"
|
||||
B_LIB_DIR="${B_LIB_DIR%/*}"
|
||||
B_CALL_DIR="${PWD}" # Verzeichnis, aus dem aufgerufen wurde
|
||||
B_TMP_DIR="" # wird von b_tmpdir gesetzt
|
||||
|
||||
# --- Standard-Optionen (gelten für jedes Script) ------------------------------
|
||||
# Format: "kurz|lang|typ[:META]|default|Beschreibung" typ = flag | value
|
||||
# Variable im Script: OPTION_<LANG> (Großbuchstaben, '-' → '_'), z. B. OPTION_DRY_RUN
|
||||
B_OPTS_STD=(
|
||||
"h|help|flag|0|diese Hilfe anzeigen"
|
||||
"d|debug|flag|0|ausführliche Ausgabe (Log-Level debug)"
|
||||
"q|quiet|flag|0|keine Konsolenausgabe (Logdatei bleibt)"
|
||||
"n|dry-run|flag|0|nur anzeigen, nichts ändern"
|
||||
"l|logfile|value:DATEI||zusätzlich in DATEI loggen"
|
||||
)
|
||||
declare -p B_OPTS &>/dev/null || B_OPTS=() # Script/.env hängen mit B_OPTS+=(…) an
|
||||
declare -ga ARGS=() # Positionsargumente nach b_init
|
||||
|
||||
declare -grA _B_LEVELS=([debug]=10 [info]=20 [warn]=30 [error]=40)
|
||||
declare -gA _B_OPT_SHORT=() _B_OPT_TYPE=() _B_OPT_META=() _B_OPT_DEFAULT=() _B_OPT_DESC=()
|
||||
declare -ga _B_OPT_ORDER=()
|
||||
|
||||
#===============================================================================
|
||||
# Ausgabe / Logging
|
||||
#===============================================================================
|
||||
|
||||
## @brief Zentrale Ausgabe auf Konsole und/oder Logdatei, gefiltert nach Level
|
||||
## @param $1 Level: debug | info | warn | error
|
||||
## @param $@ Text
|
||||
## @return 0 ok, 2 unbekanntes Level
|
||||
blog() {
|
||||
local level="${1:-}"; shift || true
|
||||
local n="${_B_LEVELS[$level]:-}" msg="$*" c="" r=""
|
||||
[[ -n "$n" ]] || { printf 'blog: unbekanntes Level "%s"\n' "$level" >&2; return 2; }
|
||||
|
||||
if (( ${B_LOG_CONSOLE:-1} )) && (( n >= ${_B_LEVELS[${B_LOG_LEVEL:-info}]:-20} )); then
|
||||
if [[ "${B_LOG_COLOR:-auto}" == 1 || ( "${B_LOG_COLOR:-auto}" == auto && -t 2 ) ]]; then
|
||||
r=$'\e[0m'
|
||||
case "$level" in debug) c=$'\e[2m' ;; warn) c=$'\e[33m' ;; error) c=$'\e[31m' ;; esac
|
||||
fi
|
||||
case "$level" in
|
||||
info) printf '%s\n' "$msg" ;;
|
||||
debug) printf '%s[DEBUG] %s%s\n' "$c" "$msg" "$r" >&2 ;;
|
||||
warn) printf '%s[WARN] %s%s\n' "$c" "$msg" "$r" >&2 ;;
|
||||
error) printf '%s[FEHLER] %s%s\n' "$c" "$msg" "$r" >&2 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ -n "${B_LOG_FILE:-}" ]] \
|
||||
&& (( n >= ${_B_LEVELS[${B_LOG_FILE_LEVEL:-${B_LOG_LEVEL:-info}}]:-20} )); then
|
||||
printf '%(%Y-%m-%d %H:%M:%S)T %-5s %s[%d] %s\n' -1 "${level^^}" "$SCRIPT_NAME" "$$" "$msg" \
|
||||
>> "$B_LOG_FILE" 2>/dev/null || true
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
## @brief Ersatz für echo: Info-Meldung über blog
|
||||
## @param $@ Text
|
||||
## @return 0
|
||||
becho() { blog info "$@"; }
|
||||
|
||||
## @brief Debug-Meldung (nur bei -d bzw. B_LOG_LEVEL=debug)
|
||||
## @param $@ Text
|
||||
## @return 0
|
||||
bdebug() { blog debug "$@"; }
|
||||
|
||||
## @brief Warnung (stderr)
|
||||
## @param $@ Text
|
||||
## @return 0
|
||||
bwarn() { blog warn "$@"; }
|
||||
|
||||
## @brief Fehlermeldung (stderr)
|
||||
## @param $@ Text
|
||||
## @return 0
|
||||
berror() { blog error "$@"; }
|
||||
|
||||
## @brief Fehlermeldung ausgeben und Script beenden (EXIT-Trap räumt auf)
|
||||
## @param $1 Text
|
||||
## @param $2 Exit-Code (Default 1; 2 = Aufruffehler, zeigt Hilfe-Hinweis)
|
||||
## @return – (beendet das Script)
|
||||
bdie() {
|
||||
local rc="${2:-1}"
|
||||
berror "${1:-Abbruch}"
|
||||
(( rc == 2 )) && blog info "Hilfe: ${SCRIPT_NAME} --help"
|
||||
exit "$rc"
|
||||
}
|
||||
|
||||
#===============================================================================
|
||||
# Hilfen für den Script-Alltag
|
||||
#===============================================================================
|
||||
|
||||
## @brief Befehl ausführen – oder bei --dry-run nur anzeigen
|
||||
## @param $@ Befehl mit Argumenten (kein String, kein eval)
|
||||
## @return Exit-Code des Befehls, 0 im Dry-Run
|
||||
brun() {
|
||||
if (( ${OPTION_DRY_RUN:-0} )); then
|
||||
becho "[dry-run] $(printf '%q ' "$@")"
|
||||
return 0
|
||||
fi
|
||||
bdebug "run: $(printf '%q ' "$@")"
|
||||
"$@"
|
||||
}
|
||||
|
||||
## @brief Prüft, ob Programme vorhanden sind
|
||||
## @param $@ Programmnamen
|
||||
## @return 0 alle da, 1 mindestens eins fehlt (wird gemeldet)
|
||||
b_require() {
|
||||
local cmd rc=0
|
||||
for cmd in "$@"; do
|
||||
command -v "$cmd" &>/dev/null || { berror "Programm fehlt: $cmd"; rc=1; }
|
||||
done
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
## @brief Prüft, ob Variablen gesetzt und nicht leer sind
|
||||
## @param $@ Variablennamen
|
||||
## @return 0 alle gesetzt, 1 mindestens eine fehlt (wird gemeldet)
|
||||
b_require_vars() {
|
||||
local v rc=0
|
||||
for v in "$@"; do
|
||||
[[ -n "${!v:-}" ]] || { berror "Variable nicht gesetzt: $v (in .env oder Umgebung)"; rc=1; }
|
||||
done
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
## @brief Legt ein temporäres Verzeichnis an (B_TMP_DIR), wird beim Exit gelöscht
|
||||
## @return 0 ok, 1 mktemp fehlgeschlagen
|
||||
b_tmpdir() {
|
||||
[[ -n "$B_TMP_DIR" ]] && return 0
|
||||
B_TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/${SCRIPT_NAME%.sh}.XXXXXX")" || return 1
|
||||
bdebug "Temp-Verzeichnis: $B_TMP_DIR"
|
||||
}
|
||||
|
||||
## @brief Macht einen Pfad absolut; relative Pfade gelten relativ zu $2
|
||||
## @param $1 Pfad
|
||||
## @param $2 Basisverzeichnis (Default: SCRIPT_DIR)
|
||||
## @return 0; Ergebnis auf stdout
|
||||
b_abspath() {
|
||||
local p="$1" base="${2:-$SCRIPT_DIR}"
|
||||
[[ "$p" == /* ]] || p="${base%/}/${p}"
|
||||
printf '%s\n' "$p"
|
||||
}
|
||||
|
||||
#===============================================================================
|
||||
# Usage aus der Präambel
|
||||
#===============================================================================
|
||||
|
||||
## @brief Gibt die Präambel des Scripts als Hilfe aus ('## ' wird entfernt,
|
||||
## die Zeile '## @OPTIONS@' wird durch die Optionsliste ersetzt)
|
||||
## @return 0
|
||||
b_usage() {
|
||||
local line part=0
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^#-{10,} ]]; then
|
||||
(( ++part >= 2 )) && break
|
||||
continue
|
||||
fi
|
||||
(( part == 1 )) && [[ "$line" == '##'* ]] || continue
|
||||
line="${line#'##'}"; line="${line# }"
|
||||
if [[ "$line" == *'@OPTIONS@'* ]]; then _b_opts_help; else printf '%s\n' "$line"; fi
|
||||
done < "$SCRIPT_PATH"
|
||||
return 0
|
||||
}
|
||||
|
||||
## @brief Optionsliste für die Hilfe (aus B_OPTS_STD + B_OPTS)
|
||||
## @return 0
|
||||
_b_opts_help() {
|
||||
local long s left d
|
||||
for long in "${_B_OPT_ORDER[@]}"; do
|
||||
s="${_B_OPT_SHORT[$long]}"
|
||||
left="${s:+-$s | }--${long}"
|
||||
[[ "${_B_OPT_TYPE[$long]}" == value ]] && left+=" ${_B_OPT_META[$long]}"
|
||||
d="${_B_OPT_DEFAULT[$long]}"
|
||||
if [[ "${_B_OPT_TYPE[$long]}" == value && -n "$d" ]]; then
|
||||
printf ' %-28s %s (Default: %s)\n' "$left" "${_B_OPT_DESC[$long]}" "$d"
|
||||
else
|
||||
printf ' %-28s %s\n' "$left" "${_B_OPT_DESC[$long]}"
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
#===============================================================================
|
||||
# Optionen
|
||||
#===============================================================================
|
||||
|
||||
## @brief Variablenname zu einer Langoption: dry-run → OPTION_DRY_RUN
|
||||
## @param $1 Langname
|
||||
## @return 0; Name auf stdout
|
||||
_b_optvar() { local n="${1^^}"; printf 'OPTION_%s\n' "${n//-/_}"; }
|
||||
|
||||
## @brief Baut die Optionstabellen aus B_OPTS_STD + B_OPTS und prüft sie
|
||||
## @return 0 ok, 2 Definitionsfehler
|
||||
_b_opts_build() {
|
||||
local def s long type meta deflt desc rest
|
||||
local -A seen_short=()
|
||||
_B_OPT_ORDER=()
|
||||
for def in "${B_OPTS_STD[@]}" "${B_OPTS[@]}"; do
|
||||
IFS='|' read -r s long type deflt desc rest <<< "$def"
|
||||
meta="${type#*:}"; type="${type%%:*}"; [[ "$meta" == "$type" ]] && meta="WERT"
|
||||
if [[ -z "$long" || ! "$long" =~ ^[a-z][a-z0-9-]*$ || ! "$type" =~ ^(flag|value)$ || -n "$rest" ]]; then
|
||||
berror "Ungültige Optionsdefinition: \"$def\" (kurz|lang|flag|value[:META]|default|Text)"
|
||||
return 2
|
||||
fi
|
||||
if [[ -n "$s" ]]; then
|
||||
[[ -n "${seen_short[$s]:-}" && "${seen_short[$s]}" != "$long" ]] && {
|
||||
berror "Kurzoption -$s doppelt vergeben (${seen_short[$s]}, $long)"; return 2; }
|
||||
seen_short[$s]="$long"
|
||||
fi
|
||||
[[ -n "${_B_OPT_TYPE[$long]:-}" ]] || _B_OPT_ORDER+=("$long")
|
||||
_B_OPT_SHORT[$long]="$s"; _B_OPT_TYPE[$long]="$type"; _B_OPT_META[$long]="$meta"
|
||||
_B_OPT_DEFAULT[$long]="$deflt"; _B_OPT_DESC[$long]="$desc"
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
## @brief Setzt OPTION_<LANG> für eine erkannte Option
|
||||
## @param $1 Langname
|
||||
## @param $2 Wert
|
||||
## @return 0 ok, 2 ungültiger Wert
|
||||
_b_opt_set() {
|
||||
local long="$1" val="$2" var
|
||||
var="$(_b_optvar "$long")"
|
||||
if [[ "${_B_OPT_TYPE[$long]}" == flag && ! "$val" =~ ^[01]$ ]]; then
|
||||
berror "Option --$long erwartet 0 oder 1, nicht \"$val\""
|
||||
return 2
|
||||
fi
|
||||
printf -v "$var" '%s' "$val"
|
||||
_B_CLI_SET[$long]=1
|
||||
return 0
|
||||
}
|
||||
|
||||
## @brief Parst die Kommandozeile → OPTION_* und ARGS (Positionsargumente)
|
||||
## @param $@ Argumente des Scripts
|
||||
## @return 0 ok, 2 Aufruffehler
|
||||
_b_opts_parse() {
|
||||
local arg name val s i long
|
||||
ARGS=()
|
||||
while (( $# )); do
|
||||
arg="$1"; shift
|
||||
case "$arg" in
|
||||
--) ARGS+=("$@"); break ;;
|
||||
--?*)
|
||||
name="${arg#--}"; val=""
|
||||
if [[ "$name" == *=* ]]; then val="${name#*=}"; name="${name%%=*}"; fi
|
||||
[[ -n "${_B_OPT_TYPE[$name]:-}" ]] || { berror "Unbekannte Option: --$name"; return 2; }
|
||||
if [[ "${_B_OPT_TYPE[$name]}" == value && "$arg" != *=* ]]; then
|
||||
(( $# )) || { berror "Option --$name braucht einen Wert"; return 2; }
|
||||
val="$1"; shift
|
||||
elif [[ "${_B_OPT_TYPE[$name]}" == flag && "$arg" != *=* ]]; then
|
||||
val=1
|
||||
fi
|
||||
_b_opt_set "$name" "$val" || return 2
|
||||
;;
|
||||
-?*)
|
||||
for (( i = 1; i < ${#arg}; i++ )); do
|
||||
s="${arg:i:1}"; long=""
|
||||
for name in "${_B_OPT_ORDER[@]}"; do
|
||||
[[ "${_B_OPT_SHORT[$name]}" == "$s" ]] && { long="$name"; break; }
|
||||
done
|
||||
[[ -n "$long" ]] || { berror "Unbekannte Option: -$s"; return 2; }
|
||||
if [[ "${_B_OPT_TYPE[$long]}" == value ]]; then
|
||||
val="${arg:i+1}" # -lDATEI oder -l DATEI
|
||||
if [[ -z "$val" ]]; then
|
||||
(( $# )) || { berror "Option -$s braucht einen Wert"; return 2; }
|
||||
val="$1"; shift
|
||||
fi
|
||||
_b_opt_set "$long" "$val" || return 2
|
||||
break
|
||||
fi
|
||||
_b_opt_set "$long" 1 || return 2
|
||||
done
|
||||
;;
|
||||
*) ARGS+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
#===============================================================================
|
||||
# Trap / Aufräumen
|
||||
#===============================================================================
|
||||
|
||||
## @brief EXIT-Trap: ruft cleanup() des Scripts (falls definiert), löscht B_TMP_DIR
|
||||
## @return – (beendet mit dem ursprünglichen Exit-Code)
|
||||
_b_on_exit() {
|
||||
local rc=$?
|
||||
trap - EXIT
|
||||
set +e
|
||||
if declare -F cleanup &>/dev/null; then
|
||||
cleanup "$rc" || bwarn "cleanup() meldete Fehler"
|
||||
fi
|
||||
[[ -n "$B_TMP_DIR" && -d "$B_TMP_DIR" ]] && rm -rf -- "$B_TMP_DIR"
|
||||
bdebug "Ende ${SCRIPT_NAME} (rc=${rc})"
|
||||
exit "$rc"
|
||||
}
|
||||
|
||||
## @brief ERR-Trap: meldet fehlgeschlagenen Befehl mit Funktion und Zeile
|
||||
## (bewusstes 'return N' hat die Funktion schon selbst gemeldet → nur debug)
|
||||
## @param $1 Zeilennummer
|
||||
## @param $2 Befehl
|
||||
## @return 0 (Abbruch übernimmt set -e)
|
||||
_b_on_err() {
|
||||
local rc=$? lvl=error
|
||||
[[ "$2" == return* ]] && lvl=debug
|
||||
blog "$lvl" "rc=${rc} in ${FUNCNAME[1]:-main}() Zeile ${1}: ${2}"
|
||||
return 0
|
||||
}
|
||||
|
||||
#===============================================================================
|
||||
# Einstieg
|
||||
#===============================================================================
|
||||
|
||||
## @brief Initialisiert das Script: Trap, .env, Defaults, Optionen, Logging, Hilfe
|
||||
## Vorrang: Default < .env < Umgebung (B_*, OPTION_*) < Kommandozeile
|
||||
## @param $@ Argumente des Scripts (danach: set -- "${ARGS[@]}")
|
||||
## @return 0 ok; beendet bei --help (0) oder Aufruffehler (2)
|
||||
b_init() {
|
||||
local v long var env_file
|
||||
local -A env_keep=()
|
||||
declare -gA _B_CLI_SET=()
|
||||
|
||||
trap _b_on_exit EXIT
|
||||
trap '_b_on_err "$LINENO" "$BASH_COMMAND"' ERR
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
# 1. exportierte Steuer-Variablen aus der Umgebung merken (gewinnen gegen .env)
|
||||
for v in $(compgen -e); do
|
||||
[[ "$v" =~ ^(B_[A-Z0-9_]+|OPTION_[A-Z0-9_]+)$ ]] && env_keep[$v]="${!v}"
|
||||
done
|
||||
|
||||
# 2. .env laden (Bash-Syntax; relativ zum Script, nicht zum $PWD)
|
||||
env_file="$(b_abspath "${B_ENV_FILE:-.env}")"
|
||||
if [[ -f "$env_file" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$env_file" || bdie "Fehler beim Laden von $env_file"
|
||||
fi
|
||||
for v in "${!env_keep[@]}"; do printf -v "$v" '%s' "${env_keep[$v]}"; done
|
||||
|
||||
# 3. Defaults für alles, was weder .env noch Umgebung gesetzt hat
|
||||
: "${B_LOG_LEVEL:=info}" "${B_LOG_CONSOLE:=1}" "${B_LOG_FILE:=}" \
|
||||
"${B_LOG_FILE_LEVEL:=}" "${B_LOG_COLOR:=auto}"
|
||||
[[ -n "${_B_LEVELS[$B_LOG_LEVEL]:-}" ]] || { bwarn "B_LOG_LEVEL=$B_LOG_LEVEL ungültig → info"; B_LOG_LEVEL=info; }
|
||||
[[ -n "$B_LOG_FILE" ]] && B_LOG_FILE="$(b_abspath "$B_LOG_FILE" "$SCRIPT_DIR")"
|
||||
|
||||
_b_opts_build || exit 2
|
||||
for long in "${_B_OPT_ORDER[@]}"; do
|
||||
var="$(_b_optvar "$long")"
|
||||
[[ -v "$var" ]] || printf -v "$var" '%s' "${_B_OPT_DEFAULT[$long]}"
|
||||
done
|
||||
|
||||
# 4. Kommandozeile
|
||||
_b_opts_parse "$@" || bdie "Aufruffehler" 2
|
||||
|
||||
# 5. Standard-Optionen anwenden
|
||||
(( OPTION_DEBUG )) && B_LOG_LEVEL=debug
|
||||
(( OPTION_QUIET )) && B_LOG_CONSOLE=0
|
||||
if [[ -n "${_B_CLI_SET[logfile]:-}" ]]; then
|
||||
B_LOG_FILE="$(b_abspath "$OPTION_LOGFILE" "$B_CALL_DIR")" # CLI: relativ zum $PWD
|
||||
elif [[ -n "$OPTION_LOGFILE" ]]; then
|
||||
B_LOG_FILE="$(b_abspath "$OPTION_LOGFILE" "$SCRIPT_DIR")"
|
||||
fi
|
||||
if [[ -n "$B_LOG_FILE" ]]; then
|
||||
mkdir -p -- "${B_LOG_FILE%/*}" 2>/dev/null && touch -- "$B_LOG_FILE" 2>/dev/null \
|
||||
|| { B_LOG_FILE=""; bwarn "Logdatei nicht beschreibbar – nur Konsole"; }
|
||||
fi
|
||||
|
||||
(( OPTION_HELP )) && { b_usage; exit 0; }
|
||||
|
||||
bdebug "Start ${SCRIPT_NAME} (_functions ${B_FUNCTIONS_VERSION}) in ${SCRIPT_DIR}, Aufruf aus ${B_CALL_DIR}"
|
||||
bdebug ".env: $( [[ -f "$env_file" ]] && echo "$env_file" || echo "keine" ) · Log: ${B_LOG_LEVEL}/${B_LOG_FILE:-ohne Datei}"
|
||||
for long in "${_B_OPT_ORDER[@]}"; do
|
||||
var="$(_b_optvar "$long")"; bdebug " ${var}=${!var}"
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- projektspezifische Erweiterungen -----------------------------------------
|
||||
if [[ -f "${B_LIB_DIR}/_functions.local.sh" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "${B_LIB_DIR}/_functions.local.sh"
|
||||
fi
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------
|
||||
## @file bashdoc.sh
|
||||
## @brief Erzeugt Markdown-Doku aus den '##'-Kommentaren von Bash-Scripts
|
||||
## (Präambel + Funktionsköpfe, angelehnt an javadoc)
|
||||
## @author <AUTOR – aus ai/context/responsible.md>
|
||||
## @owner <VERANTWORTLICH – aus ai/context/responsible.md>
|
||||
## @assist KI-unterstützt (Claude), geprüft von <AUTOR>
|
||||
## @version 1.0.0 (07.10.2026)
|
||||
##
|
||||
## @usage bashdoc.sh [Optionen] <script.sh> [...]
|
||||
## Ausgabe auf stdout oder mit -o in eine Datei
|
||||
## @options
|
||||
## @OPTIONS@
|
||||
## @exit 0 = ok · 1 = Fehler · 2 = Aufruffehler
|
||||
#-------------------------------------------------------------------------------
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_PATH="$(readlink -f -- "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="${SCRIPT_PATH%/*}"
|
||||
SCRIPT_NAME="${SCRIPT_PATH##*/}"
|
||||
# shellcheck source=_functions.sh
|
||||
source "${SCRIPT_DIR}/_functions.sh" || { echo "FEHLER: ${SCRIPT_DIR}/_functions.sh fehlt" >&2; exit 1; }
|
||||
|
||||
B_OPTS+=(
|
||||
"o|output|value:DATEI||Markdown in DATEI schreiben (relativ = zum Aufrufverzeichnis)"
|
||||
"p|private|flag|0|auch interne Funktionen (_name) dokumentieren"
|
||||
)
|
||||
b_init "$@"
|
||||
set -- "${ARGS[@]}"
|
||||
|
||||
## @brief Wandelt ein Script in Markdown um
|
||||
## @param $1 Script-Datei
|
||||
## @return 0 ok, 1 Datei nicht lesbar; Markdown auf stdout
|
||||
doc_file() {
|
||||
local file="$1"
|
||||
[[ -r "$file" ]] || { berror "Nicht lesbar: $file"; return 1; }
|
||||
awk -v fname="${file##*/}" -v private="$OPTION_PRIVATE" '
|
||||
function flush_block(name, i, l, tag) {
|
||||
if (!private && name ~ /^_/) { n = 0; return }
|
||||
printf "\n### `%s()`\n\n", name
|
||||
for (i = 1; i <= n; i++) {
|
||||
l = blk[i]
|
||||
if (l ~ /^@brief/) { sub(/^@brief[ \t]*/, "", l); print l; last = "brief" }
|
||||
else if (l ~ /^@param/) { sub(/^@param[ \t]*/, "", l); print "- **Param** " l; last = "param" }
|
||||
else if (l ~ /^@return/) { sub(/^@return[ \t]*/, "", l); print "- **Return** " l; last = "return" }
|
||||
else if (l ~ /^@/) { tag = l; sub(/[ \t].*/, "", tag); sub(/^@[a-z]+[ \t]*/, "", l); print "- **" substr(tag, 2) "** " l }
|
||||
else if (l != "") { print (last == "brief" ? l : " " l) }
|
||||
}
|
||||
n = 0
|
||||
}
|
||||
function print_opts( i, f, left) {
|
||||
print " -h -d -q -n -l Standardoptionen (Hilfe, Debug, still, Trockenlauf, Logdatei)"
|
||||
for (i = 1; i <= nopt; i++) {
|
||||
split(opt[i], f, "|"); sub(/:.*/, "", f[3])
|
||||
left = (f[1] != "" ? "-" f[1] " | " : "") "--" f[2]
|
||||
if (opt_meta[i] != "") left = left " " opt_meta[i]
|
||||
printf " %-26s %s%s\n", left, f[5], (f[3] == "value" && f[4] != "" ? " (Default: " f[4] ")" : "")
|
||||
}
|
||||
}
|
||||
# 1. Durchlauf: Script-Optionen aus B_OPTS+=( "…" ) sammeln
|
||||
FNR == NR {
|
||||
if ($0 ~ /^[ \t]*B_OPTS\+=\(/) inopts = 1
|
||||
if (inopts) {
|
||||
l = $0
|
||||
while (match(l, /"[^"]*"/)) {
|
||||
o = substr(l, RSTART + 1, RLENGTH - 2); l = substr(l, RSTART + RLENGTH)
|
||||
opt[++nopt] = o; split(o, f, "|"); opt_meta[nopt] = (f[3] ~ /:/ ? substr(f[3], index(f[3], ":") + 1) : (f[3] == "value" ? "WERT" : ""))
|
||||
}
|
||||
if ($0 ~ /\)[ \t]*$/) inopts = 0
|
||||
}
|
||||
next
|
||||
}
|
||||
# 2. Durchlauf: Präambel + Funktionsköpfe
|
||||
/^#-{10,}/ { part++; if (part == 1) printf "## %s\n\n```text\n", fname; if (part == 2) print "```"; next }
|
||||
part == 1 && /^##/ { l = $0; sub(/^## ?/, "", l); if (l !~ /@OPTIONS@/) print l; else print_opts(); next }
|
||||
part >= 2 && /^[ \t]*##/ { l = $0; sub(/^[ \t]*## ?/, "", l); blk[++n] = l; next }
|
||||
part >= 2 && n > 0 && /^[ \t]*(function[ \t]+)?[A-Za-z_][A-Za-z0-9_]*[ \t]*\(\)/ {
|
||||
name = $0; sub(/^[ \t]*(function[ \t]+)?/, "", name); sub(/[ \t]*\(.*/, "", name)
|
||||
flush_block(name); next
|
||||
}
|
||||
part >= 2 && n > 0 && !/^[ \t]*#/ { n = 0 }
|
||||
' "$file" "$file"
|
||||
}
|
||||
|
||||
## @brief Hauptablauf
|
||||
## @param $@ Script-Dateien
|
||||
## @return 0 ok, 1 Fehler
|
||||
main() {
|
||||
(( $# )) || bdie "Mindestens ein Script angeben" 2
|
||||
local f out="" rc=0
|
||||
[[ -n "$OPTION_OUTPUT" ]] && out="$(b_abspath "$OPTION_OUTPUT" "$B_CALL_DIR")"
|
||||
{
|
||||
printf '# Script-Doku\n\n> Erzeugt mit `bin/bashdoc.sh` am %(%d.%m.%Y)T – Quelle sind die `##`-Kommentare.\n\n' -1
|
||||
for f in "$@"; do
|
||||
doc_file "$(b_abspath "$f" "$B_CALL_DIR")" || rc=1
|
||||
printf '\n'
|
||||
done
|
||||
} > "${out:-/dev/stdout}"
|
||||
[[ -n "$out" ]] && becho "Doku geschrieben: $out"
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------
|
||||
## @file kb_index.sh
|
||||
## @brief Erzeugt ai/context/knowledge/INDEX.md aus dem Frontmatter aller Einträge
|
||||
## und prüft die Pflichtfelder (id, title, type, status, updated, owner)
|
||||
## @author <AUTOR – aus ai/context/responsible.md>
|
||||
## @owner <VERANTWORTLICH – aus ai/context/responsible.md>
|
||||
## @assist KI-unterstützt (Claude), geprüft von <AUTOR>
|
||||
## @version 1.0.0 (07.10.2026)
|
||||
##
|
||||
## @usage kb_index.sh [Optionen]
|
||||
## @options
|
||||
## @OPTIONS@
|
||||
## @exit 0 = ok · 1 = Fehler / Index veraltet (--check) · 2 = Aufruffehler
|
||||
#-------------------------------------------------------------------------------
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_PATH="$(readlink -f -- "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="${SCRIPT_PATH%/*}"
|
||||
SCRIPT_NAME="${SCRIPT_PATH##*/}"
|
||||
# shellcheck source=_functions.sh
|
||||
source "${SCRIPT_DIR}/_functions.sh" || { echo "FEHLER: ${SCRIPT_DIR}/_functions.sh fehlt" >&2; exit 1; }
|
||||
|
||||
B_OPTS+=(
|
||||
"c|check|flag|0|nur prüfen: Pflichtfelder ok und INDEX.md aktuell?"
|
||||
"k|kbdir|value:DIR|../ai/context/knowledge|Wurzel der Knowledge-DB (relativ = zum Script)"
|
||||
)
|
||||
b_init "$@"
|
||||
set -- "${ARGS[@]}"
|
||||
|
||||
REQUIRED=(id title type status updated owner)
|
||||
|
||||
## @brief Liest einen Frontmatter-Wert (einzeilig) aus einer Markdown-Datei
|
||||
## @param $1 Datei
|
||||
## @param $2 Schlüssel
|
||||
## @return 0; Wert auf stdout (leer, wenn nicht vorhanden)
|
||||
fm_get() {
|
||||
awk -v key="$2" '
|
||||
NR == 1 && $0 != "---" { exit }
|
||||
NR > 1 && $0 == "---" { exit }
|
||||
NR > 1 {
|
||||
k = $0; sub(/:.*/, "", k)
|
||||
if (k == key) { v = $0; sub(/^[^:]*:[ \t]*/, "", v); sub(/[ \t]+#.*$/, "", v); gsub(/^"|"$/, "", v); print v; exit }
|
||||
}' "$1"
|
||||
}
|
||||
|
||||
## @brief Baut den Index-Inhalt und meldet fehlende Pflichtfelder
|
||||
## @param $1 KB-Wurzel
|
||||
## @return 0 ok, 1 mindestens ein Eintrag unvollständig; Markdown auf stdout
|
||||
build_index() {
|
||||
local kb="$1" f rel dir last_dir="" key val rc=0 n=0
|
||||
local -A ids=()
|
||||
printf '# Knowledge-Index\n\n'
|
||||
printf '> Automatisch erzeugt von `bin/kb_index.sh` – nicht von Hand bearbeiten.\n'
|
||||
printf '> Format der Einträge: [README.md](README.md)\n'
|
||||
while IFS= read -r f; do
|
||||
rel="${f#"$kb"/}"; dir="${rel%%/*}"
|
||||
[[ "$dir" == "$rel" ]] && dir="."
|
||||
for key in "${REQUIRED[@]}"; do
|
||||
[[ -n "$(fm_get "$f" "$key")" ]] || { berror "${rel}: Pflichtfeld '${key}' fehlt"; rc=1; }
|
||||
done
|
||||
val="$(fm_get "$f" id)"
|
||||
if [[ -n "$val" ]]; then
|
||||
[[ -n "${ids[$val]:-}" ]] && { berror "${rel}: id '${val}' doppelt (auch ${ids[$val]})"; rc=1; }
|
||||
ids[$val]="$rel"
|
||||
fi
|
||||
if [[ "$dir" != "$last_dir" ]]; then
|
||||
printf '\n## %s\n\n| id | Titel | Status | Stand | Tags |\n|---|---|---|---|---|\n' "$dir"
|
||||
last_dir="$dir"
|
||||
fi
|
||||
printf '| %s | [%s](%s) | %s | %s | %s |\n' "$val" "$(fm_get "$f" title)" "$rel" \
|
||||
"$(fm_get "$f" status)" "$(fm_get "$f" updated)" "$(fm_get "$f" tags | tr -d '[]')"
|
||||
(( ++n ))
|
||||
done < <(find "$kb" -type f -name '*.md' ! -name 'README.md' ! -name 'INDEX.md' ! -name '_*' | LC_ALL=C sort)
|
||||
(( n )) || printf '\n_(noch keine Einträge)_\n'
|
||||
bdebug "${n} Einträge gelesen"
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
## @brief Hauptablauf
|
||||
## @return 0 ok, 1 Fehler / veraltet
|
||||
main() {
|
||||
(( $# == 0 )) || bdie "Keine Positionsargumente erwartet" 2
|
||||
local kb rc=0 new
|
||||
kb="$(readlink -f -- "$(b_abspath "$OPTION_KBDIR")")"
|
||||
[[ -d "$kb" ]] || { berror "KB-Verzeichnis fehlt: $kb"; return 1; }
|
||||
b_tmpdir
|
||||
new="${B_TMP_DIR}/INDEX.md"
|
||||
build_index "$kb" > "$new" || rc=1
|
||||
|
||||
if (( OPTION_CHECK )); then
|
||||
if ! cmp -s "$new" "${kb}/INDEX.md"; then
|
||||
berror "INDEX.md ist veraltet – bin/kb_index.sh ausführen"; rc=1
|
||||
fi
|
||||
(( rc )) || becho "Knowledge-DB ok"
|
||||
return "$rc"
|
||||
fi
|
||||
if cmp -s "$new" "${kb}/INDEX.md"; then
|
||||
becho "INDEX.md unverändert"
|
||||
else
|
||||
brun cp -- "$new" "${kb}/INDEX.md"
|
||||
becho "INDEX.md aktualisiert"
|
||||
fi
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in new issue
Block a user