busybox/coreutils/sort.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * SuS3 compliant sort implementation for busybox
   4 *
   5 * Copyright (C) 2004 by Rob Landley <rob@landley.net>
   6 *
   7 * MAINTAINER: Rob Landley <rob@landley.net>
   8 *
   9 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  10 *
  11 * See SuS3 sort standard at:
  12 * http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html
  13 */
  14//config:config SORT
  15//config:       bool "sort (7.4 kb)"
  16//config:       default y
  17//config:       help
  18//config:       sort is used to sort lines of text in specified files.
  19//config:
  20//config:config FEATURE_SORT_BIG
  21//config:       bool "Full SuSv3 compliant sort (support -ktcbdfiogM)"
  22//config:       default y
  23//config:       depends on SORT
  24//config:       help
  25//config:       Without this, sort only supports -rusz, and an integer version
  26//config:       of -n. Selecting this adds sort keys, floating point support, and
  27//config:       more. This adds a little over 3k to a nonstatic build on x86.
  28//config:
  29//config:       The SuSv3 sort standard is available at:
  30//config:       http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html
  31//config:
  32//config:config FEATURE_SORT_OPTIMIZE_MEMORY
  33//config:       bool "Use less memory (but might be slower)"
  34//config:       default n   # defaults to N since we are size-paranoid tribe
  35//config:       depends on SORT
  36//config:       help
  37//config:       Attempt to use less memory (by storing only one copy
  38//config:       of duplicated lines, and such). Useful if you work on huge files.
  39
  40//applet:IF_SORT(APPLET_NOEXEC(sort, sort, BB_DIR_USR_BIN, BB_SUID_DROP, sort))
  41
  42//kbuild:lib-$(CONFIG_SORT) += sort.o
  43
  44//usage:#define sort_trivial_usage
  45//usage:       "[-nru"
  46//usage:        IF_FEATURE_SORT_BIG("gMcszbdfiokt] [-o FILE] [-k start[.offset][opts][,end[.offset][opts]] [-t CHAR")
  47//usage:       "] [FILE]..."
  48//usage:#define sort_full_usage "\n\n"
  49//usage:       "Sort lines of text\n"
  50//usage:        IF_FEATURE_SORT_BIG(
  51//usage:     "\n        -o FILE Output to FILE"
  52//usage:     "\n        -c      Check whether input is sorted"
  53//usage:     "\n        -b      Ignore leading blanks"
  54//usage:     "\n        -f      Ignore case"
  55//usage:     "\n        -i      Ignore unprintable characters"
  56//usage:     "\n        -d      Dictionary order (blank or alphanumeric only)"
  57//usage:        )
  58//-h, --human-numeric-sort: compare human readable numbers (e.g., 2K 1G)
  59//usage:     "\n        -n      Sort numbers"
  60//usage:        IF_FEATURE_SORT_BIG(
  61//usage:     "\n        -g      General numerical sort"
  62//usage:     "\n        -M      Sort month"
  63//usage:     "\n        -t CHAR Field separator"
  64//usage:     "\n        -k N[,M] Sort by Nth field"
  65//usage:        )
  66//usage:     "\n        -r      Reverse sort order"
  67//usage:     "\n        -s      Stable (don't sort ties alphabetically)"
  68//usage:     "\n        -u      Suppress duplicate lines"
  69//usage:     "\n        -z      Lines are terminated by NUL, not newline"
  70///////:     "\n        -m      Ignored for GNU compatibility"
  71///////:     "\n        -S BUFSZ Ignored for GNU compatibility"
  72///////:     "\n        -T TMPDIR Ignored for GNU compatibility"
  73//usage:
  74//usage:#define sort_example_usage
  75//usage:       "$ echo -e \"e\\nf\\nb\\nd\\nc\\na\" | sort\n"
  76//usage:       "a\n"
  77//usage:       "b\n"
  78//usage:       "c\n"
  79//usage:       "d\n"
  80//usage:       "e\n"
  81//usage:       "f\n"
  82//usage:        IF_FEATURE_SORT_BIG(
  83//usage:                "$ echo -e \"c 3\\nb 2\\nd 2\" | $SORT -k 2,2n -k 1,1r\n"
  84//usage:                "d 2\n"
  85//usage:                "b 2\n"
  86//usage:                "c 3\n"
  87//usage:        )
  88//usage:       ""
  89
  90#include "libbb.h"
  91
  92/* These are sort types */
  93enum {
  94        FLAG_n  = 1,            /* Numeric sort */
  95        FLAG_g  = 2,            /* Sort using strtod() */
  96        FLAG_M  = 4,            /* Sort date */
  97/* ucsz apply to root level only, not keys.  b at root level implies bb */
  98        FLAG_u  = 8,            /* Unique */
  99        FLAG_c  = 0x10,         /* Check: no output, exit(!ordered) */
 100        FLAG_s  = 0x20,         /* Stable sort, no ascii fallback at end */
 101        FLAG_z  = 0x40,         /* Input and output is NUL terminated, not \n */
 102/* These can be applied to search keys, the previous four can't */
 103        FLAG_b  = 0x80,         /* Ignore leading blanks */
 104        FLAG_r  = 0x100,        /* Reverse */
 105        FLAG_d  = 0x200,        /* Ignore !(isalnum()|isspace()) */
 106        FLAG_f  = 0x400,        /* Force uppercase */
 107        FLAG_i  = 0x800,        /* Ignore !isprint() */
 108        FLAG_m  = 0x1000,       /* ignored: merge already sorted files; do not sort */
 109        FLAG_S  = 0x2000,       /* ignored: -S, --buffer-size=SIZE */
 110        FLAG_T  = 0x4000,       /* ignored: -T, --temporary-directory=DIR */
 111        FLAG_o  = 0x8000,
 112        FLAG_k  = 0x10000,
 113        FLAG_t  = 0x20000,
 114        FLAG_bb = 0x80000000,   /* Ignore trailing blanks  */
 115        FLAG_no_tie_break = 0x40000000,
 116};
 117
 118static const char sort_opt_str[] ALIGN1 = "^"
 119                        "ngMucszbrdfimS:T:o:k:*t:"
 120                        "\0" "o--o:t--t"/*-t, -o: at most one of each*/;
 121/*
 122 * OPT_STR must not be string literal, needs to have stable address:
 123 * code uses "strchr(OPT_STR,c) - OPT_STR" idiom.
 124 */
 125#define OPT_STR (sort_opt_str + 1)
 126
 127#if ENABLE_FEATURE_SORT_BIG
 128static char key_separator;
 129
 130static struct sort_key {
 131        struct sort_key *next_key;  /* linked list */
 132        unsigned range[4];          /* start word, start char, end word, end char */
 133        unsigned flags;
 134} *key_list;
 135
 136
 137/* This is a NOEXEC applet. Be very careful! */
 138
 139
 140static char *get_key(char *str, struct sort_key *key, int flags)
 141{
 142        int start = start; /* for compiler */
 143        int end;
 144        int len, j;
 145        unsigned i;
 146
 147        /* Special case whole string, so we don't have to make a copy */
 148        if (key->range[0] == 1 && !key->range[1] && !key->range[2] && !key->range[3]
 149         && !(flags & (FLAG_b | FLAG_d | FLAG_f | FLAG_i | FLAG_bb))
 150        ) {
 151                return str;
 152        }
 153
 154        /* Find start of key on first pass, end on second pass */
 155        len = strlen(str);
 156        for (j = 0; j < 2; j++) {
 157                if (!key->range[2*j])
 158                        end = len;
 159                /* Loop through fields */
 160                else {
 161                        unsigned char ch = 0;
 162
 163                        end = 0;
 164                        for (i = 1; i < key->range[2*j] + j; i++) {
 165                                if (key_separator) {
 166                                        /* Skip body of key and separator */
 167                                        while ((ch = str[end]) != '\0') {
 168                                                        end++;
 169                                                if (ch == key_separator)
 170                                                        break;
 171                                        }
 172                                } else {
 173                                        /* Skip leading blanks */
 174                                        while (isspace(str[end]))
 175                                                end++;
 176                                        /* Skip body of key */
 177                                        while (str[end] != '\0') {
 178                                                if (isspace(str[end]))
 179                                                        break;
 180                                                end++;
 181                                        }
 182                                }
 183                        }
 184                        /* Remove last delim: "abc:def:" => "abc:def" */
 185                        if (j && ch) {
 186                                //if (str[end-1] != key_separator)
 187                                //  bb_error_msg(_and_die("BUG! "
 188                                //  "str[start:%d,end:%d]:'%.*s'",
 189                                //  start, end, (int)(end-start), &str[start]);
 190                                end--;
 191                        }
 192                }
 193                if (!j) start = end;
 194        }
 195        /* Strip leading whitespace if necessary */
 196        if (flags & FLAG_b)
 197                /* not using skip_whitespace() for speed */
 198                while (isspace(str[start])) start++;
 199        /* Strip trailing whitespace if necessary */
 200        if (flags & FLAG_bb)
 201                while (end > start && isspace(str[end-1])) end--;
 202        /* -kSTART,N.ENDCHAR: honor ENDCHAR (1-based) */
 203        if (key->range[3]) {
 204                end = key->range[3];
 205                if (end > len) end = len;
 206        }
 207        /* -kN.STARTCHAR[,...]: honor STARTCHAR (1-based) */
 208        if (key->range[1]) {
 209                start += key->range[1] - 1;
 210                if (start > len) start = len;
 211        }
 212        /* Make the copy */
 213        if (end < start)
 214                end = start;
 215        str = xstrndup(str+start, end-start);
 216        /* Handle -d */
 217        if (flags & FLAG_d) {
 218                for (start = end = 0; str[end]; end++)
 219                        if (isspace(str[end]) || isalnum(str[end]))
 220                                str[start++] = str[end];
 221                str[start] = '\0';
 222        }
 223        /* Handle -i */
 224        if (flags & FLAG_i) {
 225                for (start = end = 0; str[end]; end++)
 226                        if (isprint_asciionly(str[end]))
 227                                str[start++] = str[end];
 228                str[start] = '\0';
 229        }
 230        /* Handle -f */
 231        if (flags & FLAG_f)
 232                for (i = 0; str[i]; i++)
 233                        str[i] = toupper(str[i]);
 234
 235        return str;
 236}
 237
 238static struct sort_key *add_key(void)
 239{
 240        struct sort_key **pkey = &key_list;
 241        while (*pkey)
 242                pkey = &((*pkey)->next_key);
 243        return *pkey = xzalloc(sizeof(struct sort_key));
 244}
 245
 246#define GET_LINE(fp) \
 247        ((option_mask32 & FLAG_z) \
 248        ? bb_get_chunk_from_file(fp, NULL) \
 249        : xmalloc_fgetline(fp))
 250#else
 251#define GET_LINE(fp) xmalloc_fgetline(fp)
 252#endif
 253
 254/* Iterate through keys list and perform comparisons */
 255static int compare_keys(const void *xarg, const void *yarg)
 256{
 257        int flags = option_mask32, retval = 0;
 258        char *x, *y;
 259
 260#if ENABLE_FEATURE_SORT_BIG
 261        struct sort_key *key;
 262
 263        for (key = key_list; !retval && key; key = key->next_key) {
 264                flags = key->flags ? key->flags : option_mask32;
 265                /* Chop out and modify key chunks, handling -dfib */
 266                x = get_key(*(char **)xarg, key, flags);
 267                y = get_key(*(char **)yarg, key, flags);
 268#else
 269        /* This curly bracket serves no purpose but to match the nesting
 270         * level of the for () loop we're not using */
 271        {
 272                x = *(char **)xarg;
 273                y = *(char **)yarg;
 274#endif
 275                /* Perform actual comparison */
 276                switch (flags & (FLAG_n | FLAG_M | FLAG_g)) {
 277                default:
 278                        bb_error_msg_and_die("unknown sort type");
 279                        break;
 280                /* Ascii sort */
 281                case 0:
 282#if ENABLE_LOCALE_SUPPORT
 283                        retval = strcoll(x, y);
 284#else
 285                        retval = strcmp(x, y);
 286#endif
 287                        break;
 288#if ENABLE_FEATURE_SORT_BIG
 289                case FLAG_g: {
 290                        char *xx, *yy;
 291                        double dx = strtod(x, &xx);
 292                        double dy = strtod(y, &yy);
 293                        /* not numbers < NaN < -infinity < numbers < +infinity) */
 294                        if (x == xx)
 295                                retval = (y == yy ? 0 : -1);
 296                        else if (y == yy)
 297                                retval = 1;
 298                        /* Check for isnan */
 299                        else if (dx != dx)
 300                                retval = (dy != dy) ? 0 : -1;
 301                        else if (dy != dy)
 302                                retval = 1;
 303                        /* Check for infinity.  Could underflow, but it avoids libm. */
 304                        else if (1.0 / dx == 0.0) {
 305                                if (dx < 0)
 306                                        retval = (1.0 / dy == 0.0 && dy < 0) ? 0 : -1;
 307                                else
 308                                        retval = (1.0 / dy == 0.0 && dy > 0) ? 0 : 1;
 309                        } else if (1.0 / dy == 0.0)
 310                                retval = (dy < 0) ? 1 : -1;
 311                        else
 312                                retval = (dx > dy) ? 1 : ((dx < dy) ? -1 : 0);
 313                        break;
 314                }
 315                case FLAG_M: {
 316                        struct tm thyme;
 317                        int dx;
 318                        char *xx, *yy;
 319
 320                        xx = strptime(x, "%b", &thyme);
 321                        dx = thyme.tm_mon;
 322                        yy = strptime(y, "%b", &thyme);
 323                        if (!xx)
 324                                retval = (!yy) ? 0 : -1;
 325                        else if (!yy)
 326                                retval = 1;
 327                        else
 328                                retval = dx - thyme.tm_mon;
 329                        break;
 330                }
 331                /* Full floating point version of -n */
 332                case FLAG_n: {
 333                        double dx = atof(x);
 334                        double dy = atof(y);
 335                        retval = (dx > dy) ? 1 : ((dx < dy) ? -1 : 0);
 336                        break;
 337                }
 338                } /* switch */
 339                /* Free key copies. */
 340                if (x != *(char **)xarg) free(x);
 341                if (y != *(char **)yarg) free(y);
 342                /* if (retval) break; - done by for () anyway */
 343#else
 344                /* Integer version of -n for tiny systems */
 345                case FLAG_n:
 346                        retval = atoi(x) - atoi(y);
 347                        break;
 348                } /* switch */
 349#endif
 350        } /* for */
 351
 352        if (retval == 0) {
 353                /* So far lines are "the same" */
 354
 355                if (option_mask32 & FLAG_s) {
 356                        /* "Stable sort": later line is "greater than",
 357                         * IOW: do not allow qsort() to swap equal lines.
 358                         */
 359                        uint32_t *p32;
 360                        uint32_t x32, y32;
 361                        char *line;
 362                        unsigned len;
 363
 364                        line = *(char**)xarg;
 365                        len = (strlen(line) + 4) & (~3u);
 366                        p32 = (void*)(line + len);
 367                        x32 = *p32;
 368                        line = *(char**)yarg;
 369                        len = (strlen(line) + 4) & (~3u);
 370                        p32 = (void*)(line + len);
 371                        y32 = *p32;
 372
 373                        /* If x > y, 1, else -1 */
 374                        retval = (x32 > y32) * 2 - 1;
 375                } else
 376                if (!(option_mask32 & FLAG_no_tie_break)) {
 377                        /* fallback sort */
 378                        flags = option_mask32;
 379                        retval = strcmp(*(char **)xarg, *(char **)yarg);
 380                }
 381        }
 382
 383        if (flags & FLAG_r)
 384                return -retval;
 385
 386        return retval;
 387}
 388
 389#if ENABLE_FEATURE_SORT_BIG
 390static unsigned str2u(char **str)
 391{
 392        unsigned long lu;
 393        if (!isdigit((*str)[0]))
 394                bb_error_msg_and_die("bad field specification");
 395        lu = strtoul(*str, str, 10);
 396        if ((sizeof(long) > sizeof(int) && lu > INT_MAX) || !lu)
 397                bb_error_msg_and_die("bad field specification");
 398        return lu;
 399}
 400#endif
 401
 402int sort_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 403int sort_main(int argc UNUSED_PARAM, char **argv)
 404{
 405        char **lines;
 406        char *str_ignored, *str_o, *str_t;
 407        llist_t *lst_k = NULL;
 408        int i;
 409        int linecount;
 410        unsigned opts;
 411#if ENABLE_FEATURE_SORT_OPTIMIZE_MEMORY
 412        bool can_drop_dups;
 413        size_t prev_len = 0;
 414        char *prev_line = (char*) "";
 415        /* Postpone optimizing if the input is small, < 16k lines:
 416         * even just free()ing duplicate lines takes time.
 417         */
 418        size_t count_to_optimize_dups = 0x3fff;
 419#endif
 420
 421        xfunc_error_retval = 2;
 422
 423        /* Parse command line options */
 424        opts = getopt32(argv,
 425                        sort_opt_str,
 426                        &str_ignored, &str_ignored, &str_o, &lst_k, &str_t
 427        );
 428#if ENABLE_FEATURE_SORT_OPTIMIZE_MEMORY
 429        /* Can drop dups only if -u but no "complicating" options,
 430         * IOW: if we do a full line compares. Safe options:
 431         * -o FILE Output to FILE
 432         * -z      Lines are terminated by NUL, not newline
 433         * -r      Reverse sort order
 434         * -s      Stable (don't sort ties alphabetically)
 435         * Not sure about these:
 436         * -b      Ignore leading blanks
 437         * -f      Ignore case
 438         * -i      Ignore unprintable characters
 439         * -d      Dictionary order (blank or alphanumeric only)
 440         * -n      Sort numbers
 441         * -g      General numerical sort
 442         * -M      Sort month
 443         */
 444        can_drop_dups = ((opts & ~(FLAG_o|FLAG_z|FLAG_r|FLAG_s)) == FLAG_u);
 445        /* Stable sort needs every line to be uniquely allocated,
 446         * disable optimization to reuse strings:
 447         */
 448        if (opts & FLAG_s)
 449                count_to_optimize_dups = (size_t)-1L;
 450#endif
 451        /* global b strips leading and trailing spaces */
 452        if (opts & FLAG_b)
 453                option_mask32 |= FLAG_bb;
 454#if ENABLE_FEATURE_SORT_BIG
 455        if (opts & FLAG_t) {
 456                if (!str_t[0] || str_t[1])
 457                        bb_error_msg_and_die("bad -t parameter");
 458                key_separator = str_t[0];
 459        }
 460        /* note: below this point we use option_mask32, not opts,
 461         * since that reduces register pressure and makes code smaller */
 462
 463        /* Parse sort key */
 464        while (lst_k) {
 465                enum {
 466                        FLAG_allowed_for_k =
 467                                FLAG_n | /* Numeric sort */
 468                                FLAG_g | /* Sort using strtod() */
 469                                FLAG_M | /* Sort date */
 470                                FLAG_b | /* Ignore leading blanks */
 471                                FLAG_r | /* Reverse */
 472                                FLAG_d | /* Ignore !(isalnum()|isspace()) */
 473                                FLAG_f | /* Force uppercase */
 474                                FLAG_i | /* Ignore !isprint() */
 475                        0
 476                };
 477                struct sort_key *key = add_key();
 478                char *str_k = llist_pop(&lst_k);
 479
 480                i = 0; /* i==0 before comma, 1 after (-k3,6) */
 481                while (*str_k) {
 482                        /* Start of range */
 483                        /* Cannot use bb_strtou - suffix can be a letter */
 484                        key->range[2*i] = str2u(&str_k);
 485                        if (*str_k == '.') {
 486                                str_k++;
 487                                key->range[2*i+1] = str2u(&str_k);
 488                        }
 489                        while (*str_k) {
 490                                int flag;
 491                                const char *idx;
 492
 493                                if (*str_k == ',' && !i++) {
 494                                        str_k++;
 495                                        break;
 496                                } /* no else needed: fall through to syntax error
 497                                        because comma isn't in OPT_STR */
 498                                idx = strchr(OPT_STR, *str_k);
 499                                if (!idx)
 500                                        bb_error_msg_and_die("unknown key option");
 501                                flag = 1 << (idx - OPT_STR);
 502                                if (flag & ~FLAG_allowed_for_k)
 503                                        bb_error_msg_and_die("unknown sort type");
 504                                /* b after ',' means strip _trailing_ space */
 505                                if (i && flag == FLAG_b)
 506                                        flag = FLAG_bb;
 507                                key->flags |= flag;
 508                                str_k++;
 509                        }
 510                }
 511        }
 512#endif
 513
 514        /* Open input files and read data */
 515        argv += optind;
 516        if (!*argv)
 517                *--argv = (char*)"-";
 518        linecount = 0;
 519        lines = NULL;
 520        do {
 521                /* coreutils 6.9 compat: abort on first open error,
 522                 * do not continue to next file: */
 523                FILE *fp = xfopen_stdin(*argv);
 524                for (;;) {
 525                        char *line = GET_LINE(fp);
 526                        if (!line)
 527                                break;
 528
 529#if ENABLE_FEATURE_SORT_OPTIMIZE_MEMORY
 530                        if (count_to_optimize_dups != 0)
 531                                count_to_optimize_dups--;
 532                        if (count_to_optimize_dups == 0) {
 533                                size_t len;
 534                                char *new_line;
 535
 536                                /* On kernel/linux/arch/ *.[ch] files,
 537                                 * this reduces memory usage by 6%.
 538                                 *  yes | head -99999999 | sort
 539                                 * goes down from 1900Mb to 380 Mb.
 540                                 */
 541                                len = strlen(line);
 542                                if (len <= prev_len) {
 543                                        new_line = prev_line + (prev_len - len);
 544                                        if (strcmp(line, new_line) == 0) {
 545                                                /* it's a tail of the prev line */
 546                                                if (can_drop_dups && prev_len == len) {
 547                                                        /* it's identical to prev line */
 548                                                        free(line);
 549                                                        continue;
 550                                                }
 551                                                free(line);
 552                                                line = new_line;
 553                                                /* continue using longer prev_line
 554                                                 * for future tail tests.
 555                                                 */
 556                                                goto skip;
 557                                        }
 558                                }
 559                                prev_len = len;
 560                                prev_line = line;
 561 skip: ;
 562                        }
 563#else
 564//TODO: lighter version which only drops total dups if can_drop_dups == true
 565#endif
 566                        lines = xrealloc_vector(lines, 6, linecount);
 567                        lines[linecount++] = line;
 568                }
 569                fclose_if_not_stdin(fp);
 570        } while (*++argv);
 571
 572#if ENABLE_FEATURE_SORT_BIG
 573        /* If no key, perform alphabetic sort */
 574        if (!key_list)
 575                add_key()->range[0] = 1;
 576        /* Handle -c */
 577        if (option_mask32 & FLAG_c) {
 578                int j = (option_mask32 & FLAG_u) ? -1 : 0;
 579                for (i = 1; i < linecount; i++) {
 580                        if (compare_keys(&lines[i-1], &lines[i]) > j) {
 581                                fprintf(stderr, "Check line %u\n", i);
 582                                return EXIT_FAILURE;
 583                        }
 584                }
 585                return EXIT_SUCCESS;
 586        }
 587#endif
 588
 589        /* For stable sort, store original line position beyond terminating NUL */
 590        if (option_mask32 & FLAG_s) {
 591                for (i = 0; i < linecount; i++) {
 592                        uint32_t *p32;
 593                        char *line;
 594                        unsigned len;
 595
 596                        line = lines[i];
 597                        len = (strlen(line) + 4) & (~3u);
 598                        lines[i] = line = xrealloc(line, len + 4);
 599                        p32 = (void*)(line + len);
 600                        *p32 = i;
 601                }
 602                /*option_mask32 |= FLAG_no_tie_break;*/
 603                /* ^^^redundant: if FLAG_s, compare_keys() does no tie break */
 604        }
 605
 606        /* Perform the actual sort */
 607        qsort(lines, linecount, sizeof(lines[0]), compare_keys);
 608
 609        /* Handle -u */
 610        if (option_mask32 & FLAG_u) {
 611                int j = 0;
 612                /* coreutils 6.3 drop lines for which only key is the same
 613                 * -- disabling last-resort compare, or else compare_keys()
 614                 * will be the same only for completely identical lines.
 615                 */
 616                option_mask32 |= FLAG_no_tie_break;
 617                for (i = 1; i < linecount; i++) {
 618                        if (compare_keys(&lines[j], &lines[i]) == 0)
 619                                free(lines[i]);
 620                        else
 621                                lines[++j] = lines[i];
 622                }
 623                if (linecount)
 624                        linecount = j+1;
 625        }
 626
 627        /* Print it */
 628#if ENABLE_FEATURE_SORT_BIG
 629        /* Open output file _after_ we read all input ones */
 630        if (option_mask32 & FLAG_o)
 631                xmove_fd(xopen(str_o, O_WRONLY|O_CREAT|O_TRUNC), STDOUT_FILENO);
 632#endif
 633        {
 634                int ch = (option_mask32 & FLAG_z) ? '\0' : '\n';
 635                for (i = 0; i < linecount; i++)
 636                        printf("%s%c", lines[i], ch);
 637        }
 638
 639        fflush_stdout_and_exit(EXIT_SUCCESS);
 640}
 641