Shell: Add where builtin

The builtin is based on the behaviour of the z-shell.
Namely it tries to resolve every argument one by one.

When resolving (in the order below) the following results can occur:
  1. The argument is a shell built-in command. Then print it.
  2. The argument is an alias. In this case we print the mapped value.
  3. The argument was found in the `PATH` environment variable.
     In this case we print the resolved absolute path
     and try to find more occurences in the `PATH` environment variable.
  4. None of the above. If no earlier argument got resolved,
     we print the error `{argument} not found`.

If at least one argument got resolved we exit with exit code 0,
otherwise 1.

By not using Core::File to resolve the executable in the environment
but rather using a modified version of the code we print every
matching executable of the given name. This behaviour matches
up with the z-shell.

The builtin has the following flags to modify the behaviour according
to the users needs:
  - `-p --path-only`: This skips the built-in and alias checks
  (step 1 & 2)
  - `-s --follow-symlink`: This follows the symlinks of an executable to
  its symlink-free location.
  - `-w --type`: This displays the type of the found object
  without any additional descriptive information.
This commit is contained in:
Vetrox 2022-12-22 21:51:27 +01:00 committed by Ali Mohammad Pur
parent 8238f926fd
commit 0e26f2657e
Notes: sideshowbarker 2024-07-17 02:33:04 +09:00
2 changed files with 101 additions and 0 deletions

View file

@ -39,6 +39,106 @@ int Shell::builtin_dump(int argc, char const** argv)
return 0;
}
enum FollowSymlinks {
Yes,
No
};
static Vector<DeprecatedString> find_matching_executables_in_path(StringView filename, FollowSymlinks follow_symlinks = FollowSymlinks::No)
{
// Edge cases in which there are guaranteed no solutions
if (filename.is_empty() || filename.contains('/'))
return {};
char const* path_str = getenv("PATH");
auto path = DEFAULT_PATH_SV;
if (path_str != nullptr) // maybe && *path_str
path = { path_str, strlen(path_str) };
Vector<DeprecatedString> executables;
auto directories = path.split_view(':');
for (auto directory : directories) {
auto file = DeprecatedString::formatted("{}/{}", directory, filename);
if (follow_symlinks == FollowSymlinks::Yes) {
auto path_or_error = Core::File::read_link(file);
if (!path_or_error.is_error())
file = path_or_error.release_value();
}
if (access(file.characters(), X_OK) == 0)
executables.append(move(file));
}
return executables;
}
int Shell::builtin_where(int argc, char const** argv)
{
Vector<StringView> arguments;
bool do_only_path_search { false };
bool do_follow_symlinks { false };
bool do_print_only_type { false };
Core::ArgsParser parser;
parser.add_positional_argument(arguments, "List of shell builtins, aliases or executables", "arguments");
parser.add_option(do_only_path_search, "Search only for executables in the PATH environment variable", "path-only", 'p');
parser.add_option(do_follow_symlinks, "Follow symlinks and print the symlink free path", "follow-symlink", 's');
parser.add_option(do_print_only_type, "Print the argument type instead of a human readable description", "type", 'w');
if (!parser.parse(argc, const_cast<char**>(argv), Core::ArgsParser::FailureBehavior::PrintUsage))
return 1;
auto const lookup_alias = [do_only_path_search, &m_aliases = this->m_aliases](StringView alias) -> Optional<DeprecatedString> {
if (do_only_path_search)
return {};
return m_aliases.get(alias);
};
auto const lookup_builtin = [do_only_path_search](StringView builtin) -> Optional<DeprecatedString> {
if (do_only_path_search)
return {};
for (auto const& _builtin : builtin_names) {
if (_builtin == builtin) {
return builtin;
}
}
return {};
};
bool at_least_one_succeded { false };
for (auto const& argument : arguments) {
auto const alias = lookup_alias(argument);
if (alias.has_value()) {
if (do_print_only_type)
outln("{}: alias", argument);
else
outln("{}: aliased to {}", argument, alias.value());
at_least_one_succeded = true;
}
auto const builtin = lookup_builtin(argument);
if (builtin.has_value()) {
if (do_print_only_type)
outln("{}: builtin", builtin.value());
else
outln("{}: shell built-in command", builtin.value());
at_least_one_succeded = true;
}
auto const executables = find_matching_executables_in_path(argument, do_follow_symlinks ? FollowSymlinks::Yes : FollowSymlinks::No);
for (auto const& path : executables) {
if (do_print_only_type)
outln("{}: command", argument);
else
outln(path);
at_least_one_succeded = true;
}
if (!at_least_one_succeded)
warnln("{} not found", argument);
}
return at_least_one_succeded ? 0 : 1;
}
int Shell::builtin_alias(int argc, char const** argv)
{
Vector<DeprecatedString> arguments;

View file

@ -25,6 +25,7 @@
#define ENUMERATE_SHELL_BUILTINS() \
__ENUMERATE_SHELL_BUILTIN(alias) \
__ENUMERATE_SHELL_BUILTIN(where) \
__ENUMERATE_SHELL_BUILTIN(cd) \
__ENUMERATE_SHELL_BUILTIN(cdh) \
__ENUMERATE_SHELL_BUILTIN(pwd) \