46using std::stringstream;
49int processOpts(
int argc,
char* argv[]);
52void split(
const std::string& s,
char delim, vector<string>& elems);
53int findPaths(
string filename);
54void readAliasesAndFunctions();
84static list<string> aliases, functions;
85static string home, cwd;
87int processOpts(
int argc,
char* argv[]) {
88 int c, longOption, ttyOnly = 0;
89 struct option long_options[] = {{
"all", no_argument, &opts.all, 1},
90 {
"read-alias", no_argument, &opts.readAliases, 1},
91 {
"skip-alias", no_argument, &opts.readAliases, 0},
92 {
"read-functions", no_argument, &opts.readFunctions, 1},
93 {
"skip-functions", no_argument, &opts.readFunctions, 0},
94 {
"skip-dot", no_argument, &longOption, opt_skip_dot},
95 {
"show-dot", no_argument, &longOption, opt_show_dot},
96 {
"skip-tilde", no_argument, &longOption, opt_skip_tilde},
97 {
"show-tilde", no_argument, &longOption, opt_show_tilde},
98 {
"tty-only", no_argument, &longOption, opt_tty_only},
99 {
"version", no_argument, 0,
'v'},
100 {
"help", no_argument, 0,
'h'},
104 c = getopt_long(argc, argv,
"aivVh", long_options, NULL);
111 switch (longOption) {
113 opts.skipDot = !ttyOnly;
116 opts.showDot = !ttyOnly;
119 opts.skipTilde = !ttyOnly;
122 opts.showTilde = !ttyOnly;
125 ttyOnly = !isatty(1);
133 opts.readAliases = 1;
149 cout <<
"Usage: which [options] [--] COMMAND [...]" << endl;
150 cout <<
"Write the full path of COMMAND(s) to standard output." << endl;
152 cout <<
" --version, -[vV] Print version and exit successfully." << endl;
153 cout <<
" --help, Print this help and exit successfully." << endl;
154 cout <<
" --skip-dot Skip directories in PATH that start with a dot." << endl;
155 cout <<
" --skip-tilde Skip directories in PATH that start with a "
158 cout <<
" --show-dot Don't expand a dot to current directory in "
161 cout <<
" --show-tilde Output a tilde for HOME directory for non-root." << endl;
162 cout <<
" --tty-only Stop processing options on the right if not on "
165 cout <<
" --all, -a Print all matches in PATH, not just the first" << endl;
166 cout <<
" --read-alias, -i Read list of aliases from stdin." << endl;
167 cout <<
" --skip-alias Ignore option --read-alias; don't read stdin." << endl;
168 cout <<
" --read-functions Read shell functions from stdin." << endl;
169 cout <<
" --skip-functions Ignore option --read-functions; don't read "
173 cout <<
"Recommended use is to write the output of (alias; declare -f) to "
176 cout <<
"input, so that which can show aliases and shell functions. See "
179 cout <<
"examples." << endl;
181 cout <<
"If the options --read-alias and/or --read-functions are specified "
184 cout <<
"output can be a full alias or function definition, optionally "
187 cout <<
"the full path of each command used inside of those." << endl;
191 cout <<
"which v1.0, Copyright (C) 2014 Nathan Hoad" << endl;
194void split(
const std::string& s,
char delim, vector<string>& elems) {
197 while (std::getline(ss, item, delim)) {
198 elems.push_back(item);
202int findPaths(
string filename) {
207 for (
auto& alias : aliases) {
208 if (alias.find(filename) == 0) {
209 cout << alias << endl;
215 for (
auto& function : functions) {
216 if (function.find(filename) == 0) {
217 cout << function << endl;
223 path = getenv(
"PATH");
225 split(path ? path :
"",
':', dirs);
227 for (
auto& dir : dirs) {
229 while (dir.rbegin() != dir.rend() && *dir.rbegin() ==
'/')
232 string fullpath = dir +
"/" + filename;
236 if ((stat(fullpath.c_str(), &sb) == 0) && sb.st_mode & S_IXUSR) {
237 if (opts.showDot && dir[0] ==
'.' && fullpath.compare(0, cwd.size(), cwd) == 0) {
238 printed_path = string(
"./") + filename;
240 if (opts.showTilde && !home.empty() && fullpath.compare(0, home.size(), home) == 0) {
241 fullpath = fullpath.substr(home.size(), fullpath.size() - home.size());
242 printed_path = string(
"~") + fullpath;
244 printed_path = fullpath;
248 if (printed_path.size()) {
249 if (opts.skipTilde && printed_path[0] ==
'~' && geteuid() != 0)
251 else if (opts.skipDot && printed_path[0] ==
'.')
253 cout << printed_path << endl;
264void readAliasesAndFunctions() {
265 if (!opts.readFunctions && !opts.readAliases)
269 cerr <<
"which: warning: stdin is a tty" << endl;
271 string line, function;
273 while (std::getline(cin, line,
'\n')) {
274 if (opts.readAliases && regex_match(line, regex(
"^.+=.+?$")))
275 aliases.push_back(line);
276 else if (opts.readFunctions) {
277 function = line +
"\n";
278 while (std::getline(cin, line,
'\n') && line !=
"}") {
279 function += line +
"\n";
284 functions.push_back(function);
288 cerr <<
"which: line doesn't appear to be an alias and functions "
300 _home = getenv(
"HOME");
301 home = _home ? _home :
"";
303 memset(_cwd, 0, PATH_MAX);
304 if (getcwd(_cwd, PATH_MAX) == NULL) {
305 cerr <<
"which: unable to retrieve current working directory " << strerror(errno) << endl;
312int main(
int argc,
char* argv[]) {
315 if (processOpts(argc, argv) != 0)
319 readAliasesAndFunctions();
321 status = EXIT_SUCCESS;
323 for (
int i = optind; i < argc; i++) {
324 if (findPaths(argv[i]) != 0) {
325 cout << argv[i] <<
" not found" << endl;
326 status = EXIT_FAILURE;