| name | string-manipulation-mastery |
| description | Advanced bash string manipulation including parameter expansion, pattern matching, regex, and text processing (2025) |
CRITICAL GUIDELINES
Windows File Path Requirements
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (\) in file paths, NOT forward slashes (/).
Bash String Manipulation Mastery (2025)
Overview
Comprehensive guide to string manipulation in bash using parameter expansion, pattern matching, regular expressions, and built-in transformations. Master these techniques to avoid spawning external processes like sed, awk, or cut for simple operations.
Parameter Expansion Basics
String Length
#!/usr/bin/env bash
set -euo pipefail
str="Hello, World!"
echo "${#str}"
arr=("short" "much longer string")
echo "${#arr[0]}"
echo "${#arr[1]}"
echo "${#arr[@]}"
Substring Extraction
#!/usr/bin/env bash
set -euo pipefail
str="Hello, World!"
echo "${str:7}"
echo "${str:0:5}"
echo "${str:7:5}"
echo "${str: -6}"
echo "${str:(-6)}"
echo "${str: -6:5}"
last_n() {
local str="$1" n="$2"
echo "${str: -$n}"
}
last_n "Hello" 3
between() {
local str="$1" start="$2" end="$3"
echo ""
}
between 3 7
Default Values
#!/usr/bin/env bash
set -euo pipefail
name="${1:-Anonymous}"
echo "Hello, $name"
: "${CONFIG_FILE:=/etc/app.conf}"
debug_flag="${DEBUG:+--verbose}"
: "${REQUIRED_VAR:?REQUIRED_VAR must be set}"
setup_config() {
: "${DB_HOST:=localhost}"
: "${DB_PORT:=5432}"
: "${DB_NAME:=myapp}"
: "${DB_USER:=postgres}"
}
Indirect Expansion
#!/usr/bin/env bash
set -euo pipefail
config_host="server.example.com"
config_port="8080"
key="config_host"
echo "${!key}"
for suffix in host port; do
var="config_$suffix"
echo "$suffix = ${!var}"
done
for var in "${!config_@}"; do
echo "$var = ${!var}"
done
arr=(a b c d e)
idx=2
echo "${arr[$idx]}"
get_array_element() {
local -n arr_ref="$1"
local idx="$2"
echo "${arr_ref[$idx]}"
}
get_array_element arr 3
Pattern Matching
Prefix Removal
#!/usr/bin/env bash
set -euo pipefail
path="/home/user/documents/file.tar.gz"
echo "${path#*/}"
echo "${path##*/}"
filename="archive.tar.gz"
echo "${filename#*.}"
echo "${filename##*.}"
url="https://example.com/path"
echo "${url#https://}"
get_extension() {
local file="$1"
echo "${file##*.}"
}
strip_leading_zeros() {
local num="$1"
echo "${num#"${num%%[!0]*}"}"
}
strip_leading_zeros "000123"
Suffix Removal
#!/usr/bin/env bash
set -euo pipefail
path="/home/user/documents/file.tar.gz"
echo "${path%/*}"
echo "${path%%/*}"
filename="archive.tar.gz"
echo "${filename%.*}"
echo "${filename%%.*}"
dirname="${path%/*}"
basename="${path##*/}"
name_without_ext="${basename%.*}"
change_extension() {
local file="$1" new_ext="$2"
echo "${file%.*}.$new_ext"
}
change_extension "doc.txt" "md"
Pattern Substitution
#!/usr/bin/env bash
set -euo pipefail
str="hello hello hello"
echo "${str/hello/hi}"
echo "${str//hello/hi}"
echo "${str/#hello/hi}"
echo "${str/%hello/goodbye}"
echo "${str//hello/}"
sanitize_filename() {
local name="$1"
name="${name// /_}"
name="${name//[^a-zA-Z0-9._-]/}"
echo "$name"
}
sanitize_filename "My File (2024).txt"
normalize_path() {
path=
[[ == *//* ]];
path=
}
Case Transformation
Basic Case Changes
#!/usr/bin/env bash
set -euo pipefail
str="Hello World"
echo "${str,}"
echo "${str,,}"
echo "${str^}"
str2="hello world"
echo "${str2^}"
echo "${str,,}"
echo "${str2^^}"
echo "${str~~}"
Pattern-Based Case Changes
#!/usr/bin/env bash
set -euo pipefail
str="hello world"
echo "${str^^[aeiou]}"
str2="HELLO WORLD"
echo "${str2,,[AEIOU]}"
title_case() {
local str="$1"
local result=""
local capitalize=true
for ((i=0; i<${#str}; i++)); do
local char="${str:$i:1}"
if [[ "$char" == " " ]]; then
result+="$char"
capitalize=true
elif $capitalize; then
result+="${char^}"
capitalize=false
else
result+="${char,}"
fi
done
echo "$result"
}
title_case
Regular Expressions
Bash Regex Matching
#!/usr/bin/env bash
set -euo pipefail
str="Hello World 123"
if [[ "$str" =~ ^Hello ]]; then
echo "Starts with Hello"
fi
if [[ "$str" =~ [0-9]+ ]]; then
echo "Contains numbers"
fi
email="user@example.com"
if [[ "$email" =~ ^([^@]+)@(.+)$ ]]; then
echo "User: ${BASH_REMATCH[1]}"
echo "Domain: ${BASH_REMATCH[2]}"
echo "Full match: ${BASH_REMATCH[0]}"
fi
pattern='^[0-9]{4}-[0-9]{2}-[0-9]{2}$'
date="2024-03-15"
if [[ "$date" =~ $pattern ]]; then
echo "Valid date format"
fi
log_line='2024-03-15 10:30:45 ERROR Connection failed'
pattern=
[[ =~ ]];
=
=
level=
message=
Practical Regex Patterns
#!/usr/bin/env bash
set -euo pipefail
is_valid_email() {
local email="$1"
local pattern='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
[[ "$email" =~ $pattern ]]
}
is_valid_ip() {
local ip="$1"
local octet='(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
local pattern="^${octet}\.${octet}\.${octet}\.${octet}$"
[[ "$ip" =~ $pattern ]]
}
parse_url() {
local url="$1"
local pattern='^(https?|ftp)://([^/:]+)(:([0-9]+))?(/.*)?$'
if [[ "$url" =~ $pattern ]]; then
echo "Protocol: ${BASH_REMATCH[1]}"
echo "Host: ${BASH_REMATCH[2]}"
echo "Port: ${BASH_REMATCH[4]:-default}"
>&2
1
}
() {
version=
pattern=
[[ =~ ]];
}
String Splitting and Joining
Split String to Array
#!/usr/bin/env bash
set -euo pipefail
str="one,two,three,four"
IFS=',' read -ra arr <<< "$str"
echo "${arr[0]}"
echo "${arr[2]}"
str="one||two||three"
arr=()
while [[ "$str" == *"||"* ]]; do
arr+=("${str%%||*}")
str="${str#*||}"
done
arr+=("$str")
mapfile -t lines <<< "$(echo -e "line1\nline2\nline3")"
str="one two three"
read -ra arr <<< "$str"
Join Array to String
#!/usr/bin/env bash
set -euo pipefail
arr=("one" "two" "three" "four")
join_by() {
local IFS="$1"
shift
echo "$*"
}
join_by ',' "${arr[@]}"
join_by ' | ' "${arr[@]}"
join_array() {
local delim="$1"
shift
local first="$1"
shift
printf '%s' "$first" "${@/#/$delim}"
}
join_array ',' "${arr[@]}"
printf '"%s" ' "${arr[@]}"
printf '%s\n' "${arr[@]}"
Text Processing Without External Commands
Trim Whitespace
#!/usr/bin/env bash
set -euo pipefail
trim_leading() {
local str="$1"
echo "${str#"${str%%[![:space:]]*}"}"
}
trim_trailing() {
local str="$1"
echo "${str%"${str##*[![:space:]]}"}"
}
trim() {
local str="$1"
str="${str#"${str%%[![:space:]]*}"}"
str="${str%"${str##*[![:space:]]}"}"
echo "$str"
}
trim_extglob() {
shopt -s extglob
local str="$1"
str="${str##+([[:space:]])}"
str="${str%%+([[:space:]])}"
echo "$str"
}
str=" hello world "
trim "$str"
String Repetition
#!/usr/bin/env bash
set -euo pipefail
repeat() {
local str="$1"
local n="$2"
local result=""
for ((i=0; i<n; i++)); do
result+="$str"
done
echo "$result"
}
repeat "ab" 5
repeat_printf() {
local str="$1"
local n="$2"
printf '%s' $(printf '%.0s'"$str" $(seq 1 "$n"))
}
separator() {
local char="${1:--}"
local width="${2:-80}"
printf '%*s\n' "$width" '' | tr
}
separator 40
Character Replacement
#!/usr/bin/env bash
set -euo pipefail
str="hello world"
echo "${str//l/L}"
echo "${str//o/}"
translate() {
local str="$1"
local from="$2"
local to="$3"
for ((i=0; i<${#from}; i++)); do
str="${str//${from:$i:1}/${to:$i:1}}"
done
echo "$str"
}
translate "hello" "el" "ip"
Padding and Alignment
#!/usr/bin/env bash
set -euo pipefail
pad_right() {
local str="$1"
local width="$2"
local char="${3:- }"
printf "%-${width}s" "$str" | tr ' ' "$char"
}
pad_left() {
local str="$1"
local width="$2"
local char="${3:- }"
printf "%${width}s" "$str" | tr ' ' "$char"
}
center() {
local str="$1"
local width="$2"
local len=${#str}
local padding=$(( (width - len) / 2 ))
$((width - len - padding))
}
() {
num=
width=
}
zero_pad 42 5
() {
}
print_table_row
print_table_row
Extended Globbing
Enable and Use
#!/usr/bin/env bash
set -euo pipefail
shopt -s extglob
ls *.@(jpg|png|gif)
ls !(*.bak|*.tmp)
ls +([0-9]).txt
str=" hello world "
echo "${str##+([[:space:]])}"
echo "${str//+([[:space:]])/ }"
file="archive.tar.gz.bak"
echo "${file%.@(tar|gz|bak)*}"
case "$response" in
@(yes|y|Y|YES))
echo "Affirmative"
;;
@(no|n|N|NO))
echo "Negative"
;;
esac
Practical Extended Glob Patterns
#!/usr/bin/env bash
set -euo pipefail
shopt -s extglob
rm -f *.@(bak|backup|orig|~)
ls *.@(c|cpp|h|hpp|cc)
for file in !(test_*|_*).py; do
process "$file"
done
version_pattern='+([0-9]).+([0-9]).+([0-9])?(-+([a-z0-9]))'
if [[ "$version" == $version_pattern ]]; then
echo "Valid version"
fi
clean_string() {
local str="$1"
echo "${str//+([[:space:]])/ }"
}
file_pattern='*.@(test|spec)?.@(js|ts)'
Bash 5.3+ String Features
In-Shell Substitution for Strings
#!/usr/bin/env bash
set -euo pipefail
result=${ echo "${str^^}"; }
build_path() {
local parts=("$@")
REPLY=""
for part in "${parts[@]}"; do
${| REPLY+="${REPLY:+/}$part"; }
done
}
accumulate() {
local -n result="$1"
shift
for item in "$@"; do
${| result+="$item"; }
done
}
Performance Tips
Avoid Subshells for Simple Operations
#!/usr/bin/env bash
set -euo pipefail
str="hello world"
basename=$(basename "$path")
dirname=$(dirname "$path")
upper=$(echo "$str" | tr 'a-z' 'A-Z')
len=$(echo -n "$str" | wc -c)
basename="${path##*/}"
dirname="${path%/*}"
upper="${str^^}"
len="${#str}"
Batch String Operations
#!/usr/bin/env bash
set -euo pipefail
str="$input"
str="${str// / }"
str="${str#"${str%%[![:space:]]*}"}"
str="${str%"${str##*[![:space:]]}"}"
str="${str,,}"
normalize_string() {
local str="$1"
str="${str// / }"
str="${str#"${str%%[![:space:]]*}"}"
str="${str%"${str##*[![:space:]]}"}"
echo "${str,,}"
}
result=$(normalize_string "$input")
Resources
Master bash string manipulation to write efficient scripts without external dependencies.