Adding an :ID: property to Org-mode files in directory
I wanted to add an :ID: property to several hundred files, but only if one didn't already exist. I cheated and asked ChatGPT for help. It helped.
This is a bit esoteric, but I'm writing it down here anyway.
I have switched between using Denote and Org-roam for my Org-mode notes several times. They are mostly compatible, so this hasn't been too troublesome.
One thing I needed to do was make sure that all the .org files included an :ID: property at the top so that Org-roam includes them in its database. The ID property looks like this:
:PROPERTIES:
:ID: 4def7046-3ef8-4436-a079-09362bf66aff
:END:
Many of my Denote files already include this because I like to drag and drop files and images into the notes, and the ID property makes this work correctly. But there were maybe 300 files without an ID, and I had no desire to add the property manually to every one of them.
I started to write a shell script to do this, but got hung up on some bit of syntax, so I searched for the error and found the fix. Then I had trouble with the sed
command, etc.
Sometimes I forget that ChatGPT exists. Sometimes I ignore it because I want to learn stuff on my own. But sometimes I just want the answer, and, problematic as LLMs can be, there's no denying their usefulness.
A couple of prompts, a few iterations, and I had what I needed in about 10 minutes.
#!/bin/bash
# Directory containing the Org-mode files
ORG_DIR="${1:-.}" # Defaults to current directory if not provided
# Loop through all .org files in the specified directory
for file in "$ORG_DIR"/*.org; do
# Skip if no Org-mode files are found
[ -e "$file" ] || continue
# Check if the :ID: property exists in the first 5 lines
if ! head -n 5 "$file" | grep -q "^:ID:"; then
# Generate a unique ID (UUID)
id=$(uuidgen)
# Create the :PROPERTIES: block with the :ID:
properties_block=":PROPERTIES:\n:ID: $id\n:END:"
# Insert the :PROPERTIES: block at the very top of the file
gsed -i "1i $properties_block" "$file"
echo "Added :ID: $id inside a :PROPERTIES: block to $file"
else
echo ":ID: already exists in the first 5 lines of $file"
fi
done
The only change I needed to make was to substitute gsed
for sed
because the macOS version of sed was missing a switch and throwing an error.
While I was in there, I asked ChatGPT for a version of the script in Emacs lisp, just in case. I had it write the function for use in a Dired buffer, since that's where I'm most likely to want it. Here's the function...
(defun add-id-to-org-files-in-dired ()
"Add :ID: property inside a :PROPERTIES: block at the top of all Org files in the current Dired buffer."
(interactive)
;;(require 'uuidgen) ;; Ensure `uuidgen` is available
(dired-map-dired-file-lines
(lambda (file)
(when (and (string-suffix-p ".org" file t) (file-exists-p file))
(with-temp-buffer
(insert-file-contents file)
;; Check the first 5 lines for an existing :ID: property
(unless (save-excursion
(goto-char (point-min))
(search-forward ":ID:" (line-end-position 5) t))
;; Insert the :PROPERTIES: block at the top of the file
(goto-char (point-min))
(insert ":PROPERTIES:\n:ID: " (org-id-uuid) "\n:END:\n\n")
(write-region (point-min) (point-max) file))
(message "Added :ID: to %s" file))))))
Done and done. There may be better, cleaner ways to do this, but my problem is solved, which is all I wanted.