Find and replace each instance of a single string with another from a list or file

Viewed 120

I have a large XML file with multiple instances of the string REPLACEME. In a second file, I have a list of strings (which contain commas) such as:

58,-21,0
234,-38,0

I'd like to replace each instance of REPLACEME in the first file with one of the values from the second file, and then move on to the next instance.

I've looked at bash (sed, awk, perl) and Powershell. I've been told I shouldn't use for loops, but use file loops and parse the file back in. So I've tried this:

file2=/file2.txt
while IFS= read -r line; do
  printf '%s\n' "$line"
  sed '0,/REPLACEME/s//$line/' /file1.xml
done < "$file2"

But it doesn't do anything. Possibly because the XML files have symbols? It doesn't error, it just doesn't do anything.

Happy to ditch my code entirely or switch parsers if I can find something that can achieve the replacement result?

EDIT: Asked for an example of the XML. The XML markup is part of a KML file, the strings are coordinate points. Example below:

<Placemark>
    <name>5005</name>
        <MultiGeometry>
            <Polygon>
                -snip-
            </Polygon>
            <Point>
            <gx:drawOrder>1</gx:drawOrder>
                <coordinates>REPLACEME</coordinates>
            </Point>
        </MultiGeometry>
</Placemark>

There are hundreds of such entries in this file, which need to be filled using corresponding coordinates from the list in file2.

2 Answers

Sounds like all you need is:

awk 'NR==FNR{a[NR]=$0; next} /REPLACEME/{sub(/REPLACEME/,a[++c])} 1' file2.txt file1.xml

The normal advice is to use an XML-aware tool like xmlstarlet or xmllint when manipulating xml files but I personally don't know either well enough to solve this problem with them and IMHO it's not necessary for what you're doing, assuming REPLACEME only occurs in the context shown in your example.

When manipulating text the best advice isn't "don't use for loops" it's "don't use shell loops" so using a while loop in this context is also a bad approach. See why-is-using-a-shell-loop-to-process-text-considered-bad-practice.

When doing anything other than just manipulating text (e.g. reading a list of URLs from a file to run curl on) then a shell loop can be appropriate and in that case (but also consider xargs instead) and then yes you should avoid for, see https://mywiki.wooledge.org/DontReadLinesWithFor.

Your sed script sed '0,/REPLACEME/s//$line/' won't let $line expand since it's inside single quotes.

Using a combination of sed and ed - the sed creates ed commands from your file2.txt:

(sed 's|.*|/REPLACEME/s/REPLACEME/&/|' file2.txt; echo '1,$p') | ed -s file1.xml

The first REPLACEME in the XML file is replaced by the first line of file2.txt, the second by the second, and so on.

If you want to save the changes to the file instead of just printing them to standard output, replace the echo '1,$p' with echo w.

If you have more lines in file2.txt than there are REPLACEME lines in file1.xml, ed will print out a question mark for each on standard error. If you don't want to see those, redirect to /dev/null:

$ (sed 's|.*|/REPLACEME/s/REPLACEME/&/|' file2.txt; echo '1,$p') | ed -s file1.xml 2>/dev/null
<Placemark>
    <name>5005</name>
        <MultiGeometry>
            <Polygon>
                -snip-
            </Polygon>
            <Point>
            <gx:drawOrder>1</gx:drawOrder>
                <coordinates>58,-21,0</coordinates>
            </Point>
        </MultiGeometry>
</Placemark>

If not for the 2>/dev/null, your sample files would have also produced one ? because file2.txt has two lines and there's only one REPLACEME in the XML file.

Related