Last active
March 21, 2026 04:33
-
-
Save mischief/b734df526b16413f8d80e62fa601a2e8 to your computer and use it in GitHub Desktop.
a "shell" in lua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local unistd = require("posix.unistd") | |
| local stdlib = require("posix.stdlib") | |
| local wait = require("posix.sys.wait") | |
| local topcmd = nil | |
| local Cmd = {} | |
| function Cmd:exec() | |
| local cmds = {} | |
| table.insert(cmds, self) | |
| local p = self.pipeto | |
| while p do | |
| table.insert(cmds, p) | |
| p = p.pipeto | |
| end | |
| local pipes = {} | |
| for i = 1, #cmds - 1 do | |
| local p0, p1 = unistd.pipe() | |
| if not p0 then | |
| error(p1) | |
| end | |
| pipes[i] = {p0, p1} | |
| end | |
| local pids = {} | |
| for i = 1, #cmds do | |
| local pid, errmsg = unistd.fork() | |
| if pid == nil then | |
| error(errmsg) | |
| elseif pid == 0 then | |
| -- child | |
| if i > 1 then | |
| -- stdin | |
| local fd, errmsg = unistd.dup2(pipes[i-1][1], 0) | |
| if not fd then | |
| error(errmsg) | |
| end | |
| end | |
| if i < #cmds then | |
| -- stdout | |
| local fd, errmsg = unistd.dup2(pipes[i][2], 1) | |
| if not fd then | |
| error(errmsg) | |
| end | |
| end | |
| for i = 1, #pipes do | |
| unistd.close(pipes[i][1]) | |
| unistd.close(pipes[i][2]) | |
| end | |
| local ok, err = unistd.execp(cmds[i].cmd, cmds[i].argt) | |
| if not ok then | |
| io.stderr:write(err .. "\n") | |
| unistd._exit(1) | |
| end | |
| else | |
| table.insert(pids, pid) | |
| -- parent | |
| end | |
| end | |
| for i = 1, #pipes do | |
| unistd.close(pipes[i][1]) | |
| unistd.close(pipes[i][2]) | |
| end | |
| for i = 1, #pids do | |
| local status, errmsg = wait.wait(pids[i]) | |
| if status == nil then | |
| error(errmsg) | |
| end | |
| end | |
| return nil | |
| end | |
| function Cmd:__call(...) | |
| self:arg(select(1, ...)) | |
| return self | |
| end | |
| function Cmd:__bor(right) | |
| local c = self | |
| if self.parent then | |
| c = self.parent | |
| end | |
| c.pipeto = right | |
| return self | |
| end | |
| function Cmd:__band(what) | |
| -- it's a fake Command, convert it to arg | |
| local v = stdlib.getenv(what.cmd) | |
| if not v then | |
| error("no such environment variable " .. what.cmd) | |
| end | |
| self:arg(v) | |
| -- set parent so we can track pipes correctly | |
| what.parent = self | |
| return self | |
| end | |
| function Cmd:arg(a) | |
| self.argt[#self.argt + 1] = a | |
| return self | |
| end | |
| local function newcmd(c) | |
| return setmetatable({ | |
| cmd = c, argt = {} | |
| }, { | |
| __index = Cmd, | |
| __call = Cmd.__call, | |
| __bor = Cmd.__bor, | |
| __band = Cmd.__band, | |
| }) | |
| end | |
| local function getenv(arg) | |
| if arg then | |
| print(stdlib.getenv(arg)) | |
| else | |
| for k,v in pairs(stdlib.getenv()) do | |
| print(string.format("%s=%s", k, v)) | |
| end | |
| end | |
| end | |
| local function setenv(k, v) | |
| stdlib.setenv(k,v) | |
| end | |
| local gbls = { | |
| print = print, | |
| quit = function() os.exit(0) end, | |
| help = function() | |
| print([[Syntax: | |
| help: this help | |
| print("hello") | |
| setenv("X", "Y") | |
| setenv("X") | |
| getenv("X") | |
| cmd "arg1" "arg2" | |
| cmd1 | cmd2 | |
| cmd1 "arg" | cmd2 "arg" | cmd3 "arg" | |
| cmd1 &ENVVAR | |
| ]]) | |
| end, | |
| getenv = getenv, | |
| setenv = setenv, | |
| } | |
| local function gindex(t, k) | |
| if gbls[k] then return gbls[k] end | |
| local cmd = newcmd(k) | |
| if not topcmd then | |
| topcmd = cmd | |
| end | |
| return cmd | |
| end | |
| local env = setmetatable({ | |
| prompt = "lsh# ", | |
| }, {__index = gindex}) | |
| repeat | |
| io.write(env.prompt) | |
| local line = io.read() | |
| if not line then break end | |
| local chk, err = load("return " .. line, "stdin", "t", env) | |
| if not chk then | |
| print(err) | |
| else | |
| local ok, result = pcall(chk) | |
| if not ok then | |
| print(result) | |
| goto continue | |
| end | |
| if ok and type(result) == "function" and not topcmd then | |
| result() | |
| goto continue | |
| end | |
| if topcmd then | |
| local ok, err = pcall(function() topcmd:exec() end) | |
| end | |
| topcmd = nil | |
| if not ok then | |
| print(err) | |
| end | |
| end | |
| ::continue:: | |
| until false | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment