diff --git a/src/libutil/util.cc b/src/libutil/util.cc index dc724db3e..f65332b45 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1299,6 +1299,32 @@ void runProgram2(const RunOptions & options) throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); } +std::optional which(std::string_view cmdName) +{ + for (auto const & dir : tokenizeString(getEnv("PATH").value_or(""), ":")) { + auto supposedExe = dir + "/" + cmdName; + if (pathExists(supposedExe)) { + return std::move(supposedExe); + } + } + + return std::nullopt; +} + +std::vector whichAll(std::string_view cmdName) +{ + std::vector results; + + for (auto const & dir : tokenizeString(getEnv("PATH").value_or(""), ":")) { + auto supposedExe = dir + "/" + cmdName; + if (pathExists(supposedExe)) { + results.push_back(std::move(supposedExe)); + } + } + + return results; +} + void closeMostFDs(const std::set & exceptions) { diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 9c2385e84..fc20e88e1 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -521,6 +521,31 @@ public: */ std::vector stringsToCharPtrs(const Strings & ss); +/** Gets the first file called `cmdName` in colon-separated PATH. + * + * @param cmdName The name of the file to look for in PATH. + * + * @return std::nullopt if the specified filename was not found. + * + * @note This does *not* check if the found file is executable or not. + * + * @see whichAll() + */ +std::optional which(std::string_view cmdName); + +/** Gets all files called `cmdName` in colon-separated PATH. + * + * @param cmdName The name of the file to look for in PATH. + * + * @return an std::vector of @ref Path "Paths", which will be empty if + * none were found. + * + * @note This does *not* check if the found files are executable or not. + * + * @see which() + */ +std::vector whichAll(std::string_view cmdName); + /** * Close all file descriptors except those listed in the given set. * Good practice in child processes. diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index da7a1d7a0..88c6b78ba 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -77,9 +77,9 @@ struct CmdDoctor : StoreCommand { PathSet dirs; - for (auto & dir : tokenizeString(getEnv("PATH").value_or(""), ":")) - if (pathExists(dir + "/nix-env")) - dirs.insert(dirOf(canonPath(dir + "/nix-env", true))); + for (auto const & nixExeInPath : whichAll("nix-env")) { + dirs.insert(canonPath(dirOf(nixExeInPath), true)); + } if (dirs.size() != 1) { std::stringstream ss; diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index af219c1b9..bed160e55 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -104,11 +104,9 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand { Path where; - for (auto & dir : tokenizeString(getEnv("PATH").value_or(""), ":")) - if (pathExists(dir + "/nix-env")) { - where = dir; - break; - } + if (auto nixEnvPath = which("nix-env")) { + where = dirOf(*nixEnvPath); + } if (where == "") throw Error("couldn't figure out how Nix is installed, so I can't upgrade it");