新增SHELL得SKILL

This commit is contained in:
zeaslity
2026-01-22 10:26:01 +08:00
parent 631cce9e1e
commit 0d511d9a03
11 changed files with 1154 additions and 3 deletions

View File

@@ -1,6 +1,6 @@
---
name: developing-project-management
description: Guides development of rmdc-project-management module including project lifecycle management, version control (Git-like), ACL permissions, TOTP authorization, and workflow integration. Triggered when modifying project CRUD, draft/version APIs, permission grants, or authorization features. Keywords: project lifecycle, version snapshot, ACL, TOTP, workflow callback, SuperAdmin.
description: Guides development of rmdc-project-management module including project lifecycle management, version control (Git-like), ACL permissions, TOTP authorization, and workflow integration. Triggered when modifying project CRUD, draft/version APIs, permission grants, or authorization features. Keywords: project mangement, project lifecycle, version snapshot, ACL, TOTP, workflow callback, SuperAdmin.
argument-hint: "<change-type> [target]" where change-type is one of: api|entity|service|migration|frontend|auth. Example: "api draft-submit" or "migration add-field"
allowed-tools:
- Read

View File

@@ -0,0 +1,204 @@
---
name: writing-bash-scripts
description: Generates production-grade Bash scripts with strict mode, modular structure, unified logging, error handling, and ShellCheck compliance. Triggered by requests like "write a bash script", "create shell script", "generate CLI tool in bash". Keywords: bash, shell, script, devops.
argument-hint: "<functional-requirement-description>"
allowed-tools:
- Read
- Write
- Bash
- Glob
- Grep
---
# Writing Production-Grade Bash Scripts
## Purpose
Transform functional requirements into a single, deployable Bash script following engineering best practices.
## Arguments
`$ARGUMENTS` = functional requirement description (目标、输入/输出、流程、约束、示例数据等)
If `$ARGUMENTS` is empty, prompt user for:
- Target functionality
- Input/output specification
- Constraints (OS, permissions, dependencies)
## Dynamic Context
!`echo "Current working directory: $(pwd)"`
!`bash --version | head -1`
---
## Plan Phase
### Deliverables Checklist
- [ ] Single `.sh` file with shebang `#!/usr/bin/env bash`
- [ ] Bash >= 5.0 requirement documented
- [ ] ASCII call graph at script top
- [ ] usage/help function (-h/--help)
- [ ] Parameter parsing (getopts)
- [ ] Exit code documentation
- [ ] --dry-run support
- [ ] --verbose/--quiet support
- [ ] Cleanup trap for temp files
### Decision Points
| Decision | Default | Override Condition |
|----------|---------|-------------------|
| POSIX vs Bash-specific | POSIX preferred | Use Bash features only when necessary (arrays, `[[ ]]`, mapfile); comment reason |
| Destructive ops | Require `--force` | Never auto-delete without confirmation |
| Temp file location | `mktemp -d` | User-specified via `--tmp-dir` |
---
## Execute Phase
### Script Structure (固定顺序)
```
1. Shebang + Metadata
2. ASCII Call Graph
3. Global Constants (readonly)
4. Strict Mode Setup
5. Dependency/Environment Checks
6. Logging & Error Handling Functions
7. Business Functions
8. main() + Entry Point
9. Self-test Examples (comments)
```
### Strict Mode (必须)
```bash
set -Eeuo pipefail
IFS=$' \t\n'
```
### Required Functions
| Function | Purpose |
|----------|---------|
| `usage` | Print help with examples |
| `die` | `die <msg> <exit_code>` - unified error exit |
| `log_debug/info/warn/error` | Timestamped logging with LOG_LEVEL control |
| `run_cmd` | Command wrapper: dry-run, retry, error capture |
| `cleanup` | Remove temp files/locks on EXIT/ERR/INT/TERM |
### Function Documentation Format
```bash
### <one-line description>
### @param <name> <type> <purpose>
### @return <exit_code> <status>
### @require <dependency/permission>
### @side_effect <file/network/system impact>
function_name() {
...
}
```
### Exit Codes
| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | General error |
| 2 | Invalid arguments |
| 3 | Missing dependency |
| 4 | Permission denied |
| 5 | File/resource not found |
| 126 | Command not executable |
| 127 | Command not found |
> See `reference/exit-codes.md` for extended codes.
### Logging Format
```
[YYYY-MM-DD HH:MM:SS] [LEVEL] message
```
---
## Verify Phase
### Pre-Delivery Checklist
#### Frontmatter & Structure
- [ ] Shebang is `#!/usr/bin/env bash`
- [ ] Bash version requirement stated
- [ ] ASCII call graph present
- [ ] Sections in correct order
#### Safety & Defaults
- [ ] `set -Eeuo pipefail` enabled
- [ ] All variables double-quoted (except intentional word splitting with comment)
- [ ] No unquoted `$@` or `$*`
- [ ] No unsafe `eval`
- [ ] `--dry-run` implemented
- [ ] `--force` required for destructive ops
- [ ] `mktemp` used for temp files
- [ ] trap covers EXIT/ERR/INT/TERM
#### Functions
- [ ] Every function has `###` doc block
- [ ] `@param`, `@return`, `@require` present
- [ ] snake_case naming
- [ ] Input validation on all external inputs
#### Logging & Errors
- [ ] `log_*` functions implemented
- [ ] LOG_LEVEL controls output
- [ ] `die` includes line number and function stack
- [ ] `run_cmd` wrapper used for external commands
#### Quality
- [ ] Passes ShellCheck (or disables with `# shellcheck disable=SCxxxx` + reason)
- [ ] 3+ self-test examples in comments (normal, error, dry-run)
- [ ] No hardcoded paths without override option
### ShellCheck Validation
```bash
shellcheck -x -s bash <script.sh>
```
> Use `scripts/shellcheck-wrapper.sh` for automated checking.
---
## Common Pitfalls
| Pitfall | Fix |
|---------|-----|
| Unquoted variables in paths with spaces | Always `"$var"` |
| Missing `local` in functions | Declare with `local var=...` |
| Assuming GNU tools on macOS | Check `uname` or use portable flags |
| Ignoring exit code of piped commands | `set -o pipefail` + check `${PIPESTATUS[@]}` |
| Temp files left on error | Trap cleanup on ERR/EXIT |
---
## Reference Files
| When | Read |
|------|------|
| Need detailed code structure | `reference/code-structure.md` |
| Writing function docs | `reference/function-template.md` |
| Defining custom exit codes | `reference/exit-codes.md` |
| Starting from scratch | `examples/minimal-template.sh` |
---
## Minimal Self-Test Commands
```bash
# 1. Help
./script.sh --help
# 2. Dry-run
./script.sh --dry-run <args>
# 3. Invalid argument (expect exit 2)
./script.sh --invalid-option
# 4. Verbose mode
./script.sh --verbose <args>
```

View File

@@ -0,0 +1,362 @@
#!/usr/bin/env bash
#===============================================================================
# Script Name : minimal-template.sh
# Description : Minimal production-grade Bash script template
# Author : <author>
# Version : 1.0.0
# License : MIT
# Last Updated : <date>
# Bash Version : >= 5.0
#
# Assumptions:
# - Running on Linux/macOS with GNU coreutils
# - UTF-8 locale available
#===============================================================================
#===============================================================================
# ASCII CALL GRAPH
#===============================================================================
# main()
# ├── parse_args()
# ├── check_dependencies()
# ├── setup_temp()
# ├── do_work()
# │ └── helper_function()
# └── cleanup() [via trap]
#===============================================================================
#===============================================================================
# GLOBAL CONSTANTS
#===============================================================================
readonly VERSION="1.0.0"
readonly SCRIPT_NAME="${0##*/}"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Exit codes
readonly E_SUCCESS=0
readonly E_GENERAL=1
readonly E_ARGS=2
readonly E_DEPS=3
readonly E_PERM=4
readonly E_NOTFOUND=5
#===============================================================================
# STRICT MODE
#===============================================================================
set -Eeuo pipefail
IFS=$' \t\n'
#===============================================================================
# CONFIGURABLE DEFAULTS
#===============================================================================
LOG_LEVEL="${LOG_LEVEL:-INFO}"
DRY_RUN=false
VERBOSE=false
FORCE=false
TEMP_DIR=""
#===============================================================================
# LOGGING & ERROR HANDLING
#===============================================================================
### Log message at DEBUG level
### @param message string Message to log
### @return 0 Always
log_debug() {
[[ "${LOG_LEVEL}" == "DEBUG" ]] || return 0
printf '[%s] [DEBUG] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
### Log message at INFO level
### @param message string Message to log
### @return 0 Always
log_info() {
[[ "${LOG_LEVEL}" =~ ^(DEBUG|INFO)$ ]] || return 0
printf '[%s] [INFO] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
### Log message at WARN level
### @param message string Message to log
### @return 0 Always
log_warn() {
[[ "${LOG_LEVEL}" =~ ^(DEBUG|INFO|WARN)$ ]] || return 0
printf '[%s] [WARN] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
### Log message at ERROR level
### @param message string Message to log
### @return 0 Always
log_error() {
printf '[%s] [ERROR] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
### Exit with error message, location info, and exit code
### @param message string Error message
### @param exit_code int Exit code (default: 1)
### @return never Exits script
### @side_effect Terminates script
die() {
local message="$1"
local exit_code="${2:-$E_GENERAL}"
local caller_info=""
# > Build caller info for debugging
if [[ "${BASH_LINENO[0]:-0}" -gt 0 ]]; then
caller_info=" (${FUNCNAME[1]:-main}:${BASH_LINENO[0]:-?})"
fi
log_error "${message}${caller_info}"
exit "$exit_code"
}
### Execute command with dry-run support
### @param cmd array Command and arguments
### @return 0 Success
### @return 1 Command failed
### @side_effect Executes external command
run_cmd() {
local -a cmd=("$@")
if [[ "${DRY_RUN}" == "true" ]]; then
log_info "[DRY-RUN] Would execute: ${cmd[*]}"
return 0
fi
[[ "${VERBOSE}" == "true" ]] && log_debug "Executing: ${cmd[*]}"
if ! "${cmd[@]}"; then
log_error "Command failed: ${cmd[*]}"
return 1
fi
}
### Cleanup temporary resources
### @return 0 Always
### @side_effect Removes temp directory
cleanup() {
local exit_code=$?
if [[ -n "${TEMP_DIR:-}" && -d "${TEMP_DIR}" ]]; then
rm -rf "${TEMP_DIR}"
log_debug "Cleaned up temp dir: ${TEMP_DIR}"
fi
exit "$exit_code"
}
# > Set traps for cleanup on exit/error/interrupt
trap cleanup EXIT ERR INT TERM
#===============================================================================
# DEPENDENCY & ENVIRONMENT CHECKS
#===============================================================================
### Check Bash version meets minimum requirement
### @return 0 Version OK
### @return 1 Version too old
### @require Bash >= 5.0
check_bash_version() {
local required_major=5
local required_minor=0
local current_major="${BASH_VERSINFO[0]}"
local current_minor="${BASH_VERSINFO[1]}"
if (( current_major < required_major )) || \
(( current_major == required_major && current_minor < required_minor )); then
die "Bash >= ${required_major}.${required_minor} required (current: ${current_major}.${current_minor})" "$E_DEPS"
fi
}
### Check required external commands exist
### @return 0 All dependencies present
### @return 3 Missing dependency
check_dependencies() {
local -a required_cmds=("date" "mktemp" "rm") # > Add your dependencies here
local cmd
for cmd in "${required_cmds[@]}"; do
if ! command -v "$cmd" &>/dev/null; then
die "Required command not found: $cmd" "$E_DEPS"
fi
done
log_debug "All dependencies satisfied"
}
#===============================================================================
# HELPER FUNCTIONS
#===============================================================================
### Create temporary directory
### @return 0 Success
### @side_effect Sets TEMP_DIR global
setup_temp() {
TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/${SCRIPT_NAME}.XXXXXX")"
readonly TEMP_DIR
log_debug "Created temp dir: ${TEMP_DIR}"
}
### Display usage information
### @return 0 Always
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} [OPTIONS] <argument>
Description:
Brief description of what this script does.
Options:
-h, --help Show this help message
-v, --verbose Enable verbose output
-q, --quiet Suppress non-error output
-n, --dry-run Show what would be done without executing
-f, --force Force operation (skip confirmations)
--version Show version information
Arguments:
<argument> Description of required argument
Examples:
${SCRIPT_NAME} --help
${SCRIPT_NAME} --dry-run input.txt
${SCRIPT_NAME} --verbose --force input.txt
Exit Codes:
0 Success
1 General error
2 Invalid arguments
3 Missing dependency
4 Permission denied
5 Resource not found
EOF
}
#===============================================================================
# BUSINESS FUNCTIONS
#===============================================================================
### Main business logic (replace with actual implementation)
### @param input string Input to process
### @return 0 Success
### @return 1 Processing failed
do_work() {
local input="$1"
log_info "Processing: ${input}"
# > Replace with actual business logic
run_cmd echo "Working on: ${input}"
log_info "Done"
}
#===============================================================================
# ARGUMENT PARSING
#===============================================================================
### Parse command line arguments
### @param args array Command line arguments
### @return 0 Success
### @return 2 Invalid argument
parse_args() {
local positional=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit "$E_SUCCESS"
;;
--version)
echo "${SCRIPT_NAME} version ${VERSION}"
exit "$E_SUCCESS"
;;
-v|--verbose)
VERBOSE=true
LOG_LEVEL="DEBUG"
shift
;;
-q|--quiet)
LOG_LEVEL="ERROR"
shift
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
-f|--force)
FORCE=true
shift
;;
--)
shift
positional+=("$@")
break
;;
-*)
die "Unknown option: $1" "$E_ARGS"
;;
*)
positional+=("$1")
shift
;;
esac
done
# > Restore positional parameters
set -- "${positional[@]}"
# > Validate required arguments
if [[ $# -lt 1 ]]; then
die "Missing required argument. Use --help for usage." "$E_ARGS"
fi
# > Export parsed values for main()
INPUT_ARG="$1"
}
#===============================================================================
# MAIN
#===============================================================================
### Main entry point - orchestrates script execution
### @param args array Command line arguments
### @return 0 Success
main() {
check_bash_version
parse_args "$@"
check_dependencies
setup_temp
log_info "Starting ${SCRIPT_NAME} v${VERSION}"
[[ "${DRY_RUN}" == "true" ]] && log_warn "Running in dry-run mode"
do_work "${INPUT_ARG}"
log_info "Completed successfully"
}
# > Entry point
main "$@"
#===============================================================================
# SELF-TEST EXAMPLES
#===============================================================================
# Run these commands to verify script behavior:
#
# 1. Show help:
# ./minimal-template.sh --help
#
# 2. Dry-run mode:
# ./minimal-template.sh --dry-run test-input
#
# 3. Invalid argument (expect exit 2):
# ./minimal-template.sh --invalid-option
#
# 4. Missing argument (expect exit 2):
# ./minimal-template.sh
#
# 5. Verbose mode:
# ./minimal-template.sh --verbose test-input
#===============================================================================

View File

@@ -0,0 +1,121 @@
# Bash Script Code Structure Reference
## Section Order (固定顺序,不可打乱)
```
┌─────────────────────────────────────────────────────────────┐
│ 1. SHEBANG + METADATA │
│ #!/usr/bin/env bash │
│ # Author / Version / License / Last Updated │
├─────────────────────────────────────────────────────────────┤
│ 2. ASCII CALL GRAPH │
│ # main() │
│ # ├── parse_args() │
│ # ├── check_dependencies() │
│ # ├── business_function_1() │
│ # │ └── helper_function() │
│ # └── cleanup() │
├─────────────────────────────────────────────────────────────┤
│ 3. GLOBAL CONSTANTS │
│ readonly VERSION="1.0.0" │
│ readonly SCRIPT_NAME="${0##*/}" │
│ readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" │
├─────────────────────────────────────────────────────────────┤
│ 4. STRICT MODE │
│ set -Eeuo pipefail │
│ IFS=$' \t\n' │
├─────────────────────────────────────────────────────────────┤
│ 5. CONFIGURABLE DEFAULTS │
│ LOG_LEVEL="${LOG_LEVEL:-INFO}" │
│ DRY_RUN=false │
│ VERBOSE=false │
│ FORCE=false │
├─────────────────────────────────────────────────────────────┤
│ 6. DEPENDENCY & ENVIRONMENT CHECKS │
│ check_bash_version() │
│ check_dependencies() │
│ check_permissions() │
├─────────────────────────────────────────────────────────────┤
│ 7. LOGGING & ERROR HANDLING │
│ log_debug() / log_info() / log_warn() / log_error() │
│ die() │
│ run_cmd() │
│ cleanup() │
│ trap setup │
├─────────────────────────────────────────────────────────────┤
│ 8. BUSINESS FUNCTIONS │
│ (grouped by domain, each <50 lines) │
├─────────────────────────────────────────────────────────────┤
│ 9. MAIN + ENTRY │
│ main() { parse_args; check_deps; do_work; } │
│ main "$@" │
└─────────────────────────────────────────────────────────────┘
```
## Strict Mode Explained
```bash
set -E # ERR trap inherited by functions
set -e # Exit on error
set -u # Error on unset variables
set -o pipefail # Pipe fails if any command fails
```
Combined: `set -Eeuo pipefail`
## IFS Safety
```bash
# Default safe IFS
IFS=$' \t\n'
# When processing CSV or custom delimiter, use local override:
local IFS=','
read -ra fields <<< "$line"
```
## Variable Quoting Rules
| Context | Correct | Wrong |
|---------|---------|-------|
| Assignment | `var="$value"` | `var=$value` |
| Command arg | `cmd "$file"` | `cmd $file` |
| Array expansion | `"${arr[@]}"` | `${arr[@]}` |
| Intentional splitting | `$unquoted # > reason` | (must comment) |
## Readonly Constants Pattern
```bash
# Script metadata
readonly VERSION="1.0.0"
readonly SCRIPT_NAME="${0##*/}"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Exit codes
readonly E_SUCCESS=0
readonly E_GENERAL=1
readonly E_ARGS=2
readonly E_DEPS=3
readonly E_PERM=4
readonly E_NOTFOUND=5
```
## Temp Directory Pattern
```bash
# Create temp dir with automatic cleanup
TEMP_DIR=""
setup_temp() {
TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/${SCRIPT_NAME}.XXXXXX")"
readonly TEMP_DIR
}
cleanup() {
if [[ -n "${TEMP_DIR:-}" && -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT ERR INT TERM
```

View File

@@ -0,0 +1,99 @@
# Exit Codes Reference
## Standard Exit Codes (必须遵循)
| Code | Constant | Meaning | Use Case |
|------|----------|---------|----------|
| 0 | `E_SUCCESS` | Success | Normal completion |
| 1 | `E_GENERAL` | General error | Catch-all for unspecified errors |
| 2 | `E_ARGS` | Invalid arguments | Wrong number, invalid format, unknown option |
| 3 | `E_DEPS` | Missing dependency | Required command not found |
| 4 | `E_PERM` | Permission denied | Cannot read/write/execute required resource |
| 5 | `E_NOTFOUND` | Resource not found | File, directory, or resource doesn't exist |
## Extended Exit Codes (按需选用)
| Code | Constant | Meaning | Use Case |
|------|----------|---------|----------|
| 6 | `E_CONFIG` | Configuration error | Invalid or missing config file |
| 7 | `E_NETWORK` | Network error | Connection failed, timeout |
| 8 | `E_TIMEOUT` | Operation timeout | Command exceeded time limit |
| 9 | `E_LOCK` | Lock acquisition failed | Cannot acquire file/process lock |
| 10 | `E_STATE` | Invalid state | Precondition not met |
## Reserved Exit Codes (不要使用)
| Code | Reserved By | Meaning |
|------|-------------|---------|
| 126 | Shell | Command found but not executable |
| 127 | Shell | Command not found |
| 128 | Shell | Invalid exit code (exit only takes 0-255) |
| 128+N | Shell | Fatal signal N (e.g., 130 = SIGINT, 137 = SIGKILL, 143 = SIGTERM) |
| 255 | Shell | Exit code out of range |
## Declaration Pattern
```bash
# Exit codes (declare at top, after shebang)
readonly E_SUCCESS=0
readonly E_GENERAL=1
readonly E_ARGS=2
readonly E_DEPS=3
readonly E_PERM=4
readonly E_NOTFOUND=5
readonly E_CONFIG=6
readonly E_NETWORK=7
readonly E_TIMEOUT=8
readonly E_LOCK=9
readonly E_STATE=10
```
## Usage Examples
```bash
# Argument validation
[[ $# -ge 1 ]] || die "Missing required argument" "$E_ARGS"
# Dependency check
command -v jq &>/dev/null || die "jq is required" "$E_DEPS"
# Permission check
[[ -w "$output_dir" ]] || die "Cannot write to: $output_dir" "$E_PERM"
# File existence
[[ -f "$config_file" ]] || die "Config not found: $config_file" "$E_NOTFOUND"
# Network error (with retry)
curl -sf "$url" || die "Failed to fetch: $url" "$E_NETWORK"
```
## Exit Code Ranges by Category
| Range | Category | Description |
|-------|----------|-------------|
| 0 | Success | All operations completed |
| 1-10 | Application errors | Defined by script |
| 11-63 | Reserved | For future application use |
| 64-78 | BSD sysexits.h | Standard Unix exit codes |
| 79-125 | Reserved | For future use |
| 126-255 | Shell reserved | Do not use |
## sysexits.h Reference (可选,用于更严格的标准化)
| Code | Name | Meaning |
|------|------|---------|
| 64 | EX_USAGE | Command line usage error |
| 65 | EX_DATAERR | Data format error |
| 66 | EX_NOINPUT | Cannot open input |
| 67 | EX_NOUSER | Addressee unknown |
| 68 | EX_NOHOST | Host name unknown |
| 69 | EX_UNAVAILABLE | Service unavailable |
| 70 | EX_SOFTWARE | Internal software error |
| 71 | EX_OSERR | System error |
| 72 | EX_OSFILE | Critical OS file missing |
| 73 | EX_CANTCREAT | Can't create output file |
| 74 | EX_IOERR | I/O error |
| 75 | EX_TEMPFAIL | Temporary failure; retry |
| 76 | EX_PROTOCOL | Protocol error |
| 77 | EX_NOPERM | Permission denied |
| 78 | EX_CONFIG | Configuration error |

View File

@@ -0,0 +1,187 @@
# Function Documentation Template
## Required Doc Block Format
Every function MUST have this header (缺一不可):
```bash
### <One-line功能描述动词开头>
### @param <param_name> <type> <用途说明>
### @param <param_name> <type> <用途说明>
### @return <exit_code> <状态描述>
### @require <依赖项/命令/权限/环境变量>
### @side_effect <对文件/网络/系统的影响> (可选,有副作用时必填)
function_name() {
local param1="$1"
local param2="${2:-default}"
# Implementation
}
```
## Type Annotations
| Type | Description | Example |
|------|-------------|---------|
| `string` | Any text | `@param name string User's name` |
| `int` | Integer number | `@param count int Number of retries` |
| `bool` | true/false | `@param verbose bool Enable verbose mode` |
| `path` | File/directory path | `@param config path Path to config file` |
| `array` | Bash array | `@param files array List of input files` |
| `fd` | File descriptor | `@param logfd fd File descriptor for logging` |
## Complete Example
```bash
### Validate that a file exists and is readable
### @param file_path path Absolute or relative path to validate
### @param allow_empty bool Whether empty files are valid (default: false)
### @return 0 File is valid and readable
### @return 1 File does not exist
### @return 2 File is not readable
### @return 3 File is empty (when allow_empty=false)
### @require read permission on file_path
validate_file() {
local file_path="$1"
local allow_empty="${2:-false}"
# > Check existence first (most common failure)
if [[ ! -e "$file_path" ]]; then
log_error "File not found: $file_path"
return 1
fi
# > Check readability
if [[ ! -r "$file_path" ]]; then
log_error "File not readable: $file_path"
return 2
fi
# > Check empty (only if not allowed)
if [[ "$allow_empty" != "true" && ! -s "$file_path" ]]; then
log_error "File is empty: $file_path"
return 3
fi
return 0
}
```
## Logging Functions Template
```bash
### Log message at DEBUG level
### @param message string Message to log
### @return 0 Always succeeds
log_debug() {
[[ "${LOG_LEVEL:-INFO}" == "DEBUG" ]] || return 0
printf '[%s] [DEBUG] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
### Log message at INFO level
### @param message string Message to log
### @return 0 Always succeeds
log_info() {
[[ "${LOG_LEVEL:-INFO}" =~ ^(DEBUG|INFO)$ ]] || return 0
printf '[%s] [INFO] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
### Log message at WARN level
### @param message string Message to log
### @return 0 Always succeeds
log_warn() {
[[ "${LOG_LEVEL:-INFO}" =~ ^(DEBUG|INFO|WARN)$ ]] || return 0
printf '[%s] [WARN] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
### Log message at ERROR level
### @param message string Message to log
### @return 0 Always succeeds
log_error() {
printf '[%s] [ERROR] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
```
## Die Function Template
```bash
### Exit with error message and code, including location info
### @param message string Error message
### @param exit_code int Exit code (default: 1)
### @return never Returns (exits script)
### @side_effect Terminates script execution
die() {
local message="$1"
local exit_code="${2:-1}"
local caller_info=""
# > Build stack trace for debugging
if [[ "${BASH_LINENO[0]:-0}" -gt 0 ]]; then
caller_info=" (${FUNCNAME[1]:-main}:${BASH_LINENO[0]:-?})"
fi
log_error "${message}${caller_info}"
exit "$exit_code"
}
```
## Run Command Wrapper Template
```bash
### Execute command with dry-run support and error handling
### @param cmd string Command to execute (as array: "${cmd[@]}")
### @return 0 Command succeeded
### @return 1 Command failed
### @side_effect Executes external command (unless DRY_RUN=true)
run_cmd() {
local -a cmd=("$@")
if [[ "${DRY_RUN:-false}" == "true" ]]; then
log_info "[DRY-RUN] Would execute: ${cmd[*]}"
return 0
fi
if [[ "${VERBOSE:-false}" == "true" ]]; then
log_debug "Executing: ${cmd[*]}"
fi
if ! "${cmd[@]}"; then
log_error "Command failed: ${cmd[*]}"
return 1
fi
return 0
}
```
## Input Validation Pattern
```bash
### Validate path argument
### @param path path Path to validate
### @param must_exist bool Path must exist (default: true)
### @param must_be_writable bool Path must be writable (default: false)
validate_path() {
local path="$1"
local must_exist="${2:-true}"
local must_be_writable="${3:-false}"
# > Reject empty paths
[[ -n "$path" ]] || die "Path cannot be empty" 2
# > Resolve to absolute path
if [[ "$path" != /* ]]; then
path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")"
fi
if [[ "$must_exist" == "true" && ! -e "$path" ]]; then
die "Path does not exist: $path" 5
fi
if [[ "$must_be_writable" == "true" && ! -w "$path" ]]; then
die "Path not writable: $path" 4
fi
printf '%s' "$path"
}
```

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env bash
#===============================================================================
# Script Name : shellcheck-wrapper.sh
# Description : Wrapper for ShellCheck with project defaults
# Version : 1.0.0
#===============================================================================
set -Eeuo pipefail
readonly SCRIPT_NAME="${0##*/}"
### Display usage
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} <script.sh> [shellcheck-options...]
Runs ShellCheck with recommended settings for production Bash scripts.
Default flags applied:
-x Follow source statements
-s bash Shell dialect: bash
-S style Minimum severity: style (catch everything)
Examples:
${SCRIPT_NAME} myscript.sh
${SCRIPT_NAME} myscript.sh -S warning # Only warnings and above
${SCRIPT_NAME} *.sh # Check multiple files
EOF
}
### Check ShellCheck is installed
check_shellcheck() {
if ! command -v shellcheck &>/dev/null; then
echo "ERROR: shellcheck not found. Install with:" >&2
echo " Ubuntu/Debian: apt install shellcheck" >&2
echo " macOS: brew install shellcheck" >&2
echo " Windows: scoop install shellcheck" >&2
exit 3
fi
}
main() {
if [[ $# -lt 1 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then
usage
exit 0
fi
check_shellcheck
local script="$1"
shift
if [[ ! -f "$script" ]]; then
echo "ERROR: File not found: $script" >&2
exit 5
fi
echo "Running ShellCheck on: $script"
echo "========================================"
# > Run with defaults + any user-provided options
# shellcheck disable=SC2086
shellcheck -x -s bash -S style "$@" "$script"
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
echo "========================================"
echo "✓ No issues found"
else
echo "========================================"
echo "✗ ShellCheck found issues (exit code: $exit_code)"
fi
exit $exit_code
}
main "$@"