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!

3 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%’;

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.