I've always loved text files. they are quick. easy. you can toss `em anywhere. AND it's universal. anyways, I've accumulated thousands upon thousands of files over the years.
... I've started organizing them.
kinda like all those notion and obsidian guys have been talking about for years with their 'second brain' stuff.. whelp, I've baught in. wait, I did that years ago, but I have started pulling all my notes into one space. Instead of in archives from old devices or backups I've done years ago that weren't carried over in an organized fashion. The last few plus years of notes I have organized ish as thats when I started backing them up online after one of them "ooops, where did I put those files" situations.. Anyways... Working on notes, its a lot.
one thing that helps in this age of AI, is naming files with ai generated content. Example, I have my own words and thoughts exist at buddhism.md, where AI content would exist at buddhism.ai.md. other subjects within buddhism can be pulled out extra into like buddhism.eightfold.md for notes specific to the eightfold path in general. etc.. but using .ai. to help keep your words from the words of AI.
It helps to also have a 'reference' section of your notes for all thoughts not your own.
My notes folder has also now diverged, in a sense. I now have a public and private folder within my notes folder. The private notes exist in a dotfolder /notes/.private, everything else is 'public'. That, with .gitignore to push everything public to a git repo makes sharing my notes on things much easier.
also have a set of scripts journal.sh and zetlmd.sh, the journal script you place in your .private folder, it creates a file in ./daily_journal with things 3 tasks for the day, a todo list, and habit tracker. the zetlmd.sh is a bit more... odd. ish. it takes notes. they have a title, content, and tags. each note when saved is placed in 'notes/zettswith a filename like1761016752629.md` which is just a markdown file timestamped, it also created tag files.. which is where some fun magic happens.
---
# another note
tags: [demo]
link: ./zetts/1761016930096.md
content: |
this is another note.
---
# first
tags: [demo]
link: ./zetts/1761016752629.md
content: |
words
I use the system to quickly hop into a topics tag file, look at the latest notes on that topic and am able to quickly go into that note, or just yolo it and add additional notes within that tag file, or whatever... whatever feels right to you.. ^>^
I suppose I could also share the scripts here...
journal.sh
#!/bin/bash
# This script creates a new daily journal entry by carrying over unfinished
# tasks and updating a habit tracker from the most recent entry or a template.
# --- Configuration ---
NOTES_DIR="$(dirname "$0")"
JOURNAL_DIR="$NOTES_DIR/daily_journal"
TODAY=$(date +"%Y-%m-%d")
NEW_FILE_PATH="$JOURNAL_DIR/$TODAY.md"
# --- Inline Default Template ---
# This variable is used if no previous journal entry is found.
# the habit tracker works by tossing a & in front of them when completed
# the number following a habit is your current streak
read -r -d '' DEFAULT_TEMPLATE <<'EOF'
## Todays Tasks
1. do today
## TODO
- ~~ task completed~~
- pick up a space
- take the thing to the place
# habit tracker
&water
exercise
meditate
&read (12)
EOF
# --- Setup ---
# Create the daily_journal directory if it doesn't already exist.
mkdir -p "$JOURNAL_DIR"
# --- Pre-flight Check ---
# If a journal for today already exists, just open it.
if [ -f "$NEW_FILE_PATH" ]; then
echo "Journal for $TODAY already exists. Opening..."
helix "$NEW_FILE_PATH"
exit 0
fi
# --- Function to Process Content ---
# This function contains the logic to parse tasks, todos, and habits.
# It can read from a file via redirection (<) or a variable via here-string (<<<).
process_content() {
current_section=""
while IFS= read -r line; do
# Determine which section we are in
if [[ "$line" == "## Todays Tasks" ]]; then
current_section="tasks"
continue
elif [[ "$line" == "## TODO" ]]; then
current_section="todo"
continue
elif [[ "$line" == "# habit tracker" ]]; then
current_section="habits"
continue
fi
# Process the line based on the current section
case "$current_section" in
"tasks")
if [[ -n "$line" && ! "$line" =~ ~~ ]]; then
TASKS_CONTENT+="$line\n"
fi
;;
"todo")
if [[ -n "$line" && ! "$line" =~ ~~ ]]; then
TODO_CONTENT+="$line\n"
fi
;;
"habits")
if [[ -n "$line" ]]; then
trimmed_line=$(echo -e "${line}" | sed -e 's/^[[:space:]]*//')
if [[ "$trimmed_line" == "&"* ]]; then
habit_text=$(echo "$trimmed_line" | sed 's/^&//')
if [[ "$habit_text" =~ \(([0-9]+)\) ]]; then
streak_num=${BASH_REMATCH[1]}
((streak_num++))
base_habit=$(echo "$habit_text" | sed 's/\s*([0-9]\+)\s*//')
new_habit=$(echo "$base_habit ($streak_num)" | sed 's/^\s*//;s/\s*$//')
else
base_habit=$(echo "$habit_text" | sed 's/^\s*//;s/\s*$//')
new_habit="$base_habit (1)"
fi
HABITS_CONTENT+="$new_habit\n"
else
base_habit=$(echo "$line" | sed 's/\s*([0-9]\+)\s*//')
habit=$(echo "$base_habit" | sed 's/^\s*//;s/\s*$//')
HABITS_CONTENT+="$habit\n"
fi
fi
;;
esac
done
}
# --- Determine Which Source to Process ---
# Find the latest .md file in the journal directory.
LATEST_FILE=$(ls -1 "$JOURNAL_DIR" | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}\.md$' | sort -r | head -n 1)
# Initialize Content Variables
TASKS_CONTENT=""
TODO_CONTENT=""
HABITS_CONTENT=""
if [ -n "$LATEST_FILE" ]; then
echo "Found latest journal entry: $LATEST_FILE. Processing..."
process_content < "$JOURNAL_DIR/$LATEST_FILE" # Read from the latest file
else
echo "No previous journal found. Using default inline template."
process_content <<< "$DEFAULT_TEMPLATE" # Read from the template variable
fi
# --- Write the New Journal File ---
{
printf "## Todays Tasks\n"
printf "%b" "$TASKS_CONTENT"
printf "\n"
printf "## TODO\n"
printf "%b" "$TODO_CONTENT"
printf "\n"
printf "# habit tracker\n"
printf "%b" "$HABITS_CONTENT"
} > "$NEW_FILE_PATH"
echo "Successfully created new journal: $NEW_FILE_PATH"
# --- Open File in helix ---
helix "$NEW_FILE_PATH"
Start it by first doing a chmod +x ./journal.sh then ./journal.sh anytime you want to pop it open to work on it throughout the day. I keep mine in my Notes/.private/ folder and use a alias to open it myself.
The habit tracker works by tossing an & in front of them when completed,
the number following a habit is your current streak.
additionally, the LAST LINE needs to be edited to your favorite editor. I use helix.
zetlmd.sh
#!/bin/bash
# --- Configuration ---
NOTES_DIR="notes"
ZETTS_DIR="$NOTES_DIR/zetts"
# --- Utility Functions ---
# Function to get current timestamp for filename
get_timestamp() {
date +%s%3N
}
# --- Core Note Management Functions ---
# Function to create a tag file
create_tag_file() {
local tag_name="$1"
if [ ! -f "$NOTES_DIR/$tag_name.md" ]; then
touch "$NOTES_DIR/$tag_name.md"
echo "Created tag file: $tag_name.md" # Re-enable for CLI clarity
fi
}
# Function to prepend content to tag file
prepend_content_to_tag_file() {
local tag_name="$1"
local title="$2"
local formatted_tags="$3"
local note_full_content="$4"
local filename="$5"
local link_path="./zetts/$filename"
local existing_content=""
if [ -f "$NOTES_DIR/$tag_name.md" ]; then
existing_content=$(cat "$NOTES_DIR/$tag_name.md")
fi
# Indent the note content so it can be correctly placed in a YAML literal block.
# Using printf to echo the content to sed is safer than using echo.
local indented_content
indented_content=$(printf '%s' "$note_full_content" | sed 's/^/ /')
# Overwrite the tag file with the new entry in YAML multi-document format.
# Each note is a separate YAML document starting with ---
printf -- "---\n# %s\ntags: %s\nlink: %s\ncontent: |\n%s\n%s" \
"$title" \
"$formatted_tags" \
"$link_path" \
"$indented_content" \
"$existing_content" > "$NOTES_DIR/$tag_name.md"
}
# Function to update all tag files after a note is created
update_tag_files_for_note() {
local filename="$1"
# The 'action' parameter is no longer used since we only "add" notes,
# but we'll leave it in the function signature for now.
local action="$2"
if [ "$action" = "add" ]; then
# Read the new note's content to get title, tags, and full content
local note_path="$ZETTS_DIR/$filename"
local title=$(grep -m 1 "^title:" "$note_path" | sed 's/^title: //')
local tags_str=$(grep -m 1 "^tags:" "$note_path" | sed 's/^tags: //')
local full_content=$(awk 'BEGIN{c=0} /^---$/{c++; next} c==2' "$note_path")
# Split the tags string into an array
IFS=',' read -ra tag_array <<< "${tags_str:1:-1}"
for tag in "${tag_array[@]}"; do
# Trim whitespace from the tag
tag=$(echo "$tag" | xargs)
if [[ ! -z "$tag" ]]; then
create_tag_file "$tag"
prepend_content_to_tag_file "$tag" "$title" "$tags_str" "$full_content" "$filename"
fi
done
fi
}
# Function to format tags
format_tags() {
local tags_raw=$(echo "$1" | tr -cd '[:alnum:] ' | tr '[:upper:]' '[:lower:]')
local formatted_tags=$(echo "$tags_raw" | sed 's/ /,/g')
echo "[$formatted_tags]"
}
# --- Main Operations ---
# Create a new note (this is now the default behavior)
create_note() {
read -p "Enter Title: " title
if [ -z "$title" ]; then title="Untitled Note"; fi
echo "Enter Content (Press Ctrl+D on a new line to finish):"
content=$(cat) # Reads multi-line input until Ctrl+D
read -p "Enter Tags (space-separated): " tags_input
local formatted_tags=$(format_tags "$tags_input")
local filename=$(get_timestamp).md
local formatted_content=$(echo "$content" | fold -s -w 72) # Still folds for main note readability
mkdir -p "$ZETTS_DIR"
# Create YAML headline and write to file
echo -e "---\ntitle: $title\ntags: $formatted_tags\n---\n$formatted_content\n" > "$ZETTS_DIR/$filename"
echo "Zett created: $ZETTS_DIR/$filename"
# Update tag files
update_tag_files_for_note "$filename" "add"
echo "Content inserted into tag files."
}
# --- Help Function ---
show_help() {
echo "Usage: zetlmd.sh [command]
Commands:
(default) Create a new note (interactive CLI input) - just run './zetlmd.sh'
help Show this help message
Description:
zetlmd.sh is a simple command-line Zettelkasten-inspired note-taking system.
It stores notes in markdown files with YAML front matter and manages tag files
to provide an overview of notes per tag.
Note format:
---
title: Your Note Title
tags: [tag1,tag2,another_tag]
---
Your note content goes here.
You can link to other notes using [Note Title]( ./zetts/filename.md )
"
}
# --- Main Script Logic ---
mkdir -p "$ZETTS_DIR" # Ensure directories exist
# If no arguments are provided, default to creating a new note
if [ -z "$1" ]; then
create_note
exit 0
fi
case "$1" in
"help" | "-h" | "--help")
show_help
;;
*)
echo "Invalid command: '$1'."
show_help # Show help on invalid command
exit 1
;;
esac
exit 0
This is the note input system, same as the other script with chmod and launching, but instead of opening a daily journal in helix, it asks for a title, some content, and tags and tells ya how to use it. that's it.
how you browse and edit those files varies by person to person, but these work for me for 2 small corners of my greater notes folder. specifically .private for the journal and fleeting for zetlmd.sh. Although using fleeting is probably the wrong name for this specific capture tool,... it works for me. I also have a series of folders laid out like this:
01_Inbox
02_Projects
03_Life_Management
04_Technology
05_Interests_and_Hobbies
06_Reference
use it as a source of inspiration if ya like, change the folder names around, do things, experiment... have fun.. but yea, thanks for listening to my side tracked ted talk mid shitpost about how I was bored sorting through thousands of random notes scattered to the 7 winds across many devices and folders and archives and....
DeClutter your digital life
cleaning up helps your mental health, just like drinking water and exercise. ;-)
07_Archive