String Operations in Bash
Length, slicing, search-and-replace, case conversion, and trimming — Bash has a surprisingly powerful set of built-in string operations that don't need external tools.
Bash's ${parameter%…} and related expansions let you slice, strip, and transform strings without spawning subshells or calling sed/awk. These built-in operations are fast, readable, and available in every Bash 3+ environment.
String Length
str="Hello, World!"
echo "${#str}" # 13 — counts all characters including space and !Substrings — Slicing
str="Hello, World!"
# 0123456789...
echo "${str:0:5}" # Hello — from index 0, length 5
echo "${str:7}" # World! — from index 7 to end
echo "${str:7:5}" # World — from index 7, length 5
echo "${str: -6}" # orld! — last 6 chars (space before - is required)
echo "${str: -6:5}" # orld — last 6 chars, take 5Strip Prefix and Suffix
These are invaluable for extracting filenames, extensions, and path components without calling basename or dirname.
path="/home/alice/docs/report.tar.gz"
# Strip shortest prefix matching pattern
echo "${path#*/}" # home/alice/docs/report.tar.gz (removes leading /)
# Strip longest prefix matching pattern
echo "${path##*/}" # report.tar.gz (removes everything up to last /)
# Equivalent to: basename "$path"
# Strip shortest suffix matching pattern
echo "${path%.*}" # /home/alice/docs/report.tar (removes .gz)
# Strip longest suffix matching pattern
echo "${path%%.*}" # /home/alice/docs/report (removes .tar.gz)
# Practical: get filename without any extension
filename="${path##*/}" # report.tar.gz
noext="${filename%%.*}" # report# vs ## and % vs %%
# (one hash) → strip shortest match from the start
## (two hashes) → strip longest match from the start
% (one percent) → strip shortest match from the end
%% (two percents) → strip longest match from the end
Memory aid: # comments are at the start of lines; % is at the end like end-of-line percent.
Search and Replace
text="the cat sat on the mat"
# Replace first match
echo "${text/cat/dog}" # the dog sat on the mat
# Replace all matches
echo "${text//the/a}" # a cat sat on a mat
# Replace only at start (prefix)
echo "${text/#the/A}" # A cat sat on the mat
# Replace only at end (suffix)
echo "${text/%mat/rug}" # the cat sat on the rug
# Delete a pattern (replace with nothing)
echo "${text/the /}" # cat sat on the mat (first occurrence)
echo "${text//the /}" # cat sat on mat (all occurrences)Case Conversion (Bash 4+)
str="Hello World"
echo "${str,,}" # hello world — all lowercase
echo "${str^^}" # HELLO WORLD — all uppercase
echo "${str,}" # hELLO WORLD — lowercase first char only
echo "${str^}" # Already uppercase — uppercase first char (no-op if already upper)
# Practical: normalise user input
read -r -p "Enter choice (yes/no): " answer
answer="${answer,,}" # convert to lowercase before checking
if [[ "$answer" == "yes" ]]; then echo "Got yes"; fiGiven `file="/var/log/syslog"`, what does `${file##*/}` return?
Given a variable filepath="/home/user/projects/my-app.tar.gz", use parameter expansion (no calling basename or dirname) to extract:
- Just the filename:
my-app.tar.gz - The directory:
/home/user/projects - The filename without all extensions:
my-app - Just the first extension:
tar