From c31b9f430fb68b248b5bbc3c3454138b08991e36 Mon Sep 17 00:00:00 2001 From: Kurtis Rader Date: Fri, 23 Jun 2017 15:42:38 -0700 Subject: [PATCH] implement `command -a` Fixes #2778 --- doc_src/command.txt | 4 +++- src/builtin_command.cpp | 28 ++++++++++++++++++++++------ src/path.cpp | 39 +++++++++++++++++++++++++++++++++++++-- src/path.h | 18 ++++++++++++------ 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/doc_src/command.txt b/doc_src/command.txt index b05c77e9c..90c5aee2b 100644 --- a/doc_src/command.txt +++ b/doc_src/command.txt @@ -11,7 +11,9 @@ command [OPTIONS] COMMANDNAME [ARGS...] The following options are available: -- `-s` or `--search` returns the name of the disk file that would be executed, or nothing if no file with the specified name could be found in the `$PATH`. +- `-a` or `--all` returns all the external commands that are found in `$PATH` in the order they are found. + +- `-s` or `--search` returns the name of the external command that would be executed, or nothing if no file with the specified name could be found in the `$PATH`. With the `-s` option, `command` treats every argument as a separate command to look up and sets the exit status to 0 if any of the specified commands were found, or 1 if no commands could be found. Additionally passing a `-q` or `--quiet` option prevents any paths from being printed, like the `type -q`, for testing only the exit status. diff --git a/src/builtin_command.cpp b/src/builtin_command.cpp index a8e37e2a6..3e53b55a4 100644 --- a/src/builtin_command.cpp +++ b/src/builtin_command.cpp @@ -3,6 +3,8 @@ #include +#include + #include "builtin.h" #include "builtin_command.h" #include "common.h" @@ -16,9 +18,11 @@ struct command_cmd_opts_t { bool print_help = false; bool find_path = false; bool quiet = false; + bool all_paths = false; }; -static const wchar_t *short_options = L"hqsv"; +static const wchar_t *short_options = L":ahqsv"; static const struct woption long_options[] = {{L"help", no_argument, NULL, 'h'}, + {L"all", no_argument, NULL, 'a'}, {L"quiet", no_argument, NULL, 'q'}, {L"search", no_argument, NULL, 's'}, {NULL, 0, NULL, 0}}; @@ -30,6 +34,10 @@ static int parse_cmd_opts(command_cmd_opts_t &opts, int *optind, int argc, wchar wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { + case 'a': { + opts.all_paths = true; + break; + } case 'h': { opts.print_help = true; break; @@ -74,7 +82,7 @@ int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **argv) { return STATUS_CMD_OK; } - if (!opts.find_path) { + if (!opts.find_path && !opts.all_paths) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_INVALID_ARGS; } @@ -82,10 +90,18 @@ int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int found = 0; for (int idx = optind; argv[idx]; ++idx) { const wchar_t *command_name = argv[idx]; - wcstring path; - if (path_get_path(command_name, &path)) { - if (!opts.quiet) streams.out.append_format(L"%ls\n", path.c_str()); - ++found; + if (opts.all_paths) { + wcstring_list_t paths = path_get_paths(command_name); + for (auto path : paths) { + if (!opts.quiet) streams.out.append_format(L"%ls\n", path.c_str()); + ++found; + } + } else { + wcstring path; + if (path_get_path(command_name, &path)) { + if (!opts.quiet) streams.out.append_format(L"%ls\n", path.c_str()); + ++found; + } } } diff --git a/src/path.cpp b/src/path.cpp index e76ed2726..9d6866054 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -25,10 +25,10 @@ static bool path_get_path_core(const wcstring &cmd, wcstring *out_path, const env_var_t &bin_path_var) { - int err = ENOENT; debug(3, L"path_get_path( '%ls' )", cmd.c_str()); - // If the command has a slash, it must be a full path. + // If the command has a slash, it must be an absolute or relative path and thus we don't bother + // looking for a matching command. if (cmd.find(L'/') != wcstring::npos) { if (waccess(cmd, X_OK) != 0) { return false; @@ -46,6 +46,7 @@ static bool path_get_path_core(const wcstring &cmd, wcstring *out_path, return false; } + int err = ENOENT; wcstring bin_path; if (!bin_path_var.missing()) { bin_path = bin_path_var; @@ -105,6 +106,40 @@ bool path_get_path(const wcstring &cmd, wcstring *out_path) { return path_get_path_core(cmd, out_path, env_get_string(L"PATH")); } +wcstring_list_t path_get_paths(const wcstring &cmd) { + debug(3, L"path_get_paths('%ls')", cmd.c_str()); + wcstring_list_t paths; + + // If the command has a slash, it must be an absolute or relative path and thus we don't bother + // looking for matching commands in the PATH var. + if (cmd.find(L'/') != wcstring::npos) { + struct stat buff; + if (wstat(cmd, &buff)) return paths; + if (!S_ISREG(buff.st_mode)) return paths; + if (waccess(cmd, X_OK)) return paths; + paths.push_back(cmd); + return paths; + } + + wcstring env_path = env_get_string(L"PATH"); + std::vector pathsv; + tokenize_variable_array(env_path, pathsv); + for (auto path : pathsv) { + if (path.empty()) continue; + append_path_component(path, cmd); + if (waccess(path, X_OK) == 0) { + struct stat buff; + if (wstat(path, &buff) == -1) { + if (errno != EACCES) wperror(L"stat"); + continue; + } + if (S_ISREG(buff.st_mode)) paths.push_back(path); + } + } + + return paths; +} + bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd, const env_vars_snapshot_t &env_vars) { int err = ENOENT; diff --git a/src/path.h b/src/path.h index 338b6144e..371d0ef86 100644 --- a/src/path.h +++ b/src/path.h @@ -29,16 +29,22 @@ bool path_get_config(wcstring &path); /// \return whether the directory was returned successfully bool path_get_data(wcstring &path); -/// Finds the full path of an executable. Returns YES if successful. +/// Finds the full path of an executable. /// -/// \param cmd The name of the executable. -/// \param output_or_NULL If non-NULL, store the full path. -/// \param vars The environment variables snapshot to use -/// \return 0 if the command can not be found, the path of the command otherwise. The result should -/// be freed with free(). +/// Args: +/// cmd - The name of the executable. +/// output_or_NULL - If non-NULL, store the full path. +/// vars - The environment variables snapshot to use +/// +/// Returns: +/// false if the command can not be found else true. The result +/// should be freed with free(). bool path_get_path(const wcstring &cmd, wcstring *output_or_NULL, const env_vars_snapshot_t &vars = env_vars_snapshot_t::current()); +/// Return all the paths that match the given command. +wcstring_list_t path_get_paths(const wcstring &cmd); + /// Returns the full path of the specified directory, using the CDPATH variable as a list of base /// directories for relative paths. The returned string is allocated using halloc and the specified /// context.