Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 32

Bash String Manipulation

Recommended resources

Hello! Welcome to your next lesson in the "Advanced Bash Scripting" module.

In our previous lessons, we explored powerful external commands for text processing. We saw how grep finds lines, sed edits them, and awk processes them column by column. These tools are essential, but they come with a small performance cost: each time you call one in a script, the shell has to create a new process to run it.

Today, we shift our focus inward to the shell itself. Your learning outcome is to manipulate strings using Bash parameter expansion for slicing, substitution, and case modification. This is a fundamental skill for writing efficient and clean Bash scripts. By using these built-in operators, you can perform many common string operations without forking external commands, resulting in scripts that are faster and more self-contained.

1. Why and How: The Core of Parameter Expansion

At its simplest, you've been using parameter expansion every time you've written $MY_VAR. The more explicit form, ${MY_VAR}, is where the real power lies. Using braces {} not only prevents ambiguity (e.g., echo "${MY_VAR}_suffix" vs. $MY_VAR_suffix) but also unlocks a rich set of operators for string manipulation.

Before we dive into the techniques, it's crucial to understand why this is a preferred method in many cases.

Mastering Bash Parameter Expansion

The article 'Mastering Bash Parameter Expansion' by Nawaz Dhandala on OneUptime provides an excellent overview of the benefits.

Please read the introduction (the section just below the main title) and the 'Performance Benefits' section at the end of the article. This will frame why learning these operators is so valuable for a DevOps engineer.

As you've just read, the performance gain from avoiding subprocesses can be significant, especially inside loops that process many files or data points.


2. Slicing and Dicing: Substring Extraction

