4

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
4
  • You may be able to just drop the /g from the end of your sed command. Then each repetition should change only the first TOCHANGE it finds Apr 24, 2018 at 14:04
  • @cunninghamp3, dropping the g would stop sed changing more than one occurrence per line, but not on the whole file (there would still be up to one replacement per line of the file) Apr 24, 2018 at 14:19
  • does TOCHANGE always come as the 2nd column? Apr 24, 2018 at 14:23
  • @StéphaneChazelas - yup. another instance of my mixing up what i can do in vim with sed Doh! Apr 24, 2018 at 14:28

4 Answers 4

6

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).

4
  • or just with -P option Apr 24, 2018 at 14:45
  • @RomanPerekhrest, well using -P would make that code non-portable, while using POSIXLY_CORRECT would be harmless with other awk implementations. Apr 24, 2018 at 14:54
  • So many thanks, this awk solution solved the problem !
    – E.C
    Apr 24, 2018 at 18:34
  • Hmm, instead of gsub+sub, one could use match() in the while condition, then use string concatenation: $0 = substr($0, 1, RSTART-1) repl substr($0, RSTART+RLENGTH) Oct 2, 2018 at 17:22
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\(.*\)/\2\1/' 

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
1

With this awk

awk -v old='TOCHANGE' '
NR==FNR{a[NR]=$0;next}
$2==old{$2=a[++i]}
1' changefile infile > outfile
1
  • Note: this solution will work in one case only - if "TOCHANGE" pattern occurs only at once in each line and it is the second field. But maybe it's enough for solving the OP task.
    – MiniMax
    Apr 25, 2018 at 10:32
1

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .