08 November 2025

This post is about TB (ToolBox) - the shell interpreter for MOP2 operating system.

Invoking applications

Applications are invoked by providing an absolute path to the binary executable and the list of arguments. In MOP2 paths must be formatted as MOUNTPOINT:/path/to/my/file. All paths are absolute and MOP2 doesn’t support relative paths (there’s no concept of a CWD or current working directory).

Example of listing currently running processes
base:/bin/pctl ls

Typing out the entire path might get tiresome. Imagine typing MOUNTPOINT:/path/to/app arg more args every time you want to call an app. This is what TB aliases/macros are for. They make the user type less ;).

Example of calling an application via an alias
$pctl ls

Now that’s way better!

Creating new aliases

To create an alias we can type

mkalias pctl base:/bin/pctl

And then we can use our $pctl!

But there’s another issue - we have to write aliases for every application, which isn’t better than us typing out the entire path. Luckliy, there’s a solution for this. TB has two useful functions that can help us solve this - eachfile and mkaliasbn.

eachfile takes a directory, an ignore list and a command, which is run for every entry in the said directory. We can also access the current directory entry via special symbol called &EF-ELEM. In base/scripts/rc.tb we can see the full example in action.

eachfile !.gitkeep base:/bin \
  mkaliasbn &EF-ELEM

This script means: for each file in base:/bin (excluding .gitkeep), call mkaliasbn for the current entry. mkaliasbn then takes the base name of a path, which is expanded by &EF-ELEM and creates an alias. mkaliasbn just simply does mkalias <app> MP:/path/<app>.

Logging

In the UNIX shell there’s one very useful statement - set -x. set -x tells the shell to print out executed commands. It’s useful for script debugging or in general to know what the script does (or if it’s doing anything / not stuck). This is one thing that I hate about Windows - it shows up a stupid dotted spinner and doesn’t tell you what it’s doing and you start wondering. Is it stuck? Is it waiting for a drive/network/other I/O? Is it bugged? Can I turn of my PC? Will it break if I do? The user SHOULD NOT have these kinds of questions. That’s why I believe that set -x is very important.

I wanted to have something similar in TB, so I’ve added a setlogcmds function. It takes yes or no as an argument to enable/disable logging. It can be invoked like so:

setlogcmds yes

Now the user will see printed statements, for eg. during the system start up:

this is an init script!
+$tb -m runfile -f base:/scripts/mount.tb
Mounting filesystems...
+$fs mount -mp uhome -fs LittleFS -dev atasd-ch0-M-part2 -fmt no
OK uhome

String stack and subshells

In UNIX shell, it’s common to get the output of an application, store it into a variable and then pass that variable around to other apps. For eg:

# Use of a subshell
MYVAR=$(cat file.txt)
echo $MYVAR | myapp # or something...

In TB, I’ve decided to go with a stack, since I find it easier to implement than a variable hashmap. A stack can be implemented using any dynamic list BTW.

The stack in TB is manipulated via stackpush and stackpop functions. We can stackpush a string using stackpush <my string> and then stackpop it to remove it. We can also access the top of the stack via $stack. It’s a special magic alias, which expands to the string that is at the top. An example of stack usage would be:

stackpush 'hello world string!'
print $stack
stackpop

The do function

The do function does what a subshell does in UNIX shell. We can do a command an then have it’s output placed at the top of the stack. An example of this would be:

do print 'hello world from subshell'
print $stack
stackpop

It’s a simpler, more primitive mechanism than the UNIX subshells, but it gets the job done ;).