summaryrefslogtreecommitdiff
path: root/Shell
diff options
context:
space:
mode:
authorAnotherTest <ali.mpfard@gmail.com>2020-06-22 15:37:20 +0430
committerAndreas Kling <kling@serenityos.org>2020-07-05 15:43:14 +0200
commitc5d0aa9a448dc1270804164b3cf56deb8f1d81d2 (patch)
treea751c05ba79043d53ce7a7e46b20bf8c4b602cae /Shell
parent42304d7bf1bf8b447b05018df7dee325f7a42e52 (diff)
downloadserenity-c5d0aa9a448dc1270804164b3cf56deb8f1d81d2.zip
Shell: Allow commands in variables, and properly substitute them on use
This allows the below interaction to work: ``` $ silence=(2>&1 >/dev/null) $ do_noisy_thing with these args $silence <nothing here lol> ```
Diffstat (limited to 'Shell')
-rw-r--r--Shell/AST.cpp131
-rw-r--r--Shell/AST.h40
-rw-r--r--Shell/Parser.cpp20
-rw-r--r--Shell/Parser.h1
-rw-r--r--Shell/Shell.cpp37
5 files changed, 161 insertions, 68 deletions
diff --git a/Shell/AST.cpp b/Shell/AST.cpp
index 732338737e..eac2501ee4 100644
--- a/Shell/AST.cpp
+++ b/Shell/AST.cpp
@@ -52,6 +52,31 @@ static inline void print_indented(const String& str, int indent)
dbgprintf("%.*c%s\n", indent * 2, ' ', str.characters());
}
+static inline Vector<Command> join_commands(Vector<Command> left, Vector<Command> right)
+{
+ Command command;
+
+ auto last_in_left = left.take_last();
+ auto first_in_right = right.take_first();
+
+ command.argv.append(last_in_left.argv);
+ command.argv.append(first_in_right.argv);
+
+ command.redirections.append(last_in_left.redirections);
+ command.redirections.append(first_in_right.redirections);
+
+ command.should_wait = first_in_right.should_wait && last_in_left.should_wait;
+ command.is_pipe_source = first_in_right.is_pipe_source;
+ command.should_notify_if_in_background = first_in_right.should_wait && last_in_left.should_notify_if_in_background;
+
+ Vector<Command> commands;
+ commands.append(left);
+ commands.append(command);
+ commands.append(right);
+
+ return commands;
+}
+
void Node::dump(int level) const
{
print_indented(String::format("%s at %d:%d", class_name().characters(), m_position.start_offset, m_position.end_offset), level);
@@ -164,8 +189,16 @@ void ListConcatenate::dump(int level) const
RefPtr<Value> ListConcatenate::run(TheExecutionInputType input_value)
{
- auto list = m_list->run(input_value);
- auto element = m_element->run(input_value);
+ auto list = m_list->run(input_value)->resolve_without_cast(input_value);
+ auto element = m_element->run(input_value)->resolve_without_cast(input_value);
+
+ if (list->is_command() || element->is_command()) {
+ auto joined_commands = join_commands(element->resolve_as_commands(input_value), list->resolve_as_commands(input_value));
+
+ if (joined_commands.size() == 1)
+ return create<CommandValue>(joined_commands[0]);
+ return create<CommandSequenceValue>(move(joined_commands));
+ }
return create<ListValue>({ move(element), move(list) });
}
@@ -304,8 +337,11 @@ RefPtr<Value> CastToCommand::run(TheExecutionInputType input_value)
return m_inner->run(input_value);
auto shell = input_value;
- auto argv = m_inner->run(input_value)->resolve_as_list(input_value);
+ auto value = m_inner->run(input_value)->resolve_without_cast(input_value);
+ if (value->is_command())
+ return value;
+ auto argv = value->resolve_as_list(input_value);
return create<CommandValue>(move(argv));
}
@@ -353,13 +389,24 @@ CastToCommand::~CastToCommand()
void CastToList::dump(int level) const
{
Node::dump(level);
- m_inner->dump(level + 1);
+ if (m_inner)
+ m_inner->dump(level + 1);
+ else
+ print_indented("(empty)", level + 1);
}
RefPtr<Value> CastToList::run(TheExecutionInputType input_value)
{
+ if (!m_inner)
+ return create<ListValue>({});
+
auto shell = input_value;
- auto values = m_inner->run(input_value)->resolve_as_list(input_value);
+ auto inner_value = m_inner->run(input_value);
+
+ if (inner_value->is_command())
+ return inner_value;
+
+ auto values = inner_value->resolve_as_list(input_value);
Vector<RefPtr<Value>> cast_values;
for (auto& value : values)
cast_values.append(create<StringValue>(value));
@@ -369,7 +416,8 @@ RefPtr<Value> CastToList::run(TheExecutionInputType input_value)
void CastToList::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
{
- m_inner->highlight_in_editor(editor, shell, metadata);
+ if (m_inner)
+ m_inner->highlight_in_editor(editor, shell, metadata);
}
HitTestResult CastToList::hit_test_position(size_t offset)
@@ -377,6 +425,9 @@ HitTestResult CastToList::hit_test_position(size_t offset)
if (!position().contains(offset))
return {};
+ if (!m_inner)
+ return {};
+
return m_inner->hit_test_position(offset);
}
@@ -520,7 +571,7 @@ void DynamicEvaluate::dump(int level) const
RefPtr<Value> DynamicEvaluate::run(TheExecutionInputType input_value)
{
- auto result = m_inner->run(input_value);
+ auto result = m_inner->run(input_value)->resolve_without_cast(input_value);
// Dynamic Evaluation behaves differently between strings and lists.
// Strings are treated as variables, and Lists are treated as commands.
if (result->is_string()) {
@@ -815,28 +866,10 @@ void Join::dump(int level) const
RefPtr<Value> Join::run(TheExecutionInputType input_value)
{
- Command command;
-
auto left = m_left->run(input_value)->resolve_as_commands(input_value);
auto right = m_right->run(input_value)->resolve_as_commands(input_value);
- auto last_in_left = left.take_last();
- auto first_in_right = right.take_first();
-
- command.argv.append(last_in_left.argv);
- command.argv.append(first_in_right.argv);
-
- command.redirections.append(last_in_left.redirections);
- command.redirections.append(first_in_right.redirections);
-
- command.should_wait = first_in_right.should_wait && last_in_left.should_wait;
-
- Vector<Command> commands;
- commands.append(left);
- commands.append(command);
- commands.append(right);
-
- return create<CommandSequenceValue>(move(commands));
+ return create<CommandSequenceValue>(join_commands(move(left), move(right)));
}
void Join::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
@@ -935,22 +968,18 @@ void Pipe::dump(int level) const
RefPtr<Value> Pipe::run(TheExecutionInputType input_value)
{
- int pipefd[2];
- int rc = pipe(pipefd);
- if (rc < 0) {
- dbg() << "Error: cannot pipe(): " << strerror(errno);
- return create<StringValue>("");
- }
auto left = m_left->run(input_value)->resolve_as_commands(input_value);
auto right = m_right->run(input_value)->resolve_as_commands(input_value);
auto last_in_left = left.take_last();
auto first_in_right = right.take_first();
- last_in_left.redirections.append(*new FdRedirection(STDOUT_FILENO, pipefd[1], Rewiring::Close::Destination));
+ auto pipe_write_end = new FdRedirection(STDIN_FILENO, -1, Rewiring::Close::Destination);
+ auto pipe_read_end = new FdRedirection(STDOUT_FILENO, -1, pipe_write_end, Rewiring::Close::RefreshDestination);
+ first_in_right.redirections.append(*pipe_write_end);
+ last_in_left.redirections.append(*pipe_read_end);
last_in_left.should_wait = false;
last_in_left.is_pipe_source = true;
- first_in_right.redirections.append(*new FdRedirection(STDIN_FILENO, pipefd[0], Rewiring::Close::Destination));
Vector<Command> commands;
commands.append(left);
@@ -1247,8 +1276,8 @@ void Juxtaposition::dump(int level) const
RefPtr<Value> Juxtaposition::run(TheExecutionInputType input_value)
{
- auto left_value = m_left->run(input_value);
- auto right_value = m_right->run(input_value);
+ auto left_value = m_left->run(input_value)->resolve_without_cast(input_value);
+ auto right_value = m_right->run(input_value)->resolve_without_cast(input_value);
auto left = left_value->resolve_as_list(input_value);
auto right = right_value->resolve_as_list(input_value);
@@ -1539,6 +1568,8 @@ RefPtr<Value> VariableDeclarations::run(TheExecutionInputType input_value)
if (value->is_list()) {
auto parts = value->resolve_as_list(input_value);
shell->set_local_variable(name, adopt(*new ListValue(move(parts))));
+ } else if (value->is_command()) {
+ shell->set_local_variable(name, value);
} else {
auto part = value->resolve_as_list(input_value);
shell->set_local_variable(name, adopt(*new StringValue(part[0])));
@@ -1678,9 +1709,7 @@ SimpleVariableValue::~SimpleVariableValue()
}
Vector<String> SimpleVariableValue::resolve_as_list(TheExecutionInputType input_value)
{
- auto shell = input_value;
-
- if (auto value = shell->lookup_local_variable(m_name))
+ if (auto value = resolve_without_cast(input_value); value != this)
return value->resolve_as_list(input_value);
char* env_value = getenv(m_name.characters());
@@ -1695,6 +1724,16 @@ Vector<String> SimpleVariableValue::resolve_as_list(TheExecutionInputType input_
return res;
}
+RefPtr<Value> SimpleVariableValue::resolve_without_cast(TheExecutionInputType input_value)
+{
+ auto shell = input_value;
+
+ if (auto value = shell->lookup_local_variable(m_name))
+ return value;
+
+ return this;
+}
+
SpecialVariableValue::~SpecialVariableValue()
{
}
@@ -1723,28 +1762,24 @@ Vector<String> TildeValue::resolve_as_list(TheExecutionInputType input_value)
return { shell->expand_tilde(builder.to_string()) };
}
-Result<Rewiring, String> CloseRedirection::apply() const
+Result<RefPtr<Rewiring>, String> CloseRedirection::apply()
{
- auto rc = close(fd);
- if (rc < 0)
- return String { strerror(errno) };
-
- return String {};
+ return static_cast<RefPtr<Rewiring>>((adopt(*new Rewiring(fd, fd, Rewiring::Close::ImmediatelyCloseDestination))));
}
CloseRedirection::~CloseRedirection()
{
}
-Result<Rewiring, String> PathRedirection::apply() const
+Result<RefPtr<Rewiring>, String> PathRedirection::apply()
{
- auto check_fd_and_return = [my_fd = this->fd](int fd, const String& path) -> Result<Rewiring, String> {
+ auto check_fd_and_return = [my_fd = this->fd](int fd, const String& path) -> Result<RefPtr<Rewiring>, String> {
if (fd < 0) {
String error = strerror(errno);
dbg() << "open() failed for '" << path << "' with " << error;
return error;
}
- return Rewiring { my_fd, fd };
+ return static_cast<RefPtr<Rewiring>>((adopt(*new Rewiring(my_fd, fd, Rewiring::Close::Destination))));
};
switch (direction) {
case AST::PathRedirection::WriteAppend:
diff --git a/Shell/AST.h b/Shell/AST.h
index 25b85c4f3f..9094437bad 100644
--- a/Shell/AST.h
+++ b/Shell/AST.h
@@ -50,18 +50,36 @@ struct Position {
bool contains(size_t offset) const { return start_offset <= offset && offset <= end_offset; }
};
-struct Rewiring {
+struct Rewiring : public RefCounted<Rewiring> {
int source_fd { -1 };
int dest_fd { -1 };
+ Rewiring* other_pipe_end { nullptr };
enum class Close {
None,
Source,
Destination,
- } must_be_closed { Close::None };
+ RefreshDestination,
+ ImmediatelyCloseDestination,
+ } fd_action { Close::None };
+
+ Rewiring(int source, int dest, Close close = Close::None)
+ : source_fd(source)
+ , dest_fd(dest)
+ , fd_action(close)
+ {
+ }
+
+ Rewiring(int source, int dest, Rewiring* other_end, Close close)
+ : source_fd(source)
+ , dest_fd(dest)
+ , other_pipe_end(other_end)
+ , fd_action(close)
+ {
+ }
};
struct Redirection : public RefCounted<Redirection> {
- virtual Result<Rewiring, String> apply() const = 0;
+ virtual Result<RefPtr<Rewiring>, String> apply() = 0;
virtual ~Redirection();
virtual bool is_path_redirection() const { return false; }
virtual bool is_fd_redirection() const { return false; }
@@ -71,7 +89,7 @@ struct Redirection : public RefCounted<Redirection> {
struct CloseRedirection : public Redirection {
int fd { -1 };
- virtual Result<Rewiring, String> apply() const override;
+ virtual Result<RefPtr<Rewiring>, String> apply() override;
virtual ~CloseRedirection();
CloseRedirection(int fd)
: fd(fd)
@@ -92,7 +110,7 @@ struct PathRedirection : public Redirection {
ReadWrite,
} direction { Read };
- virtual Result<Rewiring, String> apply() const override;
+ virtual Result<RefPtr<Rewiring>, String> apply() override;
virtual ~PathRedirection();
PathRedirection(String path, int fd, decltype(direction) direction)
: path(move(path))
@@ -108,10 +126,14 @@ private:
struct FdRedirection : public Redirection
, public Rewiring {
- virtual Result<Rewiring, String> apply() const override { return *this; }
+ virtual Result<RefPtr<Rewiring>, String> apply() override { return static_cast<RefPtr<Rewiring>>(this); }
virtual ~FdRedirection();
FdRedirection(int source, int dest, Rewiring::Close close)
- : Rewiring({ source, dest, close })
+ : Rewiring(source, dest, close)
+ {
+ }
+ FdRedirection(int source, int dest, Rewiring* pipe_end, Rewiring::Close close)
+ : Rewiring(source, dest, pipe_end, close)
{
}
@@ -136,6 +158,7 @@ class Value : public RefCounted<Value> {
public:
virtual Vector<String> resolve_as_list(TheExecutionInputType) = 0;
virtual Vector<Command> resolve_as_commands(TheExecutionInputType);
+ virtual RefPtr<Value> resolve_without_cast(TheExecutionInputType) { return this; }
virtual ~Value();
virtual bool is_command() const { return false; }
virtual bool is_glob() const { return false; }
@@ -245,6 +268,7 @@ private:
class SimpleVariableValue final : public Value {
public:
virtual Vector<String> resolve_as_list(TheExecutionInputType) override;
+ RefPtr<Value> resolve_without_cast(TheExecutionInputType) override;
virtual ~SimpleVariableValue();
SimpleVariableValue(String name)
: m_name(name)
@@ -305,6 +329,7 @@ public:
virtual bool is_glob() const { return false; }
virtual bool is_tilde() const { return false; }
virtual bool is_variable_decls() const { return false; }
+ virtual bool is_syntax_error() const { return false; }
virtual bool is_list() const { return false; }
@@ -745,6 +770,7 @@ private:
virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
virtual HitTestResult hit_test_position(size_t) override { return { nullptr, nullptr }; }
virtual String class_name() const override { return "SyntaxError"; }
+ virtual bool is_syntax_error() const override { return true; }
};
class Tilde final : public Node {
diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp
index 7e715cf416..c06c51577c 100644
--- a/Shell/Parser.cpp
+++ b/Shell/Parser.cpp
@@ -200,7 +200,19 @@ RefPtr<AST::Node> Parser::parse_variable_decls()
auto name_expr = create<AST::BarewordLiteral>(move(var_name));
+ auto start = push_start();
auto expression = parse_expression();
+ if (!expression || expression->is_syntax_error()) {
+ m_offset = start->offset;
+ if (peek() == '(') {
+ consume();
+ auto command = parse_pipe_sequence();
+ expect(')');
+ if (!command)
+ m_offset = start->offset;
+ expression = command;
+ }
+ }
if (!expression) {
if (is_whitespace(peek())) {
auto string_start = push_start();
@@ -432,10 +444,10 @@ RefPtr<AST::Node> Parser::parse_expression()
if (starting_char == '(') {
consume();
auto list = parse_list_expression();
- if (!list)
- list = create<AST::SyntaxError>();
- if (!expect(')'))
- return read_concat(create<AST::SyntaxError>());
+ if (!expect(')')) {
+ m_offset = rule_start->offset;
+ return nullptr;
+ }
return read_concat(create<AST::CastToList>(move(list))); // Cast To List
}
diff --git a/Shell/Parser.h b/Shell/Parser.h
index 54b2d410ae..de47af0b46 100644
--- a/Shell/Parser.h
+++ b/Shell/Parser.h
@@ -107,6 +107,7 @@ sequence :: variable_decls? pipe_sequence ';' sequence
| variable_decls? pipe_sequence
variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '*
+ | identifier '=' '(' pipe_sequence ')' (' '+ variable_decls)? ' '*
pipe_sequence :: command '|' pipe_sequence
| command
diff --git a/Shell/Shell.cpp b/Shell/Shell.cpp
index 5a0d413353..e18f1995bb 100644
--- a/Shell/Shell.cpp
+++ b/Shell/Shell.cpp
@@ -324,7 +324,7 @@ int Shell::run_command(const StringView& cmd)
if (!command)
return 0;
-#ifndef SH_DEBUG
+#ifdef SH_DEBUG
dbg() << "Command follows";
command->dump(0);
#endif
@@ -349,7 +349,7 @@ RefPtr<Job> Shell::run_command(AST::Command& command)
FileDescriptionCollector fds;
// Resolve redirections.
- Vector<AST::Rewiring> rewirings;
+ NonnullRefPtrVector<AST::Rewiring> rewirings;
for (auto& redirection : command.redirections) {
auto rewiring_result = redirection->apply();
if (rewiring_result.is_error()) {
@@ -358,12 +358,29 @@ RefPtr<Job> Shell::run_command(AST::Command& command)
continue;
}
auto& rewiring = rewiring_result.value();
- rewirings.append(rewiring);
- if (rewiring.must_be_closed == AST::Rewiring::Close::Source) {
- fds.add(rewiring.source_fd);
- } else if (rewiring.must_be_closed == AST::Rewiring::Close::Destination) {
- fds.add(rewiring.dest_fd);
+ if (rewiring->fd_action != AST::Rewiring::Close::ImmediatelyCloseDestination)
+ rewirings.append(*rewiring);
+
+ if (rewiring->fd_action == AST::Rewiring::Close::Source) {
+ fds.add(rewiring->source_fd);
+ } else if (rewiring->fd_action == AST::Rewiring::Close::Destination) {
+ if (rewiring->dest_fd != -1)
+ fds.add(rewiring->dest_fd);
+ } else if (rewiring->fd_action == AST::Rewiring::Close::ImmediatelyCloseDestination) {
+ fds.add(rewiring->dest_fd);
+ } else if (rewiring->fd_action == AST::Rewiring::Close::RefreshDestination) {
+ ASSERT(rewiring->other_pipe_end);
+
+ int pipe_fd[2];
+ int rc = pipe(pipe_fd);
+ if (rc < 0) {
+ perror("pipe(RedirRefresh)");
+ return nullptr;
+ }
+ rewiring->dest_fd = pipe_fd[1];
+ rewiring->other_pipe_end->dest_fd = pipe_fd[0]; // This fd will be added to the collection on one of the next iterations.
+ fds.add(pipe_fd[1]);
}
}
@@ -377,7 +394,7 @@ RefPtr<Job> Shell::run_command(AST::Command& command)
#endif
int rc = dup2(rewiring.dest_fd, rewiring.source_fd);
if (rc < 0) {
- perror("dup2");
+ perror("dup2(run)");
return nullptr;
}
}
@@ -410,7 +427,7 @@ RefPtr<Job> Shell::run_command(AST::Command& command)
#endif
int rc = dup2(rewiring.dest_fd, rewiring.source_fd);
if (rc < 0) {
- perror("dup2");
+ perror("dup2(run)");
return nullptr;
}
}
@@ -457,6 +474,8 @@ RefPtr<Job> Shell::run_command(AST::Command& command)
job->disown();
};
+ fds.collect();
+
return *job;
}