Linux Basic — Substring Extraction

2 minute read

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 at
  • LENGTH — 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 via IFS=… read
  • Codepoint-correct multi-byte slicing — use awk or a real language
  • Regex-based slicing — use =~ or sed
# 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

Updated: