{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreie2jc5vovthggufv6ej2o5va6povk67ptktp3jwkhvxwgg3hb47tu",
    "uri": "at://did:plc:46ti67tc37qcmwp2vaynk6fq/app.bsky.feed.post/3mhocfe2yomt2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiges63cfpgrdjjux7quqbosrbo4rgdgz3dvxf6g73cnop7cxkn7me"
    },
    "mimeType": "image/jpeg",
    "size": 69825
  },
  "path": "/en/blog/2026-zsh-calculator",
  "publishedAt": "2026-03-22T19:40:19.898Z",
  "site": "https://vincent.bernat.ch",
  "tags": [
    "1",
    "The equal alias",
    "The quoting problem",
    "Automatic quoting with ZLE",
    "Storing unquoted history",
    "Numbat",
    "Qalculate",
    "2",
    "(q-) parameter expansion\nflag",
    "Bart Schaefer",
    "zshrc",
    "noglob",
    "auto-expanding aliases",
    "↩︎"
  ],
  "textContent": "I often need a quick calculation or a unit conversion. Rather than reaching for a separate tool, a few lines of _Zsh_ configuration turn `=` into a calculator. Typing `= 660km / (2/3)c * 2 -> ms` gives me `6.60457 ms`1 without leaving my terminal, thanks to the Zsh line editor.\n\n  * The equal alias\n  * The quoting problem\n    * Automatic quoting with ZLE\n    * Storing unquoted history\n\n\n\n# The equal alias\n\nThe main idea looks simple: define `=` as an alias to a calculator command. I prefer Numbat, a scientific calculator that supports unit conversions. Qalculate is a close second.2 If neither is available, we fall back to Zsh’s built-in _zcalc_ module.\n\nAs the `alias` built-in uses `=` as a separator for name and value, we need to alter the `aliases` associative array:\n\n\n    if (( $+commands[numbat] )); then\n      aliases[=]='numbat -e'\n    elif (( $+commands[qalc] )); then\n      aliases[=]='qalc'\n    else\n      autoload -Uz zcalc\n      aliases[=]='zcalc -f -e'\n    fi\n\n\nWith this in place, `= 847/11` becomes `numbat -e 847/11`.\n\n# The quoting problem\n\nThe first problem surfaces quickly. Typing `= 5 * 3` fails: Zsh expands the `*` character as a glob pattern before passing it to the calculator. The same issue applies to other characters that Zsh treats specially, such as `>` or `|`. You must quote the expression:\n\n\n    $ = '5 * 3'\n    15\n\n\nWe fix this by hooking into the Zsh line editor to **quote the expression** before executing it.\n\n## Automatic quoting with ZLE\n\nZsh calls the `accept-line` widget when you submit a command. We replace it with a function that detects the `=` prefix and quotes the expression:\n\n\n    _vbe_calc_accept() {\n      case $BUFFER in\n        \"=\"*)\n          typeset -g _vbe_calc_expr=$BUFFER # not used yet\n          BUFFER=\"= ${(q-)${${BUFFER#=}# }}\"\n          ;;\n      esac\n      zle .accept-line\n    }\n    zle -N accept-line _vbe_calc_accept\n\n\nWhen you type `= 5 * 3` and press `↲`, `_vbe_calc_accept` strips the `=` prefix, quotes the remainder with the (q-) parameter expansion\nflag, and rewrites the buffer to `= '5 * 3'` before invoking the original `.accept-line` widget. As a bonus, you can save a few keystrokes with `=5*3`! 🚀\n\nYou can now compute math expressions and convert units directly from your shell. Zsh automatically quotes your expressions:\n\n\n    $ = '1 + 2'\n    3\n    $ = 'pi/3 + pi |> cos'\n    -0.5\n    $ = '17 USD -> EUR'\n    14.7122 €\n    $ = '180*500mg -> g'\n    90 g\n    $ = '5 gigabytes / (2 minutes + 17 seconds) -> megabits/s'\n    291.971 Mbit/s\n    $ = 'now() -> tz(\"Asia/Tokyo\")'\n    2026-03-22 22:00:03 JST (UTC +09), Asia/Tokyo\n    $ = '1 / (40 rods / hogshead) -> L / 100km'\n    118548 × 0.01 l/km\n\n\nThe metric system is the tool of the devil! My car gets forty rods to the hogshead, and that's the way I like it! ― _Grampa Simpson_ , A Star Is Burns\n\n## Storing unquoted history\n\nAs is, Zsh records the _quoted_ expression in history. You must unquote it before submitting it again. Otherwise, the ZLE widget quotes it a second time. Bart Schaefer provided a solution to store the original version:\n\n\n    _vbe_calc_history() {\n      return ${+_vbe_calc_expr}\n    }\n    add-zsh-hook zshaddhistory _vbe_calc_history\n\n    _vbe_calc_preexec() {\n      (( ${+_vbe_calc_expr} )) && print -s $_vbe_calc_expr\n      unset _vbe_calc_expr\n      return 0\n    }\n    add-zsh-hook preexec _vbe_calc_preexec\n\n\nThe `zshaddhistory` hook returns 1 if we are evaluating an expression, telling _Zsh_ not to record the command. The `preexec` hook then adds the original, unquoted command with `print -s`.\n\n* * *\n\nThe complete code is available in my zshrc. A common alternative is the noglob precommand modifier. If you stick with `to` instead of `->` for unit conversion, it covers 90% of use cases. For a related Zsh line editor trick, see how I use auto-expanding aliases to fix common typos.\n\n* * *\n\n  1. This is the fastest a packet can travel back and forth between Paris and Marseille over optical fiber. ↩︎\n\n  2. Qalculate is less understanding with units. For example, it parses “Mbps” as megabarn per picosecond: ☢️\n\n         $ numbat -e '5 MB/s -> Mbps'\n         40 Mbps\n         $ qalc 5 MB/s to Mbps\n         5 megabytes/second = 0.000005 B/ps\n\n\n↩︎\n\n\n\n  *[ZLE]: Zsh Line Editor",
  "title": "Vincent Bernat: Calculate “1/(40rods/​hogshead) → L/100km” from your Zsh prompt",
  "updatedAt": "2026-03-22T13:37:09.000Z"
}