DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 48: Elvish

Elvish is a recent attempt at creating a better shell.

I briefly tried it as a shell, and I instantly hated it, but that's possibly because default settings need adjusting. Anyway, complaints from the first few minutes of interactive use:

  • Ctrl-A and Ctrl-E don't work
  • typing name of a directory doesn't go to that directory (autocd mode)
  • tab completion wouldn't work from any part of the name like cd elvish<TAB> wouldn't expand to cd episode-48-elvish, it only works with start of the name
  • there's no smart autocompletion at all, like git <TAB> has no idea what git even is
  • documentation for customizing the shell is nonexistent
  • it was also quite unclear where the hell it even keeps the data on OSX (apparently ~/.local/state/elvish/db.bolt for history file, ~/.config/elvish/rc.elv for its RC file)
  • history file is some kind of binary in nonstandard format (not even SQLite), which is really inconvenient
  • it doesn't support alias etc., so I cannot easily setup RC file by just copying by .zshrc and adjusting things a bit
  • how do I even customize prompt? default is bad, and there's no documentation again
  • it doesn't seem to support redirection from commands like diff <(fizzbuzz.elv) <(fizzbuzz.py)

Anyway, I'm not here to evaluate it for interactive use. These issues are likely solvable with better documentation and maybe better presets or migration script.

Mainly I want to see how well it's doing as a programming language for shell scripts.

You can install it from brew install elvish and starting elvish from whichever shell you're using. This way it will inherit most environmental settings like $PATH, $EDITOR etc. from your normal shell.

Overall documentation is very poor. Many things have some definition but no examples, many things don't have any explanation at all. I needed to figure things out by experimenting, so I might have missed something in this episode due to that. Of course for its excuse, Elvish is not 1.0 yet, and documenting things is hard.

Hello, World!

#!/usr/bin/env elvish

echo "Hello, World!"
Enter fullscreen mode Exit fullscreen mode

We can run it from a file:

$ ./hello.elv
Hello, World!
Enter fullscreen mode Exit fullscreen mode

And of course we can run it interactively from Elvish shell:

$ echo "Hello, World!"
Hello, World!
Enter fullscreen mode Exit fullscreen mode

JSON

Elvish wants to live in a world of proper data structures, but Unix commands work with either lines of text, or just one big unstructured blob of text.

To get some interoperability going, Elvish uses to-json and from-json commands. These don't convert to and from JSON document, they convert to and from JSON streams.

$ echo '{"name": "Alice", "surname": "Smith"}' | from-json | each {|x| echo "Hello, "$x[name]"!" }
Hello, Alice!
Enter fullscreen mode Exit fullscreen mode

Weirdly there's no string interpolation in double quotes like in other shells - you need to unquote, put the expression, and resume quoting.

$ var catfacts = (curl -s 'https://cat-fact.herokuapp.com/facts' | from-json)
$ for fact $catfacts { echo $fact[text] }
Wikipedia has a recording of a cat meowing, because why not?
When cats grimace, they are usually "taste-scenting." They have an extra organ that, with some breathing control, allows the cats to taste-sense the air.
Cats make more than 100 different sounds whereas dogs make around 10.
Most cats are lactose intolerant, and milk can cause painful stomach cramps and diarrhea. It's best to forego the milk and just give your cat the standard: clean, cool drinking water.
Owning a cat can reduce the risk of stroke and heart attack by a third.
Enter fullscreen mode Exit fullscreen mode

FizzBuzz

The FizzBuzz isn't too difficult:

#!/usr/bin/env elvish

# FizzBuzz in Elvish
fn fizzbuzz {|n|
  if (== (% $n 15) 0) {
    echo "FizzBuzz"
  } elif (== (% $n 5) 0) {
    echo "Buzz"
  } elif (== (% $n 3) 0) {
    echo "Fizz"
  } else {
    echo $n
  }
}

seq 1 100 | each {|n| fizzbuzz $n}
Enter fullscreen mode Exit fullscreen mode

You probably know what FizzBuzz does by now, so let's go through the code:

  • # for comments as usual
  • seq 1 100 is a standard Unix builtin command, printing numbers 1 to 100 on separate lines
  • you can pipe it into | each, that processes things line by line normally
  • there's a Ruby-style block syntax, like {|variables| code}
  • fn name {|arguments| code} is a function definition
  • if/elif/else syntax looks totally reasonable
  • surprisingly for a shell, math uses Lisp style prefix notation. This actually makes a lot of sense, as in shell first thing is usually command and rest are arguments anyway, so Elvish just interpreted parentheses as a subcommand

