bash is a programming language like any other, and you could use anything with a REPL as your shell. Python should do. In fact, I'll try it right now..
Yes, it works. Just sudo chsh -s /usr/bin/python <username> and off you go.
Once you start doing this for a bit, you'll notice that the Python REPL is an incredibly poor UI for repeated execution of subprocesses. It is very elaborate. Having to constantly wrap your strings in quotes, calling subprocess modules, exec, painstaking handing of streams, etc.
Then you start looking for a language that has better syntax for calling external programs.. hmm...
Bash. Or zsh, or ksh, etc. These languages excel at it. But that's all they are: programming languages that happen to be super easy to use when it comes to starting external programs.
This is why it makes little sense to bind them to the OS. As far as the OS is concerned: there is no Bash. Just like there is no Python. There is just syscalls.
Python REPL, even with recent additions of TAB completion, is a poor REPL, period.
IPython, on the other hand, offers a much better programming environment than shell while still allowing easy access to most of the things you mention. Example:
In [1]: from pathlib import Path
In [2]: file = Path("~/.sbclrc").expanduser().read_text()
In [3]: !echo "$file" | awk '!/^;;/' # EDIT: to clarify, this shows passing
# Python variable to a shell pipeline.
#-quicklisp
(let ((quicklisp-init (merge-pathnames quicklisp/setup.lisp
(user-homedir-pathname))))
(when (probe-file quicklisp-init)
(load quicklisp-init)))
All things considered, between !, %, %% and /, IPython is a decent shell. I was using it for a few years as my main shell, actually - its QT Console implementation specifically. I was working on Windows back then, PowerShell didn't exist yet, and `cmd.exe` was... well, exactly the same as today.TLDR: a shell is just a REPL with a few conveniences for specific tasks. Re-implement these and suddenly any REPL becomes a workable shell.
That's Bash (or at least sh). Which excels at piping the output of programs into other programs, and pay that cost for everything else.
It's actually the same philosophy that shells use: in bash you frequently invoke sed, awk, grep, etc. because it's easier than writing the same directly in the shell. In IPython, you invoke bash to do piping, because it's easier than connecting a host of subprocesses' stdins/stdouts in Python.
So again, there are some things you expect from your shell, and as long as you get those things (no matter how), you can use any REPL as a shell without problems.
[1] because of syntax or lack of library or something like that
This 2013 thread shaped a lot of the way I think about Unix: https://news.ycombinator.com/item?id=6530180
My personal take on the book is that the kernel was meant to be a portable virtual machine, extensible through processes, and that those would be the building blocks of user applications for which the shell would act as glue.
In other words, the shell and the OS are separate because most of what we commonly call "the OS" may be interpreted as mere encapsulation of less versatile hardware architectures.
Syscalls, in general, are used in lieu of objects or other abstractions because they more accurate mirror what the underlying hardware is doing. This isn't always the case, some syscalls are maintained for POSIX-compatibility and add a lot of complexity to emulate behavior that is no longer reflective of the hardware.
At the end of the day, you'll find that it's very difficult to maintain the highest levels of performance while also presenting an API that has a high level of abstraction. Things like dynamically-resizable lists, using hash tables for everything, runtime metaprogramming, and other such niceties of modern HLLs aren't free from a performance perspective.
If you really want to know more, I would suggest reading one of McKusick books on operating system design (the most recent being The Design and Implementation of the FreeBSD Operating System 2/e, but even the older ones are still largely relevant).
Maintaining this "useless POSIX compatibility trap" has a certain amount of utility; I for one like not having to re-write all of my programs every few years. I imagine others feel the same.
In closing, some projects that are pushing the boundaries of OS design which you may want to check out include:
* Redox OS (https://www.redox-os.org/) - a UNIX-like OS done from scratch in Rust
* OpenBSD (https://www.openbsd.org/) - one of the old-school Unices, written in plain old C, but with some modern security tricks up it's sleeves
* Helen OS (http://www.helenos.org/) - a new microkernel OS written from scratch in C++, Helen OS is not UNIX-like
* DragonFlyBSD (https://www.dragonflybsd.org/) - a FreeBSD fork focused on file systems research
* Haiku (https://www.haiku-os.org/) - binary and source compatible with BeOS, mostly written in C++, but also has a POSIX compatibility layer
I was on the fence about including ReactOS but wound up not including that either.
What should one be using instead in the current scenario?
Python, Rust, Golang?
If the tasks performed by your program can be expressed maintainably and performantly in shell, go for it.
For programs one size up, something like Perl or Ruby or any other language that allows easy execution of shell commands can be a great solution.
Of course, this all depends on any number of variables. Team competencies, target environments, etc. And with some effort even larger bash scripts can be kept maintainable-ish, and in some target environments (can't always count on other languages being available!) shell scripts might be the only available programming environment.
Exactly. Unfortunately.
Unless we have a solution that is significantly better than POSIX/UNIX, switching to anything else incurs a significant cost that no one is willing to pay.
Short of that, we probably need a technology breakthrough that brings in a complete architectural change.
The problem is that it doesn't even suffice for the new thing to be "significantly better". Because of the huge sunk cost, the new thing needs to be able to do desirable things that a POSIX-compatible OS strictly cannot do. Otherwise, it will always be easier and faster to just glue another subsystem onto the Linux kernel and continue using that.
You've already lost my interest. Bash/Shell is incredibly powerful and doesn't need a higher-level abstraction. That is what programming languages and CLI tools are for.
But mm... why you would want to mutable state like current directory in first place? Or even mutable filesystem? Why aren't those just immutable parameters of your program?
Because that's how just about any OS, application, library, filesystem, device driver, database, nearly everything related to computing at all, works. Reinventing the universe has never, ever lead to success. If you want even the slightest hope for non-negligible adoption of your operating system, you need to be able to interface with the rest of the world. And that's why you don't want the filesystem to be an "immuatble parameter of your program".
> any OS, application, library, filesystem, device driver, database, nearly everything related to computing at all
-is trying to actually do to your files and operation like reverting changes is trivial. Sure it's not performance optimal and effects what kind of software is ideal to work within such a system. It's just a different approach with different trade-offs.
Anyway if I one day want to build new OS it's for trying different interesting approaches and not yet another 'successful any-OS'.
You can actually consider current directory as immutable already.
It became "badly mutable" for example if you manipulate the current directory and this change is observable from a program point of view which is already running. Would that be really useful?
To clarify I am not trying to say that absolutely everything should be immutable but it's fun thought experiment how far you can go.
Being immutable doesn't mean that everything is static but that things aren't changeable in place. So instead of having a global mutable state, you have this local state which you can alter by creating new instances and passing them around. In the end, the difference is that on what scope changes are observable and how that constraint effects on your design. In some sense: mutable = changes are uncontrollable, immutable = changes are controllable.
I’m not saying POSIX shells are without fault but they weren’t just created because C is too low level; they were created because people used a terminal who weren’t always techies so Bell Labs created a language which anyone could easily write simple programs with. Granted that need has dissipated a little but your solution of having one language to rule them all creates more problems than it solves (really the only problem it solves is OCD for a few LISP enthusiasts).
The reason there are so many different programming languages isn’t just an element of NIH; some languages do genuinely handle some use cases better than some other languages. Plus there is always going to be an element of user preference. So your idea of a perfect system would be hell for a great many other people - myself included (and I do generally enjoy functional programming).
At some point when I wasn't proficient enough in Bash yet, I was writing scripts for automation using Perl and Ruby. The 'logic part' is definitely much easier in these languages. But simple file/directory stuff is far more complex actually. A lot of this has to do with error handling and different expectations how that is supposed to work. In a default shell script, errors are handled very forgivingly which is very much how most people work when performing tasks manually - not every step is super important.
On the other hand Shell scripting documentation is crap. Most of it is from 80s/90s, full of irrelevant details for the practical person. A bit like 90s/00s JS documentation before MDN.
Compare Rust, which tries to keep allocations (and the size/asymptotic complexity of allocations) explicit and visible.
[1] The optimizer catches some cases, but not all of them.
There is a long history of alternative shells csh, tcsh, zsh, fish, etc. All with various level of abstraction and various amounts of "programming language type constructs" (for lack of a better term).
At the end of the day, long arguments have been had over which is the better shell. It's all just personal preference. Hence it can be set in /etc/passwd per user.
You prefer python? chsh is there to change it.
Space on a line is limited and one directional, so thr tools naturally evolved towards strange single line incantations instead of full fledged programs.
Commodore Pet did it right by introducing a navigable terminal where you could move between lines and don't need to open an editor to write multiline programs.
Modern CLIs can usually emulate at least a few of the old scrolling character matrix displays, but only a few bash commands (e.g. top) use the extra features.
I use a separate tool to call a lot of functions written in C. That tool is called “Python”. Or “Ruby”.
Sometimes I use a separate tool to call functions written in Python (or JavaScript, or...) and that tool is called PostgreSQL.
Sometimes I use Ruby to call C to call Postgres to call JS.
> Now that we have better languages, why not use them to write the OS bottom-up?
What better languages for implementing an OS? Lisp? We had that before C. Rust? Either way—and I like both languages—I don't want to use either for a shell. Red? Maybe, but I don't think we're to the point of a Red OS yet, and while it's probably eventually usable for that purpose, I don't see it as necessarily ideal for OS implementation though it might be tolerable as a shell language.