LinuxQuestions.org
Welcome to the most active Linux Forum on the web.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Linux Forums > Linux - General
User Name
Password
Linux - General This Linux forum is for general Linux questions and discussion.
If it is Linux Related and doesn't seem to fit in any other forum then this is the place.

Notices


Reply
  Search this Thread
Old 08-16-2016, 12:15 PM   #1
bradvan
Member
 
Registered: Mar 2009
Posts: 367

Rep: Reputation: 61
sed match multiple lines


I have an xml file that has multiple user sections. They look like:
Code:
<ADD_USER
   USERNAME = "Ralph Delp"
   USER_LOGIN = rpdelp
   PASSWORD = "%user_password%">
</ADD_USER>
There are multiple sections like this and I want to delete specific blocks. I am trying:
Code:
sed -i '/^<ADD_USER/{ N;/^   USERNAME = "Ralph Delp"/ {/^<ADD_USER/,/^<\/ADD_USER>/d}}' file
but I'm not getting a match and delete. I tried without the '^' in the username line:
Code:
sed -i '/^<ADD_USER/{ N;/USERNAME = "Ralph Delp"/ {/^<ADD_USER/,/^<\/ADD_USER>/d}}' file
It does seem to make the match, but only deletes the ADD_USER and USERNAME lines. Any suggestions?

Last edited by bradvan; 08-17-2016 at 06:13 AM. Reason: Missed the closing '>' on ADD_USER
 
Old 08-16-2016, 06:40 PM   #2
syg00
LQ Veteran
 
Registered: Aug 2003
Location: Australia
Distribution: Lots ...
Posts: 21,137

Rep: Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122
Quote:
Originally Posted by bradvan View Post
Any suggestions?
Don't use sed.
You will get answers, most likely involving adding lines to the hold buffer until you hit the end line (</ADD_USER), then do the match and delete. If you just want the job done, use awk or perl or whatever - something that has better logic.
 
1 members found this post helpful.
Old 08-16-2016, 07:03 PM   #3
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,269
Blog Entries: 24

Rep: Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196
Agree, don't use sed!

The file is XML, so a simple XML tool like xmlstarlet would do the trick.

If the file is formatted as shown you could use awk with '<ADD_USER' or </ADD_USER> as the record separator.
 
1 members found this post helpful.
Old 08-17-2016, 02:54 AM   #4
Turbocapitalist
LQ Guru
 
Registered: Apr 2005
Distribution: Linux Mint, Devuan, OpenBSD
Posts: 7,328
Blog Entries: 3

Rep: Reputation: 3726Reputation: 3726Reputation: 3726Reputation: 3726Reputation: 3726Reputation: 3726Reputation: 3726Reputation: 3726Reputation: 3726Reputation: 3726Reputation: 3726
Quote:
Originally Posted by bradvan View Post
Code:
<ADD_USER
Is the element really broken like that? It seems to be missing the closing less than ( > ) sign. If it's really not there, then you'll probably have to try "awk" as suggested. But if your file is well-formed XML then you can use some really easy XML parsers like XML::TreeBuilder or XML::TreeBuilder::XPath in Perl.
 
1 members found this post helpful.
Old 08-17-2016, 06:14 AM   #5
bradvan
Member
 
Registered: Mar 2009
Posts: 367

Original Poster
Rep: Reputation: 61
Thanks! Taking a look at other tools.
Will post once I have a working answer.
 
Old 08-17-2016, 06:17 AM   #6
syg00
LQ Veteran
 
Registered: Aug 2003
Location: Australia
Distribution: Lots ...
Posts: 21,137

Rep: Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122Reputation: 4122
Do you have a blank (null) line after each entry ?. If yes, it will be real easy in awk.
 
Old 08-17-2016, 02:31 PM   #7
ondoho
LQ Addict
 
Registered: Dec 2013
Posts: 19,872
Blog Entries: 12

Rep: Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053
xml.
(and yes, the syntax is valid)
i have been succesfully using xmllint (part of libxml2) for things like this. the magic word to search for is xpath.
 
Old 08-18-2016, 07:04 AM   #8
bradvan
Member
 
Registered: Mar 2009
Posts: 367