Fibonacci

Let's try to write a Fibonacci function in Elvish:

#!/usr/bin/env elvish

fn fib {|a b|
  echo $a
  fib $b (+ $a $b)
}

fib 1 1
Enter fullscreen mode Exit fullscreen mode

It keeps printing forever, but that's totally fine, as it's shell, so we can just | head -n 5, right? Well...

$ ./fib.elv | head -n 5
1
1
2
3
5
Exception: reader gone
Traceback:
  fib.elv, line 4:
      echo $a
  fib.elv, line 5:
      fib $b (+ $a $b)
  fib.elv, line 5:
      fib $b (+ $a $b)
  fib.elv, line 5:
      fib $b (+ $a $b)
  fib.elv, line 5:
      fib $b (+ $a $b)
  fib.elv, line 5:
      fib $b (+ $a $b)
  fib.elv, line 5:
      fib $b (+ $a $b)
  fib.elv, line 8:
    fib 1 1
Exception: ./fib.elv exited with 2
[tty 120], line 1: ./fib.elv | head -n 5
Enter fullscreen mode Exit fullscreen mode

Well, one nice thing - full stacktrace. But what about that exception? Time for a lesson on Unix.

SIGPIPE

Did you even think how the hell | head -n 5 works anyway? If you have a command that generates a lot of output, and you pipe it into head, at some point head will go "OK, I had enough" and exit. Then the command will try to send data to a program that's no longer running, and crash. So why you can do seq 1 1000000 | head -n 5 or cat /usr/share/dict/words | head -n 5 or such?

To support such cases - and pipelines are very important for Unix systems to work - operating system sends SIGPIPE signal to the program trying to sending data. By default, and that's why all programs written in C and such work so well as Unix commands, this command just tells the program to exit on the spot.

If you want your language so be friendly with Unix utilities, this is also behavior you need. There are really only three modern languages that attempt that - Perl, Ruby, and Raku. Perl (as well as various older languages like Sed, Awk, all other shells, and so on), just don't do anything about SIGPIPE, which makes them work perfectly out of the box in this situation.

$ perl -le 'print for 1..1_000_000' | head -n 5
1
2
3
4
5
Enter fullscreen mode Exit fullscreen mode

Ruby by default catches SIGPIPE, and converts sending data to program that exit to an exception, so you cannot do that in Ruby:

$ ruby -e '(1..1_000_000).each{|n| puts n}' | head -n 5
1
2
3
4
5
Traceback (most recent call last):
    5: from -e:1:in `<main>'
    4: from -e:1:in `each'
    3: from -e:1:in `block in <main>'
    2: from -e:1:in `puts'
    1: from -e:1:in `puts'
-e:1:in `write': Broken pipe @ io_writev - <STDOUT> (Errno::EPIPE)
Enter fullscreen mode Exit fullscreen mode

On the other hand, it is recognized as a common enough use case, so you can tell Ruby to do the traditional thing with trap("PIPE", "EXIT"):

$ ruby -e 'trap("PIPE", "EXIT"); (1..1_000_000).each{|n| puts n}' | head -n 5
1
2
3
4
5
Enter fullscreen mode Exit fullscreen mode

And because very specific exception is thrown in this case Errno::EPIPE, you can also catch just that exception while letting all other exceptions happen. So what Ruby is doing is perfectly fine, the default is not very pipeline friendly but there are two very easy ways (trap("PIPE", "EXIT") or Error::EPIPE exception) to make your program pipeline friendly.

Raku also converts this to an exception, which is fine default:

$ raku -e 'say $_ for 1..1_000_000' | head -n 5
1
2
3
4
5
Failed to write bytes to filehandle: Broken pipe
  in block <unit> at -e line 1

Unhandled exception: Failed to write bytes to filehandle: Broken pipe
   at SETTING::src/core.c/Exception.pm6:568  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:<anon>)
 from gen/moar/stage2/NQPHLL.nqp:2117  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/nqp/lib/NQPHLL.moarvm:command_eval)
 from gen/moar/Compiler.nqp:109  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/lib/Perl6/Compiler.moarvm:command_eval)
 from gen/moar/stage2/NQPHLL.nqp:2036  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/nqp/lib/NQPHLL.moarvm:command_line)
 from gen/moar/rakudo.nqp:127  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/perl6.moarvm:MAIN)
 from gen/moar/rakudo.nqp:1  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/perl6.moarvm:<mainline>)
 from <unknown>:1  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/perl6.moarvm:<main>)
 from <unknown>:1  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/perl6.moarvm:<entry>)
