From 6377a5824f6d27db2df0fe852443ff4f050d8efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 22 Apr 2015 10:43:34 +0200 Subject: [PATCH 1/4] sh.lua absorbed besh.lua's piping mechanics. RIP besh. --- .../opencomputers/loot/BetterShell/besh.lua | 495 ------------------ .../opencomputers/loot/BetterShell/besh.man | 34 -- .../opencomputers/loot/OpenOS/bin/ls.lua | 31 +- .../opencomputers/loot/OpenOS/bin/sh.lua | 280 ++++++++-- .../opencomputers/loot/OpenOS/usr/man/sh | 16 +- .../assets/opencomputers/loot/loot.properties | 1 - 6 files changed, 284 insertions(+), 573 deletions(-) delete mode 100644 src/main/resources/assets/opencomputers/loot/BetterShell/besh.lua delete mode 100644 src/main/resources/assets/opencomputers/loot/BetterShell/besh.man diff --git a/src/main/resources/assets/opencomputers/loot/BetterShell/besh.lua b/src/main/resources/assets/opencomputers/loot/BetterShell/besh.lua deleted file mode 100644 index 370d7ed5b..000000000 --- a/src/main/resources/assets/opencomputers/loot/BetterShell/besh.lua +++ /dev/null @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/BetterShell/besh.man b/src/main/resources/assets/opencomputers/loot/BetterShell/besh.man deleted file mode 100644 index 5167677d3..000000000 --- a/src/main/resources/assets/opencomputers/loot/BetterShell/besh.man +++ /dev/null @@ -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. \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/ls.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/ls.lua index aafad7e01..8117ff447 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/ls.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/ls.lua @@ -8,6 +8,10 @@ if #dirs == 0 then table.insert(dirs, ".") end +local function formatOutput() + return component.isAvailable("gpu") and io.output() == io.stdout +end + io.output():setvbuf("line") for i = 1, #dirs do local path = shell.resolve(dirs[i]) @@ -22,7 +26,7 @@ for i = 1, #dirs do io.write(reason .. "\n") else 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() component.gpu.setForeground(c) end @@ -48,15 +52,16 @@ for i = 1, #dirs do local col = 1 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)) end for _, d in ipairs(lsd) do if options.a or d:sub(1, 1) ~= "." then - io.write(text.padRight(d, m)) - if options.l or io.output() ~= io.stdout or col % columns == 0 then - io.write("\n") + if options.l or not formatOutput() or col % columns == 0 then + io.write(d .. "\n") + else + io.write(text.padRight(d, m)) end col = col + 1 end @@ -71,12 +76,16 @@ for i = 1, #dirs do setColor(0xFFFFFF) end if options.a or f:sub(1, 1) ~= "." then - io.write(text.padRight(f, m)) - if options.l then - setColor(0xFFFFFF) - io.write(fs.size(fs.concat(path, f)), "\n") - elseif io.output() ~= io.stdout or col % columns == 0 then - io.write("\n") + if not formatOutput() then + io.write(f .. "\n") + else + io.write(text.padRight(f, m)) + if options.l then + setColor(0xFFFFFF) + io.write(fs.size(fs.concat(path, f)), "\n") + elseif col % columns == 0 then + io.write("\n") + end end col = col + 1 end diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua index 81b87dd88..12ee95afc 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua @@ -8,6 +8,60 @@ local term = require("term") local text = require("text") 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 result = value:gsub("%$(%w+)", os.getenv):gsub("%$%b{}", function(match) return os.getenv(expand(match:sub(3, -2))) or match end) @@ -88,14 +142,13 @@ local function evaluate(value) return results end -local function execute(env, command, ...) - local parts, reason = text.tokenize(command) - if not parts then - return false, reason - elseif #parts == 0 then - return true +local function parseCommand(tokens, ...) + if #tokens == 0 then + return 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 = {} program = evaluate(program) for i = 2, #program do @@ -103,7 +156,7 @@ local function execute(env, command, ...) end local program, reason = shell.resolve(program[1], "lua") if not program then - return false, reason + return nil, reason end for i = 1, #args do for _, arg in ipairs(evaluate(args[i])) do @@ -111,37 +164,202 @@ local function execute(env, command, ...) end end 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 table.insert(args, arg) end table.insert(args, 1, true) 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 - -- Emulate CC behavior by making yields a filtered event.pull() - while args[1] and coroutine.status(thread) ~= "dead" do - result = table.pack(coroutine.resume(thread, table.unpack(args, 2, args.n))) - if coroutine.status(thread) ~= "dead" then - args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n))) - end - end - if not args[1] then - 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" + 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 + args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n))) + elseif not args[1] then + args[2] = debug.traceback(threads[i], args[2]) end - elseif type(result[2]) == "string" then - result[2] = debug.traceback(thread, result[2]) 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 return table.unpack(result, 1, result.n) end diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/sh b/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/sh index 4a8d7b393..9341521e6 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/sh +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/usr/man/sh @@ -5,7 +5,7 @@ SYNOPSIS sh 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: echo "a b" @@ -19,6 +19,20 @@ DESCRIPTION cp /bin/* /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`. EXAMPLES diff --git a/src/main/resources/assets/opencomputers/loot/loot.properties b/src/main/resources/assets/opencomputers/loot/loot.properties index 4e9e7d187..95e955686 100644 --- a/src/main/resources/assets/opencomputers/loot/loot.properties +++ b/src/main/resources/assets/opencomputers/loot/loot.properties @@ -5,7 +5,6 @@ # 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. #The color defaults to gray. It must be a dye's ore-dict name. -BetterShell=besh:1:dyeLightGray Builder=build:1:dyeYellow MazeGen=maze:1:dyeOrange Network=network:1:dyeLime From 76172d0dfbc650c65f27bfdf5f8de912dd67ba60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 22 Apr 2015 11:22:14 +0200 Subject: [PATCH 2/4] Made env variables local (i.e. os.getenv/setenv will operate per process now), to avoid programs running in parallel (via piping) to interfere. Copying env vars from last process to shell's env to avoid breaking compat. (cd.lua will still work that way, e.g.) Closes #634. --- .../assets/opencomputers/loot/OpenOS/bin/sh.lua | 13 ++++++++++--- .../opencomputers/loot/OpenOS/boot/02_os.lua | 3 --- .../opencomputers/loot/OpenOS/lib/process.lua | 17 +++++++++++------ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua index 12ee95afc..0a47315a2 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua @@ -266,6 +266,7 @@ local function execute(env, command, ...) local program, args, input, output, mode = table.unpack(commands[i]) local reason threads[i], reason = process.load(program, env, function() + os.setenv("_", program) if input then local file, reason = io.open(shell.resolve(input)) if not file then @@ -304,9 +305,6 @@ local function execute(env, command, ...) 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") @@ -351,6 +349,15 @@ local function execute(env, command, ...) end end + -- copy env vars from last process; mostly to ensure stuff like cd.lua works + local lastVars = rawget(process.info(threads[#threads]).data, "vars") + if lastVars then + local localVars = process.info().data.vars + for k,v in pairs(lastVars) do + localVars[k] = v + end + end + for _, input in ipairs(inputs) do input:close() end diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/boot/02_os.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/boot/02_os.lua index 1e759bf2b..22b727183 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/boot/02_os.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/boot/02_os.lua @@ -8,8 +8,6 @@ local function env() -- copy parent env when first requested; easiest way to keep things -- like number of env vars trivial (#vars). local data = require("process").info().data - --[[ TODO breaking change; will require set to be a shell built-in and - may break other programs relying on setenv being global. if not rawget(data, "vars") then local vars = {} for k, v in pairs(data.vars or {}) do @@ -17,7 +15,6 @@ local function env() end data.vars = vars end - --]] data.vars = data.vars or {} return data.vars end diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/lib/process.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/lib/process.lua index 35692818e..3ed63c42e 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/lib/process.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/lib/process.lua @@ -83,12 +83,17 @@ function process.running(level) -- kept for backwards compat, prefer process.inf end end -function process.info(level) - level = level or 1 - local process = findProcess() - while level > 1 and process do - process = process.parent - level = level - 1 +function process.info(levelOrThread) + local process + if type(levelOrThread) == "thread" then + process = findProcess(levelOrThread) + else + local level = levelOrThread or 1 + process = findProcess() + while level > 1 and process do + process = process.parent + level = level - 1 + end end if process then return {path=process.path, env=process.env, command=process.command, data=process.data} From 4f70ea4152feadd3f7f330b1d037de07d11cc815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 22 Apr 2015 11:22:33 +0200 Subject: [PATCH 3/4] Bump version number. --- build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.properties b/build.properties index ab649e181..8116216aa 100644 --- a/build.properties +++ b/build.properties @@ -1,7 +1,7 @@ minecraft.version=1.7.10 forge.version=10.13.2.1291 -oc.version=1.5.8 +oc.version=1.5.9 oc.subversion=dev ae2.version=rv2-beta-26 From 57714dad273f62dbd2c16fba6a241e66c5620cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 22 Apr 2015 11:38:09 +0200 Subject: [PATCH 4/4] Fixed ls -l in pure io mode. --- .../resources/assets/opencomputers/loot/OpenOS/bin/ls.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/ls.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/ls.lua index 8117ff447..06ad47f5b 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/ls.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/ls.lua @@ -77,7 +77,11 @@ for i = 1, #dirs do end if options.a or f:sub(1, 1) ~= "." then if not formatOutput() then - io.write(f .. "\n") + io.write(f) + if options.l then + io.write(" " .. fs.size(fs.concat(path, f))) + end + io.write("\n") else io.write(text.padRight(f, m)) if options.l then