state-directory-manager
Included with Lifetime
$97 forever
Manage persistent state directories for bash scripts
bashbashstatepersistenceconfigdirectoryxdg
What this skill does
# State Directory Manager
Patterns for managing persistent state, configuration, and cache directories in bash scripts following XDG Base Directory specification.
## When to Use This Skill
✅ **Use when:**
- Scripts need to persist data between runs
- Storing user preferences or configuration
- Caching results for performance
- Managing log files with rotation
- Creating portable CLI tools
❌ **Avoid when:**
- One-time scripts that don't need state
- Scripts that should be purely stateless
- When environment variables are sufficient
## Core Capabilities
### 1. XDG Base Directory Standard
Follow the XDG specification for directory locations:
```bash
#!/bin/bash
# ABOUTME: XDG Base Directory compliant state management
# ABOUTME: Cross-platform directory locations
# XDG Base Directories with fallbacks
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
# Application-specific directories
APP_NAME="my-tool"
CONFIG_DIR="$XDG_CONFIG_HOME/$APP_NAME"
DATA_DIR="$XDG_DATA_HOME/$APP_NAME"
STATE_DIR="$XDG_STATE_HOME/$APP_NAME"
CACHE_DIR="$XDG_CACHE_HOME/$APP_NAME"
LOG_DIR="$STATE_DIR/logs"
# Initialize directories
init_directories() {
mkdir -p "$CONFIG_DIR"
mkdir -p "$DATA_DIR"
mkdir -p "$STATE_DIR"
mkdir -p "$CACHE_DIR"
mkdir -p "$LOG_DIR"
}
```
### 2. Workspace-Hub Pattern
Alternative using home directory (from workspace-hub scripts):
```bash
#!/bin/bash
# ABOUTME: Workspace-hub style state directory management
# ABOUTME: Simple $HOME/.app-name pattern
APP_NAME="workspace-hub"
APP_DIR="${HOME}/.${APP_NAME}"
# Directory structure
CONFIG_DIR="$APP_DIR/config"
DATA_DIR="$APP_DIR/data"
LOGS_DIR="$APP_DIR/logs"
CACHE_DIR="$APP_DIR/cache"
TEMP_DIR="$APP_DIR/tmp"
# Initialize with proper permissions
init_app_dirs() {
local dirs=("$CONFIG_DIR" "$DATA_DIR" "$LOGS_DIR" "$CACHE_DIR" "$TEMP_DIR")
for dir in "${dirs[@]}"; do
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir"
chmod 700 "$dir" # Private by default
fi
done
}
# Clean old temp files
clean_temp() {
find "$TEMP_DIR" -type f -mtime +1 -delete 2>/dev/null || true
}
```
### 3. Configuration File Management
Read and write configuration files:
```bash
#!/bin/bash
# ABOUTME: Configuration file management
# ABOUTME: Key-value pairs with defaults
CONFIG_FILE="$CONFIG_DIR/config"
# Default configuration
declare -A DEFAULT_CONFIG=(
["parallel_workers"]="5"
["log_level"]="INFO"
["auto_sync"]="true"
["timeout"]="30"
)
# Initialize config with defaults
init_config() {
if [[ ! -f "$CONFIG_FILE" ]]; then
{
echo "# Configuration for $APP_NAME"
echo "# Generated: $(date)"
echo ""
for key in "${!DEFAULT_CONFIG[@]}"; do
echo "${key}=${DEFAULT_CONFIG[$key]}"
done
} > "$CONFIG_FILE"
fi
}
# Read config value
get_config() {
local key="$1"
local default="${2:-${DEFAULT_CONFIG[$key]:-}}"
if [[ -f "$CONFIG_FILE" ]]; then
local value
value=$(grep "^${key}=" "$CONFIG_FILE" 2>/dev/null | cut -d'=' -f2-)
echo "${value:-$default}"
else
echo "$default"
fi
}
# Write config value
set_config() {
local key="$1"
local value="$2"
init_config
if grep -q "^${key}=" "$CONFIG_FILE" 2>/dev/null; then
# Update existing
sed -i "s|^${key}=.*|${key}=${value}|" "$CONFIG_FILE"
else
# Add new
echo "${key}=${value}" >> "$CONFIG_FILE"
fi
}
# Load all config into associative array
load_config() {
declare -gA CONFIG
# Start with defaults
for key in "${!DEFAULT_CONFIG[@]}"; do
CONFIG[$key]="${DEFAULT_CONFIG[$key]}"
done
# Override with file values
if [[ -f "$CONFIG_FILE" ]]; then
while IFS='=' read -r key value; do
[[ "$key" =~ ^#.*$ || -z "$key" ]] && continue
CONFIG[$key]="$value"
done < "$CONFIG_FILE"
fi
}
# Usage
init_config
load_config
echo "Parallel workers: ${CONFIG[parallel_workers]}"
set_config "parallel_workers" "10"
```
### 4. State File Operations
Track persistent state between runs:
```bash
#!/bin/bash
# ABOUTME: State file operations
# ABOUTME: Track last run, progress, etc.
STATE_FILE="$STATE_DIR/state.json"
# Initialize state
init_state() {
if [[ ! -f "$STATE_FILE" ]]; then
cat > "$STATE_FILE" << EOF
{
"version": "1.0.0",
"created": "$(date -Iseconds)",
"last_run": null,
"run_count": 0,
"last_status": null
}
EOF
fi
}
# Get state value (requires jq)
get_state() {
local key="$1"
local default="${2:-null}"
if [[ -f "$STATE_FILE" ]] && command -v jq &>/dev/null; then
jq -r ".$key // $default" "$STATE_FILE"
else
echo "$default"
fi
}
# Update state value (requires jq)
set_state() {
local key="$1"
local value="$2"
init_state
if command -v jq &>/dev/null; then
local temp=$(mktemp)
jq ".$key = $value" "$STATE_FILE" > "$temp" && mv "$temp" "$STATE_FILE"
fi
}
# Record run
record_run() {
local status="$1"
set_state "last_run" "\"$(date -Iseconds)\""
set_state "last_status" "\"$status\""
set_state "run_count" "$(($(get_state run_count 0) + 1))"
}
# Simple key-value state (no jq required)
STATE_KV_FILE="$STATE_DIR/state.kv"
get_state_kv() {
local key="$1"
local default="$2"
if [[ -f "$STATE_KV_FILE" ]]; then
grep "^${key}=" "$STATE_KV_FILE" 2>/dev/null | cut -d'=' -f2- || echo "$default"
else
echo "$default"
fi
}
set_state_kv() {
local key="$1"
local value="$2"
mkdir -p "$(dirname "$STATE_KV_FILE")"
if [[ -f "$STATE_KV_FILE" ]] && grep -q "^${key}=" "$STATE_KV_FILE"; then
sed -i "s|^${key}=.*|${key}=${value}|" "$STATE_KV_FILE"
else
echo "${key}=${value}" >> "$STATE_KV_FILE"
fi
}
```
### 5. Cache Management
Implement caching with expiration:
```bash
#!/bin/bash
# ABOUTME: Cache management with TTL
# ABOUTME: Store and retrieve cached data
CACHE_TTL="${CACHE_TTL:-3600}" # 1 hour default
# Get cache file path
cache_path() {
local key="$1"
local hash=$(echo -n "$key" | md5sum | cut -c1-16)
echo "$CACHE_DIR/${hash}"
}
# Check if cache is valid
cache_valid() {
local key="$1"
local ttl="${2:-$CACHE_TTL}"
local path=$(cache_path "$key")
if [[ -f "$path" ]]; then
local age=$(($(date +%s) - $(stat -c %Y "$path" 2>/dev/null || stat -f %m "$path")))
[[ $age -lt $ttl ]]
else
return 1
fi
}
# Get from cache
cache_get() {
local key="$1"
local ttl="${2:-$CACHE_TTL}"
local path=$(cache_path "$key")
if cache_valid "$key" "$ttl"; then
cat "$path"
return 0
fi
return 1
}
# Set cache
cache_set() {
local key="$1"
local value="$2"
local path=$(cache_path "$key")
mkdir -p "$CACHE_DIR"
echo "$value" > "$path"
}
# Delete cache
cache_delete() {
local key="$1"
local path=$(cache_path "$key")
rm -f "$path"
}
# Clear all cache
cache_clear() {
rm -rf "$CACHE_DIR"/*
}
# Clean expired cache entries
cache_clean() {
local ttl="${1:-$CACHE_TTL}"
find "$CACHE_DIR" -type f -mmin "+$((ttl / 60))" -delete 2>/dev/null || true
}
# Usage with automatic caching
get_with_cache() {
local key="$1"
local command="$2"
local ttl="${3:-$CACHE_TTL}"
if cache_valid "$key" "$ttl"; then
cache_get "$key"
else
local result
result=$(eval "$command")
cache_set "$key" "$result"
echo "$result"
fi
}
# Example
result=$(get_with_cache "api_response" "curl -s https://api.example.com/data" 300)
```
### 6. Log File Management
Manage logs with rotation:
```bash
#!/bin/bash
# ABOUTME: Log file management with rotation
# ABOUTME: Automatic cleanup Related in bash
json-config-loader
IncludedConfiguration file parsing patterns for bash scripts (INI, key=value, JSON)
bash
git-sync-manager
IncludedMulti-repository git synchronization patterns for batch operations
bash
interactive-menu-builder
IncludedBuild multi-level interactive CLI menus for bash scripts
bash
complexity-scorer
IncludedScore task complexity using keyword matching and heuristics
bash
usage-tracker
IncludedTrack and analyze usage metrics with timestamped logging
bash
parallel-batch-executor
IncludedParallel task execution patterns for 300% performance gains
bash