Zettelkasten Forum

Converting filename tags to note-body hashtags

Hi all. I am trying The Archive, and quite enjoying it so far after a long time using nvAlt.

I've been using codes in my note filenames to roughly categorize them. Files beginning with !qt are quotes, while !lg are log entries of various kinds, !cn is for notes from calls or conversations, and so on. I also have some one-letter hashtags that provide another layer of categorization (#f for financial, for example).

However, these turn out to not be as useful as I expected. Also, they make the rest of the filenames (note title + a date/time stamp) less readable. After reading some of the discussion here, I've decided to convert these to #hashtags within the note body text, whether I stick with nvAlt or switch to The Archive indefinitely.

Any suggestions for doing this in bulk? I have about 1,400 notes so far, so I'd rather not do it manually. Solutions using Python, KeyboardMaestro or AppleScript preferred. Javascript would be OK too, with a sufficiently step-by-step guide -- I don't know JS, but I can blunder my way through a tutorial, for example.

I don't really need to change the filenames -- I can leave that as-is, but I'd like to apply hashtags to notes based on the codes in the filenames.



  • Hmm, shouldn't be too hard -- but that depends on the content of your notes!

    The nvALT tag converter I wrote doesn't try to be clever about finding a location. I suggest you start with a simple script that reads the file tags and simple appends the #hashtags at the end of the file. You can always place them elsewhere later. Fixing things as you go proved to be a suitable tactic for most changes to my notes so far. I never suffered from not converting all the things into a new format I came up with.

    If your notes are in ~/Documents/Notes, create a Ruby file in ~/Documents/tag_convert.rb:

    #!/usr/bin/env ruby
    CURRENT_PATH = File.expand_path(File.dirname(__FILE__))
    NOTE_PATH = File.join(CURRENT_PATH, "Notes")
    EXT = ".txt"
    TAG_MAP = {
      "#f" => "financial",
      "!lg" => "log" 
      # add rest here
    Dir.glob(File.join(SOURCE_PATH, "*#{EXT}")) do |path|
      # get rid of everything but the file's name:
      # SomePrefix_foo.txt  -> foo
      name = File.basename(path, EXT)[/(?<=_)(.*)/] 
      puts "Converting #{name}..."
      tags = []
      TAG_MAP.keys.each do |key|
        # Pretty basic matcher, will match `!lg` in `XX!lgXX`, 
        # may need to adjust for word matching:
        if name.include?(key)
          tags << TAG_MAP[key]
      # Append concatenated list of tags to file
      open(path, 'a') do |file|
        file.puts ""
        file.puts tags.map { |t| "#" + t }.join(" ")
        file.puts ""

    Author at Zettelkasten.de • https://christiantietze.de/

  • Thanks! I cobbled together a python script that did more or less the same thing -- maybe not quite as elegantly. In case it's useful for others, here's what I did:

    import os
    import time
    import datetime
    tag_dict = {
        '!bk' : '#backup',
        '!cn' : '#contact',
        '!dp' : '#prose',
        '!id' : '#idea',
        '!lg' : '#log',
        '!pr' : '#prose',
        '!qt' : '#quote',
        '!rf' : '#reference',
        '!rl' : '#running_list',
        '!rp' : '#recipe',
        '!tc' : '#prose',
        '!tl' : '#tasklist',
        '!tx' : '#prose',
    path = "/Users/username/Dropbox/Notes"
    filenames = os.listdir(path)
    exts = []
    prefixes = []
    for filename in filenames:
        ext = filename.split(".")[-1]
        if ext in ['txt', 'md']:
            prefix = filename[:3]
            if prefix not in prefixes:
            if prefix in tag_dict:
                nu_tag = tag_dict[prefix]
                print("{} gets tag {}".format(filename, nu_tag))
                with open('{}/{}'.format(path, filename), 'a') as f:
                fileStats = os.stat(f'{path}/{filename}')
                create_time = fileStats.st_birthtime
                os.utime(f'{path}/{filename}', (create_time, create_time))

    As I often do with one-time scripts, I built it out in stages. I used the three lines beginning prefix = filename[:3] to create the keys for tag_dict, which I then inserted earlier in the script, assigning new tags.

    I added the last section (beginning with the first reference to fileStats) because I realized too late that appending all those tags immediately changed the file modification date for every note that I altered. That makes sense, of course, but I like to keep my notes in rough chronological order, so I tried to set the file modification dates to the file creation date (since I only rarely edit a note once I write it).

    Unfortunately, it turns out that the Mac keeps separate file-modification metadata that's used in the Finder (and in The Archive); my script doesn't alter that. I wound up using a simple utility (File Date Changer Version 5) from the Mac App Store to do it for me.

Sign In or Register to comment.