-5

I'm trying to understand AWK command. The problem that I'm trying to solve is as follow: I have XML file with structure:

<root>
...
<elem_name name='type1'>
...
<prop name='a' type="xxx" value="000"/>
<prop name='b' type="xxx" value="000"/>
<prop name='c' type="xxx" value="000"/>
...
</elem_name>
<elem_name name='type2'>
....
<prop name='a' type="xxx" value="000"/>
<prop name='b' type="xxx" value="000"/>
<prop name='c' type="xxx" value="000"/>
...
</elem_name>
...
</root>

And I have to edit value of prop 'b' under 'type1' root. As I mentioned I would like to do it with awk or sed. I'm aware of better tools to do it.

For now, I created following command, but it is not working properly.

gawk '/elem_name name="type1"/ {for(i=1; i<=4; i++) {getline;found=index($0,"a");if(found != 0){sub("value=", "test", $0)}; print > "test.xml"}} {print > "test.xml"    }' original_file.xml

Firstly, I the value is not changed after script execution, the 'name="type1" is removed from output file.

Guinea
  • 1
  • 3
  • 1
    can you use python? it will make your life MUCH easier. – balderman Sep 25 '21 at 14:51
  • 2
    [Don't Parse XML/HTML With Regex.](https://stackoverflow.com/a/1732454/3776858) I suggest to use an XML/HTML parser (xmlstarlet, xmllint ...). – Cyrus Sep 25 '21 at 14:57
  • No, I think that I can use Perl but only like oneliner to execute it from Bash script. – Guinea Sep 25 '21 at 14:57
  • 1
    @Cyrus I can use xmlint, but I have no idea how to edit xml file using that tool. – Guinea Sep 25 '21 at 14:58
  • 1
    @Guinea The python you have in your OS should be able to run the answer I have posted. – balderman Sep 25 '21 at 15:01
  • Regarding [I can use Perl but only like oneliner to execute it from Bash script.](https://stackoverflow.com/questions/69327243/edit-value-under-specific-tag-using-awk#comment122534412_69327243) - why do you care how many lines anything is if it's inside a bash script? Please [edit] your question to show the expected output so we can help you. – Ed Morton Sep 26 '21 at 15:41

4 Answers4

2

I have to edit value of prop 'b' under 'type1' root

Since I assume you have python in the OS you are running I share the below solution.

import xml.etree.ElementTree as ET

FILE_NAME = 'myxml.xml'

root = ET.parse(FILE_NAME)
ele = root.find(".//elem_name[@name='type1']")
b = ele.find(".//prop[@name='b']")
b.attrib['value'] = 'new_value_goes_here'
root.write(FILE_NAME)
balderman
  • 22,927
  • 7
  • 34
  • 52
  • Thank you, I appreciate your answer, but as I mentioned I want to avoid other tools. Main reason is that I would like to encapsulate everything in one bash file. – Guinea Sep 25 '21 at 15:10
  • your bash file can be a 1 liner like `python3 myscirpt.py myxml.xml` :-) If you have specific questions about the py script- I will be happy to answer. – balderman Sep 25 '21 at 15:14
  • Yea, but the 'myscript.py' has to be stored somewhere, that's not the solution for me ;) – Guinea Sep 25 '21 at 15:17
  • It can be part of the bash which will redirect it to a file (`myscript.py`) and call it later - isnt it? – balderman Sep 25 '21 at 15:18
  • Yeah, but how to save it to original file? Instead of getting to stdout? ET.write(original_file_name)? – Guinea Sep 25 '21 at 15:52
  • OK - so now your question is: "how should the python script save myxml.xml back to disk after modification" - is that correct? – balderman Sep 25 '21 at 15:53
  • Well, instead of parsing from string, as you have done in the example ET.fromstring(xml), i will use ET.parse(path). And after the change operation, I want to see the chagnes in the same file, that I passed as a path above. – Guinea Sep 25 '21 at 15:57
  • @Guinea Code was modified in order to support your needs. – balderman Sep 25 '21 at 15:58
1

Update attribute with xmlstarlet:

xmlstarlet edit --omit-decl \
  --update '//root/elem_name[@name="type1"]/prop[@name="b"]/@value' \
  --value "test" file.xml

Output with removed ...:

<root>
  <elem_name name="type1">
    <prop name="a" type="xxx" value="000"/>
    <prop name="b" type="xxx" value="test"/>
    <prop name="c" type="xxx" value="000"/>
  </elem_name>
  <elem_name name="type2">
    <prop name="a" type="xxx" value="000"/>
    <prop name="b" type="xxx" value="000"/>
    <prop name="c" type="xxx" value="000"/>
  </elem_name>
</root>

See: xmlstarlet edit

Cyrus
  • 84,225
  • 14
  • 89
  • 153
1

It can be done with xmllint in one-liner taking advantage of its --shell option as

 (echo 'cd //elem_name[@name="type1"]/prop[@name="b"]/@value' ; echo "set some other xxxxx value"; echo "save test-e.xml" ; echo "bye") | xmllint --shell test.xml

The commands to xmllint are intuitive, "shell" alike

Change directory to the desired node expressed as an XPath expression:
cd //elem_name[@name="type1"]/prop[@name="b"]/@value

Set element value (no quotes):
set some other xxxxx value

Save xml doc to a new file:
save test-e.xml

Result:

<?xml version="1.0"?>
<root>
  <elem_name name="type1">
    <prop name="a" type="xxx" value="000"/>
    <prop name="b" type="xxx" value="some other xxxxx value"/>
    <prop name="c" type="xxx" value="000"/>
  </elem_name>
  <elem_name name="type2">
    <prop name="a" type="xxx" value="000"/>
    <prop name="b" type="xxx" value="000"/>
    <prop name="c" type="xxx" value="000"/>
  </elem_name>
</root>

The whole node content could be changed also

xmllint --shell test.xml 

Commands at prompt

/ > cd //elem_name[@name="type1"]
elem_name > set <new>cdvfbg</new>
elem_name > cat
<elem_name name="type1">
  <new>cdvfbg</new>
</elem_name>
elem_name > save test-e.xml

To get an interactive prompt and play around any xml/html file do:

xmllint --shell test.xml

Same commands above could be executed there. Try help to get a list of possible commands.

LMC
  • 10,453
  • 2
  • 27
  • 52
0

It sounds like this is what you're trying to do, assuming your input is always formatted as shown in your example, using any awk in any shell on every Unix box:

awk -v elem='type1' -v prop='b' -v val='test' '
    $1 == "</elem_name>" {
        inElem = 0
    }
    inElem {
        if ( ($1 == "<prop") && ($2 == ("name=\047" prop "\047")) ) {
            match($0,/value="[^"]*"/)
            $0 = substr($0,1,RSTART+6) val substr($0,RSTART+RLENGTH-1)
        }
    }
    $1 == "<elem_name" {
        inElem = ( $2 == ("name=\047" elem "\047>") )
    }
    { print }
' file
<root>
...
<elem_name name='type1'>
...
<prop name='a' type="xxx" value="000"/>
<prop name='b' type="xxx" value="test"/>
<prop name='c' type="xxx" value="000"/>
...
</elem_name>
<elem_name name='type2'>
....
<prop name='a' type="xxx" value="000"/>
<prop name='b' type="xxx" value="000"/>
<prop name='c' type="xxx" value="000"/>
...
</elem_name>
...
</root>
Ed Morton
  • 188,023
  • 17
  • 78
  • 185