mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-13 09:18:05 -04:00
sh.lua absorbed besh.lua's piping mechanics. RIP besh.
This commit is contained in:
parent
33bf52ae94
commit
6377a5824f
@ -1,495 +0,0 @@
|
|||||||
-- BEtter SHell, a wrapper for the normal shell that adds many POSIX
|
|
||||||
-- features, such as pipes, redirects and variable expansion.
|
|
||||||
|
|
||||||
local component = require("component")
|
|
||||||
local computer = require("computer")
|
|
||||||
local event = require("event")
|
|
||||||
local fs = require("filesystem")
|
|
||||||
local process = require("process")
|
|
||||||
local shell = require("shell")
|
|
||||||
local term = require("term")
|
|
||||||
local text = require("text")
|
|
||||||
local unicode = require("unicode")
|
|
||||||
|
|
||||||
-- Avoid scoping issues by simple forward declaring all methods... it's
|
|
||||||
-- not a beauty, but it works :P
|
|
||||||
local expand, expandCmd, expandMath, expandParam, parseCommand, parseCommands
|
|
||||||
|
|
||||||
expandParam = function(param)
|
|
||||||
local par, word, op = nil, nil, nil
|
|
||||||
for _, oper in ipairs{':%-', '%-', ':=', '=', ':%?','%?', ':%+', '%+'} do
|
|
||||||
par, word = param:match("(.-)"..oper.."(.*)")
|
|
||||||
if word then
|
|
||||||
op = oper
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if word then
|
|
||||||
local stat = os.getenv(par)
|
|
||||||
if op == ':%-' then
|
|
||||||
if stat ~= '' and stat ~= nil then
|
|
||||||
return stat
|
|
||||||
else
|
|
||||||
return word
|
|
||||||
end
|
|
||||||
elseif op == '%-' then
|
|
||||||
if stat ~= '' and stat ~= nil then
|
|
||||||
return stat
|
|
||||||
elseif stat == '' then
|
|
||||||
return nil
|
|
||||||
elseif stat == nil then
|
|
||||||
return expand(word)
|
|
||||||
end
|
|
||||||
elseif op == ':=' then
|
|
||||||
if stat ~= '' and stat ~= nil then
|
|
||||||
return stat
|
|
||||||
else
|
|
||||||
os.setenv(par, word)
|
|
||||||
return expand(word)
|
|
||||||
end
|
|
||||||
elseif op == '=' then
|
|
||||||
if stat ~= '' and stat ~= nil then
|
|
||||||
return stat
|
|
||||||
elseif stat == '' then
|
|
||||||
return nil
|
|
||||||
elseif stat == nil then
|
|
||||||
os.setenv(par, word)
|
|
||||||
return expand(word)
|
|
||||||
end
|
|
||||||
elseif op == ':%?' then
|
|
||||||
if stat ~= '' and stat ~= nil then
|
|
||||||
return stat
|
|
||||||
else
|
|
||||||
error(par.." is not set!")
|
|
||||||
end
|
|
||||||
elseif op == '%?' then
|
|
||||||
if stat ~= '' and stat ~= nil then
|
|
||||||
return stat
|
|
||||||
elseif stat == '' then
|
|
||||||
return nil
|
|
||||||
elseif stat == nil then
|
|
||||||
error(par.." is not set")
|
|
||||||
end
|
|
||||||
elseif op == ':%+' then
|
|
||||||
if stat ~= '' and stat ~= nil then
|
|
||||||
return expand(word)
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
elseif op == '%+' then
|
|
||||||
if stat ~= nil then
|
|
||||||
return expand(word)
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif string.sub(param, 1,1) == '#' then
|
|
||||||
return #(os.getenv(param:sub(2, -1)))
|
|
||||||
else
|
|
||||||
return os.getenv(param)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
expandCmd = function(cmd)
|
|
||||||
return cmd
|
|
||||||
end
|
|
||||||
|
|
||||||
expandMath = function(expr)
|
|
||||||
local success, reason = load("return "..expr, os.getenv("SHELL"), 't', {})
|
|
||||||
if success then
|
|
||||||
return success()
|
|
||||||
else
|
|
||||||
return reason
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
expand = function(token)
|
|
||||||
local expr = {}
|
|
||||||
local matchStack = {}
|
|
||||||
local escaped = false
|
|
||||||
local lastEnd = 1
|
|
||||||
local doubleQuote = false
|
|
||||||
local singleQuote = false
|
|
||||||
local mathBoth
|
|
||||||
local endToken = {}
|
|
||||||
for i = 1, unicode.len(token) do
|
|
||||||
local char = unicode.sub(token, i, i)
|
|
||||||
if escaped then
|
|
||||||
if expr then
|
|
||||||
table.insert(expr, char)
|
|
||||||
end
|
|
||||||
escaped = false
|
|
||||||
elseif char == '\\' then
|
|
||||||
escaped = not escaped
|
|
||||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
|
||||||
lastEnd = i+1
|
|
||||||
elseif char == matchStack[#matchStack] then
|
|
||||||
local match
|
|
||||||
table.remove(matchStack)
|
|
||||||
if char == '}' then
|
|
||||||
local param = table.concat(table.remove(expr))
|
|
||||||
match = expandParam(param)
|
|
||||||
elseif char == ')' then
|
|
||||||
if expr[#expr].cmd then
|
|
||||||
local cmd = table.concat(table.remove(expr))
|
|
||||||
match = expandCmd(cmd)
|
|
||||||
elseif expr[#expr].math then
|
|
||||||
if not mathBoth then
|
|
||||||
mathBoth = i
|
|
||||||
elseif mathBoth == i - 1 then
|
|
||||||
local mth = table.concat(table.remove(expr))
|
|
||||||
match = expandMath(mth)
|
|
||||||
mathBoth = nil
|
|
||||||
else
|
|
||||||
return nil, "Unmatched )"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif char == '`' then
|
|
||||||
local cmd = table.concat(table.remove(expr))
|
|
||||||
match = expandCmd(cmd)
|
|
||||||
elseif char == "'" then
|
|
||||||
singleQuote = false
|
|
||||||
elseif char == '"' then
|
|
||||||
doubleQuote = false
|
|
||||||
end
|
|
||||||
if #expr > 0 then
|
|
||||||
table.insert(expr[#expr], match)
|
|
||||||
else
|
|
||||||
table.insert(endToken, match)
|
|
||||||
end
|
|
||||||
lastEnd = i+1
|
|
||||||
elseif char == '"' and not singleQuote then
|
|
||||||
doubleQuote = true
|
|
||||||
if #expr <= 1 then
|
|
||||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
|
||||||
end
|
|
||||||
table.insert(matchStack, '"')
|
|
||||||
lastEnd = i+1
|
|
||||||
elseif char == "'" and not doubleQuote then
|
|
||||||
singleQuote = not singleQuote
|
|
||||||
if #expr <= 0 then
|
|
||||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
|
||||||
end
|
|
||||||
table.insert(matchStack, "'")
|
|
||||||
lastEnd = i+1
|
|
||||||
elseif char == "$" and not singleQuote then
|
|
||||||
if #expr <= 0 then
|
|
||||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
|
||||||
end
|
|
||||||
table.insert(expr, {})
|
|
||||||
lastEnd = i -1
|
|
||||||
elseif char == '{' and #expr > 0 and #(expr[#expr]) == 0 then
|
|
||||||
table.insert(matchStack, '}')
|
|
||||||
if expr[#expr] == 0 then
|
|
||||||
expr[#expr].special = true
|
|
||||||
end
|
|
||||||
elseif char == '(' and #expr > 0 and #(expr[#expr]) == 0 then
|
|
||||||
table.insert(matchStack, ')')
|
|
||||||
if expr[#expr].cmd then
|
|
||||||
expr[#expr].cmd = false
|
|
||||||
expr[#expr].math = true
|
|
||||||
else
|
|
||||||
expr[#expr].cmd = true
|
|
||||||
end
|
|
||||||
elseif char == '`' then
|
|
||||||
table.insert(expr, {cmd = true})
|
|
||||||
table.insert(matchStack, '`')
|
|
||||||
elseif char == '"' and not singleQuote then
|
|
||||||
doubleQuote = not doubleQuote
|
|
||||||
elseif char == "'" and not doubleQuote then
|
|
||||||
singleQuote = not singleQuote
|
|
||||||
elseif #expr > 0 and (char:match("[%a%d_]") or #matchStack > 0) then
|
|
||||||
table.insert(expr[#expr], char)
|
|
||||||
elseif #expr > 0 then -- We are done with gathering the name
|
|
||||||
table.insert(endToken, os.getenv(table.concat(table.remove(expr))))
|
|
||||||
lastEnd = i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
while #expr > 0 do
|
|
||||||
local xpr = table.remove(expr)
|
|
||||||
table.insert(expr[#expr] or endToken, os.getenv(table.concat(xpr)))
|
|
||||||
lastEnd = #token + 1
|
|
||||||
end
|
|
||||||
if lastEnd <= #token then
|
|
||||||
table.insert(endToken, unicode.sub(token, lastEnd, -1))
|
|
||||||
end
|
|
||||||
return table.concat(endToken)
|
|
||||||
end
|
|
||||||
|
|
||||||
parseCommand = function(tokens)
|
|
||||||
if #tokens == 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Variable expansion for all command parts.
|
|
||||||
for i = 1, #tokens do
|
|
||||||
tokens[i] = expand(tokens[i])
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Resolve alias for command.
|
|
||||||
local program, args = shell.resolveAlias(tokens[1], table.pack(select(2, table.unpack(tokens))))
|
|
||||||
|
|
||||||
-- Find redirects.
|
|
||||||
local input, output, mode = nil, nil, "write"
|
|
||||||
tokens = args
|
|
||||||
args = {}
|
|
||||||
local function smt(call) -- state metatable factory
|
|
||||||
local function index(_, token)
|
|
||||||
if token == "<" or token == ">" or token == ">>" then
|
|
||||||
return "parse error near " .. token
|
|
||||||
end
|
|
||||||
call(token)
|
|
||||||
return "args" -- default, return to normal arg parsing
|
|
||||||
end
|
|
||||||
return {__index=index}
|
|
||||||
end
|
|
||||||
local sm = { -- state machine for redirect parsing
|
|
||||||
args = setmetatable({["<"]="input", [">"]="output", [">>"]="append"},
|
|
||||||
smt(function(token)
|
|
||||||
table.insert(args, token)
|
|
||||||
end)),
|
|
||||||
input = setmetatable({}, smt(function(token)
|
|
||||||
input = token
|
|
||||||
end)),
|
|
||||||
output = setmetatable({}, smt(function(token)
|
|
||||||
output = token
|
|
||||||
mode = "write"
|
|
||||||
end)),
|
|
||||||
append = setmetatable({}, smt(function(token)
|
|
||||||
output = token
|
|
||||||
mode = "append"
|
|
||||||
end))
|
|
||||||
}
|
|
||||||
-- Run state machine over tokens.
|
|
||||||
local state = "args"
|
|
||||||
for i = 1, #tokens do
|
|
||||||
local token = tokens[i]
|
|
||||||
state = sm[state][token]
|
|
||||||
if not sm[state] then
|
|
||||||
return nil, state
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return program, args, input, output, mode
|
|
||||||
end
|
|
||||||
|
|
||||||
parseCommands = function(command)
|
|
||||||
local tokens, reason = text.tokenize(command)
|
|
||||||
if not tokens then
|
|
||||||
return nil, reason
|
|
||||||
elseif #tokens == 0 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local commands, command = {}, {}
|
|
||||||
for i = 1, #tokens do
|
|
||||||
if tokens[i] == "|" then
|
|
||||||
if #command == 0 then
|
|
||||||
return nil, "parse error near '|'"
|
|
||||||
end
|
|
||||||
table.insert(commands, command)
|
|
||||||
command = {}
|
|
||||||
else
|
|
||||||
table.insert(command, tokens[i])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #command > 0 then
|
|
||||||
table.insert(commands, command)
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, #commands do
|
|
||||||
commands[i] = table.pack(parseCommand(commands[i]))
|
|
||||||
if commands[i][1] == nil then
|
|
||||||
return nil, commands[i][2]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return commands
|
|
||||||
end
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
local memoryStream = {}
|
|
||||||
|
|
||||||
function memoryStream:close()
|
|
||||||
self.closed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function memoryStream:seek()
|
|
||||||
return nil, "bad file descriptor"
|
|
||||||
end
|
|
||||||
|
|
||||||
function memoryStream:read(n)
|
|
||||||
if self.closed then
|
|
||||||
if self.buffer == "" and self.redirect.read then
|
|
||||||
return self.redirect.read:read(n)
|
|
||||||
end
|
|
||||||
return nil -- eof
|
|
||||||
end
|
|
||||||
if self.buffer == "" then
|
|
||||||
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
|
|
||||||
end
|
|
||||||
local result = string.sub(self.buffer, 1, n)
|
|
||||||
self.buffer = string.sub(self.buffer, n + 1)
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function memoryStream:write(value)
|
|
||||||
local ok
|
|
||||||
if self.redirect.write then
|
|
||||||
ok = self.redirect.write:write(value)
|
|
||||||
end
|
|
||||||
if not self.closed then
|
|
||||||
self.buffer = self.buffer .. value
|
|
||||||
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
|
|
||||||
ok = true
|
|
||||||
end
|
|
||||||
if ok then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return nil, "stream is closed"
|
|
||||||
end
|
|
||||||
|
|
||||||
function memoryStream.new()
|
|
||||||
local stream = {closed = false, buffer = "",
|
|
||||||
redirect = {}, result = {}, args = {}}
|
|
||||||
local metatable = {__index = memoryStream,
|
|
||||||
__gc = memoryStream.close,
|
|
||||||
__metatable = "memorystream"}
|
|
||||||
return setmetatable(stream, metatable)
|
|
||||||
end
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
local function execute(env, command, ...)
|
|
||||||
checkArg(1, command, "string")
|
|
||||||
local commands, reason = parseCommands(command)
|
|
||||||
if not commands then
|
|
||||||
return false, reason
|
|
||||||
end
|
|
||||||
if #commands == 0 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Piping data between programs works like so:
|
|
||||||
-- program1 gets its output replaced with our custom stream.
|
|
||||||
-- program2 gets its input replaced with our custom stream.
|
|
||||||
-- repeat for all programs
|
|
||||||
-- custom stream triggers execution of 'next' program after write.
|
|
||||||
-- custom stream triggers yield before read if buffer is empty.
|
|
||||||
-- custom stream may have 'redirect' entries for fallback/duplication.
|
|
||||||
local threads, pipes, inputs, outputs = {}, {}, {}, {}
|
|
||||||
for i = 1, #commands do
|
|
||||||
local program, args, input, output, mode = table.unpack(commands[i])
|
|
||||||
local reason
|
|
||||||
threads[i], reason = process.load(shell.resolve(program, "lua"), env, function()
|
|
||||||
if input then
|
|
||||||
local file, reason = io.open(shell.resolve(input))
|
|
||||||
if not file then
|
|
||||||
error(reason)
|
|
||||||
end
|
|
||||||
table.insert(inputs, file)
|
|
||||||
if pipes[i - 1] then
|
|
||||||
pipes[i - 1].stream.redirect.read = file
|
|
||||||
io.input(pipes[i - 1])
|
|
||||||
else
|
|
||||||
io.input(file)
|
|
||||||
end
|
|
||||||
elseif pipes[i - 1] then
|
|
||||||
io.input(pipes[i - 1])
|
|
||||||
end
|
|
||||||
if output then
|
|
||||||
local file, reason = io.open(shell.resolve(output), mode == "append" and "a" or "w")
|
|
||||||
if not file then
|
|
||||||
error(reason)
|
|
||||||
end
|
|
||||||
if mode == "append" then
|
|
||||||
io.write("\n")
|
|
||||||
end
|
|
||||||
table.insert(outputs, file)
|
|
||||||
if pipes[i] then
|
|
||||||
pipes[i].stream.redirect.write = file
|
|
||||||
io.output(pipes[i])
|
|
||||||
else
|
|
||||||
io.output(file)
|
|
||||||
end
|
|
||||||
elseif pipes[i] then
|
|
||||||
io.output(pipes[i])
|
|
||||||
end
|
|
||||||
end, command)
|
|
||||||
if not threads[i] then
|
|
||||||
return false, reason
|
|
||||||
end
|
|
||||||
|
|
||||||
if i < #commands then
|
|
||||||
pipes[i] = require("buffer").new("rw", memoryStream.new())
|
|
||||||
pipes[i]:setvbuf("no")
|
|
||||||
end
|
|
||||||
if i > 1 then
|
|
||||||
pipes[i - 1].stream.next = threads[i]
|
|
||||||
pipes[i - 1].stream.args = args
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = select(2, table.unpack(commands[1]))
|
|
||||||
table.insert(args, 1, true)
|
|
||||||
for _, arg in ipairs(table.pack(...)) do
|
|
||||||
table.insert(args, arg)
|
|
||||||
end
|
|
||||||
args.n = #args
|
|
||||||
local result = nil
|
|
||||||
for i = 1, #threads do
|
|
||||||
-- Emulate CC behavior by making yields a filtered event.pull()
|
|
||||||
while args[1] and coroutine.status(threads[i]) ~= "dead" do
|
|
||||||
result = table.pack(coroutine.resume(threads[i], table.unpack(args, 2, args.n)))
|
|
||||||
if coroutine.status(threads[i]) ~= "dead" then
|
|
||||||
if type(result[2]) == "string" then
|
|
||||||
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
|
||||||
else
|
|
||||||
args = {true, n=1}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if pipes[i] then
|
|
||||||
pipes[i]:close()
|
|
||||||
end
|
|
||||||
if i < #threads and not result[1] then
|
|
||||||
io.write(result[2])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for _, input in ipairs(inputs) do
|
|
||||||
input:close()
|
|
||||||
end
|
|
||||||
for _, output in ipairs(outputs) do
|
|
||||||
output:close()
|
|
||||||
end
|
|
||||||
if not args[1] then
|
|
||||||
return false, args[2]
|
|
||||||
end
|
|
||||||
if not result[1] and type(result[2]) == "table" and result[2].reason == "terminated" then
|
|
||||||
if result[2].code then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false, "terminated"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return table.unpack(result, 1, result.n)
|
|
||||||
end
|
|
||||||
|
|
||||||
local args, options = shell.parse(...)
|
|
||||||
local history = {}
|
|
||||||
|
|
||||||
if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then
|
|
||||||
-- interactive shell. use original shell for input but register self as
|
|
||||||
-- global SHELL for command execution.
|
|
||||||
local oldShell = os.getenv("SHELL")
|
|
||||||
os.setenv("SHELL", process.running())
|
|
||||||
os.execute("/bin/sh")
|
|
||||||
os.setenv("SHELL", oldShell)
|
|
||||||
else
|
|
||||||
-- execute command.
|
|
||||||
local result = table.pack(execute(...))
|
|
||||||
if not result[1] then
|
|
||||||
error(result[2])
|
|
||||||
end
|
|
||||||
return table.unpack(result, 2)
|
|
||||||
end
|
|
@ -1,34 +0,0 @@
|
|||||||
NAME
|
|
||||||
besh - alternative command interpreter (shell)
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
besh
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
This is an alternative to the the basic, built-in standard shell of OpenOS. It provides some additional functionality such as piping. Otherwise it behaves similar to the built-in shell. To run a command, enter it and press enter. The first token in a command will usually be a program. Any additional parameters will be passed along to the program.
|
|
||||||
|
|
||||||
Arguments to programs can be quoted, to provide strings with multiple spaces in them, for example:
|
|
||||||
echo "a b"
|
|
||||||
will print the string `a b` to the screen. It is also possible to use single quotes (echo 'a b').
|
|
||||||
|
|
||||||
Single quotes also suppress variable expansion. Per default, expressions like `$NAME` and `${NAME}` are expanded using environment variables (also accessible via the `os.getenv` method).
|
|
||||||
|
|
||||||
BeSh provides basic redirects and piping:
|
|
||||||
cat f > f2
|
|
||||||
copies the contents of file `f` to `f2`, for example.
|
|
||||||
echo "this is a \"test\"" >> f2
|
|
||||||
will append the string 'this is a "test"' to the file `f2`.
|
|
||||||
|
|
||||||
Redirects can be combined:
|
|
||||||
cat < f >> f2
|
|
||||||
will feed the contents of file `f` to cat, which will then output it (in append mode) to file `f2`.
|
|
||||||
|
|
||||||
Finally, pipes can be used to pass data between programs:
|
|
||||||
ls | cat > f
|
|
||||||
will enumerate the files and directories in the working directory, write them to its output stream, which is cat's input stream, which will in turn write the data to file `f`.
|
|
||||||
|
|
||||||
This shell also supports aliases, which can be created using `alias` and removed using `unalias` (or using the `shell` API). For example, `dir` is a standard alias for `ls`.
|
|
||||||
|
|
||||||
EXAMPLES
|
|
||||||
besh
|
|
||||||
Starts a new shell.
|
|
@ -8,6 +8,10 @@ if #dirs == 0 then
|
|||||||
table.insert(dirs, ".")
|
table.insert(dirs, ".")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function formatOutput()
|
||||||
|
return component.isAvailable("gpu") and io.output() == io.stdout
|
||||||
|
end
|
||||||
|
|
||||||
io.output():setvbuf("line")
|
io.output():setvbuf("line")
|
||||||
for i = 1, #dirs do
|
for i = 1, #dirs do
|
||||||
local path = shell.resolve(dirs[i])
|
local path = shell.resolve(dirs[i])
|
||||||
@ -22,7 +26,7 @@ for i = 1, #dirs do
|
|||||||
io.write(reason .. "\n")
|
io.write(reason .. "\n")
|
||||||
else
|
else
|
||||||
local function setColor(c)
|
local function setColor(c)
|
||||||
if component.isAvailable("gpu") and component.gpu.getForeground() ~= c then
|
if formatOutput() and component.gpu.getForeground() ~= c then
|
||||||
io.stdout:flush()
|
io.stdout:flush()
|
||||||
component.gpu.setForeground(c)
|
component.gpu.setForeground(c)
|
||||||
end
|
end
|
||||||
@ -48,15 +52,16 @@ for i = 1, #dirs do
|
|||||||
|
|
||||||
local col = 1
|
local col = 1
|
||||||
local columns = math.huge
|
local columns = math.huge
|
||||||
if component.isAvailable("gpu") and io.output() == io.stdout then
|
if formatOutput() then
|
||||||
columns = math.max(1, math.floor((component.gpu.getResolution() - 1) / m))
|
columns = math.max(1, math.floor((component.gpu.getResolution() - 1) / m))
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, d in ipairs(lsd) do
|
for _, d in ipairs(lsd) do
|
||||||
if options.a or d:sub(1, 1) ~= "." then
|
if options.a or d:sub(1, 1) ~= "." then
|
||||||
io.write(text.padRight(d, m))
|
if options.l or not formatOutput() or col % columns == 0 then
|
||||||
if options.l or io.output() ~= io.stdout or col % columns == 0 then
|
io.write(d .. "\n")
|
||||||
io.write("\n")
|
else
|
||||||
|
io.write(text.padRight(d, m))
|
||||||
end
|
end
|
||||||
col = col + 1
|
col = col + 1
|
||||||
end
|
end
|
||||||
@ -71,12 +76,16 @@ for i = 1, #dirs do
|
|||||||
setColor(0xFFFFFF)
|
setColor(0xFFFFFF)
|
||||||
end
|
end
|
||||||
if options.a or f:sub(1, 1) ~= "." then
|
if options.a or f:sub(1, 1) ~= "." then
|
||||||
io.write(text.padRight(f, m))
|
if not formatOutput() then
|
||||||
if options.l then
|
io.write(f .. "\n")
|
||||||
setColor(0xFFFFFF)
|
else
|
||||||
io.write(fs.size(fs.concat(path, f)), "\n")
|
io.write(text.padRight(f, m))
|
||||||
elseif io.output() ~= io.stdout or col % columns == 0 then
|
if options.l then
|
||||||
io.write("\n")
|
setColor(0xFFFFFF)
|
||||||
|
io.write(fs.size(fs.concat(path, f)), "\n")
|
||||||
|
elseif col % columns == 0 then
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
col = col + 1
|
col = col + 1
|
||||||
end
|
end
|
||||||
|
@ -8,6 +8,60 @@ local term = require("term")
|
|||||||
local text = require("text")
|
local text = require("text")
|
||||||
local unicode = require("unicode")
|
local unicode = require("unicode")
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local memoryStream = {}
|
||||||
|
|
||||||
|
function memoryStream:close()
|
||||||
|
self.closed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function memoryStream:seek()
|
||||||
|
return nil, "bad file descriptor"
|
||||||
|
end
|
||||||
|
|
||||||
|
function memoryStream:read(n)
|
||||||
|
if self.closed then
|
||||||
|
if self.buffer == "" and self.redirect.read then
|
||||||
|
return self.redirect.read:read(n)
|
||||||
|
end
|
||||||
|
return nil -- eof
|
||||||
|
end
|
||||||
|
if self.buffer == "" then
|
||||||
|
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
|
||||||
|
end
|
||||||
|
local result = string.sub(self.buffer, 1, n)
|
||||||
|
self.buffer = string.sub(self.buffer, n + 1)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function memoryStream:write(value)
|
||||||
|
local ok
|
||||||
|
if self.redirect.write then
|
||||||
|
ok = self.redirect.write:write(value)
|
||||||
|
end
|
||||||
|
if not self.closed then
|
||||||
|
self.buffer = self.buffer .. value
|
||||||
|
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
|
||||||
|
ok = true
|
||||||
|
end
|
||||||
|
if ok then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return nil, "stream is closed"
|
||||||
|
end
|
||||||
|
|
||||||
|
function memoryStream.new()
|
||||||
|
local stream = {closed = false, buffer = "",
|
||||||
|
redirect = {}, result = {}, args = {}}
|
||||||
|
local metatable = {__index = memoryStream,
|
||||||
|
__gc = memoryStream.close,
|
||||||
|
__metatable = "memorystream"}
|
||||||
|
return setmetatable(stream, metatable)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
local function expand(value)
|
local function expand(value)
|
||||||
local result = value:gsub("%$(%w+)", os.getenv):gsub("%$%b{}",
|
local result = value:gsub("%$(%w+)", os.getenv):gsub("%$%b{}",
|
||||||
function(match) return os.getenv(expand(match:sub(3, -2))) or match end)
|
function(match) return os.getenv(expand(match:sub(3, -2))) or match end)
|
||||||
@ -88,14 +142,13 @@ local function evaluate(value)
|
|||||||
return results
|
return results
|
||||||
end
|
end
|
||||||
|
|
||||||
local function execute(env, command, ...)
|
local function parseCommand(tokens, ...)
|
||||||
local parts, reason = text.tokenize(command)
|
if #tokens == 0 then
|
||||||
if not parts then
|
return
|
||||||
return false, reason
|
|
||||||
elseif #parts == 0 then
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
local program, args = shell.resolveAlias(parts[1], table.pack(select(2, table.unpack(parts))))
|
|
||||||
|
local program, args = shell.resolveAlias(tokens[1], table.pack(select(2, table.unpack(tokens))))
|
||||||
|
|
||||||
local eargs = {}
|
local eargs = {}
|
||||||
program = evaluate(program)
|
program = evaluate(program)
|
||||||
for i = 2, #program do
|
for i = 2, #program do
|
||||||
@ -103,7 +156,7 @@ local function execute(env, command, ...)
|
|||||||
end
|
end
|
||||||
local program, reason = shell.resolve(program[1], "lua")
|
local program, reason = shell.resolve(program[1], "lua")
|
||||||
if not program then
|
if not program then
|
||||||
return false, reason
|
return nil, reason
|
||||||
end
|
end
|
||||||
for i = 1, #args do
|
for i = 1, #args do
|
||||||
for _, arg in ipairs(evaluate(args[i])) do
|
for _, arg in ipairs(evaluate(args[i])) do
|
||||||
@ -111,37 +164,202 @@ local function execute(env, command, ...)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
args = eargs
|
args = eargs
|
||||||
|
|
||||||
|
-- Find redirects.
|
||||||
|
local input, output, mode = nil, nil, "write"
|
||||||
|
tokens = args
|
||||||
|
args = {}
|
||||||
|
local function smt(call) -- state metatable factory
|
||||||
|
local function index(_, token)
|
||||||
|
if token == "<" or token == ">" or token == ">>" then
|
||||||
|
return "parse error near " .. token
|
||||||
|
end
|
||||||
|
call(token)
|
||||||
|
return "args" -- default, return to normal arg parsing
|
||||||
|
end
|
||||||
|
return {__index=index}
|
||||||
|
end
|
||||||
|
local sm = { -- state machine for redirect parsing
|
||||||
|
args = setmetatable({["<"]="input", [">"]="output", [">>"]="append"},
|
||||||
|
smt(function(token)
|
||||||
|
table.insert(args, token)
|
||||||
|
end)),
|
||||||
|
input = setmetatable({}, smt(function(token)
|
||||||
|
input = token
|
||||||
|
end)),
|
||||||
|
output = setmetatable({}, smt(function(token)
|
||||||
|
output = token
|
||||||
|
mode = "write"
|
||||||
|
end)),
|
||||||
|
append = setmetatable({}, smt(function(token)
|
||||||
|
output = token
|
||||||
|
mode = "append"
|
||||||
|
end))
|
||||||
|
}
|
||||||
|
-- Run state machine over tokens.
|
||||||
|
local state = "args"
|
||||||
|
for i = 1, #tokens do
|
||||||
|
local token = tokens[i]
|
||||||
|
state = sm[state][token]
|
||||||
|
if not sm[state] then
|
||||||
|
return nil, state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return program, args, input, output, mode
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseCommands(command)
|
||||||
|
local tokens, reason = text.tokenize(command)
|
||||||
|
if not tokens then
|
||||||
|
return nil, reason
|
||||||
|
elseif #tokens == 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local commands, command = {}, {}
|
||||||
|
for i = 1, #tokens do
|
||||||
|
if tokens[i] == "|" then
|
||||||
|
if #command == 0 then
|
||||||
|
return nil, "parse error near '|'"
|
||||||
|
end
|
||||||
|
table.insert(commands, command)
|
||||||
|
command = {}
|
||||||
|
else
|
||||||
|
table.insert(command, tokens[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #command > 0 then -- push tail command
|
||||||
|
table.insert(commands, command)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #commands do
|
||||||
|
commands[i] = table.pack(parseCommand(commands[i]))
|
||||||
|
if commands[i][1] == nil then
|
||||||
|
return nil, commands[i][2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return commands
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local function execute(env, command, ...)
|
||||||
|
checkArg(1, command, "string")
|
||||||
|
local commands, reason = parseCommands(command)
|
||||||
|
if not commands then
|
||||||
|
return false, reason
|
||||||
|
end
|
||||||
|
if #commands == 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Piping data between programs works like so:
|
||||||
|
-- program1 gets its output replaced with our custom stream.
|
||||||
|
-- program2 gets its input replaced with our custom stream.
|
||||||
|
-- repeat for all programs
|
||||||
|
-- custom stream triggers execution of 'next' program after write.
|
||||||
|
-- custom stream triggers yield before read if buffer is empty.
|
||||||
|
-- custom stream may have 'redirect' entries for fallback/duplication.
|
||||||
|
local threads, pipes, inputs, outputs = {}, {}, {}, {}
|
||||||
|
for i = 1, #commands do
|
||||||
|
local program, args, input, output, mode = table.unpack(commands[i])
|
||||||
|
local reason
|
||||||
|
threads[i], reason = process.load(program, env, function()
|
||||||
|
if input then
|
||||||
|
local file, reason = io.open(shell.resolve(input))
|
||||||
|
if not file then
|
||||||
|
error("could not open '" .. input .. "': " .. reason, 0)
|
||||||
|
end
|
||||||
|
table.insert(inputs, file)
|
||||||
|
if pipes[i - 1] then
|
||||||
|
pipes[i - 1].stream.redirect.read = file
|
||||||
|
io.input(pipes[i - 1])
|
||||||
|
else
|
||||||
|
io.input(file)
|
||||||
|
end
|
||||||
|
elseif pipes[i - 1] then
|
||||||
|
io.input(pipes[i - 1])
|
||||||
|
end
|
||||||
|
if output then
|
||||||
|
local file, reason = io.open(shell.resolve(output), mode == "append" and "a" or "w")
|
||||||
|
if not file then
|
||||||
|
error("could not open '" .. output .. "': " .. reason, 0)
|
||||||
|
end
|
||||||
|
if mode == "append" then
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
|
table.insert(outputs, file)
|
||||||
|
if pipes[i] then
|
||||||
|
pipes[i].stream.redirect.write = file
|
||||||
|
io.output(pipes[i])
|
||||||
|
else
|
||||||
|
io.output(file)
|
||||||
|
end
|
||||||
|
elseif pipes[i] then
|
||||||
|
io.output(pipes[i])
|
||||||
|
end
|
||||||
|
end, command)
|
||||||
|
if not threads[i] then
|
||||||
|
return false, reason
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO needs moving of env vars into process lib/info
|
||||||
|
-- os.setenv("_", program)
|
||||||
|
|
||||||
|
if i < #commands then
|
||||||
|
pipes[i] = require("buffer").new("rw", memoryStream.new())
|
||||||
|
pipes[i]:setvbuf("no")
|
||||||
|
end
|
||||||
|
if i > 1 then
|
||||||
|
pipes[i - 1].stream.next = threads[i]
|
||||||
|
pipes[i - 1].stream.args = args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local args = select(2, table.unpack(commands[1]))
|
||||||
for _, arg in ipairs(table.pack(...)) do
|
for _, arg in ipairs(table.pack(...)) do
|
||||||
table.insert(args, arg)
|
table.insert(args, arg)
|
||||||
end
|
end
|
||||||
table.insert(args, 1, true)
|
table.insert(args, 1, true)
|
||||||
args.n = #args
|
args.n = #args
|
||||||
local thread, reason = process.load(program, env, nil, command)
|
|
||||||
if not thread then
|
|
||||||
return false, reason
|
|
||||||
end
|
|
||||||
os.setenv("_", program)
|
|
||||||
local result = nil
|
local result = nil
|
||||||
-- Emulate CC behavior by making yields a filtered event.pull()
|
for i = 1, #threads do
|
||||||
while args[1] and coroutine.status(thread) ~= "dead" do
|
-- Emulate CC behavior by making yields a filtered event.pull()
|
||||||
result = table.pack(coroutine.resume(thread, table.unpack(args, 2, args.n)))
|
while args[1] and coroutine.status(threads[i]) ~= "dead" do
|
||||||
if coroutine.status(thread) ~= "dead" then
|
result = table.pack(coroutine.resume(threads[i], table.unpack(args, 2, args.n)))
|
||||||
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
if coroutine.status(threads[i]) ~= "dead" then
|
||||||
end
|
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
||||||
end
|
elseif not args[1] then
|
||||||
if not args[1] then
|
args[2] = debug.traceback(threads[i], args[2])
|
||||||
return false, debug.traceback(thread, args[2])
|
|
||||||
end
|
|
||||||
if not result[1] then
|
|
||||||
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
|
||||||
if result[2].code then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false, "terminated"
|
|
||||||
end
|
end
|
||||||
elseif type(result[2]) == "string" then
|
|
||||||
result[2] = debug.traceback(thread, result[2])
|
|
||||||
end
|
end
|
||||||
|
if pipes[i] then
|
||||||
|
pipes[i]:close()
|
||||||
|
end
|
||||||
|
if not result[1] then
|
||||||
|
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||||
|
if result[2].code then
|
||||||
|
result.n = 1
|
||||||
|
else
|
||||||
|
result[2] = "terminated"
|
||||||
|
end
|
||||||
|
elseif type(result[2]) == "string" then
|
||||||
|
result[2] = debug.traceback(threads[i], result[2])
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, input in ipairs(inputs) do
|
||||||
|
input:close()
|
||||||
|
end
|
||||||
|
for _, output in ipairs(outputs) do
|
||||||
|
output:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not args[1] then
|
||||||
|
return false, args[2]
|
||||||
end
|
end
|
||||||
return table.unpack(result, 1, result.n)
|
return table.unpack(result, 1, result.n)
|
||||||
end
|
end
|
||||||
|
@ -5,7 +5,7 @@ SYNOPSIS
|
|||||||
sh
|
sh
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
This is the basic, built-in standard shell of OpenOS. It provides very basic functionality compared to what real OS's shells can achieve, but does the job for getting started. To run a command, enter it and press enter. The first token in a command will usually be a program. Any additional parameters will be passed along to the program.
|
This is the basic, built-in standard shell of OpenOS. It provides basic functionality and does the job for getting started. To run a command, enter it and press enter. The first token in a command will usually be a program. Any additional parameters will be passed along to the program.
|
||||||
|
|
||||||
Arguments to programs can be quoted, to provide strings with multiple spaces in them, for example:
|
Arguments to programs can be quoted, to provide strings with multiple spaces in them, for example:
|
||||||
echo "a b"
|
echo "a b"
|
||||||
@ -19,6 +19,20 @@ DESCRIPTION
|
|||||||
cp /bin/* /usr/bin/
|
cp /bin/* /usr/bin/
|
||||||
will copy all files from `/bin` to `/usr/bin`.
|
will copy all files from `/bin` to `/usr/bin`.
|
||||||
|
|
||||||
|
The shell provides basic redirects and piping:
|
||||||
|
cat f > f2
|
||||||
|
copies the contents of file `f` to `f2`, for example.
|
||||||
|
echo 'this is a "test"' >> f2
|
||||||
|
will append the string 'this is a "test"' to the file `f2`.
|
||||||
|
|
||||||
|
Redirects can be combined:
|
||||||
|
cat < f >> f2
|
||||||
|
will feed the contents of file `f` to cat, which will then output it (in append mode) to file `f2`.
|
||||||
|
|
||||||
|
Finally, pipes can be used to pass data between programs:
|
||||||
|
ls | cat > f
|
||||||
|
will enumerate the files and directories in the working directory, write them to its output stream, which is cat's input stream, which will in turn write the data to file `f`.
|
||||||
|
|
||||||
The shell also supports aliases, which can be created using `alias` and removed using `unalias` (or using the `shell` API). For example, `dir` is a standard alias for `ls`.
|
The shell also supports aliases, which can be created using `alias` and removed using `unalias` (or using the `shell` API). For example, `dir` is a standard alias for `ls`.
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
# weight 2 is two times as likely to be generated than an item with
|
# weight 2 is two times as likely to be generated than an item with
|
||||||
# weight 1. Weight 0 means it will not spawn at all.
|
# weight 1. Weight 0 means it will not spawn at all.
|
||||||
#The color defaults to gray. It must be a dye's ore-dict name.
|
#The color defaults to gray. It must be a dye's ore-dict name.
|
||||||
BetterShell=besh:1:dyeLightGray
|
|
||||||
Builder=build:1:dyeYellow
|
Builder=build:1:dyeYellow
|
||||||
MazeGen=maze:1:dyeOrange
|
MazeGen=maze:1:dyeOrange
|
||||||
Network=network:1:dyeLime
|
Network=network:1:dyeLime
|
||||||
|
Loading…
x
Reference in New Issue
Block a user