1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-16 20:15:07 +00:00

Shell: Allow control structures to appear in pipe sequences

This makes commands like the following to be possible:
```sh
$ ls && for $(seq 10) { echo $it }
$ ls | for $(seq 1) { cat > foobar }
```
This commit is contained in:
AnotherTest 2020-09-07 20:49:53 +04:30 committed by Andreas Kling
parent 7b5ead64a5
commit aa2df9277d
5 changed files with 73 additions and 41 deletions

View file

@ -107,6 +107,18 @@ void Node::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(RefPtr
} }
} }
Vector<Command> Node::to_lazy_evaluated_commands(RefPtr<Shell> shell)
{
if (would_execute()) {
// Wrap the node in a "should immediately execute next" command.
return {
Command { {}, {}, true, false, true, true, {}, { NodeWithAction(*this, NodeWithAction::Sequence) } }
};
}
return run(shell)->resolve_as_commands(shell);
}
void Node::dump(int level) const 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); print_indented(String::format("%s at %d:%d", class_name().characters(), m_position.start_offset, m_position.end_offset), level);
@ -184,7 +196,7 @@ void And::dump(int level) const
RefPtr<Value> And::run(RefPtr<Shell> shell) RefPtr<Value> And::run(RefPtr<Shell> shell)
{ {
auto commands = m_left->run(shell)->resolve_as_commands(shell); auto commands = m_left->to_lazy_evaluated_commands(shell);
commands.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::And }); commands.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::And });
return create<CommandSequenceValue>(move(commands)); return create<CommandSequenceValue>(move(commands));
} }
@ -340,10 +352,7 @@ RefPtr<Value> Background::run(RefPtr<Shell> shell)
// as it runs the node, which means nodes likes And and Or will evaluate // as it runs the node, which means nodes likes And and Or will evaluate
// all but their last subnode before yielding to this, causing a command // all but their last subnode before yielding to this, causing a command
// like `foo && bar&` to effectively be `foo && (bar&)`. // like `foo && bar&` to effectively be `foo && (bar&)`.
auto value = m_command->run(shell)->resolve_without_cast(shell); auto commands = m_command->to_lazy_evaluated_commands(shell);
ASSERT(!value->is_job());
auto commands = value->resolve_as_commands(shell);
for (auto& command : commands) for (auto& command : commands)
command.should_wait = false; command.should_wait = false;
@ -1207,8 +1216,8 @@ void Join::dump(int level) const
RefPtr<Value> Join::run(RefPtr<Shell> shell) RefPtr<Value> Join::run(RefPtr<Shell> shell)
{ {
auto left = m_left->run(shell)->resolve_as_commands(shell); auto left = m_left->to_lazy_evaluated_commands(shell);
auto right = m_right->run(shell)->resolve_as_commands(shell); auto right = m_right->to_lazy_evaluated_commands(shell);
return create<CommandSequenceValue>(join_commands(move(left), move(right))); return create<CommandSequenceValue>(join_commands(move(left), move(right)));
} }
@ -1263,7 +1272,7 @@ void Or::dump(int level) const
RefPtr<Value> Or::run(RefPtr<Shell> shell) RefPtr<Value> Or::run(RefPtr<Shell> shell)
{ {
auto commands = m_left->run(shell)->resolve_as_commands(shell); auto commands = m_left->to_lazy_evaluated_commands(shell);
commands.last().next_chain.empend(*m_right, NodeWithAction::Or); commands.last().next_chain.empend(*m_right, NodeWithAction::Or);
return create<CommandSequenceValue>(move(commands)); return create<CommandSequenceValue>(move(commands));
} }
@ -1315,8 +1324,8 @@ void Pipe::dump(int level) const
RefPtr<Value> Pipe::run(RefPtr<Shell> shell) RefPtr<Value> Pipe::run(RefPtr<Shell> shell)
{ {
auto left = m_left->run(shell)->resolve_as_commands(shell); auto left = m_left->to_lazy_evaluated_commands(shell);
auto right = m_right->run(shell)->resolve_as_commands(shell); auto right = m_right->to_lazy_evaluated_commands(shell);
auto last_in_left = left.take_last(); auto last_in_left = left.take_last();
auto first_in_right = right.take_first(); auto first_in_right = right.take_first();
@ -1512,7 +1521,7 @@ RefPtr<Value> Sequence::run(RefPtr<Shell> shell)
return execute_node->run(shell); return execute_node->run(shell);
} }
auto left = m_left->run(shell)->resolve_as_commands(shell); auto left = m_left->to_lazy_evaluated_commands(shell);
// This could happen if a comment is next to a command. // This could happen if a comment is next to a command.
if (left.size() == 1) { if (left.size() == 1) {
auto& command = left.first(); auto& command = left.first();
@ -1523,7 +1532,7 @@ RefPtr<Value> Sequence::run(RefPtr<Shell> shell)
if (left.last().should_wait) if (left.last().should_wait)
left.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::Sequence }); left.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::Sequence });
else else
left.append(m_right->run(shell)->resolve_as_commands(shell)); left.append(m_right->to_lazy_evaluated_commands(shell));
return create<CommandSequenceValue>(move(left)); return create<CommandSequenceValue>(move(left));
} }

