Elvish – An experimental Unix shell in Go
github.com
github.com
I'm on the go now, and will come back to add more details and try to respond to questions here.
You said you had read about powershell, would be interesting to know if you plan on doing something with datastructures as well as that is its power feature :)
- Focus on the lispy parts of sh - prefix functions everywhere and a simple, regular syntax. Not that anything with FD redirections as a primitive can get anywhere near S-expressions' simplicity, but shell is naturally more regular than most programming languages (even if bash goes out of its way to be complex).
- Be a real programming language that doesn't make you reach for awk or perl (separate, incompatible environments) to do moderately complex things sanely.
- Emphasize using pipelines rather than 'backwards' function application to naturally string together operations.
- Emphasize lambdas.
- Typed (i.e. non-string) pipes.
- Syntax highlighting.
- Nonzero-exit modeled as exceptions. (I think elvish is doing this, but I haven't reviewed the code in detail.)
Things I want but don't see in the readme include:
- A story for passing typed data around between processes (potentially in different languages) rather than just builtins. I'm not sure exactly what the story should be, but it should exist.
- A somewhat more succinct syntax, with metaprogramming kept in mind.
- A JIT, eventually.
There seems to be a lot more in the first category than the second... give up? But elvish isn't anywhere near done, and I want to do things all my own way for once. I'll keep going, and post my project on HN if it gets anywhere. :)
But to the OP, congratulations on elvish. Hope it gets finished and seriously takes off.
Their REPLs are more powerful than UNIX shell, you get the whole OS at your disposal using the same language.
The REPLs are graphical, similar to the ideas being brought back by IPython.
Combining objects or functions on the REPLs is Zen compared to the simple UNIX pipes.
No really. I have not yet decided how error/exception (http://www.haskell.org/haskellwiki/Error_vs._Exception) handling should work.
I actually like how golang does it - errors with panic/recover, exceptions with return values. But I have not been able to make a decision yet - the situation is a bit more complex than golang. As a result there is no if/else builtin yet, let alone raise and try/except (or panic and recover).
I haven't thought much about FFI yet. But since in shells you interact with other programs so much, adding FFI would a worthy attempt.
> - A somewhat more succinct syntax, with metaprogramming kept in mind.
Coming up with syntax is actually the hardest thing I find when writing Elvish, since there is much more syntax constraints for a shell. Would be happy to hear advices.
> - A JIT, eventually.
Maybe later.
http://en.wikipedia.org/wiki/Common_Object_Request_Broker_Ar...
By these guys?
Whatever happened to that?
http://google.com/search?q=xml-rpc
Still being used some.
Does there exist anything like 'type protocols' across languages? Would it be feasible?
- Grep the output of ps, without having to count column numbers or do special work to preserve the header line after filtering.
- Search for files matching some conditions, without remembering the weird and unique syntax of find(1) - instead you would write something vaguely along the lines of
ls | filt {< $1.size 500} {eq $1.type file}
where the stuff after the pipe is the shell language, so theoretically more memorable than find -size -500c -type f -maxdepth 1
As a bonus, if you want to cache the output for multiple searches rather than going through the the kernel every time
(OS X find is rather slow on huge source trees), you can save the output of ls to a file and keep the rest the same. Good luck doing that with find.- Handle filenames with spaces and newlines without issues. Simple, but newlines at least are near impossible to solve in the standard shell environment - only a few select tools support null-separated lists. Spaces are easier... unless you want such extravagances as multiple filename columns in a table.
Of course it's not that simple: structured data can sometimes be hard to work with and formatting for display is an issue; Unix's "everything should be plain text" philosophy didn't come out of nowhere.
But I honestly believe that it's time to reexamine that tenet.
> parser directory/file.lang | interpreter
Does this make sense? Would it have some utility?
First, it's universal. Python and Perl and Java all have huge ecosystems, but one of the few places where programs written in all those languages can come together is the shell. By "real programming language" I don't mean some monstrosity with its own packages and whatnot. I just mean a simple principled language to tie things together that doesn't feel like a Turing tarpit.
Second, its highest priority is efficient interactive (REPL) use (although, as a "real language", it should be much better than bash for scripts as well). Some languages have good REPLs, but most don't, and there are important design considerations for them.
For example, if you're banging out a one-liner where you have some data and make a series of transformations on it - thinking through each step in turn - you don't want to have to write functions in reverse order of application, third(second(first(data))). With pipelines, it's forward, first | second | third. In a batch program (in both imperative and functional languages, though more often in imperative), you might avoid this by performing each step as a separate statement, assigning each result to a descriptive variable. But interactively, who has time for variable names? (The elvish author makes a similar argument in the readme.) Yet few languages support this outside of OOP dot syntax, which only works in a subset of cases.
Author here; I use it regularly to replace bash scripts in system glue code -- using golang channels to pipe data between shell commands is awesome. It's heavily (heavily!) inspired by amoffat's "sh" library [2] which lets one call any shell program as if it were a function.
But, Gosh exists to scratch some itches as a shell scripting alternative. It's nowhere near the full-fledged interactive shell environment Elvish is gunning for. Elvish looks to have a very exciting future :)
[1] https://github.com/polydawn/pogo/blob/master/gosh-demo.go
I do take issue with one of the things you wrote though:
> a more complex program is formed by concatenating simpler programs, hence the term "concatenative programming". Compare this to the functional approach, where constructs are nested instead of connected one after another.
There's nothing about the functional approach that necessitates writing "nested" functions, as you describe. With higher-order functions, you can structure your code in almost any arbitrary way. In particular, haskell's >>= operator has this behavior, and you can easily write an operator like
x |> f = f x
to facilitate something like 2 |> addOne |> timesTwo |> show |> reverse |> putStrLn
Or whatever one desires :)But I have always doubted that if the language doesn't explicitly endorse a particular paradigm, you will find it pretty awkward to work with other people. Which is why less expressive languages that emphasize particular ways of doing things still make sense.
(-> 2
addOne
timesTwo
str
reverse
(apply str)
println)Which was possible already in Lisp and Smalltalk based OS.
Combining functions raises the experience to another level. :)
The decision actually arises from avoiding using both single and double quotes for quoting (http://prog21.dadgum.com/172.html), so I just copied golang's string literal syntax.
On a side note, I have to say I love README's that look like this. The author did a couple things I really like. 1) They attributed feature ideas to the people they got them from. 2) They listed the good things right along with the bad/lacking. 3) Screenshots.
As it's in development, I will upgrade it again in a couple days.
Best of luck to xiaq, a fellow fish shell contributor. It's definitely good to see more innovation in the ossified command-line shell space.
Assuming this is planning on using Go's concurrency support, it will be very interesting to see how it deals with the nasty interactions between fork and multithreading.
For now syscall.ForkExec (http://godoc.org/syscall#ForkExec) is sufficient for me. It is written to avoid async-unsafe calls between fork and exec, which is also what fish does IIRC.
Note: I am not familiar with the implementation techniques behind shells.
It's kind of roundabout, but the brilliance of this approach lies in what happens between those two calls. There exists process metadata that survives the call to exec, such as where stdout goes, or whether the process is in the foreground. So shells call fork, the clone sets up the metadata for the target process, and then calls exec to start it.
But when a multithreaded program forks, the clone is very limited in what it can do (before exec). In particular, the clone must not acquire a lock that may have been held at the time of fork (which usually rules out heap allocations!). Now say something goes wrong: the clone needs to print an error message, without locking anything. But lots of functions acquire locks internally. How do you know what's safe to call?
fish solves this by providing its own known-safe implementations of printf() and friends, and being careful to only call those after fork. Go solves this by disallowing any user-code between fork and exec. Instead it provides a single posix_spawn-like entry point called ForkExec, and does some black magic (like raw syscalls - see https://code.google.com/p/go/source/browse/src/pkg/syscall/e... ) in between the underlying fork and exec calls.
My hunch is that a shell written in Go will eventually bump up against the limitations of ForkExec. Happily Go has a strong FFI, so you can hopefully implement this stuff in C, if it comes to that!
I'm aware that one can install support for proper autocompletion by installing additional stuff. That shouldn't be required, smart autocompletion should work out of the box. I don't want to configure and/or install stuff for every single application that I use.
[1] http://bash-completion.alioth.debian.org/
[2] https://github.com/oinksoft/dotfiles/tree/master/lib/bash-co...
[3] https://github.com/oinksoft/dotfiles/blob/master/bashrc#L22-...
put 1 2 3 4 5 | filter {|x| > $x 2} | map {|x| * 2 $x}
could be written just as put 1 2 3 4 5 | filter > $_ 2 | map * 2 $_Would the presence of $_ denote a lambda? how would nested lambda's work?
Why not:
put 1 2 3 4 5 | filter -> > $1 2 | map -> * 2 $1
I think that would be easier to parse, and not much typing overhead, also $_ is a weird thing. cat <<eof\
| while read a;do if [[ $a -gt 2 ]];then echo $a;fi; done \
|while read b;do echo $((b*2));done
1
2
3
4
5
eof
(Better expressed with seq): seq 5 \
| while read a;do if [[ $a -gt 2 ]];then echo $a;fi; done \
| while read b;do echo $((b*2));done
But we can do (a little) better: # Ok, this is a bit odd, the filter is actually inverse...
filter()
{
while read it
do
#NOT SAFE!!
if eval ${*}
then
echo "${it}"
fi
done
}
map()
{
while read it
do
#NOT SAFE!!
eval ${*}
echo "${it}"
done
}
#Valid bash:
seq 5 \
| filter test '$it' -gt 2 \
| map 'it=$((it*2))'
Sorry, didn't mean to turn this into stack overflow, the "simple" syntax
just tickled me...> put 1 2 3 4 5 | filter > $it 2 | map * 2 $it
I really wonder how computing would look like if those systems had succeed in the market, instead of UNIX.
$ go get github.com/xiaq/elvish
# github.com/xiaq/elvish/edit/tty
dev/go/src/github.com/xiaq/elvish/edit/tty/termios.go:20: undefined: syscall.TCGETS
dev/go/src/github.com/xiaq/elvish/edit/tty/termios.go:24: undefined: syscall.TCSETS
dev/go/src/github.com/xiaq/elvish/edit/tty/termios.go:49: cannot use &term.Lflag (type *uint64) as type *uint32 in argument to setFlag
dev/go/src/github.com/xiaq/elvish/edit/tty/termios.go:53: cannot use &term.Lflag (type *uint64) as type *uint32 in argument to setFlag
# github.com/xiaq/elvish/sys
dev/go/src/github.com/xiaq/elvish/sys/select.go:66: not enough arguments to return
I opened up an issue for you: https://github.com/xiaq/elvish/issues/16 $ go version
go version go1.3 darwin/amd64EDIT: Specifically, the first two lines are complaining about an undefined syscall and the next two are complaining about something terminal-related being 64-bit where 32-bit is expected, so those certainly seem like plausible things that would differ across OSes.
http://golang.org/src/pkg/syscall/types_linux.go
-but not for Darwin-
http://golang.org/src/pkg/syscall/types_darwin.go
So it seems somewhat Linux tied.
(def lols (->> strs
(filter #(re-find #"LOL" %))
(map upper-case)
sort))
How I read it: take strs, filter the strings that contain "LOL", turn them into upper case and then sort them. Basically it reads the same as the pipeline example in the README.[1] http://fishshell.com/ [2] http://xiki.org/ [3] https://www.kickstarter.com/projects/xiki/xiki-the-command-r...
Looking forward to what's in store.
CL-USER 7 > + (sin 1) (cos 2)
0.4253241clang: error: no such file or directory: 'libgcc.a'
> / 1 0
+Inf
Is not the correct answer - even as a limit - since the limit of 1 / x (when x -> 0) can be +Inf or -Inf. > / 1 -0
-Inf
BTW this is just IEEE floating point.[1] http://en.wikipedia.org/wiki/IEEE_floating_point#Exception_h...
Wasn't that the reasoning behind csh[1]? (She scripts csh by the c source). Did you have a look at plan 9's rc[2]?
I welcome attempts at new/better shells -- doesn't look like elvish will be my saviour (partly due to the heavy use of backticks) -- but there is always need for fresh blood in the battle for the terminal.
I suppose that "vi keybindings that makes sense" and "a programmable line editor" is meant to indicate that rlwrap isn't good enough? I must say, after a few years (has it really been years) of "set editing-mode vi" in .inputrc, I actually think it works kind of nice with (plain) bash. I suppose there's room for improvement in terms of history editing etc... But either due to lack of imagination or force of habit, I've never really felt a pull towards zsh (or fish, or other "improved" shells). But playing around with ipython and/or Conque for vim has made me consider looking for greener shells than bash.
The secret, I think, is to avoid doing to much in the shell, and rather try to subtly improve on the "simple programs build complex pipes"-idea. I actually think some rethinking of core command line tools (cat/tac/tee, grep/sort/uniq, seq etc) might be a better investment than "better syntax".
Not that better syntax is a bad idea -- a "strict" subset of modern bash would be good, with saner handling of words/expansion/substitution -- essentially defaulting to proper checking for empty variables ("${might_be_empty}a" == "a") (but isn't [[ -z "${var} ]] always better anyway..?), always defaulting to "${var}" rather than $var, ${var}, and preferring var="$(some_command_that_outputs)" to var=`dirty backtick command`...
Essentially getting rid of all the crazy old cruft that's needed for backwards compatibility, and defaulting to sane, modern versions (it's what's hard about scripting (especially posix [k|b])sh -- there are 5 wrong ways to do everything, 2 mostly right and 1 perfect -- but which is perfect often depends on context...).
[1] I'm not a csh-fan, for some reasons, see: http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/ But mostly I'm just grumpy and conservative, and having mostly figured out how to properly get things right in ksh/bash, I stubbornly refuse to use something else ;-)
If you want a programming language, you know where to find one.
Really?
It seems that Unix pipes are the go-to example of what you might call pipeline programming, as if it originated there or because every programmer is a shell-programmer first and foremost. But I'm not sure that this is such a secret technique exclusive to shell programmers - object-oriented languages can and does seem to like to use "fluent interfaces", I think its called, which has the same pipelining style. Functional programmers are able to and probably find it convenient to use a "pipeline style" on longer expressions that are essentially long chains of function application or function composition - they just have to flip the order of the operators for function application and function composition, respectively. This is possible in languages like Haskell, and I think it is even pretty idiomatic in F#.