Dealing with xpath changes in Big Sur

In one of the recent betas for macOS 11.0 Big Sur, the xpath command line tool changed. Big Sur uses the 5.28 version of the tool, while Catalina defaults to the 5.18 version.

These aren’t version numbers for xpath but actually version numbers for perl. When you look at all the xpath executables you will see that they are actually perl scripts.

> head -n1 /usr/bin/xpath*
==> /usr/bin/xpath <==
#!/usr/bin/perl

==> /usr/bin/xpath5.18 <==
#!/usr/bin/perl5.18 -w

==> /usr/bin/xpath5.28 <==
#!/usr/bin/perl5.28

The perl environment will choose the xpath script ending in the version number matching the perl version automatically. The plain xpath script with no version number serves as fallback.

Either way, Catalina runs perl 5.18 and xpath5.18 and Big Sur runs perl 5.28 and the newer xpath5.28 script.

The problem here is that the newer xpath script has a different syntax:

[5.18] xpath [filename] query
[5.28] xpath [options] -e query [-e query...] [filename...]

Either version will use stdin when there is no file name, but the newer xpath requires the query string to be labeled with a -e argument.

So, your scripts that are using the xpath tool to parse XML data, will fail in Big Sur!

The easiest fix is to change the script to use the new syntax, i.e. insert the -e at the right place and if necessary re-arrange the arguments. But then the script will fail on older versions of macOS. Many of us will have to write our scripts to be able to support the latest and older versions of macOS for a transition time. For some MacAdmins the transition time can be several years.

Since Big Sur still includes xpath5.18, another solution is to just hardcode the version whenever you use xpath. But this will only defer the problem to a future version of macOS, when the 5.18 version of the script is removed.

A better solution is to check the version of macOS that is being used and to call xpath with the proper for each version:

    if [[ $(sw_vers -buildVersion) > "20A" ]]; then
        xpath -e "//query" "/path/to/file"
    else
        xpath "/path/to/file" "//query" 
    fi

This will call xpath with the new syntax on Big Sur (20A) and higher and use the older syntax otherwise. (Why I use the build version.)

If you are using xpath in multiple locations in a script, using this code everywhere will become tedious. In the Installomator script, we often use xpath to parse the download URL out of an XML file. As of now, there are nine occurences of xpath, but as more people contribute to Installomator, this is likely to go up.

There is a nice sleight-of-hand trick you can use to make the script resilient to change in Big Sur, without having to touch every use of xpath in the code.

At the beginning of the script (before the first use of xpath) insert this function:

xpath() {
    # the xpath tool changes in Big Sur 
    if [[ $(sw_vers -buildVersion) > "20A" ]]; then
        /usr/bin/xpath -e "$@"
    else
        /usr/bin/xpath "$@"
    fi
}

Since the shell interpreter (bash or zsh) will prefer a local function over an external executable, this function will now be used for all uses of xpath in the remaining code. We don’t need to touch them at all.

Within the function, we tell the interpreter to use the executable by using its full path.

In Installomator, we pipe data into xpath, so there are no files involved and the above works fine.

When you use xpath with files, it gets a bit more complicated, because the order of the arguments changed between the versions.

With the assumption that the current use is xpath [filename] query we can use the following:

xpath() { # [filename] query
    # xpath in Big Sur changes syntax
    if [[ -n $2 ]]; then
        local query=$2
        local filename=$1
    else 
        local query=$1
        local filename=""
    fi

    if [[ $(sw_vers -buildVersion) > "20A" ]]; then
        /usr/bin/xpath -e "$query" "$filename"
    else
        /usr/bin/xpath "$filename" "$query"
    fi
}

You will want to make a note for some point in the future, when you finally can drop Catalina support, to revisit these scripts and clean them up for the new syntax.

I have not yet encountered other command line tools that change in a similar way in Big Sur. If you do, you should be able to use a similar function to simplify the transtition.

When you do, let us know in the comments!

Published by

ab

Mac Admin, Consultant, and Author

9 thoughts on “Dealing with xpath changes in Big Sur”

  1. Yeeesh. Luckily I only have one script borrowed from somebody else that uses XPath in a Jamf EA script. When it comes to XML munging I usually reach for Python.

    I did a bunch of XML and XPath stuff for my Jamf 400 exam to stretch myself.

  2. Great find, Armin!
    Fortunately for me I’ve been always using the –xpath option within xmllint binary in my scripts, that appears to be unaffected (might be worth the blog post update).

  3. Thanks, Armin.

    The following Jamf Pro-specific SQL queries may prove helpful:

    Scripts:
    SELECT script_id,file_name FROM scripts WHERE script_contents LIKE ‘%xpath%’;

    Extension Attributes:
    SELECT extension_attribute_id,display_name FROM extension_attributes WHERE script_contents_mac LIKE ‘%xpath%’;

  4. This is extremely helpful. One interesting thing I found was xpath also seems to be “better” in Big Sur.

    So in a self service policy for Jamf, I have a script that shows the LAPS password from the JSS.

    I had to use your fix to fix xpath, but also had to adjust awk because xpath was being more specific.

    #Fix for Big Sur changes in xpath — Should really fix
    if [[ $(sw_vers -buildVersion) > “20A” ]]; then
    lapsPass=$(echo $xml | awk ‘{print $1}’)
    else
    lapsPass=$(echo $xml | awk ‘{print $7}’)
    fi

    I’m really not clear WHY this is the case, but haven’t dug in too much.

    query for $xml is just:

    xml=$(curl -s -u $apiUser:$apiPass -H “Accept: application/xml” $jssURL/JSSResource/computers/udid/$udid/subset/extension_attributes | xpath “//*[id=$EAID]/value/text()” 2>&1)

    No huge deal. I really should change to using xmllint for this, but your fix was easier. =)

    Thank you!

  5. Hey,
    Thanks for this!

    For some reason i’m getting this error:
    Script exit code: 1
    Script result: Found 1 nodes in stdin:
    — NODE —
    Current Machine ID: 4534
    LAPS Current Pass:
    LAPS New Pass: ****************
    ERROR: LAPS Password Not Updated

    What can it be?

    1. I guess I should be flattered that you think I can determine what is wrong with your script from the distance, without seeing the actual script and not knowing anything about the context. Alas, I cannot.

      What I do see is ‘LAPS,’ so I guess this has to do with macOS LAPS, which I have heard of, but have never used and no experience with. I’d recommend asking for feedback on their GitHub site or (better) in the #macoslaps channel on the MacAdmins Slack.

      1. Hey,
        Sorry for this, I was referred here from the macOS-LAPS repository, so I forgot to mention the context 🙂

        Thank you for your reply, I will check there.

    2. Hi @Yehiam,

      Looks like it’s not pulling the value. I would start with a local copy of the script and just try with the xpath piece and echo out the XML to see what it dumps out.

      Would also make sure, if you’re just copying and pasting my example, make sure it’s not pasting in fancy quotes (and fancy doublequotes), which breaks scripts.

      And yeah, this is laps, but it’s not joshua-d-miller’s in my case, it’s the simple script based one by kc9wwh that I’ve forked and have working pretty well for about 1000 macs at this point.

  6. Hey,
    Sorry for this, I was referred here from the MacOs-LAPS repository so I was thinking that the context are clear

    Thank for your replay, I will check there.

Comments are closed.