Replace pattern each time with a different string (taken from external file)

I have an input:

a
b TOCHANGE
c
d 
e TOCHANGE

where I need to change the patterns “TOCHANGE” using an external file :

line1
line2
...

so that I get the following output :

a
b line1    
c
d
e line2

I tried the following command :

while read k ; do sed -i "s/TOCHANGE/$k/g" input ; done < externalfile

but I got :

a
b line1    
c
d
e line1

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

With perl:

perl -pi -e 's{TOCHANGE}{chomp ($repl = <STDIN>); $repl}ge' input <externalfile

With awk, assuming TOCHANGE doesn’t occur in externalfile (or more generally that replacements don’t generate new occurrences of TOCHANGE which could also happen for instance on an input that contains TOTOCHANGE FROMTOCHANGE and externalfile contains CHANGE and WHATEVER):

POSIXLY_CORRECT=1 PAT=TOCHANGE awk '
  {
    while ($0 ~ ENVIRON["PAT"]) {
      getline repl < "externalfile"
      gsub(/[&\]/, "\\&", repl)
      sub(ENVIRON["PAT"], repl)
    }
    print
  }' < input > input.new

(POSIXLY_CORRECT=1 is needed for GNU awk where without which it wouldn’t work correctly for replacement strings that contain backslash characters).

Note that $PAT above is taken as an extended regular expression. You may need to escape ERE operators if you want them to be treated literally (like PAT='TO.CHANGE' to replace TO.CHANGE strings).

Method 2

This can be done with GNU sed as given:

sed -e '/TOCHANGE/R file_2' input.txt |
sed -e '/TOCHANGE/N;s/TOCHANGE(.*)n(.*)/21/'

In the first pass, sed will place a line of file_2 below the TOCHANGE line for all lines in input.txt

In the next pass, the line comprising TOCHANGE will be joined together with the following line and a s/// substitution will get the intended output.

With Perl it can be accomplished as:

perl -pe 's|TOCHANGE|<STDIN> =~ s/n//r|e' input.txt < file_2

Method 3

With this awk

awk -v old='TOCHANGE' '
NR==FNR{a[NR]=$0;next}
$2==old{$2=a[++i]}
1' changefile infile > outfile

Method 4

Couple tricky solutions with the specific awk features usage.

First variant

If the "TOCHANGE" pattern occurs only at once in each row. The usual awk will be enough.

awk '{
    if(NF == 2) {
        getline OFS < "file_2"
        $1 = $1
    }    
    print
}' FS='TOCHANGE' input.txt

Second variant

If the "TOCHANGE" pattern can occur many times in each row. The gawk needed.

gawk '{
    RS="n"
    if(RT)
        getline ORS < "file_2"
    else
        ORS=""

    print

    RS="TOCHANGE"
}' RS='TOCHANGE' input.txt

Testing

input.txt

a
b TOCHANGE
c
d 
e TOCHANGE
f
g TOCHANGE

file_2

line1
line2
line3
line4

Output

a
b line1
c
d 
e line2
f
g line3


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x