---
title: "링크 감사 스크립트가 표 안에서만 거짓 양성을 내는 이유, 이스케이프된 파이프"
description: "위키링크 감사에서 멀쩡한 문서가 깨졌다고 나왔는데 전부 마크다운 표 안의 링크였습니다. 표 셀의 이스케이프된 파이프가 정규식의 문서명 캡처에 백슬래시를 끼워 넣은 것이 원인이었습니다."
date: 2026-11-10
updated: 2026-11-10T03:43:52.139Z
tags: [markdown, regex, tooling, debugging]
canonical: https://blog.wooncloud.com/posts/link-audit-escaped-pipe-false-positive
---

문서 저장소의 위키링크를 전수 스캔해서 깨진 링크를 찾는 스크립트를 돌렸더니, 이런 항목이 올라왔습니다.

```text
1x  [[01. 화면 구성 분석\]]
1x  [[02. 재생 동작 분석\]]
2x  [[후보 목록\]]
```

그런데 `01. 화면 구성 분석.md` 는 바로 옆 폴더에 멀쩡히 있었습니다. 앱에서 링크를 눌러 봐도 잘 열립니다. 스크립트만 깨졌다고 말하고 있었어요.

공통점은 하나였습니다. 전부 마크다운 표 안에 있는 링크였습니다.

![같은 링크라도 표 셀 안에서는 이스케이프된 바이트가 붙어, 같은 파일을 가리켜도 스캐너만 깨진 것으로 집계한다.](/images/posts/link-audit-escaped-pipe-false-positive/ede8064e-dcd0-4060-b10d-d2cd73dbeb9c.webp)

## 표 안의 링크는 저장된 바이트가 다릅니다

위키링크의 별칭 문법은 파이프를 씁니다.

```text
[[실제 문서명|화면에 보일 텍스트]]
```

그런데 마크다운 표에서 파이프는 셀 구분자입니다. 그대로 쓰면 셀이 하나 더 생겨서 행이 깨지니, 표 안에서는 이스케이프해야 합니다.

```text
| 항목 | 근거 |
|---|---|
| 화면 정리 | [[01. 화면 구성 분석\|가능, 회귀 테스트 선행]] |
```

같은 링크가 표 밖에서는 `|`, 표 안에서는 `\|` 로 저장되는 겁니다. 렌더러는 셀을 파싱하면서 `\|` 를 `|` 로 되돌린 뒤에 위키링크를 해석하니, 사람 눈에는 아무 문제가 없습니다.

## 정규식은 왜 백슬래시를 먹을까요?

감사 스크립트의 정규식은 보통 이렇게 생겼습니다.

```python
LINK = re.compile(r'\[\[([^\]\n|#]+?)(?:\|[^\]\n]*)?(?:#[^\]\n]*)?\]\]')
```

문서명 캡처인 `[^\]\n|#]` 는 파이프를 만나면 멈춥니다. 그런데 표 안의 링크는 파이프 앞에 백슬래시가 먼저 옵니다. 백슬래시는 제외 대상이 아니라서 문서명의 일부로 캡처되고, 그 다음 `|` 에서 별칭 분기로 넘어갑니다.

그래서 스크립트가 찾는 이름은 `01. 화면 구성 분석` 이 아니라 `01. 화면 구성 분석\` 이 됩니다. 그런 파일이 있을 리 없죠.

![표 안의 링크는 파이프 앞에 백슬래시가 붙어 정규식이 문서명 끝에 백슬래시를 캡처하므로 없는 파일로 집계되고, 파싱 전에 이스케이프를 벗기면 올바른 문서명이 캡처된다.](/images/posts/link-audit-escaped-pipe-false-positive/0cb3df90-f92b-4025-8cf0-9eebcf984b30.svg)

## 왜 하필 표에서만 틀릴까요?

거짓 양성이 표 안에서만 나오니까 눈치채기가 어렵습니다.

같은 문서를 가리키는 링크가 본문에도 있으면 그쪽은 정상으로 잡힙니다. 한 문서가 "있음"과 "없음"으로 동시에 집계되는 거죠. 리포트를 보고 파일을 열어 확인해도 재현이 안 됩니다.

게다가 표는 요약, 인덱스 문서에 몰려 있습니다. 링크 밀도가 가장 높은 문서에서 집중적으로 틀리는 셈입니다. 깨진 링크 수를 지표처럼 추적하고 있었다면, 표를 많이 쓴 날 이유 없이 나빠졌을 겁니다.

## 파싱 전에 이스케이프부터 벗깁니다

링크 문법을 파싱하기 전 단계에서 처리하는 게 안전합니다.

```python
# 표 셀 이스케이프를 먼저 정규화한 뒤 링크를 파싱한다
text = text.replace('\\|', '|')

for m in LINK.finditer(text):
    target = m.group(1).strip()
```

이미 나온 결과를 사후에 보정해야 한다면, 후행 백슬래시를 떼고 다시 판정해서 오탐을 걸러냅니다.

```python
if target.endswith('\\') and resolve(target[:-1]):
    continue  # 표 안 이스케이프에서 온 거짓 양성
```

## 다른 곳에서도 생기는 문제입니다

컨테이너 문법이 자식 문법의 바이트를 바꾸는 곳이면 어디서나 같은 일이 생깁니다.

표 셀 안의 인라인 코드도 `|` 를 이스케이프해야 하고, CSV 필드 안의 따옴표는 `""` 로 이중화됩니다. YAML 블록 스칼라 안에 넣은 정규식이나 셸 히어독 안의 변수 표기도 비슷합니다.

문서를 데이터로 스캔할 때는 렌더러가 보는 문자열과 파일에 저장된 바이트가 다르다고 전제해야겠습니다. 화면에서는 정상인데 스캐너만 실패한다면, 그 사이에 이스케이프 계층이 하나 더 있을 가능성이 큽니다.

## 참고

- https://spec.commonmark.org/0.31.2/#backslash-escapes
- https://github.github.com/gfm/#tables-extension-
