Files
HyperionOS/Src/Hyperion-bash/bin/hysh
2026-02-22 21:53:02 -06:00

999 lines
33 KiB
Plaintext

--:Minify:--
syscall.open("/dev/tty/TTY1","r") --stdin (Device 0)
syscall.open("/dev/tty/TTY1","w") --stdout (Device 1)
syscall.open("/dev/null","w") --stderr (device 2)
local success, errorMsg = xpcall(function()
local fs = require("sys.fs")
syscall.devctl(1,"clear")
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"spos",1,1)
print("HyperionOS hysh Shell")
local userhost = (syscall.getUsername() or "Unknown").."@"..(syscall.getHostname() or "Unknown")
local commandHistory = {}
local terminate = false
syscall.setEnviron("SHELL","rtbash")
syscall.setEnviron("PATH","/bin/")
syscall.chdir("/")
local oldWD = ""
for i = 1, 16 do
syscall.devctl(1,"sbgc",i); printInline(" ")
end
print("\n")
syscall.sigcatch(function(sig)
if sig == 1 then terminate = true end
end)
local function abspath(p)
if not p or p == "" then return syscall.getcwd() end
if p:sub(1,1) ~= "/" then p = syscall.getcwd().."/"..p end
local parts = {}
for seg in p:gmatch("[^/]+") do
if seg == ".." then if #parts > 0 then table.remove(parts) end
elseif seg ~= "." then table.insert(parts, seg) end
end
return "/"..table.concat(parts, "/")
end
local function basename(p)
p = p:gsub("/$","")
return p:match("([^/]+)$") or p
end
local function dirname(p)
p = p:gsub("/$","")
local d = p:match("^(.*)/[^/]+$")
if not d then return "." end
if d == "" then return "/" end
return d
end
local function readall(fd)
local t = {}
while true do
local ok, chunk = pcall(syscall.read, fd, 65536)
if not ok or not chunk or chunk == "" then break end
t[#t+1] = chunk
end
return table.concat(t)
end
local function readfile(path)
local ok, fd = pcall(syscall.open, path, "r")
if not ok then return nil, fd end
local data = readall(fd)
pcall(syscall.close, fd)
return data
end
local function writefile(path, data)
local ok, fd = pcall(syscall.open, path, "w")
if not ok then return false, fd end
pcall(syscall.write, fd, data)
pcall(syscall.close, fd)
return true
end
local function parseargs(rawargs, flagspec, longflags)
local opts = {}
local args = {}
longflags = longflags or {}
local i = 1
while i <= #rawargs do
local v = rawargs[i]
if v == "--" then
for j = i+1, #rawargs do args[#args+1] = rawargs[j] end
break
elseif v:sub(1,2) == "--" then
local lf = v:sub(3)
if longflags[lf] ~= nil then opts[lf] = true
else opts["_unknown"] = v end
elseif v:sub(1,1) == "-" and #v > 1 then
for k = 2, #v do
local c = v:sub(k,k)
if flagspec:find(c, 1, true) then opts[c] = true
else opts["_unknown"] = "-"..c end
end
else
args[#args+1] = v
end
i = i + 1
end
return opts, args
end
local function eachline(text, fn)
local pos = 1
while pos <= #text do
local nl = text:find("\n", pos, true)
if nl then fn(text:sub(pos, nl-1)); pos = nl+1
else fn(text:sub(pos)); break end
end
end
local function splitlines(text)
local lines = {}
eachline(text, function(l) lines[#lines+1] = l end)
return lines
end
local function copyfile(src, dst)
local data, err = readfile(src)
if not data then return false, err end
local ok, err2 = writefile(dst, data)
if not ok then return false, err2 end
local ok2, stat = pcall(syscall.stat, src)
if ok2 and stat and stat.perms then pcall(syscall.chmod, dst, stat.perms) end
return true
end
local function rmtree(path)
local t = syscall.type(path)
if t == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, e in ipairs(list) do
rmtree(path:gsub("/$","").."/"..e)
end
end
end
if t then pcall(syscall.remove, path) end
end
local function copytree(src, dst)
pcall(syscall.mkdir, dst)
local ok, list = pcall(syscall.listdir, src)
if not ok then return end
for _, e in ipairs(list) do
local s = src:gsub("/$","").."/"..e
local d = dst:gsub("/$","").."/"..e
local t = syscall.type(s)
if t == "directory" then copytree(s, d)
elseif t == "file" then copyfile(s, d)
elseif t == "symlink" then
local ok2, tgt = pcall(syscall.readlink, s)
if ok2 then pcall(syscall.remove, d); pcall(syscall.symlink, tgt, d) end
end
end
end
local builtinCmds = {}
builtinCmds.cd = function(path)
local cwd = syscall.getcwd()
local dirIn = path or ""
if dirIn == "-" then
if oldWD == "" then print("hysh: cd: no previous directory"); return end
print(oldWD); local tmp = oldWD; oldWD = cwd; syscall.chdir(tmp); return
end
local target = abspath(dirIn)
if target:sub(-1) ~= "/" then target = target.."/" end
if not fs.isDir(target) then
print("hysh: cd: "..dirIn..": No such directory"); return
end
oldWD = cwd; syscall.chdir(target)
end
builtinCmds.exit = function(code)
syscall.exit(tonumber(code) or 0)
end
builtinCmds.echo = function(...)
local args = {...}
local n = false
local start = 1
if args[1] == "-n" then n = true; start = 2 end
local out = table.concat(args, " ", start)
if n then printInline(out) else print(out) end
end
builtinCmds.pwd = function()
print(syscall.getcwd())
end
builtinCmds["true"] = function() end
builtinCmds["false"] = function() end
builtinCmds.sleep = function(...)
local args = {...}
if #args == 0 then print("sleep: missing operand"); return end
local total = 0
for _, a in ipairs(args) do
local n, u = a:match("^([%d%.]+)([smhd]?)$")
if not n then print("sleep: invalid time '"..a.."'"); return end
n = tonumber(n)
if u == "m" then n = n * 60
elseif u == "h" then n = n * 3600
elseif u == "d" then n = n * 86400 end
total = total + n
end
sleep(total)
end
builtinCmds.clear = function()
syscall.devctl(1,"clear")
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"spos",1,1)
end
builtinCmds.whoami = function()
print(syscall.getUsername() or "unknown")
end
builtinCmds.hostname = function(...)
local args = {...}
if #args == 0 then
print(syscall.getHostname() or "unknown")
else
local ok, err = pcall(syscall.setHostname, args[1])
if not ok then print("hostname: "..tostring(err)) end
end
end
builtinCmds.uname = function(...)
local opts, _ = parseargs({...}, "asnrm")
local all = opts.a
local parts = {}
local results = {}
for word in string.gmatch(syscall.version(), "%S+") do
table.insert(results, word)
end
if all or opts.s or (not opts.s and not opts.n and not opts.r and not opts.m) then
parts[#parts+1] = results[1]
end
if all or opts.n then parts[#parts+1] = (syscall.getHostname() or "hyperion") end
if all or opts.r then parts[#parts+1] = results[2] end
if all or opts.m then parts[#parts+1] = "virtual" end
print(table.concat(parts, " "))
end
builtinCmds.printenv = function(...)
local args = {...}
local vars = {"PATH","HOME","USER","SHELL","TERM","HOSTNAME","PWD","OLDPWD","LANG","TZ","EDITOR"}
if #args == 0 then
for _, k in ipairs(vars) do
local ok, v = pcall(syscall.getEnviron, k)
if ok and v then print(k.."="..v) end
end
else
for _, k in ipairs(args) do
local ok, v = pcall(syscall.getEnviron, k)
if ok and v then print(v) end
end
end
end
builtinCmds.env = function(...)
local args = {...}
local i = 1
while i <= #args and args[i]:match("^[%w_]+=") do
local k, v = args[i]:match("^([^=]+)=(.*)")
pcall(syscall.setEnviron, k, v)
i = i + 1
end
if i > #args then
builtinCmds.printenv()
end
end
builtinCmds.touch = function(...)
local _, args = parseargs({...}, "amc")
if #args == 0 then print("touch: missing operand"); return end
for _, a in ipairs(args) do
local path = abspath(a)
if not syscall.exists(path) then
local ok, fd = pcall(syscall.open, path, "w")
if ok then pcall(syscall.close, fd)
else print("touch: cannot create '"..a.."': "..tostring(fd)) end
end
end
end
builtinCmds.mkdir = function(...)
local opts, args = parseargs({...}, "p")
if #args == 0 then print("mkdir: missing operand"); return end
for _, a in ipairs(args) do
local path = abspath(a)
if path:sub(-1) ~= "/" then path = path.."/" end
if fs.isDir(path) then
print("mkdir: cannot create '"..a.."': Directory exists"); return
end
local ok, err = pcall(syscall.mkdir, path)
if not ok then print("mkdir: cannot create '"..a.."': "..tostring(err)) end
end
end
builtinCmds.rm = function(...)
local opts, args = parseargs({...}, "rRf")
if #args == 0 then print("rm: missing operand"); return end
local recursive = opts.r or opts.R
for _, a in ipairs(args) do
local path = abspath(a)
local t = syscall.type(path)
if not t then
if not opts.f then print("rm: cannot remove '"..a.."': No such file or directory") end
elseif t == "directory" then
if not recursive then print("rm: cannot remove '"..a.."': Is a directory")
else rmtree(path) end
else
local ok, err = pcall(syscall.remove, path)
if not ok then print("rm: cannot remove '"..a.."': "..tostring(err)) end
end
end
end
builtinCmds.cp = function(...)
local opts, args = parseargs({...}, "rRp")
if #args < 2 then print("cp: missing operand"); return end
local recursive = opts.r or opts.R
local dst = abspath(args[#args])
local dstIsDir = syscall.type(dst) == "directory"
if #args > 2 and not dstIsDir then
print("cp: target '"..args[#args].."' is not a directory"); return
end
for i = 1, #args-1 do
local src = abspath(args[i])
local t = syscall.type(src)
if not t then
print("cp: '"..args[i].."': No such file or directory")
elseif t == "directory" then
if not recursive then print("cp: omitting directory '"..args[i].."'")
else copytree(src, dstIsDir and (dst:gsub("/$","").."/"..basename(args[i])) or dst) end
else
local d = dstIsDir and (dst:gsub("/$","").."/"..basename(args[i])) or dst
local ok, err = copyfile(src, d)
if not ok then print("cp: '"..args[i].."': "..tostring(err)) end
end
end
end
builtinCmds.mv = function(...)
local opts, args = parseargs({...}, "f")
if #args < 2 then print("mv: missing operand"); return end
local dst = abspath(args[#args])
local dstIsDir = syscall.type(dst) == "directory"
if #args > 2 and not dstIsDir then
print("mv: target '"..args[#args].."' is not a directory"); return
end
for i = 1, #args-1 do
local src = abspath(args[i])
local t = syscall.type(src)
if not t then
print("mv: '"..args[i].."': No such file or directory")
else
local d = dstIsDir and (dst:gsub("/$","").."/"..basename(args[i])) or dst
if t == "directory" then
copytree(src, d); rmtree(src)
else
local ok, err = copyfile(src, d)
if ok then pcall(syscall.remove, src)
else print("mv: '"..args[i].."': "..tostring(err)) end
end
end
end
end
builtinCmds.cat = function(...)
local args = {...}
if #args == 0 then
while true do
local ok, chunk = pcall(syscall.read, 0, 4096)
if not ok or not chunk or chunk == "" then break end
printInline(chunk)
end
print("")
return
end
for _, a in ipairs(args) do
local path = abspath(a)
if not syscall.exists(path) then
print("cat: "..a..": No such file or directory")
else
local ok, fd = pcall(syscall.open, path, "r")
if not ok then print("cat: "..a..": "..tostring(fd))
else
while true do
local ok2, chunk = pcall(syscall.read, fd, 65536)
if not ok2 or not chunk or chunk == "" then break end
printInline(chunk)
end
pcall(syscall.close, fd)
end
end
end
end
builtinCmds.head = function(...)
local raw = {...}
local n = 10
local files = {}
local i = 1
while i <= #raw do
local v = raw[i]
if v == "-n" then i=i+1; n=tonumber(raw[i]) or 10
elseif v:match("^%-n%d+$") then n=tonumber(v:sub(3))
elseif v:match("^%-%d+$") then n=tonumber(v:sub(2))
elseif v:sub(1,1) ~= "-" or v == "-" then files[#files+1] = v
end
i=i+1
end
local multi = #files > 1
local function dohead(text, label)
if multi then
syscall.devctl(1,"sfgc",4); print("==> "..label.." <=="); syscall.devctl(1,"sfgc",1)
end
local count = 0
for line in (text.."\n"):gmatch("([^\n]*)\n") do
if count >= n then break end
print(line); count = count+1
end
end
if #files == 0 then
dohead(readall(0), "stdin")
else
for _, a in ipairs(files) do
if a == "-" then dohead(readall(0), "stdin")
else
local data, err = readfile(abspath(a))
if not data then print("head: "..a..": "..tostring(err))
else dohead(data, a) end
end
end
end
end
builtinCmds.tail = function(...)
local raw = {...}
local n = 10
local files = {}
local i = 1
while i <= #raw do
local v = raw[i]
if v == "-n" then i=i+1; n=tonumber(raw[i]) or 10
elseif v:match("^%-n%d+$") then n=tonumber(v:sub(3))
elseif v:match("^%-%d+$") then n=tonumber(v:sub(2))
elseif v:sub(1,1) ~= "-" or v == "-" then files[#files+1] = v
end
i=i+1
end
local multi = #files > 1
local function dotail(text, label)
if multi then
syscall.devctl(1,"sfgc",4); print("==> "..label.." <=="); syscall.devctl(1,"sfgc",1)
end
local lines = splitlines(text)
local start = math.max(1, #lines - n + 1)
for j = start, #lines do print(lines[j]) end
end
if #files == 0 then dotail(readall(0), "stdin")
else
for _, a in ipairs(files) do
if a == "-" then dotail(readall(0), "stdin")
else
local data, err = readfile(abspath(a))
if not data then print("tail: "..a..": "..tostring(err))
else dotail(data, a) end
end
end
end
end
builtinCmds.wc = function(...)
local opts, args = parseargs({...}, "lwc")
local showAll = not opts.l and not opts.w and not opts.c
if showAll then opts.l=true; opts.w=true; opts.c=true end
local function count(text)
local l,w,b = 0,0,#text
for _ in text:gmatch("\n") do l=l+1 end
for _ in text:gmatch("%S+") do w=w+1 end
return l,w,b
end
local function fmt(l,w,c,lbl)
local p={}
if opts.l then p[#p+1]=string.format("%7d",l) end
if opts.w then p[#p+1]=string.format("%7d",w) end
if opts.c then p[#p+1]=string.format("%7d",c) end
if lbl then p[#p+1]=" "..lbl end
print(table.concat(p))
end
local tl,tw,tc = 0,0,0
if #args == 0 then
local l,w,c = count(readall(0)); fmt(l,w,c)
else
for _, a in ipairs(args) do
local data, err = readfile(abspath(a))
if not data then print("wc: "..a..": "..tostring(err))
else
local l,w,c = count(data); fmt(l,w,c,a)
tl=tl+l; tw=tw+w; tc=tc+c
end
end
if #args > 1 then fmt(tl,tw,tc,"total") end
end
end
builtinCmds.grep = function(...)
local opts, args = parseargs({...}, "ivnlcrR", {["ignore-case"]=true,["invert-match"]=true})
if #args == 0 then print("grep: missing pattern"); return end
local pat = args[1]
if opts.i or opts["ignore-case"] then pat = pat:lower() end
local recursive = opts.r or opts.R
local found = false
local function greptext(text, label, showlabel)
local count = 0
local linenum = 0
local hasMatch = false
eachline(text, function(line)
linenum = linenum+1
local test = (opts.i or opts["ignore-case"]) and line:lower() or line
local m = (test:find(pat) ~= nil)
if opts.v or opts["invert-match"] then m = not m end
if m then
count=count+1; hasMatch=true; found=true
if not opts.l and not opts.c then
local out = ""
if showlabel then out=out..label..":" end
if opts.n then out=out..linenum..":" end
print(out..line)
end
end
end)
if opts.l and hasMatch then print(label) end
if opts.c then
print((showlabel and label..":" or "")..count)
end
end
local function grepfile(path, label, showlabel)
local data, err = readfile(path)
if not data then print("grep: "..label..": "..tostring(err)); return end
greptext(data, label, showlabel)
end
local function grepdir(dir, prefix)
local ok, list = pcall(syscall.listdir, dir)
if not ok then return end
for _, e in ipairs(list) do
local fp = dir:gsub("/$","").."/"..e
local lbl = (prefix ~= "" and prefix.."/" or "")..e
local t = syscall.type(fp)
if t == "directory" then grepdir(fp, lbl)
elseif t == "file" then grepfile(fp, lbl, true) end
end
end
if #args == 1 then
greptext(readall(0), "(stdin)", false)
else
local showlabel = #args > 2 or recursive
for i = 2, #args do
local path = abspath(args[i])
local t = syscall.type(path)
if t == "directory" then
if recursive then grepdir(path, args[i])
else print("grep: "..args[i]..": Is a directory") end
elseif t then
grepfile(path, args[i], showlabel)
else
print("grep: "..args[i]..": No such file or directory")
end
end
end
end
builtinCmds.sort = function(...)
local opts, args = parseargs({...}, "rnu")
local lines = {}
local function addlines(text)
local ls = splitlines(text)
for _, l in ipairs(ls) do lines[#lines+1] = l end
end
if #args == 0 then addlines(readall(0))
else
for _, a in ipairs(args) do
local data, err = readfile(abspath(a))
if not data then print("sort: "..a..": "..tostring(err))
else addlines(data) end
end
end
table.sort(lines, function(a,b)
if opts.n then
local na = tonumber(a:match("^%-?%d+%.?%d*")) or 0
local nb = tonumber(b:match("^%-?%d+%.?%d*")) or 0
return opts.r and na > nb or na < nb
end
return opts.r and a > b or a < b
end)
local prev = nil
for _, l in ipairs(lines) do
if not opts.u or l ~= prev then print(l); prev = l end
end
end
builtinCmds.uniq = function(...)
local opts, args = parseargs({...}, "cdui")
local text
if args[1] then
local data, err = readfile(abspath(args[1]))
if not data then print("uniq: "..args[1]..": "..tostring(err)); return end
text = data
else text = readall(0) end
local prev, prevCount = nil, 0
local out = {}
local function emit(line, count)
if opts.d and count == 1 then return end
if opts.u and count > 1 then return end
out[#out+1] = opts.c and string.format("%7d %s", count, line) or line
end
eachline(text, function(line)
local cmp = opts.i and line:lower() or line
local cprev = opts.i and (prev and prev:lower()) or prev
if cmp == cprev then prevCount = prevCount+1
else
if prev ~= nil then emit(prev, prevCount) end
prev = line; prevCount = 1
end
end)
if prev ~= nil then emit(prev, prevCount) end
if args[2] then
local ok, err = writefile(abspath(args[2]), table.concat(out, "\n").."\n")
if not ok then print("uniq: "..args[2]..": "..tostring(err)) end
else
for _, l in ipairs(out) do print(l) end
end
end
builtinCmds.tee = function(...)
local opts, args = parseargs({...}, "a")
local mode = opts.a and "a" or "w"
local fds = {}
for _, a in ipairs(args) do
local ok, fd = pcall(syscall.open, abspath(a), mode)
if not ok then print("tee: "..a..": "..tostring(fd))
else fds[#fds+1] = fd end
end
while true do
local ok, chunk = pcall(syscall.read, 0, 4096)
if not ok or not chunk or chunk == "" then break end
pcall(syscall.write, 1, chunk)
for _, fd in ipairs(fds) do pcall(syscall.write, fd, chunk) end
end
for _, fd in ipairs(fds) do pcall(syscall.close, fd) end
end
builtinCmds.find = function(...)
local raw = {...}
local roots, filters = {}, {}
local maxdepth, mindepth = nil, 0
local i = 1
while i <= #raw and raw[i]:sub(1,1) ~= "-" do
roots[#roots+1] = raw[i]; i=i+1
end
if #roots == 0 then roots = {"."} end
while i <= #raw do
local tok = raw[i]
if tok == "-name" then
i=i+1
local pat = "^"..(raw[i] or ""):gsub("([%.%+%-%^%$%(%)%[%]%%])","%%%1"):gsub("%*",".*"):gsub("%?",".").. "$"
filters[#filters+1] = function(_, e, _2, _3) return e:match(pat) ~= nil end
elseif tok == "-type" then
i=i+1; local ft=raw[i] or ""
filters[#filters+1] = function(_, _, t, _2)
if ft=="f" then return t=="file"
elseif ft=="d" then return t=="directory"
elseif ft=="l" then return t=="symlink" end; return true
end
elseif tok == "-maxdepth" then i=i+1; maxdepth=tonumber(raw[i]) or 0
elseif tok == "-mindepth" then i=i+1; mindepth=tonumber(raw[i]) or 0
elseif tok == "-empty" then
filters[#filters+1] = function(path, _, t, _2)
if t=="file" then local ok,s=pcall(syscall.stat,path); return ok and s and (s.size or 0)==0
elseif t=="directory" then local ok,l=pcall(syscall.listdir,path); return ok and l and #l==0 end
return false
end
end
i=i+1
end
local function match(path, e, t, depth)
for _, f in ipairs(filters) do if not f(path,e,t,depth) then return false end end
return true
end
local function walk(path, disp, depth)
local t = syscall.type(path)
if not t then return end
local e = path:match("([^/]+)/?$") or path
if depth >= mindepth and match(path,e,t,depth) then print(disp) end
if t=="directory" and (maxdepth==nil or depth<maxdepth) then
local ok,list = pcall(syscall.listdir, path)
if ok then
table.sort(list)
for _, child in ipairs(list) do
walk(path:gsub("/$","").."/"..child, disp:gsub("/$","").."/"..child, depth+1)
end
end
end
end
local cwd = syscall.getcwd()
for _, r in ipairs(roots) do
local abs = (r == ".") and cwd or abspath(r)
walk(abs, r, 0)
end
end
builtinCmds.stat = function(...)
local _, args = parseargs({...}, "")
if #args == 0 then print("stat: missing operand"); return end
local function permstr(p)
local function b(n) return math.floor(p/(2^n))%2==1 end
return (b(5) and"r"or"-")..(b(4) and"w"or"-")..(b(9) and"x"or"-")..
(b(3) and"r"or"-")..(b(2) and"w"or"-")..(b(8) and"x"or"-")..
(b(1) and"r"or"-")..(b(0) and"w"or"-")..(b(7) and"x"or"-")
end
local function octperm(p)
local function b(n) return math.floor(p/(2^n))%2 end
local o = b(6)*4096+b(5)*256+b(4)*128+b(9)*64+b(3)*32+b(2)*16+b(8)*8+b(1)*4+b(0)*2+b(7)*1
return string.format("%04o",o)
end
for _, a in ipairs(args) do
local path = abspath(a)
if not syscall.exists(path) then
print("stat: '"..a.."': No such file or directory")
else
local t = syscall.type(path)
local ok, s = pcall(syscall.stat, path)
s = ok and s or {}
local tc = t=="directory" and "d" or (t=="symlink" and "l" or "-")
print(" File: "..a)
print(" Size: "..tostring(s.size or 0).." Type: "..(t or "?"))
print(" Owner: "..tostring(s.owner or 0).." Group: "..tostring(s.group or 0))
print("Access: ("..octperm(s.perms or 0).."/"..tc..permstr(s.perms or 0)..")")
if t == "symlink" then
local ok2, tgt = pcall(syscall.readlink, path)
if ok2 then print(" Link: "..tgt) end
end
print("")
end
end
end
builtinCmds.basename = function(...)
local args = {...}
if #args == 0 then print("basename: missing operand"); return end
local p = args[1]
while #p > 1 and p:sub(-1)=="/" do p=p:sub(1,-2) end
local b = p:match("([^/]+)$") or p
if args[2] and b:sub(-#args[2]) == args[2] then b = b:sub(1, #b-#args[2]) end
print(b)
end
builtinCmds.dirname = function(...)
local args = {...}
if #args == 0 then print("dirname: missing operand"); return end
for _, p in ipairs(args) do
while #p > 1 and p:sub(-1)=="/" do p=p:sub(1,-2) end
local d = p:match("^(.*)/[^/]+$")
if not d then print(".")
elseif d == "" then print("/")
else print(d) end
end
end
builtinCmds.df = function(...)
local opts, _ = parseargs({...}, "h")
local function hfmt(n)
local u={"K","M","G","T"}; local i=0
while n>=1024 and i<#u do n=n/1024; i=i+1 end
if i==0 then return tostring(math.floor(n)) end
if n<10 then return string.format("%.1f%s",n,u[i]) end
return math.floor(n)..u[i]
end
local function fmt(n) return opts.h and hfmt(n) or tostring(n) end
print(string.format("%-20s %10s %10s %10s %6s %-s","Filesystem","Size","Used","Avail","Use%","Mounted on"))
local mounts = {{"$","/"}, {"devfs0000","/dev/"}, {"tmpfs0000","/tmp/"}}
for _, m in ipairs(mounts) do
local id, mp = m[1], m[2]
print(string.format("%-20s %10s %10s %10s %6s %-s", id, "?", "?", "?", "?%", mp))
end
end
local function getUserInput()
syscall.devctl(1,"sfgc",3)
syscall.write(1, userhost)
syscall.devctl(1,"sfgc",1)
syscall.write(1, ":")
syscall.devctl(1,"sfgc",10)
syscall.write(1, syscall.getcwd())
syscall.devctl(1,"sfgc",1)
syscall.write(1, "$ ")
local curOffsetStr = syscall.devctl(1, "gpos")
local curOffsetX = tonumber(curOffsetStr:sub(1, curOffsetStr:find(";")-1))
local curOffsetY = tonumber(curOffsetStr:sub(curOffsetStr:find(";")+1))
local input = ""
local blinkState = false
local cursorPos = 1
local history = 0
local dirty = true
local function redraw()
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
syscall.write(1, string.sub(input, 1, cursorPos-1))
if blinkState then
syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1)
end
if cursorPos > #input then
syscall.write(1, " ")
else
syscall.write(1, string.sub(input, cursorPos, cursorPos))
end
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
syscall.write(1, string.sub(input, cursorPos+1) .. " ")
end
while true do
local key = syscall.read(0)
if key and key ~= "" then
if key=="\19" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end
elseif key=="\20" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end
elseif key=="\17" then
if history<#commandHistory then
history=history+1
input=commandHistory[#commandHistory-history+1]
cursorPos=#input+1;dirty=true
end
elseif key=="\18" then
if history>1 then
history=history-1
input=commandHistory[#commandHistory-history+1]
cursorPos=#input+1;dirty=true
elseif history==1 then
history=0;input="";cursorPos=1;dirty=true
end
elseif key=="\b" then
if cursorPos>1 then
input=string.sub(input,1,cursorPos-2)..string.sub(input,cursorPos)
cursorPos=cursorPos-1;dirty=true
end
elseif key=="\n" then
syscall.devctl(1,"sfgc",1);syscall.devctl(1,"sbgc",16)
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
syscall.write(1, input.." \n")
return input
else
input=string.sub(input,1,cursorPos-1)..key..string.sub(input,cursorPos)
cursorPos=cursorPos+1;dirty=true
end
end
local curBlink = ((math.floor(syscall.getUptime()/500)%2)==0)
if curBlink~=blinkState then blinkState=curBlink;dirty=true end
if dirty then redraw();dirty=false end
end
end
local function printError(progName, msg)
syscall.devctl(1,"sfgc",2)
local s = tostring(msg)
local line, rest = s:match("%]:(%d+): (.+)$")
if not line then line, rest = s:match(":(%d+): (.+)$") end
if line then
printInline(progName..": error on line "..line..": "); print(rest)
else
print(progName..": "..s)
end
syscall.devctl(1,"sfgc",1)
end
local function runCommand(command)
do
local func = load("return "..command, "@equation", "t", {})
if func then
local ok, result = pcall(func)
if ok and type(result)=="number" then print(result); return end
end
end
terminate = false
local args = string.split(command, " ")
for i = #args, 1, -1 do
if args[i] == "" then table.remove(args, i) end
end
if #args == 0 then return end
if builtinCmds[args[1]] then
local ok, err = pcall(builtinCmds[args[1]], table.unpack(args, 2))
if not ok then printError(args[1], err) end
return
end
local cmdPath = ""
if string.find(args[1], "/") then
local candidate = args[1]
if candidate:sub(1,1) ~= "/" then candidate = syscall.getcwd().."/"..candidate end
if fs.exists(candidate) then cmdPath = candidate end
else
local paths = string.split(syscall.getEnviron("PATH"), ":")
for _, p in pairs(paths) do
if fs.exists(p..args[1]) then cmdPath = p..args[1]; break end
end
if cmdPath == "" then
local cwd = syscall.getcwd()
local candidate = cwd..(cwd:sub(-1)=="/" and "" or "/")..args[1]
if fs.exists(candidate) then cmdPath = candidate end
end
end
if cmdPath == "" then print(args[1]..": Command not found"); return end
local progName = cmdPath:match("([^/]+)$") or args[1]
local xok, xerr = pcall(syscall.access, cmdPath, "x")
if not xok then
syscall.devctl(1,"sfgc",2); print(progName..": Permission denied"); syscall.devctl(1,"sfgc",1)
return
end
local text = fs.readAllText(cmdPath)
local program, err = load(text, progName)
if not program then
syscall.devctl(1,"sfgc",2)
local line, rest = tostring(err):match(":(%d+): (.+)$")
if line then printInline(progName..": load error on line "..line..": "); print(rest)
else print(progName..": load error: "..tostring(err)) end
syscall.devctl(1,"sfgc",1); return
end
local proc = syscall.spawn(function(...)
syscall.open("/dev/tty/TTY1","r")
syscall.open("/dev/tty/TTY1","w")
syscall.open("/dev/null","w")
local ok2, msg = pcall(program, ...)
if not ok2 then printError(progName, msg) end
end, progName, nil, {table.unpack(args, 2)})
while true do
local exited, code = syscall.collect(proc)
if exited then
if code then print("\nTask exited with code:\n"..tostring(code)) end
return
end
if terminate then
local ok2 = syscall.kill(proc)
if ok2 then
syscall.devctl(1,"sbgc",16); syscall.devctl(1,"sfgc",2)
print("\nProgram Terminated."); syscall.devctl(1,"sfgc",1)
end
terminate = false; break
end
sleep(0.05)
end
end
while true do
local command = getUserInput()
if command ~= "" then
if command ~= commandHistory[#commandHistory] then
table.insert(commandHistory, command)
end
runCommand(command)
end
end
end, debug.traceback)
if not success then
syscall.log("Error running shell: "..errorMsg, "ERROR")
syscall.devctl(1,"sfgc",2)
syscall.devctl(1,"sbgc",16)
print()
print("Error running shell: ")
print(errorMsg)
end