0

This question already has an answer here:

I would like to transform this XML to where only certain lines are joined together. This is so I can later grep only specific lines. If this XML used attributes, then the data would already be in 1 line. I don't want to do this with any XSLT. grep, sed, awk, xmlstarlet, xpath would be great

So if a line contains "<instruction>", then I need to grab the next 4 lines and put them all in one line. No commas.  As shown below.

this is not like the question posted here: that had a set pattern of a date that started the sequence to "join".

awk '
    /\<instruction\>/ && line {print line; line=""}
    {line = line ? line" "$0 : $0}
    END {print line}
'

this folded in the line UP TO the end tag , but didn't bring in the end tag line itself.

Platform will be on Windows 2012R2 server. Will install Gnu utils. File is approx 100k in size.

                        <instruction>
                          <name>AAAAA</name>
                          <value>WHITE</value>
                          <type>0</type>
                        </instruction>
                        <instruction>
                          <name>BBBBB</name>
                          <value>WHITE</value>
                          <type>0</type>
                        </instruction>
                        <instruction>
                          <name>CCCCC</name>
                          <value>WHITE</value>
                          <type>0</type>
                        </instruction>
                      <routing/>
                      <phantom>False</phantom>
                      <AssemblyHistory/>

DESIRED OUTPUT: (may have wrapped, but the Instruction element should all be in 1 line)

<instruction><name>AAAAA</name><value>WHITE</value><type>0</type></instruction> <instruction><name>BBBBB</name><value>WHITE</value><type>0</type></instruction> <instruction><name>CCCCC</name><value>WHITE</value><type>0</type></instruction> <routing/> <phantom>False</phantom> <AssemblyHistory/>

SIMILAR EXAMPLE (not well formed XML)

data line 2
cust:
tommy
smith
123 main
endcust
data line 16
TRACE: error at xy123
cust:
mary 
smith
444 broadway
endcust
LOG: logon tty3
LOG: free memory before gc 33453211
root: ps -ealf  tty0 


data line 2
cust: tommy smith123 main endcust
data line 16
TRACE: error at xy123
cust:mary smith444 broadway endcust
LOG: logon tty3
LOG: free memory before gc 33453211
root: ps -ealf  tty0 

Copy the entire file to stdout, omitting no lines. When you find "cust:", either: 1) JOIN the next 4 lines OR 2) JOIN lines up until you find "endcust"

Its the same thing, but this time you can't use an XML tool. So if this was the case, how would you solve it? If the top Question is XML, then fine. If not, use the 2nd example

marked as duplicate by X Tian, Rui F Ribeiro, G-Man, Kiwy, Christopher Apr 23 '18 at 13:09

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 1
    You are starting with xml so the other suggestions that an xml tool is the correct way to go are correct in my opinion. I would use xsltproc, however you say, we should ignore the fact that it is xml, how do you join 4 lines between <instruction> ... </instruction>, so I'm flagging this as duplicatte of this question, you should be able to adapt that answer to your specifc instance. – X Tian Apr 21 '18 at 12:51
  • I'm saying "you CAN ignore" thats its XML if seeing that this looks like XML confuses you. I need to run something from the command line (like sed,grep,awk). Its a text file thats also a well formed XML document (which could aid someone in coming up with a solution). – Jaymer Jaymer. Apr 21 '18 at 16:40
  • and thank you for pointing out the possible duplicate of another thread, but if I was super smart, then true, I should be able to adapt that answer to your specifc instance. But I spent time playing with that awk and I cannot get it to do what I want, which is why I ask here. – Jaymer Jaymer. Apr 21 '18 at 16:42
  • @ X Tian This folds in all lines, even ones that don't have the <instruction> element awk ' /\<instruction\>/ { if (NR>2) print "" printf "%s", $0 next } {printf "%s", $0} END {print ""} ' – Jaymer Jaymer. Apr 21 '18 at 17:08
  • Cool, looks v similar to one of the dupes I highlighted, except I think NR >2, is a typo, it should be 1, although will still do what you want with your input file. Pleased I could help – X Tian Apr 21 '18 at 17:17
0

You can use sed's range operator to tackle your scenario, but before that make sure that the tag and antitag on same lines are dealt with. Also multiple tags on the same line will not be handled.

sed -e '
    s/^[[:blank:]]*//
    \|<instruction>.*</instruction>|b
    \|<instruction>|,\|</instruction>|!b
    H;\|</instruction>|!d
    s/.*//;x;s/\n[[:blank:]]*//g;s/^\n//
