Skip to content

Shell Commands

opencode-agent[bot] edited this page Sep 6, 2026 · 4 revisions

Shell & Commands

JNode's command-line environment provides a Bash-like shell experience, implemented entirely in Java, with a custom command framework for parsing arguments and handling I/O redirection.

Overview

Unlike Unix, where commands are typically separate executable binaries launched via fork() and exec(), JNode commands are Java classes executed within the same JVM (often within their own Isolates or Proclets).

The shell subsystem handles interactive input, script execution (e.g., jnode.ini), tab-completion, aliasing, and I/O redirection.

Architecture

Location: shell/src/shell/org/jnode/shell/

1. The Shell (CommandShell)

The main interactive loop. It reads input from the user (via CommandShellReader), parses it, and executes it using an interpreter.

2. The Interpreter (CommandInterpreter)

JNode supports different interpreters.

  • DefaultInterpreter: Simple execution of single commands.
  • RedirectingInterpreter: Handles Unix-style pipelines (|) and I/O redirection (>, <).
  • bjorne: A Bourne-compatible shell script interpreter capable of running more complex shell scripts.

3. The Invoker (CommandInvoker)

Decides how the Java class representing the command is run.

  • DefaultCommandInvoker: Runs the command in the current context.
  • ThreadCommandInvoker: Runs the command in a new VmThread.
  • ProcletCommandInvoker: Runs the command in a new Proclet (a lightweight isolated context sharing the same address space but with separate standard streams and environment variables).

Writing a Command

Commands are implemented by extending AbstractCommand (or implementing Command).

public class MyCommand extends AbstractCommand {
    private final StringArgument arg = new StringArgument("name", Argument.MANDATORY, "Your name");

    public MyCommand() {
        super("Prints a greeting");
        registerArguments(arg);
    }

    public void execute() throws Exception {
        PrintWriter out = getOutput().getPrintWriter();
        out.println("Hello, " + arg.getValue());
    }
}

Argument Parsing (org.jnode.shell.syntax)

JNode commands do not typically parse String[] args manually. Instead, they register Argument objects (e.g., FileArgument, StringArgument, FlagArgument). The shell's syntax parser automatically validates input against these arguments before execute() is called. This also automatically provides --help text and tab-completion.

For detailed information on how the syntax system works, see Syntax.

Command Registration

Commands must be registered to be accessible from the shell. This is done via Plugin Extension Points.

In a plugin's plugin.xml descriptor:

<extension point="org.jnode.shell.aliases">
  <alias name="mycmd" class="com.example.MyCommand"/>
</extension>

This tells the AliasManager to map the keyword mycmd to the given class. When the user types mycmd, the shell instantiates the class and invokes it.

Built-in Commands

Many standard Unix-like commands are implemented in shell/src/shell/org/jnode/shell/command/ and cli/src/.

  • File operations: ls, cp, mv, rm, cat, mkdir
  • System info: free, ps, threads, gc, dmesg
  • Network: ping, ifconfig, route
  • Disk: mount, fdisk, format

I/O Redirection

Because all commands run in the same JVM, System.out and System.in cannot be safely redirected globally. Instead, commands use getInput(), getOutput(), and getError() provided by AbstractCommand. The Proclet system manages these streams per-thread-group, allowing CommandA | CommandB to work correctly without polluting the global System.out.

Install Command

The install command provides an interactive shell-based installer:

install [device-name]

This launches CommandLineInstaller (via InstallCommand), which:

  • Accepts an optional device name argument (e.g., install hda0)
  • Auto-discovers a single JFAT partition if no argument is given
  • Falls back to an interactive prompt if multiple or no candidates are found
  • Registers two actions: CopyFilesActionGrubInstallerAction (files first, then GRUB)
  • Provides console-based readline I/O via System.in/System.out
  • Drives the same action sequence as the boot-time installer but from within a live JNode shell
  • Supports Step.back navigation during collect() for revisiting previous steps

Device Auto-Discovery

CommandLineInstaller resolves the target device via three strategies:

  1. CLI argument — passed directly to the constructor
  2. Auto-discovery — scans mounted filesystems for a single JFAT partition under /devices/
  3. Interactive prompt — asks the user to enter a device name

The resolved device ID and mount point are cached in InputContext so downstream actions (GrubInstallerAction, CopyFilesAction) can read them without re-prompting.

Action Ordering

CopyFilesAction runs before GrubInstallerAction so that GRUB's stage1.5 write does not corrupt the freshly written FAT filesystem. The mount point is resolved by filesystem identity (same approach as JGrub.getMountPoint) rather than by path substring matching.

GrubInstallerAction

Handles device selection and GRUB installation:

  • Reads DEVICE_ID from the action context (set by CommandLineInstaller)
  • Only prompts for device input if DEVICE_ID is absent
  • execute() installs GRUB stage1/stage1.5/stage2 on the selected device
  • Guards against no-device-selected with IllegalStateException

JGrub

JGrub handles whole-disk device names (no partition suffix):

  • If partitionSuffix is empty, defaults partitionNumber to 0
  • If parentDeviceName is empty, uses the device itself as the parent

Related Pages

Clone this wiki locally