1

In shell script, how can I add lines after a certain pattern? Say I have the following file and I want to add two lines after block 1 and blk 2.

abc
def

[block 1]
    apples = 3
    grapes = 4

[blk 2]
    banana = 2
    apples = 3

[block 1] and [blk 2] will be present in the file.

The output I am expecting is below.

abc
def

[block 1]
    oranges = 5
    pears = 2
    apples = 3
    grapes = 4

[blk 2]
    oranges = 5
    pears = 2
    banana = 2
    apples = 3

I thought of doing this with sed. I tried the below command but it does not work on my Mac. I checked these posts but I couldn't find what I am doing wrong.

$sed -i '/\[block 1\]/a\n\toranges = 3\n\tpears = 2' sample2.txt
sed: 1: "sample2.txt": unterminated substitute pattern

How can I fix this? Thanks for your help!

[Edit] I tried the below and these didn't work on my Mac.

$sed -E '/\[block 1\]|\[blk 2\]/r\\n\\toranges = 3\\n\\tpears = 2' sample2.txt
abc
def

    [block 1]
        apples = 3
        grapes = 4

    [blk 2]
        banana = 2
        apples = 3

$sed -E '/\[block 1\]|\[blk 2\]/r\n\toranges = 3\n\tpears = 2' sample2.txt
abc
def

    [block 1]
        apples = 3
        grapes = 4

    [blk 2]
        banana = 2
        apples = 3

Awk attempt:

$awk -v RS= '/\[block 1\]/{$0 = $0 ORS "\toranges = 3" ORS "\tpears = 2" ORS}
      /\[blk 2\]/{$0 = $0 ORS "\toranges = 5" ORS "\tpears = 2" ORS} 1' sample2.txt
abc
def

    [block 1]
        apples = 3
        grapes = 4

    [blk 2]
        banana = 2
        apples = 3
    oranges = 3
    pears = 2

    oranges = 5
    pears = 2
SyncMaster
  • 9,754
  • 34
  • 94
  • 137
  • try without inplace or give a backup name. – karakfa Nov 02 '17 at 21:22
  • I still get an error. sed '/\[block 1\]/a\n\toranges = 3\n\tpears = 2' sample2.txt sed: 1: "/\[block 1\]/a\n\torang ...": extra characters after \ at the end of a command – SyncMaster Nov 02 '17 at 21:25

3 Answers3

4

Note that the text provided to the a command has to be on a separate line:

sed '/\[block 1\]/ {a\
\toranges = 3\n\tpears = 2
}' file

and all embedded newlines have to be escaped. Another way to write it (probably more readable):

sed '/\[block 1\]/ {a\
    oranges = 3\
    pears = 2
}' file

Also, consider the r command as an alternative to the a command when larger amounts of text have to be inserted (e.g. more than one line). It will read data from a text file provided:

sed '/\[block 1\]/r /path/to/text' file

To handle multiple sections with one sed program, you can use the alternation operator (available in ERE, notice the -E flag):

sed -E '/\[block 1\]|\[blk 2\]/r /path/to/text' file
randomir
  • 17,989
  • 1
  • 40
  • 55
  • Or you can use `sed '/\[bl\(k\|ock\) [12]\]/ {a\ ...` (and 4-spaces instead of a `\t`) – David C. Rankin Nov 02 '17 at 21:41
  • I tried both these and the file didn't get updated. 1. sed -E '/\[block 1\]|\[blk 2\]/r\\n\\toranges = 3\\n\\tpears = 2' sample2.txt 2. sed -E '/\[block 1\]|\[blk 2\]/r\n\toranges = 3\n\tpears = 2' sample2.txt Am I missing something? – SyncMaster Nov 02 '17 at 21:48
  • @DavidC.Rankin, Actually, (escaped) alternation operator (`\|`) in BRE is a [GNU `sed` extension](https://www.gnu.org/software/sed/manual/sed.html#index-GNU-extensions_002c-to-basic-regular-expressions-4). In [POSIX](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html), only ERE allows alternation. – randomir Nov 02 '17 at 21:48
  • @SyncMaster, if you want an inplace update, add the `-i` flag. – randomir Nov 02 '17 at 21:49
  • @SyncMaster, if you want to use `r` command, store the text to be added into a separate file, then reference it with `r file` (see my answer, `/path/to/text` is path to your text file, the contents of which will be inserted). – randomir Nov 02 '17 at 21:55
  • 1
    @SyncMaster, or just try the first (or second) example. You can simply copy & paste it, without creating an intermediate file you need for the last two examples. – randomir Nov 02 '17 at 22:38
  • ANSI-C quoting can be used to insert newlines: $'\n' – Joe Bowbeer Aug 07 '19 at 10:15
1

This awk should work with empty RS. This breaks each block into a single record.

awk -v RS= '/\[block 1\]/{$0 = $0 ORS "\toranges = 3" ORS "\tpears = 2" ORS} 
      /\[blk 2\]/{$0 = $0 ORS "\toranges = 5" ORS "\tpears = 2" ORS} 1' file

abc
def
[block 1]
    apples = 3
    grapes = 4
    oranges = 3
    pears = 2

[blk 2]
    banana = 2
    apples = 3
    oranges = 5
    pears = 2
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • I didn't get the expected output. The new lines did not get inserted in [block 1]. This is what I got when I ran this. $awk -v RS= '/\[block 1\]/{$0 = $0 ORS "\toranges = 3" ORS "\tpears = 2" ORS} > /\[blk 2\]/{$0 = $0 ORS "\toranges = 5" ORS "\tpears = 2" ORS} 1' sample2.txt abc def [block 1] apples = 3 grapes = 4 [blk 2] banana = 2 apples = 3 oranges = 3 pears = 2 oranges = 5 pears = 2 – SyncMaster Nov 02 '17 at 21:42
  • 1
    Strange... OSX should use `'\n'` as it's end of line -- the same as Linux (unlike pre-OSX that used `'\r'` instead) – David C. Rankin Nov 02 '17 at 21:45
  • Same issue on my Linux machine too – SyncMaster Nov 02 '17 at 22:04
  • 1
    @SyncMaster: This has been tested on OSX awk. [Here is a working demo](https://ideone.com/E6lqAH) – anubhava Nov 03 '17 at 04:32
  • Also make sure your input file does not have DOS line ending (carriage returns). Check with `cat -A sample2.txt` command. If it shows `^M` at the end of line then it means a DOS file that needs to be converted to Unix file – anubhava Nov 03 '17 at 04:39
1

This might work for you (GNU sed):

sed '/^\[\(block 1\|blk 2\)\]\s*$/{n;h;s/\S.*/oranges = 5/p;s//pears = 2/p;x}' file

Locate the required match, print it and then store the next line in the hold space. Replace the first non-space character to the end of the line with the first required line, repeat for the second required string and then revert to the original line.

potong
  • 55,640
  • 6
  • 51
  • 83