Skip to content

Sed Branches

SED scripts can make decisions and jump to different parts of the script using branch commands. This allows for more complex text processing logic, similar to goto statements in other programming languages. The primary commands for branching are b, t, and T.

The b command unconditionally jumps to a label. If no label is provided, it branches to the end of the script, effectively skipping any subsequent commands for the current cycle.

The t Command (Conditional Branch on Success)

Section titled “The t Command (Conditional Branch on Success)”

The t label command jumps to the specified label only if a s/// (substitute) command has made a successful substitution since the last input line was read or the last t or T command was executed. If no label is provided, it branches to the end of the script. This is useful for performing actions only when a change has occurred.

Let’s see a simple example. Suppose we want to mark lines containing “Martin” by prepending “AUTHOR_UPDATE: ” but only if we successfully replace “Martin” with “M.A.R.T.I.N”. We’ll use our standard books.txt file:

File: `books.txt`
1) A Storm of Swords, George R. R. Martin, 1216
2) The Two Towers, J. R. R. Tolkien, 352
3) The Alchemist, Paulo Coelho, 197
4) The Fellowship of the Ring, J. R. R. Tolkien, 432
5) The Pilgrimage, Paulo Coelho, 288
6) A Game of Thrones, George R. R. Martin, 864
[jerry]$ sed '
/Martin/ {
s/Martin/M.A.R.T.I.N/
t was_substituted
}
b # Skip if no substitution or not Martin
:was_substituted
s/^/AUTHOR_UPDATE: /
' books.txt

Executing this command yields:

AUTHOR_UPDATE: 1) A Storm of Swords, George R. R. M.A.R.T.I.N, 1216
2) The Two Towers, J. R. R. Tolkien, 352
3) The Alchemist, Paulo Coelho, 197
4) The Fellowship of the Ring, J. R. R. Tolkien, 432
5) The Pilgrimage, Paulo Coelho, 288
AUTHOR_UPDATE: 6) A Game of Thrones, George R. R. M.A.R.T.I.N, 864

Here, if s/Martin/M.A.R.T.I.N/ is successful, t was_substituted jumps to the label :was_substituted, and s/^/AUTHOR_UPDATE: / is executed. Otherwise, the t command does nothing, and b (unconditional branch to end of script for this line) would be hit, or if not present, execution would continue to the label command (which does nothing by itself) and then the prefix substitution, which is not what we’d want for non-Martin lines. The current logic ensures only substituted lines get the prefix.

The t command is powerful for creating loops. Let’s consider an example where we want to prepend up to four hyphens to lines containing “Paulo”. This example involves joining lines first, so we’ll use a slightly different input file, books_split.txt, where titles and authors are on separate lines:

File: `books_split.txt`
A Storm of Swords
George R. R. Martin
The Two Towers
J. R. R. Tolkien
The Alchemist
Paulo Coelho
The Fellowship of the Ring
J. R. R. Tolkien
The Pilgrimage
Paulo Coelho
A Game of Thrones
George R. R. Martin

The command uses h; n; H; x to join pairs of lines, then s/\n/, / to replace the embedded newline with a comma and space. After that, it enters a loop for lines containing “Paulo”.

[jerry]$ sed -n '
h;N;H;x
s/\n/, /
:Loop
/Paulo/s/^/-/;
/----/!t Loop
p
' books_split.txt

When the above code is executed, it produces the following result:

A Storm of Swords, George R. R. Martin
The Two Towers, J. R. R. Tolkien
----The Alchemist, Paulo Coelho
The Fellowship of the Ring, J. R. R. Tolkien
----The Pilgrimage, Paulo Coelho
A Game of Thrones, George R. R. Martin

Let’s break down the looping part:

  1. h;N;H;x: These commands work together. h copies the first line (e.g., title) to hold space. N appends the next line (e.g., author) to pattern space (this is slightly different from the original h;n;H;x; but h;N;H;x is also a common idiom for joining two lines, effectively N followed by G; s/\n// when hold space has the first line). A simpler N ; s/\n/, / would often suffice for joining two lines if printing immediately. The original sequence h;n;H;x means: Line 1 to hold. Line 2 to pattern. Append Line 2 to hold (Hold: L1\nL2). Swap pattern and hold (Pattern: L1\nL2).
  2. s/\n/, /: Replaces the newline (now in pattern space) with a comma and space.
  3. :Loop: Defines a label named Loop.
  4. /Paulo/s/^/-/: Checks if the (joined) line contains "Paulo". If yes, it prepends a hyphen (-) to the beginning of the line. This s///command is the one that determines ift` will branch.
  5. /----/!t Loop: This is the conditional branch. !t Loop means: if the pattern space does not match /----/ (i.e., does not start with four hyphens), and the preceding s/// command (/Paulo/s/^/-/) was successful, then jump back to the Loop label. If the s/// was not successful (e.g. “Paulo” not in line), t will not branch.
  6. p: Prints the (potentially modified) line from the pattern space. -n suppresses default output, so p is needed here. This process repeats, adding one hyphen at a time to lines with “Paulo”, until four hyphens are prepended or “Paulo” is not found.

To improve readability, SED commands can be written on separate lines in a script file or using -e for each command. For a one-liner, you can use semicolons (though newlines are often clearer as shown above):

[jerry]$ sed -n 'h;N;H;x; s/\n/, /; :Loop; /Paulo/s/^/-/; /----/!t Loop; p' books_split.txt

The output is the same:

A Storm of Swords, George R. R. Martin
The Two Towers, J. R. R. Tolkien
----The Alchemist, Paulo Coelho
The Fellowship of the Ring, J. R. R. Tolkien
----The Pilgrimage, Paulo Coelho
A Game of Thrones, George R. R. Martin

The T Command (Conditional Branch on Failure - GNU Extension)

Section titled “The T Command (Conditional Branch on Failure - GNU Extension)”

The T label command is a GNU extension. It’s the opposite of t: it jumps to label only if the last s/// command failed to make a substitution. If no label is provided, it branches to the end of the script. This is useful for actions when a specific pattern wasn’t found and replaced.

Understanding branching unlocks advanced scripting capabilities in SED, allowing for sophisticated text transformations based on conditions within your data.