
Shell Bash
- 97 installs
- 19 repo stars
- Updated January 20, 2026
- miles990/claude-software-skills
Helps with ai & agent building tasks.
About
shell-bash is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- shell-bash
- AI & Agent Building
- AI-coding skill
Shell Bash by the numbers
- 97 all-time installs (skills.sh)
- +5 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #4,486 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/miles990/claude-software-skills --skill shell-bashAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 97 |
|---|---|
| repo stars | ★ 19 |
| Last updated | January 20, 2026 |
| Repository | miles990/claude-software-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Shell & Bash Scripting
Overview
Shell scripting patterns for automation, system administration, and CLI tools.
---
Script Fundamentals
Script Structure
#!/usr/bin/env bash
#
# Script: backup.sh
# Description: Backup files to remote server
# Usage: ./backup.sh [options] <source> <destination>
#
set -euo pipefail # Exit on error, undefined vars, pipe failures
IFS=$'\n\t' # Safer word splitting
# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
readonly LOG_FILE="/var/log/${SCRIPT_NAME%.sh}.log"
# Default values
VERBOSE=false
DRY_RUN=false
COMPRESS=true
# Cleanup on exit
cleanup() {
local exit_code=$?
# Cleanup temporary files
rm -f "${TEMP_FILE:-}"
exit "$exit_code"
}
trap cleanup EXIT
# Logging functions
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE"
}
info() { log "INFO" "$@"; }
warn() { log "WARN" "$@" >&2; }
error() { log "ERROR" "$@" >&2; }
debug() { [[ "$VERBOSE" == true ]] && log "DEBUG" "$@" || true; }
die() {
error "$@"
exit 1
}
# Usage
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [options] <source> <destination>
Options:
-v, --verbose Enable verbose output
-n, --dry-run Show what would be done
-h, --help Show this help message
Examples:
$SCRIPT_NAME /data /backup
$SCRIPT_NAME -v --dry-run /home/user /mnt/backup
EOF
}
# Main function
main() {
parse_args "$@"
validate_inputs
perform_backup
}
# Run main if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fiArgument Parsing
# Using getopts (POSIX)
parse_args_getopts() {
while getopts ":vnh" opt; do
case $opt in
v) VERBOSE=true ;;
n) DRY_RUN=true ;;
h) usage; exit 0 ;;
\?) die "Invalid option: -$OPTARG" ;;
:) die "Option -$OPTARG requires an argument" ;;
esac
done
shift $((OPTIND - 1))
SOURCE="${1:-}"
DESTINATION="${2:-}"
}
# Using manual parsing (supports long options)
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose)
VERBOSE=true
shift
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
-c|--compress)
COMPRESS=true
shift
;;
--no-compress)
COMPRESS=false
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
-*)
die "Unknown option: $1"
;;
*)
break
;;
esac
done
# Positional arguments
SOURCE="${1:-}"
DESTINATION="${2:-}"
}
# Validation
validate_inputs() {
[[ -z "$SOURCE" ]] && die "Source path is required"
[[ -z "$DESTINATION" ]] && die "Destination path is required"
[[ -e "$SOURCE" ]] || die "Source does not exist: $SOURCE"
}---
Variables and Data
Variable Operations
# Variable assignment
name="John"
readonly CONSTANT="immutable"
# Default values
name="${name:-default}" # Use default if unset or empty
name="${name:=default}" # Assign default if unset or empty
name="${name:+alternative}" # Use alternative if set and non-empty
name="${name:?error message}" # Error if unset or empty
# String manipulation
str="Hello, World!"
echo "${str:0:5}" # "Hello" (substring)
echo "${str: -6}" # "World!" (last 6 chars)
echo "${#str}" # 13 (length)
echo "${str/World/Bash}" # "Hello, Bash!" (replace first)
echo "${str//o/0}" # "Hell0, W0rld!" (replace all)
echo "${str#Hello, }" # "World!" (remove prefix)
echo "${str%!}" # "Hello, World" (remove suffix)
echo "${str^^}" # "HELLO, WORLD!" (uppercase)
echo "${str,,}" # "hello, world!" (lowercase)
# Parameter expansion
filename="/path/to/file.tar.gz"
echo "${filename##*/}" # "file.tar.gz" (basename)
echo "${filename%/*}" # "/path/to" (dirname)
echo "${filename%%.*}" # "/path/to/file" (remove all extensions)
echo "${filename%.gz}" # "/path/to/file.tar" (remove last extension)Arrays
# Indexed arrays
declare -a fruits=("apple" "banana" "cherry")
fruits+=("date") # Append
echo "${fruits[0]}" # "apple" (first element)
echo "${fruits[-1]}" # "date" (last element)
echo "${fruits[@]}" # All elements
echo "${#fruits[@]}" # 4 (length)
echo "${!fruits[@]}" # 0 1 2 3 (indices)
# Iterate
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# With indices
for i in "${!fruits[@]}"; do
echo "$i: ${fruits[i]}"
done
# Associative arrays (bash 4+)
declare -A user=(
[name]="John"
[email]="john@example.com"
[age]=30
)
echo "${user[name]}" # "John"
echo "${!user[@]}" # Keys: name email age
echo "${user[@]}" # Values
# Iterate key-value
for key in "${!user[@]}"; do
echo "$key: ${user[$key]}"
done
# Array slicing
echo "${fruits[@]:1:2}" # "banana" "cherry" (from index 1, 2 elements)
# Array filtering (bash 4+)
evens=()
for n in "${numbers[@]}"; do
(( n % 2 == 0 )) && evens+=("$n")
done---
Control Flow
Conditionals
# Test operators
# Strings
[[ -z "$str" ]] # Empty
[[ -n "$str" ]] # Not empty
[[ "$a" == "$b" ]] # Equal
[[ "$a" != "$b" ]] # Not equal
[[ "$a" < "$b" ]] # Less than (lexicographic)
[[ "$a" =~ ^[0-9]+$ ]] # Regex match
# Numbers
[[ "$a" -eq "$b" ]] # Equal
[[ "$a" -ne "$b" ]] # Not equal
[[ "$a" -lt "$b" ]] # Less than
[[ "$a" -le "$b" ]] # Less than or equal
[[ "$a" -gt "$b" ]] # Greater than
[[ "$a" -ge "$b" ]] # Greater than or equal
# Files
[[ -e "$file" ]] # Exists
[[ -f "$file" ]] # Regular file
[[ -d "$dir" ]] # Directory
[[ -r "$file" ]] # Readable
[[ -w "$file" ]] # Writable
[[ -x "$file" ]] # Executable
[[ -s "$file" ]] # Size > 0
[[ "$a" -nt "$b" ]] # Newer than
[[ "$a" -ot "$b" ]] # Older than
# If-elif-else
if [[ "$status" == "success" ]]; then
echo "Success!"
elif [[ "$status" == "pending" ]]; then
echo "Still pending..."
else
echo "Failed"
fi
# Case statement
case "$command" in
start|begin)
start_service
;;
stop|end)
stop_service
;;
restart)
stop_service
start_service
;;
*)
echo "Unknown command: $command"
exit 1
;;
esac
# Short-circuit
[[ -f "$file" ]] && process_file "$file"
[[ -d "$dir" ]] || mkdir -p "$dir"Loops
# For loop
for item in item1 item2 item3; do
echo "$item"
done
# C-style for
for ((i = 0; i < 10; i++)); do
echo "$i"
done
# While loop
counter=0
while [[ $counter -lt 5 ]]; do
echo "$counter"
((counter++))
done
# Read file line by line
while IFS= read -r line; do
echo "$line"
done < "$file"
# Process command output
while IFS= read -r file; do
echo "Processing: $file"
done < <(find . -name "*.txt")
# Until loop
until [[ -f "$lockfile" ]]; do
sleep 1
done
# Break and continue
for i in {1..10}; do
[[ $i -eq 5 ]] && continue
[[ $i -eq 8 ]] && break
echo "$i"
done---
Functions
# Basic function
greet() {
local name="$1"
echo "Hello, $name!"
}
# Function with return value
is_valid_email() {
local email="$1"
[[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]
}
# Check return value
if is_valid_email "test@example.com"; then
echo "Valid email"
fi
# Function with output capture
get_user_count() {
wc -l < /etc/passwd
}
count=$(get_user_count)
# Function with array parameter
process_files() {
local -a files=("$@")
for file in "${files[@]}"; do
echo "Processing: $file"
done
}
process_files file1.txt file2.txt file3.txt
# Function with named reference (bash 4.3+)
modify_array() {
local -n arr=$1
arr+=("new_element")
}
my_array=("a" "b" "c")
modify_array my_array
echo "${my_array[@]}" # "a b c new_element"
# Error handling in functions
safe_divide() {
local dividend="$1"
local divisor="$2"
if [[ "$divisor" -eq 0 ]]; then
echo "Error: Division by zero" >&2
return 1
fi
echo $((dividend / divisor))
}
result=$(safe_divide 10 2) && echo "Result: $result"---
Text Processing
# grep - search patterns
grep "error" logfile.txt # Find lines with "error"
grep -i "error" logfile.txt # Case-insensitive
grep -E "error|warning" logfile.txt # Extended regex
grep -v "debug" logfile.txt # Invert match
grep -c "error" logfile.txt # Count matches
grep -l "error" *.log # List files with matches
grep -r "TODO" src/ # Recursive search
# sed - stream editor
sed 's/old/new/' file.txt # Replace first occurrence
sed 's/old/new/g' file.txt # Replace all
sed -i.bak 's/old/new/g' file.txt # In-place with backup
sed '/pattern/d' file.txt # Delete matching lines
sed -n '10,20p' file.txt # Print lines 10-20
sed 's/^/prefix: /' file.txt # Add prefix
# awk - field processing
awk '{print $1}' file.txt # First field
awk -F: '{print $1}' /etc/passwd # Custom delimiter
awk '{sum += $1} END {print sum}' data.txt # Sum first column
awk 'NR > 1' file.txt # Skip header
awk '$3 > 100 {print $1, $3}' data.txt # Conditional print
awk '{print NR": "$0}' file.txt # Add line numbers
# cut - extract fields
cut -d: -f1 /etc/passwd # First field
cut -c1-10 file.txt # Characters 1-10
cut -d, -f1,3 data.csv # Fields 1 and 3
# sort and uniq
sort file.txt # Sort lines
sort -n numbers.txt # Numeric sort
sort -r file.txt # Reverse sort
sort -t: -k3 -n /etc/passwd # Sort by field
uniq file.txt # Remove adjacent duplicates
sort file.txt | uniq -c # Count occurrences
sort file.txt | uniq -d # Show duplicates only
# tr - translate characters
echo "hello" | tr 'a-z' 'A-Z' # Uppercase
tr -d '\r' < file.txt # Remove carriage returns
tr -s ' ' < file.txt # Squeeze spaces
# xargs - build commands
find . -name "*.txt" | xargs wc -l
find . -name "*.log" -print0 | xargs -0 rm
echo "a b c" | xargs -n1 echo
cat urls.txt | xargs -P4 -I{} curl {} # Parallel execution---
Process Management
# Background processes
long_running_command &
pid=$! # Get PID of last background job
wait $pid # Wait for specific process
# Run multiple in background and wait
for file in *.txt; do
process_file "$file" &
done
wait # Wait for all
# Parallel processing with xargs
find . -name "*.jpg" -print0 | xargs -0 -P4 -I{} convert {} {}.png
# Job control
jobs # List jobs
fg %1 # Bring job 1 to foreground
bg %1 # Resume job 1 in background
kill %1 # Kill job 1
# Process substitution
diff <(sort file1.txt) <(sort file2.txt)
while read -r line; do
echo "$line"
done < <(command_that_outputs)
# Command groups
{ cmd1; cmd2; cmd3; } > output.txt # Group and redirect
( cd /tmp && cmd1; cmd2 ) # Subshell (doesn't affect current shell)
# Coprocesses
coproc my_coproc { while read -r line; do echo "Got: $line"; done; }
echo "Hello" >&"${my_coproc[1]}"
read -r response <&"${my_coproc[0]}"---
Related Skills
- [[automation-scripts]] - Build automation
- [[devops-cicd]] - CI/CD pipelines
- [[development-environment]] - Environment setup
#!/bin/bash
# ===========================================
# Common Bash Utilities Template
# Usage: source common-utils.sh
# ===========================================
# Strict mode
set -euo pipefail
IFS=$'\n\t'
# ===========================================
# Colors & Formatting
# ===========================================
# Colors (check if terminal supports colors)
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color
else
RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' BOLD='' NC=''
fi
# Print functions
print_info() { echo -e "${BLUE}ℹ${NC} $*"; }
print_success() { echo -e "${GREEN}✓${NC} $*"; }
print_warning() { echo -e "${YELLOW}⚠${NC} $*"; }
print_error() { echo -e "${RED}✗${NC} $*" >&2; }
print_step() { echo -e "${CYAN}→${NC} $*"; }
# ===========================================
# Error Handling
# ===========================================
# Die with message
die() {
print_error "$@"
exit 1
}
# Cleanup on exit
cleanup() {
# Add cleanup tasks here
:
}
trap cleanup EXIT
# Error handler
on_error() {
local line=$1
print_error "Error on line $line"
}
trap 'on_error $LINENO' ERR
# ===========================================
# Checks & Validation
# ===========================================
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Require command or die
require_command() {
local cmd=$1
local install_hint=${2:-"Please install $cmd"}
if ! command_exists "$cmd"; then
die "$cmd is required. $install_hint"
fi
}
# Check if running as root
is_root() {
[[ $EUID -eq 0 ]]
}
# Require root
require_root() {
if ! is_root; then
die "This script must be run as root"
fi
}
# Check if variable is set
is_set() {
[[ -n "${!1:-}" ]]
}
# Require environment variable
require_env() {
local var=$1
if ! is_set "$var"; then
die "Environment variable $var is required"
fi
}
# ===========================================
# User Interaction
# ===========================================
# Confirm action (default yes)
confirm() {
local prompt=${1:-"Continue?"}
local default=${2:-"y"}
if [[ $default == "y" ]]; then
read -rp "$prompt [Y/n] " response
[[ -z $response || $response =~ ^[Yy] ]]
else
read -rp "$prompt [y/N] " response
[[ $response =~ ^[Yy] ]]
fi
}
# Prompt for input
prompt() {
local prompt=$1
local default=${2:-""}
local result
if [[ -n $default ]]; then
read -rp "$prompt [$default]: " result
echo "${result:-$default}"
else
read -rp "$prompt: " result
echo "$result"
fi
}
# Select from options
select_option() {
local prompt=$1
shift
local options=("$@")
PS3="$prompt "
select opt in "${options[@]}"; do
if [[ -n $opt ]]; then
echo "$opt"
break
fi
done
}
# ===========================================
# File Operations
# ===========================================
# Create directory if not exists
ensure_dir() {
local dir=$1
[[ -d $dir ]] || mkdir -p "$dir"
}
# Backup file
backup_file() {
local file=$1
local backup="${file}.bak.$(date +%Y%m%d_%H%M%S)"
if [[ -f $file ]]; then
cp "$file" "$backup"
print_info "Backed up $file to $backup"
fi
}
# Safe file write (atomic)
safe_write() {
local file=$1
local content=$2
local tmp="${file}.tmp.$$"
echo "$content" > "$tmp"
mv "$tmp" "$file"
}
# ===========================================
# String Operations
# ===========================================
# Trim whitespace
trim() {
local var="$*"
var="${var#"${var%%[![:space:]]*}"}"
var="${var%"${var##*[![:space:]]}"}"
echo -n "$var"
}
# Lowercase
lowercase() {
echo "$1" | tr '[:upper:]' '[:lower:]'
}
# Uppercase
uppercase() {
echo "$1" | tr '[:lower:]' '[:upper:]'
}
# ===========================================
# Date/Time
# ===========================================
# ISO timestamp
timestamp() {
date -u +"%Y-%m-%dT%H:%M:%SZ"
}
# Human-readable date
date_human() {
date +"%Y-%m-%d %H:%M:%S"
}
# ===========================================
# Networking
# ===========================================
# Wait for port to be available
wait_for_port() {
local host=$1
local port=$2
local timeout=${3:-30}
local start=$(date +%s)
print_step "Waiting for $host:$port..."
while ! nc -z "$host" "$port" 2>/dev/null; do
if (( $(date +%s) - start > timeout )); then
die "Timeout waiting for $host:$port"
fi
sleep 1
done
print_success "$host:$port is available"
}
# Get public IP
get_public_ip() {
curl -s https://ifconfig.me || curl -s https://api.ipify.org
}
# ===========================================
# JSON (requires jq)
# ===========================================
# Get JSON value
json_get() {
local json=$1
local key=$2
echo "$json" | jq -r "$key"
}
# ===========================================
# Retry Logic
# ===========================================
# Retry command with exponential backoff
retry() {
local max_attempts=${1:-3}
local delay=${2:-1}
shift 2
local cmd=("$@")
local attempt=1
while (( attempt <= max_attempts )); do
if "${cmd[@]}"; then
return 0
fi
if (( attempt < max_attempts )); then
print_warning "Attempt $attempt failed, retrying in ${delay}s..."
sleep "$delay"
delay=$((delay * 2))
fi
((attempt++))
done
print_error "Command failed after $max_attempts attempts"
return 1
}
# ===========================================
# Script Info
# ===========================================
# Get script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Get script name
SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
# ===========================================
# Usage Example
# ===========================================
# main() {
# require_command "jq" "brew install jq"
# require_env "API_KEY"
#
# print_info "Starting script..."
#
# if confirm "Proceed with operation?"; then
# print_step "Executing..."
# # Do work
# print_success "Done!"
# fi
# }
#
# main "$@"
Shell/Bash Scripts
Utility scripts and templates for shell scripting.
Files
| Script | Purpose |
|---|---|
common-utils.sh | Common utility functions |
Usage
Source in Your Script
#!/bin/bash
source "$(dirname "$0")/common-utils.sh"
# Now use the functions
print_info "Starting..."
require_command "jq"
require_env "API_KEY"Copy Functions
Copy individual functions you need:
# Just colors
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
# Just confirm
confirm() {
read -rp "$1 [Y/n] " response
[[ -z $response || $response =~ ^[Yy] ]]
}Available Functions
Output
| Function | Description |
|---|---|
print_info | Blue info message |
print_success | Green success message |
print_warning | Yellow warning |
print_error | Red error (stderr) |
print_step | Cyan step indicator |
die | Print error and exit |
Validation
| Function | Description |
|---|---|
command_exists | Check if command available |
require_command | Die if command missing |
is_root | Check if running as root |
require_root | Die if not root |
require_env | Die if env var not set |
User Interaction
| Function | Description |
|---|---|
confirm | Yes/no confirmation |
prompt | Prompt for input |
select_option | Select from options |
File Operations
| Function | Description |
|---|---|
ensure_dir | Create dir if not exists |
backup_file | Create timestamped backup |
safe_write | Atomic file write |
String Operations
| Function | Description |
|---|---|
trim | Remove whitespace |
lowercase | Convert to lowercase |
uppercase | Convert to uppercase |
Networking
| Function | Description |
|---|---|
wait_for_port | Wait for port availability |
get_public_ip | Get public IP |
Utility
| Function | Description |
|---|---|
timestamp | ISO timestamp |
retry | Retry with backoff |
Script Template
#!/bin/bash
# ===========================================
# Script: my-script.sh
# Description: What this script does
# Usage: ./my-script.sh [options]
# ===========================================
set -euo pipefail
# Source utilities
source "$(dirname "$0")/common-utils.sh"
# Configuration
readonly VERSION="1.0.0"
readonly CONFIG_FILE="${HOME}/.config/myapp/config"
# Show help
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS] COMMAND
Options:
-h, --help Show this help
-v, --version Show version
-d, --dry-run Dry run mode
Commands:
init Initialize configuration
run Run the main task
clean Clean up
Examples:
$(basename "$0") init
$(basename "$0") -d run
EOF
}
# Parse arguments
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) usage; exit 0 ;;
-v|--version) echo "v$VERSION"; exit 0 ;;
-d|--dry-run) DRY_RUN=true; shift ;;
*) break ;;
esac
done
# Main
main() {
local command=${1:-""}
case $command in
init)
print_step "Initializing..."
ensure_dir "$(dirname "$CONFIG_FILE")"
print_success "Done"
;;
run)
if $DRY_RUN; then
print_info "[DRY RUN] Would execute task"
else
print_step "Running task..."
# Do work
print_success "Complete"
fi
;;
clean)
if confirm "Remove all data?"; then
rm -rf "$CONFIG_FILE"
print_success "Cleaned"
fi
;;
*)
usage
exit 1
;;
esac
}
main "$@"Best Practices
Strict Mode
set -euo pipefail # Exit on error, undefined vars, pipe failures
IFS=$'\n\t' # Safer word splittingQuoting
# Always quote variables
echo "$variable"
rm -rf "${dir}/"
# Use arrays for commands with spaces
cmd=("docker" "run" "-v" "/path:/path")
"${cmd[@]}"Checking
# File exists
[[ -f "$file" ]] && echo "exists"
# Directory exists
[[ -d "$dir" ]] && echo "exists"
# String not empty
[[ -n "$var" ]] && echo "not empty"
# String empty
[[ -z "$var" ]] && echo "empty"Related skills
AI & Agent Buildingagents