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
  • 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 – cunninghamp3 Apr 24 '18 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) – Stéphane Chazelas Apr 24 '18 at 14:19
  • does TOCHANGE always come as the 2nd column? – RomanPerekhrest Apr 24 '18 at 14:23
  • @StéphaneChazelas - yup. another instance of my mixing up what i can do in vim with sed Doh! – cunninghamp3 Apr 24 '18 at 14:28
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).

  • or just with -P option – RomanPerekhrest Apr 24 '18 at 14:45
  • @RomanPerekhrest, well using -P would make that code non-portable, while using POSIXLY_CORRECT would be harmless with other awk implementations. – Stéphane Chazelas Apr 24 '18 at 14:54
  • So many thanks, this awk solution solved the problem ! – E.C Apr 24 '18 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) – glenn jackman Oct 2 '18 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
  • 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 '18 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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.