Addressing
So far we have looked at applying the substitution to all lines in a text file. However, there may be cases where we wish to limit the lines to even consider for editing.

sed provides two types of addressing for line recognition.

The first is the line's literal address. You simple specify the line or line range you wish to apply the edit command to.

'1 s/\<U\.S\.\>United States/'

Change the 1st occurrence of U.S. to United States but only on the 1st line of the text file.

Because sed is a line editor and does not know what the last line is until it is read, the $ may be used represent the last line.

'$ s/\<The End\>/&, But not really/'

On encountering the last line of the file, if it contains the string "The End", replace it with itself and ", But not really"

Addresses may be combined to form a range.

'10,20 s/\<up\>/down/g'

Ranges are inclusive, so this would change will include lines 10 and 20 if the substitute is valid.

The second address form is a specified regular expression. This is basically a grep preceding the command.

To use a regular expression, place the regular expression at the beginning of the command between two forward slashes.

'/^Totals: / s/\<periodically\>/weekly/g'

For each any line starting with the word Totals: and a space, substitute any occurrences of the word periodically with weekly.

Like line number addresses, you may specify a range using regular expressions.

#Insert 2 spaces at the beginning of each line of a block of preformatted
# text.
sed '/<pre>/,/<\/pre>/ s/^/ /' /home/hopper/berezin/ph/330.html | less

<pre> and </pre> indicate the start and end of a pre-formatted text block in a web page. This sed command will start applying the edit to the lines in the web-page text on finding the start html marker <pre> and will continue applying the edit to all lines read from the file until it encounters the end marker <\pre>. sed will not apply the edit to any lines encountered after the end marker unless it encounters another <pre>.

It is possible to mix lines and patterns in range specification. The following uses the p (print) command and -n option to make sed act like a cross between head and grep.

#This shows all lines in a file from the 1st to the line containing <body>.
sed -n '1,/<body>/ p' /home/hopper/berezin/ph/index.html | less

You may nest commands for a particular address/range with the braces.

For the following example, we look for the first part of the body, <body<, markup because it may have additional formatting information before the closing >s. We are doing something similar with <table \* l1

sed '/<body/,/<\/body>/ {
  # indent all lines in body by 2 spaces
  s/^/  /

  /<table \/\* l1/,/<\/table \/\* l1/ {
    # substitute capitalized paragraph markers with lower case
    s/<P>/<p>/g

    # make sure all table data markups are in lower case.
    s/<[Tt][Dd]/<td/g

    # Indent all lines in the table
    s/^/  /

  }
}' /home/hopper/berezin/ph/index.html | less

Important : the braces after the address filter must be on the same line as the filter and should have no characters after, not even spaces.

Other issues raised in the example above. If the ranges you are searching for are themselves embedded, such as a table inside a table, the range toggling conditions may be mis-interpreted. In this case, the author (me) was kind enough to add documentation, the /* l1, which can be used to clarify which table markups are the target.

Also, note that several backslashes were needed to suppress the meta-meaning of / and *

The ! (not or invert) negates the address condition. So :

'1 ! s/^/  /'

indents with two spaces all lines except the 1st line of the file.

In the above range examples, the edits were applied to all lines including the line that starts the range and the line that ends the range. Here is how to skip the start and end line while acting on everything in between.

sed '/<table \/\* l1/,/<\/table \/\* l1/ {
  # Indent all lines except the lines with the <table> and </table> markups.
  # if not beginning table marker
  #    if not ending table marker
  #      indent lines
  #    endif
  # endif
  /<table \/\* l1/! {
    /<\/table \/\* l1/! {
      s/^/  /
    }
  }
}' /home/hopper/berezin/ph/index.html | less

When used with a range, the not excludes the lines within the range. The following skips from the beginning of the file to the body markup.

sed '/<table \/\* l1/,/<\/table \/\* l1/ ! {
 s/^/  /
}' /home/hopper/berezin/ph/index.html | less

Remember the NOT applies to the address not the command.