Enter fullscreen mode Exit fullscreen mode

Unfortunately it doesn't seem to have any workarounds for this. Trapping the SIGTRAP will make it try to write the data second time after the trap, so you get a double exception instead:

$ raku -e 'signal(SIGPIPE).tap:{exit}; say $_ for 1..1_000_000' | head -n 5
1
2
3
4
5
Unhandled exception in code scheduled on thread 4
Failed to write bytes to filehandle: Broken pipe
  in block  at -e line 1

Unhandled exception: Failed to write bytes to filehandle: Broken pipe
   at <unknown>:1  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from SETTING::src/core.c/Rakudo/Internals.pm6:1788  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:exit)
 from SETTING::src/core.c/Rakudo/Internals.pm6:1796  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:exit)
 from SETTING::src/core.c/Rakudo/Internals.pm6:1794  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:exit)
 from SETTING::src/core.c/Scheduler.pm6:29  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:handle_uncaught)
 from SETTING::src/core.c/ThreadPoolScheduler.pm6:261  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
Failed to write bytes to filehandle: Broken pipe
  in block <unit> at -e line 1
  in block <unit> at -e line 1

 from SETTING::src/core.c/ThreadPoolScheduler.pm6:260  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from SETTING::src/core.c/ThreadPoolScheduler.pm6:259  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from SETTING::src/core.c/Rakudo/Internals.pm6:1788  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:exit)
 from SETTING::src/core.c/Rakudo/Internals.pm6:1795  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:exit)
 from SETTING::src/core.c/Rakudo/Internals.pm6:1794  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:exit)
 from -e:1  (<ephemeral file>:)
 from SETTING::src/core.c/Supply-factories.pm6:153  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from SETTING::src/core.c/Supply-factories.pm6:117  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from SETTING::src/core.c/Lock/Async.pm6:204  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:run-under-recursion-list)
Unhandled exception: Failed to write bytes to filehandle: Broken pipe
 from SETTING::src/core.c/Lock/Async.pm6:183  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:run-with-updated-recursion-list)
 from SETTING::src/core.c/Lock/Async.pm6:146  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:protect-or-queue-on-recursion)
 from SETTING::src/core.c/Supply-factories.pm6:117  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from SETTING::src/core.c/signals.pm6:55  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
   at SETTING::src/core.c/Exception.pm6:568  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:<anon>)
 from SETTING::src/core.c/ThreadPoolScheduler.pm6:248  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from SETTING::src/core.c/ThreadPoolScheduler.pm6:245  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from SETTING::src/core.c/ThreadPoolScheduler.pm6:242  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:run-one)
 from gen/moar/stage2/NQPHLL.nqp:2117  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/nqp/lib/NQPHLL.moarvm:command_eval)
 from SETTING::src/core.c/ThreadPoolScheduler.pm6:297  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:)
 from gen/moar/Compiler.nqp:109  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/lib/Perl6/Compiler.moarvm:command_eval)
 from SETTING::src/core.c/Thread.pm6:54  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/CORE.c.setting.moarvm:THREAD-ENTRY)
 from gen/moar/stage2/NQPHLL.nqp:2036  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/nqp/lib/NQPHLL.moarvm:command_line)
 from gen/moar/rakudo.nqp:127  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/perl6.moarvm:MAIN)
 from gen/moar/rakudo.nqp:1  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/perl6.moarvm:<mainline>)
 from <unknown>:1  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/perl6.moarvm:<main>)
 from <unknown>:1  (/usr/local/Cellar/rakudo-star/2021.04/bin/../share/perl6/runtime/perl6.moarvm:<entry>)
Enter fullscreen mode Exit fullscreen mode

If there's a way to do this, it's not in documentation, or anywhere else I checked. This looks like a use case Raku should address, probably doing something similar to what Ruby is doing.

