Coffee Space


Listen:

Shell Concept

Preview Image

Typically most shells on the Linux operating system will run Bash (/bin/bash), Sh (/bin/sh - typically an alias to Bash), Ash, Zsh (more popular now) and others. Whilst these are cool and interesting, these are typically meant for really high-end, high-RAM systems.

Recently I have been having trouble with my 128MB server, trying to run too much on such a low-end system. One of the things I am having issue with is /bin/bash consuming a significant amount of RAM, once you have 10 shells open or so. The other repetitive RAM consuming resource was screen, but I don’t have the time or expertise to replace this.

This got me thinking: “If I were to design a new shell for this use-case, what may it look like?”.

Requirements

This is a list of things it must have:

  1. Run programs - This is the most obvious use of a shell language, it must be able to run programs. Sometimes you want to run and block until they are complete, other times you want to run them in the background.
  2. A clear threading model - One of the weirdest things about Bash is the threading model. I think this should be cleaned up to make it very clear, with the possibility of interacting with the threads created more easily.
  3. Process output from programs - We want the ability to get the output from a program and perform operations on it.
  4. Operate on numbers - Very often we will want to perform an operation on numbers.
  5. Operate on strings - Obviously various string comparisons and string manipulations.

Of course, this should not be compiled…

And this is a list of things that would be nice to have:

  1. C-style syntax - I think Bash syntax is somewhat awful. Bash is sold as a scripting language, but is actually Turing-complete, so is technically a type of programming language. Like something such as PHP, it grew and grew in functionality, and now it is a bit of a mess.
  2. Fixed-RAM allocation

Design

Generally I would want to use the Elk JS engine (and my review of it). It deals with all of the issues I would want to address and it is ultra simple.

I would probably look to expose the C library functions, so for comparing strings you may do something like the following for strcmp():

0001 let a = "hello";
0002 let b = "world";
0003 let c = "hello";
0004 if(strcmp(a, b) !== 0){
0005   /* a != b */
0006 }
0007 if(strcmp(b, c) !== 0){
0008   /* b != c */
0009 }
0010 if(strcmp(c, a) === 0){
0011   /* c == a */
0012 }

For running external programs, we could then make use of exec() to run external programs:

0013 execvp("ls");

It would take some thinking to figure out a nice way of returning the exit condition of the comamnd, the stdout and stdin. Ideally we want some kind of support for pipes, but it is not clear to be what may be the cleanest way to support this. Elk supports objects, so perhaps there is some Pipe type where we can define the stdin, stdout and stderr. This may look like:

0014 let pipe = /* Some declaration */;
0015 pipe.stdin = other_pipe.stdout;
0016 execvp(pipe, "ls");
0017 /* Do some stuff */
0018 printf(pipe.stdout);
0019 printf(pipe.stderr);

It is not very clean and maybe there is a nicer way to achieve this. Perhaps another option could be:

0020 pipe(funct1(), func2(), /*..*/);

We would probably want to implement our own cd (change directory) function so that we can track the current location the programs are to be run in. For simplicity the context would probably be global.

Edit: It occurs to me that there is actually a nicer way to implement this, where we simply allow for the dropping of the function rounded-brackets (( and )). We can also use a symbol to replace the pipe name, such as |, and get to something like:

0021 | funct1() funct2();
0022 // Equiv: pipe(funct1(), funct2());
0023 | prog1 prog2;
0024 // Equiv: pipe(prog1(), prog2());
0025 | prog1 (| funct1, funct2(/* params */));
0026 // Equiv: pipe(prog1, pipe(funct1(), funct2(/* params */)));
0027 | prog1 funct1 funct2(/* params */));
0028 // Equiv: pipe(prog1, funct1(), funct2(/* params */));

Essentially the rounded-brackets () become optional. I also quite like the simplicity of using & for forking, perhaps it would also follow the same syntax:

0029 fork(prog1());
0030 &(prog1());
0031 & prog1();
0032 & prog1;

Obviously the brackets would be the cleaner syntax in this case, especially if you want arguments for programs or functions.

Future

In terms of a name, jsh appears taken for a Java based shell idea. A Jay is a bird, so maybe there could be something funny here regarding a Jay bird with a shell, like a flying tortoise. So “JayShell” could be good.

If I get some time I will consider hacking together a quick implementation of this. Even something that is ultra-lightweight that can execute commands is already really useful. If we can process their output is some meaningful way, this is also really cool.