[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6MS4J7PC7C3S4G9CEPH2RZQ":3,"skill-same:01M6MS4J7PC7C3S4G9CEPH2RZQ":78},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":38,"linkOnly":39,"markdownContent":40,"markdownBody":41,"frontMatter":42,"refName":50,"commitSha":51,"skillDirectory":52,"forksCount":53,"repositoryHtmlUrl":54,"repositoryHomepageUrl":6,"ownerHtmlUrl":55,"ownerBlogUrl":6,"ownerDescription":56,"updatedAt":57,"install":58,"resources":71},"01M6MS4J7PC7C3S4G9CEPH2RZQ","shell-scripting",null,"Write robust, portable shell scripts. Use when parsing arguments, handling errors properly, writing POSIX-compatible scripts, managing temp files, running commands in parallel, managing background processes, or adding --help to scripts.","Kernel8901\u002Fai-agent-skills-classification","Kernel8901","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F72647553?v=4","01M6KV388J2H5PNC8D8P940BJD","01M6KV38CS5B3AY63RKZB410YM",7,"MIT","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002FCLI工具\u002Fshell-scripting\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],"scripting","shell","shell scripting","write","darwin","posix","errors","processes","scripts","commands","files","use","writing","parallel","Bash","shell scripts","parsing","win32","help","linux","2026-11-10 10:12:30",false,"---\nname: shell-scripting\ndescription: Write robust, portable shell scripts. Use when parsing arguments, handling errors properly, writing POSIX-compatible scripts, managing temp files, running commands in parallel, managing background processes, or adding --help to scripts.\nmetadata: {\"clawdbot\":{\"emoji\":\"?\",\"requires\":{\"bins\":[\"bash\"]},\"os\":[\"linux\",\"darwin\",\"win32\"]}}\n---\n\n# 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\n## When to Use\n\n- Writing scripts that others (or future you) will run\n- Automating multi-step workflows\n- Parsing command-line arguments with flags and options\n- Handling errors and cleanup properly\n- Running tasks in parallel\n- Making scripts portable across Linux and macOS\n- Wrapping complex commands with a simpler interface\n\n## Script Template\n\n```bash\n#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\n# Description: What this script does (one line)\n# Usage: script.sh [options] \u003Crequired-arg>\n\nreadonly SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nreadonly SCRIPT_NAME=\"$(basename \"$0\")\"\n\n# Defaults\nVERBOSE=false\nOUTPUT_DIR=\".\u002Foutput\"\n\nusage() {\n    cat \u003C\u003CEOF\nUsage: $SCRIPT_NAME [options] \u003Cinput-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 \u002Ftmp\u002Fresults 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```\n\n## Error Handling\n\n### set flags\n\n```bash\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```\n\n### Trap for cleanup\n\n```bash\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```\n\n### Error handling patterns\n\n```bash\n# Check command exists before using it\ncommand -v jq >\u002Fdev\u002Fnull 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\u002F$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:\u002F\u002Fapi.example.com\u002Fhealth\n```\n\n## Argument Parsing\n\n### Simple: positional + flags\n\n```bash\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```\n\n### getopts (POSIX, short options only)\n\n```bash\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```\n\n## Temp Files and Directories\n\n```bash\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\u002Fsuffix\nTMPFILE=$(mktemp \u002Ftmp\u002Fmyapp.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>\u002Fdev\u002Fnull || mktemp -d -t 'myapp')\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n```\n\n## Parallel Execution\n\n### xargs -P\n\n```bash\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 {} .\u002Fprocess.sh {}\n\n# Parallel with progress indicator\nfind . -name \"*.jpg\" | xargs -P 8 -I {} sh -c 'convert {} -resize 800x600 resized\u002F{} && echo \"Done: {}\"'\n```\n\n### Background jobs + wait\n\n```bash\n# Run tasks in background, wait for all\npids=()\nfor file in data\u002F*.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```\n\n### GNU Parallel (if available)\n\n```bash\n# Process files with 8 parallel jobs\nparallel -j 8 .\u002Fprocess.sh {} ::: data\u002F*.csv\n\n# With progress bar\nparallel --bar -j 4 convert {} -resize 800x600 resized\u002F{\u002F} ::: *.jpg\n\n# Pipe input lines\ncat urls.txt | parallel -j 10 curl -sO {}\n```\n\n## Process Management\n\n### Background processes\n\n```bash\n# Start in background\nlong_running_command &\nBG_PID=$!\n\n# Check if still running\nkill -0 $BG_PID 2>\u002Fdev\u002Fnull && 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>\u002Fdev\u002Fnull' EXIT\n```\n\n### Process supervision\n\n```bash\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 .\u002Fmy-server --port 8080\n```\n\n### Timeout\n\n```bash\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>\u002Fdev\u002Fnull ) &\nTIMER_PID=$!\nlong_running_command\nkill $TIMER_PID 2>\u002Fdev\u002Fnull\n```\n\n## Portability (Linux vs macOS)\n\n### Common differences\n\n```bash\n# sed: macOS requires -i '' (empty backup extension)\n# Linux:\nsed -i 's\u002Fold\u002Fnew\u002Fg' file.txt\n# macOS:\nsed -i '' 's\u002Fold\u002Fnew\u002Fg' file.txt\n# Portable:\nsed -i.bak 's\u002Fold\u002Fnew\u002Fg' 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)\u002F$(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```\n\n### POSIX-safe patterns\n\n```bash\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```\n\n## Config File Parsing\n\n### Source a config file\n\n```bash\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```\n\n### Parse INI-style config\n\n```bash\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 \u003C \"$file\"\n}\n\nparse_ini config.ini\necho \"$database_host\"  # localhost\necho \"$app_debug\"      # true\n```\n\n## Useful Patterns\n\n### Confirm before destructive action\n\n```bash\nconfirm() {\n    local prompt=\"${1:-Are you sure?}\"\n    read -rp \"$prompt [y\u002FN] \" response\n    [[ \"$response\" =~ ^[Yy]$ ]]\n}\n\nconfirm \"Delete all files in \u002Ftmp\u002Fdata?\" || die \"Aborted\"\nrm -rf \u002Ftmp\u002Fdata\u002F*\n```\n\n### Progress indicator\n\n```bash\n# Simple counter\ntotal=$(wc -l \u003C file_list.txt)\ncount=0\nwhile IFS= read -r file; do\n    ((count++))\n    printf \"\\rProcessing %d\u002F%d...\" \"$count\" \"$total\" >&2\n    process \"$file\"\ndone \u003C file_list.txt\necho \"\" >&2\n```\n\n### Lock file (prevent concurrent runs)\n\n```bash\nLOCKFILE=\"\u002Ftmp\u002F${SCRIPT_NAME}.lock\"\n\nacquire_lock() {\n    if ! mkdir \"$LOCKFILE\" 2>\u002Fdev\u002Fnull; 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```\n\n### Stdin or file argument\n\n```bash\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```\n\n## Tips\n\n- Always start with `set -euo pipefail`. It catches 80% of silent bugs.\n- Always use `trap cleanup EXIT` for temp files. Never rely on reaching the cleanup code at the end.\n- Quote all variable expansions: `\"$var\"` not `$var`. Unquoted variables break on spaces and globs.\n- Use `[[ ]]` instead of `[ ]` in bash. It handles empty strings, spaces, and pattern matching better.\n- `shellcheck` is the best linter for shell scripts. Run it: `shellcheck myscript.sh`. Install it if available.\n- `readonly` for constants prevents accidental overwrite: `readonly DB_HOST=\"localhost\"`.\n- Write a `usage()` function and call it on `-h`\u002F`--help` and on missing required arguments. Future users (including you) will thank you.\n- Prefer `printf` over `echo` for anything that might contain special characters or needs formatting.\n- Test scripts with `bash -n script.sh` (syntax check) before running.\n","# 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\n## When to Use\n\n- Writing scripts that others (or future you) will run\n- Automating multi-step workflows\n- Parsing command-line arguments with flags and options\n- Handling errors and cleanup properly\n- Running tasks in parallel\n- Making scripts portable across Linux and macOS\n- Wrapping complex commands with a simpler interface\n\n## Script Template\n\n```bash\n#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\n# Description: What this script does (one line)\n# Usage: script.sh [options] \u003Crequired-arg>\n\nreadonly SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nreadonly SCRIPT_NAME=\"$(basename \"$0\")\"\n\n# Defaults\nVERBOSE=false\nOUTPUT_DIR=\".\u002Foutput\"\n\nusage() {\n    cat \u003C\u003CEOF\nUsage: $SCRIPT_NAME [options] \u003Cinput-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 \u002Ftmp\u002Fresults 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```\n\n## Error Handling\n\n### set flags\n\n```bash\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```\n\n### Trap for cleanup\n\n```bash\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```\n\n### Error handling patterns\n\n```bash\n# Check command exists before using it\ncommand -v jq >\u002Fdev\u002Fnull 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\u002F$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:\u002F\u002Fapi.example.com\u002Fhealth\n```\n\n## Argument Parsing\n\n### Simple: positional + flags\n\n```bash\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```\n\n### getopts (POSIX, short options only)\n\n```bash\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```\n\n## Temp Files and Directories\n\n```bash\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\u002Fsuffix\nTMPFILE=$(mktemp \u002Ftmp\u002Fmyapp.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>\u002Fdev\u002Fnull || mktemp -d -t 'myapp')\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n```\n\n## Parallel Execution\n\n### xargs -P\n\n```bash\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 {} .\u002Fprocess.sh {}\n\n# Parallel with progress indicator\nfind . -name \"*.jpg\" | xargs -P 8 -I {} sh -c 'convert {} -resize 800x600 resized\u002F{} && echo \"Done: {}\"'\n```\n\n### Background jobs + wait\n\n```bash\n# Run tasks in background, wait for all\npids=()\nfor file in data\u002F*.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```\n\n### GNU Parallel (if available)\n\n```bash\n# Process files with 8 parallel jobs\nparallel -j 8 .\u002Fprocess.sh {} ::: data\u002F*.csv\n\n# With progress bar\nparallel --bar -j 4 convert {} -resize 800x600 resized\u002F{\u002F} ::: *.jpg\n\n# Pipe input lines\ncat urls.txt | parallel -j 10 curl -sO {}\n```\n\n## Process Management\n\n### Background processes\n\n```bash\n# Start in background\nlong_running_command &\nBG_PID=$!\n\n# Check if still running\nkill -0 $BG_PID 2>\u002Fdev\u002Fnull && 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>\u002Fdev\u002Fnull' EXIT\n```\n\n### Process supervision\n\n```bash\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 .\u002Fmy-server --port 8080\n```\n\n### Timeout\n\n```bash\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>\u002Fdev\u002Fnull ) &\nTIMER_PID=$!\nlong_running_command\nkill $TIMER_PID 2>\u002Fdev\u002Fnull\n```\n\n## Portability (Linux vs macOS)\n\n### Common differences\n\n```bash\n# sed: macOS requires -i '' (empty backup extension)\n# Linux:\nsed -i 's\u002Fold\u002Fnew\u002Fg' file.txt\n# macOS:\nsed -i '' 's\u002Fold\u002Fnew\u002Fg' file.txt\n# Portable:\nsed -i.bak 's\u002Fold\u002Fnew\u002Fg' 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)\u002F$(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```\n\n### POSIX-safe patterns\n\n```bash\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```\n\n## Config File Parsing\n\n### Source a config file\n\n```bash\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```\n\n### Parse INI-style config\n\n```bash\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 \u003C \"$file\"\n}\n\nparse_ini config.ini\necho \"$database_host\"  # localhost\necho \"$app_debug\"      # true\n```\n\n## Useful Patterns\n\n### Confirm before destructive action\n\n```bash\nconfirm() {\n    local prompt=\"${1:-Are you sure?}\"\n    read -rp \"$prompt [y\u002FN] \" response\n    [[ \"$response\" =~ ^[Yy]$ ]]\n}\n\nconfirm \"Delete all files in \u002Ftmp\u002Fdata?\" || die \"Aborted\"\nrm -rf \u002Ftmp\u002Fdata\u002F*\n```\n\n### Progress indicator\n\n```bash\n# Simple counter\ntotal=$(wc -l \u003C file_list.txt)\ncount=0\nwhile IFS= read -r file; do\n    ((count++))\n    printf \"\\rProcessing %d\u002F%d...\" \"$count\" \"$total\" >&2\n    process \"$file\"\ndone \u003C file_list.txt\necho \"\" >&2\n```\n\n### Lock file (prevent concurrent runs)\n\n```bash\nLOCKFILE=\"\u002Ftmp\u002F${SCRIPT_NAME}.lock\"\n\nacquire_lock() {\n    if ! mkdir \"$LOCKFILE\" 2>\u002Fdev\u002Fnull; 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```\n\n### Stdin or file argument\n\n```bash\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```\n\n## Tips\n\n- Always start with `set -euo pipefail`. It catches 80% of silent bugs.\n- Always use `trap cleanup EXIT` for temp files. Never rely on reaching the cleanup code at the end.\n- Quote all variable expansions: `\"$var\"` not `$var`. Unquoted variables break on spaces and globs.\n- Use `[[ ]]` instead of `[ ]` in bash. It handles empty strings, spaces, and pattern matching better.\n- `shellcheck` is the best linter for shell scripts. Run it: `shellcheck myscript.sh`. Install it if available.\n- `readonly` for constants prevents accidental overwrite: `readonly DB_HOST=\"localhost\"`.\n- Write a `usage()` function and call it on `-h`\u002F`--help` and on missing required arguments. Future users (including you) will thank you.\n- Prefer `printf` over `echo` for anything that might contain special characters or needs formatting.\n- Test scripts with `bash -n script.sh` (syntax check) before running.\n",{"name":5,"metadata":43,"description":7},{"clawdbot":44},{"os":45,"emoji":46,"requires":47},[37,22,35],"?",{"bins":48},[49],"bash","main","f4cb2d49f7667e79ef7804f50396d5aaffdf1f56","CLI工具\u002Fshell-scripting",1,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification","https:\u002F\u002Fgithub.com\u002FKernel8901","a little little little dobe in life but science geek","2026-04-05 16:43:56",{"source":8,"skillName":5,"commands":59,"prompt":69,"zipAvailable":70},[60,63,66],{"tool":61,"command":62},"npx","npx skills add Kernel8901\u002Fai-agent-skills-classification --skill shell-scripting",{"tool":64,"command":65},"bunx","bunx skills add Kernel8901\u002Fai-agent-skills-classification --skill shell-scripting",{"tool":67,"command":68},"pnpm","pnpm dlx skills add Kernel8901\u002Fai-agent-skills-classification --skill shell-scripting","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002FCLI工具\u002Fshell-scripting\u002FSKILL.md",true,[72],{"path":73,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":76,"sourceHtmlUrl":77},"_meta.json","REFERENCE","参考文档",294,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002FCLI工具\u002Fshell-scripting\u002F_meta.json",[79,102,138,149,159,189],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":38},"01M6Q5R6WZ06RRB9RQTW54B34B","zyfai","Earn yield on any Ethereum wallet on Base, Arbitrum, and Plasma. Use when a user wants passive DeFi yield on their funds. Deploys a non-custodial subaccount (Safe) linked to their EOA, enables automated yield optimization, and lets them deposit\u002Fwithdraw anytime.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzyfai-sdk\u002FSKILL.md",[],[81,86,87,88,89,90,29,91,92,93,94,95,96,97,98,99,100,101],"safe","ethereum","defi","Optimization","user","Wallet","base","deposit","withdraw","yield","arbitrum","non-custodial","passive","defi yield","earn","yield optimization",{"skillNo":103,"name":104,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":120,"publishedAt":38},"01M6Q5R337B5JSNER4P4YS03TN","zerion-api","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api\u002FSKILL.md",[107,112,115],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":113,"categoryName":114,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":116,"categoryName":117,"taxonomyType":118,"taxonomyTypeDesc":119},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[121,122,123,124,125,126,127,88,128,91,129,130,131,132,133,134,135,136,137],"api","zerion api","zerion","MCP","mcp server","server","blockchain","transactions","crypto wallet","crypto","evm","solana","query","across","positions","token","prices",{"skillNo":139,"name":140,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":146,"publishedAt":38},"01M6Q5QZFYTDAZMZ27BN5VZXAR","zerion-api-skill","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api-skill\u002FSKILL.md",[143,144,145],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},{"categoryNo":113,"categoryName":114,"taxonomyType":110,"taxonomyTypeDesc":111},{"categoryNo":116,"categoryName":117,"taxonomyType":118,"taxonomyTypeDesc":119},[121,147,148,122,123,124,125,126,127,88,128,91,130,131,132,133,134,135,136,137],"api skill","zerion api skill",{"skillNo":150,"name":151,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":152,"categories":153,"keywords":157,"publishedAt":38},"01M6Q5QW17QW98RA3FE45E4RD8","zerion-api-mcp","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api-mcp\u002FSKILL.md",[154,155,156],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},{"categoryNo":113,"categoryName":114,"taxonomyType":110,"taxonomyTypeDesc":111},{"categoryNo":116,"categoryName":117,"taxonomyType":118,"taxonomyTypeDesc":119},[124,121,158,122,123,125,126,88,128,91,129,130,131,132,133,134,135,136,137],"zerion api mcp",{"skillNo":160,"name":161,"nameZh":6,"description":162,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":163,"categories":164,"keywords":168,"publishedAt":38},"01M6Q5QQYZB7A501BDEQWKGSJT","yahoo-finance-forex","Fetches real-time FOREX news and market data from Yahoo Finance for major currency pairs (EUR\u002FUSD, GBP\u002FUSD, USD\u002FJPY, etc.). Analyzes sentiment and provides trading context.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fyahoo-finance-forex\u002FSKILL.md",[165],{"categoryNo":166,"categoryName":167,"taxonomyType":118,"taxonomyTypeDesc":119},"01JBSEEDCAT000000000000040","财务",[169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188],"finance","yahoo finance","forex","yahoo","yahoo finance forex","github","https","sentiment","real-time","python3","install","Market","usd","news","time","currency","gbp","pip","market data","for",{"skillNo":190,"name":191,"nameZh":6,"description":192,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":193,"categories":194,"keywords":196,"publishedAt":38},"01M6Q5QM6VS84WZVM7BKXXPF32","yahoo-finance","This skill should be used when the user asks to \"get stock prices\", \"check stock quotes\", \"look up earnings\", \"get financial data\", \"find trending stocks\", or needs stock market data from Yahoo Finance.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fyahoo-finance-cli\u002FSKILL.md",[195],{"categoryNo":166,"categoryName":167,"taxonomyType":118,"taxonomyTypeDesc":119},[169,170,172,197,198,199,200,201,202,180,203,204,205,206,207,208,209,210,137,187],"cli","homebrew","find","check","brew","up","local","get","stock","link","trending","sf","quotes","ln"]