노트 이름을 바꿀 때 링크가 깨지지 않게 하는 법

노트 파일 이름 하나를 바꿨을 뿐인데 다른 노트에서 그 노트를 가리키던 링크가 조용히 죽는다. 클릭해 보기 전까지는 아무 티도 안 난다. 옵시디언 같은 노트 볼트를 오래 쓰다 보면 언젠가 이름 짓는 규칙을 새로 정하게 되는데, 그 규칙을 정한 시점 이전에 쌓인 파일들은 그 규칙을 몰랐던 채로 남아 있다. 규칙을 소급 적용하려고 옛 이름을 손대는 순간, 그 이름을 참조하던 다른 노트들이 줄줄이 흔들린다.
나는 이 문제를 두 번 겪었다. 한 번은 파일 이름 규칙을 뒤늦게 정리할 때였고, 다른 한 번은 폴더 하나를 상위 주제 아래로 옮길 때였다. 둘 다 이름이나 경로 하나만 바뀌었을 뿐인데 그 경로를 참조하는 지점이 볼트 곳곳에 흩어져 있었다. 둘 다 자동 치환만으로는 안 끝났고, 사람이 손으로 확인하는 단계를 하나 더 넣고 나서야 마무리됐다.
규칙을 나중에 도입하면 옛 이름이 위반으로 남는다
노트 이름 규칙은 보통 볼트가 커진 뒤에 만들어진다. 처음 몇 달은 되는대로 이름을 짓다가, 파일이 쌓이고 나서야 "이런 식으로는 안 되겠다" 싶어서 규칙을 세운다. 문제는 그 규칙이 미래의 파일에만 적용된다는 점이다. 규칙을 세운 날짜 이전에 만든 파일은 그 규칙을 지킬 이유가 없었으니, 어쩌다 맞아떨어지지 않는 한 안 지킨다.
이 상태를 그냥 두면 볼트 안에 두 가지 이름 짓기 방식이 섞여 산다. 최근 파일은 짧고 일관된 이름을 쓰고, 오래된 파일은 문장을 그대로 옮긴 듯한 긴 이름을 쓴다. 검색할 때마다 어느 쪽 방식으로 찾아야 할지 헷갈리고, 폴더를 열어 보면 이름 길이부터 들쭉날쭉하다. 그래서 규칙을 소급 적용하기로 했다. 수백 개 파일이 쌓인 폴더 하나를 골라 전수 재검토에 들어갔다.
여기서부터가 진짜 문제다. 이름을 바꾸는 순간 그 파일을 가리키던 다른 노트의 링크는 옛 이름을 그대로 붙든 채로 남는다. 위키링크든 마크다운 링크든, 파일 이름이 곧 참조 값이기 때문이다. 파일 하나의 이름을 축약하면 그 이름을 참조 값으로 쓴 자리 — 링크와 frontmatter — 가 그 순간 깨진다. 볼트가 클수록 그 자리가 어디인지 손으로 세기 어려워진다. 그래서 이름을 바꾸기 전에 먼저 정해야 할 게 있다. 무엇을 바꾸고 무엇을 그대로 둘지 가르는 기준이다.
위반 판정 기준부터 세운다
전수 재검토를 이름부터 손대며 시작하면 같은 판단을 파일마다 다시 내리게 된다. 판정 기준 없이 눈에 거슬리는 이름부터 고치기 시작하면, 어디까지가 고칠 대상이고 어디부터는 놔둬도 되는지 매번 다시 판단해야 한다. 그래서 먼저 위반이 무엇인지부터 문장으로 정의했다.
위반으로 본 것은 세 가지다. 첫째, 문장형 표현이다. 이름이 제목이 아니라 문장처럼 늘어져 있는 경우다. 둘째, 중복어다. 같은 뜻의 단어가 이름 안에 두 번 들어간 경우다. 셋째, 워크플로 상태어다. "검증대기"나 "미회상"처럼 그 파일이 지금 어떤 처리 단계에 있는지를 이름에 박아 둔 경우다. 상태는 바뀌는데 이름은 안 바뀌니 시간이 지나면 이름 자체가 거짓말이 된다. 예를 들어 수학1_지수함수_개념정리_검증대기.md라면 "검증대기"라는 상태어가 이름에 굳어 있는 대표적인 위반이다. 검증이 끝나도 이름은 그대로 "검증대기"로 남는다.
예를 들면 이렇다. "지수함수와 로그함수의 개념과 예제와 유제를 정리한 노트.md"처럼 조사와 어미까지 그대로 남은 이름은 문장형 표현 위반이다. "프로젝트기획_아이디어_구상_아이디어정리.md"처럼 "아이디어"라는 단어가 이름 안에서 두 번 겹치면 중복어 위반이다. 두 경우 다 제목이 아니라 문장이나 메모를 그대로 옮겨 붙인 흔적이라, 축약해도 정보가 사라지지 않는다.
반대로 위반이 아니라고 정한 것도 뚜렷하게 갈랐다. 번호 접두어는 그대로 유지한다. 01_, 수학1_처럼 순서나 소속을 나타내는 접두어는 정렬과 탐색에 쓰이므로 손대지 않는다. 단어 나열형 이름도 위반으로 보지 않는다. 여러 주제어를 그냥 이어 붙인 이름은 길어 보여도 문장이 아니라 색인에 가깝다. 길다는 이유만으로 줄이면 오히려 그 이름이 담고 있던 정보가 사라진다. 폴더 구조, 그러니까 파일이 어느 폴더에 속하는지는 이번 판정에서 아예 건드리지 않기로 했다. 이름 규칙과 폴더 구조는 서로 다른 층위의 문제라서 한 번에 섞으면 판단이 흐려진다.
이 기준으로 폴더 하나를 전수 훑으니 축약 대상은 열 건 남짓으로 좁혀졌다. 폴더 이름은 대체로 짧아서 손댈 곳이 없었다. 기준을 먼저 세워 두니 판단이 매번 흔들리지 않아 재검토 자체는 오래 걸리지 않았다.
이름은 mv로, 링크는 리터럴 치환으로
위반 목록이 정해지면 이름을 바꾼다. 방법은 특별할 것 없이 일반적인 이동, 즉 mv다. 파일 내용은 손대지 않고 이름만 바뀐다. 확장자는 .md 그대로 손대지 않는다. 여기까지는 쉽다. 어려운 건 그다음이다. 이름이 바뀐 파일을 참조하던 다른 노트들도 같이 고쳐야 한다.
이때 고른 방법이 볼트 전체에서 옛 파일 이름의 stem(확장자를 뗀 순수 이름)을 새 stem으로 바꾸는 리터럴 문자열 치환이다. 정규식이 아니라 리터럴, 즉 있는 그대로의 문자열을 찾아 그대로 바꾸는 방식을 골랐다. 정규식은 강력한 대신 패턴을 살짝만 잘못 짜도 엉뚱한 곳을 건드리거나 걸려야 할 곳을 놓친다. 리터럴 치환도 위험이 아예 없지는 않지만 그 위험은 예측 가능한 범위에 갇힌다. 옛 stem이 짧거나 흔한 문자열이면 관련 없는 본문 문장이나 다른 파일 이름의 일부와 겹쳐 잘못 걸릴 수 있다. 그래서 치환하기 전에 옛 stem으로 먼저 검색해 몇 군데에 걸리는지 세어 보고, 걸린 자리가 파일명이나 링크가 아니라 흘러가는 문장 속이면 일괄 치환 대신 손으로 하나씩 확인했다. 그 개수는 하나를 더 알려준다 — 같은 stem을 쓰는 파일이 다른 폴더에 또 있는지다. 있다면 볼트 전체 일괄 치환은 위험하다. 그 다른 파일을 가리키던 멀쩡한 링크까지 새 이름으로 같이 바뀌어 버리므로, 이럴 때는 이름이 아니라 링크가 실제로 가리키는 폴더 경로로 대상을 좁혀 이번에 옮긴 파일과 일치하는 참조만 골라 바꿨다.
이 한 가지 방식이 세 종류의 참조를 한 번에 덮는다. 위키링크([[옛이름]]), 마크다운 링크([텍스트](옛이름.md)), 그리고 frontmatter의 aliases나 title 필드까지, 파일 이름이 등장하는 자리라면 표기 방식과 상관없이 같은 문자열이 그대로 박혀 있다. 다만 aliases나 title은 항상 파일 이름을 그대로 담고 있지는 않다. 사람이 붙인 표시용 제목일 때가 많아서 실제로 옛 파일 이름과 똑같은 문자열일 때만 바꾸고, 옛 이름으로도 계속 찾아지게 하려고 일부러 남겨 둔 alias는 그대로 둔다. 그 나머지는 문자열 하나만 정확히 바꾸면 세 형식을 따로따로 처리할 필요가 없다. 형식별로 파서를 따로 짜는 것보다 훨씬 단순하고, 단순한 만큼 실수할 자리도 줄었다.
다만 리터럴 치환에는 전제가 하나 붙는다. 참조하는 쪽이 정확히 같은 문자열을 써야 한다는 것이다. 파일 이름을 그대로 안 쓰고 말로 풀어 쓴 경로 표기 같은 건 이 방식이 못 잡는다.
이동 전후를 네 번 확인한다
이름을 바꾸고 링크를 고쳤다고 끝난 게 아니다. 검증 없이 다음 파일로 넘어가면 문제는 한참 뒤, 원인을 되짚기 어려운 시점에 드러난다. 그래서 이동한 파일마다 네 가지를 확인하는 걸 절차로 굳혔다.
첫째는 무손실 확인이다. 이동 전 파일과 이동 후 파일의 줄 수부터 비교하지만, 이건 제일 값싸게 보는 1차 확인일 뿐 증거는 아니다. 줄 수가 같아도 한 줄의 내용이 바뀌거나 인코딩이 바뀌거나 줄 끝 공백이 사라지는 경우는 줄 수만으로 못 잡는다. 그래서 이동 전후 파일을 해시(바이트 단위 비교)로 대조해서 내용이 정말 같은지 확정한다. 줄 수가 어긋나면 그 자리에서 바로 이상 신호로 보고, 해시까지 일치해야 무손실로 판정한다. 둘째는 경로 확인이다. 옛 경로에는 파일이 더는 존재하지 않아야 하고, 새 경로에는 파일이 존재해야 한다. 당연해 보이지만 스크립트로 일괄 처리하다 보면 이 당연한 것도 어긋날 때가 있다. 셋째는 잔여 참조 확인이다. 볼트 전체를 다시 스캔하되, 세는 대상은 옛 stem이 등장하는 모든 자리가 아니라 위키링크 대상·마크다운 링크 대상·frontmatter의 aliases나 title처럼 참조 값으로 쓰인 자리로 좁힌다. 리터럴 치환이 제대로 됐다면 이 값은 0이어야 하고, 일부러 손대지 않은 본문 속 언급은 따로 목록으로 뽑아 하나씩 의도된 것인지 확인한다. 그러니 0이라는 건 "이 문자열이 볼트에서 통째로 사라졌다"가 아니라 "의도치 않은 참조가 안 남았다"는 뜻이다. 넷째는 링크 파서로 하는 확인이다. 위키링크와 마크다운 링크를 실제로 해석해서 그 링크가 가리키는 대상이 진짜로 존재하는지, 즉 깨진 링크가 몇 개인지를 센다.
넷째 확인에서 "깨졌다"의 기준도 분명히 해 뒀다. 링크가 가리키는 파일이 볼트 안에 없으면 깨진 것으로 세고, 외부 URL이나 같은 문서 안의 앵커 링크는 검사 대상에서 뺀다.
네 가지를 다 통과하면 이름 변경 자체는 안전하다고 본다. 실제로 첫 번째 사례, 그러니까 볼트 하나 안에서 이름 열 건 남짓을 축약한 작업에서는 이 네 가지 확인이 전부 깨끗하게 통과했다. 줄 수와 해시 모두 이동 전후로 전부 일치했고, 옛 경로는 전부 사라졌으며 새 경로는 전부 존재했다. 재스캔 결과 옛 이름의 잔여 참조는 0건이었다. 여기까지만 보면 검증이 끝난 것처럼 보인다.
첫 검토가 놓친 것, 두 번째 검토가 잡은 것
그런데 다른 종류의 작업에서 이 낙관이 깨졌다. 파일 이름이 아니라 폴더 하나를 상위 주제 아래로 옮기는 작업이었다. 폴더를 옮기고 리터럴 경로 치환까지 끝냈고, 위에서 말한 네 가지 확인도 통과했다. 그런데 첫 검토 패스가 세 가지를 놓쳤다. 두 번째로 다시 훑고 나서야 발견됐다.
첫째, breadcrumb 형태로 적힌 상위 경로 표기가 문제였다. 어떤 노트에는 "지금 이 노트가 어디에 속하는지"를 사람이 손으로 문장처럼 적어 둔 부분이 있었는데, 그 표기가 리터럴 치환이 찾던 정확한 패턴과 미묘하게 어긋나 있었다. 자동 치환은 정확히 같은 문자열만 잡으니, 형태가 살짝 다른 이 표기는 그대로 옛 경로를 붙든 채 남았고 결국 손으로 고쳐야 했다. 둘째, 인덱스 파일이 지켜야 할 관례를 어겼다. 이 볼트에는 폴더의 인덱스 파일이 그 폴더의 허브 파일로 링크해야 한다는 관례가 있는데, 폴더를 옮기는 과정에서 그 방향이 어긋나 있었다. 셋째, breadcrumb에 표시되는 제목이 실제 구조와 안 맞았다. 폴더는 이미 새 위치로 옮겨졌는데 화면에 보이는 이름표는 옛 구조를 그대로 반영하고 있었다.
세 가지 다 공통점이 있다. 리터럴 치환이 잡는 대상은 파일 이름이라는 정확한 문자열이지만, 이 셋은 그 문자열을 그대로 쓰지 않고 사람이 문장처럼 풀어 쓴 표기였다. 자동화가 덮는 범위와 사람 손으로 쓴 표기는 서로 다른 층위에 있다. 하나가 깨끗하다고 다른 하나도 깨끗하다는 보장은 없다. 이 사실을 몰랐다면 "잔여 참조 0건"만 믿고 다음 작업으로 넘어갔을 것이다.
그래서 절차 자체를 고쳤다. 리터럴 치환과 기계적 검증(해시·경로·잔여 참조·링크 파서)이 끝나도 그걸로 검증이 끝났다고 선언하지 않는다. 그 다음에 별도의 두 번째 검토 패스를 둔다. 이번에는 문자열을 기계적으로 훑는 게 아니라, 사람이 손으로 쓴 표기가 남아 있을 만한 자리 — breadcrumb, 인덱스와 허브 사이의 링크 방향, 화면에 보이는 제목 — 를 따로 눈으로 짚는다. 실제로 이 두 번째 패스에서 세 가지 문제가 전부 잡혔고, 그 뒤에야 작업을 마무리로 처리했다.
두 번째 검토는 사람이 직접 훑어야 하니 첫 번째 기계적 검증보다 손이 더 간다. 그래도 생략하지 않는 이유는 분명하다. 기계적 검증 네 가지는 전부 초록불이었는데도 실제로는 세 군데가 깨져 있었다. 초록불과 실제 상태가 다를 수 있다는 걸 한 번 겪고 나니, 두 번째 패스를 건너뛸 이유가 없어졌다. 자동 치환은 문자열을 믿고, 두 번째 검토는 사람이 쓴 문장을 믿는다. 이 둘은 서로 대신하지 못한다.
지금까지 순서를 표 하나로 정리하면 이렇다.
| 단계 | 확인 대상 |
|---|---|
| 위반 판정 | 문장형 표현·중복어·워크플로 상태어만 축약, 번호 접두어·단어 나열형·폴더 구조는 유지 |
| 이름 변경 | 일반 mv로 이동, 내용은 손대지 않음 |
| 링크 치환 | 옛 stem → 새 stem 리터럴 문자열 치환(정규식 아님, stem이 볼트에서 고유할 때만 일괄 적용), 위키링크·마크다운 링크·frontmatter 동시 커버 |
| 기계적 검증 | 해시(바이트 단위) 비교로 무손실 확정(줄 수는 1차 확인), 옛 경로 부재·새 경로 존재, 볼트 재스캔 잔여 참조, 링크 파서 broken 카운트 |
| 두 번째 검토 | breadcrumb 표기, 인덱스→허브 링크 방향, 화면 표시 제목처럼 사람이 손으로 쓴 자리 |
이름을 바꾸는 일 자체는 옵시디언 같은 노트 볼트가 계속 커지는 한 끝나지 않는다. 볼트가 자랄 때마다 새 규칙이나 구조 변경이 나타나고, 그때마다 같은 절차를 다시 밟는다. 비슷한 노트 관리 기록은 라이프 카테고리에 모아 둔다. 이름을 언제 바꿀지가 아니라 어떤 메모를 언제 wiki로 승격시킬지는 판단 기준이 아예 다른 문제라, 그 기준은 wiki 승격 판단 기준에서 따로 다뤘다. 남은 숙제는 두 번째 검토 패스를 사람이 눈으로 훑는 대신 자동으로 잡아낼 방법을 찾는 것이다. breadcrumb 표기나 인덱스-허브 링크 방향처럼 정형화된 자리는 별도 검사 스크립트로 옮길 여지가 있어 보인다.
자주 묻는 질문
정규식 대신 리터럴 문자열 치환을 쓰는 이유는?
정규식은 패턴을 잘못 짜면 엉뚱한 곳을 건드리거나 걸려야 할 곳을 놓치는 위험이 있다. 리터럴 치환은 그 위험을 없애지만, 옛 stem이 짧거나 흔한 문자열이면 관련 없는 본문 문장이나 다른 파일 이름의 일부와 겹쳐 잘못 걸릴 수 있어 치환 전에 몇 군데가 걸리는지 먼저 세어 보고 흘러가는 문장 속 자리는 손으로 확인한다. 위키링크·마크다운 링크·frontmatter는 한 방식으로 동시에 덮는다.
번호 접두어가 붙은 긴 이름이나 단어 나열형 이름도 축약해야 하나?
아니다. 번호 접두어는 정렬과 탐색에 쓰이므로 그대로 유지하고, 여러 주제어를 이어 붙인 단어 나열형 이름은 길어도 문장이 아니라 색인에 가까워 위반으로 보지 않는다. 축약 대상은 문장형 표현·중복어·워크플로 상태어가 들어간 이름으로 한정한다.
리터럴 치환 뒤 잔여 참조가 0건이면 검증이 끝난 걸로 볼 수 있나?
아니다. 잔여 참조 0건은 파일 이름과 정확히 같은 문자열을 쓴 참조에만 해당한다. breadcrumb처럼 사람이 손으로 문장처럼 풀어 쓴 경로 표기는 리터럴 패턴과 어긋나면 치환에서 빠지므로, 기계적 검증과 별도로 사람이 눈으로 훑는 두 번째 검토 패스를 둬야 한다.
이름을 바꿀 때 폴더 구조도 같이 손봐야 하나?
아니다. 이름 규칙 판정과 폴더 구조는 다른 층위의 문제라서 한 번에 섞으면 판단이 흐려진다. 파일 이름 위반을 판정할 때는 소속 폴더를 그대로 두고, 폴더 구조를 바꿀 필요가 있으면 별도 작업으로 분리해 같은 절차(리터럴 치환·기계적 검증·두 번째 검토)를 다시 적용한다.
아직 댓글이 없습니다.