Mac에서 작성한 Obsidian 문서를 윈도나 웹에서 열어보면 간혹 깨진 문자가 섞여있다.
Mac 화면에선 보이지 않고 나머지 시스템에선 보인다.

 <- Mac이 아닌 시스템에서만 보임

이 문자의 정체가 뭘까. JS inspector에 붙여넣어보니

Uncaught SyntaxError: illegal character U+0008

U+0008BACKSPACE였다.

해결을 위한 시도들

어쩌면 내가 macOS에서 한영전환을 특이하게 해서 그럴 수도 있다. 기본적으로 CAPS Lock 키도 한영전환용으로 쓰면서, 거기에 더해 Karabiner Elements를 이용해서, 우측 Opt 키를 다른 조합 없이 눌렀다 뗄 때 한영전환이 되도록 설정했는데 어쩌면 그게 원인일 수도 있다.
아무튼, 투명 깨진 문자가 생겼을 때 Mac에서는 화면에 보이지 않기 때문에 수습이 필요하다.

옵시디언 플러그인을 사용하면 깨진 문자가 보이는가?

  • Obsidian에 Control Character라는 플러그인을 찾아 설치해봤다. 여전히 문제의 0x08 문자는 화면 출력이 안된다. 이 플러그인 설치 후 한 가지 문제가 발생했는데, 스페이스 다음의 첫 한글은 작은 점 안에 표시된다. 삭제.
  • Show Whitespace 플러그인 역시 0x08은 표시해주지 못한다. 삭제.
  • 이 플러그인들은 공백, 탭, 개행 문자에 특화되어있는 듯 하다. 둘 다 삭제했다.

커밋 시점에 수정?

Quartz sync시 이상한 현상에서와 마찬가지로 git hook을 이용해 커밋 이벤트에 특수문자를 삭제해는 접근이다.

  • tr, awk, sed는 UTF-8 대응이 완전하지 않아서 제외. perl을 사용함.
  • iconv -c 사용해봤으나 0x08 문자 사라지지 않음.

ChatGPT 시켜서 만든 스크립트:

#!/bin/bash
 
remove_control_chars() {
  local file="$1"
  local lockfile="$file.lock"
  local dry_run=$2
 
  # 동시 처리를 방지하기 위해 잠금 파일을 생성 시도
  if ! mkdir "$lockfile" 2>/dev/null; then
    echo "File is already being processed: $file"
    return
  fi
 
  # 스크립트가 실패하더라도 잠금 파일과 임시 파일을 제거하도록 보장
  trap 'rm -rf "$lockfile"; rm -f "$file.tmp" "$file.tmp.cleaned"' EXIT
 
  # 임시 파일을 생성하고 iconv를 사용하여 잘못된 UTF-8 문자를 제거
  iconv -f UTF-8 -t UTF-8 -c "$file" > "$file.tmp" 2>/dev/null || { echo "Error processing $file"; exit 1; }
 
  # Perl을 사용하여 특정 제어 문자(예: 백스페이스 0x08)를 안전하게 제거
  perl -CSD -pe 's/\x08//g' "$file.tmp" > "$file.tmp.cleaned" || { echo "Error removing control characters from $file"; exit 1; }
 
  # 파일이 수정된 경우 업데이트하고 수정된 파일 이름을 출력
  if ! cmp -s "$file" "$file.tmp.cleaned"; then
    if [ "$dry_run" = true ]; then
      echo "[Dry-Run] Updated Markdown file: $file"
    else
      echo "Updated Markdown file: $file"
      cp "$file.tmp.cleaned" "$file"
    fi
  fi
 
  # 초기 임시 파일 제거
  rm -f "$file.tmp.cleaned" "$file.tmp"
 
  # 잠금 해제
  rm -rf "$lockfile"
}
 
# dry-run 플래그에 대한 인수 파싱
dry_run=false
while [[ "$1" == --* ]]; do
  case "$1" in
    --dry-run)
      dry_run=true
      ;;
  esac
  shift
done
 
# find 명령을 사용하여 모든 .md 파일을 재귀적으로 읽고 각 파일에 대해 remove_control_chars 호출
find ./content -type f -name "*.md" -print0 | while IFS= read -r -d '' file; do
  remove_control_chars "$file" $dry_run
done

위 스크립트를 pre-commit 스크립트에 넣어볼 예정.