Anyway, back to other languages. If your language really cares about Unix pipelines (that's why I singled out C, Awk, Sed, shells, Perl, Ruby, and Raku, as these do - and all except Raku work or just need a simple switch), instantly hard quitting in this case is fine. For everything else, if the language supports exceptions, it would much rather throw a specific exception, which both lets application deal with the error, and lets the langugae do all the cleanup tasks it might have scheduled.

Just instantly hard quitting when you receive a signal is not something most languages want to do. So in languages that support exceptions, which is most of them, they'd much rather throw an exception, and then do all the proper cleanup and exit properly. So it's totally reasonable for Python to do this:

$ echo -e 'for n in range(1,10000):\n  print(n)' | python3 | head -n 5
1
2
3
4
5
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
BrokenPipeError: [Errno 32] Broken pipe
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
BrokenPipeError: [Errno 32] Broken pipe
Enter fullscreen mode Exit fullscreen mode

It's not even that bad, as it's specific exception BrokenPipeError, and you can deal with it. Weirdly it's not even that bad to do that in Python:

$ echo -e 'import signal\nsignal.signal(signal.SIGPIPE, signal.SIG_DFL)\nfor n in range(1,10000):\n  print(n)' | python3 | head -n 5
1
2
3
4
5
Enter fullscreen mode Exit fullscreen mode

Anyway, somehow Go - which in many ways is a terribly designed language that obviously only got popular due to a certain evil Big Tech company pushing it - also catches SIGPIPE. Even thought it was written by computer equivalent of the Amish, and doesn't even have exceptions in this day and age! And Elvish is written in Go, never reverted this, so it also catches SIGPIPE, and so Elvish programs crash if you | head -n 5 them.

That was a long detour, but that's the story why Elvish is doing the wrong thing.

This is absolutely baffling in a shell, as of all the languages in existence, shell scripts simply have to support being piped to | head -n 5 and such! What's even the point of having Unix shell if you can't do something as simple. It looks like it was reported before, and closed without any fix. As far as I can tell, there's no way to even change that in options in Elvish the way you can in Ruby - there's no interface to operating system signals, and there are no specific exception types in Elvish, so if you wrap it in a try block, it will catch all exceptions.

And Raku should also provide some way to get exit-on-SIGPIPE like Ruby and Python do. It looks like it almost got there, it just needs one more keyword argument to signal function.

Exception handling

We can't make it autoquit, but we can catch the exception, so let's try just that:

#!/usr/bin/env elvish

fn fib {|a b|
  echo $a
  fib $b (+ $a $b)
}

try {
  fib 1 1
} except e {
  echo $e[reason] >&2
}
Enter fullscreen mode Exit fullscreen mode
$ ./fib2.elv | head -n 400 | tail -n 10
2315700212878644019141884587055058551781936851295739770295042651311250305217930509
3746881652193013001948452031301618270087167924331813432662649918207032018665867029
6062581865071657021090336618356676821869104775627553202957692569518282323883797538
9809463517264670023038788649658295091956272699959366635620342487725314342549664567
15872045382336327044129125268014971913825377475586919838578035057243596666433462105
25681508899600997067167913917673267005781650175546286474198377544968911008983126672
41553554281937324111297039185688238919607027651133206312776412602212507675416588777
67235063181538321178464953103361505925388677826679492786974790147181418684399715449
108788617463475645289761992289049744844995705477812699099751202749393926359816304226
176023680645013966468226945392411250770384383304492191886725992896575345044216019675
<unknown reader gone>
Enter fullscreen mode Exit fullscreen mode

The good things - it now works, and Elvish supports big integers by default. Bad thing - there's no way to specify what kind of exception we're catching, it's either all or nothing. If you wrap your program in a try except block, it will catch all the exceptions, not just EPIPE / BrokenPipeError / or whatever it would like to be called.

I wanted to write a few more examples, but the SIGPIPE detour took way too much time.

Should you use Elvish?

Other than completely failing at handling SIGPIPE, it's a nicer scripting language than shell. But then you know what you could do instead of looking for a nicer shell? Use a real programming language like Ruby or Python or one of so many others.

Well, people just refuse to listen to good advice to stop writing programs in shell. And given that, it makes a lot of sense for people to keep trying to create a "better shell". For that I think Elvish is doing a lot of things right, but it just isn't ready to be treated seriously. Maybe in a few years it will be a serious contender.

Code

All code examples for the series will be in this repository.

Code for the Elvish episode is available here.

Oldest comments (5)

Collapse
 
rsteube profile image
rsteube • Edited

Sad to see you had a bad first impression. I am rather amazed by it myself.

I assume you didn't spend much time with it and the documentation (this being a speedrun) so just to get some facts right (at least the ones i know):

  • Ctrl-A and Ctrl-E don't work

Just needs to be enabled: elv.sh/ref/readline-binding.html

  • typing name of a directory doesn't go to that directory (autocd mode)

No, but it has Directory History invoked by CTRL-L which provides a menu completion for every folder you've been to before.
I find this the better feature and don't miss autocd myself.
You can see this on the main page under [3].

  • tab completion wouldn't work from any part of the name like cd elvish wouldn't expand to cd episode-48-elvish, it only works with start of the name

Would't make much sense anyway. If you do this on the command line elvish is passed to the completion script as prefix of the current word and you are likely to end up with prefiltered completion candidates.
What elvish provides, and this is more consistent, is that when you do cd <TAB> you can filter them during menu completion (not limited to start of name).

  • there's no smart autocompletion at all, like git has no idea what git even is

There is. Not having shell completion scripts is a general issue of new shells. Well at least it was.
zzamboni has git completion for elvish and I got a git completer that works in pretty much every shell.

  • documentation for customizing the shell is nonexistent

Don't know what exactly you are missing, but documentation is quite extensive.
I agree it's not easy not find yourself around in the beginning (writing documentation is hard), but I find it generally pretty good.

  • it was also quite unclear where the hell it even keeps the data on OSX (apparently ~/.local/state/elvish/db.bolt for history file, ~/.config/elvish/rc.elv for its RC file)

I adheres now to XDG, which at least for us linux folks is pretty normal.

  • history file is some kind of binary in nonstandard format (not even SQLite), which is really inconvenient

That's boltdb. Pretty famous amoung Go developers. It is a fast and simple key/value database.
Quite a better fit IMO than a relational database like SQLite in this case.

  • it doesn't support alias etc., so I cannot easily setup RC file by just copying by .zshrc and adjusting things a bit

Not in the POSIX style, yes. But it does support aliases.

  • how do I even customize prompt? default is bad, and there's no documentation again

See here. Starship also supports elvish.

  • it doesn't seem to support redirection from commands like diff <(fizzbuzz.elv) <(fizzbuzz.py)

That's true, I think there was a reason against this but I don't really miss it.


There's a nice Quick Tour now showing some correspondence between Elvish and bash syntax.

Collapse
 
taw profile image
Tomasz Wegrzanowski

So I reviewed Elvish the way it comes out of the box, not what it could be customized to eventually. I'm aware that shells and editors can be turned into something totally awesome if you put enough effort into this - since every shell action can call some script, and that script can do all kinds of useful things. I think this new user perspective is more useful.
If there was some elvish-completion package I could brew install, I'd do it (Ubuntu has bash-completion), but there was nothing like that.

Elvish is currently just asking for too much from any potential users:

  • I'd need to go scour documentation for all the fixes before I even get basic shell functionality I expect working. I think in 2022, it's totally reasonable to expect Ctrl-A, Ctrl-E, git completion etc. to just work out of the box.
  • then I'd need to learn new shell syntax to manually import my .profile, even if it's just a bunch of env vars and aliases (it makes sense it wouldn't support a more complicated .profile). It should either support just enough POSIX syntax for this to work, or come with some importer script.
  • then I'd need to figure out how to make things like rbenv / nodenv / etc. working

And only then I would be able to really start using it. With a lot of common features still missing, like <(), correct sigpipe handling for | head -n 10, autocd, etc.

The "Comparison with bash" is a good start, but it doesn't cover that much. I think expanding that list would really help any people who want to give Elvish a try.

I can totally believe that Elvish will be good enough to recommend in some time if it fixes these issues, and comes with some decent preset, I'd just not recommend it today.

Collapse
 
cben profile image
Beni Cherniavsky-Paskin

I just want to mention rbenv, nodenv etc. put real executable shim scripts in $PATH, which makes them compatible with any unix caller, including non-POSIX shells 👍

In contrast some competitor tools (nvm is an example I think) mess with shell aliases/functions, and require extra effort when switching shells 👎

That may be justified in some cases — e.g. I don't see any universal way to implement direnv, it really needs per-shell hooking... It's just that "foo version manager" class of tools can be done robustly by executable shims.

Thread Thread
 
taw profile image
Tomasz Wegrzanowski

I use default zsh so in principle any could work, but in my experience rbenv/nodenv simply cause the least problems, so I use them over alternatives.

Collapse
 
rsteube profile image
rsteube

Just because I stumbled on this right now: seems there is a module providing smart matching (not just start of the name):

github.com/xiaq/edit.elv/blob/mast...