View file

@ -196,6 +196,7 @@ struct Command {
bool should_wait { true }; bool should_wait { true };
bool is_pipe_source { false }; bool is_pipe_source { false };
bool should_notify_if_in_background { true }; bool should_notify_if_in_background { true };
bool should_immediately_execute_next { false };
mutable RefPtr<Pipeline> pipeline; mutable RefPtr<Pipeline> pipeline;
Vector<NodeWithAction> next_chain; Vector<NodeWithAction> next_chain;
@ -233,7 +234,7 @@ public:
} }
CommandValue(Vector<String> argv) CommandValue(Vector<String> argv)
: m_command({ move(argv), {}, true, false, true, nullptr, {} }) : m_command({ move(argv), {}, true, false, true, false, nullptr, {} })
{ {
} }
@ -410,6 +411,8 @@ public:
virtual RefPtr<Node> leftmost_trivial_literal() const { return nullptr; } virtual RefPtr<Node> leftmost_trivial_literal() const { return nullptr; }
Vector<Command> to_lazy_evaluated_commands(RefPtr<Shell> shell);
protected: protected:
Position m_position; Position m_position;
bool m_is_syntax_error { false }; bool m_is_syntax_error { false };

View file

@ -167,12 +167,7 @@ RefPtr<AST::Node> Parser::parse_sequence()
break; break;
} }
RefPtr<AST::Node> first = nullptr; auto first = parse_or_logical_sequence();
if (auto control_structure = parse_control_structure())
first = control_structure;
if (!first)
first = parse_or_logical_sequence();
if (!first) if (!first)
return var_decls; return var_decls;
@ -311,23 +306,27 @@ RefPtr<AST::Node> Parser::parse_and_logical_sequence()
RefPtr<AST::Node> Parser::parse_pipe_sequence() RefPtr<AST::Node> Parser::parse_pipe_sequence()
{ {
auto rule_start = push_start(); auto rule_start = push_start();
auto command = parse_command(); auto left = parse_control_structure();
if (!command) if (!left) {
return nullptr; if (auto cmd = parse_command())
left = cmd;
else
return nullptr;
}
consume_while(is_whitespace); consume_while(is_whitespace);
if (peek() != '|') if (peek() != '|')
return command; return left;
consume(); consume();
if (auto pipe_seq = parse_pipe_sequence()) { if (auto pipe_seq = parse_pipe_sequence()) {
return create<AST::Pipe>(move(command), move(pipe_seq)); // Pipe return create<AST::Pipe>(move(left), move(pipe_seq)); // Pipe
} }
putback(); putback();
return command; return left;
} }
RefPtr<AST::Node> Parser::parse_command() RefPtr<AST::Node> Parser::parse_command()

View file

@ -107,7 +107,6 @@ toplevel :: sequence?
sequence :: variable_decls? or_logical_sequence terminator sequence sequence :: variable_decls? or_logical_sequence terminator sequence
| variable_decls? or_logical_sequence '&' sequence | variable_decls? or_logical_sequence '&' sequence
| variable_decls? control_structure terminator sequence
| variable_decls? or_logical_sequence | variable_decls? or_logical_sequence
| variable_decls? terminator sequence | variable_decls? terminator sequence
@ -125,6 +124,8 @@ variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '*
pipe_sequence :: command '|' pipe_sequence pipe_sequence :: command '|' pipe_sequence
| command | command
| control_structure '|' pipe_sequence
| control_structure
control_structure :: for_expr control_structure :: for_expr
| if_expr | if_expr

View file

@ -439,7 +439,7 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
} }
// If the command is empty, store the redirections and apply them to all later commands. // If the command is empty, store the redirections and apply them to all later commands.
if (command.argv.is_empty()) { if (command.argv.is_empty() && !command.should_immediately_execute_next) {
m_global_redirections.append(command.redirections); m_global_redirections.append(command.redirections);
return nullptr; return nullptr;
} }
@ -481,6 +481,25 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
return IterationDecision::Continue; return IterationDecision::Continue;
}; };
auto apply_rewirings = [&] {
for (auto& rewiring : rewirings) {
#ifdef SH_DEBUG
dbgprintf("in %s<%d>, dup2(%d, %d)\n", command.argv.is_empty() ? "(<Empty>)" : command.argv[0].characters(), getpid(), rewiring.dest_fd, rewiring.source_fd);
#endif
int rc = dup2(rewiring.dest_fd, rewiring.source_fd);
if (rc < 0) {
perror("dup2(run)");
return IterationDecision::Break;
}
// dest_fd is closed via the `fds` collector, but rewiring.other_pipe_end->dest_fd
// isn't yet in that collector when the first child spawns.
if (rewiring.other_pipe_end && close(rewiring.other_pipe_end->dest_fd) < 0)
perror("close other pipe end");
}
return IterationDecision::Continue;
};
for (auto& redirection : m_global_redirections) { for (auto& redirection : m_global_redirections) {
if (resolve_redirection(redirection) == IterationDecision::Break) if (resolve_redirection(redirection) == IterationDecision::Break)
return nullptr; return nullptr;
@ -491,6 +510,19 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
return nullptr; return nullptr;
} }
if (command.should_immediately_execute_next) {
ASSERT(command.argv.is_empty());
SavedFileDescriptors fds { rewirings };
if (apply_rewirings() == IterationDecision::Break)
return nullptr;
for (auto& next_in_chain : command.next_chain)
run_tail(next_in_chain, 0);
return nullptr;
}
int retval = 0; int retval = 0;
if (run_builtin(command, rewirings, retval)) { if (run_builtin(command, rewirings, retval)) {
for (auto& next_in_chain : command.next_chain) for (auto& next_in_chain : command.next_chain)
@ -524,20 +556,8 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
tcsetattr(0, TCSANOW, &default_termios); tcsetattr(0, TCSANOW, &default_termios);
for (auto& rewiring : rewirings) { if (apply_rewirings() == IterationDecision::Break)
#ifdef SH_DEBUG return nullptr;
dbgprintf("in %s<%d>, dup2(%d, %d)\n", argv[0], getpid(), rewiring.dest_fd, rewiring.source_fd);
#endif
int rc = dup2(rewiring.dest_fd, rewiring.source_fd);
if (rc < 0) {
perror("dup2(run)");
return nullptr;
}
// dest_fd is closed via the `fds` collector, but rewiring.other_pipe_end->dest_fd
// isn't yet in that collector when the first child spawns.
if (rewiring.other_pipe_end && close(rewiring.other_pipe_end->dest_fd) < 0)
perror("close other pipe end");
}
fds.collect(); fds.collect();