Linux Basic — Substring Extraction
Substring Extraction
Slicing a piece out of a string in Bash is a one-line parameter expansion. No cut, no awk, no fork — just ${var:offset:length}.
The Syntax
${var:OFFSET}
${var:OFFSET:LENGTH}
OFFSET— 0-based byte position to start atLENGTH— number of bytes to take (optional; defaults to “rest of string”)
s="Hello, World!"
echo "${s:0:5}" # Hello (start at 0, take 5)
echo "${s:7:5}" # World (start at 7, take 5)
echo "${s:7}" # World! (start at 7, take rest)
echo "${s:-1}" # Hello, World! ← oops! see below
Trap: ${s:-1} looks like “from offset -1”, but it’s actually the default operator from the previous page (${var:-DEFAULT}). You need a space (or parentheses) to disambiguate.
Negative Offsets — From the End
s="Hello, World!"
echo "${s: -1}" # ! (last char — space is REQUIRED)
echo "${s: -6}" # World! (last six chars)
echo "${s: -6:5}" # World (last six, take five)
Alternative: parentheses for clarity:
echo "${s:(-6):5}" # World
Use the negative form whenever you want “the last N characters” — no need to compute len - N yourself.
Length Pattern (Compute Once)
When you need both a slice and its length:
s="Hello, World!"
len=${#s} # 13
echo "${s:0:$((len/2))}" # Hello,
echo "${s:$((len/2))}" # World!
Slicing Positional Parameters
$@ and $* can be sliced just like a variable:
set -- a b c d e
echo "${@:2}" # b c d e (from index 2 onward)
echo "${@:2:3}" # b c d (3 elements starting at 2)
echo "${@:(-1)}" # e (last)
Useful for “shift everything after the first arg”:
script_name=$1
remaining=("${@:2}")
mycmd "${remaining[@]}"
Slicing Arrays
Same syntax — just give the array name and @ or *:
arr=(red green blue yellow purple)
echo "${arr[@]:1:2}" # green blue (2 elements starting at index 1)
echo "${arr[@]: -2}" # yellow purple (last two — space required)
echo "${arr[@]:0:3}" # red green blue
To assign the slice back to an array (a deep copy):
first_three=( "${arr[@]:0:3}" )
echo "${#first_three[@]}" # 3
When Bash Slicing Is Too Limited
Bash slicing is byte-indexed. If you need:
- Field extraction by delimiter — use
cut,awk, or split viaIFS=… read - Codepoint-correct multi-byte slicing — use
awkor a real language - Regex-based slicing — use
=~orsed
# Field 2 from a CSV (1-based)
echo "alice,bob,carol,dan" | cut -d, -f2
# bob
# Same with Bash native split (no fork)
IFS=, read -ra parts <<< "alice,bob,carol,dan"
echo "${parts[1]}"
# bob
IFS=… read -ra is covered in Case Conversion & Splitting.
Practical Recipes
File Path Components
path="/var/log/nginx/access.log.2026-06-17"
# Last 10 chars
echo "${path: -10}" # 2026-06-17
# Directory portion (this works for fixed-shape paths only)
echo "${path:0:9}" # /var/log/
# But for real path work, use the pattern removal operators in the next page —
# they are much more flexible.
URL Scheme
url="https://example.com/path"
echo "${url:0:5}" # https
For “everything up to the colon” the pattern operators are nicer:
echo "${url%%:*}" # https (next page)
Truncate for Display
title="A very long blog post title that won't fit"
echo "${title:0:25}…"
# A very long blog post tit…
Common Mistakes
| ❌ Wrong | ✅ Right | Issue |
|---|---|---|
${s:-1} for “last char” |
${s: -1} (space) or ${s:(-1)} |
:- is the default operator |
${s:0,5} (comma) |
${s:0:5} (colon) |
Bash uses colon between offset and length |
${s[1:5]} (Python-style) |
${s:1:4} |
length, not end index |
| Slicing by character on UTF-8 | use awk / Python |
Bash slices bytes |
${@:1:-1} (“all but last”) |
${@:1:$#-1} |
bash needs explicit math |
Next → Pattern Removal