Bash string manipulation is surprisingly powerful, allowing you to do more than just echo variables.

Let’s see it in action. Imagine you have a log file where each line starts with a timestamp like [2023-10-27 10:30:00]. You want to extract just the date part.

log_line="[2023-10-27 10:30:00] INFO: User logged in."
date_part="${log_line#[*]}" # Remove leading '['
date_part="${date_part%% *}" # Remove everything from the first space onwards
echo "$date_part"

This outputs 2023-10-27.

The core problem Bash string manipulation solves is processing text data directly within the shell, avoiding the need for external tools like awk or sed for many common tasks. It operates on shell parameter expansion, which is built into Bash itself.

Here are some practical tricks:

  1. Substring Extraction: Get a part of a string.

    my_string="Hello, World!"
    substring="${my_string:7:5}" # Start at index 7, take 5 characters
    echo "$substring" # Outputs "World"
    

    This is like slicing a string in Python. The first number is the starting index (0-based), and the second is the length.

  2. Remove Prefix: Strip characters from the beginning.

    filename="archive.tar.gz"
    base="${filename#archive.}" # Remove "archive."
    echo "$base" # Outputs "tar.gz"
    

    The single # removes the shortest matching prefix. Use ## for the longest.

  3. Remove Suffix: Strip characters from the end.

    filepath="/var/log/syslog.log"
    dir_part="${filepath%.log}" # Remove ".log"
    echo "$dir_part" # Outputs "/var/log/syslog"
    

    The single % removes the shortest matching suffix. Use %% for the longest.

  4. Replace First Occurrence: Change a specific part of a string.

    sentence="The quick brown fox jumps over the lazy dog."
    new_sentence="${sentence/fox/cat}" # Replace first "fox" with "cat"
    echo "$new_sentence" # Outputs "The quick brown cat jumps over the lazy dog."
    

    This is incredibly useful for simple substitutions.

  5. Replace All Occurrences: Substitute every instance.

    data="apple,banana,apple,orange"
    comma_separated="${data//,/-}" # Replace all commas with hyphens
    echo "$comma_separated" # Outputs "apple-banana-apple-orange"
    

    The double slash // is the key here for global replacement.

  6. Convert to Lowercase: Standardize case.

    UPPER="HELLO WORLD"
    lower="${UPPER,,}" # Convert entire string to lowercase
    echo "$lower" # Outputs "hello world"
    

    This is a Bash 4.0+ feature.

  7. Convert to Uppercase:

    lower="hello world"
    upper="${lower,,}" # Convert entire string to uppercase
    echo "$upper" # Outputs "HELLO WORLD"
    

    Note: ,, is lowercase, ^^ is uppercase.

  8. Get Length: Count characters.

    message="important"
    length="${#message}"
    echo "$length" # Outputs "9"
    

    Simple and direct.

  9. Trim Whitespace (Leading): Remove spaces from the start.

    indented="   Hello"
    trimmed="${indented#"${indented%%[![:space:]]*}"}" # Remove leading whitespace
    echo "|$trimmed|" # Outputs "|Hello|"
    

    This is a bit more complex, involving removing characters up to the first non-space character.

  10. Trim Whitespace (Trailing): Remove spaces from the end.

    indented="Hello   "
    trimmed="${indented%"${indented##*[![:space:]]}"}" # Remove trailing whitespace
    echo "|$trimmed|" # Outputs "|Hello|"
    

    Similar logic to leading trim, but working from the end.

  11. Trim Whitespace (Both): Combine the two.

    indented="   Hello   "
    trimmed="${indented#"${indented%%[![:space:]]*}"}"
    trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
    echo "|$trimmed|" # Outputs "|Hello|"
    

    You apply the leading trim, then the trailing trim to the result.

  12. Pad String (Left): Add characters to the beginning.

    num="123"
    padded="${numpadStart(3,0)}" # Pad with '0' to a width of 3
    echo "$padded" # Outputs "0123" (If num were '23', this would be '023')
    

    This is a Bash 4.0+ feature. The first argument is the desired width, the second is the character to pad with.

  13. Pad String (Right): Add characters to the end.

    num="123"
    padded="${numpadEnd(5,X)}" # Pad with 'X' to a width of 5
    echo "$padded" # Outputs "123XX"
    

    Similar to padLeft, but pads on the right.

  14. Extract Filename from Path: Get just the file part.

    filepath="/home/user/documents/report.txt"
    filename="${filepath##*/}" # Remove everything up to the last '/'
    echo "$filename" # Outputs "report.txt"
    

    This is a common use of the longest prefix removal.

  15. Extract Directory from Path: Get the path without the filename.

    filepath="/home/user/documents/report.txt"
    dir_path="${filepath%/*}" # Remove everything from the last '/' onwards
    echo "$dir_path" # Outputs "/home/user/documents"
    

    This uses the shortest suffix removal.

A subtle but powerful aspect is how these expansions are performed. They happen before the shell attempts to execute any commands or interpret other special characters, making them a safe and predictable way to manipulate text data you’ve read from files or user input.

When you need to construct a new filename based on an existing one, but ensure it always has a specific extension, you might combine suffix removal and replacement. For instance, to change .log to .bak for all files in a directory:

for file in *.log; do
  if [[ -f "$file" ]]; then
    new_name="${file%.log}.bak"
    echo "Renaming '$file' to '$new_name'"
    # mv "$file" "$new_name" # Uncomment to actually rename
  fi
done

This loop iterates through all .log files, removes the .log suffix, and appends .bak. It’s a concise way to manage file operations.

The next common challenge after mastering string manipulation is efficiently processing lines of text that require more complex pattern matching or field extraction, often leading to awk or sed.

Want structured learning?

Take the full Bash course →