Bash String Manipulation: Substring, Replace, Split and More
March 17, 2026
Bash string manipulation uses parameter expansion — a built-in syntax that handles most string operations without any external commands. Understanding ${var#prefix}, ${var//old/new}, and ${var:pos:len} will let you slice, replace, and transform strings entirely within Bash, making your scripts faster and more portable. This guide covers every essential bash string operation with concrete examples.
1. String Length and Substring Extraction
str="Hello, World!"
# String length
echo "${#str}" # 13
# Substring: start at position 7, take 5 characters
echo "${str:7:5}" # World
# Start at position 7, take everything to the end
echo "${str:7}" # World!
# Negative position: start 6 chars from the end
echo "${str: -6}" # World! (note the space before -)
# Negative position + length
echo "${str: -6:5}" # World
# Extract filename without extension
filename="report.tar.gz"
echo "${filename%.*}" # report.tar (removes last .gz)
echo "${filename%%.*}" # report (removes from first dot)
For negative positions, a space before the minus sign is required: ${str: -6}. Without the space, Bash interprets it as the default-value syntax ${str:-default}.
2. Find and Replace
str="the cat sat on the mat"
# Replace FIRST occurrence
echo "${str/the/a}" # a cat sat on the mat
# Replace ALL occurrences (double slash)
echo "${str//the/a}" # a cat sat on a mat
# Delete a substring (replace with nothing)
echo "${str// /}" # thecatsatonthemat (remove all spaces)
# Replace using a glob pattern
path="/usr/local/bin/script.sh"
echo "${path/\/usr\/local/~}" # ~/bin/script.sh
# Case-insensitive replace requires a workaround (use sed for this)
echo "$str" | sed 's/The/a/gi' # handles case-insensitive
3. Case Conversion
Bash 4+ supports built-in case conversion operators:
name="hello world"
echo "${name^^}" # HELLO WORLD (all uppercase)
echo "${name,,}" # hello world (all lowercase)
echo "${name^}" # Hello world (capitalize first char)
echo "${name,}" # hello world (lowercase first char)
# Uppercase only certain chars (^[aeiou] = capitalize vowels)
echo "${name^^[aeiou]}" # hEllO wOrld
4. Stripping Prefixes and Suffixes
The # and % operators strip matched patterns from the start or end of a string. Use one symbol for shortest match, two for longest:
path="/home/user/documents/report.pdf"
# Strip prefix (from the LEFT)
echo "${path#*/}" # home/user/documents/report.pdf (strips up to first /)
echo "${path##*/}" # report.pdf (strips up to LAST / — greedy)
# Strip suffix (from the RIGHT)
echo "${path%.*}" # /home/user/documents/report (strips .pdf)
echo "${path%%.*}" # /home/user/documents/report (same here, but watch out with multiple dots)
# Practical: get just the filename
filename="${path##*/}"
echo "$filename" # report.pdf
# Practical: get directory path
dir="${path%/*}"
echo "$dir" # /home/user/documents
# Practical: get extension
ext="${filename##*.}"
echo "$ext" # pdf
5. Splitting Strings
csv="apple,banana,cherry"
# Split on comma using IFS
IFS=',' read -ra parts <<< "$csv"
echo "${parts[0]}" # apple
echo "${parts[1]}" # banana
echo "${parts[2]}" # cherry
# Loop over parts
for part in "${parts[@]}"; do
echo "Part: $part"
done
# Split a colon-delimited path
IFS=':' read -ra dirs <<< "$PATH"
for dir in "${dirs[@]}"; do
echo "$dir"
done
6. Trim Whitespace
str=" hello world "
# Trim with xargs (trims both ends)
trimmed=$(echo "$str" | xargs)
echo "$trimmed" # hello world
# Trim with parameter expansion (leading spaces)
str="${str#"${str%%[![:space:]]*}"}"
# Trim with sed
trimmed=$(echo "$str" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
echo "$trimmed" # hello world
For bulk find-and-replace across files, the bash sed command guide covers this in depth. For checking whether a string contains a substring, see the bash case statement guide which uses pattern matching.
Summary
Bash parameter expansion handles all common string operations without forking external processes. The key patterns: ${#var} for length, ${var:pos:len} for substrings, ${var//old/new} for global replace, ${var^^}/${var,,} for case, and ${var##*/}/${var%.*} for path/extension manipulation.