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