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