Backing up Bear Blog posts

Making a local markdown file backup with images and metadata

In the bearblog settings there is an ‘Export all blog data’ which provides a csv dump of all posts and metadata, which is lovely, but I wanted a markdown file version with any images also backed up locally, because it’s always the images that end up lost or broken!

So I put together a bash script to run on the csv that makes a local backup:

I haven’t automated the csv download itself, I’m happy to just do this now and then by hand.

(change OUTDIR to where you want it to go. ~/Sync is backed up with syncthing for me, as well as being in my nightly backups)

 1#!/usr/bin/env bash
 2set -euo pipefail
 3
 4CSV="$1"
 5OUTDIR=~/Sync/writing/bear-blog-backup-$(date +%Y-%m-%d)
 6mkdir -p "$OUTDIR"
 7cd "$OUTDIR"
 8
 9# strip BOM, python convert to json
10json=$(python3 -c 'import csv, json, sys; print(json.dumps(list(csv.DictReader(open(sys.argv[1])))))' "$CSV")
11
12echo "$json" | jq -c '.[]' | while read -r row; do
13	slug=$(echo "$row" | jq -r '.["canonical url"] // empty')
14	[ -z "$slug" ] && slug=$(echo "$row" | jq -r '.slug')
15	file="${slug}.md"
16	[ "$file" = ".md" ] && file="unnamed-$(date +%s).md"
17	echo "Backing up $file"
18
19	{
20		while IFS= read -r key; do
21			val=$(echo "$row" | jq -c -r --arg k "$key" '.[$k]')
22			[ "$val" = "" ] && continue
23			clean_key=$(echo "$key" | tr ' ' '_' | tr '[:upper:]' '[:lower:]')
24			# flatten arrays
25			if [[ $val == \[*\] ]]; then
26				val=$(echo "$val" | jq -r '. | join(", ")')
27			fi
28			echo "${clean_key}: ${val}"
29		done < <(echo "$row" | jq -r 'keys[]' | grep -v '^content$')
30
31		echo
32		echo '---'
33		echo
34		echo "$row" | jq -r '.content'
35	} >"$file"
36
37        sed -i 's/^\xEF\xBB\xBF//' "$file"
38
39	# replace image URLs + download
40	while read -r img; do
41		url=$(echo "$img" | grep -oE 'https[^)]+')
42		fname=$(basename "${url%%\?*}")
43		sed -i "s|$url|./$fname|g" "$file"
44		curl -sL "$url" -o "$fname"
45	done < <(grep -oE '!\[[^]]*\]\(https[^)]+\)' "$file")
46done
47
48tar -czf "${OUTDIR}.tar.gz" -C "$(dirname "$OUTDIR")" "$(basename "$OUTDIR")"
49
50echo "Backup complete: $OUTDIR.tar.gz"

And here is what it captures from this recent post

 1all_tags: other
 2first_published_at: 2025-10-02T19:36:00+00:00
 3is_page: False
 4make_discoverable: True
 5meta_description: "But you like her ironically, right?"
 6publish: True
 7published_date: 2025-10-02T19:36:00+00:00
 8slug: taytay
 9title: Taylor Swift and the joy of liking things
10uid: hdXSmQIbyUohKfrFfRYV
11
12---
13
14On the eve of Taylor's next album release, I am reminded how nice it is to like things. Something to look forward to! What fun. How delightful. No downsides.
15
16Growing up I thought liking things was dangerous. It made me vulnerable to attack. It was safer to be cynical, to hedge all preferences with criticisms. Give myself some breathing room in case my opinion reflected badly on me. Certainly don't let someone see my bookshelf.
17
18But a few years ago I listened to some Taylor Swift and liked it, and the extreme uncoolness of liking Taylor Swift meant there was no hedging to be done. It was either like her sincerely, or reject her fully.[^1]
19
20![90,000 screaming fans](img/tay.webp)
21
22So I like her unapologetically. People try to give me an out sometimes, "but you like her ironically, right?" my boss once asked me. Nah. I just like her. "You like her physically, you mean?" Nah. I like her music.
23
24I found myself leaning into it a little. Listening to her more and more. It is now a thing people know about me. Max likes Taylor Swift. Odd, but wholesome. A positive thing.
25
26And I want to keep it that way. I don't want to be a fan. I don't read the discourse, I don't try to defend her on the internet. I'm a secular swifty. I won't be reading any reviews of the new album. I'm going to listen to it, and have my own opinions. Maybe listen to it again a few days later, maybe change my mind. Who knows.
27
28I will enjoy it without considering what that means about me. And that is nice.
29
30[^1]: (Also, I'm older now, and care less.)