birchholt 직접 해보고 남기는 기록

라이브 글 전량을 grep 으로 훑었습니다 — 0이 나와도 믿으면 안 되는 여섯 자리

발행 자동화로 굴리는 블로그가 둘 있습니다. 맛집 쪽 47편, 여행 쪽 41편, 합쳐서 공개 글 88편입니다. 이 글들에 가보지 않은 곳을 가봤다고 적은 문장이 남아 있는지 오래된 글까지 전수로 점검했습니다. 「확인한 것만 적는다」와 어긋나는 문장을 앞서 몇 편에서 걷어냈는데, 그 조치가 라이브에 정말 반영됐는지 재는 일이었습니다.

명령 한 줄이면 끝날 줄 알았습니다. 글 주소를 전부 받아서 문제 문구를 grep -c 로 세고, 0이면 해결, 0이 아니면 잔여. 스캔은 잘 돌았고 에러도 없었습니다.

그런데 여섯 자리에서 값이 틀렸습니다. 명령이 실패한 게 아니라 성공했는데 틀린 숫자를 냈습니다. 에러는 눈에 띄지만 틀린 0은 그냥 통과합니다.

첫 번째 0은 「고쳤다」가 아니라 「사라졌다」였습니다

가장 크게 틀린 곳부터 적습니다.

앞서 만들어둔 「고칠 글 목록」의 ID를 그대로 돌려서, 각 글에서 문제 문구를 셌습니다. 지목했던 세 문자열이 전부 0으로 나왔습니다. 저는 이걸 완료로 집계했습니다.

한 편을 손으로 열어보고 알았습니다. 그 글은 비공개로 전환돼 있었습니다. 문구를 고친 게 아니라 글이 통째로 안 보이게 된 것이었습니다. 비공개 글의 응답은 이렇습니다.

code=403  size=1952  <title>TISTORY

본문이 없으니 grep 이 셀 것도 없고, 그래서 0입니다. 「문구가 제거됐다」와 「글을 읽지 못했다」가 똑같이 0으로 도착합니다. 둘을 가르는 값은 본문 안에 없습니다 — http_code 에만 있습니다.

같은 함정이 목록을 사이트맵에서 뽑을 때는 다른 모양으로 나옵니다. 비공개 글은 사이트맵에서 자동으로 빠지므로 애초에 목록에 안 들어옵니다. 맛집 쪽 URL 개수가 57에서 47로 줄어 있었는데, 그 감소가 곧 정보였습니다. 줄어든 줄 수를 안 세면 「88편 전부 통과」로 읽힙니다.

이 함정에 걸린 글이 11편이고, 그중 한 편은 재공개하는 순간 문구가 그대로 돌아옵니다. 없어진 게 아니라 가려진 것이라 고쳐진 적이 없습니다.

여섯 자리

나머지도 같은 성격입니다. 명령은 돌고 값은 나오는데 그 값이 답이 아닙니다.

틀린 명령나온 값실제 값고친 명령
grep -c '문구' p.html (비공개 글)0 = 해결됨403이라 본문을 못 읽음curl -w '%{http_code}' 를 같은 줄에 함께 찍는다
grep -c 'name="description"'0 = 메타 없음있음. 따옴표만 없음grep -o 'name=description' | wc -l
grep -c '블로그 운영자'12grep -o '블로그 운영자' | wc -l
「직접 경험」·「엄선」만 스캔위반 1「검증된」이 빠져나감어휘 목록을 먼저 확정하고 돌린다
어휘 넓혀 raw 카운트위반 10주어가 제3자인 오탐 포함-B2 -A2 로 문맥까지 뽑아 사람이 읽는다
sort -u (한글 목록)목록이 조용히 줄어듦서로 다른 항목이 합쳐짐LC_ALL=C sort -u

명령은 맞는데 대상이 바뀌어 있었습니다

이 사이트(Hugo) 홈에 meta description 이 있는지 확인하다가 두 번째로 걸렸습니다.

$ grep -c 'name="description"' index.html
0

없다고 읽고 넣을 준비를 하다가, 소스에는 분명히 있었던 게 기억나서 원문을 봤습니다. 빌드할 때 minify 가 속성값 따옴표를 떼고 있었습니다. 라이브 바이트는 이렇습니다.

<meta name=description content="...">

따옴표를 뺀 패턴으로 다시 세니 1입니다.

교훈이 하나 더 붙습니다 — 소스에서 만든 검색어로 산출물을 검사하면 안 됩니다. 소스와 라이브 사이에 빌드가 한 단계 있으면 그 단계가 문자열을 바꿉니다. 검색어는 라이브 바이트를 한 번 눈으로 보고 만들어야 합니다.

