#!/usr/bin/env bash set -euo pipefail BACKUP_EXT=".bak" DRY_RUN=0 WITH_CPP=0 WITH_BASH=0 WITH_PYTHON=0 WITH_CMAKE=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 --cmake Zusätzlich auch CMake-Skripte (*.cmake) 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 --cmake cmake ./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 ;; --cmake) WITH_CMAKE=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 TARGET_DIR=$(cd -- "$TARGET_DIR" && pwd) 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" ;; *.cmake) echo "bash" ;; *) 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 if [[ "$WITH_CMAKE" -eq 1 ]]; then find_expr+=( -o -name "*.cmake" ) fi declare -A seen_files=() while IFS= read -r -d '' file; do real_file="$file" if [[ -L "$file" ]]; then target=$(readlink "$file") if [[ "$target" != /* ]]; then dir="$(cd "$(dirname "$file")" && pwd -P)" || continue target="$dir/$target" fi [[ -L "$target" ]] && continue [[ ! -f "$target" ]] && continue real_file="$target" fi [[ -n "${seen_files[$real_file]+x}" ]] && continue seen_files[$real_file]=1 mode="$(detect_mode "$real_file")" [[ -n "$mode" ]] && process_file "$mode" "$real_file" done < <(find "$TARGET_DIR" \( -type f -o -type l \) \( "${find_expr[@]}" \) -print0)