String Operations
| Since: | ${#var} / ${var:offset:length} / ${var/old/new} | Bash(bash拡張) |
|---|
In shell scripts, you can get the length of a string, extract a substring, replace text, and strip patterns using only the shell's built-in parameter expansion — no external commands like sed or awk are needed. Keeping processing within the shell reduces subprocess creation and keeps scripts simple and fast.
String length (${#var})
${#var} returns the number of characters (not bytes) in the value of variable var as an integer. When the locale is set to UTF-8, multibyte characters are each counted as one character.
| Syntax | Description |
|---|---|
${#var} | Returns the length of variable var. Returns 0 if the variable is unset or empty. |
${#array[@]} | Returns the number of elements in array array. |
${#array[i]} | Returns the length of the element at index i in the array. |
Substring extraction (${var:offset:length})
${var:offset:length} extracts length characters starting at position offset (0-based) and returns the result. If length is omitted, it returns everything from offset to the end of the string. A negative offset counts from the end, but because it can be confused with the :- default-value expansion, it is recommended to write ${var: -3} (with a space after the colon) or ${var:$(( ${#var} - 3 ))}.
| Syntax | Description |
|---|---|
${var:offset} | Returns from position offset (0-based) to the end of the string. |
${var:offset:length} | Returns length characters starting at position offset. |
${var: -n} | Returns the last n characters (a space after the colon is required). |
${var:offset:length} (array) | Returns length elements of the array starting at index offset. |
Substitution (${var/old/new})
${var/old/new} replaces the first match of pattern old (glob patterns are supported) with new and returns the result. Using // replaces all matches. Omitting new (i.e., ${var/old/}) deletes the matched portion.
| Syntax | Description |
|---|---|
${var/old/new} | Replaces the first match of old with new. |
${var//old/new} | Replaces all matches of old with new. |
${var/old/} | Deletes the first match of old (replacement with an empty string). |
${var//old/} | Deletes all matches of old. |
${var/#old/new} | Replaces only when the string starts with old. |
${var/%old/new} | Replaces only when the string ends with old. |
Pattern stripping (${var#pattern} / ${var%pattern})
Pattern stripping returns the value of a variable with a matching prefix or suffix removed. It is commonly used to strip the directory part or file extension from a path. # / % use the shortest match, while ## / %% use the longest match.
| Syntax | Description |
|---|---|
${var#pattern} | Removes the shortest match of pattern from the beginning. |
${var##pattern} | Removes the longest match of pattern from the beginning. |
${var%pattern} | Removes the shortest match of pattern from the end. |
${var%%pattern} | Removes the longest match of pattern from the end. |
The following table shows parameter expansion equivalents for basename and dirname as path manipulation examples.
| Purpose | Syntax (parameter expansion) | Equivalent external command |
|---|---|---|
| Get filename (strip directory) | ${path##*/} | basename "$path" |
| Get directory name (strip filename) | ${path%/*} | dirname "$path" |
| Strip extension | ${file%.*} | — |
| Get extension only | ${file##*.} | — |
Case conversion (bash 4.0 and later)
Bash 4.0 and later support case conversion using ^^ and ,,. These are not available in the default bash on macOS (version 3.2), so use the tr command as an alternative when running on macOS.
| Syntax | Description |
|---|---|
${var^^} | Converts all characters to uppercase (bash 4.0 and later). |
${var,,} | Converts all characters to lowercase (bash 4.0 and later). |
${var^} | Converts only the first character to uppercase (bash 4.0 and later). |
${var,} | Converts only the first character to lowercase (bash 4.0 and later). |
echo "$var" | tr '[:lower:]' '[:upper:]' | Uppercase conversion using the tr command. Works with bash 3.x and sh as well. |
Sample code
yakuza_string.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates string operations (length, extraction,
# substitution, stripping) using Yakuza series characters
# -----------------------------------------------
# -----------------------------------------------
# 1. String length (${#var})
# -----------------------------------------------
name1="桐生一馬"
name2="真島吾朗"
name3="冴島大河"
name4="秋山駿"
name5="堂島大吾"
echo "=== 1. String length ==="
# Use ${#var} to get the character count of a variable
echo "${name1} length: ${#name1}" # → 4
echo "${name2} length: ${#name2}" # → 4
echo "${name3} length: ${#name3}" # → 4
echo "${name4} length: ${#name4}" # → 3
echo "${name5} length: ${#name5}" # → 4
echo ""
# -----------------------------------------------
# 2. Substring extraction (${var:offset:length})
# -----------------------------------------------
echo "=== 2. Substring extraction ==="
title="東城会四代目会長・桐生一馬"
# offset=0, length=5 → extract 5 characters from the start
echo "Title: ${title:0:5}" # → 東城会四代
# offset=6 → extract from position 6 to the end
echo "Name part: ${title:6}" # → 桐生一馬
# Extract the last 2 characters (a space after the colon is required)
echo "Last 2 chars: ${title: -2}" # → 一馬
echo ""
# -----------------------------------------------
# 3. Substitution (${var/old/new})
# -----------------------------------------------
echo "=== 3. Substitution ==="
org="東城会"
# Replace only the first match
sentence="桐生一馬は東城会の幹部だ。東城会は神室町を拠点とする。"
echo "Single replace: ${sentence/東城会/近江連合}"
# → 桐生一馬は近江連合の幹部だ。東城会は神室町を拠点とする。
# Use // to replace all occurrences
echo "Replace all: ${sentence//東城会/近江連合}"
# → 桐生一馬は近江連合の幹部だ。近江連合は神室町を拠点とする。
# Omitting new deletes the matched part
echo "Delete: ${sentence/東城会/}"
# → 桐生一馬はの幹部だ。東城会は神室町を拠点とする。
# Anchor-at-start substitution (#) to prepend a title
name="桐生一馬"
echo "Prefix match: ${name/#桐生/東城会四代目・桐生}"
# → 東城会四代目・桐生一馬
echo ""
# -----------------------------------------------
# 4. Pattern stripping (${var#pattern} / ${var%pattern})
# -----------------------------------------------
echo "=== 4. Pattern stripping ==="
filepath="/home/yakuza/save/kiryu_chapter5.dat"
# Remove longest prefix up to the last / → get filename only
filename="${filepath##*/}"
echo "Filename: ${filename}" # → kiryu_chapter5.dat
# Remove shortest suffix from last . → strip extension
basename_noext="${filename%.*}"
echo "No extension: ${basename_noext}" # → kiryu_chapter5
# Remove longest prefix up to last . → get extension only
ext="${filename##*.}"
echo "Extension: ${ext}" # → dat
# Remove shortest suffix from last / → get directory part
dirpath="${filepath%/*}"
echo "Directory: ${dirpath}" # → /home/yakuza/save
echo ""
# -----------------------------------------------
# 5. Combined example — format a character record
# -----------------------------------------------
echo "=== 5. Combined example ==="
# Assume a record is stored in "title_name" format
record="四代目会長_桐生一馬"
# Extract the part before _ (title)
role="${record%%_*}"
echo "Title: ${role}" # → 四代目会長
# Extract the part after _ (name)
fullname="${record##*_}"
echo "Name: ${fullname}" # → 桐生一馬
# Display the character count of the name
echo "Name length: ${#fullname}" # → 4
# Extract the family name (first 2 characters)
family="${fullname:0:2}"
echo "Family name: ${family}" # → 桐生
$ chmod +x yakuza_string.sh $ ./yakuza_string.sh === 1. String length === 桐生一馬 length: 4 真島吾朗 length: 4 冴島大河 length: 4 秋山駿 length: 3 堂島大吾 length: 4 === 2. Substring extraction === Title: 東城会四代 Name part: 桐生一馬 Last 2 chars: 一馬 === 3. Substitution === Single replace: 桐生一馬は近江連合の幹部だ。東城会は神室町を拠点とする。 Replace all: 桐生一馬は近江連合の幹部だ。近江連合は神室町を拠点とする。 Delete: 桐生一馬はの幹部だ。東城会は神室町を拠点とする。 Prefix match: 東城会四代目・桐生一馬 === 4. Pattern stripping === Filename: kiryu_chapter5.dat No extension: kiryu_chapter5 Extension: dat Directory: /home/yakuza/save === 5. Combined example === Title: 四代目会長 Name: 桐生一馬 Name length: 4 Family name: 桐生
Pattern expansion symbols summary
| Symbol | Target | Match | Example |
|---|---|---|---|
# | Prefix | Shortest | ${var#*/} → removes up to the first /. |
## | Prefix | Longest | ${var##*/} → removes up to the last / (gets filename). |
% | Suffix | Shortest | ${var%.*} → removes from the last . onward (strips extension). |
%% | Suffix | Longest | ${var%%.*} → removes everything from the first . onward. |
/ | Whole string | First match | ${var/a/b} → replaces the first a with b. |
// | Whole string | All matches | ${var//a/b} → replaces all occurrences of a with b. |
/# | Prefix | Anchored at start | ${var/#a/b} → replaces only when the string starts with a. |
/% | Suffix | Anchored at end | ${var/%a/b} → replaces only when the string ends with a. |
Summary
Shell script string operations can be completed entirely within the shell by combining parameter expansion symbols, without relying on external commands. Use ${#var} to get the string length, ${var:offset:length} to extract a substring from any position, and ${var/old/new} with ${var//old/new} for single and global substitution. To strip patterns from the beginning, use ${var#pattern} (shortest) or ${var##pattern} (longest); to strip from the end, use ${var%pattern} (shortest) or ${var%%pattern} (longest). For path manipulation in particular, remembering three forms — ${path##*/} (get filename), ${path%/*} (get directory), and ${file%.*} (strip extension) — means you can avoid calling basename or dirname. For the basics of declaring and referencing variables, see also Variable assignment, referencing, and scope.
If you find any errors or copyright issues, please contact us.