I'm working with firewalld XML file that looking like:
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Public</short>
<description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"></service>
<service name="dhcpv6-client"></service>
<service name="cockpit"></service>
<service name="https"></service>
<service name="http"></service>
<port port="5000" protocol="tcp"></port>
<port port="5001" protocol="tcp"></port>
<port port="9999" protocol="tcp"></port>
<port port="9998" protocol="tcp"></port>
<rule>
<protocol value="vrrp"></protocol>
<accept></accept>
</rule>
</zone>
I want to add several new TCP ports into it, sort them and ensure they are unique
kislyuk/yq provides the xq
command which can be used to manipulate XML files using jq
syntax.
For this case, just append another object (wrapped into an array) to the existing array, and apply unique
to it (which also sorts the items). Note that the port numbers are processed as strings, so in order to have them sorted numerically, convert them for the comparisons using tonumber
. Finally, the -x
flag will tranform the result back into XML.
xq -x '.zone.port |= (
. + [{"@port": "993", "@protocol": "tcp"}]
| unique_by(."@port" | tonumber)
)' file.xml
<zone>
<short>Public</short>
<description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"></service>
<service name="dhcpv6-client"></service>
<service name="cockpit"></service>
<service name="https"></service>
<service name="http"></service>
<port port="993" protocol="tcp"></port>
<port port="5000" protocol="tcp"></port>
<port port="5001" protocol="tcp"></port>
<port port="9998" protocol="tcp"></port>
<port port="9999" protocol="tcp"></port>
<rule>
<protocol value="vrrp"></protocol>
<accept></accept>
</rule>
</zone>
If you want to add multiple entries at once, consider using --args
to read them in as command-line arguments (available since jq 1.6):
xq -x '.zone.port |= (
. + [$ARGS.positional | _nwise(2) | {"@port": first, "@protocol": last}]
| unique_by(."@port" | tonumber)
)' file.xml --args 6543 tcp 210 udp
<zone>
<short>Public</short>
<description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"></service>
<service name="dhcpv6-client"></service>
<service name="cockpit"></service>
<service name="https"></service>
<service name="http"></service>
<port port="210" protocol="udp"></port>
<port port="5000" protocol="tcp"></port>
<port port="5001" protocol="tcp"></port>
<port port="6543" protocol="tcp"></port>
<port port="9998" protocol="tcp"></port>
<port port="9999" protocol="tcp"></port>
<rule>
<protocol value="vrrp"></protocol>
<accept></accept>
</rule>
</zone>