summaryrefslogtreecommitdiff
path: root/Userland/Shell/Builtin.cpp
diff options
context:
space:
mode:
authorVetrox <39677514+Vetrox@users.noreply.github.com>2022-12-22 21:51:27 +0100
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2022-12-27 07:25:42 +0330
commit0e26f2657ec30f51fc17c83e12a0dbb556d9e0d3 (patch)
tree2f2ee9672e65f875692412e58c4d6067f523ed64 /Userland/Shell/Builtin.cpp
parent8238f926fda6dbdaa7131a55965e76179d82f8ba (diff)
downloadserenity-0e26f2657ec30f51fc17c83e12a0dbb556d9e0d3.zip
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.
Diffstat (limited to 'Userland/Shell/Builtin.cpp')
-rw-r--r--Userland/Shell/Builtin.cpp100
1 files changed, 100 insertions, 0 deletions
diff --git a/Userland/Shell/Builtin.cpp b/Userland/Shell/Builtin.cpp
index 989b3031c9..d49a2f4283 100644
--- a/Userland/Shell/Builtin.cpp
+++ b/Userland/Shell/Builtin.cpp
@@ -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;