Skip to content
Lesson 4 of 14

Step 1 of 5 · Reading · ~3 min

Learn

Strings and Output

Strings

By the end of this lesson you can build strings, measure and slice them, and cut prefixes and suffixes off a value with the shell's own built-in tools. Everything in bash is already a string, so "string handling" means two tiers: parameter expansion (built into the shell, instant) and delegating to programs such as tr and cut. Reach for the built-in first.

Building strings

Concatenation is adjacency, with no operator:

first="Ada"
full="$first Lovelace"          # Ada Lovelace
greeting="Hello, ${first}!"     # braces mark where the name ends
greeting+=" Welcome."           # += appends text to a variable

Double quotes are the default for building text: they expand variables and keep spacing.

Measuring and slicing

Everything lives inside ${ }. Try these on s="hello world":

echo "${#s}"            # 11           length (the # goes INSIDE the braces)
echo "${s^^}"           # HELLO WORLD  uppercase; ${s^} only the first letter
echo "${s,,}"           # hello world  lowercase
echo "${s:0:5}"         # hello        offset 0, length 5
echo "${s:6}"           # world        from offset 6 to the end
echo "${s/world/bash}"  # hello bash   replace the first match
echo "${s//o/0}"        # hell0 w0rld  replace every match

Cutting prefixes and suffixes

Four forms remove text that matches a glob pattern (* means any text, ? any one character; not a regular expression) from one end of the value:

FormRemovesMatch
${v#pat}from the frontshortest
${v##pat}from the frontlongest
${v%pat}from the backshortest
${v%%pat}from the backlongest

On a keyboard # sits left of $ (the front) and % to its right (the back). One character means the shortest match, two mean the longest.

ver="v2.14.7"
echo "${ver#v}"        # 2.14.7   drop the leading v
echo "${ver%.*}"       # v2.14    shortest suffix that looks like .something
echo "${ver%%.*}"      # v2       longest suffix that looks like .something
echo "${ver##*.}"      # 7        longest prefix that looks like something.

The longest and shortest forms differ only when the pattern can match in more than one way, and that is exactly when you need to choose. Real data is full of it:

url="https://example.com/docs/guide.html"
rest="${url#*://}"      # example.com/docs/guide.html
host="${rest%%/*}"      # example.com

Programs for streams of text

Some jobs want the toolbox, which the pipes lesson covers properly: echo "$s" | tr 'a-z' 'A-Z' translates characters and echo "$s" | cut -d' ' -f1 takes the first space-separated field. Rule of thumb: expansions for one variable's worth of text, programs for streams of lines. Starting a process for every string operation in a loop is the classic slow script.

Common mistakes

  • ${s#} for the length. The length is ${#s}. ${s#} strips an empty prefix and prints the string unchanged, and $#s is the argument count followed by the letter s.
  • Cutting the wrong string. ${v%.*} removes from the end of the whole value. If the value contains a path, a dot in a directory name can be the one that matches; cut the file name off first, then work on that.
  • Treating patterns as regular expressions. ${v%.*} means "a dot, then anything". It is a glob.
  • Mixing up the doubled forms. %.* and %%.* give different answers on a.b.c: a.b versus a.

Your exercise

Split a file path into the directory it lives in, the file name without its extension, and the extension. The extension is the text after the last dot. Do the splitting with parameter expansion, and keep an eye on dots that belong to directory names.

Up nextprintf and HeredocsStrings and Output

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…