The parse yaml utility which I used in my shell script produces variables as below by eval command

policy1_name='ipfilter'
policy1_scope='api'
policy1_apiname='apiname'
policy1_inboundsession='rate-limit-by-key calls="25" renewal-period="60"
counter-key="@\(Regex.Match\(context.xxx.xxxx.GetValueOrDefault\("X-xxxxxxx-For",""\), @"^[.0-9]*"\)?.Value\)'
policy1_outboundsession_ipAddressesFrom='1xxxxxxxx'
policy1_outboundsession_ipAddressesTo='1yyyyyyy'
policy1_=' policy1_name policy1_scope policy1_apiname policy1_inboundsession policy1_outboundsession'
policy1_outboundsession_=' policy1_outboundsession_ipAddressesFrom policy1_outboundsession_ipAddressesTo'
policy_=' policy1'

I know, I can use xmlstarlet to create element inside my outputpolicy.xml file as given below.

for g in $(eval echo \$${f}_apiname); do
     echo " this policy will be applied to apis,$(eval echo \$${f}_apiname)"
    done
      if [ -z "$(eval echo \$${f}_inboundsession_)" ]; then
        echo 'the inbound session is not present'
      else
        echo 'the inbound session is present and append the policy settings to inbound'
        xmlstarlet ed -O  -s '//inbound' -t elem -n rate-limit-by-key  -i '//inbound/rate-limit-by-key' -t attr -n calls -v xx -i '//inbound/rate-limit-by-key' -t attr -n renewal-period -v yy -i '//inbound/rate-limit-by-key' -t attr -n counter-key -v '@(Regex.Match(context.x.y.GetValueOrDefault("xxxxxxx",""), @"^[.x-y]*")?.Value)' policy.xml > io_ou_ipfilter.xml

but what I am looking is use xmlstarlet in my script to read the output of the above script variable "$(eval echo $${f}inboundsession) and insert the given string under inbound session of the below policy.xml file if the string is not preset.

Expected Output

<policies>
    <inbound>
      <base />
      <rate-limit-by-key calls="xxx" renewal-period="xx" counter-key="@(Regex.Match(context.x.y.GetValueOrDefault("xxxxxxx",""), @"^[.x-y]*")?.Value)" />
    </inbound>
    <backend>
      <base />
    </backend>
    <outbound>
      <base />
    </outbound>
    <on-error>
      <base />
    </on-error>
</policies>
1

There are 1 best solutions below

0
urznow On

Here's how you could add the rate-limit-by-key node, or not if it exists.

# shellcheck shell=sh disable=SC2016

xmlstarlet edit -O \
  -a '/policies/inbound/base[not(following-sibling::rate-limit-by-key)]' \
    -t elem -n 'rate-limit-by-key' \
  --var lim '$prev' \
  -s '$lim' -t attr -n 'calls' -v 'xx' \
  -s '$lim' -t attr -n 'renewal-period' -v 'yy' \
  -s '$lim' -t attr -n 'counter-key' \
    -v '@(Regex.Match(context.x.y.GetValueOrDefault("xxxxxxx",""), @"^[.x-y]*")?.Value)' \
"${infile:-file.xml}"

where:

  • -a … (aka --append) adds a following-sibling node, it executes only if its argument matches an existing node and so the XPath predicate (not(following-sibling::rate-limit-by-key)) specifies an add-if-not-exists condition
  • if -a … matches nothing then $prev [] will match nothing and this will nullify the following $prev-dependent -s … (aka --subnode) options
  • the -v (aka --value) options here use string literals but they can be changed to pull in a shell variable (-v "${somevar}") or the result of a command substitution ( -v "$(somecmd)")

[] In an xmlstarlet edit command --var defines a named variable, and the back reference $prev variable (aka $xstar:prev) refers to the node(s) created by the most recent -s, -i, or -a option which all define or redefine it (see xmlstarlet.txt for a few examples of --var and $prev).


Note that the expected output you posted is not XML as it contains quote characters in an attribute value which an XML serializer will likely output as &quot;.

Given this input file,

<policies>
  <inbound>
    <base/>
  </inbound>
</policies>

the command above produces,

<policies>
  <inbound>
    <base/>
    <rate-limit-by-key calls="xx" renewal-period="yy" counter-key="@(Regex.Match(context.x.y.GetValueOrDefault(&quot;xxxxxxx&quot;,&quot;&quot;), @&quot;^[.x-y]*&quot;)?.Value)"/>
  </inbound>
</policies>