999 lines
33 KiB
Plaintext
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
|