1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1992-2013 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Eclipse Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.eclipse.org/org/documents/epl-v10.html *
11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <glenn.s.fowler@gmail.com> *
18 * David Korn <dgkorn@gmail.com> *
19 * *
20 ***********************************************************************/
21 #pragma prototyped
22 /*
23 * Glenn Fowler
24 * AT&T Research
25 *
26 * xargs -- construct arg list and exec -- use tw instead
27 */
28
29 static const char usage[] =
30 "[-?\n@(#)$Id: xargs (AT&T Research) 2013-09-19 $\n]"
31 USAGE_LICENSE
32 "[--plugin?ksh]"
33 "[+NAME?xargs - construct arg list and execute command]"
34 "[+DESCRIPTION?\bxargs\b constructs a command line consisting of the "
35 "\acommand\a and \aargument\a operands specified followed by as many "
36 "arguments read in sequence from standard input as will fit in length "
37 "and number constraints specified by the options and the local system. "
38 "\axargs\a executes the constructed command and waits for its "
39 "completion. This sequence is repeated until an end-of-file condition is "
40 "detected on standard input or an invocation of a constructed command "
41 "line returns an exit status of 255. If \acommand\a is omitted then the "
42 "equivalent of \b/bin/echo\b is used.]"
43 "[+?Arguments in the standard input must be separated by unquoted blank "
44 "characters, or unescaped blank characters or newline characters. A "
45 "string of zero or more non-double-quote and non-newline characters can "
46 "be quoted by enclosing them in double-quotes. A string of zero or more "
47 "non-apostrophe and non-newline characters can be quoted by enclosing "
48 "them in apostrophes. Any unquoted character can be escaped by preceding "
49 "it with a backslash. The utility will be executed one or more times "
50 "until the end-of-file is reached. The results are unspecified if "
51 "\acommand\a attempts to read from its standard input.]"
52 "[e:eof?Set the end of file string. The first input line matching this "
53 "string terminates the input list. There is no eof string if \astring\a "
54 "is omitted. The default eof string is \b_\b if neither \b--eof\b nor "
55 "\b-E\b are specified. For backwards compatibility \astring\a must "
56 "immediately follow the \b-e\b option flag; \b-E\b follows standard "
57 "option syntax.]:?[string]"
58 "[i:insert|replace?Replace occurences of \astring\a in the command "
59 "arguments with names read from the standard input. Implies \b--exit\b "
60 "and \b--lines=1\b. For backwards compatibility \astring\a must "
61 "immediately follow the \b-i\b option flag; \b-I\b follows standard "
62 "option syntax.]:?[string:={}]"
63 "[l:lines|max-lines?Use at most \alines\a lines from the standard input. "
64 "Lines with trailing blanks logically continue onto the next line. For "
65 "backwards compatibility \alines\a must immediately follow the \b-l\b "
66 "option flag; \b-L\b follows standard option syntax.]#?[lines:=1]"
67 "[n:args|max-args?Use at most \aargs\a arguments per command line. Fewer "
68 "than \aargs\a will be used if \b--size\b is exceeded.]#[args]"
69 "[p:interactive|prompt?Prompt to determine if each command should "
70 "execute. A \by\b or \bY\b recsponse executes, otherwise the command is "
71 "skipped. Implies \b--verbose\b.]"
72 "[N|0:null?The file name list is NUL terminated; there is no other "
73 "special treatment of the list.]"
74 "[s:size|max-chars?Use at most \achars\a characters per command. The "
75 "default is as large as possible.]#[chars]"
76 "[t:trace|verbose?Print the command line on the standard error before "
77 "executing it.]"
78 "[x:exit?Exit if \b--size\b is exceeded.]"
79 "[X:exact?If \b--args=\b\aargs\a was specified then terminate before the "
80 "last command if it would run with less than \aargs\a arguments.]"
81 "[z:nonempty|no-run-if-empty?If no file names are found then do not "
82 "execute the command. By default the command is executed at least once.]"
83 "[E?Equivalent to \b--eof=string\b.]:[string]"
84 "[I?Equivalent to \b--insert=string\b.]:[string]"
85 "[L?Equivalent to \b--lines=number\b.]#[number]"
86
87 "\n"
88 "\n[ command [ argument ... ] ]\n"
89 "\n"
90
91 "[+EXIT STATUS]"
92 "{"
93 "[+0?All invocations of \acommand\a returned exit status 0.]"
94 "[+1-125?A command line meeting the specified requirements could "
95 "not be assembled, one or more of the invocations of \acommand\a "
96 "returned non-0 exit status, or some other error occurred.]"
97 "[+126?\acommand\a was found but could not be executed.]"
98 "[+127?\acommand\a was not found.]"
99 "}"
100 "[+SEE ALSO?\bfind\b(1), \btw\b(1)]"
101 ;
102
103 #include <cmd.h>
104 #include <cmdarg.h>
105 #include <ctype.h>
106
107 typedef struct Xargs_s
108 {
109 Cmddisc_t disc;
110 Cmdarg_t* cmd;
111 Shbltin_t* context;
112 } Xargs_t;
113
114 static int
run(int argc,char ** argv,Cmddisc_t * disc)115 run(int argc, char** argv, Cmddisc_t* disc)
116 {
117 if (((Xargs_t*)disc)->context)
118 return sh_run(((Xargs_t*)disc)->context, argc, argv);
119 }
120
121 int
b_xargs(int argc,register char ** argv,Shbltin_t * context)122 b_xargs(int argc, register char** argv, Shbltin_t* context)
123 {
124 register int c;
125 register int q;
126 register char* s;
127 register Sfio_t* sp;
128
129 int argmax = 0;
130 char* eof = "_";
131 char* insert = 0;
132 int lines = 0;
133 size_t size = 0;
134 int term = -1;
135
136 Xargs_t xargs;
137
138 cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
139 CMDDISC(&xargs.disc, CMD_CHECKED|CMD_EMPTY, errorf);
140 xargs.disc.runf = run;
141 xargs.context = context;
142 for (;;)
143 {
144 switch (optget(argv, usage))
145 {
146 case 'e':
147 /*
148 * backwards compatibility requires no space between
149 * option and value
150 */
151
152 if (opt_info.arg == argv[opt_info.index - 1])
153 {
154 opt_info.arg = 0;
155 opt_info.index--;
156 }
157 /*FALLTHROUGH*/
158 case 'E':
159 eof = opt_info.arg;
160 continue;
161 case 'i':
162 /*
163 * backwards compatibility requires no space between
164 * option and value
165 */
166
167 if (opt_info.arg == argv[opt_info.index - 1])
168 {
169 opt_info.arg = 0;
170 opt_info.index--;
171 }
172 /*FALLTHROUGH*/
173 case 'I':
174 insert = opt_info.arg ? opt_info.arg : "{}";
175 xargs.disc.flags |= CMD_INSERT;
176 term = '\n';
177 continue;
178 case 'l':
179 /*
180 * backwards compatibility requires no space between
181 * option and value
182 */
183
184 if (opt_info.arg == argv[opt_info.index - 1])
185 {
186 opt_info.arg = 0;
187 opt_info.index--;
188 }
189 /*FALLTHROUGH*/
190 case 'L':
191 argmax = opt_info.num ? opt_info.num : 1;
192 lines = 1;
193 continue;
194 case 'n':
195 argmax = opt_info.num;
196 continue;
197 case 'p':
198 xargs.disc.flags |= CMD_QUERY;
199 continue;
200 case 's':
201 size = opt_info.num;
202 continue;
203 case 't':
204 xargs.disc.flags |= CMD_TRACE;
205 continue;
206 case 'x':
207 xargs.disc.flags |= CMD_MINIMUM;
208 continue;
209 case 'z':
210 xargs.disc.flags &= ~CMD_EMPTY;
211 continue;
212 case 'D':
213 error_info.trace = -opt_info.num;
214 continue;
215 case 'N':
216 term = 0;
217 continue;
218 case 'X':
219 xargs.disc.flags |= CMD_EXACT;
220 continue;
221 case '?':
222 error(ERROR_USAGE|4, "%s", opt_info.arg);
223 continue;
224 case ':':
225 error(2, "%s", opt_info.arg);
226 continue;
227 }
228 break;
229 }
230 argv += opt_info.index;
231 if (error_info.errors)
232 error(ERROR_USAGE|4, "%s", optusage(NiL));
233 if (argv[0])
234 {
235 if (context)
236 {
237 char* av[4];
238
239 av[0] = "whence";
240 av[1] = "-q";
241 av[2] = argv[0];
242 av[3] = 0;
243 if (run(3, av, &xargs.disc))
244 error(3, "%s: command not found", argv[0]);
245 }
246 else
247 {
248 char buf[PATH_MAX];
249
250 if (!pathpath(argv[0], NiL, PATH_REGULAR|PATH_EXECUTE, buf, sizeof(buf)))
251 error(3, "%s: command not found", argv[0]);
252 }
253 }
254 if (xargs.cmd = cmdopen(argv, argmax, size, insert, &xargs.disc))
255 {
256 sfopen(sfstdin, NiL, "rt");
257 error_info.line = 1;
258 if (term >= 0)
259 while (!sh_checksig(context))
260 {
261 if (!(s = sfgetr(sfstdin, term, 0)))
262 {
263 if (sfvalue(sfstdin) > 0)
264 error(2, "last argument incomplete");
265 break;
266 }
267 error_info.line++;
268 if ((c = sfvalue(sfstdin) - 1) && (s[c-1] != '\r' || --c))
269 cmdarg(xargs.cmd, s, c);
270 }
271 else if (!(sp = sfstropen()))
272 error(ERROR_SYSTEM|2, "out of space [arg]");
273 else
274 {
275 while (!sh_checksig(context))
276 {
277 switch (c = sfgetc(sfstdin))
278 {
279 case '"':
280 case '\'':
281 q = c;
282 while ((c = sfgetc(sfstdin)) != q)
283 {
284 if (c == EOF)
285 goto arg;
286 if (c == '\n')
287 {
288 error(1, "missing %c quote", q);
289 error_info.line++;
290 goto arg;
291 }
292 sfputc(sp, c);
293 }
294 continue;
295 case '\\':
296 if ((c = sfgetc(sfstdin)) == EOF)
297 {
298 if (sfstrtell(sp))
299 goto arg;
300 break;
301 }
302 if (c == '\n')
303 error_info.line++;
304 sfputc(sp, c);
305 continue;
306 case EOF:
307 if (sfstrtell(sp))
308 goto arg;
309 break;
310 case '\n':
311 error_info.line++;
312 arg:
313 c = sfstrtell(sp);
314 if (!(s = sfstruse(sp)))
315 error(ERROR_SYSTEM|3, "out of space");
316 if (eof && streq(s, eof))
317 break;
318 if (c || insert)
319 {
320 if (lines && c > 1 && isspace(s[c - 2]))
321 cmdarg(xargs.cmd, 0, -1);
322 cmdarg(xargs.cmd, s, c);
323 }
324 continue;
325 default:
326 if (isspace(c))
327 goto arg;
328 sfputc(sp, c);
329 continue;
330 }
331 break;
332 }
333 sfclose(sp);
334 }
335 if (sferror(sfstdin))
336 error(ERROR_SYSTEM|2, "input read error");
337 cmdclose(xargs.cmd);
338 }
339 else if (!error_info.errors)
340 error(ERROR_SYSTEM|2, "out of space");
341 return error_info.errors != 0;
342 }
343