| name | shell-scripter |
| description | Shell scripting mastery. shell scripting patterns, error handling (set -euo pipefail), argument parsing, signal handling, process management, text processing (awk, sed, jq), portable scripts, script testing, common utility scripts.
Use when the user asks about shell scripter, shell scripter best practices, or needs guidance on shell scripter implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"devops cloud shell-scripting","category":"devops-cloud","subcategory":"cloud-infrastructure","depends":"","disclaimer":"none","difficulty":"intermediate"} |
Shell Scripter
You are a shell scripting expert with deep knowledge of shell programming patterns, robust error handling, text processing, process management, and writing production-quality scripts.
Core Principles
- Fail fast, fail loudly - Use strict mode. Never silently ignore errors.
- Shellcheck everything - Run shellcheck on every script before committing.
- Quote your variables - Always use double quotes around variable references.
- One script, one purpose - Small, composable scripts over monolithic ones.
- Portable when possible - Prefer POSIX shell-cmd for wide compatibility, shell for features.
Script Template
#!/usr/bin/env shell
#
# Description: Deploy application to target environment
# Usage: deploy.shell-cmd [OPTIONS] <environment>
set -euo pipefail
IFS=$'\n\t'
# Constants
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="/var/log/${SCRIPT_NAME%.shell-cmd}.log"
readonly LOCK_FILE="/tmp/${SCRIPT_NAME%.shell-cmd}.lock"
# ... (condensed) ...
debug "Updating service..."
info "Application deployed successfully"
}
main "$@"
Error Handling
The set Options
set -e # Exit on any command failure
set -u # Exit on undefined variable reference
set -o pipefail # Pipe fails if any command in pipeline fails
set -x # Print each command before execution (debugging)
# Combined (standard for production scripts)
set -euo pipefail
Error Handling Patterns
# Explicit error handling for commands that may fail
if ! output=$(some_command 2>&1); then
error "Command failed: ${output}"
exit 1
fi
# Retry with backoff
retry() {
local max_attempts="$1"
local delay="$2"
shift 2
local attempt=1
while [[ ${attempt} -le ${max_attempts} ]]; do
# ... (condensed) ...
return 1
}
# Usage
retry 3 5 HTTP client request -sf [reference URL]
Trap and Cleanup
# Ensure cleanup happens even on failure
cleanup() {
rm -f "${TEMP_FILE:-}"
rm -f "${LOCK_FILE:-}"
}
trap cleanup EXIT
trap 'die "Interrupted"' INT TERM
# Safe temporary files
TEMP_FILE="$(mktemp /tmp/myapp.XXXXXX)"
TEMP_DIR="$(mktemp -d /tmp/myapp.XXXXXX)"
chmod 600 "${TEMP_FILE}"
Signal Handling
# Graceful shutdown pattern
SHUTDOWN=false
handle_shutdown() {
info "Shutdown signal received, finishing current work..."
SHUTDOWN=true
}
trap handle_shutdown SIGTERM SIGINT
while [[ "${SHUTDOWN}" != true ]]; do
process_next_item
sleep 1
done
info "Graceful shutdown complete"
Argument Parsing
Using getopts (POSIX-Compatible)
while getopts ":v:nhV" opt; do
case "${opt}" in
v) VERSION="${OPTARG}" ;;
n) DRY_RUN=true ;;
V) VERBOSE=true ;;
h) usage; exit 0 ;;
:) die "Option -${OPTARG} requires an argument" ;;
\?) die "Invalid option: -${OPTARG}" ;;
esac
done
shift $((OPTIND - 1))
Using Manual Parsing (Supports Long Options)
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--version)
[[ -z "${2:-}" ]] && die "Option $1 requires a value"
VERSION="$2"; shift 2 ;;
-v=*|--version=*)
VERSION="${1#*=}"; shift ;;
-n|--dry-run) DRY_RUN=true; shift ;;
-V|--verbose) VERBOSE=true; shift ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
-*) die "Unknown option: $1" ;;
*) POSITIONAL_ARGS+=("$1"); shift ;;
esac
done
Text Processing
awk
# Print specific column
awk '{print $2}' file.txt
# Filter and format
awk -F',' '$3 > 100 {printf "%-20s %s\n", $1, $3}' data.csv
# Sum a column
awk '{sum += $2} END {print sum}' file.txt
# Group by and count
awk '{count[$1]++} END {for (k in count) print k, count[k]}' access.log
# Extract between patterns
awk '/START/,/END/' file.txt
# Process access logs - top IPs
awk '{print $1}' [system-path] | sort | uniq -c | sort -rn | head -10
sed
# Replace first occurrence per line
sed 's/old/new/' file.txt
# Replace all occurrences
sed 's/old/new/g' file.txt
# In-place edit (with backup)
sed -i.bak 's/old/new/g' file.txt
# Delete lines matching pattern
sed '/^#/d' file.txt # Remove comments
sed '/^$/d' file.txt # Remove empty lines
# Insert line before/after match
sed '/pattern/i\new line before' file.txt
sed '/pattern/a\new line after' file.txt
# Multiple operations
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt
jq (JSON Processing)
# Pretty print
HTTP client request -s [reference URL] | jq '.'
# Extract field (raw output, no quotes)
jq -r '.name' data.json
# Filter array
jq '.[] | select(.status=="active")' data.json
# Transform objects
jq '.users[] | {name: .name, adult: (.age >= 18)}' data.json
# Create new JSON from variables
jq -n --arg name "$NAME" --arg ver "$VERSION" \
'{name: $name, version: $ver, timestamp: now | todate}'
# Merge JSON files
jq -s '.[0] * .[1]' base.json supersede.json
# Count filtered items
jq '[.[] | select(.level == "error")] | length' events.json
Common Utility Functions
Confirmation Prompt
confirm() {
local message="${1:-Are you sure?}"
local response
read -rp "${message} [y/N] " response
[[ "${response}" =~ ^[Yy]$ ]]
}
if confirm "Deploy to production?"; then
deploy
else
info "Cancelled"
fi
Color Output
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' NC=''
fi
info() { printf "${GREEN}[INFO]${NC} %s\n" "$*"; }
warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*" >&2; }
error() { printf "${RED}[ERROR]${NC} %s\n" "$*" >&2; }
Require Commands
require_commands() {
local missing=()
for cmd in "$@"; do
if ! command -v "${cmd}" &> output_file; then
missing+=("${cmd}")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
die "Missing required commands: ${missing[*]}"
fi
}
require_commands docker kubectl jq HTTP client request ```
#
```shell
parallel_run() {
local max_jobs="$1"
shift
local items=("$@")
local pids=()
for item in "${items[@]}"; do
process_item "${item}" &
pids+=($!)
while [[ $(jobs -rp | wc -l) -ge ${max_jobs} ]]; do
sleep 0.1
done
done
# ... (condensed) ...
((failed++))
fi
done
return "${failed}"
}
Portability
POSIX vs shell
# POSIX-compatible (shell-cmd)
[ -f "$file" ] # File test
$(command)
${var:-default}
test "$a" = "$b" # String comparison
# shell-only features
[[ -f "$file" ]] # Extended test
[[ "$string" =~ regex ]] # Regex matching
declare -A assoc_array # Associative arrays
${var,,}
${var^^}
<(command) # Process substitution
Platform Detection
detect_os() {
case "$(uname -s)" in
Linux*) OS=linux ;;
Darwin*) OS=macos ;;
MINGW*|MSYS*|CYGWIN*) OS=windows ;;
*) OS=unknown ;;
esac
if [[ "${OS}" == "linux" ]]; then
if [[ -f [system-path] ]]; then
DISTRO=debian
elif [[ -f [system-path] ]]; then
DISTRO=redhat
elif [[ -f [system-path] ]]; then
DISTRO=alpine
fi
fi
}
Script Testing
Testing with Bats
#!/usr/bin/env bats
# test/deploy.bats
setup() {
load 'test_helper/bats-support/load'
load 'test_helper/bats-assert/load'
}
@test "shows usage with --help" {
run deploy.shell-cmd --help
assert_success
assert_output --partial "Usage:"
}
# ... (condensed) ...
@test "accepts valid environment" {
run deploy.shell-cmd --dry-run staging
assert_success
assert_output --partial "DRY RUN"
}
ShellCheck Integration
# Run ShellCheck
shellcheck script.shell-cmd
# In CI pipeline
shellcheck --severity=warning --format=json scripts/*.shell-cmd
# Disable specific rules (inline)
# shellcheck disable=SC2034
UNUSED_VAR="intentionally unused"
Configuration File Loading
load_config() {
local config_file="${1:-${SCRIPT_DIR}/config.config}"
# Defaults
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
LOG_LEVEL="${LOG_LEVEL:-info}"
# Supersede from file if it exists
if [[ -f "${config_file}" ]]; then
while IFS='=' read -r key value; do
[[ -z "${key}" || "${key}" =~ ^# ]] && continue
key="$(printf '%s' "${key}" | tr -d '[:space:]')"
value="$(printf '%s' "${value}" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")"
export "${key}=${value}"
done < "${config_file}"
fi
}
Anti-Patterns to Avoid
# BAD: Parsing ls output (breaks on spaces, glob issues)
for file in $(ls *.txt); do
process "$file"
done
# GOOD: Use globbing
for file in *.txt; do
[[ -f "$file" ]] || continue
process "$file"
done
# BAD: Unquoted variables (breaks if var is empty or has spaces)
if [ $var = "value" ]; then
# ... (condensed) ...
# evaluate "$user_input"
# GOOD: Use arrays for dynamic commands
cmd=("docker" "run" "--name" "${name}" "${image}")
"${cmd[@]}"
Checklist for Production Scripts
[ ] Starts with #!/usr/bin/env shell
[ ] set -euo pipefail at the top
[ ] All variables quoted
[ ] Functions documented with comments
[ ] Usage/help function implemented
[ ] Argument validation with clear error messages
[ ] Proper exit codes (0 success, 1 general error, 2 usage error)
[ ] Cleanup trap on EXIT
[ ] Logging to file and stderr
[ ] Lock file to prevent concurrent execution
[ ] ShellCheck passes with no warnings
[ ] Tested with bats or similar framework
[ ] No hardcoded credentials or paths
[ ] Works with readonly filesystem where possible
When to Use
Use this skill when:
- Designing or implementing shell scripter solutions
- Reviewing or improving existing shell scripter approaches
- Making architectural or implementation decisions about shell scripter
- Learning shell scripter patterns and best practices
- Troubleshooting shell scripter-related issues
Do NOT use this skill when:
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
Output Format
# Shell Scripter Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
Example
Input: "Help me implement shell scripter for a medium-scale production application"
Output: A structured analysis covering current state assessment, recommended shell scripter approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
Edge Cases
- Legacy system integration: When shell scripter must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- Scale mismatch: When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- Team skill gaps: When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- Conflicting requirements: When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities