

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
12345678# Shell Script Best Practices910## Overview1112Rules for writing robust, maintainable shell scripts in go-crypto-wallet.13These rules are based on code review feedback and industry best practices.1415## Critical Shell Options1617### Always Use Strict Mode1819```bash20set -euo pipefail21```2223**Explanation:**2425- `-e`: Exit immediately if any command fails26- `-u`: Treat unset variables as errors27- `-o pipefail`: Fail if any command in a pipeline fails (not just the last one)2829**Why pipefail matters:**3031```bash32# WITHOUT pipefail (BAD)33set -eu34cat nonexistent.txt | grep "pattern" # Only grep's exit code matters3536# WITH pipefail (GOOD)37set -euo pipefail38cat nonexistent.txt | grep "pattern" # Fails immediately if cat fails39```4041## Variable Configuration4243### Make Hardcoded Values Configurable4445**Bad:**4647```bash48# Hardcoded volume name - brittle if project name changes49docker volume rm "go-crypto-wallet_wallet-mysql"50```5152**Good:**5354```bash55# Configurable with default56DOCKER_VOLUME_NAME="${DOCKER_VOLUME_NAME:-go-crypto-wallet_wallet-mysql}"57docker volume rm "$DOCKER_VOLUME_NAME"58```5960**Benefits:**6162- Flexibility for different environments63- No breakage if project structure changes64- Easy to override in CI/CD6566### Environment Variable Naming6768Use descriptive, uppercase names with underscores:6970```bash71RPC_USER="${RPC_USER:-xyz}"72RPC_PASSWORD="${RPC_PASSWORD:-xyz}"73WALLET_PASSPHRASE="${WALLET_PASSPHRASE:-test}"74DOCKER_VOLUME_NAME="${DOCKER_VOLUME_NAME:-go-crypto-wallet_wallet-mysql}"75```7677### Use Environment Variables Instead of Modifying Config Files7879**Bad (creates backups, modifies files):**8081```bash82# Backup and modify config file83sed -i.bak 's|host: "127.0.0.1:18332"|host: "127.0.0.1:18332/wallet/watch"|' config.yaml8485# ... operations ...8687# Restore backup88if [ -f "config.yaml.bak" ]; then89 mv "config.yaml.bak" "config.yaml"90fi91```9293**Good (use environment variables):**9495```bash96# Create wrapper function to set environment variable per-command97watch_with_wallet() {98 WALLET_BITCOIN_HOST="127.0.0.1:18332/wallet/watch" watch "$@"99}100101# Use wrapper function102watch_with_wallet -c config.yaml create payment103```104105**Benefits:**106107- No risk of leaving modified configs if script fails108- No backup files to manage109- Cleaner and more robust110- Easy to override in CI/CD111- No file permission issues112113**When to use this pattern:**114115- Applications that support environment variable overrides (check config documentation)116- Temporary config changes for scripts or tests117- Multi-environment setups (dev, staging, prod)118119## Error Handling120121### Refactor Duplicate Error Handling122123**Bad (Duplicated):**124125```bash126if echo "$output" | grep -q "error"; then127 log_error "Operation failed"128 log_error "This could indicate:"129 log_error " - Reason 1"130 log_error " - Reason 2"131 return 1132fi133134# ... later in code ...135136if echo "$output2" | grep -q "error"; then137 log_error "Operation failed"138 log_error "This could indicate:"139 log_error " - Reason 1"140 log_error " - Reason 2"141 return 1142fi143```144145**Good (DRY with helper function):**146147```bash148# Create helper function149log_operation_error() {150 log_error "Operation failed"151 log_error "This could indicate:"152 log_error " - Reason 1"153 log_error " - Reason 2"154 return 1155}156157# Use it158if echo "$output" | grep -q "error"; then159 log_operation_error160fi161162if echo "$output2" | grep -q "error"; then163 log_operation_error164fi165```166167### Error Handling in Command Substitution168169Always handle errors in command substitution:170171```bash172# Get output with error handling173balance_json=$(bitcoin-cli getbalances 2>&1 || true)174175# Parse with fallback176balance=$(echo "$balance_json" | jq -r '.amount // 0' 2>/dev/null || echo "0")177```178179## Robust Comparisons180181### Variable Quoting in Test Conditions (SC2086)182183**Always quote variables in `[ ]` test conditions** to prevent globbing and word splitting.184185**Bad (Unquoted - can fail if variable is empty or contains spaces):**186187```bash188# These can fail unexpectedly189while [ $counter -lt $max ]; do190if [ $removal_attempts -lt $max_attempts ]; then191```192193**Good (Properly quoted):**194195```bash196# Always quote variables in test conditions197while [ "$counter" -lt "$max" ]; do198if [ "$removal_attempts" -lt "$max_attempts" ]; then199```200201**Why this matters:**202203- If `$counter` is empty, `[ -lt 5 ]` fails with "unary operator expected"204- If `$counter` contains spaces, it splits into multiple arguments205- Quoted variables ensure reliable behavior in all cases206207**This applies to all test operators:**208209```bash210# Numeric comparisons - quote both sides211[ "$a" -lt "$b" ]212[ "$a" -gt "$b" ]213[ "$a" -eq "$b" ]214215# String comparisons - quote both sides216[ "$str" = "value" ]217[ "$str" != "value" ]218[ -z "$str" ]219[ -n "$str" ]220```221222### Floating Point Comparisons with bc223224**Bad (Fragile):**225226```bash227if (($(echo "$balance > 0" | bc -l))); then228 # Fails if bc is not installed or input is malformed229fi230```231232**Good (Robust):**233234```bash235if [ -n "$balance" ] && [ "$(echo "$balance > 0" | bc -l 2>/dev/null || echo 0)" -eq 1 ]; then236 # Handles bc failures gracefully237fi238```239240**Why this is better:**241242- Checks variable is not empty first243- Redirects bc errors to /dev/null244- Defaults to 0 on failure245- Uses `-eq 1` for reliable comparison246247### String Comparisons248249Always quote variables:250251```bash252# BAD253if [ $var = "value" ]; then254255# GOOD256if [ "$var" = "value" ]; then257258# EVEN BETTER (handles empty/unset)259if [ "${var:-}" = "value" ]; then260```261262## Script Structure263264### Standard Script Template265266```bash267#!/usr/bin/env bash268269# Script Description270# Usage: ./script.sh [OPTIONS]271# Options:272# --option Description273274set -euo pipefail275276# Script directory for relative paths277SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"278PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"279280# Source common utilities281# shellcheck source=path/to/common.sh282source "${SCRIPT_DIR}/common.sh"283284# Configuration with defaults285VERBOSE="${VERBOSE:-false}"286CONFIG_FILE="${CONFIG_FILE:-config.yaml}"287288###############################################################################289# Functions290###############################################################################291292show_help() {293 cat <<EOF294Usage: $0 [OPTIONS]295296Options:297 --help Show this help message298 --verbose Enable verbose output299EOF300}301302main() {303 # Parse arguments304 while [ $# -gt 0 ]; do305 case "$1" in306 --help|-h)307 show_help308 exit 0309 ;;310 --verbose)311 VERBOSE=true312 shift313 ;;314 *)315 log_error "Unknown option: $1"316 show_help317 exit 1318 ;;319 esac320 done321322 # Main logic here323}324325# Run main326main "$@"327```328329## Common Patterns330331### Directory Operations332333```bash334# Create directory if needed335mkdir -p "${TARGET_DIR}"336337# Clean directory except .gitkeep338find "$dir" -type f ! -name '.gitkeep' -delete 2>/dev/null || true339340# Remove directory contents except specific file341find "$wallet_dir" -mindepth 1 ! -name 'bitcoin.conf' -exec rm -rf {} + 2>/dev/null || true342```343344### File Path Handling345346```bash347# Always quote paths with spaces348cd "/path/with spaces" # GOOD349cd /path/with spaces # BAD - will fail350351# Use double quotes for variables352cp "${SOURCE_FILE}" "${DEST_FILE}"353354# Extract filename from path355filename="${path##*/}"356357# Extract directory from path358dirname="${path%/*}"359```360361### Loop Patterns362363```bash364# Loop over arguments365for arg in "$@"; do366 echo "$arg"367done368369# Loop with counter370for i in $(seq 1 10); do371 echo "$i"372done373374# Loop over array375wallets=(watch keygen sign1 sign2)376for wallet in "${wallets[@]}"; do377 echo "$wallet"378done379```380381## Docker Operations382383### Safe Docker Volume Removal384385```bash386# Try multiple times with backoff387removal_attempts=0388max_removal_attempts=5389390# Note: Always quote variables in test conditions391while [ "$removal_attempts" -lt "$max_removal_attempts" ]; do392 if docker volume rm "$volume_name" 2>/dev/null; then393 log_info "Volume removed successfully"394 break395 fi396 removal_attempts=$((removal_attempts + 1))397 if [ "$removal_attempts" -lt "$max_removal_attempts" ]; then398 log_warn "Retrying in 2 seconds... (attempt $removal_attempts/$max_removal_attempts)"399 sleep 2400 fi401done402```403404### Wait for Container Health405406```bash407wait_for_healthy() {408 local container_name=$1409 local max_wait=${2:-60}410 local counter=0411412 # Note: Always quote variables in test conditions413 while [ "$counter" -lt "$max_wait" ]; do414 status=$(docker inspect --format='{{.State.Health.Status}}' "$container_name" 2>/dev/null || echo "not_found")415416 if [ "$status" = "healthy" ]; then417 return 0418 fi419420 counter=$((counter + 1))421 sleep 1422 done423424 return 1425}426```427428## Logging Best Practices429430### Use Descriptive Log Levels431432```bash433log_info "Starting operation..."434log_warn "Retrying due to temporary failure"435log_error "Critical error occurred"436log_debug "Variable value: $var" # Only shown if VERBOSE=true437```438439### Log Context for Debugging440441```bash442# BAD443log_error "Failed"444445# GOOD446log_error "Failed to create wallet"447log_error "Container: $container_name"448log_error "Output: $output"449log_error "Exit code: $?"450```451452## Security Considerations453454### Never Log Sensitive Information455456```bash457# BAD458log_info "Password: $PASSWORD"459log_info "Private key: $PRIVATE_KEY"460461# GOOD462log_info "Credentials loaded"463log_info "Private key file: ${KEY_FILE} (not displaying contents)"464```465466### Validate User Input467468```bash469# Validate before use470if [ -z "$user_input" ]; then471 log_error "Input cannot be empty"472 exit 1473fi474475# Sanitize paths476if [[ "$path" =~ \.\. ]]; then477 log_error "Path traversal detected"478 exit 1479fi480```481482## Testing and Validation483484### Check Prerequisites485486```bash487check_prerequisites() {488 # Check required commands489 for cmd in docker jq bc; do490 if ! command -v "$cmd" >/dev/null 2>&1; then491 log_error "$cmd is not installed"492 exit 1493 fi494 done495496 # Check required files497 for file in "$CONFIG_FILE" "$KEY_FILE"; do498 if [ ! -f "$file" ]; then499 log_error "Required file not found: $file"500 exit 1501 fi502 done503}504```505506### Dry Run Mode507508```bash509DRY_RUN="${DRY_RUN:-false}"510511execute_command() {512 local cmd="$1"513514 if [ "$DRY_RUN" = "true" ]; then515 log_info "[DRY RUN] Would execute: $cmd"516 else517 eval "$cmd"518 fi519}520```521522## Common Utilities from common.sh523524This project provides common utilities in `scripts/operation/common.sh`:525526```bash527# Source it528source "${SCRIPT_DIR}/../../common.sh"529530# Available functions531log_info "message"532log_warn "message"533log_error "message"534log_step "Major Section"535log_substep "Minor Section"536537check_docker538wait_for_healthy "container-name" 60539btc_cli "btc-watch" "getblockcount"540clean_dir_except_gitkeep "data/address/btc"541```542543## ShellCheck Integration544545**Always run Makefile targets after modifying shell scripts:**546547```bash548# Format all shell scripts549make shfmt550551# Lint all shell scripts552make shellcheck553```554555These targets automatically process all `.sh` files in the `scripts/` directory.556557For individual file testing (optional):558559```bash560# Format single file561shfmt -l -w script.sh562563# Check single file564shellcheck script.sh565```566567### Common ShellCheck Directives568569```bash570# Disable specific warning571# shellcheck disable=SC2086572echo $var573574# Mark sourced file575# shellcheck source=path/to/file.sh576source "${SCRIPT_DIR}/common.sh"577578# Disable for entire file (use sparingly)579# shellcheck disable=SC1090,SC2034580```581582## File Naming Conventions583584- Use lowercase with hyphens: `e2e-p2pkh-2of3.sh`585- Descriptive names: `setup-bitcoin-nodes.sh` not `setup.sh`586- Prefix with purpose: `test-`, `e2e-`, `setup-`, `cleanup-`587588## Documentation589590### Comprehensive Header Comments591592```bash593#!/usr/bin/env bash594595# Bitcoin E2E Workflow Script - Pattern 2: P2PKH 2-of-3 Multisig596# This script automates the complete Bitcoin workflow597#598# Usage: ./script.sh [OPTIONS]599#600# Options:601# --reset Full reset and run from scratch602# --cleanup Stop containers and cleanup state603# --verbose Enable verbose output604# -h, --help Display help message605#606# Environment Variables:607# RPC_USER Bitcoin RPC username (default: xyz)608# RPC_PASSWORD Bitcoin RPC password (default: xyz)609#610# Reference Documentation:611# docs/chains/btc/operations/e2e-transaction-patterns.md612```613614### Function Documentation615616```bash617# Wait for Docker container to be healthy618# Usage: wait_for_healthy "container-name" [max_wait_seconds]619# Arguments:620# $1 - Container name621# $2 - Maximum wait time in seconds (default: 60)622# Returns:623# 0 on success, 1 on timeout624wait_for_healthy() {625 local container_name=$1626 local max_wait=${2:-60}627 # ... implementation ...628}629```630631## Language Requirements632633### Write All Comments and Messages in English634635All shell scripts must use English for:636637- **Header comments** - Script description, usage, options638- **Inline comments** - Code explanations639- **Function documentation** - Usage, arguments, returns640- **Log messages** - Info, warning, error messages641- **Help text** - Command-line help output642643**Bad (Japanese):**644645```bash646# 変換完了647log_success "変換完了!"648log_info "DRY-RUN モードでした"649```650651**Good (English):**652653```bash654# Conversion complete655log_success "Conversion complete!"656log_info "DRY-RUN mode was enabled"657```658659**Rationale:**660661- Ensures accessibility for international contributors662- Maintains consistency across the codebase663- Facilitates collaboration with global developer community664- Aligns with project documentation standards (see `documentation-language` rule)665666## Makefile Integration667668Shell scripts are often called from Makefiles:669670```makefile671.PHONY: btc-e2e-reset672btc-e2e-reset:673 ./scripts/operation/btc/e2e/e2e-workflow.sh --reset674675.PHONY: btc-e2e-verbose676btc-e2e-verbose:677 ./scripts/operation/btc/e2e/e2e-workflow.sh --verbose678```679680## References681682- [ShellCheck](https://www.shellcheck.net/) - Shell script analysis tool683- [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)684- [Bash Hackers Wiki](https://wiki.bash-hackers.org/)685- Project: @scripts/operation/common.sh - Common utilities686- Project: @scripts/operation/btc/e2e/ - E2E test examples687688## Quick Checklist689690Before committing shell scripts:691692- [ ] `set -euo pipefail` at the top693- [ ] All hardcoded values are configurable via environment variables694- [ ] Floating point comparisons use robust bc patterns695- [ ] No duplicate error handling code (use helper functions)696- [ ] All variables are quoted: `"$var"`697- [ ] Error messages include context698- [ ] No sensitive information in logs699- [ ] Prerequisites are checked700- [ ] Help message is comprehensive701- [ ] **All comments and messages are in English**702- [ ] Run `make shfmt` (format all shell scripts)703- [ ] Run `make shellcheck` (lint all shell scripts)704- [ ] File is executable: `chmod +x script.sh`705
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/agent-files.mdc · 126 | Cursor rules | lint-formatarchdo-notagent-behaviour | 77/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/documentation-language.mdc · 126 | Cursor rules | archdo-notdocs | 36/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/general.mdc · 126 | Cursor rules | buildtestarchgit+2 | 83/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/github-actions.mdc · 126 | Cursor rules | stylearchgitperformance+4 | 77/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/proto.mdc · 126 | Cursor rules | buildlint-formatstylearch+3 | 96/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/security.mdc · 126 | Cursor rules | archsecuritydo-notagent-behaviour | 76/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/task-context-loading.mdc · 126 | Cursor rules | archtypesdatabasedo-not+1 | 93/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/yaml.mdc · 126 | Cursor rules | lint-formatstylearchtypes+3 | 84/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.github/copilot-instructions.md · 126 | Copilot instructions | teststylearchsecurity+1 | 56/100 | 14 days ago | |
| hiromaily/go-crypto-walletAGENTS.md · 126 | AGENTS.md | archtypesdo-notagent-behaviour+1 | 79/100 | 14 days ago | |
| hiromaily/go-crypto-walletCLAUDE.md · 126 | CLAUDE.md | archtypesdo-notagent-behaviour+1 | 79/100 | 14 days ago | |
| hiromaily/go-crypto-walletpkg/AGENTS.md · 126 | AGENTS.md | stylearchdo-not | 80/100 | 14 days ago | |
| hiromaily/go-crypto-wallet.cursor/rules/ssot.mdc · 126 | Cursor rules | stylearchtypesdo-not+2 | 84/100 | 14 days ago |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 14 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 46 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 14 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 14 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 14 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 46 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/hiromaily-go-crypto-wallet-cursor-rules-shell-script)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.