Original Poster
Rep: Reputation: 61
Well, I have been looking at perl, but still not quite there. The xml files is the output of hponcfg which queries the local integrated lights out (iLO) interface on the HP server. I want to be able to add/remove users from iLO without having to go in to the graphical interface. A more complete listing of the xml is:
Code:
<!-- HPONCFG VERSION = "4.4.0" -->
<RIBLC VERSION="2.1">
 <LOGIN USER_LOGIN="Administrator" PASSWORD="password">
  <DIR_INFO MODE="write">
  <MOD_DIR_CONFIG>
    <DIR_AUTHENTICATION_ENABLED VALUE = "N"/>
    <SEVERAL MORE DIR LINES/>
  </MOD_DIR_CONFIG>
  </DIR_INFO>
  <RIB_INFO MODE="write">
  <MOD_NETWORK_SETTINGS>
    <SPEED_AUTOSELECT VALUE = "Y"/>
    <NIC_SPEED VALUE = "10"/>
    <SEVERAL MORE NETWORK LINES/>
  </MOD_NETWORK_SETTINGS>
  </RIB_INFO>
  <USER_INFO MODE="write">
  <ADD_USER
   USERNAME = "Ralph Delp"
   USER_LOGIN = rpdelp
   PASSWORD = "%user_password%">
   <ADMIN_PRIV value = "Y"/>
   <REMOTE_CONS_PRIV value = "Y"/>
   <RESET_SERVER_PRIV value = "Y"/>
   <VIRTUAL_MEDIA_PRIV value = "Y"/>
   <CONFIG_ILO_PRIV value = "Y"/>
  </ADD_USER>
  <ADD_USER
   USERNAME = "Root User"
   USER_LOGIN = "root"
   PASSWORD = "%user_password%">
   <ADMIN_PRIV value = "Y"/>
   <REMOTE_CONS_PRIV value = "Y"/>
   <RESET_SERVER_PRIV value = "Y"/>
   <VIRTUAL_MEDIA_PRIV value = "Y"/>
   <CONFIG_ILO_PRIV value = "Y"/>
  </ADD_USER>
 </USER_INFO>
 </LOGIN>
</RIBCL>
I am trying to delete elements based on the attribute values. In this case USERNAME. You edit out what you want to keep, and then you can re-submit what you want to delete to iLO with the edited xml file. So, if I want to preserve Ralph's account, I need to remove it from the xml file before sending it back and telling hponcfg to delete what is in the sent xml file. My perl is having a little problem. I can print all of the USERNAME values, but I can't seem to match against it. I have:
Code:
#!/usr/bin/perl
use strict;
use warnings;
use XML::LibXML;
die "Usage: $0 filename <name to delete>\n"
   unless ( @ARGV > 2 );