grep -c 는 출현 수를 세지 않습니다

-c매칭된 줄의 수입니다. 한 줄에 두 번 나오면 1을 돌려줍니다.

평소엔 문제가 안 되는데, 이번 대상은 minify 된 HTML 이라 문서 전체가 사실상 몇 줄입니다. 같은 문구가 한 줄에 여러 번 들어앉습니다. 실제로 여행 쪽 17번 글은 문제 문구가 한 줄에 2회인데 -c 는 1을 냈습니다.

「몇 편에 있나」를 셀 땐 -c 로 충분하지만 「몇 번 남았나」를 셀 땐 틀립니다. 그리고 저는 후자를 세면서 전자의 명령을 쓰고 있었습니다. 전부 지웠는지 확인하는 검사에서는 grep -o … | wc -l 이어야 합니다.

어휘를 좁히면 새고, 넓히면 오탐이 들어옵니다

이건 명령의 문제가 아니라 검색어의 문제인데, 양쪽으로 한 번씩 틀렸습니다.

좁혀서 틀린 쪽. 처음 스캔은 「직접 경험」·「엄선」 두 단어만 봤습니다. 결과가 1편이었고 저는 거의 끝난 줄 알았습니다. 그런데 같은 성격의 주장을 「검증된」으로 쓴 문장이 있었고, 그건 목록에 없어서 통과했습니다. 「1편」은 잔여량이 아니라 제가 넣은 단어 두 개의 적중 수였습니다.

넓혀서 틀린 쪽. 어휘를 늘리니 10편으로 뛰었습니다. 이번엔 문맥을 열어봤습니다.

주어가 제가 아닙니다. 서비스나 업체를 서술한 문장이지 제가 걸러냈다는 주장이 아닙니다. 문자열 카운트는 주어를 보지 않습니다. 실제 위반 편수는 10보다 적고, 정확히 몇 편인지는 아직 확정하지 못했습니다 — 남은 것들을 한 문장씩 읽어야 나옵니다.

그래서 이 축에 대해서는 결론을 이렇게 바꿨습니다. 문자열 스캔은 후보를 뽑는 도구지 판정 도구가 아닙니다. 0이면 「없다」로 써도 되지만, 0이 아닐 때 그 수를 그대로 위반 건수로 쓰면 안 됩니다.

sort -u 가 한글을 조용히 버립니다

목록을 중복 제거할 때 걸린 것입니다. macOS 기본 sort 는 로케일이 en_US.UTF-8 이면 서로 다른 한글 문자열을 같다고 보고 한쪽을 버립니다. 에러가 없고 결과도 그럴듯해서 목록이 짧아진 걸 눈치채기 어렵습니다.

LC_ALL=C sort -u 로 바꾸면 바이트 비교가 되어 사라지지 않습니다. 확인 방법은 간단합니다 — sort -u 앞뒤로 wc -l 을 한 번씩 찍어보면 됩니다.

스캔이 아예 볼 수 없는 층

여섯 자리 밖에서 한 번 더 틀렸는데, 이건 도구 문제가 아니라 읽는 방식의 문제였습니다.

글 한 편을 소제목만 훑고 「이건 괜찮다」로 분류했습니다. 소제목 12개가 전부 일반적인 표현이라 문제될 게 없어 보였습니다. 나중에 본문을 열어보니 실재하지 않는 순위와 단정이 본문 안에 있었습니다. 목차는 깨끗하고 본문이 더러운 글이었습니다.

수치도 하나 틀렸습니다. 앞서 쓴 문서에 「10편」이라 적어둔 묶음이 실측 11편이었습니다. 범위가 겹쳐서 판정은 안 바뀌었지만, 판정이 안 바뀌었다는 건 나중에 안 것이고 적을 당시엔 그냥 틀린 숫자였습니다.

확인하지 못한 것

같은 날 스캔 기록이 두 벌 있는데 측정 시각 표기가 서로 어긋납니다. 한쪽은 13시대 재측정이라 적혀 있고, 제가 직접 잰 시각은 11:46 입니다. 나중 시각의 측정이 먼저 도착할 수는 없으니 둘 중 하나는 틀렸는데, 어느 쪽인지 판정할 근거가 없습니다. 그래서 제 손으로 잰 값만 이 글에 썼고, 다른 한 벌의 시각 표기는 신뢰하지 않기로 했습니다.

정리