1

I have 2 files and would like to replace the 1st matching block of text from file1 (matching criteria start indicator as <<EOF and End of block criteria is EOF) with sed from another file2 data.

Example:

file1

resource "policy" "temp1" { 
 policy = << EOF
 {
  policy1-data
 }
 EOF
}

resource "policy" "temp2" { 
 policy = << EOF
 {
  policy2-data
 }
 EOF
}

file2

{
  modified-policy-data
}

output file

resource "policy" "temp1" { 
 policy = << EOF
 {
  modified-policy-data
 }
 EOF
}

resource "policy" "temp2" { 
 policy = << EOF
 {
  policy2-data
 }
 EOF
}

I tried below sed command but that replaces all occurrences of matching criteria in file1 from file2 data where I would like to replace only first matching block occurrence and rest keep same as is from file1

lead='<<EOF'
tail='EOF'
sed -nie '/'"$lead"'/ { p r file2 :a n /'"$tail"'/ { p b } ba }p' file1
raj
  • 137
  • 1
  • 1
  • 10
  • What is the **actual** command you tried? The one you show is not valid sed syntax. – jhnc Apr 13 '23 at 01:59
  • nit-pick - `head` rather than `lead` is usually used as the opposite of `tail`, using `lead` instead of `head` might cause some confusion at some point, especially given how similar they are. – Ed Morton Apr 13 '23 at 13:18

6 Answers6

1

Not sed but if ed is available/acceptable with your given input/data, something like:

The script.ed name it to your own hearts content.

0r file2
kx
2m/^.*<< EOF/;/^.*EOF/;?[ ]*{?
+1d
1,'xd
,p
Q

Now run

ed -s file1 < script.ed

Output

resource "policy" "temp1" { 
 policy = << EOF
 {
  modified-policy-data
 }
 EOF
}

resource "policy" "temp2" { 
 policy = << EOF
 {
  policy2-data
 }
 EOF
}

In one-line

printf '%s\n' '0r file2' 'kx' '2m/^.*<< EOF/;/^.*EOF/;?[ ]*{?' '+1d' "1,'xd" ,p Q | ed -s file

with variables in a shell script, something like:

file1

resource "policy" "temp1" { 
 policy = << EOF
 {
  policy1-data
 }
 EOF
}

resource "policy" "temp2" { 
 policy = << EOF
 {
  policy2-data
 }
 EOF
}

file2

{
  modified-policy-data
}

foo
bar
baz

The script.

#!/bin/sh

lead='<< EOF'
tail='EOF'

ed -s file1 <<-EOD
  0r file2
  kx
  2m/^.*$lead/;/^.*$tail/;?[ ]*{?
  +1d
  1,'xd
  ,p
  Q
EOD

OTOH, your data/files/input looks like tcl


  • Change Q to w if in_place editing is needed.

  • Remove the ,p to silence the output.

  • See GNU ed

  • See POSIX ed

Jetchisel
  • 7,493
  • 2
  • 19
  • 18
1

Once you match lead you must never start a new cycle (ie. b)

sed -nie '
  p
  /'"$lead"'/ {
    r file2

    :a
    n
    /'"$tail"'/ ! ba

    :b
    p
    n
    bb
  }
' file1
jhnc
  • 11,310
  • 1
  • 9
  • 26
1

It seems like awk would be easier than sed. Something like:

awk '! seen && /<< EOF$/ { seen=1; print; 
    while( (getline < "file2") > 0 ) { print } 
    while( (getline > 0) && ! /EOF/ ); } 1' file1
William Pursell
  • 204,365
  • 48
  • 270
  • 300
1

Using any awk and aligning the new text with the existing EOF:

$ cat tst.awk
NR == FNR {
    new = (NR>1 ? new ORS : "") $0
    next
}
cnt == 1 {
    if ( index($0,tail) ) {
        indent = $0
        sub(/[^[:space:]].*/,"",indent)
        gsub(ORS,ORS indent,new)
        print indent new ORS $0
        cnt++
    }
    next
}
index($0,lead) {
    cnt++
}
{ print }

$ awk -v lead='<< EOF' -v tail='EOF' -f tst.awk file2 file1
resource "policy" "temp1" {
 policy = << EOF
 {
   modified-policy-data
 }
 EOF
}

resource "policy" "temp2" {
 policy = << EOF
 {
  policy2-data
 }
 EOF
}
Ed Morton
  • 188,023
  • 17
  • 78
  • 185
1

This might work for you (GNU csplit):

csplit -s file1 '/EOF/+1' '%EOF%' && cat xx00 file2 xx01 > file3 && rm xx??

Use csplit to split file1 into two and remove the text to replaced by file2.

Concatenate the files above with the replacement file sandwiched between them.

Alternative using GNU sed:

sed -ne '/EOF/!{p;b};p;:a;n;//!ba;e cat file2' -e ':b;p;n;bb' file1

Clean up unwanted files.

potong
  • 55,640
  • 6
  • 51
  • 83
0

If you want to specify which section to replace by resource name, you could do it like this with GNU sed and a bash function:

repl file1 file2 temp1

Where repl is defined as:

repl() {
  sed -E '
    # Always keep 2 lines in pattern space
    N
    /resource.*"'$3'".*\n.*<< *EOF/ {
      p
      :a
      /^ *EOF$/! {
        N
        s/.*\n//
        ba
      }
      r '$2'
      N
    }
    P
    D' "$1"
}

Testing:

repl file1 file2 temp1

Output:

resource "policy" "temp1" {
 policy = << EOF
{
  modified-policy-data
}
 EOF
}

resource "policy" "temp2" {
 policy = << EOF
 {
  policy2-data
 }
 EOF
}

Or replace temp2:

repl file1 file2 temp2

Output:

resource "policy" "temp1" {
 policy = << EOF
 {
  policy1-data
 }
 EOF
}

resource "policy" "temp2" {
 policy = << EOF
{
  modified-policy-data
}
 EOF
}
Thor
  • 45,082
  • 11
  • 119
  • 130