satya
satya

Reputation: 111

Insert multiple lines in file before specified pattern

Unable to add lines before a matching line if the content has new lines and this content is generated by a function

another alternative that looked good (Insert multiple lines into a file after specified pattern using shell script) but it only appends "AFTER". I need "BEFORE"

put the xml content into add.txt then

sed '/4/r add.txt' $FILE

#/bin/sh

FILE=/tmp/sample.txt
form_xml_string()
{
  echo "<number value=\"11942\">"
  echo "  <string-attribute>\"hello\"</string-attribute>"
  echo "</number>"
}

create_file()
{
  if [ -e $FILE ]
  then
          echo "Removing file $FILE"
          rm $FILE
  fi

  i=1
  while [ $i -le 5 ]
  do
          echo "$i" >> $FILE
          i=$(( i+1 ))
   done
}

create_file
cat $FILE

# file sample.txt has numbers 1 to 5 in each line
# Now append the content from form_xml_string to line before 4
# command I tried
CONTENT=`form_xml_string`
echo "CONTENT is $CONTENT"
sed -i "/4/i $CONTENT" $FILE
cat $FILE

Expected output:

1
2
3
<number value="11942">
  <string-attribute>"hello"</string-attribute>
</number>
4
5

Actual output (or error): sed: -e expression #1, char 31: unknown command: `<'

Upvotes: 4

Views: 1624

Answers (3)

ᴍᴇʜᴏᴠ
ᴍᴇʜᴏᴠ

Reputation: 5276

Say you want to insert the contents of /tmp/snippet before the first } only.

awk '
    /}/ && !done {          # Search for the first "}" and check if insertion is not done
        while ((getline line < "/tmp/snippet") > 0) {
            print line      # Print each line from the snippet file
        }
        print "}"           # Print the original "}" after the snippet
        done=1              # Set done to 1 to avoid further insertions
        next                # Skip the original line containing "}"
    }
    { print }               # Print all other lines as is
' /path/to/file > /tmp/tmp2 && mv /tmp/tmp2 /path/to/file

Having to have /tmp/tmp2 is ugly but it's required

Upvotes: 0

ceving
ceving

Reputation: 23871

Use e instead of r.

From the Sed manual about the e command:

Note that, unlike the r command, the output of the command will be printed immediately; the r command instead delays the output to the end of the current cycle.

The delay of the r command is the problem, that you can not output anything after it.

Example for the e command:

seq 0 9 | sed -e '/4/{
  h
  e cat add.xml
  g
}'

h copies the matching line into the hold space and g copies it back into the pattern space. By this it appears after the "add.xml" in the output.

Upvotes: 2

Ghassen Manai
Ghassen Manai

Reputation: 81

It's normal that you get that error, the syntax of your text is not compatibale with your sed command, allow me to elaborate:

  • First, you have a lot of /s in your text, and / is the delimiter insed, that confuses the command, which is why you get that error. So you should escape all / in the text you are using, by replacing them with \\/ (The extra \ is going to be interpreted by the shell).

  • Second, in the man for sed, we can see this small line about /i:

Insert text, which has each embedded newline preceded by a backslash

This means that you also need to add a \ before every newline, in your example this means adding \\ at the end of every echo.

Edit:

Thanks to Toby Speight's comment, I noticed that I forgot completly about the possiblity of changing sed's delimiter, which can make your life a lot easier, since you wouldn't have to add \\ before every / in your text. To do this all you need is to change this line sed -i "/4/i $CONTENT" $FILE to, for example, this sed -i "\\_4_i $CONTENT" $FILE.

Here's how your script will become once you introduce these changes:

#! /bin/sh
FILE=/tmp/sample.txt
form_xml_string()
{
  echo "<number value=\"11942\">\\"
  echo "  <string-attribute>\"hello\"</string-attribute>\\"
  echo "</number>"
}

create_file()
{
  if [ -e $FILE ]
  then
          echo "Removing file $FILE"
          rm $FILE
  fi

  i=1
  while [ $i -le 5 ]
  do
          echo "$i" >> $FILE
          i=$(( i+1 ))
   done
}

create_file
cat $FILE

# file sample.txt has numbers 1 to 5 in each line
# Now append the content from form_xml_string to line before 4
# command I tried
CONTENT=`form_xml_string`
echo "CONTENT is $CONTENT"
sed -i "\\_4_i $CONTENT" $FILE
cat $FILE

Upvotes: 4

Related Questions