/
secho.c
608 lines (508 loc) · 13.9 KB
/
secho.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
/*
* silly echo, inspired by a mocking discussion on the
* plan 9 mailing list.
*
* Copyright (c) 2008 by David Loren Parsons. All rights reserved.
*
* Distribution terms for this piece of work are found in the file
* COPYRIGHT, which must be distributed with this code.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#if HAVE_LIBGEN_H
# include <libgen.h>
#endif
#include <regex.h>
#include "cstring.h"
enum { ASCII, BINARY, HEX, OCTAL, DECIMAL, BASEN, SPQR } format = ASCII;
int base; /* -B <base> */
enum { UPPERCASE, LOWERCASE, ASIS } whichcase = ASIS;
int zero = 0;
int count = 0; /* return # of fields */
char *cmd = 0; /* run command with each argument */
char delim = 0; /* field delimiter */
int doescapes=0; /* "allow escape sequences" ? */
char *filename = 0; /* read from file */
int cmdline = 1; /* use cmdline arguments */
int width = 0; /* linewidth */
int multicolumn = 0; /* "multicolumn output" */
int numbered = 0; /* oneperline + numbers */
int nonl = 0; /* don't put a newline at the end */
int quiet = 0; /* if ! -E && ! -o, output goes to /dev/null */
char outputdelim = ' '; /* output delimiter */
int nononprint = 0; /* strip nonprinting characters */
int visnonprint = 0; /* make non-printing characters visible */
int wordwrap = 0; /* wordwrap at width */
char *voice = 0; /* "send to speaker, having the given voice say it" */
int plan9e = 0; /* don't echo anything if no args */
FILE* output = 0; /* output file, changed by -f */
int counter = 0; /* # of lines, for -N & -C */
char *pgm = 0;
STRING(regex_t) regex = { 0 };
Cstring oline = { 0 };
Cstring arg = { 0 }, command = { 0 };
void printfmt(FILE *, char*, unsigned char);
void printc(unsigned char, FILE *);
#ifndef HAVE_BASENAME
char *
basename(char *p)
{
char *q = strrchr(p, '/');
return q ? (1+q) : p;
}
#endif
/* whine bitterly about something, then die
*/
void
die(char *fmt, ...)
{
va_list ptr;
fprintf(stderr, "%s: ", pgm);
va_start(ptr,fmt);
vfprintf(stderr, fmt, ptr);
va_end(ptr);
putc('\n', stderr);
exit(1);
}
/* allocate memory or die trying
*/
void *
xmalloc(int size)
{
void *ret = malloc(size);
if ( ret ) return ret;
die("cannot allocate %d byte%s\n", size, (size==1)?"":"s");
}
#define malloc xmalloc
/* reallocate memory or die trying
*/
void *
xrealloc(void *ptr, int size)
{
void *ret = realloc(ptr, size);
if ( ret ) return ret;
die("cannot reallocate %d byte%s\n", size, (size==1)?"":"s");
}
#define realloc xrealloc
struct x_option options[] = {
{ '?', '?', "help", 0, "Print this help message" },
{ '@', 0 , "version", 0, "Print a version message" },
{ '%', 0 , "copyright", 0, "Print the copyright notice" },
{ '0', '0', 0, 0, "Fields are separated with nulls" },
{ '1', '1', "single-column", 0, "One field per line" },
{ '9', '9', "plan9", 0, "Plan 9 compatability; echo nothing if\nno arguments were given" },
{ 'a', 'a', "ascii", 0, "Output in ASCII (default)" },
{ 'B', 'B', "base", "BASE","Output in given base, 2..32." },
{ 'b', 'b', "binary", 0, "Output in binary" },
{ 'C', 'C', "count", 0, "Don't echo anything; just print the\nnumber of fields" },
{ 'c', 'c', "run", "CMD","Run CMD on each argument, replacing $?\nwith the argument itself" },
{ 'D', 'D', "decimal", 0, "Output in decimal" },
{ 'd', 'd', "delimiter", "CHAR","Input field delimiter" },
{ 'E', 'E', "to-stderr", 0, "Print to stderr instead of stdout" },
{ 'e', 'e', "allow-escapes", 0, "Allow escape sequences" },
{ 'f', 'f', "input", "FILE","Read from FILE, then from command line\n(if any)" },
{ 'i', 'i', "from-stdin", 0, "Read arguments from stdin" },
{ 'L', 'L', "width", "LEN","Line width set to LEN" },
{ 'l', 'l', "lowercase", 0, "Turn uppercase to lowercase" },
{ 'm', 'm', "multi-column", 0, "Multi-column output" },
{ 'N', 'n', "numbered", 0, "One field per line, numbering each field" },
{ 'n', 'n', 0, 0, "Suppress newline" },
{ 'O', 'O', "octal", 0, "Output in octal" },
{ 'o', 'o', "output", "FILE","Write to FILE instead of standard output" },
{ 'q', 'q', "quiet", 0, "Quiet mode; redirect output to /dev/null\nif not to a file" },
{ 'R', 'R', "SPQR", 0, "Print output in roman numerals" },
{ 'r', 'r', "pattern", "RE","Print every thing that matches RE" },
{ 'S', 'S', "say", "VOICE","Send to speaker, having the given\nvoice say it" },
{ 's', 's', "separator", "CHAR","Separate output fields with CHAR" },
{ 't', 't', "tabs", 0, "Separate fields with tabs" },
{ 'u', 'u', "uppercase", 0, "Convert lowercase to uppercase" },
{ 'V', 'V', "no-nonprinting", 0, "Strip non-printing characters" },
{ 'v', 'v', "see-nonprinting", 0, "Make non-printing characters visible" },
{ 'X', 'X', "hexadecimal", 0, "Output in uppercase hexadecimal" },
{ 'x', 'x', 0, 0, "Output in lowercase hexadecimal" },
};
#define NR(x) (sizeof x / sizeof x[0])
/* spit out a usage message, then die
*/
void
usage(int rc)
{
char eb[BUFSIZ];
setbuf(stderr, eb);
fprintf(stderr, "usage: %s [options] [args...]\n", pgm);
showopts(stderr, NR(options), options);
exit(rc);
}
/* printf onto the end of a Cstring
*/
void
Cprintf(Cstring *arg, char *fmt, ...)
{
va_list ptr;
int avail, needed;
if ( (*arg).alloc <= S(*arg) )
RESERVE(*arg,100);
avail = (*arg).alloc - S(*arg);
/* try writing the fmt into the existing
* Cstring, and if that fails reserve enough
* room to fit it and try again.
*/
va_start(ptr, fmt);
needed = vsnprintf(T(*arg)+S(*arg), avail, fmt, ptr);
va_end(ptr);
if ( needed >= avail ) {
RESERVE(*arg, needed+2);
va_start(ptr, fmt);
vsnprintf(T(*arg)+S(*arg), needed+1, fmt, ptr);
va_end(ptr);
}
S(*arg) += needed;
}
/* do a command, replacing every instance of $? with the
* current argument.
*/
void
docommand(FILE *out)
{
int i, j;
S(command) = 0;
for (i=0; cmd[i]; i++) {
if ( (cmd[i] == '$') && (cmd[i+1] == '?') ) {
++i;
STRINGCAT(command, T(arg), S(arg));
}
else
EXPAND(command) = cmd[i];
}
EXPAND(command) = 0;
system(T(command));
}
/* display a number in base <n>
*/
void
basen(unsigned int c, FILE *out)
{
static char base32[] = "0123456789abcdefghijklmnopqrstuvwxyz";
if ( c/base )
basen(c/base, out);
c %= base;
switch (whichcase) {
case UPPERCASE: printc(toupper(base32[c]), out); break;
case LOWERCASE: printc(tolower(base32[c]), out); break;
default: printc(base32[c], out); break;
}
}
/*
* roman numeral output
*/
char *r1to10[] = { "2", "0", "00", "000", "01",
"1", "10", "100", "1000", "02" };
struct {
int val;
char let[3];
} d_r[] = { { 0, "???" },
{ 1, "IVX" }, { 10, "XLC" }, { 100, "CDM" },
{ 1000, "Mvx" }, { 10000, "xlc" }, { 100000, "cdm" }, };
#define NRDR (sizeof d_r / sizeof d_r[0])
void
baser(FILE *out, unsigned char number)
{
int i, j, rep;
if ( number == 0 )
printfmt(out, "nil", 0);
else
for ( i=6; i > 0; --i ) {
if ( rep = (number / d_r[i].val) )
for (j=0; r1to10[rep][j]; j++)
printc(d_r[i].let[r1to10[rep][j]-'0'], out);
number %= d_r[i].val;
}
}
/* print a character, processing it according to whichever of the
* bewildering collection of options were used.
*/
void
outc(unsigned char c, FILE *out)
{
int i;
switch (format) {
case ASCII:
if ( (c == '') && !doescapes ) {
printc('^', out);
printc('[', out);
break;
}
if ( !isprint(c) ) {
if ( nononprint ) break;
if ( visnonprint ) { printfmt(out, "%#o", c); break; }
}
switch (whichcase) {
case UPPERCASE: printc(toupper(c), out); break;
case LOWERCASE: printc(tolower(c), out); break;
default: printc(c, out); break;
}
break;
case BINARY:
printc(' ', out);
for (i=7; i; --i)
printc( c & (1<<i) ? '1': '0', out);
break;
case OCTAL:
printfmt(out, " %03o", c);
break;
case DECIMAL:
printfmt(out, " %d", c);
break;
case HEX:
printfmt(out, (whichcase==UPPERCASE)?" %02X":" %02x", c);
break;
case SPQR:
printc(' ', out);
baser(out, c);
break;
default:
printc(' ', out);
basen(c, out);
break;
}
}
/* match a string against every regular expression give,
* only returning 1 of nothing matches.
*/
int
match_re(char *text)
{
int i;
for ( i=0; i < S(regex); i++)
if ( regexec(&T(regex)[i], text, 0, 0, 0) != 0 )
return 0;
return 1;
}
/* flush any leftover parts of the output buffer
*/
flush(FILE *out)
{
fwrite(T(oline), S(oline), 1, out);
S(oline) = 0;
}
/* write a character (in some arbitrary printf format)
* to the output buffer
*/
void
printfmt(FILE *out, char *fmt, unsigned char arg)
{
int i;
static Cstring Ppbuf = { 0 };
S(Ppbuf) = 0;
Cprintf(&Ppbuf, fmt, arg);
for (i=0; i < S(Ppbuf); i++)
printc(T(Ppbuf)[i], out);
}
/* write a character to the output buffer, printing
* as necessary.
*/
void
printc(unsigned char c, FILE *out)
{
if ( width ) {
int tb;
if ( c == '\t' ) {
if ( !(tb = S(oline)%8) )
tb = 8;
while (tb-- > 0)
printc(' ', out);
}
if ( c == '\n' || c == '' ) {
flush(out);
putc(c, out);
}
else {
if ( S(oline) >= width ) {
int eol;
if ( wordwrap ) {
for ( eol=S(oline)-1; (eol > 0) && !isspace(T(oline)[eol-1]); --eol)
;
if ( eol <= 0 ) eol = width;
}
else
eol = width;
fwrite(T(oline), eol, 1, out);
putc('\n', out);
CLIP(oline,0,eol);
}
EXPAND(oline) = c;
}
}
else
putc(c, out);
}
/* print an argument.
*/
void
printarg(FILE *out)
{
int i;
if ( counter > 1 )
printc(outputdelim, out);
if ( numbered )
printfmt(out, "%d\t", counter);
for (i=0; i < S(arg); i++)
outc(T(arg)[i], out);
}
/* read arguments from a file.
*/
void
filetokens(FILE *in, FILE *out)
{
char c;
char mydelim = zero ? 0 : (delim ? delim : '\n');
do {
S(arg) = 0;
while ( (c = getc(in)) != EOF && c != mydelim )
EXPAND(arg) = c;
if ( (c == EOF) && (S(arg) == 0) )
break;
EXPAND(arg) = 0;
S(arg)--;
if ( !match_re(T(arg)) )
continue;
counter++;
if ( cmd )
docommand(out);
else if ( out )
printarg(out);
} while ( c != EOF );
}
/* read arguments off the command line
*/
void
cmdtokens(char *in, FILE *out)
{
do {
S(arg) = 0;
while ( *in && (*in != delim) )
EXPAND(arg) = *in++;
if ( *in ) ++in;
EXPAND(arg) = 0;
S(arg)--;
if ( !match_re(T(arg)) )
continue;
counter++;
if ( cmd )
docommand(out);
else if ( out )
printarg(out);
} while ( *in );
}
/* add a new regular expression to the gantlet
*/
void
add_re(char *pat)
{
int rc;
char oops[80];
#ifndef REG_BASIC
#define REG_BASIC 0
#endif
rc = regcomp(&EXPAND(regex), pat, REG_BASIC);
if ( rc ) {
regerror(rc, &T(regex)[S(regex)-1], oops, sizeof oops);
die("%s: %s", pat, oops);
}
}
/* frankenecho, in the flesh
*/
main(argc, argv)
int argc;
char **argv;
{
int opt;
extern char version[];
extern char copyright[];
opterr = 1;
pgm = basename(argv[0]);
while ( (opt=x_getopt(argc,argv, NR(options), options)) != EOF ) {
switch (opt) {
case '0': delim = 0; zero = 1; break;
case '1': outputdelim='\n'; nonl = 0; break;
case '9': plan9e = 1; break;
case 'a': format = ASCII; break;
case 'B': format = BASEN;
base = atoi(x_optarg);
if ( base <= 0 || base > 32 )
die("bad base for -B argument");
switch (base) {
case 2: format = BINARY; break;
case 8: format = OCTAL; break;
case 10: format = DECIMAL; break;
case 16: format = HEX; break;
}
break;
case 'b': format = BINARY; break;
case 'C': quiet = 2; count = 1; break;
case 'c': cmd = x_optarg; nonl = 1; break;
case 'd': delim = x_optarg[0]; break;
case 'E': output = stderr; break;
case 'e': doescapes = 1; break;
case 'f': filename = x_optarg; cmdline = 1; break;
case 'i': filename = "-"; cmdline = 0; break;
case 'L': width = atoi(x_optarg); break;
case 'l': whichcase = LOWERCASE; break;
case 'm': multicolumn = 1; break;
case 'n': nonl = 1; break;
case 'N': numbered = 1; outputdelim='\n'; nonl=0; break;
case 'O': format = OCTAL; break;
case 'o': if ( ! (output = fopen(x_optarg, "w")) )
die("can't write to %s", x_optarg);
break;
case 'q': quiet = 1; break;
case 'R': format = SPQR; whichcase = ASIS; break;
case 'r': add_re(x_optarg); break;
case 'S': voice = x_optarg; die("-S is not implemented here"); break;
case 't': outputdelim='\t'; break;
case 's': outputdelim=x_optarg[0]; break;
case 'u': whichcase = UPPERCASE; break;
case 'V': nononprint = 1; break;
case 'v': visnonprint = 1; break;
case 'w': wordwrap = 1; break;
case 'X': format = HEX; whichcase = UPPERCASE; break;
case 'x': format = HEX; whichcase = LOWERCASE; break;
case '@': printf("version %s\n", version); exit(0);
case '%': printf("%s\n", copyright); exit(0);
case '?': usage(0);
default: usage(1);
}
}
switch ( quiet ) {
case 0: output = stdout; break;
case 1: if ( output ) quiet = 0; break;
case 2: output = 0;
}
if ( filename ) {
FILE* input;
if ( strcmp(filename, "-") == 0 )
filetokens(stdin, output);
else if ( input = fopen(filename, "r") ) {
filetokens(input, output);
fclose(input);
}
else
die("can't open %s for input", filename);
}
if ( cmdline ) {
int i;
for (i=x_optind; i < argc; i++)
cmdtokens(argv[i], output);
}
if ( output ) {
flush(output);
if ( !(nonl || (plan9e && (counter == 0))) )
putc('\n', output);
}
if ( count )
fprintf( output ? output : stdout, "%d\n", counter);
exit(0);
}