1This file is type.def, from which is created type.c.
2It implements the builtin "type" in Bash.
3
4Copyright (C) 1987-2020 Free Software Foundation, Inc.
5
6This file is part of GNU Bash, the Bourne Again SHell.
7
8Bash is free software: you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation, either version 3 of the License, or
11(at your option) any later version.
12
13Bash is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with Bash.  If not, see <http://www.gnu.org/licenses/>.
20
21$PRODUCES type.c
22
23$BUILTIN type
24$FUNCTION type_builtin
25$SHORT_DOC type [-afptP] name [name ...]
26Display information about command type.
27
28For each NAME, indicate how it would be interpreted if used as a
29command name.
30
31Options:
32  -a	display all locations containing an executable named NAME;
33		includes aliases, builtins, and functions, if and only if
34		the `-p' option is not also used
35  -f	suppress shell function lookup
36  -P	force a PATH search for each NAME, even if it is an alias,
37		builtin, or function, and returns the name of the disk file
38		that would be executed
39  -p	returns either the name of the disk file that would be executed,
40		or nothing if `type -t NAME' would not return `file'
41  -t	output a single word which is one of `alias', `keyword',
42		`function', `builtin', `file' or `', if NAME is an alias,
43		shell reserved word, shell function, shell builtin, disk file,
44		or not found, respectively
45
46Arguments:
47  NAME	Command name to be interpreted.
48
49Exit Status:
50Returns success if all of the NAMEs are found; fails if any are not found.
51$END
52
53#include <config.h>
54
55#include "../bashtypes.h"
56#include "posixstat.h"
57
58#if defined (HAVE_UNISTD_H)
59#  include <unistd.h>
60#endif
61
62#include <stdio.h>
63#include "../bashansi.h"
64#include "../bashintl.h"
65
66#include "../shell.h"
67#include "../parser.h"
68#include "../execute_cmd.h"
69#include "../findcmd.h"
70#include "../hashcmd.h"
71
72#if defined (ALIAS)
73#include "../alias.h"
74#endif /* ALIAS */
75
76#include "common.h"
77#include "bashgetopt.h"
78
79extern int find_reserved_word PARAMS((char *));
80
81/* For each word in LIST, find out what the shell is going to do with
82   it as a simple command. i.e., which file would this shell use to
83   execve, or if it is a builtin command, or an alias.  Possible flag
84   arguments:
85	-t		Returns the "type" of the object, one of
86			`alias', `keyword', `function', `builtin',
87			or `file'.
88
89	-p		Returns the pathname of the file if -type is
90			a file.
91
92	-a		Returns all occurrences of words, whether they
93			be a filename in the path, alias, function,
94			or builtin.
95
96	-f		Suppress shell function lookup, like `command'.
97
98	-P		Force a path search even in the presence of other
99			definitions.
100
101   Order of evaluation:
102	alias
103	keyword
104	function
105	builtin
106	file
107 */
108
109int
110type_builtin (list)
111     WORD_LIST *list;
112{
113  int dflags, any_failed, opt;
114  WORD_LIST *this;
115
116  if (list == 0)
117    return (EXECUTION_SUCCESS);
118
119  dflags = CDESC_SHORTDESC;	/* default */
120  any_failed = 0;
121
122  /* Handle the obsolescent `-type', `-path', and `-all' by prescanning
123     the arguments and converting those options to the form that
124     internal_getopt recognizes. Converts `--type', `--path', and `--all'
125     also. THIS SHOULD REALLY GO AWAY. */
126  for (this = list; this && this->word->word[0] == '-'; this = this->next)
127    {
128      char *flag = &(this->word->word[1]);
129
130      if (STREQ (flag, "type") || STREQ (flag, "-type"))
131	{
132	  this->word->word[1] = 't';
133	  this->word->word[2] = '\0';
134	}
135      else if (STREQ (flag, "path") || STREQ (flag, "-path"))
136	{
137	  this->word->word[1] = 'p';
138	  this->word->word[2] = '\0';
139	}
140      else if (STREQ (flag, "all") || STREQ (flag, "-all"))
141	{
142	  this->word->word[1] = 'a';
143	  this->word->word[2] = '\0';
144	}
145    }
146
147  reset_internal_getopt ();
148  while ((opt = internal_getopt (list, "afptP")) != -1)
149    {
150      switch (opt)
151	{
152	case 'a':
153	  dflags |= CDESC_ALL;
154	  break;
155	case 'f':
156	  dflags |= CDESC_NOFUNCS;
157	  break;
158	case 'p':
159	  dflags |= CDESC_PATH_ONLY;
160	  dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC);
161	  break;
162	case 't':
163	  dflags |= CDESC_TYPE;
164	  dflags &= ~(CDESC_PATH_ONLY|CDESC_SHORTDESC);
165	  break;
166	case 'P':	/* shorthand for type -ap */
167	  dflags |= (CDESC_PATH_ONLY|CDESC_FORCE_PATH);
168	  dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC);
169	  break;
170	CASE_HELPOPT;
171	default:
172	  builtin_usage ();
173	  return (EX_USAGE);
174	}
175    }
176  list = loptend;
177
178  while (list)
179    {
180      int found;
181
182      found = describe_command (list->word->word, dflags);
183
184      if (!found && (dflags & (CDESC_PATH_ONLY|CDESC_TYPE)) == 0)
185	sh_notfound (list->word->word);
186
187      any_failed += found == 0;
188      list = list->next;
189    }
190
191  opt = (any_failed == 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
192  return (sh_chkwrite (opt));
193}
194
195/*
196 * Describe COMMAND as required by the type and command builtins.
197 *
198 * Behavior is controlled by DFLAGS.  Flag values are
199 *	CDESC_ALL	print all descriptions of a command
200 *	CDESC_SHORTDESC	print the description for type and command -V
201 *	CDESC_REUSABLE	print in a format that may be reused as input
202 *	CDESC_TYPE	print the type for type -t
203 *	CDESC_PATH_ONLY	print the path for type -p
204 *	CDESC_FORCE_PATH	force a path search for type -P
205 *	CDESC_NOFUNCS	skip function lookup for type -f
206 *	CDESC_ABSPATH	convert to absolute path, no ./ prefix
207 *	CDESC_STDPATH	command -p standard path list
208 *
209 * CDESC_ALL says whether or not to look for all occurrences of COMMAND, or
210 * return after finding it once.
211 */
212int
213describe_command (command, dflags)
214     char *command;
215     int dflags;
216{
217  int found, i, found_file, f, all;
218  char *full_path, *x, *pathlist;
219  SHELL_VAR *func;
220#if defined (ALIAS)
221  alias_t *alias;
222#endif
223
224  all = (dflags & CDESC_ALL) != 0;
225  found = found_file = 0;
226  full_path = (char *)NULL;
227
228#if defined (ALIAS)
229  /* Command is an alias? */
230  if (((dflags & CDESC_FORCE_PATH) == 0) && expand_aliases && (alias = find_alias (command)))
231    {
232      if (dflags & CDESC_TYPE)
233	puts ("alias");
234      else if (dflags & CDESC_SHORTDESC)
235	printf (_("%s is aliased to `%s'\n"), command, alias->value);
236      else if (dflags & CDESC_REUSABLE)
237	{
238	  x = sh_single_quote (alias->value);
239	  printf ("alias %s=%s\n", command, x);
240	  free (x);
241	}
242
243      found = 1;
244
245      if (all == 0)
246	return (1);
247    }
248#endif /* ALIAS */
249
250  /* Command is a shell reserved word? */
251  if (((dflags & CDESC_FORCE_PATH) == 0) && (i = find_reserved_word (command)) >= 0)
252    {
253      if (dflags & CDESC_TYPE)
254	puts ("keyword");
255      else if (dflags & CDESC_SHORTDESC)
256	printf (_("%s is a shell keyword\n"), command);
257      else if (dflags & CDESC_REUSABLE)
258	printf ("%s\n", command);
259
260      found = 1;
261
262      if (all == 0)
263	return (1);
264    }
265
266  /* Command is a function? */
267  if (((dflags & (CDESC_FORCE_PATH|CDESC_NOFUNCS)) == 0) && (func = find_function (command)))
268    {
269      if (dflags & CDESC_TYPE)
270	puts ("function");
271      else if (dflags & CDESC_SHORTDESC)
272	{
273	  char *result;
274
275	  printf (_("%s is a function\n"), command);
276
277	  /* We're blowing away THE_PRINTED_COMMAND here... */
278
279	  result = named_function_string (command, function_cell (func), FUNC_MULTILINE|FUNC_EXTERNAL);
280	  printf ("%s\n", result);
281	}
282      else if (dflags & CDESC_REUSABLE)
283	printf ("%s\n", command);
284
285      found = 1;
286
287      if (all == 0)
288	return (1);
289    }
290
291  /* Command is a builtin? */
292  if (((dflags & CDESC_FORCE_PATH) == 0) && find_shell_builtin (command))
293    {
294      if (dflags & CDESC_TYPE)
295	puts ("builtin");
296      else if (dflags & CDESC_SHORTDESC)
297	{
298	  if (posixly_correct && find_special_builtin (command) != 0)
299	    printf (_("%s is a special shell builtin\n"), command);
300	  else
301	    printf (_("%s is a shell builtin\n"), command);
302	}
303      else if (dflags & CDESC_REUSABLE)
304	printf ("%s\n", command);
305
306      found = 1;
307
308      if (all == 0)
309	return (1);
310    }
311
312  /* Command is a disk file? */
313  /* If the command name given is already an absolute command, just
314     check to see if it is executable. */
315  if (absolute_program (command))
316    {
317      f = file_status (command);
318      if (f & FS_EXECABLE)
319	{
320	  if (dflags & CDESC_TYPE)
321	    puts ("file");
322	  else if (dflags & CDESC_SHORTDESC)
323	    printf (_("%s is %s\n"), command, command);
324	  else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY))
325	    printf ("%s\n", command);
326
327	  /* There's no use looking in the hash table or in $PATH,
328	     because they're not consulted when an absolute program
329	     name is supplied. */
330	  return (1);
331	}
332    }
333
334  /* If the user isn't doing "-a", then we might care about
335     whether the file is present in our hash table. */
336  if (all == 0 || (dflags & CDESC_FORCE_PATH))
337    {
338      if (full_path = phash_search (command))
339	{
340	  if (dflags & CDESC_TYPE)
341	    puts ("file");
342	  else if (dflags & CDESC_SHORTDESC)
343	    printf (_("%s is hashed (%s)\n"), command, full_path);
344	  else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY))
345	    printf ("%s\n", full_path);
346
347	  free (full_path);
348	  return (1);
349	}
350    }
351
352  /* Now search through $PATH. */
353  while (1)
354    {
355      if (dflags & CDESC_STDPATH)	/* command -p, all cannot be non-zero */
356	{
357	  pathlist = conf_standard_path ();
358	  full_path = find_in_path (command, pathlist, FS_EXEC_PREFERRED|FS_NODIRS);
359	  free (pathlist);
360	  /* Will only go through this once, since all == 0 if STDPATH set */
361	}
362      else if (all == 0)
363	full_path = find_user_command (command);
364      else
365	full_path = user_command_matches (command, FS_EXEC_ONLY, found_file);	/* XXX - should that be FS_EXEC_PREFERRED? */
366
367      if (full_path == 0)
368	break;
369
370      /* If we found the command as itself by looking through $PATH, it
371	 probably doesn't exist.  Check whether or not the command is an
372	 executable file.  If it's not, don't report a match.  This is
373	 the default posix mode behavior */
374      if (STREQ (full_path, command) || posixly_correct)
375	{
376	  f = file_status (full_path);
377	  if ((f & FS_EXECABLE) == 0)
378	    {
379	      free (full_path);
380	      full_path = (char *)NULL;
381	      if (all == 0)
382		break;
383	    }
384	  else if (ABSPATH (full_path))
385	    ;	/* placeholder; don't need to do anything yet */
386	  else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY|CDESC_SHORTDESC))
387	    {
388	      f = MP_DOCWD | ((dflags & CDESC_ABSPATH) ? MP_RMDOT : 0);
389	      x = sh_makepath ((char *)NULL, full_path, f);
390	      free (full_path);
391	      full_path = x;
392	    }
393	}
394      /* If we require a full path and don't have one, make one */
395      else if ((dflags & CDESC_ABSPATH) && ABSPATH (full_path) == 0)
396	{
397	  x = sh_makepath ((char *)NULL, full_path, MP_DOCWD|MP_RMDOT);
398	  free (full_path);
399	  full_path = x;
400	}
401
402      found_file++;
403      found = 1;
404
405      if (dflags & CDESC_TYPE)
406	puts ("file");
407      else if (dflags & CDESC_SHORTDESC)
408	printf (_("%s is %s\n"), command, full_path);
409      else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY))
410	printf ("%s\n", full_path);
411
412      free (full_path);
413      full_path = (char *)NULL;
414
415      if (all == 0)
416	break;
417    }
418
419  return (found);
420}
421