1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-15 10:04:59 +00:00

Shell: Add support for ARGV (and $*, $#)

This patchset also adds the 'shift' builtin, as well as the usual tests.
closes #2948.
This commit is contained in:
AnotherTest 2020-08-04 09:27:25 +04:30 committed by Andreas Kling
parent 192b2383ac
commit 12af65c1c9
8 changed files with 79 additions and 1 deletions

View file

@ -2004,6 +2004,7 @@ RefPtr<Value> SimpleVariableValue::resolve_without_cast(RefPtr<Shell> shell)
SpecialVariableValue::~SpecialVariableValue() SpecialVariableValue::~SpecialVariableValue()
{ {
} }
Vector<String> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell) Vector<String> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell)
{ {
switch (m_name) { switch (m_name) {
@ -2011,6 +2012,19 @@ Vector<String> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell)
return { String::number(shell->last_return_code) }; return { String::number(shell->last_return_code) };
case '$': case '$':
return { String::number(getpid()) }; return { String::number(getpid()) };
case '*':
if (auto argv = shell->lookup_local_variable("ARGV"))
return argv->resolve_as_list(shell);
return {};
case '#':
if (auto argv = shell->lookup_local_variable("ARGV")) {
if (argv->is_list()) {
auto list_argv = static_cast<AST::ListValue*>(argv.ptr());
return { String::number(list_argv->values().size()) };
}
return { "1" };
}
return { "0" };
default: default:
return { "" }; return { "" };
} }

View file

@ -233,6 +233,7 @@ public:
} }
const Vector<RefPtr<Value>>& values() const { return m_contained_values; } const Vector<RefPtr<Value>>& values() const { return m_contained_values; }
Vector<RefPtr<Value>>& values() { return m_contained_values; }
private: private:
Vector<RefPtr<Value>> m_contained_values; Vector<RefPtr<Value>> m_contained_values;

View file

@ -678,6 +678,40 @@ int Shell::builtin_setopt(int argc, const char** argv)
return 0; return 0;
} }
int Shell::builtin_shift(int argc, const char** argv)
{
int count = 1;
Core::ArgsParser parser;
parser.add_positional_argument(count, "Shift count", "count", Core::ArgsParser::Required::No);
if (!parser.parse(argc, const_cast<char**>(argv), false))
return 1;
if (count < 1)
return 0;
auto argv_ = lookup_local_variable("ARGV");
if (!argv_) {
fprintf(stderr, "shift: ARGV is unset\n");
return 1;
}
if (!argv_->is_list())
argv_ = *new AST::ListValue({ argv_ });
auto& values = static_cast<AST::ListValue*>(argv_.ptr())->values();
if ((size_t)count > values.size()) {
fprintf(stderr, "shift: shift count must not be greater than %zu\n", values.size());
return 1;
}
for (auto i = 0; i < count; ++i)
values.take_first();
return 0;
}
int Shell::builtin_time(int argc, const char** argv) int Shell::builtin_time(int argc, const char** argv)
{ {
Vector<const char*> args; Vector<const char*> args;

View file

@ -772,6 +772,8 @@ RefPtr<AST::Node> Parser::parse_variable()
switch (peek()) { switch (peek()) {
case '$': case '$':
case '?': case '?':
case '*':
case '#':
return create<AST::SpecialVariable>(consume()); // Variable Special return create<AST::SpecialVariable>(consume()); // Variable Special
default: default:
break; break;

View file

@ -162,6 +162,8 @@ dquoted_string_inner :: '\' . dquoted_string_inner? {concat}
variable :: '$' identifier variable :: '$' identifier
| '$' '$' | '$' '$'
| '$' '?' | '$' '?'
| '$' '*'
| '$' '#'
| ... | ...
comment :: '#' [^\n]* comment :: '#' [^\n]*

View file

@ -53,6 +53,7 @@
__ENUMERATE_SHELL_BUILTIN(pushd) \ __ENUMERATE_SHELL_BUILTIN(pushd) \
__ENUMERATE_SHELL_BUILTIN(popd) \ __ENUMERATE_SHELL_BUILTIN(popd) \
__ENUMERATE_SHELL_BUILTIN(setopt) \ __ENUMERATE_SHELL_BUILTIN(setopt) \
__ENUMERATE_SHELL_BUILTIN(shift) \
__ENUMERATE_SHELL_BUILTIN(time) \ __ENUMERATE_SHELL_BUILTIN(time) \
__ENUMERATE_SHELL_BUILTIN(jobs) \ __ENUMERATE_SHELL_BUILTIN(jobs) \
__ENUMERATE_SHELL_BUILTIN(disown) \ __ENUMERATE_SHELL_BUILTIN(disown) \

View file

@ -0,0 +1,15 @@
#!/bin/sh
test "$*" = "" || echo "Fail: Argv list not empty" && exit 1
test "$#" -eq 0 || echo "Fail: Argv list empty but count non-zero" && exit 1
test "$ARGV" = "$*" || echo "Fail: \$ARGV not equal to \$*" && exit 1
ARGV=(1 2 3)
test "$#" -eq 3 || echo "Fail: Assignment to ARGV does not affect \$#" && exit 1
test "$*" = "1 2 3" || echo "Fail: Assignment to ARGV does not affect \$*" && exit 1
shift
test "$*" = "2 3" || echo "Fail: 'shift' does not work correctly" && exit 1
shift 2
test "$*" = "" || echo "Fail: 'shift 2' does not work correctly" && exit 1

View file

@ -159,12 +159,14 @@ int main(int argc, char** argv)
const char* command_to_run = nullptr; const char* command_to_run = nullptr;
const char* file_to_read_from = nullptr; const char* file_to_read_from = nullptr;
Vector<const char*> script_args;
bool skip_rc_files = false; bool skip_rc_files = false;
Core::ArgsParser parser; Core::ArgsParser parser;
parser.add_option(command_to_run, "String to read commands from", "command-string", 'c', "command-string"); parser.add_option(command_to_run, "String to read commands from", "command-string", 'c', "command-string");
parser.add_positional_argument(file_to_read_from, "File to read commands from", "file", Core::ArgsParser::Required::No);
parser.add_option(skip_rc_files, "Skip running shellrc files", "skip-shellrc", 0); parser.add_option(skip_rc_files, "Skip running shellrc files", "skip-shellrc", 0);
parser.add_positional_argument(file_to_read_from, "File to read commands from", "file", Core::ArgsParser::Required::No);
parser.add_positional_argument(script_args, "Extra argumets to pass to the script (via $* and co)", "argument", Core::ArgsParser::Required::No);
parser.parse(argc, argv); parser.parse(argc, argv);
@ -181,6 +183,13 @@ int main(int argc, char** argv)
run_rc_file(Shell::local_init_file_path); run_rc_file(Shell::local_init_file_path);
} }
{
Vector<String> args;
for (auto* arg : script_args)
args.empend(arg);
shell->set_local_variable("ARGV", *new AST::ListValue(move(args)));
}
if (command_to_run) { if (command_to_run) {
dbgprintf("sh -c '%s'\n", command_to_run); dbgprintf("sh -c '%s'\n", command_to_run);
shell->run_command(command_to_run); shell->run_command(command_to_run);