Created
July 19, 2025 03:24
-
-
Save appendjeff/43a6c975af06068eb16f4adc4e10d4a6 to your computer and use it in GitHub Desktop.
Batch apply tags to obsidian files under a folder.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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