_port_caching_policy:12: bad math expression: operator expected
Today, I was doing a MacPorts self update, and wanted to see what was outdated. However, I got an interesting error when hitting tab to show auto-completion options:
> port list <tab>
_port_caching_policy:12: bad math expression: operator expected at `16777234\ni...'
Today, there’s a single hit on google for “_port_caching_policy”, and it’s the function on github. Which, fair. It’s not especially interesting, just a very basic comparison between file modification times to see if the cache should be updated or not.
stat -f%m . > /dev/null 2>&1
if [ "$?" = 0 ]; then
stat_cmd=(stat -f%Z)
else
stat_cmd=(stat --format=%Z)
fi
_port_caching_policy() {
local reg_time comp_time check_file
case "${1##*/}" in
PORT_INSTALLED_PACKAGES)
check_file=$port_prefix/var/macports/registry/registry.db
;;
PORT_AVAILABLE_PACKAGES)
check_file=${$(port dir MacPorts)%/*/*}/PortIndex
;;
esac
reg_time=$($stat_cmd $check_file)
comp_time=$($stat_cmd $1)
return $(( reg_time < comp_time ))
}
⌨️ ZSH completions
I was pretty impressed to find docs on debugging completions, and at various points used all three shortcuts( alt-2 ctrl-x h
, ctrl-x h
, and ctrl-x ?
).
It was quickly evident that stat
was returning the full details, but the code was expecting a single number from each execution:
stat '--format=%Z' /opt/local/var/macports/sources/rsync.macports.org/release/tarballs/ports/PortIndex
reg_time=$'device 16777234\ninode 19150376\nmode 33188\nnlink 1\nuid 0\ngid 0\nrdev 0\nsize 21393774\natime 1689623078\nmtime 1689614240\nctime 1689623082\nblksize 4096\nblocks 41792\nlink '
stat '--format=%Z' /Users/daniel/.cache/zsh4humans/v4/cache/zcompcache-5.9/PORT_AVAILABLE_PACKAGES
comp_time=$'device 16777234\ninode 16062342\nmode 33188\nnlink 1\nuid 501\ngid 20\nrdev 0\nsize 590845\natime 1689287896\nmtime 1689273088\nctime 1689273088\nblksize 4096\nblocks 1160\nlink '
_port_caching_policy:12: bad math expression: operator expected at `16777234\ni...'
📝 stat vs stat vs zsh/stat
Ok, so the behavior of stat
has changed. Maybe old code that needs to be updated for Ventura? Except that the code already handles the BSD-flavored stat, as well as the coreutils version.
And the stat
command in my terminal doesn’t behave like either of those, because … it’s the zsh/stat builtin module, with output like this:
device 16777234
inode 1129680
mode 16877
nlink 7
uid 501
gid 20
rdev 0
size 224
atime 1689639115
mtime 1689639115
ctime 1689639115
blksize 4096
blocks 0
link
Prominent in the documentation:
The same command is provided with two names; as the name stat is often used by an external command it is recommended that only the zstat form of the command is used. This can be arranged by loading the module with the command ‘zmodload -F zsh/stat b:zstat’.
🕰️ zstat +mtime
I was partway through a PR to add a third case, preferring to use zstat
if it’s loaded. It makes sense to me that using the shell builtin would be preferable, but I don’t know how common it is to have it loaded. So I don’t think it can completely replace the if / else that determines stat_cmd
. And any theoretical performance win from an in-process syscall (vs executing the separate binary) is going to be invisible against cost of reading the (currently) 580 KB cache file.
if (( $+builtins[zstat] )); then
stat_cmd=(zstat +mtime)
else
# existing bsd vs coreutils switch
fi
I’d written and tested a change, and was working on the rationale for the commit message.
🔎 Who loaded zsh/stat
so that it shadows stat
?
I was fairly late to switch to zsh, and when I finally did, zsh4humans v4 had a compelling sales pitch:
A turnkey configuration for Z shell that aims to work really well out of the box. It combines the best Zsh plugins into a coherent whole that feels like a finished product rather than a DIY starter kit. If you want a great shell that just works, this project is for you.
I wasn’t interested in the SSH-based features, and turned them off. I made some basic changes to the config, and it’s been working great for me. So much so, that I never switched to the v5 branch, and was disappointed to read the author has moved onto other things. I certainly understand though, since it looked like many “Issues” raised ended up with him effectively volunteering his time to help folks debug their shell configurations.
So when I tracked down the zmodload zsh/stat
in main.sh and then found it was fixed in v5 almost two years ago, it felt like this whole journey was self-imposed.
There were many spots where zsh/stat
was loaded as recommended, so that it only adds the zstat
builtin. If zsh
has a debugging feature for showing where a module is loaded, I never found it. Instead it was looking through the various config files, and using a multi-file grep, which was hindered by the fact this specific zmodload
command used globbing features to load several modules at once, and it wasn’t a direct textual match for zsh/stat
.
Anyway, if your code is calling stat
with -f
or --format
, and you’re unexpectedly getting all the fields, you might be inadvertently using zstat
.
I guess it’s possible that someone, someday, will also have zsh/stat
fully loaded, and the completion script will break on the same line. If so, maybe it’s worth filing an issue? Until then, it feels like a misconfiguration of my environment, and not worth handling in this obscure location.