Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Pattern documentation for configuring settings in Claude Code plugins (from the official Anthropic claude-code repo).
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/parsing-techniques.md
1# Settings File Parsing Techniques23Complete guide to parsing `.claude/plugin-name.local.md` files in bash scripts.45## File Structure67Settings files use markdown with YAML frontmatter:89```markdown10---11field1: value112field2: "value with spaces"13numeric_field: 4214boolean_field: true15list_field: ["item1", "item2", "item3"]16---1718# Markdown Content1920This body content can be extracted separately.21It's useful for prompts, documentation, or additional context.22```2324## Parsing Frontmatter2526### Extract Frontmatter Block2728```bash29#!/bin/bash30FILE=".claude/my-plugin.local.md"3132# Extract everything between --- markers (excluding the markers themselves)33FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")34```3536**How it works:**37- `sed -n` - Suppress automatic printing38- `/^---$/,/^---$/` - Range from first `---` to second `---`39- `{ /^---$/d; p; }` - Delete the `---` lines, print everything else4041### Extract Individual Fields4243**String fields:**44```bash45# Simple value46VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//')4748# Quoted value (removes surrounding quotes)49VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/')50```5152**Boolean fields:**53```bash54ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')5556# Use in condition57if [[ "$ENABLED" == "true" ]]; then58# Enabled59fi60```6162**Numeric fields:**63```bash64MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')6566# Validate it's a number67if [[ "$MAX" =~ ^[0-9]+$ ]]; then68# Use in numeric comparison69if [[ $MAX -gt 100 ]]; then70# Too large71fi72fi73```7475**List fields (simple):**76```bash77# YAML: list: ["item1", "item2", "item3"]78LIST=$(echo "$FRONTMATTER" | grep '^list:' | sed 's/list: *//')79# Result: ["item1", "item2", "item3"]8081# For simple checks:82if [[ "$LIST" == *"item1"* ]]; then83# List contains item184fi85```8687**List fields (proper parsing with jq):**88```bash89# For proper list handling, use yq or convert to JSON90# This requires yq to be installed (brew install yq)9192# Extract list as JSON array93LIST=$(echo "$FRONTMATTER" | yq -o json '.list' 2>/dev/null)9495# Iterate over items96echo "$LIST" | jq -r '.[]' | while read -r item; do97echo "Processing: $item"98done99```100101## Parsing Markdown Body102103### Extract Body Content104105```bash106#!/bin/bash107FILE=".claude/my-plugin.local.md"108109# Extract everything after the closing ---110# Counts --- markers: first is opening, second is closing, everything after is body111BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")112```113114**How it works:**115- `/^---$/` - Match `---` lines116- `{i++; next}` - Increment counter and skip the `---` line117- `i>=2` - Print all lines after second `---`118119**Handles edge case:** If `---` appears in the markdown body, it still works because we only count the first two `---` at the start.120121### Use Body as Prompt122123```bash124# Extract body125PROMPT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE")126127# Feed back to Claude128echo '{"decision": "block", "reason": "'"$PROMPT"'"}' | jq .129```130131**Important:** Use `jq -n --arg` for safer JSON construction with user content:132133```bash134PROMPT=$(awk '/^---$/{i++; next} i>=2' "$FILE")135136# Safe JSON construction137jq -n --arg prompt "$PROMPT" '{138"decision": "block",139"reason": $prompt140}'141```142143## Common Parsing Patterns144145### Pattern: Field with Default146147```bash148VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/')149150# Use default if empty151if [[ -z "$VALUE" ]]; then152VALUE="default_value"153fi154```155156### Pattern: Optional Field157158```bash159OPTIONAL=$(echo "$FRONTMATTER" | grep '^optional_field:' | sed 's/optional_field: *//' | sed 's/^"\(.*\)"$/\1/')160161# Only use if present162if [[ -n "$OPTIONAL" ]] && [[ "$OPTIONAL" != "null" ]]; then163# Field is set, use it164echo "Optional field: $OPTIONAL"165fi166```167168### Pattern: Multiple Fields at Once169170```bash171# Parse all fields in one pass172while IFS=': ' read -r key value; do173# Remove quotes if present174value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/')175176case "$key" in177enabled)178ENABLED="$value"179;;180mode)181MODE="$value"182;;183max_size)184MAX_SIZE="$value"185;;186esac187done <<< "$FRONTMATTER"188```189190## Updating Settings Files191192### Atomic Updates193194Always use temp file + atomic move to prevent corruption:195196```bash197#!/bin/bash198FILE=".claude/my-plugin.local.md"199NEW_VALUE="updated_value"200201# Create temp file202TEMP_FILE="${FILE}.tmp.$$"203204# Update field using sed205sed "s/^field_name: .*/field_name: $NEW_VALUE/" "$FILE" > "$TEMP_FILE"206207# Atomic replace208mv "$TEMP_FILE" "$FILE"209```210211### Update Single Field212213```bash214# Increment iteration counter215CURRENT=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')216NEXT=$((CURRENT + 1))217218# Update file219TEMP_FILE="${FILE}.tmp.$$"220sed "s/^iteration: .*/iteration: $NEXT/" "$FILE" > "$TEMP_FILE"221mv "$TEMP_FILE" "$FILE"222```223224### Update Multiple Fields225226```bash227# Update several fields at once228TEMP_FILE="${FILE}.tmp.$$"229230sed -e "s/^iteration: .*/iteration: $NEXT_ITERATION/" \231-e "s/^pr_number: .*/pr_number: $PR_NUMBER/" \232-e "s/^status: .*/status: $NEW_STATUS/" \233"$FILE" > "$TEMP_FILE"234235mv "$TEMP_FILE" "$FILE"236```237238## Validation Techniques239240### Validate File Exists and Is Readable241242```bash243FILE=".claude/my-plugin.local.md"244245if [[ ! -f "$FILE" ]]; then246echo "Settings file not found" >&2247exit 1248fi249250if [[ ! -r "$FILE" ]]; then251echo "Settings file not readable" >&2252exit 1253fi254```255256### Validate Frontmatter Structure257258```bash259# Count --- markers (should be exactly 2 at start)260MARKER_COUNT=$(grep -c '^---$' "$FILE" 2>/dev/null || echo "0")261262if [[ $MARKER_COUNT -lt 2 ]]; then263echo "Invalid settings file: missing frontmatter markers" >&2264exit 1265fi266```267268### Validate Field Values269270```bash271MODE=$(echo "$FRONTMATTER" | grep '^mode:' | sed 's/mode: *//')272273case "$MODE" in274strict|standard|lenient)275# Valid mode276;;277*)278echo "Invalid mode: $MODE (must be strict, standard, or lenient)" >&2279exit 1280;;281esac282```283284### Validate Numeric Ranges285286```bash287MAX_SIZE=$(echo "$FRONTMATTER" | grep '^max_size:' | sed 's/max_size: *//')288289if ! [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then290echo "max_size must be a number" >&2291exit 1292fi293294if [[ $MAX_SIZE -lt 1 ]] || [[ $MAX_SIZE -gt 10000000 ]]; then295echo "max_size out of range (1-10000000)" >&2296exit 1297fi298```299300## Edge Cases and Gotchas301302### Quotes in Values303304YAML allows both quoted and unquoted strings:305306```yaml307# These are equivalent:308field1: value309field2: "value"310field3: 'value'311```312313**Handle both:**314```bash315# Remove surrounding quotes if present316VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\\(.*\\)'$/\\1/")317```318319### --- in Markdown Body320321If the markdown body contains `---`, the parsing still works because we only match the first two:322323```markdown324---325field: value326---327328# Body329330Here's a separator:331---332333More content after the separator.334```335336The `awk '/^---$/{i++; next} i>=2'` pattern handles this correctly.337338### Empty Values339340Handle missing or empty fields:341342```yaml343field1:344field2: ""345field3: null346```347348**Parsing:**349```bash350VALUE=$(echo "$FRONTMATTER" | grep '^field1:' | sed 's/field1: *//')351# VALUE will be empty string352353# Check for empty/null354if [[ -z "$VALUE" ]] || [[ "$VALUE" == "null" ]]; then355VALUE="default"356fi357```358359### Special Characters360361Values with special characters need careful handling:362363```yaml364message: "Error: Something went wrong!"365path: "/path/with spaces/file.txt"366regex: "^[a-zA-Z0-9_]+$"367```368369**Safe parsing:**370```bash371# Always quote variables when using372MESSAGE=$(echo "$FRONTMATTER" | grep '^message:' | sed 's/message: *//' | sed 's/^"\(.*\)"$/\1/')373374echo "Message: $MESSAGE" # Quoted!375```376377## Performance Optimization378379### Cache Parsed Values380381If reading settings multiple times:382383```bash384# Parse once385FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")386387# Extract multiple fields from cached frontmatter388FIELD1=$(echo "$FRONTMATTER" | grep '^field1:' | sed 's/field1: *//')389FIELD2=$(echo "$FRONTMATTER" | grep '^field2:' | sed 's/field2: *//')390FIELD3=$(echo "$FRONTMATTER" | grep '^field3:' | sed 's/field3: *//')391```392393**Don't:** Re-parse file for each field.394395### Lazy Loading396397Only parse settings when needed:398399```bash400#!/bin/bash401input=$(cat)402403# Quick checks first (no file I/O)404tool_name=$(echo "$input" | jq -r '.tool_name')405if [[ "$tool_name" != "Write" ]]; then406exit 0 # Not a write operation, skip407fi408409# Only now check settings file410if [[ -f ".claude/my-plugin.local.md" ]]; then411# Parse settings412# ...413fi414```415416## Debugging417418### Print Parsed Values419420```bash421#!/bin/bash422set -x # Enable debug tracing423424FILE=".claude/my-plugin.local.md"425426if [[ -f "$FILE" ]]; then427echo "Settings file found" >&2428429FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")430echo "Frontmatter:" >&2431echo "$FRONTMATTER" >&2432433ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')434echo "Enabled: $ENABLED" >&2435fi436```437438### Validate Parsing439440```bash441# Show what was parsed442echo "Parsed values:" >&2443echo " enabled: $ENABLED" >&2444echo " mode: $MODE" >&2445echo " max_size: $MAX_SIZE" >&2446447# Verify expected values448if [[ "$ENABLED" != "true" ]] && [[ "$ENABLED" != "false" ]]; then449echo "⚠️ Unexpected enabled value: $ENABLED" >&2450fi451```452453## Alternative: Using yq454455For complex YAML, consider using `yq`:456457```bash458# Install: brew install yq459460# Parse YAML properly461FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")462463# Extract fields with yq464ENABLED=$(echo "$FRONTMATTER" | yq '.enabled')465MODE=$(echo "$FRONTMATTER" | yq '.mode')466LIST=$(echo "$FRONTMATTER" | yq -o json '.list_field')467468# Iterate list properly469echo "$LIST" | jq -r '.[]' | while read -r item; do470echo "Item: $item"471done472```473474**Pros:**475- Proper YAML parsing476- Handles complex structures477- Better list/object support478479**Cons:**480- Requires yq installation481- Additional dependency482- May not be available on all systems483484**Recommendation:** Use sed/grep for simple fields, yq for complex structures.485486## Complete Example487488```bash489#!/bin/bash490set -euo pipefail491492# Configuration493SETTINGS_FILE=".claude/my-plugin.local.md"494495# Quick exit if not configured496if [[ ! -f "$SETTINGS_FILE" ]]; then497# Use defaults498ENABLED=true499MODE=standard500MAX_SIZE=1000000501else502# Parse frontmatter503FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$SETTINGS_FILE")504505# Extract fields with defaults506ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')507ENABLED=${ENABLED:-true}508509MODE=$(echo "$FRONTMATTER" | grep '^mode:' | sed 's/mode: *//' | sed 's/^"\(.*\)"$/\1/')510MODE=${MODE:-standard}511512MAX_SIZE=$(echo "$FRONTMATTER" | grep '^max_size:' | sed 's/max_size: *//')513MAX_SIZE=${MAX_SIZE:-1000000}514515# Validate values516if [[ "$ENABLED" != "true" ]] && [[ "$ENABLED" != "false" ]]; then517echo "⚠️ Invalid enabled value, using default" >&2518ENABLED=true519fi520521if ! [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then522echo "⚠️ Invalid max_size, using default" >&2523MAX_SIZE=1000000524fi525fi526527# Quick exit if disabled528if [[ "$ENABLED" != "true" ]]; then529exit 0530fi531532# Use configuration533echo "Configuration loaded: mode=$MODE, max_size=$MAX_SIZE" >&2534535# Apply logic based on settings536case "$MODE" in537strict)538# Strict validation539;;540standard)541# Standard validation542;;543lenient)544# Lenient validation545;;546esac547```548549This provides robust settings handling with defaults, validation, and error recovery.550