2

I am trying to extract lines from a file where the end string is different from the begin string

I have the following input file in format:

<item_id>6</item_id>
  <info1>abc</info1>
  <info2>123</info2>
<item_id>6</item_id>
  <info1>def</info1>
  <info2>456</info2>
<item_id>6</item_id>
  <info1>ghi</info1>
  <info2>789</info2>
<item_id>9</item_id> 
  <info1>jkl</info1>
  <info2>012</info2>
<item_id>9</item_id>
  <info1>mno</info1>
  <info2>345</info2>

I want to extract from "6" until "9" (9 being a random number)

Begin is <item_id>6<\item_id>

End is <item_id>X<\item_id> X being anything but 6

Expected result:

<item_id>6</item_id>
  <info1>abc</info1>
  <info2>123</info2>
<item_id>6</item_id>
  <info1>def</info1>
  <info2>456</info2>
<item_id>6</item_id>
  <info1>ghi</info1>
  <info2>789</info2>

I tried:

sed -n "/<item_id>6<\/item_id>/,/<item_id>!6<\/item_id>/p" input.file > output.6
sed -n "/<item_id>6<\/item_id>/,/<item_id>\!6<\/item_id>/p" input.file > output.6
sed -n "/<item_id>6<\/item_id>/,/<item_id>\{!6}<\/item_id>/p" input.file > output.6
sed -n "/<item_id>6<\/item_id>/,/<item_id>(6)\@!.<\/item_id>/p" input.file > output.6
sed -n "/<item_id>6<\/item_id>/,/<item_id>! 6<\/item_id>/p" input.file > output.6
3

Maybe this GNU sed script will work for you:

sed '/<item_id>6<\/item_id>/{:1;n;/<item_id>[^6]<\/item_id>/{Q};b1};d' file

Keep deleting d lines until a 6 line is found, then keep in the internal loop b1 printing each line until a not 6 ([^6]) line is found, at which time the script is Quit.

Understand that this works only for one character numbers (the 6 or the 9).


It gets quite messy to get it to work with any number (GNU):

sed -e 'h;s,<item_id>\([0-9]*\)<\/item_id>,\1,;/^6$/!d;/^6$/{g;:1;h;s,<item_id>\([0-9]*\)<\/item_id>,\1,;t2;:3;g;n;b1};d;b;:2;/^6$/!Q;b3' file

Using a couple of vars makes it a bit better, but not that much

reg='<item_id>\([0-9]*\)<\/item_id>'
item='6'

sed -e "h;s,$reg,\1,;/^$item$/!d;/^$item$/{g;:1;h;s,$reg,\1,;t2;:3;g;n;b1};d;b;:2;/^$item$/!Q;b3" file

And a POSIXfyed one looks like gibberish:

sed -ne 'h;s,<item_id>\([0-9]*\)<\/item_id>,\1,;/^6$/!d;/^6$/{g;:1;h;s,<item_id>\([0-9]*\)<\/item_id>,\1,;t2;:3;g;p;n;b1;};d;b;:2;/^6$/!q;b3' file

Only this extended code might(?) be more readable:

reg='<item_id>\([0-9]*\)<\/item_id>'
item='6'

sed -ne 'h;                 # hold copy of line being processed.
         s,'"$reg"',\1,;    # extract included number.
         /^'"$item"'$/!d;   # Not the correct number?: get next line.
         /^'"$item"'$/{
         g;                 # restore line from hold buffer.
         :1
         h;                 # store successive lines in hold buffer.
         s,'"$reg"',\1,;    # extract the number (again).
         t2
         :3
         g;                 # restore the line back from hold buffer.
         p;                 # print the line.
         n;                 # go to process next line.
         b1
         };                 # end of loop.
         d;                 # avoid printing this last line.
         q;                 # quit the sed script.
         :2
         /^'"$item"'$/!q;   # Test if the number is the one selected.
         b3
        ' file

Maybe you should use some other language or tool.

  • This will not break out of the loop if the next item has two or three digits, only works with one digit items... – filbranden Nov 10 '18 at 21:01
  • that's great. almost there. it does not work for a string <item_id>12 until <item_id>15 for example :(.. i tried a few things but cant get it – xavi Nov 10 '18 at 21:04
  • @FilipeBrandenburger Yes, that is what I said one character numbers. Anyway, a more flexible (but hard to read) solution added. – Isaac Nov 11 '18 at 6:32
  • thanks! it makes it complicated indeed but this works perfectly. thanks again. – xavi Nov 11 '18 at 9:52
5

Assuming this is a well-formed XML document, the following uses XMLStarlet to pick out the info_id nodes whose values are 6, and then the immediately following sibling info1 and info2 nodes:

$ xmlstarlet sel -t -m '//item_id[. = 6]' \
    -c . -nl \
    -c './following-sibling::info1[1]' -nl \
    -c './following-sibling::info2[1]' -nl file.xml
<item_id>6</item_id>
<info1>abc</info1>
<info2>123</info2>
<item_id>6</item_id>
<info1>def</info1>
<info2>456</info2>
<item_id>6</item_id>
<info1>ghi</info1>
<info2>789</info2>

Alternatively, to ignore what the following nodes are called:

xmlstarlet sel -t -m '//item_id[. = 6]' \
    -c . -nl \
    -c './following-sibling::*[1]' -nl \
    -c './following-sibling::*[2]' -nl file.xml

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.