Comprehensive Reference Guide β Parameter expansion, substring operations, pattern matching, case conversion, and validation
Bash provides powerful string manipulation through parameter expansionβa set of operators that transform variable values without external commands. These built-in operations are faster than calling sed, awk, or cut, and are essential for efficient shell scripting.
Extraction: Get substrings by position or pattern
Replacement: Substitute patterns within strings
Trimming: Remove prefixes and suffixes
Case: Convert upper/lower case
Defaults: Handle empty/unset variables
# Get string length
str="Hello World"
echo ${#str} # 11
# Length of array element
arr=(one two three)
echo ${#arr[2]} # 5 (length of "three")
# Simple concatenation
first="Hello"
second="World"
combined="$first $second" # Hello World
combined="${first}${second}" # HelloWorld
# Append to variable
str="Hello"
str+=" World" # Hello World
# ${var:offset:length}
str="Hello World"
echo ${str:0:5} # Hello (first 5 chars)
echo ${str:6} # World (from position 6)
echo ${str:6:3} # Wor (3 chars from position 6)
echo ${str: -5} # World (last 5 chars, note space)
echo ${str: -5:3} # Wor (3 chars from 5th-to-last)
${str:-5} means "default value" but ${str: -5} (with space) means "last 5 characters"
| Syntax | Description | Example | Result |
|---|---|---|---|
${var#pattern} |
Remove shortest match from front | ${file#*/} |
path/to/file.txt β path/to/file.txt |
${var##pattern} |
Remove longest match from front | ${file##*/} |
/path/to/file.txt β file.txt |
${var%pattern} |
Remove shortest match from end | ${file%.*} |
file.tar.gz β file.tar |
${var%%pattern} |
Remove longest match from end | ${file%%.*} |
file.tar.gz β file |
# is on the left of $ on the keyboard β removes from front% is on the right of $ on the keyboard β removes from end
| Syntax | Description | Example | Result |
|---|---|---|---|
${var/pattern/replace} |
Replace first match | ${str/old/new} |
old old β new old |
${var//pattern/replace} |
Replace all matches | ${str//old/new} |
old old β new new |
${var/#pattern/replace} |
Replace if at beginning | ${str/#old/new} |
old text β new text |
${var/%pattern/replace} |
Replace if at end | ${str/%old/new} |
text old β text new |
${var/pattern} |
Delete first match | ${str/old} |
old old β old |
${var//pattern} |
Delete all matches | ${str//old} |
old old β (empty) |
| Syntax | Description | Example | Result |
|---|---|---|---|
${var^} |
Uppercase first character | ${str^} |
hello β Hello |
${var^^} |
Uppercase all | ${str^^} |
hello β HELLO |
${var,} |
Lowercase first character | ${str,} |
HELLO β hELLO |
${var,,} |
Lowercase all | ${str,,} |
HELLO β hello |
${var~} |
Toggle first character | ${str~} |
Hello β hello |
${var~~} |
Toggle all | ${str~~} |
Hello β hELLO |
| Syntax | Description | When Used |
|---|---|---|
${var:-default} |
Use default if unset or empty | Provide fallback value |
${var:=default} |
Assign default if unset or empty | Set and use default |
${var:+alternate} |
Use alternate if var is set | Conditional value |
${var:?error} |
Error if unset or empty | Required variables |
| Syntax | Description | Example |
|---|---|---|
${!var} |
Indirect reference | name="USER"; echo ${!name} |
${!prefix*} |
Variable names with prefix | ${!BASH*} |
${var@Q} |
Quoted for reuse | ${str@Q} |
${var@E} |
Expand escapes | ${str@E} |
${var@L} |
Lowercase (Bash 5.1+) | ${str@L} |
${var@U} |
Uppercase (Bash 5.1+) | ${str@U} |
Extracting directory, filename, basename, and extension from pathsβessential for file processing scripts.
#!/bin/bash
# Path and filename manipulation
filepath="/home/craig/documents/report.tar.gz"
echo "=== Full Path Analysis ==="
echo "Full path: $filepath"
echo ""
# Extract components using parameter expansion
echo "=== Using Parameter Expansion ==="
echo "Directory: ${filepath%/*}" # Remove from last /
echo "Filename: ${filepath##*/}" # Remove up to last /
echo "Extension: ${filepath##*.}" # Remove up to last .
echo "Without ext: ${filepath%.*}" # Remove from last .
echo "Base name: ${filepath##*/}" # Filename with ext
echo "Base no ext: ${${filepath##*/}%.*}" # Won't work - nested
# For nested operations, use intermediate variable
filename="${filepath##*/}"
echo ""
echo "=== With Intermediate Variable ==="
echo "Filename: $filename"
echo "Base name: ${filename%.*}" # report.tar
echo "First ext: ${filename##*.}" # gz
echo "All removed: ${filename%%.*}" # report
echo ""
echo "=== Multiple Extensions ==="
echo "Remove .gz: ${filepath%.gz}"
echo "Remove .tar.gz: ${filepath%.tar.gz}"
echo "Just report: ${filename%%.*}"
echo ""
echo "=== Comparison with basename/dirname ==="
echo "dirname: $(dirname "$filepath")"
echo "basename: $(basename "$filepath")"
echo "basename -s: $(basename -s .tar.gz "$filepath")"
echo ""
echo "=== Practical: Batch File Rename ==="
files=("photo.jpg" "document.pdf" "script.sh" "data.csv")
for f in "${files[@]}"; do
name="${f%.*}"
ext="${f##*.}"
echo "$f β ${name}_backup.${ext}"
done
echo ""
echo "=== Build Output Filename ==="
input="/var/log/apache/access.log"
output_dir="/backup/logs"
date_stamp=$(date +%Y%m%d)
# Extract just the filename
logname="${input##*/}"
# Build new path
output="${output_dir}/${logname%.log}_${date_stamp}.log.gz"
echo "Input: $input"
echo "Output: $output"
Substituting patterns within stringsβsingle occurrence, all occurrences, and positional replacements.
#!/bin/bash
# Search and replace operations
echo "=== Basic Replacement ==="
str="The quick brown fox jumps over the lazy dog"
echo "Original: $str"
echo ""
# Replace first occurrence
echo "First 'the' β 'a': ${str/the/a}"
# Replace all occurrences (case sensitive)
echo "All 'the' β 'a': ${str//the/a}"
# Case-insensitive workaround
lower="${str,,}"
echo "All 'THE' (lower): ${lower//the/a}"
echo ""
echo "=== Positional Replacement ==="
str="old_prefix_data_old_suffix"
echo "Original: $str"
# Replace only at beginning
echo "At start: ${str/#old/new}"
# Replace only at end
echo "At end: ${str/%old_suffix/new_suffix}"
echo ""
echo "=== Delete (Replace with Nothing) ==="
str="Hello World with spaces"
echo "Original: '$str'"
echo "Remove spaces: '${str// /}'"
# Remove specific characters
phone="(555) 123-4567"
echo "Phone: $phone"
echo "Digits only: ${phone//[^0-9]/}"
echo ""
echo "=== Replace with Patterns ==="
# Using wildcards in patterns
path="/usr/local/bin/script"
echo "Path: $path"
echo "Replace /usr with /opt: ${path/\/usr/\/opt}"
# Multiple character replacement
str="a-b-c-d-e"
echo "Dashes: $str"
echo "To underscores: ${str//-/_}"
echo ""
echo "=== Practical: Configuration Processing ==="
config_line="DATABASE_HOST=localhost"
echo "Original: $config_line"
# Extract key and value
key="${config_line%%=*}"
value="${config_line#*=}"
echo "Key: $key"
echo "Value: $value"
# Modify value
new_config="${config_line/localhost/192.168.1.100}"
echo "Updated: $new_config"
echo ""
echo "=== Practical: URL Processing ==="
url="https://example.com/path/to/resource?query=value"
echo "URL: $url"
# Extract protocol
protocol="${url%%://*}"
echo "Protocol: $protocol"
# Extract domain
temp="${url#*://}"
domain="${temp%%/*}"
echo "Domain: $domain"
# Extract path
path="/${temp#*/}"
path="${path%%\?*}"
echo "Path: $path"
echo ""
echo "=== Sanitize User Input ==="
user_input=" Hello <script>alert('xss')</script> World "
echo "Raw input: '$user_input'"
# Remove leading/trailing spaces (Bash 4.4+)
# Or use: trimmed=$(echo "$user_input" | xargs)
sanitized="${user_input#"${user_input%%[![:space:]]*}"}"
sanitized="${sanitized%"${sanitized##*[![:space:]]}"}"
# Remove potential HTML tags
sanitized="${sanitized//</<}"
sanitized="${sanitized//>/>}"
echo "Sanitized: '$sanitized'"
Converting between upper and lower case for normalization, comparison, and display.
#!/bin/bash
# Case conversion operations
echo "=== Basic Case Conversion ==="
str="Hello World"
echo "Original: $str"
echo "UPPERCASE: ${str^^}"
echo "lowercase: ${str,,}"
echo "First cap: ${str^}"
echo "First low: ${str,}"
echo "Toggle all: ${str~~}"
echo ""
echo "=== Selective Conversion ==="
str="hello world"
# Capitalize specific characters
echo "Cap vowels: ${str^^[aeiou]}"
str="HELLO WORLD"
# Lowercase specific characters
echo "Low vowels: ${str,,[AEIOU]}"
echo ""
echo "=== Case-Insensitive Comparison ==="
input="YES"
# Convert to lowercase for comparison
if [[ "${input,,}" == "yes" ]]; then
echo "'$input' matches 'yes' (case-insensitive)"
fi
# Alternative using shopt
shopt -s nocasematch
if [[ "$input" == "yes" ]]; then
echo "nocasematch: '$input' matches 'yes'"
fi
shopt -u nocasematch
echo ""
echo "=== Practical: Username Normalization ==="
usernames=("JohnDoe" "JANE_SMITH" "bob.jones" "Alice")
echo "Normalizing usernames to lowercase:"
for user in "${usernames[@]}"; do
normalized="${user,,}"
# Also replace dots with underscores
normalized="${normalized//./_}"
echo " $user β $normalized"
done
echo ""
echo "=== Practical: Title Case ==="
# Bash doesn't have native title case, build it
title_case() {
local str="$1"
local result=""
local words
read -ra words <<< "$str"
for word in "${words[@]}"; do
# Lowercase the word, then capitalize first letter
word="${word,,}"
word="${word^}"
result+="$word "
done
# Remove trailing space
echo "${result% }"
}
echo "Title case examples:"
echo " 'hello world' β $(title_case 'hello world')"
echo " 'THE QUICK FOX' β $(title_case 'THE QUICK FOX')"
echo " 'mixED CaSe' β $(title_case 'mixED CaSe')"
echo ""
echo "=== Practical: Environment Variable Handling ==="
# Environment variables are typically uppercase
var_name="database_host"
env_name="${var_name^^}"
echo "Variable: $var_name β Env: $env_name"
# Check if env var exists
if [[ -n "${!env_name:-}" ]]; then
echo "$env_name = ${!env_name}"
else
echo "$env_name is not set"
fi
echo ""
echo "=== Filename Case Standardization ==="
files=("Document.PDF" "IMAGE.JPG" "Script.SH" "Data.CSV")
echo "Standardizing extensions to lowercase:"
for f in "${files[@]}"; do
name="${f%.*}"
ext="${f##*.}"
ext_lower="${ext,,}"
echo " $f β ${name}.${ext_lower}"
done
Extracting portions of strings by positionβfirst N characters, last N characters, middle sections.
#!/bin/bash
# Substring extraction by position
str="Hello World Example"
echo "=== Basic Extraction ==="
echo "String: '$str' (length: ${#str})"
echo ""
echo "First 5: '${str:0:5}'" # Hello
echo "From 6: '${str:6}'" # World Example
echo "6 to 10: '${str:6:5}'" # World
echo "Last 7: '${str: -7}'" # Example
echo "Last 7, 4: '${str: -7:4}'" # Exam
echo ""
echo "=== Negative Length (Bash 4.2+) ==="
# Negative length means "stop before end"
echo "Skip first 6, stop before last 8: '${str:6:-8}'" # World
echo ""
echo "=== Fixed-Width Field Extraction ==="
# Simulating fixed-width record
record="JohnDoe 25New York "
# 0123456789012345678901
echo "Record: '$record'"
echo "Name (0-10): '${record:0:10}'"
echo "Age (10-12): '${record:10:2}'"
echo "City (12-): '${record:12}'"
echo ""
echo "=== Practical: Log Parsing ==="
log_line="2025-11-18 14:30:45 ERROR Connection timeout"
echo "Log: $log_line"
echo ""
echo "Date: ${log_line:0:10}"
echo "Time: ${log_line:11:8}"
echo "Level: ${log_line:20:5}"
echo "Message: ${log_line:26}"
echo ""
echo "=== Practical: Format Phone Numbers ==="
format_phone() {
local digits="${1//[^0-9]/}" # Remove non-digits
if [[ ${#digits} -eq 10 ]]; then
echo "(${digits:0:3}) ${digits:3:3}-${digits:6:4}"
elif [[ ${#digits} -eq 11 && ${digits:0:1} == "1" ]]; then
echo "+1 (${digits:1:3}) ${digits:4:3}-${digits:7:4}"
else
echo "$1 (invalid)"
fi
}
echo "Phone formatting:"
echo " 5551234567 β $(format_phone '5551234567')"
echo " 15551234567 β $(format_phone '15551234567')"
echo " 555-123-4567 β $(format_phone '555-123-4567')"
echo ""
echo "=== Practical: Truncate with Ellipsis ==="
truncate() {
local str="$1"
local max="$2"
if [[ ${#str} -gt $max ]]; then
echo "${str:0:$((max-3))}..."
else
echo "$str"
fi
}
long_text="This is a very long string that needs truncation"
echo "Original: $long_text"
echo "Max 20: $(truncate "$long_text" 20)"
echo "Max 30: $(truncate "$long_text" 30)"
echo "Max 50: $(truncate "$long_text" 50)"
echo ""
echo "=== Practical: Extract UUID Components ==="
uuid="550e8400-e29b-41d4-a716-446655440000"
echo "UUID: $uuid"
echo ""
echo "Time-low: ${uuid:0:8}"
echo "Time-mid: ${uuid:9:4}"
echo "Time-high: ${uuid:14:4}"
echo "Clock: ${uuid:19:4}"
echo "Node: ${uuid:24:12}"
Handling unset or empty variables gracefully with defaults, assignments, and error checking.
#!/bin/bash
# Default values and required variables
echo "=== Use Default If Unset/Empty ==="
unset myvar
echo "Unset: '${myvar:-default}'" # Uses default
myvar=""
echo "Empty: '${myvar:-default}'" # Uses default
myvar="value"
echo "Set: '${myvar:-default}'" # Uses value
echo ""
echo "=== Assign Default If Unset/Empty ==="
unset myvar
echo "Before: myvar='$myvar'"
: ${myvar:=assigned_default} # Assigns if unset/empty
echo "After: myvar='$myvar'"
echo ""
echo "=== Use Alternate If Set ==="
unset myvar
echo "Unset: '${myvar:+alternate}'" # Empty (var not set)
myvar="value"
echo "Set: '${myvar:+alternate}'" # Uses alternate
# Practical: conditional flag
verbose=""
echo "Verbose flag: ${verbose:+--verbose}"
verbose="yes"
echo "Verbose flag: ${verbose:+--verbose}"
echo ""
echo "=== Error If Unset/Empty ==="
# This would exit the script:
# : ${REQUIRED_VAR:?Error: REQUIRED_VAR must be set}
# Safe way to demonstrate
check_required() {
local var_name=$1
local var_value="${!var_name}"
if [[ -z "$var_value" ]]; then
echo "ERROR: $var_name is required but not set"
return 1
else
echo "OK: $var_name = '$var_value'"
return 0
fi
}
DATABASE_HOST="localhost"
unset DATABASE_PORT
check_required DATABASE_HOST
check_required DATABASE_PORT
echo ""
echo "=== Practical: Script Configuration ==="
# Common pattern for script defaults
CONFIG_FILE="${CONFIG_FILE:-/etc/myapp/config.conf}"
LOG_LEVEL="${LOG_LEVEL:-INFO}"
MAX_RETRIES="${MAX_RETRIES:-3}"
TIMEOUT="${TIMEOUT:-30}"
echo "Configuration:"
echo " CONFIG_FILE: $CONFIG_FILE"
echo " LOG_LEVEL: $LOG_LEVEL"
echo " MAX_RETRIES: $MAX_RETRIES"
echo " TIMEOUT: $TIMEOUT"
echo ""
echo "=== Practical: Command Line Defaults ==="
# Simulate processing arguments with defaults
process_args() {
local input="${1:-/dev/stdin}"
local output="${2:-/dev/stdout}"
local format="${3:-json}"
echo "Processing:"
echo " Input: $input"
echo " Output: $output"
echo " Format: $format"
}
echo "No args:"
process_args
echo ""
echo "With args:"
process_args "data.txt" "output.csv" "csv"
echo ""
echo "=== Practical: Safe Variable Expansion ==="
# Avoid errors with unset variables in strict mode
# set -u # Would cause errors on unset variables
# Safe patterns
name="${USER:-unknown}"
home="${HOME:-/tmp}"
shell="${SHELL:-/bin/sh}"
echo "Safe defaults:"
echo " name=$name, home=$home, shell=$shell"
# Check before use pattern
if [[ -n "${OPTIONAL_VAR:-}" ]]; then
echo "OPTIONAL_VAR is set: $OPTIONAL_VAR"
else
echo "OPTIONAL_VAR is not set"
fi
${var-default} only checks if unset${var:-default} checks if unset OR emptyUsing regex for validation, extraction, and complex pattern matching with [[ =~ ]].
#!/bin/bash
# Regex pattern matching
echo "=== Basic Regex Matching ==="
str="Error: Connection failed at 192.168.1.100:3306"
if [[ $str =~ Error ]]; then
echo "String contains 'Error'"
fi
if [[ $str =~ ^Error ]]; then
echo "String starts with 'Error'"
fi
echo ""
echo "=== Capture Groups with BASH_REMATCH ==="
str="User john logged in from 192.168.1.50"
if [[ $str =~ User\ ([a-z]+)\ logged\ in\ from\ ([0-9.]+) ]]; then
echo "Full match: ${BASH_REMATCH[0]}"
echo "Username: ${BASH_REMATCH[1]}"
echo "IP: ${BASH_REMATCH[2]}"
fi
echo ""
echo "=== Validation Patterns ==="
# Email validation (simplified)
validate_email() {
local email=$1
if [[ $email =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
echo "$email: VALID"
else
echo "$email: INVALID"
fi
}
validate_email "= user@example.com"
validate_email "invalid@"
validate_email "= user@example.com"
echo ""
# IP address validation
validate_ip() {
local ip=$1
local octet="([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"
local regex="^${octet}\.${octet}\.${octet}\.${octet}$"
if [[ $ip =~ $regex ]]; then
echo "$ip: VALID"
else
echo "$ip: INVALID"
fi
}
validate_ip "192.168.1.1"
validate_ip "256.1.1.1"
validate_ip "10.0.0.255"
echo ""
echo "=== Extract Multiple Matches ==="
log="Error at 10:30:45, Warning at 11:15:22, Error at 14:00:00"
# Find all times
echo "Times found:"
remaining="$log"
while [[ $remaining =~ ([0-9]{2}:[0-9]{2}:[0-9]{2}) ]]; do
echo " ${BASH_REMATCH[1]}"
# Remove the match to find next
remaining="${remaining#*${BASH_REMATCH[1]}}"
done
echo ""
echo "=== Practical: Parse Key=Value Pairs ==="
parse_kvp() {
local str="$1"
if [[ $str =~ ^([^=]+)=(.*)$ ]]; then
echo "Key: '${BASH_REMATCH[1]}'"
echo "Value: '${BASH_REMATCH[2]}'"
else
echo "Invalid format: $str"
fi
}
parse_kvp "name=John Doe"
echo ""
parse_kvp "DATABASE_URL=mysql://localhost:3306/mydb"
echo ""
echo "=== Practical: Version Number Extraction ==="
version_string="MyApp version 2.4.1-beta (build 1234)"
if [[ $version_string =~ ([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-z]+)? ]]; then
echo "Version: ${BASH_REMATCH[0]}"
echo " Major: ${BASH_REMATCH[1]}"
echo " Minor: ${BASH_REMATCH[2]}"
echo " Patch: ${BASH_REMATCH[3]}"
echo " Tag: ${BASH_REMATCH[4]:-none}"
fi
echo ""
echo "=== Practical: Log Level Detection ==="
detect_level() {
local line="$1"
if [[ $line =~ (ERROR|FATAL|CRITICAL) ]]; then
echo "CRITICAL: $line"
elif [[ $line =~ (WARN|WARNING) ]]; then
echo "WARNING: $line"
elif [[ $line =~ (INFO) ]]; then
echo "INFO: $line"
else
echo "OTHER: $line"
fi
}
detect_level "2025-11-18 ERROR Database connection failed"
detect_level "2025-11-18 WARNING Disk space low"
detect_level "2025-11-18 INFO Service started"
Padding strings to fixed widths for formatted output, reports, and data alignment.
#!/bin/bash
# String padding and alignment
echo "=== printf for Padding ==="
str="Hello"
printf "Right-align 15: '%15s'\n" "$str"
printf "Left-align 15: '%-15s'\n" "$str"
printf "Zero-pad num: '%08d'\n" 42
echo ""
echo "=== Custom Padding Functions ==="
# Pad right (left-align)
pad_right() {
local str="$1"
local width="$2"
local char="${3:- }"
printf "%-${width}s" "$str" | tr ' ' "$char"
}
# Pad left (right-align)
pad_left() {
local str="$1"
local width="$2"
local char="${3:- }"
printf "%${width}s" "$str" | tr ' ' "$char"
}
# Center
center() {
local str="$1"
local width="$2"
local len=${#str}
local padding=$(( (width - len) / 2 ))
printf "%*s%s%*s" $padding "" "$str" $((width - len - padding)) ""
}
echo "Pad right: '$(pad_right "Test" 10)'"
echo "Pad left: '$(pad_left "Test" 10)'"
echo "Pad zeros: '$(pad_left "42" 8 "0")'"
echo "Center: '$(center "Title" 20)'"
echo ""
echo "=== Practical: Table Formatting ==="
# Data
declare -a data=(
"web-01|192.168.1.10|Running|45%"
"web-02|192.168.1.11|Running|62%"
"db-01|192.168.1.20|Stopped|0%"
"cache-01|192.168.1.30|Running|28%"
)
# Header
printf "%-12s %-15s %-10s %6s\n" "Server" "IP Address" "Status" "CPU"
printf "%-12s %-15s %-10s %6s\n" "------" "----------" "------" "---"
# Rows
for row in "${data[@]}"; do
IFS='|' read -r name ip status cpu <<< "$row"
printf "%-12s %-15s %-10s %6s\n" "$name" "$ip" "$status" "$cpu"
done
echo ""
echo "=== Practical: Progress Bar ==="
progress_bar() {
local current=$1
local total=$2
local width=${3:-40}
local percent=$((current * 100 / total))
local filled=$((current * width / total))
local empty=$((width - filled))
# Build bar
local bar=""
for ((i=0; i<filled; i++)); do bar+="β"; done
for ((i=0; i<empty; i++)); do bar+="β"; done
printf "\r[%s] %3d%%" "$bar" "$percent"
}
echo "Progress bar example:"
for i in {0..100..10}; do
progress_bar $i 100 30
sleep 0.1
done
echo ""
echo ""
echo "=== Practical: Fixed-Width Record ==="
# Create fixed-width record
create_record() {
local name="$1"
local age="$2"
local city="$3"
# Fixed widths: name=20, age=3, city=15
printf "%-20s%3s%-15s\n" \
"${name:0:20}" \
"${age:0:3}" \
"${city:0:15}"
}
echo "Fixed-width records:"
create_record "John Doe" "25" "New York"
create_record "Jane Smith" "32" "Los Angeles"
create_record "Bob Johnson Jr." "45" "San Francisco"
echo ""
echo "=== Repeat Character ==="
repeat_char() {
local char="$1"
local count="$2"
printf "%${count}s" | tr ' ' "$char"
}
echo "Divider: $(repeat_char '=' 40)"
echo "Dots: $(repeat_char '.' 20)"
Splitting strings into arrays and joining array elements back into strings.
#!/bin/bash
# String splitting and joining
echo "=== Split String to Array ==="
# Using read with IFS
str="one:two:three:four"
IFS=':' read -ra arr <<< "$str"
echo "String: $str"
echo "Array: ${arr[*]}"
echo "Count: ${#arr[@]}"
echo ""
echo "=== Split on Different Delimiters ==="
# Comma-separated
csv="apple,banana,cherry,date"
IFS=',' read -ra fruits <<< "$csv"
echo "CSV: ${fruits[*]}"
# Multi-character delimiter (workaround)
str="item1||item2||item3"
# Replace delimiter with single char first
temp="${str//||/$'\n'}"
readarray -t items <<< "$temp"
echo "Double-pipe: ${items[*]}"
echo ""
echo "=== Join Array to String ==="
# Using printf and parameter expansion
arr=("one" "two" "three" "four")
# Join with comma
joined=$(IFS=','; echo "${arr[*]}")
echo "Comma-joined: $joined"
# Join with custom delimiter
join_array() {
local delim="$1"
shift
local first="$1"
shift
printf "%s" "$first" "${@/#/$delim}"
}
echo "Pipe-joined: $(join_array '|' "${arr[@]}")"
echo "Dash-joined: $(join_array ' - ' "${arr[@]}")"
echo ""
echo "=== Practical: PATH Manipulation ==="
echo "Current PATH:"
IFS=':' read -ra paths <<< "$PATH"
for i in "${!paths[@]}"; do
echo " [$i] ${paths[$i]}"
done | head -5
echo " ... (truncated)"
# Add to PATH
new_path="/opt/myapp/bin"
if [[ ":$PATH:" != *":$new_path:"* ]]; then
PATH="$new_path:$PATH"
echo "Added: $new_path"
fi
echo ""
echo "=== Practical: CSV Processing ==="
csv_data="name,age,city
John,25,New York
Jane,30,Boston
Bob,35,Chicago"
echo "CSV Data:"
while IFS=',' read -r name age city; do
printf " Name: %-8s Age: %s City: %s\n" "$name" "$age" "$city"
done <<< "$csv_data"
echo ""
echo "=== Practical: Parse Command Output ==="
# Simulate parsing 'ls -l' output
ls_output="drwxr-xr-x 2 user group 4096 Nov 18 14:30 documents"
read -r perms links owner group size month day time name <<< "$ls_output"
echo "Parsed ls output:"
echo " Name: $name"
echo " Owner: $owner"
echo " Size: $size"
echo " Date: $month $day $time"
echo ""
echo "=== Split Preserving Empty Fields ==="
# Default IFS skips empty fields
str="one::three"
IFS=':' read -ra arr <<< "$str"
echo "Default: ${#arr[@]} elements" # Only 2!
# Use -d '' to preserve
readarray -d ':' -t arr <<< "$str:"
# Remove trailing newline from last element
arr[-1]="${arr[-1]%$'\n'}"
arr[-1]="${arr[-1]%:}"
echo "With -d: ${#arr[@]} elements"
echo "Elements: '${arr[0]}' '${arr[1]}' '${arr[2]}'"
Removing leading, trailing, and excess whitespace from strings.
#!/bin/bash
# Trimming whitespace
echo "=== Basic Trim Functions ==="
# Trim leading whitespace
trim_left() {
local var="$1"
var="${var#"${var%%[![:space:]]*}"}"
echo "$var"
}
# Trim trailing whitespace
trim_right() {
local var="$1"
var="${var%"${var##*[![:space:]]}"}"
echo "$var"
}
# Trim both ends
trim() {
local var="$1"
var="${var#"${var%%[![:space:]]*}"}"
var="${var%"${var##*[![:space:]]}"}"
echo "$var"
}
str=" Hello World "
echo "Original: '$str'"
echo "Trim left: '$(trim_left "$str")'"
echo "Trim right: '$(trim_right "$str")'"
echo "Trim both: '$(trim "$str")'"
echo ""
echo "=== Alternative Methods ==="
# Using xargs (also collapses internal spaces)
str=" Hello World "
echo "Original: '$str'"
echo "xargs: '$(echo "$str" | xargs)'"
# Using sed
echo "sed trim: '$(echo "$str" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')'"
# Using awk
echo "awk trim: '$(echo "$str" | awk '{$1=$1};1')'"
echo ""
echo "=== Collapse Multiple Spaces ==="
str="Hello World with spaces"
echo "Original: '$str'"
echo "Collapsed: '${str//+([[:space:]])/ }'"
# Need extglob for +() pattern
shopt -s extglob
collapsed="${str//+([[:space:]])/ }"
shopt -u extglob
echo "Collapsed: '$collapsed'"
echo ""
echo "=== Remove All Whitespace ==="
str=" H e l l o W o r l d "
echo "Original: '$str'"
echo "No spaces: '${str//[[:space:]]/}'"
echo ""
echo "=== Practical: Clean User Input ==="
clean_input() {
local input="$1"
# Trim leading/trailing
input="${input#"${input%%[![:space:]]*}"}"
input="${input%"${input##*[![:space:]]}"}"
# Collapse multiple spaces
shopt -s extglob
input="${input//+([[:space:]])/ }"
shopt -u extglob
echo "$input"
}
inputs=(
" John Doe "
" Tab Separated "
" Multiple Spaces Here "
)
echo "Cleaning user input:"
for input in "${inputs[@]}"; do
cleaned=$(clean_input "$input")
echo " '$input' β '$cleaned'"
done
echo ""
echo "=== Practical: Clean Config Values ==="
parse_config() {
local line="$1"
# Split on =
local key="${line%%=*}"
local value="${line#*=}"
# Trim whitespace from both
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
# Remove quotes if present
value="${value#\"}"
value="${value%\"}"
value="${value#\'}"
value="${value%\'}"
echo "Key: '$key', Value: '$value'"
}
config_lines=(
" DATABASE_HOST = localhost "
"PORT=3306"
" NAME = \"My Database\" "
)
echo "Parsing config:"
for line in "${config_lines[@]}"; do
parse_config "$line"
done
echo ""
echo "=== Trim Specific Characters ==="
str="###Hello###"
echo "Original: '$str'"
echo "No hashes: '${str//\#/}'"
echo "Trim left: '${str##\#\#\#}'"
echo "Trim both: '${str//\#/}'"
# Trim specific leading chars
str="000042"
echo "Leading zeros: '${str##+(0)}'" # Needs extglob
A reusable library of string manipulation functions for your scripts.
#!/bin/bash
# Complete string manipulation library
#######################################
# Basic Operations
#######################################
# String length
str_length() {
echo "${#1}"
}
# Check if empty
str_empty() {
[[ -z "$1" ]]
}
# Check if contains substring
str_contains() {
[[ "$1" == *"$2"* ]]
}
# Check if starts with
str_starts_with() {
[[ "$1" == "$2"* ]]
}
# Check if ends with
str_ends_with() {
[[ "$1" == *"$2" ]]
}
#######################################
# Case Conversion
#######################################
str_upper() {
echo "${1^^}"
}
str_lower() {
echo "${1,,}"
}
str_capitalize() {
echo "${1^}"
}
#######################################
# Trimming
#######################################
str_trim() {
local var="$1"
var="${var#"${var%%[![:space:]]*}"}"
var="${var%"${var##*[![:space:]]}"}"
echo "$var"
}
str_trim_left() {
echo "${1#"${1%%[![:space:]]*}"}"
}
str_trim_right() {
echo "${1%"${1##*[![:space:]]}"}"
}
#######################################
# Extraction
#######################################
str_substring() {
local str="$1"
local start="$2"
local length="${3:-${#str}}"
echo "${str:start:length}"
}
str_left() {
echo "${1:0:$2}"
}
str_right() {
echo "${1: -$2}"
}
#######################################
# Search and Replace
#######################################
str_replace() {
echo "${1//$2/$3}"
}
str_replace_first() {
echo "${1/$2/$3}"
}
str_remove() {
echo "${1//$2/}"
}
#######################################
# Path Operations
#######################################
str_dirname() {
echo "${1%/*}"
}
str_basename() {
echo "${1##*/}"
}
str_extension() {
local name="${1##*/}"
echo "${name##*.}"
}
str_strip_extension() {
echo "${1%.*}"
}
#######################################
# Splitting and Joining
#######################################
str_split() {
local str="$1"
local delim="$2"
local -n arr="$3"
IFS="$delim" read -ra arr <<< "$str"
}
str_join() {
local delim="$1"
shift
local first="$1"
shift
printf "%s" "$first" "${@/#/$delim}"
}
#######################################
# Validation
#######################################
str_is_number() {
[[ "$1" =~ ^-?[0-9]+\.?[0-9]*$ ]]
}
str_is_integer() {
[[ "$1" =~ ^-?[0-9]+$ ]]
}
str_is_alpha() {
[[ "$1" =~ ^[a-zA-Z]+$ ]]
}
str_is_alnum() {
[[ "$1" =~ ^[a-zA-Z0-9]+$ ]]
}
#######################################
# Utility
#######################################
str_repeat() {
local str="$1"
local count="$2"
local result=""
for ((i=0; i<count; i++)); do
result+="$str"
done
echo "$result"
}
str_reverse() {
echo "$1" | rev
}
str_pad_left() {
printf "%$2s" "$1"
}
str_pad_right() {
printf "%-$2s" "$1"
}
#######################################
# Demo
#######################################
echo "========================================"
echo " String Library Demo"
echo "========================================"
echo ""
echo "=== Basic Operations ==="
str="Hello World"
echo "str_length '$str': $(str_length "$str")"
str_contains "$str" "World" && echo "Contains 'World': yes"
str_starts_with "$str" "Hello" && echo "Starts with 'Hello': yes"
str_ends_with "$str" "World" && echo "Ends with 'World': yes"
echo ""
echo "=== Case Conversion ==="
echo "str_upper 'hello': $(str_upper 'hello')"
echo "str_lower 'HELLO': $(str_lower 'HELLO')"
echo "str_capitalize 'hello': $(str_capitalize 'hello')"
echo ""
echo "=== Trimming ==="
echo "str_trim ' hello ': '$(str_trim ' hello ')'"
echo ""
echo "=== Extraction ==="
echo "str_substring 'Hello World' 0 5: '$(str_substring 'Hello World' 0 5)'"
echo "str_left 'Hello World' 5: '$(str_left 'Hello World' 5)'"
echo "str_right 'Hello World' 5: '$(str_right 'Hello World' 5)'"
echo ""
echo "=== Search and Replace ==="
echo "str_replace 'foo bar foo' 'foo' 'baz': '$(str_replace 'foo bar foo' 'foo' 'baz')'"
echo "str_remove 'Hello World' 'l': '$(str_remove 'Hello World' 'l')'"
echo ""
echo "=== Path Operations ==="
path="/home/user/document.txt"
echo "str_dirname '$path': $(str_dirname "$path")"
echo "str_basename '$path': $(str_basename "$path")"
echo "str_extension '$path': $(str_extension "$path")"
echo ""
echo "=== Validation ==="
str_is_number "42.5" && echo "'42.5' is number: yes"
str_is_integer "42" && echo "'42' is integer: yes"
str_is_alpha "Hello" && echo "'Hello' is alpha: yes"
echo ""
echo "=== Utility ==="
echo "str_repeat '*' 10: '$(str_repeat '*' 10)'"
echo "str_reverse 'Hello': '$(str_reverse 'Hello')'"
echo ""
echo "========================================"
# Length
${#var} # String length
# Substring
${var:offset:length} # Extract substring
${var: -n} # Last n characters
# Trim patterns
${var#pattern} # Remove shortest from front
${var##pattern} # Remove longest from front
${var%pattern} # Remove shortest from end
${var%%pattern} # Remove longest from end
# Replace
${var/find/replace} # First occurrence
${var//find/replace} # All occurrences
${var/#find/replace} # If at beginning
${var/%find/replace} # If at end
# Case (Bash 4+)
${var^^} # UPPERCASE
${var,,} # lowercase
${var^} # First char upper
# Defaults
${var:-default} # Use default if unset/empty
${var:=default} # Assign default
${var:+alternate} # Use alternate if set
${var:?error} # Error if unset/empty
| Task | Code |
|---|---|
| Get filename | ${path##*/} |
| Get directory | ${path%/*} |
| Get extension | ${file##*.} |
| Remove extension | ${file%.*} |
| Check contains | [[ $str == *"sub"* ]] |
| Check starts | [[ $str == "pre"* ]] |
| Check ends | [[ $str == *"suf" ]] |
| Split to array | IFS=':' read -ra arr <<< "$str" |
"${var}" prevents word splitting[[ ]] for string tests, not [ ][[ -z "$var" ]]${var: -5}${var-default} instead of ${var:-default}${var/pattern/} (use glob patterns)