Shell by Example: String Manipulation POSIX + Bash

Shell provides powerful string manipulation through parameter expansion. These techniques work without calling external commands.

String length with ${#var}:

Edit
#!/bin/sh
str="Hello, World!"
echo "String: '$str'"
echo "Length: ${#str}"
Output:
String: 'Hello, World!'
Length: 13

Bash

Substring extraction with ${var:start:length}.

Edit
#!/bin/bash
text="Hello, World!"
echo "First 5 chars: ${text:0:5}"
echo "From position 7: ${text:7}"
echo "Last 6 chars: ${text: -6}" # Note the space before -
Output:
First 5 chars: Hello
From position 7: World!
Last 6 chars: World!

POSIX alternative for substrings uses expr or cut.

Edit
#!/bin/sh
echo "POSIX substring with expr:"
text="Hello, World!"
echo "  First 5: $(expr "$text" : '\(.....\)')"
echo "  Using cut: $(echo "$text" | cut -c1-5)"
Output:
POSIX substring with expr:
  First 5: Hello
  Using cut: Hello

Remove prefix with ${var#pattern} and ${var##pattern}.

Edit
#!/bin/sh
filepath="/home/user/documents/file.txt"
echo "Path: $filepath"

# Remove shortest match from beginning
echo "Remove leading /: ${filepath#/}"

# Remove longest match from beginning
echo "Remove path (##*/): ${filepath##*/}"
Output:
Path: /home/user/documents/file.txt
Remove leading /: home/user/documents/file.txt
Remove path (##*/): file.txt

Remove suffix with ${var%pattern} and ${var%%pattern}.

Edit
#!/bin/sh
# Remove shortest match from end
filepath="/home/user/documents/file.txt"
echo "Remove extension (%.*): ${filepath%.*}"

# Remove filename from end (get directory)
echo "Directory (%/*): ${filepath%/*}"
Output:
Remove extension (%.*): /home/user/documents/file
Directory (%/*): /home/user/documents

More practical examples using parameter expansion.

Edit
#!/bin/sh
filename="document.backup.tar.gz"
echo "Filename: $filename"
echo "Remove .gz: ${filename%.gz}"
echo "Remove all extensions: ${filename%%.*}"
echo "Get extension: ${filename##*.}"
Output:
Filename: document.backup.tar.gz
Remove .gz: document.backup.tar
Remove all extensions: document
Get extension: gz

Extract directory and filename using parameter expansion.

Edit
#!/bin/sh
path="/usr/local/bin/script.sh"
echo "Path: $path"
echo "Directory: ${path%/*}"
echo "Filename: ${path##*/}"
echo "Basename without ext: $(basename "${path%.*}")"
Output:
Path: /usr/local/bin/script.sh
Directory: /usr/local/bin
Filename: script.sh
Basename without ext: script

Bash

Substitution with ${var/pattern/replacement}.

Edit
#!/bin/bash
msg="hello world world"
echo "Replace first: ${msg/world/universe}"
echo "Replace all: ${msg//world/universe}"
echo "Replace at start: ${msg/#hello/hi}"
echo "Replace at end: ${msg/%world/planet}"
Output:
Replace first: hello universe world
Replace all: hello universe universe
Replace at start: hi world world
Replace at end: hello world planet

POSIX substitution alternatives:

Edit
#!/bin/sh
msg="hello world world"
echo "POSIX substitution with sed:"
echo "  Replace first: $(echo "$msg" | sed 's/world/universe/')"
echo "  Replace all: $(echo "$msg" | sed 's/world/universe/g')"
Output:
POSIX substitution with sed:
  Replace first: hello universe world
  Replace all: hello universe universe

Bash

Case conversion:

Edit
#!/bin/bash
word="Hello World"
echo "Uppercase: ${word^^}"
echo "Lowercase: ${word,,}"
echo "First char upper: ${word^}"
echo "First char lower: ${word,}"
Output:
Uppercase: HELLO WORLD
Lowercase: hello world
First char upper: Hello World
First char lower: hello World

POSIX case conversion:

Edit
#!/bin/sh
word="Hello World"
echo "POSIX case conversion with tr:"
echo "  Uppercase: $(echo "$word" | tr '[:lower:]' '[:upper:]')"
echo "  Lowercase: $(echo "$word" | tr '[:upper:]' '[:lower:]')"
Output:
POSIX case conversion with tr:
  Uppercase: HELLO WORLD
  Lowercase: hello world

Bash

String comparison can be done with = and != operators. These work in both sh and bash.

The < syntax only works in bash.

Edit
#!/bin/bash
s1="apple"
s2="banana"

if [ "$s1" = "$s2" ]; then
    echo "$s1 equals $s2"
else
    echo "$s1 does not equal $s2"
fi

# Lexicographic comparison
if [ "$s1" \< "$s2" ]; then
    echo "$s1 comes before $s2"
fi
Output:
apple does not equal banana
apple comes before banana

Check if string is empty or not:

Edit
#!/bin/sh
empty=""
nonempty="hello"

[ -z "$empty" ] && echo "empty is zero-length"
[ -n "$nonempty" ] && echo "nonempty has content"
Output:
empty is zero-length
nonempty has content

Check if string contains substring:

Edit
#!/bin/sh
haystack="The quick brown fox"
needle="quick"

case "$haystack" in
    *"$needle"*) echo "'$haystack' contains '$needle'" ;;
    *) echo "'$haystack' does not contain '$needle'" ;;
esac
Output:
'The quick brown fox' contains 'quick'

Bash

Using [[ ]] for pattern matching.

Edit
#!/bin/bash
haystack="The quick brown fox"
if [[ "$haystack" == *quick* ]]; then
    echo "Found quick"
else
    echo "Not found"
fi
Output:
Found quick

Bash

Using =~ for regex.

Edit
#!/bin/bash
haystack="The quick brown fox"
if [[ "$haystack" =~ ^The ]]; then
    echo "Starts with 'The'"
fi
Output:
Starts with 'The'

Split string into parts:

Edit
#!/bin/sh
csv="apple,banana,cherry"
echo "Splitting '$csv':"

# Using IFS
IFS=','
set -- $csv
echo "  Part 1: $1"
echo "  Part 2: $2"
echo "  Part 3: $3"
unset IFS
Output:
Splitting 'apple,banana,cherry':
  Part 1: apple
  Part 2: banana
  Part 3: cherry

Bash

Join array elements:

Edit
#!/bin/bash
fruits=("apple" "banana" "cherry")
IFS=','
echo "Joined: ${fruits[*]}"
unset IFS
Output:
Joined: apple,banana,cherry

Trim whitespace:

Edit
#!/bin/sh
padded="   hello world   "
echo "Padded: '$padded'"

# Using sed:
trimmed=$(echo "$padded" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
echo "Trimmed: '$trimmed'"
Output:
Padded: '   hello world   '
Trimmed: 'hello world'

Bash

Using parameter expansion:

Edit
#!/bin/bash
padded="   hello world   "
trimmed="${padded#"${padded%%[![:space:]]*}"}"
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
echo "Padded: '$padded'"
echo "Trimmed: '$trimmed'"
Output:
Padded: '   hello world   '
Trimmed: 'hello world'

Repeat a string:

Edit
#!/bin/sh
repeat_char() {
    char="$1"
    count="$2"
    result=""
    i=0
    while [ "$i" -lt "$count" ]; do
        result="$result$char"
        i=$((i + 1))
    done
    echo "$result"
}

echo "Repeat '-' 20 times: $(repeat_char '-' 20)"
Output:
Repeat '-' 20 times: --------------------

String formatting with printf.

Edit
#!/bin/sh
printf "Padded: |%10s|\n" "hello"
printf "Left:   |%-10s|\n" "hello"

echo "String manipulation examples complete"
Output:
Padded: |     hello|
Left:   |hello     |
String manipulation examples complete

« Arithmetic | Printf »