I have an old markdown repo that I’ve imported to Obsidian. There are hundreds of files and thousands of internal links. My internal links use headings to link to sections within files. When the heading contains spaces, they are replaced in the link by hyphens. Obsidian can’t make sense of them because it wants %s instead of a hyphen. I would like to convert them all to the correct format.
They look like this: [Obsidian Overview](#obsidian-overview) when linking to a heading in the same file that looks like this: # Obsidian Overview.
How can I convert all internal links, throughout the whole vault, into valid links? Either wikilinks or markdown links would be fine, but I need to either change the hyphens to %s or change them to spaces and encase the whole thing in angled brackets.
Things I have tried
I’ve tried the Link Converter plugin, but it just changes [Obsidian Overview](#obsidian-overview) into [[#obsidian-overview|Obsidian Overview]] which still doesn’t work, because that assumes the heading itself looks like # obsidian-overview, which it doesn’t (it has no hyphen).
In the end I managed to fix this by using the following python script. To use this script, change the path at the bottom of the file to be the root of the vault you’re working on. If you comment out the three lines after if new_content != content: and uncomment the two print statements after that, you can test the output.
import os
import re
def deslugify(slug):
return slug.replace('-', ' ')
def process_file(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Find and replace markdown-style links
# e.g. [Some Header](#some-header) → [Some Header](<#some header>)
new_content = re.sub(
r'\[([^\]]+)\]\(#([a-z0-9\-]+)\)',
lambda m: f'[{m.group(1)}](<#{deslugify(m.group(2))}>)',
content,
flags=re.IGNORECASE
)
if new_content != content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"Updated: {file_path}")
# print(f"Would update: {file_path}")
# print(new_content)
def process_directory(directory):
for root, _, files in os.walk(directory):
for filename in files:
if filename.endswith('.md'):
process_file(os.path.join(root, filename))
# 🟡 Change this to your folder path
target_directory = '/path/to/vault/root'
process_directory(target_directory)
Save this script as fix_links.py and then run it on the command line using python fix_links.py (or python3 fix_links.py depending on your Python installation).