One of the most common string operations is extracting a piece of a string, or a "slice." Parameter expansion provides a clean syntax for this.

  • String Length: ${#variable} returns the number of characters in the variable.
  • Substring Extraction (Slicing): ${variable:offset:length} extracts a substring of length characters starting from offset (zero-indexed).

Let's look at a concrete example:

Bash Parameter Expansion for String Slicing
This terminal output shows a variable `var` being defined and then sliced using different offsets and lengths to extract specific words.

Notice how var:0:4 extracts "Bash". If you omit the length, it extracts everything from the offset to the end of the string.

Mastering Bash Parameter Expansion

For a detailed written explanation of these operators, let's return to the OneUptime article.

Read the sections 'String Length' and 'Substring Extraction'. Pay special attention to the note about the required space for negative offsets, as ${var:-1} has a different meaning.

Now, let's see these concepts demonstrated in a live terminal session.

Bash variable expansion

The video 'Bash variable expansion' from the nixcasts channel provides a practical walkthrough of these concepts.

Watch the segment from 03:47 to 06:15. This part demonstrates: The need for braces to disambiguate variables (e.g., ${test}s). Substring extraction (${test:6:4}). Getting the string length (${#test}).


3. Pattern-Based Manipulation

Where parameter expansion truly begins to replace tools like sed, cut, basename, and dirname is in its pattern-matching operators.

Prefix and Suffix Removal

This is incredibly useful for tasks like manipulating file paths.

  • Remove from Front (Prefix):
    • ${variable#pattern}: Deletes the shortest match of pattern from the beginning.
    • ${variable##pattern}: Deletes the longest (greedy) match of pattern from the beginning.
  • Remove from End (Suffix):
    • ${variable%pattern}: Deletes the shortest match of pattern from the end.
    • ${variable%%pattern}: Deletes the longest (greedy) match of pattern from the end.

The distinction between "shortest" and "longest" is key. For example, given file="report.tar.gz", ${file%.*} removes .gz (shortest match), while ${file%%.*} removes .tar.gz (longest match).

Basic Parameter Expansion in Bash - You Suck at Programming #026

The video 'Basic Parameter Expansion in Bash' from You Suck at Programming has a fantastic explanation of this, using the common DevOps task of getting a filename and directory path.

Watch from 00:28 to 02:35. The video clearly explains how ${filepath##*/} works like basename and ${filepath%/*} works like dirname by using greedy and non-greedy removal.

Search and Replace

You can also perform search-and-replace operations directly in the shell.

  • ${variable/pattern/string}: Replaces the first match of pattern with string.
  • ${variable//pattern/string}: Replaces all matches of pattern with string.

If you omit /string, the matched pattern is deleted.

Bash String Substitution with Parameter Expansion
A simple example of replacing the first occurrence of 'program' with 'code' in a string.

You can also "anchor" the replacement to the beginning or end of the string:

  • ${variable/#pattern/string}: Replaces only if pattern matches at the beginning.
  • ${variable/%pattern/string}: Replaces only if pattern matches at the end.

Mastering Bash Parameter Expansion

The OneUptime article provides excellent examples for both pattern removal and substitution.

Read the sections 'Pattern Removal' (including its subsections on removing from the front and end) and 'Search and Replace' (including 'Anchored Replacement').


4. Case Modification

Since Bash 4.0, you can also change the case of characters in a string, which is very useful for normalizing input.

  • ${variable^}: Uppercase the first character.
  • ${variable^^}: Uppercase all characters.
  • ${variable,}: Lowercase the first character.
  • ${variable,,}: Lowercase all characters.
env="PROD"
echo "Deploying to ${env,,} environment."



# Output: Deploying to prod environment.

This is a much cleaner and more efficient way to handle case changes than piping to tr or awk.

Mastering Bash Parameter Expansion

Let's review the syntax for this in our reference article.

Read the short 'Case Conversion' section for a summary of the operators.


5. Putting It All Together: A Practical Scenario

Let's combine these techniques to solve a common problem. Imagine you have a script that processes files, and you need to normalize the filenames for logging.

Input: filepath="/data/RAW_SALES_2026-Q1.CSV"
Desired Output: raw_sales_2026-q1

How would you achieve this? Think about it for a moment. You'll need to chain a few operations.

...

Here's the sequence:

  1. Extract the filename from the path. We need to remove the longest match of */ from the beginning.
    filename="${filepath##*/}" 
    
    
    
    # filename is now "RAW_SALES_2026-Q1.CSV"
    
  2. Remove the file extension. We need to remove the shortest match of .* from the end.
    base="${filename%.*}"
    
    
    
    # base is now "RAW_SALES_2026-Q1"
    
  3. Convert the result to lowercase.
    normalized="${base,,}"
    
    
    
    # normalized is now "raw_sales_2026-q1"
    

Putting it all on one line can be less readable, but it is possible by nesting temporary variables:

filepath="/data/RAW_SALES_2026-Q1.CSV"
tmp="${filepath##*/}"
echo "${tmp%.*}" | tr '[:upper:]' '[:lower:]' # Using tr for older bash



# or with bash 4+
normalized_base="${${filepath##*/}%.*}" # This syntax can be tricky
normalized="${normalized_base,,}"
echo "$normalized"

The step-by-step approach is generally clearer and easier to debug.


Conclusion

Today you've added a powerful set of native Bash tools to your scripting arsenal. By mastering parameter expansion, you can write more efficient, readable, and self-reliant scripts that don't depend on external commands for basic string manipulation.

Key Takeaways:

  • Always use "${variable}" with braces for clarity and to enable operators.
  • Slicing (${var:offset:length}) allows you to extract substrings without cut.
  • Pattern Removal (#, ##, %, %%) is a fast replacement for basename, dirname, and simple sed commands for stripping prefixes and suffixes.
  • Search and Replace (/, //) provides sed-like substitution capabilities directly within the shell.
  • Case Modification (^, ^^, ,, ,,) is the modern, efficient way to normalize string case, replacing tr.

Preview of the Next Lesson:
We've mastered operating on single string values. But what happens when you need to manage a list of items, like a set of server hostnames or a list of files to process? In the next lesson, we will explore how to use indexed and associative arrays to manage collections of data in Bash scripts, moving from single variables to structured data management.