I’ve been using eXist-db for some years, mainly as the backbone of LeXmart, a tool for lexicographers, and that has been used for different projects, and the most prominent of which is Dicionário da Língua Portuguesa.

eXist-db is a powerful native XML database with XQuery support, widely used in digital humanities, publishing, and other applications where documents are at the center. It has a rich REST API, a web-based admin console, and IDE plugins — but if you live in the terminal, there has never been a good way to interact with it from the command line. Well, I lie. There is a tool in JavaScript, xst, whose goal is similar to exsh, the tool I am sharing in this post. But by the time I found out about it I was already in the middle of the development of mine, and I hate JavaScript. Sorry!

Managing an eXist-db instance from scripts meant either writing custom curl calls, using the admin console in a browser, or using a library, and writing my code in a programming language, like Python. None of these compose well with standard Unix tooling, and some are too complex for day-to-day tasks.

exsh treats eXist-db collections and documents the way a Unix shell treats directories and files. The command names are familiar on purpose: ls, cat, put, rm, mkdir, cp, mv, edit. If you know how to use a terminal, you already know most of exsh.

Getting started

Well, to use exsh, you need Python, and the easiest way to install it, is using uv. It it not yet available in PyPI because I am unable to authenticate, and waiting for feedback from the PyPI support team for two weeks. For now, you need to install it from the GitHub repository:

Bash
uv tool install git+https://github.com/ambs/exist-shell
Bash

If everything goes well, the command exsh will be available in your shell. To start using it, you need to configure an eXist-db server, and then, add a collection. While it can be useful to access files independently of configuring collections, this was a design decision, to simplify the interaction and command lines.

To register a server and a collection use the following syntax:

Bash
exsh server add localhost --port 8080 --user admin
exsh collection add mydata@localhost
Bash

That’s it. mydata is now a shorthand for /db/mydata on your local server.

Day-to-day usage

Browse and read documents just like a filesystem. Either refer to the collection only, or add the path inside the collection, as you can see in lines 2 and 3 in the next block:

Bash
exsh ls mydata
exsh ls mydata:reports/2025
exsh cat mydata:reports/2025/summary.xml
Bash

You can upload, copy or delete files. You can even copy files across different collections in different servers:

Bash
exsh put report.xml mydata:reports/2025/report.xml
exsh cp mydata:reports/2025/report.xml ./local-copy.xml
exsh rm mydata:reports/2025/old.xml
Bash

If you need to edit a file, and do not want to use an IDE with an eXist-db plugin, you can use the edit command. It will open the document your $EDITORexsh downloads it, opens the editor, and re-uploads only if you saved changes:

Bash
exsh edit mydata:reports/2025/summary.xml
Bash

While not exactly a common shell command, you can even execute XQuery, and thus, pipe the XQuery results straight to the terminal:

Bash
echo 'count(collection("/db/mydata"))' | exsh exec mydata:/
exsh exec mydata:/ -f analytics.xq
Bash

Sync

The sync command transfers an entire folder tree between a local directory and a remote collection. It only transfers files that have changed, using SHA-256 hashes (push) or last_modified timestamps (pull). Direction is inferred from argument order:

Bash
# push local → remote
exsh sync ./reports mydata:reports

# pull remote → local
exsh sync mydata:reports ./reports

# preview without transferring
exsh sync --dry-run ./reports mydata:reports

# also remove files that no longer exist on the source
exsh sync --delete ./reports mydata:reports
Bash

Interrupted syncs resume near where they left off thanks to a local manifest flushed every N files (configurable with --checkpoint-every).

User and permission management

exsh can also manage users, groups, and POSIX-style permissions on the server:

Bash
exsh user ls @localhost
exsh user add alice@localhost
exsh group add editors@localhost

exsh chown alice:editors mydata:reports/annual.xml
exsh chmod 0644 mydata:reports/annual.xml
exsh chmod -R u+x mydata:scripts
Bash

What’s next

There are several features in the pipeline:

  • exsh find — search documents by XPath or content across a collection tree
  • exsh diff — compare a local file against its remote version
  • Batch operations — apply a command to the results of a query
  • Watch mode — automatically sync when local files change
  • Multiple output formats — JSON and TSV for ls and exec output, for easier piping into jq or spreadsheets

Contributions welcome

exsh is open source and actively developed. If you use eXist-db and have a workflow that exsh doesn’t cover yet, I’d love to hear about it — open an issue or send a pull request at github.com/ambs/exist-shell.

Bug reports, feature requests, and documentation improvements are all welcome.


This blog post was bootstrapped using an AI tool.

Leave a Reply