' input_xml_lookalike_file
  • this is almost the answer. thanks, but it has an error. try it with this data. its not clearing out the buffer after it joins and prints a line. LINE 0 LINE 1 LINE 2 <instruction> <name>11111</name> <value>1.02</value> <type>0</type> </instruction> <instruction> <name>BBBBB</name> <value>2.02</value> <type>0</type> </instruction> </instructions> LINE 3 LINE 4 – Jaymer Jaymer. Apr 22 '18 at 16:17
  • Also Rakesh. FYI, i will not have multiple tags on the same line, nor do i have to worry about "antitag" - though i'm not exactly sure what that is. i think you're referring to "<instruction/>" but that won't happend - instead the output program will just not output any instruction element. just in case this makes is simpler. THANKS! – Jaymer Jaymer. Apr 22 '18 at 16:23
  • anti-tag => </instruction> regarding the buffer clearing, see the edited version in my answer. – Rakesh Sherma Apr 22 '18 at 17:58
  • @'Rakesh Sherma' Awesome. Thanks very much. Perfect Solution to the asked question. – Jaymer Jaymer. Apr 22 '18 at 19:45
  • sed '/<instruction>/!bC;:A;/<\/instruction>/bB;$bB;N;bA;:B;y/\n/ /;:C;s/[[:blank:]]//g' input_xml_lookalike_file – ctac_ Apr 22 '18 at 21:04
0

I guess the reason you want to use grep/sed/awk is because you are familiar with them. That doesn't make them the right tool for the job. Using a screwdriver to hammer a nail just because you know how to use a screwdriver isn't a smart idea.

You want XML-aware tools for this: XSLT or XQuery. (For a start, what makes you think the <instruction> tag will never contain any whitespace?, and that the contents of the instruction element will always be laid out over 4 lines?)

We get a lot of users on StackOverflow asking how to generate XML formatted in a particular way, which restricts their choice of XML tools. When we ask why they need to do so, the answer is invariably that someone has written an application to read the XML using non-XML-aware tools like awk or grep or Perl. You're missing the whole point of XML (and all the interoperability benefits it brings) if you do this.

  • you wrote: For a start, what makes you think the <instruction> tag will never contain any whitespace?, and that the contents of the instruction element will always be laid out over 4 lines? – Jaymer Jaymer. Apr 21 '18 at 12:38
  • (weird, i cannot edit the above comment) Because I know the spec of the output. <instruction> will be exactly that. Will never have attributes. Will ALWAYS have 4 lines after it I need to JOIN. – Jaymer Jaymer. Apr 21 '18 at 16:36
0

To extract, e.g., the value of the value sub-node of every instruction node that also has a name sub-node whose value is Exterior Color using XMLStarlet:

xmlstarlet sel -v '//instruction[name = "Exterior Color"]/value' -nl file.xml

Given the file

<?xml version="1.0"?>
<AssemblyHistory>
  <routing>
    <instruction>
      <name>Interior Finish</name>
      <value>WHITE</value>
      <type>0</type>
    </instruction>
    <instruction>
      <name>Exterior Color</name>
      <value>WHITE</value>
      <type>0</type>
    </instruction>
    <instruction>
      <name>Base Vinyl Color</name>
      <value>WHITE</value>
      <type>0</type>
    </instruction>
  </routing>
  <phantom>False</phantom>
</AssemblyHistory>

This would return the string WHITE.

The following would return every name that corresponds to a WHITE value in all instruction nodes:

xmlstarlet sel -t -v '//instruction[value = "WHITE"]/name' -nl  file.xml
  • does not have typos. those are null nodes, or self-closing or whatever you want to call them. ignore those. this is a snippet of a 7000 line file, not the entire file. I'm not trying to extract values. For all intents, forget this is an XML file. The Q is how to find a line and join the next 4 lines only. Not just the next line. Not a line that begins with something or has a recognizable key. the Q is how to make the output look like the desited output in the Question. – Jaymer Jaymer. Apr 21 '18 at 12:42
  • @JaymerJaymer. I see. The indentation confused me. You say in the question that you'd like to grep the result for values, which is why I thought you might want to query the XML document. – Kusalananda Apr 21 '18 at 12:54
0

this almost works:

awk 'BEGIN {RS="<instruction>"; FS="\n"; OFS=""} NR>1 {$1=RS; NF--; print}'

but it skips the preceeding lines and doesnt stop joining when it sees the /instruction tag

input:
LINE 0
LINE 1
LINE 2
<instruction>
<name>Glass SQFT</name>
<value>7.02</value>
<type>0</type>
</instruction>
LINE 3

output:
<instruction><name>Glass SQFT</name><value>7.02</value><type>0</type></instruction>LINE 3

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