Example:- <item href="cover.jpeg" id="cover" media-type="image/jpeg"/>

I'd like to select @id="cover" and media-type="image/*" and retrieve @href.

My current "solution" seems to be:- xmlstarlet sel -t -m "//_:item[@id='$opf_cover_name']" -v @href -o '|' -v @media-type -n file.xml, followed by some extremely messy splitting around the '|' character of the output.

Does xmlstarlet have a substring function?
Version = 1.6.1
compiled against libxml2 2.9.4, linked with 20904
compiled against libxslt 1.1.29, linked with 10132

2 Answers 2


xmlstarlet sel -t \
    --var queryid="'$the_id'" \
    --var typeprefix="'$the_mediatype_prefix'" \
    -v '//item[@id = $queryid and starts-with(@media-type, $typeprefix)]/@href' \
    -nl file.xml

This queries the XML file using two shell variables. The the_id variable contains the id attribute value that we are filtering with, while the_mediatype_prefix contains the string that the media-type attribute has to start with.

We create two internal variables for xmlstarlet using the tool's --var option. The values need to be encoded values, which is why I insert single quotes around them (this is a bit of a hack, ideally they should be properly encoded XPath strings, but it's marginally better than injecting shell variables directly into the expression).

The XPath expression select the href attribute of each item node that has the matching id and media-type.

Using xq (an XML parser wrapper around jq, the JSON parser) instead:


xq -r \
    --arg queryid "$the_id" \
    --arg typeprefix "$the_mediatype_prefix" '
    .. | .item? |
        ."@id" == $queryid and
        (."@media-type" | startswith($typeprefix))
    )."@href"' file.xml

This is more or less identical to the xmlstarlet code, apart from using a jq expression instead of a XPath query. Here, the utility takes care of encoding your shell variables properly, even if they contain wonky values like quotes etc. (the shell variable values in the xmlstarlet example would need to be encoded manually if they contain illegal character sequences).

  • +1 I'd not previously come across the --var parameters; thank you for teaching me that Nov 30, 2021 at 12:33

You can add multiple conditions:

xmlstarlet sel -t -m "//item[@id='cover'][starts-with(@media-type,'image/')]" -v '@href' -nl file.xml
  • 1
    @JeremyBoden your question said you wanted to match against a fixed id and media-type. If you wanted to match against a variable you'd pop a variable in the expression, ensuring that the variable value was suitable mangled so as not to contain an unescaped single quote Nov 29, 2021 at 17:06
  • My problem was a variable id (which was OK) and a media-type which is a mime-type such as 'image/jpeg' or 'image/png' etc. I wasn't aware how to handle an and condition or a 'starts-with' function. Your solution is short , and it works too! Thanks. Nov 29, 2021 at 17:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .