1229 lines
41 KiB
Plaintext
1229 lines
41 KiB
Plaintext
--:Minify:--
|
||
syscall.open("/dev/tty/1","r") --stdin (Device 0)
|
||
syscall.open("/dev/tty/1","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/")
|
||
local _home = syscall.getEnviron("HOME")
|
||
if _home and _home ~= "" then
|
||
local ok = pcall(syscall.chdir, _home)
|
||
if not ok then syscall.chdir("/") end
|
||
else
|
||
syscall.chdir("/")
|
||
end
|
||
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 listDir(dir, prefix)
|
||
local ok, entries = pcall(syscall.listdir, dir)
|
||
if not ok or not entries then return {} end
|
||
local results = {}
|
||
for _, e in ipairs(entries) do
|
||
if prefix == "" or e:sub(1, #prefix) == prefix then
|
||
local fullpath = (dir == "/" and "/" or dir.."/")..e
|
||
local t = syscall.type(fullpath)
|
||
results[#results+1] = t == "directory" and (e.."/") or e
|
||
end
|
||
end
|
||
table.sort(results)
|
||
return results
|
||
end
|
||
|
||
local function listCommands(prefix)
|
||
local results = {}
|
||
local seen = {}
|
||
for name in pairs(builtinCmds) do
|
||
if prefix == "" or name:sub(1, #prefix) == prefix then
|
||
if not seen[name] then results[#results+1] = name; seen[name] = true end
|
||
end
|
||
end
|
||
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
|
||
for _, p in ipairs(paths) do
|
||
local ok, entries = pcall(syscall.listdir, p)
|
||
if ok and entries then
|
||
for _, e in ipairs(entries) do
|
||
local fullpath = (p:sub(-1)=="/" and p or p.."/")..e
|
||
local xok = pcall(syscall.access, fullpath, "x")
|
||
if xok and (prefix == "" or e:sub(1, #prefix) == prefix) then
|
||
if not seen[e] then results[#results+1] = e; seen[e] = true end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
table.sort(results)
|
||
return results
|
||
end
|
||
|
||
local function commonPrefix(list)
|
||
if #list == 0 then return "" end
|
||
local pre = list[1]
|
||
for i = 2, #list do
|
||
local s = list[i]
|
||
local j = 1
|
||
while j <= #pre and j <= #s and pre:sub(j,j) == s:sub(j,j) do j = j+1 end
|
||
pre = pre:sub(1, j-1)
|
||
if pre == "" then return "" end
|
||
end
|
||
return pre
|
||
end
|
||
|
||
local function showCompletions(completions)
|
||
syscall.write(1, "\n")
|
||
local W = 51
|
||
local maxlen = 0
|
||
for _, c in ipairs(completions) do if #c > maxlen then maxlen = #c end end
|
||
local colw = maxlen + 2
|
||
local cols = math.max(1, math.floor(W / colw))
|
||
local i = 0
|
||
for _, c in ipairs(completions) do
|
||
local padded = c .. string.rep(" ", colw - #c)
|
||
syscall.write(1, padded)
|
||
i = i + 1
|
||
if i % cols == 0 then syscall.write(1, "\n") end
|
||
end
|
||
if i % cols ~= 0 then syscall.write(1, "\n") end
|
||
end
|
||
|
||
local function parseForCompletion(input, cursorPos)
|
||
local sofar = input:sub(1, cursorPos - 1)
|
||
local tokens = {}
|
||
for tok in sofar:gmatch("%S+") do tokens[#tokens+1] = tok end
|
||
local partial = sofar:match("%S+$") or ""
|
||
local isFirst = (#tokens == 0) or (sofar:sub(-1) ~= " " and #tokens == 1)
|
||
|
||
local partialDir, partialBase
|
||
if partial:find("/") then
|
||
partialDir = partial:match("^(.*/)") or "/"
|
||
partialBase = partial:match("[^/]*$") or ""
|
||
else
|
||
partialDir = nil
|
||
partialBase = partial
|
||
end
|
||
|
||
return tokens, partial, isFirst, partialDir, partialBase
|
||
end
|
||
|
||
local tabState = { last = nil, idx = 0, list = {} }
|
||
|
||
local function doTabComplete(input, cursorPos)
|
||
local tokens, partial, isFirst, partialDir, partialBase = parseForCompletion(input, cursorPos)
|
||
|
||
local candidates
|
||
if isFirst then
|
||
if partialDir then
|
||
local dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir)
|
||
candidates = listDir(dir, partialBase)
|
||
for i, c in ipairs(candidates) do candidates[i] = partialDir..c end
|
||
else
|
||
candidates = listCommands(partialBase)
|
||
end
|
||
else
|
||
local dir, base
|
||
if partialDir then
|
||
dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir)
|
||
base = partialBase
|
||
else
|
||
dir = syscall.getcwd()
|
||
base = partialBase
|
||
end
|
||
candidates = listDir(dir, base)
|
||
if partialDir then
|
||
for i, c in ipairs(candidates) do candidates[i] = partialDir..c end
|
||
end
|
||
end
|
||
|
||
if #candidates == 0 then
|
||
return input, cursorPos, false
|
||
end
|
||
|
||
local context = input.."\0"..tostring(cursorPos)
|
||
if tabState.last ~= context then
|
||
tabState.last = context
|
||
tabState.idx = 0
|
||
tabState.list = candidates
|
||
end
|
||
|
||
if #candidates == 1 then
|
||
local completed = candidates[1]
|
||
local before = input:sub(1, cursorPos - 1 - #partial)
|
||
local after = input:sub(cursorPos)
|
||
local newInput = before .. completed .. after
|
||
local newCursor = #before + #completed + 1
|
||
tabState.last = nil
|
||
return newInput, newCursor, true
|
||
end
|
||
|
||
local pre = commonPrefix(candidates)
|
||
if #pre > #partial then
|
||
local before = input:sub(1, cursorPos - 1 - #partial)
|
||
local after = input:sub(cursorPos)
|
||
local newInput = before .. pre .. after
|
||
local newCursor = #before + #pre + 1
|
||
tabState.last = newInput.."\0"..tostring(newCursor)
|
||
tabState.list = candidates
|
||
return newInput, newCursor, true
|
||
else
|
||
showCompletions(candidates)
|
||
return input, cursorPos, true
|
||
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 getGhostSuffix()
|
||
if #input == 0 then return "" end
|
||
local _, partial, isFirst, partialDir, partialBase = parseForCompletion(input, cursorPos)
|
||
if cursorPos ~= #input + 1 then return "" end
|
||
local candidates
|
||
if isFirst then
|
||
if partialDir then
|
||
local dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir)
|
||
candidates = listDir(dir, partialBase)
|
||
for i, c in ipairs(candidates) do candidates[i] = partialDir..c end
|
||
else
|
||
candidates = listCommands(partialBase)
|
||
end
|
||
else
|
||
local dir, base
|
||
if partialDir then
|
||
dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir)
|
||
base = partialBase
|
||
else
|
||
dir = syscall.getcwd()
|
||
base = partialBase
|
||
end
|
||
candidates = listDir(dir, base)
|
||
if partialDir then
|
||
for i, c in ipairs(candidates) do candidates[i] = partialDir..c end
|
||
end
|
||
end
|
||
if #candidates == 0 then return "" end
|
||
local pre = commonPrefix(candidates)
|
||
if #pre > #partial then
|
||
return pre:sub(#partial + 1)
|
||
end
|
||
return ""
|
||
end
|
||
|
||
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)
|
||
local after = string.sub(input, cursorPos+1)
|
||
syscall.write(1, after)
|
||
local ghost = getGhostSuffix()
|
||
if #ghost > 0 then
|
||
syscall.devctl(1,"sfgc",14)
|
||
syscall.write(1, ghost)
|
||
syscall.devctl(1,"sfgc",1)
|
||
syscall.write(1, " ")
|
||
else
|
||
syscall.write(1, " ")
|
||
end
|
||
end
|
||
|
||
while true do
|
||
local key = syscall.read(0)
|
||
if key and key ~= "" then
|
||
if key=="[D" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end
|
||
elseif key=="[C" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end
|
||
elseif key=="[A" then
|
||
if history<#commandHistory then
|
||
history=history+1
|
||
input=commandHistory[#commandHistory-history+1]
|
||
cursorPos=#input+1;dirty=true
|
||
end
|
||
elseif key=="[B" 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=="[H" then cursorPos=1;dirty=true
|
||
elseif key=="[F" then cursorPos=#input+1;dirty=true
|
||
elseif key=="[3~" then
|
||
if cursorPos<=#input then
|
||
input=string.sub(input,1,cursorPos-1)..string.sub(input,cursorPos+1)
|
||
dirty=true
|
||
end
|
||
elseif key=="\t" then
|
||
local newInput, newCursor, needsRedraw = doTabComplete(input, cursorPos)
|
||
if needsRedraw then
|
||
input = newInput; cursorPos = newCursor
|
||
local posStr = syscall.devctl(1, "gpos")
|
||
local sep = posStr:find(";")
|
||
local px = tonumber(posStr:sub(1, sep-1))
|
||
local py = tonumber(posStr:sub(sep+1))
|
||
if px > 1 then
|
||
syscall.devctl(1,"spos",1,py)
|
||
local tsz = syscall.devctl(1,"size") or "51;19"
|
||
local tw = tonumber(tsz:match("^(%d+)")) or 51
|
||
syscall.write(1, string.rep(" ", tw))
|
||
syscall.devctl(1,"spos",1,py)
|
||
end
|
||
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, "$ ")
|
||
posStr = syscall.devctl(1, "gpos")
|
||
sep = posStr:find(";")
|
||
curOffsetX = tonumber(posStr:sub(1, sep-1))
|
||
curOffsetY = tonumber(posStr:sub(sep+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
|
||
elseif #key == 1 and key:byte(1) >= 32 and key:byte(1) < 127 then
|
||
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 proc = syscall.spawn(function()
|
||
-- Open standard fds so programs that don't do it themselves work correctly.
|
||
syscall.open("/dev/tty/1", "r") -- fd 0 stdin
|
||
syscall.open("/dev/tty/1", "w") -- fd 1 stdout
|
||
syscall.open("/dev/null", "w") -- fd 2 stderr
|
||
-- exec replaces this coroutine's code with a fresh isolated environment
|
||
-- compiled from disk by the kernel (via loadExecutable -> freshUserEnv),
|
||
-- so the child cannot share any upvalue or syscall table state with hysh.
|
||
syscall.exec(cmdPath, {table.unpack(args, 2)})
|
||
end, progName)
|
||
|
||
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
|