source: branches/3.2/mindi-busybox/coreutils/tr.c @ 3232

Last change on this file since 3232 was 3232, checked in by Bruno Cornec, 7 years ago
  • Update mindi-busybox to 1.21.1
File size: 9.2 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * Mini tr implementation for busybox
4 *
5 ** Copyright (c) 1987,1997, Prentice Hall   All rights reserved.
6 *
7 * The name of Prentice Hall may not be used to endorse or promote
8 * products derived from this software without specific prior
9 * written permission.
10 *
11 * Copyright (c) Michiel Huisjes
12 *
13 * This version of tr is adapted from Minix tr and was modified
14 * by Erik Andersen <andersen@codepoet.org> to be used in busybox.
15 *
16 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
17 */
18/* http://www.opengroup.org/onlinepubs/009695399/utilities/tr.html
19 * TODO: graph, print
20 */
21
22//kbuild:lib-$(CONFIG_TR) += tr.o
23
24//config:config TR
25//config:   bool "tr"
26//config:   default y
27//config:   help
28//config:     tr is used to squeeze, and/or delete characters from standard
29//config:     input, writing to standard output.
30//config:
31//config:config FEATURE_TR_CLASSES
32//config:   bool "Enable character classes (such as [:upper:])"
33//config:   default y
34//config:   depends on TR
35//config:   help
36//config:     Enable character classes, enabling commands such as:
37//config:     tr [:upper:] [:lower:] to convert input into lowercase.
38//config:
39//config:config FEATURE_TR_EQUIV
40//config:   bool "Enable equivalence classes"
41//config:   default y
42//config:   depends on TR
43//config:   help
44//config:     Enable equivalence classes, which essentially add the enclosed
45//config:     character to the current set. For instance, tr [=a=] xyz would
46//config:     replace all instances of 'a' with 'xyz'. This option is mainly
47//config:     useful for cases when no other way of expressing a character
48//config:     is possible.
49
50//usage:#define tr_trivial_usage
51//usage:       "[-cds] STRING1 [STRING2]"
52//usage:#define tr_full_usage "\n\n"
53//usage:       "Translate, squeeze, or delete characters from stdin, writing to stdout\n"
54//usage:     "\n    -c  Take complement of STRING1"
55//usage:     "\n    -d  Delete input characters coded STRING1"
56//usage:     "\n    -s  Squeeze multiple output characters of STRING2 into one character"
57//usage:
58//usage:#define tr_example_usage
59//usage:       "$ echo \"gdkkn vnqkc\" | tr [a-y] [b-z]\n"
60//usage:       "hello world\n"
61
62#include "libbb.h"
63
64enum {
65    ASCII = 256,
66    /* string buffer needs to be at least as big as the whole "alphabet".
67     * BUFSIZ == ASCII is ok, but we will realloc in expand
68     * even for smallest patterns, let's avoid that by using *2:
69     */
70    TR_BUFSIZ = (BUFSIZ > ASCII*2) ? BUFSIZ : ASCII*2,
71};
72
73static void map(char *pvector,
74        char *string1, unsigned string1_len,
75        char *string2, unsigned string2_len)
76{
77    char last = '0';
78    unsigned i, j;
79
80    for (j = 0, i = 0; i < string1_len; i++) {
81        if (string2_len <= j)
82            pvector[(unsigned char)(string1[i])] = last;
83        else
84            pvector[(unsigned char)(string1[i])] = last = string2[j++];
85    }
86}
87
88/* supported constructs:
89 *   Ranges,  e.g.,  0-9   ==>  0123456789
90 *   Escapes, e.g.,  \a    ==>  Control-G
91 *   Character classes, e.g. [:upper:] ==> A...Z
92 *   Equiv classess, e.g. [=A=] ==> A   (hmmmmmmm?)
93 * not supported:
94 *   \ooo-\ooo - octal ranges
95 *   [x*N] - repeat char x N times
96 *   [x*] - repeat char x until it fills STRING2:
97 * # echo qwe123 | /usr/bin/tr 123456789 '[d]'
98 * qwe[d]
99 * # echo qwe123 | /usr/bin/tr 123456789 '[d*]'
100 * qweddd
101 */
102static unsigned expand(const char *arg, char **buffer_p)
103{
104    char *buffer = *buffer_p;
105    unsigned pos = 0;
106    unsigned size = TR_BUFSIZ;
107    unsigned i; /* can't be unsigned char: must be able to hold 256 */
108    unsigned char ac;
109
110    while (*arg) {
111        if (pos + ASCII > size) {
112            size += ASCII;
113            *buffer_p = buffer = xrealloc(buffer, size);
114        }
115        if (*arg == '\\') {
116            arg++;
117            buffer[pos++] = bb_process_escape_sequence(&arg);
118            continue;
119        }
120        if (arg[1] == '-') { /* "0-9..." */
121            ac = arg[2];
122            if (ac == '\0') { /* "0-": copy verbatim */
123                buffer[pos++] = *arg++; /* copy '0' */
124                continue; /* next iter will copy '-' and stop */
125            }
126            i = (unsigned char) *arg;
127            while (i <= ac) /* ok: i is unsigned _int_ */
128                buffer[pos++] = i++;
129            arg += 3; /* skip 0-9 */
130            continue;
131        }
132        if ((ENABLE_FEATURE_TR_CLASSES || ENABLE_FEATURE_TR_EQUIV)
133         && *arg == '['
134        ) {
135            arg++;
136            i = (unsigned char) *arg++;
137            /* "[xyz...". i=x, arg points to y */
138            if (ENABLE_FEATURE_TR_CLASSES && i == ':') { /* [:class:] */
139#define CLO ":]\0"
140                static const char classes[] ALIGN1 =
141                    "alpha"CLO "alnum"CLO "digit"CLO
142                    "lower"CLO "upper"CLO "space"CLO
143                    "blank"CLO "punct"CLO "cntrl"CLO
144                    "xdigit"CLO;
145                enum {
146                    CLASS_invalid = 0, /* we increment the retval */
147                    CLASS_alpha = 1,
148                    CLASS_alnum = 2,
149                    CLASS_digit = 3,
150                    CLASS_lower = 4,
151                    CLASS_upper = 5,
152                    CLASS_space = 6,
153                    CLASS_blank = 7,
154                    CLASS_punct = 8,
155                    CLASS_cntrl = 9,
156                    CLASS_xdigit = 10,
157                    //CLASS_graph = 11,
158                    //CLASS_print = 12,
159                };
160                smalluint j;
161                char *tmp;
162
163                /* xdigit needs 8, not 7 */
164                i = 7 + (arg[0] == 'x');
165                tmp = xstrndup(arg, i);
166                j = index_in_strings(classes, tmp) + 1;
167                free(tmp);
168
169                if (j == CLASS_invalid)
170                    goto skip_bracket;
171
172                arg += i;
173                if (j == CLASS_alnum || j == CLASS_digit || j == CLASS_xdigit) {
174                    for (i = '0'; i <= '9'; i++)
175                        buffer[pos++] = i;
176                }
177                if (j == CLASS_alpha || j == CLASS_alnum || j == CLASS_upper) {
178                    for (i = 'A'; i <= 'Z'; i++)
179                        buffer[pos++] = i;
180                }
181                if (j == CLASS_alpha || j == CLASS_alnum || j == CLASS_lower) {
182                    for (i = 'a'; i <= 'z'; i++)
183                        buffer[pos++] = i;
184                }
185                if (j == CLASS_space || j == CLASS_blank) {
186                    buffer[pos++] = '\t';
187                    if (j == CLASS_space) {
188                        buffer[pos++] = '\n';
189                        buffer[pos++] = '\v';
190                        buffer[pos++] = '\f';
191                        buffer[pos++] = '\r';
192                    }
193                    buffer[pos++] = ' ';
194                }
195                if (j == CLASS_punct || j == CLASS_cntrl) {
196                    for (i = '\0'; i < ASCII; i++) {
197                        if ((j == CLASS_punct && isprint_asciionly(i) && !isalnum(i) && !isspace(i))
198                         || (j == CLASS_cntrl && iscntrl(i))
199                        ) {
200                            buffer[pos++] = i;
201                        }
202                    }
203                }
204                if (j == CLASS_xdigit) {
205                    for (i = 'A'; i <= 'F'; i++) {
206                        buffer[pos + 6] = i | 0x20;
207                        buffer[pos++] = i;
208                    }
209                    pos += 6;
210                }
211                continue;
212            }
213            /* "[xyz...", i=x, arg points to y */
214            if (ENABLE_FEATURE_TR_EQUIV && i == '=') { /* [=CHAR=] */
215                buffer[pos++] = *arg; /* copy CHAR */
216                if (!arg[0] || arg[1] != '=' || arg[2] != ']')
217                    bb_show_usage();
218                arg += 3;  /* skip CHAR=] */
219                continue;
220            }
221            /* The rest of "[xyz..." cases is treated as normal
222             * string, "[" has no special meaning here:
223             * tr "[a-z]" "[A-Z]" can be written as tr "a-z" "A-Z",
224             * also try tr "[a-z]" "_A-Z+" and you'll see that
225             * [] is not special here.
226             */
227 skip_bracket:
228            arg -= 2; /* points to "[" in "[xyz..." */
229        }
230        buffer[pos++] = *arg++;
231    }
232    return pos;
233}
234
235/* NB: buffer is guaranteed to be at least TR_BUFSIZE
236 * (which is >= ASCII) big.
237 */
238static int complement(char *buffer, int buffer_len)
239{
240    int len;
241    char conv[ASCII];
242    unsigned char ch;
243
244    len = 0;
245    ch = '\0';
246    while (1) {
247        if (memchr(buffer, ch, buffer_len) == NULL)
248            conv[len++] = ch;
249        if (++ch == '\0')
250            break;
251    }
252    memcpy(buffer, conv, len);
253    return len;
254}
255
256int tr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
257int tr_main(int argc UNUSED_PARAM, char **argv)
258{
259    int i;
260    smalluint opts;
261    ssize_t read_chars;
262    size_t in_index, out_index;
263    unsigned last = UCHAR_MAX + 1; /* not equal to any char */
264    unsigned char coded, c;
265    char *str1 = xmalloc(TR_BUFSIZ);
266    char *str2 = xmalloc(TR_BUFSIZ);
267    int str2_length;
268    int str1_length;
269    char *vector = xzalloc(ASCII * 3);
270    char *invec  = vector + ASCII;
271    char *outvec = vector + ASCII * 2;
272
273#define TR_OPT_complement   (3 << 0)
274#define TR_OPT_delete       (1 << 2)
275#define TR_OPT_squeeze_reps (1 << 3)
276
277    for (i = 0; i < ASCII; i++) {
278        vector[i] = i;
279        /*invec[i] = outvec[i] = FALSE; - done by xzalloc */
280    }
281
282    /* -C/-c difference is that -C complements "characters",
283     * and -c complements "values" (binary bytes I guess).
284     * In POSIX locale, these are the same.
285     */
286
287    opt_complementary = "-1";
288    opts = getopt32(argv, "+Ccds"); /* '+': stop at first non-option */
289    argv += optind;
290
291    str1_length = expand(*argv++, &str1);
292    str2_length = 0;
293    if (opts & TR_OPT_complement)
294        str1_length = complement(str1, str1_length);
295    if (*argv) {
296        if (argv[0][0] == '\0')
297            bb_error_msg_and_die("STRING2 cannot be empty");
298        str2_length = expand(*argv, &str2);
299        map(vector, str1, str1_length,
300                str2, str2_length);
301    }
302    for (i = 0; i < str1_length; i++)
303        invec[(unsigned char)(str1[i])] = TRUE;
304    for (i = 0; i < str2_length; i++)
305        outvec[(unsigned char)(str2[i])] = TRUE;
306
307    goto start_from;
308
309    /* In this loop, str1 space is reused as input buffer,
310     * str2 - as output one. */
311    for (;;) {
312        /* If we're out of input, flush output and read more input. */
313        if ((ssize_t)in_index == read_chars) {
314            if (out_index) {
315                xwrite(STDOUT_FILENO, str2, out_index);
316 start_from:
317                out_index = 0;
318            }
319            read_chars = safe_read(STDIN_FILENO, str1, TR_BUFSIZ);
320            if (read_chars <= 0) {
321                if (read_chars < 0)
322                    bb_perror_msg_and_die(bb_msg_read_error);
323                break;
324            }
325            in_index = 0;
326        }
327        c = str1[in_index++];
328        if ((opts & TR_OPT_delete) && invec[c])
329            continue;
330        coded = vector[c];
331        if ((opts & TR_OPT_squeeze_reps) && last == coded
332         && (invec[c] || outvec[coded])
333        ) {
334            continue;
335        }
336        str2[out_index++] = last = coded;
337    }
338
339    if (ENABLE_FEATURE_CLEAN_UP) {
340        free(vector);
341        free(str2);
342        free(str1);
343    }
344
345    return EXIT_SUCCESS;
346}
Note: See TracBrowser for help on using the repository browser.