my $xml_file = shift;
my $bad_name = shift;
print "Searching for bad name: $bad_name\n";
my $xml = XML::LibXML->new;
my $dom = $xml->parse_file( $xml_file );
foreach my $user ( $dom->findnodes ( '/RIBCL/LOGIN/USER_INFO/ADD_USER' )) {
   my ($name) = $user->findnodes( './@USER_NAME');
   print $name->to_literal, "\n";
   if ( "$name->to_literal" eq "$bad_name" ) {
      my ($addu) = $name->parentNode;
      print "Removing element: ", $addu->to_literal, "\n";
   }
}
Which does print out all of the user names, but never matches. Instead of the 'eq' I tried:
Code:
if ( "$name->to_literal" =~ /"$bad_name"/ ) {
but that did not work either. I thought maybe there might be some leading or trailing spaces. So, I printed with a leading and trailing "X" but found not spaces. Any ideas on why I can't make a match?
 
Old 08-18-2016, 07:05 AM   #9
bradvan
Member
 
Registered: Mar 2009
Posts: 367

Original Poster
Rep: Reputation: 61
Oh, and no. No, blank or null lines.
 
Old 08-18-2016, 08:49 AM   #10
bradvan
Member
 
Registered: Mar 2009
Posts: 367

Original Poster
Rep: Reputation: 61
OK, now making the match.
Code:
#!/usr/bin/perl
use strict;
use warnings;
use XML::LibXML;
die "Usage: $0 filename <name to delete>\n"
   unless ( @ARGV > 2 );
my $xml_file = shift;
my $bad_name = shift;
chomp($bad_name);
print "Searching for bad name: $bad_name\n";
my $xml  = XML::LibXML->new;
my $dom  = $xml->parse_file( $xml_file );
my $root = $dom->getDocumentElement;
# Retrieve array of all users
my @users = $root->getElementsByTagName('ADD_USER');
foreach my $user ( @users ) {
   my $username = $user->getAttribute('USER_NAME');
   if ( "$username" eq "$bad_name" ) {
      print "Username = $username \n";
      print "user node type is: ", $user->nodeType, "\n";
      my ($addu) = $user->parentNode;
      print "addu node type is: ", $addu->nodeType, "\n";
      print "Removing element.\n";
      $addu->removeChild( $user );
      print $dom->toString, "\n";
   }
}
The user node type and addu node type are both 1 which is an element. My presumption is that $addu->removeChild( $user ) should remove the corresponding $user element from $root, correct? I then wanted to print out the new document with the removed element, but all I get is a lot of blank lines. So, two questions:

1. Do you think I have correctly removed the element?
2. How to print out the result?

I was using the wrong variable to print out and the wrong method. This is getting me the result, but with an extra blank line. Very close!

Thanks!

Last edited by bradvan; 08-18-2016 at 08:53 AM. Reason: Figured it out
 
Old 08-18-2016, 09:48 AM   #11
bradvan
Member
 
Registered: Mar 2009
Posts: 367

Original Poster
Rep: Reputation: 61
Solved!

I can now delete multiple users at a time and no blank lines in the output. Thanks for all of the suggestions! Here is my final code:
Code:
#!/usr/bin/perl
use strict;
use warnings;
use XML::LibXML;
die "Usage: $0 filename <name to delete>\n"
   unless ( @ARGV > 1 );
my $xml_file  = shift;
my @bad_names = @ARGV;
my $output;
my $xml  = XML::LibXML->new;
my $dom  = $xml->parse_file( $xml_file );
my $root = $dom->getDocumentElement;
# Retrieve array of all users
my @users = $root->getElementsByTagName('ADD_USER');
foreach my $bad_name ( @bad_names ) {
   print "Searching for bad name: $bad_name\n";
   foreach my $user ( @users ) {
      my $username = $user->getAttribute('USER_NAME');
      if ( "$username" eq "$bad_name" ) {
         my ($addu) = $user->parentNode;
         print "Removing element for user name: ", $username, "\n";
         $addu->removeChild( $user );
         $output = $dom->toString(0);
         $outout =~ s/(?<=\n)\s*\n//g;
      }
   }
}
open( my $FH, '>', "newilo") or die "Could not open file newilo $!";
print $FH $output, "\n";
close $FH;

Last edited by bradvan; 08-18-2016 at 10:37 AM.
 
Old 08-18-2016, 05:14 PM   #12
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,269
Blog Entries: 24

Rep: Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196
I am a little late getting back in here, but this might be worth mentioning...

Using xmlstarlet (find project at sourceforge), it reduces to this...

Code:
xml ed -d "//ADD_USER[@USERNAME='Ralph Delp']" test.xml
Your example file had a couple of errors corrected to this...

Code:
<!-- HPONCFG VERSION = "4.4.0" -->
<RIBLC VERSION="2.1">
 <LOGIN USER_LOGIN="Administrator" PASSWORD="password">
  <DIR_INFO MODE="write">
  <MOD_DIR_CONFIG>
    <DIR_AUTHENTICATION_ENABLED VALUE = "N"/>
  </MOD_DIR_CONFIG>
  </DIR_INFO>
  <RIB_INFO MODE="write">
  <MOD_NETWORK_SETTINGS>
    <SPEED_AUTOSELECT VALUE = "Y"/>
    <NIC_SPEED VALUE = "10"/>
    <SEVERAL MORE NETWORK LINES/> //Removed this line
  </MOD_NETWORK_SETTINGS>
  </RIB_INFO>
  <USER_INFO MODE="write">
  <ADD_USER
   USERNAME = "Ralph Delp"
   USER_LOGIN = rpdelp //Attr value must be quoted
   PASSWORD = "%user_password%">
   <ADMIN_PRIV value = "Y"/>
   <REMOTE_CONS_PRIV value = "Y"/>
   <RESET_SERVER_PRIV value = "Y"/>
   <VIRTUAL_MEDIA_PRIV value = "Y"/>
   <CONFIG_ILO_PRIV value = "Y"/>
  </ADD_USER>
  <ADD_USER
   USERNAME = "Root User"
   USER_LOGIN = "root"
   PASSWORD = "%user_password%">
   <ADMIN_PRIV value = "Y"/>
   <REMOTE_CONS_PRIV value = "Y"/>
   <RESET_SERVER_PRIV value = "Y"/>
   <VIRTUAL_MEDIA_PRIV value = "Y"/>
   <CONFIG_ILO_PRIV value = "Y"/>
  </ADD_USER>
 </USER_INFO>
 </LOGIN>
</RIBLC> /// L-C transposed
With those changes to make it valid XML and saved as test.xml the result is...

Code:
xml ed -d "//ADD_USER[@USERNAME='Ralph Delp']" test.xml

<?xml version="1.0"?>
<!-- HPONCFG VERSION = "4.4.0" -->
<RIBLC VERSION="2.1">
  <LOGIN USER_LOGIN="Administrator" PASSWORD="password">
    <DIR_INFO MODE="write">
      <MOD_DIR_CONFIG>
        <DIR_AUTHENTICATION_ENABLED VALUE="N"/>
      </MOD_DIR_CONFIG>
    </DIR_INFO>
    <RIB_INFO MODE="write">
      <MOD_NETWORK_SETTINGS>
        <SPEED_AUTOSELECT VALUE="Y"/>
        <NIC_SPEED VALUE="10"/>
      </MOD_NETWORK_SETTINGS>
    </RIB_INFO>
    <USER_INFO MODE="write">
      <ADD_USER USERNAME="Root User" USER_LOGIN="root" PASSWORD="%user_password%">
        <ADMIN_PRIV value="Y"/>
        <REMOTE_CONS_PRIV value="Y"/>
        <RESET_SERVER_PRIV value="Y"/>
        <VIRTUAL_MEDIA_PRIV value="Y"/>
        <CONFIG_ILO_PRIV value="Y"/>
      </ADD_USER>
    </USER_INFO>
  </LOGIN>
</RIBLC>
It is a great little tool for doing XML searches and edits - kind of like sed for xml!
 
1 members found this post helpful.
Old 08-19-2016, 06:24 AM   #13
bradvan
Member
 
Registered: Mar 2009
Posts: 367

Original Poster
Rep: Reputation: 61
Thanks for that. One problem is that it is not installed on any of my servers. I did find it in the EPEL repository and was trying to work with it, but I was getting a little confused by what I was finding on the Internet versus what I found in the rpm. In my rpm there are several files under /usr/share/doc, a man page and one binary, /usr/bin/xmlstarlet. All of the examples I've seen and even the one you gave show using the command 'xml.' I'm a little confused. Shouldn't it be xmlstartlet ed -d ...?
 
Old 08-19-2016, 11:16 AM   #14
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,269
Blog Entries: 24

Rep: Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196Reputation: 4196
I recall being a little confused when I first started using it, but I made myself notes on the basic operations I use so I have not had to look back at the docs for a while.

I would suggest that you review or grab a local copy of the online documentation from here - it is much more complete than the man page and has some really good examples of all operations.

From the top link on that page you will find the XmlStarlet Command Line XML Toolkit User's Guide which will get you directly to working examples from the indexed list of common operations.

If you are not familiar with XPath notation then you might want to do a little searching for that as well - XPath is the notation used to navigate your XML documents and specify internal elements. For example, the expression I used in my last post was...

Code:
xml ed -d "//ADD_USER[@USERNAME='Ralph Delp']" test.xml
... which means "//"= any path to an element named "ADD_USER" which has an attribute named "USERNAME" with a value of "Ralph Delp". Learning the basics of XPath is easy but essential.

And yes, the command is "xml", not xmlstarlet.

If you need a little more help please post back here, it is well worth learning your way around it and I and others will be glad to help!
 
1 members found this post helpful.
Old 08-19-2016, 12:30 PM   #15
bradvan
Member
 
Registered: Mar 2009
Posts: 367

Original Poster
Rep: Reputation: 61
Thanks! Will continue to research.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] Sed print only the line that exists two lines after the match netpumber Linux - Newbie 5 12-03-2014 06:46 PM
sed match and ignore new lines ted_chou12 Programming 5 02-26-2012 11:33 AM
[SOLVED] sed match html content (multiple lines) ted_chou12 Linux - Newbie 5 12-08-2011 01:25 AM
How to use sed to delete all lines before the first match of a pattern? C_Blade Linux - Newbie 9 05-01-2010 04:18 AM
sed match last x lines of a file bradvan Programming 12 03-19-2009 11:18 PM

LinuxQuestions.org > Forums > Linux Forums > Linux - General

All times are GMT -5. The time now is 08:11 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration