mirror of
https://github.com/RGBCube/serenity
synced 2025-05-19 00:25: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:
parent
7b5ead64a5
commit
aa2df9277d
5 changed files with 73 additions and 41 deletions
|
@ -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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 });
|
||||
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
|
||||
// all but their last subnode before yielding to this, causing a command
|
||||
// like `foo && bar&` to effectively be `foo && (bar&)`.
|
||||
auto value = m_command->run(shell)->resolve_without_cast(shell);
|
||||
ASSERT(!value->is_job());
|
||||
|
||||
auto commands = value->resolve_as_commands(shell);
|
||||
auto commands = m_command->to_lazy_evaluated_commands(shell);
|
||||
for (auto& command : commands)
|
||||
command.should_wait = false;
|
||||
|
||||
|
@ -1207,8 +1216,8 @@ void Join::dump(int level) const
|
|||
|
||||
RefPtr<Value> Join::run(RefPtr<Shell> shell)
|
||||
{
|
||||
auto left = m_left->run(shell)->resolve_as_commands(shell);
|
||||
auto right = m_right->run(shell)->resolve_as_commands(shell);
|
||||
auto left = m_left->to_lazy_evaluated_commands(shell);
|
||||
auto right = m_right->to_lazy_evaluated_commands(shell);
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
return create<CommandSequenceValue>(move(commands));
|
||||
}
|
||||
|
@ -1315,8 +1324,8 @@ void Pipe::dump(int level) const
|
|||
|
||||
RefPtr<Value> Pipe::run(RefPtr<Shell> shell)
|
||||
{
|
||||
auto left = m_left->run(shell)->resolve_as_commands(shell);
|
||||
auto right = m_right->run(shell)->resolve_as_commands(shell);
|
||||
auto left = m_left->to_lazy_evaluated_commands(shell);
|
||||
auto right = m_right->to_lazy_evaluated_commands(shell);
|
||||
|
||||
auto last_in_left = left.take_last();
|
||||
auto first_in_right = right.take_first();
|
||||
|
@ -1512,7 +1521,7 @@ RefPtr<Value> Sequence::run(RefPtr<Shell> 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.
|
||||
if (left.size() == 1) {
|
||||
auto& command = left.first();
|
||||
|
@ -1523,7 +1532,7 @@ RefPtr<Value> Sequence::run(RefPtr<Shell> shell)
|
|||
if (left.last().should_wait)
|
||||
left.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::Sequence });
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@ struct Command {
|
|||
bool should_wait { true };
|
||||
bool is_pipe_source { false };
|
||||
bool should_notify_if_in_background { true };
|
||||
bool should_immediately_execute_next { false };
|
||||
|
||||
mutable RefPtr<Pipeline> pipeline;
|
||||
Vector<NodeWithAction> next_chain;
|
||||
|
@ -233,7 +234,7 @@ public:
|
|||
}
|
||||
|
||||
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; }
|
||||
|
||||
Vector<Command> to_lazy_evaluated_commands(RefPtr<Shell> shell);
|
||||
|
||||
protected:
|
||||
Position m_position;
|
||||
bool m_is_syntax_error { false };
|
||||
|
|
|
@ -167,12 +167,7 @@ RefPtr<AST::Node> Parser::parse_sequence()
|
|||
break;
|
||||
}
|
||||
|
||||
RefPtr<AST::Node> first = nullptr;
|
||||
if (auto control_structure = parse_control_structure())
|
||||
first = control_structure;
|
||||
|
||||
if (!first)
|
||||
first = parse_or_logical_sequence();
|
||||
auto first = parse_or_logical_sequence();
|
||||
|
||||
if (!first)
|
||||
return var_decls;
|
||||
|
@ -311,23 +306,27 @@ RefPtr<AST::Node> Parser::parse_and_logical_sequence()
|
|||
RefPtr<AST::Node> Parser::parse_pipe_sequence()
|
||||
{
|
||||
auto rule_start = push_start();
|
||||
auto command = parse_command();
|
||||
if (!command)
|
||||
auto left = parse_control_structure();
|
||||
if (!left) {
|
||||
if (auto cmd = parse_command())
|
||||
left = cmd;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
consume_while(is_whitespace);
|
||||
|
||||
if (peek() != '|')
|
||||
return command;
|
||||
return left;
|
||||
|
||||
consume();
|
||||
|
||||
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();
|
||||
return command;
|
||||
return left;
|
||||
}
|
||||
|
||||
RefPtr<AST::Node> Parser::parse_command()
|
||||
|
|
|
@ -107,7 +107,6 @@ toplevel :: sequence?
|
|||
|
||||
sequence :: variable_decls? or_logical_sequence terminator sequence
|
||||
| variable_decls? or_logical_sequence '&' sequence
|
||||
| variable_decls? control_structure terminator sequence
|
||||
| variable_decls? or_logical_sequence
|
||||
| variable_decls? terminator sequence
|
||||
|
||||
|
@ -125,6 +124,8 @@ variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '*
|
|||
|
||||
pipe_sequence :: command '|' pipe_sequence
|
||||
| command
|
||||
| control_structure '|' pipe_sequence
|
||||
| control_structure
|
||||
|
||||
control_structure :: for_expr
|
||||
| if_expr
|
||||
|
|
|
@ -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 (command.argv.is_empty()) {
|
||||
if (command.argv.is_empty() && !command.should_immediately_execute_next) {
|
||||
m_global_redirections.append(command.redirections);
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -481,6 +481,25 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
|
|||
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) {
|
||||
if (resolve_redirection(redirection) == IterationDecision::Break)
|
||||
return nullptr;
|
||||
|
@ -491,6 +510,19 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
|
|||
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;
|
||||
if (run_builtin(command, rewirings, retval)) {
|
||||
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);
|
||||
|
||||
for (auto& rewiring : rewirings) {
|
||||
#ifdef SH_DEBUG
|
||||
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)");
|
||||
if (apply_rewirings() == IterationDecision::Break)
|
||||
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();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue