hshell¶
A bash competitor that lets you write shell-like scripts in Kotlin, with many other useful features that bash doesn't provide.
Warning
This site provides docs for an unreleased product! PROCEED WITH CAUTION AND LOW EXPECTATIONS.
Additionally, these docs are incomplete and do not reflect the full API available. They're intended as a brief guide to be read in combination with in-house API docs and IDE auto completion.
Let's get started¶
| demo.shell.kts | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Run hshell demo.shell.kts or chmod +x demo.shell.kts; ./demo.shell.kts.

List directories and print tables¶
Print the current directory contents as colored ls style output, and a table:
1 2 3 4 5 6 7 8 9 10 11 12 | |

Find the path to the script, and its containing directory:
1 2 | |
Shell operations¶
For mv, cp, find etc see the Shell API. For wget see downloading things.
Other string utilities¶
1 | |

File ops¶
See shell API.
Dependencies¶
Brace expansion in coordinates works:
1 2 3 4 5 6 | |
XML and JSON¶
Parsing¶
The xml function takes a string and tries to parse it as a URL, or a file path, or as literal XML (in that order) and returns a DOM
Document. The json function does the same but returns a JsonElement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
They can also be given a Path or URI object.
Navigating¶
There is a path extension function on JsonElement and JsonObject that support a simple navigation language:
1 2 3 4 | |
Keys are separated by dots, and array indexing is supported. If the path isn't found then null is returned. The result can be cast
using jsonObject, jsonArray or jsonPrimitive as per usual when working with Kotlin serialization.
Document has an extension function xpath that evaluates the given XPath expression and returns the result assuming it's a string.
Cleaning up¶
Instead of writing foo.use { foo -> foo.whatever() } to clean things up, you can write foo.closeAtEnd().whatever():
1 | |
The closeAtEnd() function uses defer to register a code block to run when the script finishes:
1 2 | |
The last block deferred is run first.
Use exit(exitCode) to terminate early. Don't call exitProcess unless you're sure you want immediate termination without any finally
or deferred blocks running.
Disk caching¶
A LocalDiskCache will give you a directory associated with an arbitrary string key, passing it to a handler to fill with content if
the key misses in the cache:
1 2 3 4 5 | |
The cache will automatically keep its size in check, is thread safe and can be configured in various ways. You can also force cache misses and edit content in place.
Subshells and concurrency¶
Shell commands can be modified by global state, to avoid long repetitive argument lists (copyOptions, diffOptions etc). subshell
yields a new shell with a copy of all the current shell's state, which can then be used safely from another thread.
1 2 3 4 5 6 7 8 | |
A better way is like this:
1 2 3 | |
If the path is a file this function just invokes operation on it and returns. Otherwise, it walks over the given directory and executes operation on each entry in parallel on a thread pool, blocking whilst the entire operation completes.
The operation is invoked for all entries of a directory before the directory itself, meaning you can delete directories, sum their sizes etc. Or in other words the operation is invoked deepest first and path comes last.
The symLinkFollowing mode is respected. If it's set to SymlinkFollowing.FOLLOW_EXTERNAL or SymlinkFollowing.ALWAYS_FOLLOW that means
you may be passed paths that are not children of path, or be passed the same paths more than once, so be ready for that.
WARNING: Circular symlinks aren't currently detected.
Sockets, mmapping files etc¶
hshell doesn't provide anything specific for this, but the whole Java API is available so you can simply use that instead.
Logging things¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
On the console:

When running hshell --show-log:

Logging how long a code block took is easy, and you can give a time threshold below which no log line is made:
1 2 3 4 | |
Credential storage¶
The passwords object supports get/set operators accessible using square brackets notation (passwords["foo"] = "bar"). This provides
simple access to the system keychain. It's not really secure because the credentials will be stored under the identity of hshell itself
not the script, but in future this might be improved and it's a convenient place to store passwords. On macOS the user will be prompted to
allow access and the credential will be encrypted using the hardware security chip.