If this wants to sell itself as a shell scripting language, it should very quickly advertise what is it that makes it superior to say, bash for typical shell scripting tasks.
Shell scripts with bash are painful to the point that if I find myself writing more than around 10 lines of shell, I tend to stop and switch to Perl instead. But Perl hasn't been too popular lately and isn't ideal either, so I'm very much up for something better.
Here's some features I want from a shell scripting language:
* 100% reliable argument passing. That is, when I run `system("git", "clone", $url);` in Perl, I know with exact precision what arguments Git is going to get, and that no matter what weirdness $url contains, it'll be passed down as a single argument. Heck, make that mandatory.
* 100% reliable file iteration. I want to do a "for each file in this directory" in a manner that doesn't ever run into trouble with spaces, newlines or unusual characters.
* No length limits. If I'm processing 10K files, I don't want to run into the problem that the command line is too long.
* Excellent path parsing. Such as filename, basename, canonicalization, finding the file extension and "find the relative path between A and B".
* Good error handling and reporting
* Easy capture of stdout and stderr, at the same time. Either together or individually, as needed.
* Excellent process management. We're in 2022, FFS. We have 128 core CPUs. A modern shell scripting language should make it trivial to do something like: take these 50000 files, and feed them all through imagemagick, using every core available, while being able to report progress, record each failure, and abort the entire thing if needed.
* Excellent error reporting. I don't want things failing with "Command failed, aborted". I want things to fail with "Command 'git checkout https://....' exited with return code 3, and here's for good measure the stdout and stderr even if I redirected them somewhere".
* Give me helpers for common situations. Eg, "Recurse through this directory, while ignoring .git and vim backup files". Read this file into an array, splitting by newline, in a single line of code. It's tiresome to implement that kind of thing in every script I write. At the very least it should be simple and comfortable.
That's the kind of thing I care about for shell scripting. A better syntax is nice, but actually getting stuff done without having to work around gotchas and issues is what I'm looking for.
> 100% reliable argument passing. That is, when I run `system("git", "clone", $url);` in Perl, I know with exact precision what arguments Git is going to get, and that no matter what weirdness $url contains, it'll be passed down as a single argument. Heck, make that mandatory.
Variables are parsed as tokens so they're passed to the parameters whole, regardless of whether they contain white space or not. So you can still use the Bash terseness of parameters (eg `git clone $url`) but $url works in the same way as `system("git", "clone", $url);` in Perl.
> 100% reliable file iteration. I want to do a "for each file in this directory" in a manner that doesn't ever run into trouble with spaces, newlines or unusual characters.
Murex is inspired by Perl in that $ is a scalar and @ is an array. So if you use `f +f` builtin (to return a list of files), it returns as a JSON array. From there you can use @ to expand that array with each value being a new parameter, eg
rm -v @{ f +f } # delete all files
or use $ to pass the entire array as a JSON string. Eg echo ${ f +f }
# you could just run `f +f` without `echo` to get the list.
# This is just a contrived example of passing the array as a string.
Additionally there are lots of tools that are natively aware of arrays and will operate on them. Much like how `sed`, `grep`, `sort` et al treat documents as lists, murex will support lists as JSON arrays. Or arrays in YAML, TOML, S-expressions, etc.So you can have code that looks like this:
f +f | foreach file { echo $file }
> No length limits. If I'm processing 10K files, I don't want to run into the problem that the command line is too long.This is a kernel limit. I don't think there is any way to overcome this without using iteration instead (and suffering the performance impact form that).
> Excellent path parsing. Such as filename, basename, canonicalization, finding the file extension and "find the relative path between A and B".
There are a number of ways to query files and paths in murex:
- f: This returns files based on meta data. So will pull files, or directories, or symlinks, etc depending on the flags you pass. eg `+f` would include files. `+d` would be include directories. or `-s` would exclude symlinks.
- g: Globbing. Basically `*` and `?`. This can run as a function rather than being auto-expanded. eg `g *.txt` or `rm -v @{ g *.txt }`
- rx: Like globbing but using regexp. eg `rx '\.txt$'` or `rm -v @{ rx '\.txt$' }` - this example looks terrible but using rx does sometimes come in handy if you have more complex patterns than a standard glob could support. eg `rm -v @{rx '\.(txt|rtf|md|doc|docx)$'}`
The interactive shell also have an fzf like integration built in. So you can hit ctrl+f and then type a regexp pattern to filter the results. This means if you need to navigate through complex source tree (for example) to a specific file (eg ./src/modules/example/main.c) you could just type `vi ^fex.*main` and you'd automatically filter to that result. Or even just type `main` and only see the files in that tree with `main` in their name.
> Good error handling and reporting / Excellent error reporting. I don't want things failing with "Command failed, aborted". I want things to fail with "Command 'git checkout https://....' exited with return code 3, and here's for good measure the stdout and stderr even if I redirected them somewhere".
A lot of work here.
- `try` / `trypipe` blocks supported
- `if` and `while` blocks check the exit code. So you can do
if { which foobar} else {
err "foobar does not exist!"
}
# 'then' and 'else' are optional keywords for readability in scripting. So a one liner could read:
# !if {which foobar} {err foobar does not exist!}
- built in support for unit tests- built in support for watches (IDE feature where you can watch the state of a variable)
- unset variables error by default
- empty arrays will error by default when passed as parameters in the next release (hopefully coming this week)
- STDERR is highlighted red by default (can be disabled if that's not to your tastes) so you can clearly see any errors if they're muddled inside STDOUT.
- non zero exit numbers automatically raise an error. All errors return a line and cell number so you can find the exact source of the error in any scripts
Plus lots of other stuff that's referenced in the documents
> Easy capture of stdout and stderr, at the same time. Either together or individually, as needed.
Murex handles named pipes a little differently, they're passed as parameters inside triangle brackets, <>. STDERR is referenced with a proceeding exclamation mark. eg a normal command will appear internally as:
command1 <!err> parameter1 parameter2 etc | command2 <!err> parameter1 parameter2 etc
(You don't need to specify nor <!err> for normal operation).So if you want to send STDERR off somewhere for later processing you could create a new named pipe. eg
pipe example # creates a new pipe called "example"
command1 <!example> parameter1 parameter2 | command2 <!null> parameter1 parameter2
This would say "capture the STDERR of the first command1 and send it to a new pipe, but dump the STDERR of command2".You can then later query the named pipe:
| grep "error message"
> Give me helpers for common situations. Eg, "Recurse through this directory, while ignoring .git and vim backup files". Read this file into an array, splitting by newline, in a single line of code. It's tiresome to implement that kind of thing in every script I write. At the very least it should be simple and comfortable.This is where the typed pipelines of murex come into their own. It's like using `jq` but builtin the shell itself and works transparently with multiple different document types. There's also an extensive library of builtins for common problems, eg `jsplit` will read STDIN and output an array split based on a regexp pattern. So your example would be:
cat file | jsplit \n
> That's the kind of thing I care about for shell scripting. A better syntax is nice, but actually getting stuff done without having to work around gotchas and issues is what I'm looking for.I completely agree. I expect this shell I've created to be pretty niche and not to everyone's tastes. But I'd written it because I wanted to be a more productive sysadmin ~10 years ago and since I've moved into DevOps I've found it invaluable. It's been my primary shell for around 5 years and every time I run into a situation where I'm like "I wish I could do this easily" I just add it. The fact that it's a typed pipeline makes it really easy to add context aware features, like SQL query support against CSV files.