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?”.
This is a list of things it must have:
Of course, this should not be compiled…
And this is a list of things that would be nice to have:
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.
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.