{
  "schemaVersion": "1.0",
  "item": {
    "slug": "shell-scripting",
    "name": "Shell Scripting",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/gitgoodordietrying/shell-scripting",
    "canonicalUrl": "https://clawhub.ai/gitgoodordietrying/shell-scripting",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/shell-scripting",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=shell-scripting",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-07T17:22:31.273Z",
      "expiresAt": "2026-05-14T17:22:31.273Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
        "contentDisposition": "attachment; filename=\"afrexai-annual-report-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/shell-scripting"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/shell-scripting",
    "agentPageUrl": "https://openagent3.xyz/skills/shell-scripting/agent",
    "manifestUrl": "https://openagent3.xyz/skills/shell-scripting/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/shell-scripting/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Shell Scripting",
        "body": "Write reliable, maintainable bash scripts. Covers argument parsing, error handling, portability, temp files, parallel execution, process management, and self-documenting scripts."
      },
      {
        "title": "When to Use",
        "body": "Writing scripts that others (or future you) will run\nAutomating multi-step workflows\nParsing command-line arguments with flags and options\nHandling errors and cleanup properly\nRunning tasks in parallel\nMaking scripts portable across Linux and macOS\nWrapping complex commands with a simpler interface"
      },
      {
        "title": "Script Template",
        "body": "#!/usr/bin/env bash\nset -euo pipefail\n\n# Description: What this script does (one line)\n# Usage: script.sh [options] <required-arg>\n\nreadonly SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nreadonly SCRIPT_NAME=\"$(basename \"$0\")\"\n\n# Defaults\nVERBOSE=false\nOUTPUT_DIR=\"./output\"\n\nusage() {\n    cat <<EOF\nUsage: $SCRIPT_NAME [options] <input-file>\n\nDescription:\n  Process the input file and generate output.\n\nOptions:\n  -o, --output DIR    Output directory (default: $OUTPUT_DIR)\n  -v, --verbose       Enable verbose output\n  -h, --help          Show this help message\n\nExamples:\n  $SCRIPT_NAME data.csv\n  $SCRIPT_NAME -v -o /tmp/results data.csv\nEOF\n}\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >&2; }\ndebug() { $VERBOSE && log \"DEBUG: $*\" || true; }\ndie() { log \"ERROR: $*\"; exit 1; }\n\n# Parse arguments\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        -o|--output) OUTPUT_DIR=\"$2\"; shift 2 ;;\n        -v|--verbose) VERBOSE=true; shift ;;\n        -h|--help) usage; exit 0 ;;\n        --) shift; break ;;\n        -*) die \"Unknown option: $1\" ;;\n        *) break ;;\n    esac\ndone\n\nINPUT_FILE=\"${1:?$(usage >&2; echo \"Error: input file required\")}\"\n[[ -f \"$INPUT_FILE\" ]] || die \"File not found: $INPUT_FILE\"\n\n# Main logic\nmain() {\n    debug \"Input: $INPUT_FILE\"\n    debug \"Output: $OUTPUT_DIR\"\n    mkdir -p \"$OUTPUT_DIR\"\n\n    log \"Processing $INPUT_FILE...\"\n    # ... do work ...\n    log \"Done. Output in $OUTPUT_DIR\"\n}\n\nmain \"$@\""
      },
      {
        "title": "set flags",
        "body": "set -e          # Exit on any command failure\nset -u          # Error on undefined variables\nset -o pipefail # Pipe fails if any command in the pipe fails\nset -x          # Debug: print each command before executing (noisy)\n\n# Combined (use this in every script)\nset -euo pipefail\n\n# Temporarily disable for commands that are allowed to fail\nset +e\nsome_command_that_might_fail\nexit_code=$?\nset -e"
      },
      {
        "title": "Trap for cleanup",
        "body": "# Cleanup on exit (any exit: success, failure, or signal)\nTMPDIR=\"\"\ncleanup() {\n    [[ -n \"$TMPDIR\" ]] && rm -rf \"$TMPDIR\"\n}\ntrap cleanup EXIT\n\nTMPDIR=$(mktemp -d)\n# Use $TMPDIR freely — it's cleaned up automatically\n\n# Trap specific signals\ntrap 'echo \"Interrupted\"; exit 130' INT    # Ctrl+C\ntrap 'echo \"Terminated\"; exit 143' TERM    # kill"
      },
      {
        "title": "Error handling patterns",
        "body": "# Check command exists before using it\ncommand -v jq >/dev/null 2>&1 || die \"jq is required but not installed\"\n\n# Provide default values\nNAME=\"${NAME:-default_value}\"\n\n# Required variable (fail if unset)\n: \"${API_KEY:?Error: API_KEY environment variable is required}\"\n\n# Retry a command\nretry() {\n    local max_attempts=$1\n    shift\n    local attempt=1\n    while [[ $attempt -le $max_attempts ]]; do\n        \"$@\" && return 0\n        log \"Attempt $attempt/$max_attempts failed. Retrying...\"\n        ((attempt++))\n        sleep $((attempt * 2))\n    done\n    die \"Command failed after $max_attempts attempts: $*\"\n}\n\nretry 3 curl -sf https://api.example.com/health"
      },
      {
        "title": "Simple: positional + flags",
        "body": "# Manual parsing (no dependencies)\nFORCE=false\nDRY_RUN=false\n\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        -f|--force) FORCE=true; shift ;;\n        -n|--dry-run) DRY_RUN=true; shift ;;\n        -o|--output)\n            [[ -n \"${2:-}\" ]] || die \"--output requires a value\"\n            OUTPUT=\"$2\"; shift 2 ;;\n        --output=*)\n            OUTPUT=\"${1#*=}\"; shift ;;\n        -h|--help) usage; exit 0 ;;\n        --) shift; break ;;  # End of options\n        -*) die \"Unknown option: $1\" ;;\n        *) break ;;  # Start of positional args\n    esac\ndone\n\n# Remaining args are positional\nFILES=(\"$@\")\n[[ ${#FILES[@]} -gt 0 ]] || die \"At least one file is required\""
      },
      {
        "title": "getopts (POSIX, short options only)",
        "body": "while getopts \":o:vhf\" opt; do\n    case \"$opt\" in\n        o) OUTPUT=\"$OPTARG\" ;;\n        v) VERBOSE=true ;;\n        f) FORCE=true ;;\n        h) usage; exit 0 ;;\n        :) die \"Option -$OPTARG requires an argument\" ;;\n        ?) die \"Unknown option: -$OPTARG\" ;;\n    esac\ndone\nshift $((OPTIND - 1))"
      },
      {
        "title": "Temp Files and Directories",
        "body": "# Create temp file (automatically unique)\nTMPFILE=$(mktemp)\necho \"data\" > \"$TMPFILE\"\n\n# Create temp directory\nTMPDIR=$(mktemp -d)\n\n# Create temp with custom prefix/suffix\nTMPFILE=$(mktemp /tmp/myapp.XXXXXX)\nTMPFILE=$(mktemp --suffix=.json)  # GNU only\n\n# Always clean up with trap\ntrap 'rm -f \"$TMPFILE\"' EXIT\n\n# Portable pattern (works on macOS and Linux)\nTMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'myapp')\ntrap 'rm -rf \"$TMPDIR\"' EXIT"
      },
      {
        "title": "xargs -P",
        "body": "# Run 4 commands in parallel\ncat urls.txt | xargs -P 4 -I {} curl -sO {}\n\n# Process files in parallel (4 at a time)\nfind . -name \"*.csv\" | xargs -P 4 -I {} ./process.sh {}\n\n# Parallel with progress indicator\nfind . -name \"*.jpg\" | xargs -P 8 -I {} sh -c 'convert {} -resize 800x600 resized/{} && echo \"Done: {}\"'"
      },
      {
        "title": "Background jobs + wait",
        "body": "# Run tasks in background, wait for all\npids=()\nfor file in data/*.csv; do\n    process_file \"$file\" &\n    pids+=($!)\ndone\n\n# Wait for all and check results\nfailed=0\nfor pid in \"${pids[@]}\"; do\n    wait \"$pid\" || ((failed++))\ndone\n[[ $failed -eq 0 ]] || die \"$failed jobs failed\""
      },
      {
        "title": "GNU Parallel (if available)",
        "body": "# Process files with 8 parallel jobs\nparallel -j 8 ./process.sh {} ::: data/*.csv\n\n# With progress bar\nparallel --bar -j 4 convert {} -resize 800x600 resized/{/} ::: *.jpg\n\n# Pipe input lines\ncat urls.txt | parallel -j 10 curl -sO {}"
      },
      {
        "title": "Background processes",
        "body": "# Start in background\nlong_running_command &\nBG_PID=$!\n\n# Check if still running\nkill -0 $BG_PID 2>/dev/null && echo \"Running\" || echo \"Stopped\"\n\n# Wait for it\nwait $BG_PID\necho \"Exit code: $?\"\n\n# Kill on script exit\ntrap 'kill $BG_PID 2>/dev/null' EXIT"
      },
      {
        "title": "Process supervision",
        "body": "# Run a command, restart if it dies\nrun_with_restart() {\n    local cmd=(\"$@\")\n    while true; do\n        \"${cmd[@]}\" &\n        local pid=$!\n        log \"Started PID $pid\"\n        wait $pid\n        local exit_code=$?\n        log \"Process exited with code $exit_code. Restarting in 5s...\"\n        sleep 5\n    done\n}\n\nrun_with_restart ./my-server --port 8080"
      },
      {
        "title": "Timeout",
        "body": "# Kill command after 30 seconds\ntimeout 30 long_running_command\n\n# With custom signal (SIGKILL after SIGTERM fails)\ntimeout --signal=TERM --kill-after=10 30 long_running_command\n\n# Portable (no timeout command)\n( sleep 30; kill $$ 2>/dev/null ) &\nTIMER_PID=$!\nlong_running_command\nkill $TIMER_PID 2>/dev/null"
      },
      {
        "title": "Common differences",
        "body": "# sed: macOS requires -i '' (empty backup extension)\n# Linux:\nsed -i 's/old/new/g' file.txt\n# macOS:\nsed -i '' 's/old/new/g' file.txt\n# Portable:\nsed -i.bak 's/old/new/g' file.txt && rm file.txt.bak\n\n# date: different flags\n# GNU (Linux):\ndate -d '2026-02-03' '+%s'\n# BSD (macOS):\ndate -j -f '%Y-%m-%d' '2026-02-03' '+%s'\n\n# readlink -f: doesn't exist on macOS\n# Portable alternative:\nreal_path() { cd \"$(dirname \"$1\")\" && echo \"$(pwd)/$(basename \"$1\")\"; }\n\n# stat: different syntax\n# GNU: stat -c '%s' file\n# BSD: stat -f '%z' file\n\n# grep -P: not available on macOS by default\n# Use grep -E instead, or install GNU grep"
      },
      {
        "title": "POSIX-safe patterns",
        "body": "# Use printf instead of echo -e (echo behavior varies)\nprintf \"Line 1\\nLine 2\\n\"\n\n# Use $() instead of backticks\nresult=$(command)   # Good\nresult=`command`    # Bad (deprecated, nesting issues)\n\n# Use [[ ]] for tests (bash), [ ] for POSIX sh\n[[ -f \"$file\" ]]   # Bash (safer, no word splitting)\n[ -f \"$file\" ]     # POSIX sh\n\n# Array check (bash only, not POSIX)\nif [[ ${#array[@]} -gt 0 ]]; then\n    echo \"Array has elements\"\nfi"
      },
      {
        "title": "Source a config file",
        "body": "# Simple: source a key=value file\n# config.env:\n# DB_HOST=localhost\n# DB_PORT=5432\n\n# Validate before sourcing (security: check for commands)\nif grep -qP '^[A-Z_]+=.*[;\\`\\$\\(]' config.env; then\n    die \"Config file contains unsafe characters\"\nfi\nsource config.env"
      },
      {
        "title": "Parse INI-style config",
        "body": "# config.ini:\n# [database]\n# host = localhost\n# port = 5432\n# [app]\n# debug = true\n\nparse_ini() {\n    local file=\"$1\" section=\"\"\n    while IFS='= ' read -r key value; do\n        [[ -z \"$key\" || \"$key\" =~ ^[#\\;] ]] && continue\n        if [[ \"$key\" =~ ^\\[(.+)\\]$ ]]; then\n            section=\"${BASH_REMATCH[1]}\"\n            continue\n        fi\n        value=\"${value%%#*}\"     # Strip inline comments\n        value=\"${value%\"${value##*[![:space:]]}\"}\"  # Trim trailing whitespace\n        printf -v \"${section}_${key}\" '%s' \"$value\"\n    done < \"$file\"\n}\n\nparse_ini config.ini\necho \"$database_host\"  # localhost\necho \"$app_debug\"      # true"
      },
      {
        "title": "Confirm before destructive action",
        "body": "confirm() {\n    local prompt=\"${1:-Are you sure?}\"\n    read -rp \"$prompt [y/N] \" response\n    [[ \"$response\" =~ ^[Yy]$ ]]\n}\n\nconfirm \"Delete all files in /tmp/data?\" || die \"Aborted\"\nrm -rf /tmp/data/*"
      },
      {
        "title": "Progress indicator",
        "body": "# Simple counter\ntotal=$(wc -l < file_list.txt)\ncount=0\nwhile IFS= read -r file; do\n    ((count++))\n    printf \"\\rProcessing %d/%d...\" \"$count\" \"$total\" >&2\n    process \"$file\"\ndone < file_list.txt\necho \"\" >&2"
      },
      {
        "title": "Lock file (prevent concurrent runs)",
        "body": "LOCKFILE=\"/tmp/${SCRIPT_NAME}.lock\"\n\nacquire_lock() {\n    if ! mkdir \"$LOCKFILE\" 2>/dev/null; then\n        die \"Another instance is running (lock: $LOCKFILE)\"\n    fi\n    trap 'rm -rf \"$LOCKFILE\"' EXIT\n}\n\nacquire_lock\n# ... safe to proceed, only one instance runs ..."
      },
      {
        "title": "Stdin or file argument",
        "body": "# Read from file argument or stdin\ninput=\"${1:--}\"   # Default to \"-\" (stdin)\nif [[ \"$input\" == \"-\" ]]; then\n    cat\nelse\n    cat \"$input\"\nfi | while IFS= read -r line; do\n    process \"$line\"\ndone"
      },
      {
        "title": "Tips",
        "body": "Always start with set -euo pipefail. It catches 80% of silent bugs.\nAlways use trap cleanup EXIT for temp files. Never rely on reaching the cleanup code at the end.\nQuote all variable expansions: \"$var\" not $var. Unquoted variables break on spaces and globs.\nUse [[ ]] instead of [ ] in bash. It handles empty strings, spaces, and pattern matching better.\nshellcheck is the best linter for shell scripts. Run it: shellcheck myscript.sh. Install it if available.\nreadonly for constants prevents accidental overwrite: readonly DB_HOST=\"localhost\".\nWrite a usage() function and call it on -h/--help and on missing required arguments. Future users (including you) will thank you.\nPrefer printf over echo for anything that might contain special characters or needs formatting.\nTest scripts with bash -n script.sh (syntax check) before running."
      }
    ],
    "body": "Shell Scripting\n\nWrite reliable, maintainable bash scripts. Covers argument parsing, error handling, portability, temp files, parallel execution, process management, and self-documenting scripts.\n\nWhen to Use\nWriting scripts that others (or future you) will run\nAutomating multi-step workflows\nParsing command-line arguments with flags and options\nHandling errors and cleanup properly\nRunning tasks in parallel\nMaking scripts portable across Linux and macOS\nWrapping complex commands with a simpler interface\nScript Template\n#!/usr/bin/env bash\nset -euo pipefail\n\n# Description: What this script does (one line)\n# Usage: script.sh [options] <required-arg>\n\nreadonly SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nreadonly SCRIPT_NAME=\"$(basename \"$0\")\"\n\n# Defaults\nVERBOSE=false\nOUTPUT_DIR=\"./output\"\n\nusage() {\n    cat <<EOF\nUsage: $SCRIPT_NAME [options] <input-file>\n\nDescription:\n  Process the input file and generate output.\n\nOptions:\n  -o, --output DIR    Output directory (default: $OUTPUT_DIR)\n  -v, --verbose       Enable verbose output\n  -h, --help          Show this help message\n\nExamples:\n  $SCRIPT_NAME data.csv\n  $SCRIPT_NAME -v -o /tmp/results data.csv\nEOF\n}\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >&2; }\ndebug() { $VERBOSE && log \"DEBUG: $*\" || true; }\ndie() { log \"ERROR: $*\"; exit 1; }\n\n# Parse arguments\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        -o|--output) OUTPUT_DIR=\"$2\"; shift 2 ;;\n        -v|--verbose) VERBOSE=true; shift ;;\n        -h|--help) usage; exit 0 ;;\n        --) shift; break ;;\n        -*) die \"Unknown option: $1\" ;;\n        *) break ;;\n    esac\ndone\n\nINPUT_FILE=\"${1:?$(usage >&2; echo \"Error: input file required\")}\"\n[[ -f \"$INPUT_FILE\" ]] || die \"File not found: $INPUT_FILE\"\n\n# Main logic\nmain() {\n    debug \"Input: $INPUT_FILE\"\n    debug \"Output: $OUTPUT_DIR\"\n    mkdir -p \"$OUTPUT_DIR\"\n\n    log \"Processing $INPUT_FILE...\"\n    # ... do work ...\n    log \"Done. Output in $OUTPUT_DIR\"\n}\n\nmain \"$@\"\n\nError Handling\nset flags\nset -e          # Exit on any command failure\nset -u          # Error on undefined variables\nset -o pipefail # Pipe fails if any command in the pipe fails\nset -x          # Debug: print each command before executing (noisy)\n\n# Combined (use this in every script)\nset -euo pipefail\n\n# Temporarily disable for commands that are allowed to fail\nset +e\nsome_command_that_might_fail\nexit_code=$?\nset -e\n\nTrap for cleanup\n# Cleanup on exit (any exit: success, failure, or signal)\nTMPDIR=\"\"\ncleanup() {\n    [[ -n \"$TMPDIR\" ]] && rm -rf \"$TMPDIR\"\n}\ntrap cleanup EXIT\n\nTMPDIR=$(mktemp -d)\n# Use $TMPDIR freely — it's cleaned up automatically\n\n# Trap specific signals\ntrap 'echo \"Interrupted\"; exit 130' INT    # Ctrl+C\ntrap 'echo \"Terminated\"; exit 143' TERM    # kill\n\nError handling patterns\n# Check command exists before using it\ncommand -v jq >/dev/null 2>&1 || die \"jq is required but not installed\"\n\n# Provide default values\nNAME=\"${NAME:-default_value}\"\n\n# Required variable (fail if unset)\n: \"${API_KEY:?Error: API_KEY environment variable is required}\"\n\n# Retry a command\nretry() {\n    local max_attempts=$1\n    shift\n    local attempt=1\n    while [[ $attempt -le $max_attempts ]]; do\n        \"$@\" && return 0\n        log \"Attempt $attempt/$max_attempts failed. Retrying...\"\n        ((attempt++))\n        sleep $((attempt * 2))\n    done\n    die \"Command failed after $max_attempts attempts: $*\"\n}\n\nretry 3 curl -sf https://api.example.com/health\n\nArgument Parsing\nSimple: positional + flags\n# Manual parsing (no dependencies)\nFORCE=false\nDRY_RUN=false\n\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        -f|--force) FORCE=true; shift ;;\n        -n|--dry-run) DRY_RUN=true; shift ;;\n        -o|--output)\n            [[ -n \"${2:-}\" ]] || die \"--output requires a value\"\n            OUTPUT=\"$2\"; shift 2 ;;\n        --output=*)\n            OUTPUT=\"${1#*=}\"; shift ;;\n        -h|--help) usage; exit 0 ;;\n        --) shift; break ;;  # End of options\n        -*) die \"Unknown option: $1\" ;;\n        *) break ;;  # Start of positional args\n    esac\ndone\n\n# Remaining args are positional\nFILES=(\"$@\")\n[[ ${#FILES[@]} -gt 0 ]] || die \"At least one file is required\"\n\ngetopts (POSIX, short options only)\nwhile getopts \":o:vhf\" opt; do\n    case \"$opt\" in\n        o) OUTPUT=\"$OPTARG\" ;;\n        v) VERBOSE=true ;;\n        f) FORCE=true ;;\n        h) usage; exit 0 ;;\n        :) die \"Option -$OPTARG requires an argument\" ;;\n        ?) die \"Unknown option: -$OPTARG\" ;;\n    esac\ndone\nshift $((OPTIND - 1))\n\nTemp Files and Directories\n# Create temp file (automatically unique)\nTMPFILE=$(mktemp)\necho \"data\" > \"$TMPFILE\"\n\n# Create temp directory\nTMPDIR=$(mktemp -d)\n\n# Create temp with custom prefix/suffix\nTMPFILE=$(mktemp /tmp/myapp.XXXXXX)\nTMPFILE=$(mktemp --suffix=.json)  # GNU only\n\n# Always clean up with trap\ntrap 'rm -f \"$TMPFILE\"' EXIT\n\n# Portable pattern (works on macOS and Linux)\nTMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'myapp')\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n\nParallel Execution\nxargs -P\n# Run 4 commands in parallel\ncat urls.txt | xargs -P 4 -I {} curl -sO {}\n\n# Process files in parallel (4 at a time)\nfind . -name \"*.csv\" | xargs -P 4 -I {} ./process.sh {}\n\n# Parallel with progress indicator\nfind . -name \"*.jpg\" | xargs -P 8 -I {} sh -c 'convert {} -resize 800x600 resized/{} && echo \"Done: {}\"'\n\nBackground jobs + wait\n# Run tasks in background, wait for all\npids=()\nfor file in data/*.csv; do\n    process_file \"$file\" &\n    pids+=($!)\ndone\n\n# Wait for all and check results\nfailed=0\nfor pid in \"${pids[@]}\"; do\n    wait \"$pid\" || ((failed++))\ndone\n[[ $failed -eq 0 ]] || die \"$failed jobs failed\"\n\nGNU Parallel (if available)\n# Process files with 8 parallel jobs\nparallel -j 8 ./process.sh {} ::: data/*.csv\n\n# With progress bar\nparallel --bar -j 4 convert {} -resize 800x600 resized/{/} ::: *.jpg\n\n# Pipe input lines\ncat urls.txt | parallel -j 10 curl -sO {}\n\nProcess Management\nBackground processes\n# Start in background\nlong_running_command &\nBG_PID=$!\n\n# Check if still running\nkill -0 $BG_PID 2>/dev/null && echo \"Running\" || echo \"Stopped\"\n\n# Wait for it\nwait $BG_PID\necho \"Exit code: $?\"\n\n# Kill on script exit\ntrap 'kill $BG_PID 2>/dev/null' EXIT\n\nProcess supervision\n# Run a command, restart if it dies\nrun_with_restart() {\n    local cmd=(\"$@\")\n    while true; do\n        \"${cmd[@]}\" &\n        local pid=$!\n        log \"Started PID $pid\"\n        wait $pid\n        local exit_code=$?\n        log \"Process exited with code $exit_code. Restarting in 5s...\"\n        sleep 5\n    done\n}\n\nrun_with_restart ./my-server --port 8080\n\nTimeout\n# Kill command after 30 seconds\ntimeout 30 long_running_command\n\n# With custom signal (SIGKILL after SIGTERM fails)\ntimeout --signal=TERM --kill-after=10 30 long_running_command\n\n# Portable (no timeout command)\n( sleep 30; kill $$ 2>/dev/null ) &\nTIMER_PID=$!\nlong_running_command\nkill $TIMER_PID 2>/dev/null\n\nPortability (Linux vs macOS)\nCommon differences\n# sed: macOS requires -i '' (empty backup extension)\n# Linux:\nsed -i 's/old/new/g' file.txt\n# macOS:\nsed -i '' 's/old/new/g' file.txt\n# Portable:\nsed -i.bak 's/old/new/g' file.txt && rm file.txt.bak\n\n# date: different flags\n# GNU (Linux):\ndate -d '2026-02-03' '+%s'\n# BSD (macOS):\ndate -j -f '%Y-%m-%d' '2026-02-03' '+%s'\n\n# readlink -f: doesn't exist on macOS\n# Portable alternative:\nreal_path() { cd \"$(dirname \"$1\")\" && echo \"$(pwd)/$(basename \"$1\")\"; }\n\n# stat: different syntax\n# GNU: stat -c '%s' file\n# BSD: stat -f '%z' file\n\n# grep -P: not available on macOS by default\n# Use grep -E instead, or install GNU grep\n\nPOSIX-safe patterns\n# Use printf instead of echo -e (echo behavior varies)\nprintf \"Line 1\\nLine 2\\n\"\n\n# Use $() instead of backticks\nresult=$(command)   # Good\nresult=`command`    # Bad (deprecated, nesting issues)\n\n# Use [[ ]] for tests (bash), [ ] for POSIX sh\n[[ -f \"$file\" ]]   # Bash (safer, no word splitting)\n[ -f \"$file\" ]     # POSIX sh\n\n# Array check (bash only, not POSIX)\nif [[ ${#array[@]} -gt 0 ]]; then\n    echo \"Array has elements\"\nfi\n\nConfig File Parsing\nSource a config file\n# Simple: source a key=value file\n# config.env:\n# DB_HOST=localhost\n# DB_PORT=5432\n\n# Validate before sourcing (security: check for commands)\nif grep -qP '^[A-Z_]+=.*[;\\`\\$\\(]' config.env; then\n    die \"Config file contains unsafe characters\"\nfi\nsource config.env\n\nParse INI-style config\n# config.ini:\n# [database]\n# host = localhost\n# port = 5432\n# [app]\n# debug = true\n\nparse_ini() {\n    local file=\"$1\" section=\"\"\n    while IFS='= ' read -r key value; do\n        [[ -z \"$key\" || \"$key\" =~ ^[#\\;] ]] && continue\n        if [[ \"$key\" =~ ^\\[(.+)\\]$ ]]; then\n            section=\"${BASH_REMATCH[1]}\"\n            continue\n        fi\n        value=\"${value%%#*}\"     # Strip inline comments\n        value=\"${value%\"${value##*[![:space:]]}\"}\"  # Trim trailing whitespace\n        printf -v \"${section}_${key}\" '%s' \"$value\"\n    done < \"$file\"\n}\n\nparse_ini config.ini\necho \"$database_host\"  # localhost\necho \"$app_debug\"      # true\n\nUseful Patterns\nConfirm before destructive action\nconfirm() {\n    local prompt=\"${1:-Are you sure?}\"\n    read -rp \"$prompt [y/N] \" response\n    [[ \"$response\" =~ ^[Yy]$ ]]\n}\n\nconfirm \"Delete all files in /tmp/data?\" || die \"Aborted\"\nrm -rf /tmp/data/*\n\nProgress indicator\n# Simple counter\ntotal=$(wc -l < file_list.txt)\ncount=0\nwhile IFS= read -r file; do\n    ((count++))\n    printf \"\\rProcessing %d/%d...\" \"$count\" \"$total\" >&2\n    process \"$file\"\ndone < file_list.txt\necho \"\" >&2\n\nLock file (prevent concurrent runs)\nLOCKFILE=\"/tmp/${SCRIPT_NAME}.lock\"\n\nacquire_lock() {\n    if ! mkdir \"$LOCKFILE\" 2>/dev/null; then\n        die \"Another instance is running (lock: $LOCKFILE)\"\n    fi\n    trap 'rm -rf \"$LOCKFILE\"' EXIT\n}\n\nacquire_lock\n# ... safe to proceed, only one instance runs ...\n\nStdin or file argument\n# Read from file argument or stdin\ninput=\"${1:--}\"   # Default to \"-\" (stdin)\nif [[ \"$input\" == \"-\" ]]; then\n    cat\nelse\n    cat \"$input\"\nfi | while IFS= read -r line; do\n    process \"$line\"\ndone\n\nTips\nAlways start with set -euo pipefail. It catches 80% of silent bugs.\nAlways use trap cleanup EXIT for temp files. Never rely on reaching the cleanup code at the end.\nQuote all variable expansions: \"$var\" not $var. Unquoted variables break on spaces and globs.\nUse [[ ]] instead of [ ] in bash. It handles empty strings, spaces, and pattern matching better.\nshellcheck is the best linter for shell scripts. Run it: shellcheck myscript.sh. Install it if available.\nreadonly for constants prevents accidental overwrite: readonly DB_HOST=\"localhost\".\nWrite a usage() function and call it on -h/--help and on missing required arguments. Future users (including you) will thank you.\nPrefer printf over echo for anything that might contain special characters or needs formatting.\nTest scripts with bash -n script.sh (syntax check) before running."
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/gitgoodordietrying/shell-scripting",
    "publisherUrl": "https://clawhub.ai/gitgoodordietrying/shell-scripting",
    "owner": "gitgoodordietrying",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/shell-scripting",
    "downloadUrl": "https://openagent3.xyz/downloads/shell-scripting",
    "agentUrl": "https://openagent3.xyz/skills/shell-scripting/agent",
    "manifestUrl": "https://openagent3.xyz/skills/shell-scripting/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/shell-scripting/agent.md"
  }
}