| name | shell-bash |
| description | Shell scripting and Bash programming patterns |
| domain | programming-languages |
| version | 1.0.0 |
| tags | ["bash","shell","scripting","automation","cli"] |
| triggers | {"keywords":{"primary":["bash","shell","sh","zsh","script","terminal","cli"],"secondary":["grep","sed","awk","pipe","cron","automation","makefile"]},"context_boost":["devops","linux","unix","automation","sysadmin"],"context_penalty":["web","frontend","mobile","gui"],"priority":"medium"} |
Shell & Bash Scripting
Overview
Shell scripting patterns for automation, system administration, and CLI tools.
Script Fundamentals
Script Structure
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
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"
VERBOSE=false
DRY_RUN=false
COMPRESS=true
cleanup() {
local exit_code=$?
rm -f "${TEMP_FILE:-}"
exit "$exit_code"
}
trap cleanup EXIT
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() {
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() {
parse_args "$@"
validate_inputs
perform_backup
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Argument Parsing
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:-}"
}
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
SOURCE="${1:-}"
DESTINATION="${2:-}"
}
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
name="John"
readonly CONSTANT="immutable"
name="${name:-default}"
name="${name:=default}"
name="${name:+alternative}"
name="${name:?error message}"
str="Hello, World!"
echo "${str:0:5}"
echo "${str: -6}"
echo "${#str}"
echo "${str/World/Bash}"
echo "${str//o/0}"
echo "${str#Hello, }"
echo "${str%!}"
echo "${str^^}"
echo "${str,,}"
filename="/path/to/file.tar.gz"
echo "${filename##*/}"
echo "${filename%/*}"
echo "${filename%%.*}"
echo "${filename%.gz}"
Arrays
declare -a fruits=("apple" "banana" "cherry")
fruits+=("date")
echo "${fruits[0]}"
echo "${fruits[-1]}"
echo "${fruits[@]}"
echo "${#fruits[@]}"
echo "${!fruits[@]}"
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
for i in "${!fruits[@]}"; do
echo "$i: ${fruits[i]}"
done
declare -A user=(
[name]="John"
[email]="john@example.com"
[age]=30
)
echo "${user[name]}"
echo "${!user[@]}"
echo "${user[@]}"
for key in "${!user[@]}"; do
echo "$key: ${user[$key]}"
done
echo "${fruits[@]:1:2}"
evens=()
for n in "${numbers[@]}"; do
(( n % 2 == 0 )) && evens+=("$n")
done
Control Flow
Conditionals
[[ -z "$str" ]]
[[ -n "$str" ]]
[[ "$a" == "$b" ]]
[[ "$a" != "$b" ]]
[[ "$a" < "$b" ]]
[[ "$a" =~ ^[0-9]+$ ]]
[[ "$a" -eq "$b" ]]
[[ "$a" -ne "$b" ]]
[[ "$a" -lt "$b" ]]
[[ "$a" -le "$b" ]]
[[ "$a" -gt "$b" ]]
[[ "$a" -ge "$b" ]]
[[ -e "$file" ]]
[[ -f "$file" ]]
[[ -d "$dir" ]]
[[ -r "$file" ]]
[[ -w "$file" ]]
[[ -x "$file" ]]
[[ -s "$file" ]]
[[ "$a" -nt "$b" ]]
[[ "$a" -ot "$b" ]]
if [[ "$status" == "success" ]]; then
echo "Success!"
elif [[ "$status" == "pending" ]]; then
echo "Still pending..."
else
echo "Failed"
fi
case "$command" in
start|begin)
start_service
;;
stop|end)
stop_service
;;
restart)
stop_service
start_service
;;
*)
echo "Unknown command: $command"
exit 1
;;
esac
[[ -f "$file" ]] && process_file "$file"
[[ -d "$dir" ]] || mkdir -p "$dir"
Loops
for item in item1 item2 item3; do
echo "$item"
done
for ((i = 0; i < 10; i++)); do
echo "$i"
done
counter=0
while [[ $counter -lt 5 ]]; do
echo "$counter"
((counter++))
done
while IFS= read -r line; do
echo "$line"
done < "$file"
while IFS= read -r file; do
echo "Processing: $file"
done < <(find . -name "*.txt")
until [[ -f "$lockfile" ]]; do
sleep 1
done
for i in {1..10}; do
[[ $i -eq 5 ]] && continue
[[ $i -eq 8 ]] && break
echo "$i"
done
Functions
greet() {
local name="$1"
echo "Hello, $name!"
}
is_valid_email() {
local email="$1"
[[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]
}
if is_valid_email "test@example.com"; then
echo "Valid email"
fi
get_user_count() {
wc -l < /etc/passwd
}
count=$(get_user_count)
process_files() {
local -a files=("$@")
for file in "${files[@]}"; do
echo "Processing: $file"
done
}
process_files file1.txt file2.txt file3.txt
modify_array() {
local -n arr=$1
arr+=("new_element")
}
my_array=("a" "b" "c")
modify_array my_array
echo "${my_array[@]}"
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 "error" logfile.txt
grep -i "error" logfile.txt
grep -E "error|warning" logfile.txt
grep -v "debug" logfile.txt
grep -c "error" logfile.txt
grep -l "error" *.log
grep -r "TODO" src/
sed 's/old/new/' file.txt
sed 's/old/new/g' file.txt
sed -i.bak 's/old/new/g' file.txt
sed '/pattern/d' file.txt
sed -n '10,20p' file.txt
sed 's/^/prefix: /' file.txt
awk '{print $1}' file.txt
awk -F: '{print $1}' /etc/passwd
awk '{sum += $1} END {print sum}' data.txt
awk 'NR > 1' file.txt
awk '$3 > 100 {print $1, $3}' data.txt
awk '{print NR": "$0}' file.txt
cut -d: -f1 /etc/passwd
cut -c1-10 file.txt
cut -d, -f1,3 data.csv
sort file.txt
sort -n numbers.txt
sort -r file.txt
sort -t: -k3 -n /etc/passwd
uniq file.txt
sort file.txt | uniq -c
sort file.txt | uniq -d
echo "hello" | tr 'a-z' 'A-Z'
tr -d '\r' < file.txt
tr -s ' ' < file.txt
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 {}
Process Management
long_running_command &
pid=$!
wait $pid
for file in *.txt; do
process_file "$file" &
done
wait
find . -name "*.jpg" -print0 | xargs -0 -P4 -I{} convert {} {}.png
jobs
fg %1
bg %1
kill %1
diff <(sort file1.txt) <(sort file2.txt)
while read -r line; do
echo "$line"
done < <(command_that_outputs)
{ cmd1; cmd2; cmd3; } > output.txt
( cd /tmp && cmd1; cmd2 )
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