mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 10:57:34 +00:00
Shell: Add support for the POSIX 'read' builtin
This commit is contained in:
parent
aecd91aedc
commit
ef551a045d
2 changed files with 93 additions and 0 deletions
|
@ -15,6 +15,7 @@
|
||||||
#include <LibCore/ArgsParser.h>
|
#include <LibCore/ArgsParser.h>
|
||||||
#include <LibCore/DeprecatedFile.h>
|
#include <LibCore/DeprecatedFile.h>
|
||||||
#include <LibCore/EventLoop.h>
|
#include <LibCore/EventLoop.h>
|
||||||
|
#include <LibCore/File.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
@ -1781,6 +1782,97 @@ ErrorOr<int> Shell::builtin_argsparser_parse(Main::Arguments arguments)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorOr<int> Shell::builtin_read(Main::Arguments arguments)
|
||||||
|
{
|
||||||
|
if (!m_in_posix_mode) {
|
||||||
|
raise_error(ShellError::EvaluatedSyntaxError, "read: POSIX builtin used in non-POSIX mode");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool no_escape = false;
|
||||||
|
Vector<DeprecatedString> variables;
|
||||||
|
|
||||||
|
Core::ArgsParser parser;
|
||||||
|
parser.add_option(no_escape, "Do not interpret backslash escapes", "no-escape", 'r');
|
||||||
|
parser.add_positional_argument(variables, "Variables to read into", "variable", Core::ArgsParser::Required::Yes);
|
||||||
|
|
||||||
|
if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::Ignore))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
auto split_by_any_of = " \t\n"_short_string;
|
||||||
|
|
||||||
|
if (auto const* value_from_env = getenv("IFS"); value_from_env)
|
||||||
|
split_by_any_of = TRY(String::from_utf8({ value_from_env, strlen(value_from_env) }));
|
||||||
|
else if (auto split_by_variable = TRY(lookup_local_variable("IFS"sv)); split_by_variable)
|
||||||
|
split_by_any_of = TRY(const_cast<AST::Value&>(*split_by_variable).resolve_as_string(*this));
|
||||||
|
|
||||||
|
auto file = TRY(Core::File::standard_input());
|
||||||
|
auto buffered_stream = TRY(Core::BufferedFile::create(move(file)));
|
||||||
|
|
||||||
|
StringBuilder builder;
|
||||||
|
ByteBuffer buffer;
|
||||||
|
|
||||||
|
enum class LineState {
|
||||||
|
Done,
|
||||||
|
EscapedNewline,
|
||||||
|
};
|
||||||
|
auto read_line = [&]() -> ErrorOr<LineState> {
|
||||||
|
if (m_is_interactive && isatty(STDIN_FILENO)) {
|
||||||
|
// Show prompt
|
||||||
|
warn("read: ");
|
||||||
|
}
|
||||||
|
size_t attempted_line_size = 32;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
auto result = buffered_stream->read_line(TRY(buffer.get_bytes_for_writing(attempted_line_size)));
|
||||||
|
if (result.is_error() && result.error().is_errno() && result.error().code() == EMSGSIZE) {
|
||||||
|
attempted_line_size *= 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto used_bytes = TRY(move(result));
|
||||||
|
if (!no_escape && used_bytes.ends_with("\\\n"sv)) {
|
||||||
|
builder.append(used_bytes.substring_view(0, used_bytes.length() - 2));
|
||||||
|
return LineState::EscapedNewline;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (used_bytes.ends_with("\n"sv))
|
||||||
|
used_bytes = used_bytes.substring_view(0, used_bytes.length() - 1);
|
||||||
|
|
||||||
|
builder.append(used_bytes);
|
||||||
|
return LineState::Done;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LineState state;
|
||||||
|
do {
|
||||||
|
state = TRY(read_line());
|
||||||
|
} while (state == LineState::EscapedNewline);
|
||||||
|
|
||||||
|
auto line = builder.string_view();
|
||||||
|
if (variables.size() == 1) {
|
||||||
|
set_local_variable(variables[0], make_ref_counted<AST::StringValue>(TRY(String::from_utf8(line))));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fields = line.split_view_if(is_any_of(split_by_any_of), SplitBehavior::KeepEmpty);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < variables.size(); ++i) {
|
||||||
|
auto& variable = variables[i];
|
||||||
|
StringView variable_value;
|
||||||
|
if (i >= fields.size())
|
||||||
|
variable_value = ""sv;
|
||||||
|
else if (i == variables.size() - 1)
|
||||||
|
variable_value = line.substring_view_starting_from_substring(fields[i]);
|
||||||
|
else
|
||||||
|
variable_value = fields[i];
|
||||||
|
|
||||||
|
set_local_variable(variable, make_ref_counted<AST::StringValue>(TRY(String::from_utf8(variable_value))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool Shell::has_builtin(StringView name) const
|
bool Shell::has_builtin(StringView name) const
|
||||||
{
|
{
|
||||||
if (name == ":"sv)
|
if (name == ":"sv)
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
__ENUMERATE_SHELL_BUILTIN(noop) \
|
__ENUMERATE_SHELL_BUILTIN(noop) \
|
||||||
__ENUMERATE_SHELL_BUILTIN(break) \
|
__ENUMERATE_SHELL_BUILTIN(break) \
|
||||||
__ENUMERATE_SHELL_BUILTIN(continue) \
|
__ENUMERATE_SHELL_BUILTIN(continue) \
|
||||||
|
__ENUMERATE_SHELL_BUILTIN(read) \
|
||||||
__ENUMERATE_SHELL_BUILTIN(argsparser_parse)
|
__ENUMERATE_SHELL_BUILTIN(argsparser_parse)
|
||||||
|
|
||||||
#define ENUMERATE_SHELL_OPTIONS() \
|
#define ENUMERATE_SHELL_OPTIONS() \
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue