commit d3e9309e833c38a9a6a7ad4c2f94cd576f647505 Author: Torsten Wierschin Date: Tue Jul 14 11:39:40 2026 +0200 Initial commit diff --git a/strip_c_comments_safe.sh b/strip_c_comments_safe.sh new file mode 100755 index 000000000..3af5836e0 --- /dev/null +++ b/strip_c_comments_safe.sh @@ -0,0 +1,425 @@ +#!/usr/bin/env bash +set -euo pipefail + +BACKUP_EXT=".bak" +DRY_RUN=0 +WITH_CPP=0 +WITH_BASH=0 +WITH_PYTHON=0 +TARGET_DIR="." + +usage() { + cat <<'EOF' +Usage: + strip_c_comments_safe.sh [OPTIONEN] [VERZEICHNIS] + +Optionen: + --dry-run Nur Vorschau anzeigen, keine Dateien ändern + --backup-ext EXT Backup-Endung setzen, Standard: .bak + --cpp Zusätzlich auch C++-Dateien verarbeiten + --bash Zusätzlich auch Bash-Skripte (*.sh) verarbeiten + --python Zusätzlich auch Python-Skripte (*.py) verarbeiten + -h, --help Hilfe anzeigen + +Argumente: + VERZEICHNIS Zielverzeichnis, Standard: aktuelles Verzeichnis + +Beispiele: + ./strip_c_comments_safe.sh + ./strip_c_comments_safe.sh src + ./strip_c_comments_safe.sh --dry-run include + ./strip_c_comments_safe.sh --cpp src + ./strip_c_comments_safe.sh --bash scripts + ./strip_c_comments_safe.sh --python tools + ./strip_c_comments_safe.sh --cpp --backup-ext .orig ./project +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + DRY_RUN=1 + shift + ;; + --backup-ext) + BACKUP_EXT="${2:-}" + if [[ -z "$BACKUP_EXT" ]]; then + echo "Fehler: --backup-ext braucht ein Argument" >&2 + exit 1 + fi + shift 2 + ;; + --cpp) + WITH_CPP=1 + shift + ;; + --bash) + WITH_BASH=1 + shift + ;; + --python) + WITH_PYTHON=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + --) + shift + break + ;; + -*) + echo "Unbekannte Option: $1" >&2 + usage >&2 + exit 1 + ;; + *) + if [[ "$TARGET_DIR" != "." ]]; then + echo "Fehler: Es darf nur ein Verzeichnis angegeben werden" >&2 + usage >&2 + exit 1 + fi + TARGET_DIR="$1" + shift + ;; + esac +done + +if [[ $# -gt 0 ]]; then + if [[ "$TARGET_DIR" != "." ]]; then + echo "Fehler: Es darf nur ein Verzeichnis angegeben werden" >&2 + usage >&2 + exit 1 + fi + TARGET_DIR="$1" + shift +fi + +if [[ ! -d "$TARGET_DIR" ]]; then + echo "Fehler: Verzeichnis nicht gefunden: $TARGET_DIR" >&2 + exit 1 +fi + +process_file() { + local mode="$1" + local file="$2" + local tmp + tmp="$(mktemp)" + + if [[ "$mode" == "python" ]]; then + python3 - "$file" > "$tmp" <<'PY' +import io +import re +import sys +import tokenize + +path = sys.argv[1] +with open(path, "rb") as f: + data = f.read() + +tokens = [] +for tok in tokenize.tokenize(io.BytesIO(data).readline): + if tok.type == tokenize.ENCODING: + continue + if tok.type == tokenize.COMMENT: + line = tok.start[0] + text = tok.string + keep = (line == 1 and text.startswith("#!")) or ( + line <= 2 and re.search(r"coding[:=]\s*[-\w.]+", text) + ) + if not keep: + continue + tokens.append(tok) + +result = tokenize.untokenize(tokens) +if isinstance(result, str): + sys.stdout.write(result) +else: + sys.stdout.buffer.write(result) +PY + else + COMMENT_MODE="$mode" perl -0777 -pe ' + my $mode = $ENV{COMMENT_MODE} // "c"; + my $out = ""; + my $i = 0; + my $len = length($_); + my $state = "code"; + my $line = 1; + + sub is_ident { + my ($ch) = @_; + return defined($ch) && $ch =~ /[A-Za-z0-9_]/; + } + + sub skip_line_comment { + my ($s, $idx, $total) = @_; + while ($idx < $total && substr($s, $idx, 1) ne "\n") { + $idx++; + } + return $idx; + } + + while ($i < $len) { + my $c = substr($_, $i, 1); + my $c2 = $i + 1 < $len ? substr($_, $i, 2) : ""; + + if ($mode eq "c") { + if ($state eq "code") { + if ($c2 eq "/*") { + my $prev = length($out) ? substr($out, -1, 1) : ""; + $i += 2; + + my $saw_nl = 0; + while ($i < $len) { + my $d = substr($_, $i, 1); + my $d2 = $i + 1 < $len ? substr($_, $i, 2) : ""; + $saw_nl = 1 if $d eq "\n"; + if ($d2 eq "*/") { + $i += 2; + last; + } + $i++; + } + + my $next = $i < $len ? substr($_, $i, 1) : ""; + + if ($saw_nl) { + $out .= "\n"; + $line++; + } elsif (is_ident($prev) && is_ident($next)) { + $out .= " "; + } + + next; + } + elsif ($c2 eq "//") { + my $prev = length($out) ? substr($out, -1, 1) : ""; + $i += 2; + $i = skip_line_comment($_, $i, $len); + my $next = $i < $len ? substr($_, $i, 1) : ""; + + if ($i < $len && $next eq "\n") { + $out .= "\n"; + $line++; + $i++; + } elsif (is_ident($prev)) { + $out .= " "; + } + + next; + } + elsif ($c eq "\"") { + $out .= $c; + $i++; + $state = "string"; + next; + } + elsif ($c eq "'\''") { + $out .= $c; + $i++; + $state = "char"; + next; + } + else { + $out .= $c; + $line++ if $c eq "\n"; + $i++; + next; + } + } + elsif ($state eq "string") { + if ($c eq "\\") { + if ($i + 1 < $len) { + my $pair = substr($_, $i, 2); + $out .= $pair; + $line++ if $pair =~ /\n/; + $i += 2; + } else { + $out .= $c; + $i++; + } + next; + } + elsif ($c eq "\"") { + $out .= $c; + $i++; + $state = "code"; + next; + } + else { + $out .= $c; + $line++ if $c eq "\n"; + $i++; + next; + } + } + elsif ($state eq "char") { + if ($c eq "\\") { + if ($i + 1 < $len) { + my $pair = substr($_, $i, 2); + $out .= $pair; + $line++ if $pair =~ /\n/; + $i += 2; + } else { + $out .= $c; + $i++; + } + next; + } + elsif ($c eq "'\''") { + $out .= $c; + $i++; + $state = "code"; + next; + } + else { + $out .= $c; + $line++ if $c eq "\n"; + $i++; + next; + } + } + } else { + if ($state eq "code") { + if ($c eq "\n") { + $out .= $c; + $line++; + $i++; + next; + } + + if ($c eq "#") { + my $bol = ($i == 0 || substr($_, $i - 1, 1) eq "\n") ? 1 : 0; + my $prev = length($out) ? substr($out, -1, 1) : ""; + my $starts_comment = 0; + + if ($line == 1 && $bol && $i + 1 < $len && substr($_, $i, 2) eq "#!") { + $starts_comment = 0; + } elsif ($bol || $prev =~ /[\s;]/) { + $starts_comment = 1; + } + + if ($starts_comment) { + $i = skip_line_comment($_, $i, $len); + next; + } + } + + if ($c eq "'\''") { + $out .= $c; + $i++; + $state = "single"; + next; + } + elsif ($c eq "\"") { + $out .= $c; + $i++; + $state = "double"; + next; + } + elsif ($c eq "\\") { + if ($i + 1 < $len) { + my $pair = substr($_, $i, 2); + $out .= $pair; + $line++ if $pair =~ /\n/; + $i += 2; + } else { + $out .= $c; + $i++; + } + next; + } + else { + $out .= $c; + $i++; + next; + } + } + elsif ($state eq "single") { + $out .= $c; + $i++; + $state = "code" if $c eq "'\''"; + next; + } + elsif ($state eq "double") { + if ($c eq "\\") { + if ($i + 1 < $len) { + my $pair = substr($_, $i, 2); + $out .= $pair; + $line++ if $pair =~ /\n/; + $i += 2; + } else { + $out .= $c; + $i++; + } + next; + } + $out .= $c; + $line++ if $c eq "\n"; + $i++; + $state = "code" if $c eq "\""; + next; + } + } + } + + $_ = $out; + ' "$file" > "$tmp" + fi + + if cmp -s "$file" "$tmp"; then + rm -f "$tmp" + return 0 + fi + + if [[ "$DRY_RUN" -eq 1 ]]; then + echo "=== $file ===" + diff -u -- "$file" "$tmp" || true + rm -f "$tmp" + else + cp -p -- "$file" "$file$BACKUP_EXT" + cat -- "$tmp" > "$file" + rm -f "$tmp" + echo "Geändert: $file (Backup: $file$BACKUP_EXT)" + fi +} + +detect_mode() { + local file="$1" + case "$file" in + *.c|*.h|*.cpp|*.cxx|*.cc|*.hpp|*.hxx|*.hh) + echo "c" + ;; + *.sh) + echo "bash" + ;; + *.py) + echo "python" + ;; + *) + echo "" + ;; + esac +} + +find_expr=() +find_expr+=( -name "*.c" -o -name "*.h" ) + +if [[ "$WITH_CPP" -eq 1 ]]; then + find_expr+=( -o -name "*.cpp" -o -name "*.cxx" -o -name "*.cc" -o -name "*.hpp" -o -name "*.hxx" -o -name "*.hh" ) +fi + +if [[ "$WITH_BASH" -eq 1 ]]; then + find_expr+=( -o -name "*.sh" ) +fi + +if [[ "$WITH_PYTHON" -eq 1 ]]; then + find_expr+=( -o -name "*.py" ) +fi + +find "$TARGET_DIR" -type f \( "${find_expr[@]}" \) -print0 | +while IFS= read -r -d '' file; do + mode="$(detect_mode "$file")" + [[ -n "$mode" ]] && process_file "$mode" "$file" +done \ No newline at end of file