1 /* basename -- strip directory and suffix from file names
2    Copyright (C) 1990-2023 Free Software Foundation, Inc.
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16 
17 #include <config.h>
18 #include <getopt.h>
19 #include <stdio.h>
20 #include <sys/types.h>
21 
22 #include "system.h"
23 #include "quote.h"
24 
25 /* The official name of this program (e.g., no 'g' prefix).  */
26 #define PROGRAM_NAME "basename"
27 
28 #define AUTHORS proper_name ("David MacKenzie")
29 
30 static struct option const longopts[] =
31 {
32   {"multiple", no_argument, nullptr, 'a'},
33   {"suffix", required_argument, nullptr, 's'},
34   {"zero", no_argument, nullptr, 'z'},
35   {GETOPT_HELP_OPTION_DECL},
36   {GETOPT_VERSION_OPTION_DECL},
37   {nullptr, 0, nullptr, 0}
38 };
39 
40 void
usage(int status)41 usage (int status)
42 {
43   if (status != EXIT_SUCCESS)
44     emit_try_help ();
45   else
46     {
47       printf (_("\
48 Usage: %s NAME [SUFFIX]\n\
49   or:  %s OPTION... NAME...\n\
50 "),
51               program_name, program_name);
52       fputs (_("\
53 Print NAME with any leading directory components removed.\n\
54 If specified, also remove a trailing SUFFIX.\n\
55 "), stdout);
56 
57       emit_mandatory_arg_note ();
58 
59       fputs (_("\
60   -a, --multiple       support multiple arguments and treat each as a NAME\n\
61   -s, --suffix=SUFFIX  remove a trailing SUFFIX; implies -a\n\
62   -z, --zero           end each output line with NUL, not newline\n\
63 "), stdout);
64       fputs (HELP_OPTION_DESCRIPTION, stdout);
65       fputs (VERSION_OPTION_DESCRIPTION, stdout);
66       printf (_("\
67 \n\
68 Examples:\n\
69   %s /usr/bin/sort          -> \"sort\"\n\
70   %s include/stdio.h .h     -> \"stdio\"\n\
71   %s -s .h include/stdio.h  -> \"stdio\"\n\
72   %s -a any/str1 any/str2   -> \"str1\" followed by \"str2\"\n\
73 "),
74               program_name, program_name, program_name, program_name);
75       emit_ancillary_info (PROGRAM_NAME);
76     }
77   exit (status);
78 }
79 
80 /* Remove SUFFIX from the end of NAME if it is there, unless NAME
81    consists entirely of SUFFIX.  */
82 
83 static void
remove_suffix(char * name,char const * suffix)84 remove_suffix (char *name, char const *suffix)
85 {
86   char *np;
87   char const *sp;
88 
89   np = name + strlen (name);
90   sp = suffix + strlen (suffix);
91 
92   while (np > name && sp > suffix)
93     if (*--np != *--sp)
94       return;
95   if (np > name)
96     *np = '\0';
97 }
98 
99 /* Perform the basename operation on STRING.  If SUFFIX is non-null, remove
100    the trailing SUFFIX.  Finally, output the result string.  */
101 
102 static void
perform_basename(char const * string,char const * suffix,bool use_nuls)103 perform_basename (char const *string, char const *suffix, bool use_nuls)
104 {
105   char *name = base_name (string);
106   strip_trailing_slashes (name);
107 
108   /* Per POSIX, 'basename // /' must return '//' on platforms with
109      distinct //.  On platforms with drive letters, this generalizes
110      to making 'basename c: :' return 'c:'.  This rule is captured by
111      skipping suffix stripping if base_name returned an absolute path
112      or a drive letter (only possible if name is a file-system
113      root).  */
114   if (suffix && IS_RELATIVE_FILE_NAME (name) && ! FILE_SYSTEM_PREFIX_LEN (name))
115     remove_suffix (name, suffix);
116 
117   fputs (name, stdout);
118   putchar (use_nuls ? '\0' : '\n');
119   free (name);
120 }
121 
122 int
main(int argc,char ** argv)123 main (int argc, char **argv)
124 {
125   bool multiple_names = false;
126   bool use_nuls = false;
127   char const *suffix = nullptr;
128 
129   initialize_main (&argc, &argv);
130   set_program_name (argv[0]);
131   setlocale (LC_ALL, "");
132   bindtextdomain (PACKAGE, LOCALEDIR);
133   textdomain (PACKAGE);
134 
135   atexit (close_stdout);
136 
137   while (true)
138     {
139       int c = getopt_long (argc, argv, "+as:z", longopts, nullptr);
140 
141       if (c == -1)
142         break;
143 
144       switch (c)
145         {
146         case 's':
147           suffix = optarg;
148           /* -s implies -a, so...  */
149           FALLTHROUGH;
150 
151         case 'a':
152           multiple_names = true;
153           break;
154 
155         case 'z':
156           use_nuls = true;
157           break;
158 
159         case_GETOPT_HELP_CHAR;
160         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
161 
162         default:
163           usage (EXIT_FAILURE);
164         }
165     }
166 
167   if (argc < optind + 1)
168     {
169       error (0, 0, _("missing operand"));
170       usage (EXIT_FAILURE);
171     }
172 
173   if (!multiple_names && optind + 2 < argc)
174     {
175       error (0, 0, _("extra operand %s"), quote (argv[optind + 2]));
176       usage (EXIT_FAILURE);
177     }
178 
179   if (multiple_names)
180     {
181       for (; optind < argc; optind++)
182         perform_basename (argv[optind], suffix, use_nuls);
183     }
184   else
185     perform_basename (argv[optind],
186                       optind + 2 == argc ? argv[optind + 1] : nullptr,
187                       use_nuls);
188 
189   return EXIT_SUCCESS;
190 }
191