Skip to content

Instantly share code, notes, and snippets.

@appendjeff
Created July 19, 2025 03:24
Show Gist options
  • Select an option

  • Save appendjeff/43a6c975af06068eb16f4adc4e10d4a6 to your computer and use it in GitHub Desktop.

Select an option

Save appendjeff/43a6c975af06068eb16f4adc4e10d4a6 to your computer and use it in GitHub Desktop.
Batch apply tags to obsidian files under a folder.
#!/bin/bash
# ./obsidian_tagger.sh -t example-tag /path/to/your/obsidian/folder/
# ./obsidian_tagger.sh /path/to/your/obsidian/folder
# Configuration
TAG_TO_ADD="backpack"
BACKUP_SUFFIX=".backup"
# Function to display usage
usage() {
echo "Usage: $0 [OPTIONS] <directory>"
echo ""
echo "Options:"
echo " -t, --tag TAG Specify tag to add (default: backpack)"
echo " -b, --backup Create backup files before editing"
echo " -r, --recursive Process subdirectories recursively"
echo " -h, --help Show this help message"
echo ""
echo "Example:"
echo " $0 /path/to/obsidian/vault/folder"
echo " $0 -t important -b -r /path/to/obsidian/vault/folder"
}
# Function to add tag to a file
add_tag_to_file() {
local file="$1"
local tag="$2"
local create_backup="$3"
# Create backup if requested
if [ "$create_backup" = true ]; then
cp "$file" "${file}${BACKUP_SUFFIX}"
fi
# Check if file has YAML frontmatter
if head -n 1 "$file" | grep -q "^---$"; then
# File has frontmatter
local temp_file=$(mktemp)
local in_frontmatter=false
local frontmatter_end=false
local tags_section_exists=false
local tag_added=false
local line_number=0
while IFS= read -r line || [ -n "$line" ]; do
line_number=$((line_number + 1))
if [ "$in_frontmatter" = false ] && [ "$line" = "---" ]; then
# Start of frontmatter
in_frontmatter=true
echo "$line" >> "$temp_file"
elif [ "$in_frontmatter" = true ] && [ "$frontmatter_end" = false ] && [ "$line" = "---" ]; then
# End of frontmatter - add tags section if needed
if [ "$tags_section_exists" = false ] && [ "$tag_added" = false ]; then
echo "tags:" >> "$temp_file"
echo " - $tag" >> "$temp_file"
tag_added=true
fi
echo "$line" >> "$temp_file"
frontmatter_end=true
in_frontmatter=false
elif [ "$in_frontmatter" = true ]; then
# Inside frontmatter
if echo "$line" | grep -q "^tags:"; then
tags_section_exists=true
# Check if this is a single-line tags format
if echo "$line" | grep -q "\[.*\]"; then
# Single-line array format like "tags: [tag1, tag2]" or "tags: []"
if echo "$line" | grep -q "\\b$tag\\b"; then
# Tag already exists
tag_added=true
echo "$line" >> "$temp_file"
else
# Add tag to array
if echo "$line" | grep -q "tags: *\[\]"; then
# Empty array
modified_line=$(echo "$line" | sed "s/\[\]/[$tag]/")
elif echo "$line" | grep -q "tags: *\["; then
# Non-empty array
modified_line=$(echo "$line" | sed "s/\]/, $tag]/")
else
# Fallback
modified_line="$line"
fi
echo "$modified_line" >> "$temp_file"
tag_added=true
fi
elif echo "$line" | grep -q "tags: *$"; then
# Multi-line format starting (empty tags: line)
echo "$line" >> "$temp_file"
else
# Single value format like "tags: sometag"
if echo "$line" | grep -q "tags: *$tag *$"; then
# Tag already exists as single value
tag_added=true
echo "$line" >> "$temp_file"
else
# Convert to multi-line format
existing_tag=$(echo "$line" | sed 's/tags: *//')
echo "tags:" >> "$temp_file"
echo " - $existing_tag" >> "$temp_file"
echo " - $tag" >> "$temp_file"
tag_added=true
fi
fi
elif [ "$tags_section_exists" = true ] && echo "$line" | grep -q "^ - "; then
# Multi-line tags format item
tag_name=$(echo "$line" | sed 's/^ - *//')
if [ "$tag_name" = "$tag" ]; then
tag_added=true
fi
echo "$line" >> "$temp_file"
elif [ "$tags_section_exists" = true ] && [ "$tag_added" = false ] && \
! echo "$line" | grep -q "^ - " && \
! echo "$line" | grep -q "^[[:space:]]*$" && \
! echo "$line" | grep -q "^#"; then
# We've reached the end of tags section (new YAML key or content)
echo " - $tag" >> "$temp_file"
echo "$line" >> "$temp_file"
tag_added=true
else
echo "$line" >> "$temp_file"
fi
else
# Outside frontmatter
echo "$line" >> "$temp_file"
fi
done < "$file"
# Handle edge case: tags section exists but we hit end of file without adding tag
if [ "$tags_section_exists" = true ] && [ "$tag_added" = false ]; then
echo " - $tag" >> "$temp_file"
fi
mv "$temp_file" "$file"
else
# File doesn't have frontmatter, add it
local temp_file=$(mktemp)
echo "---" > "$temp_file"
echo "tags:" >> "$temp_file"
echo " - $tag" >> "$temp_file"
echo "---" >> "$temp_file"
echo "" >> "$temp_file"
cat "$file" >> "$temp_file"
mv "$temp_file" "$file"
fi
}
# Parse command line arguments
CREATE_BACKUP=false
RECURSIVE=false
while [[ $# -gt 0 ]]; do
case $1 in
-t|--tag)
TAG_TO_ADD="$2"
shift 2
;;
-b|--backup)
CREATE_BACKUP=true
shift
;;
-r|--recursive)
RECURSIVE=true
shift
;;
-h|--help)
usage
exit 0
;;
-*)
echo "Unknown option $1"
usage
exit 1
;;
*)
DIRECTORY="$1"
shift
;;
esac
done
# Validate arguments
if [ -z "$DIRECTORY" ]; then
echo "Error: Directory not specified"
usage
exit 1
fi
if [ ! -d "$DIRECTORY" ]; then
echo "Error: Directory '$DIRECTORY' does not exist"
exit 1
fi
# Find markdown files
if [ "$RECURSIVE" = true ]; then
FIND_CMD="find \"$DIRECTORY\" -name \"*.md\" -type f"
else
FIND_CMD="find \"$DIRECTORY\" -maxdepth 1 -name \"*.md\" -type f"
fi
# Count files to process
FILE_COUNT=$(eval $FIND_CMD | wc -l)
if [ "$FILE_COUNT" -eq 0 ]; then
echo "No markdown files found in '$DIRECTORY'"
exit 0
fi
echo "Found $FILE_COUNT markdown files to process"
echo "Adding tag: $TAG_TO_ADD"
if [ "$CREATE_BACKUP" = true ]; then
echo "Backup files will be created with suffix: $BACKUP_SUFFIX"
fi
echo ""
# Process files
PROCESSED=0
eval $FIND_CMD | while read -r file; do
echo "Processing: $(basename "$file")"
add_tag_to_file "$file" "$TAG_TO_ADD" "$CREATE_BACKUP"
PROCESSED=$((PROCESSED + 1))
done
echo ""
echo "Completed! Processed $FILE_COUNT files."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment