Introducing Sherry
The shell is an awesome abstraction, it allows you to chain together wildly disparate programs into a single script. A shell scripting language is, in some ways, the ancient precursor to LLM agents; that asks:
What if a program could interact with a computer in the same way a user does, just faster and automated?
1 The Problem
The problem is that bash and zsh, the
de facto standard shells have appalling syntax and are generally a
pain to use. I mean… just look at this mess:
set -euo pipefail
for f in /var/log/*.log; do
base="${f##*/}"
clean="${base//-/_}"
if [[ "$clean" =~ ^(sys|kern)_.+\.log$ ]]; then
case "${clean%%_*}" in
sys) priority="high" ;;
kern) priority="critical" ;;
*) priority="low" ;;
esac
if [[ -s "$f" ]]; then
echo "[$priority] ${clean%.log} (${f})"
else
echo "[skip] $clean is empty"
fi
fi
donefi? esac? "${f##*/}"?
Yes, you can learn how to be productive like this, but
none of the rest of your team will understand what the hell your
script is meant to do, and none of them will be able to
confidently update it.
Shell languages are GREAT when you want to invoke a series of programs one after the other, but if you try to do anything more complicated than that it quickly becomes untenable. They are, ironically, not good languages for scripting.
2 JavaScript
JavaScript is a real language1 It’s much clearer to read, it has vital features like JSON parsing built-in, and a package system for anything that isn’t covered in the language. But one of the things Js kinda sucks as is subprocess spawning. You can do it, but it never feels quite right.
That’s, hopefully, where sherry comes in:
import $ from "@codeenplace/sherry";
const currentGitHash = await $("git", "rev-parse", "HEAD");
for await (const commitInfo of $("git", "log")) {
console.log({ commitInfo });
}
const $_PROD = $({ env: { NODE_ENV: "production" } });
await $_PROD("npm", "run", "build");
await $("ls", "|", "rev");
await $("npm", "run", "build", {
"--setting-one": true,
"--setting-two": false,
"--setting-three": "fooBarBaz",
});2.1 Straightforward to Use
Sherry aims to meet you where you need it.
Want to run a command and get the output? Easy:
const currentGitHash = await $("git", "rev-parse", "HEAD");Want instead to stream the output and iterate over it? But of course:
for await (const commitInfo of $("git", "log")) {
console.log({ commitInfo });
}Need to set an EnvVar, or capture stderr instead? Easy:
await $({
env: { FOO: "bar" },
outputs: "stderr",
})("some", "command", "here");It also supports handy syntaxes inspired by classnames, and exotic arguments like Promises and iterators:
await $(
"echo",
{ "--foo": "bar" }, // key, val style,
{ "not-included": false }, // optional style
Promise.resolve("baz"),
function* () {
yield "qux";
},
);2.2 Familiar
sherry uses a shell under the hood, so you can
still access env vars, pipe between processes and into files:
await $("echo", "$SHELL");
await $("echo", "foo", "|", "cat");
await $("echo", "foo", ">", "/tmp/file");2.3 Powerful
We’re in JavaScript world now, it’s so much easier to parse and manipulate data, and know what you’re doing:
const data = JSON.parse(
await $(
"some-command",
{ "--format": "json" },
),
);
if (data.ok) {
await $("do-publish");
} else {
await $("sudo", "shutdown", "-h", "now");
}3 Have a Go
I’m using it, I think it’s good, it’s on npm & github. I think if you try it out you’ll be surprised; Not at how good sherry is, but at how bad bash scripting has always been.
hur durr, “
left-pad”, “wrote it in 10 days”, “[] == ''”. Yes, JavaScript has plenty of problems; it’s not 2019 any more, get a grip or close the tab.↩︎