Here is the usage text from my latest --sort=version patch (attached), now that paperwork is in order.
Cheers - Bruce $ ./sort --help Usage: ./sort [OPTION]... [FILE]... Write sorted concatenation of all FILE(s) to standard output. Mandatory arguments to long options are mandatory for short options too. Ordering options: -b, --ignore-leading-blanks ignore leading blanks -d, --dictionary-order consider only blanks and alphanumeric characters -f, --ignore-case fold lower case to upper case characters -g, --general-numeric-sort compare according to general numerical value -i, --ignore-nonprinting consider only printable characters -M, --month-sort compare (unknown) < `JAN' < ... < `DEC' -n, --numeric-sort compare according to string numerical value -R, --random-sort sort by random hash of keys -V, --version-sort sort by numeric version (see strverscmp(3C)) --random-source=FILE get random bytes from FILE (default /dev/urandom) --sort=WORD sort according to WORD: general-numeric -g, month -M, numeric -n, random -R, version -V -r, --reverse reverse the result of comparisons Other options: -c, --check, --check=diagnose-first check for sorted input; do not sort -C, --check=quiet, --check=silent like -c, but do not report first bad line --compress-program=PROG compress temporaries with PROG; decompress them with PROG -d -k, --key=POS1[,POS2] start a key at POS1, end it at POS2 (origin 1) -m, --merge merge already sorted files; do not sort -o, --output=FILE write result to FILE instead of standard output -s, --stable stabilize sort by disabling last-resort comparison -S, --buffer-size=SIZE use SIZE for main memory buffer -t, --field-separator=SEP use SEP instead of non-blank to blank transition -T, --temporary-directory=DIR use DIR for temporaries, not $TMPDIR or /tmp; multiple options specify multiple directories -u, --unique with -c, check for strict ordering; without -c, output only the first of an equal run -z, --zero-terminated end lines with 0 byte, not newline --help display this help and exit --version output version information and exit POS is F[.C][OPTS], where F is the field number and C the character position in the field; both are origin 1. If neither -t nor -b is in effect, characters in a field are counted from the beginning of the preceding whitespace. OPTS is one or more single-letter ordering options, which override global ordering options for that key. If no key is given, use the entire line as the key. SIZE may be followed by the following multiplicative suffixes: % 1% of memory, b 1, K 1024 (default), and so on for M, G, T, P, E, Z, Y. With no FILE, or when FILE is -, read standard input. *** WARNING *** The locale specified by the environment affects sort order. Set LC_ALL=C to get the traditional sort order that uses native byte values. Report bugs to <bug-coreutils@gnu.org>.
diff --git a/src/sort.c b/src/sort.c index 8b2eec5..adb4589 100644 --- a/src/sort.c +++ b/src/sort.c @@ -172,6 +172,7 @@ struct keyfield Handle numbers in exponential notation. */ bool month; /* Flag for comparison by month name. */ bool reverse; /* Reverse the sense of comparison. */ + bool version; /* sort by version number */ struct keyfield *next; /* Next keyfield to try. */ }; @@ -328,10 +329,11 @@ Ordering options:\n\ -M, --month-sort compare (unknown) < `JAN' < ... < `DEC'\n\ -n, --numeric-sort compare according to string numerical value\n\ -R, --random-sort sort by random hash of keys\n\ + -V, --version-sort sort by numeric version (see strverscmp(3C))\n\ --random-source=FILE get random bytes from FILE (default /dev/urandom)\n\ --sort=WORD sort according to WORD:\n\ general-numeric -g, month -M, numeric -n,\n\ - random -R\n\ + random -R, version -V\n\ -r, --reverse reverse the result of comparisons\n\ \n\ "), stdout); @@ -398,7 +400,7 @@ enum SORT_OPTION }; -static char const short_options[] = "-bcCdfgik:mMno:rRsS:t:T:uy:z"; +static char const short_options[] = "-bcCdfgik:mMno:rRsS:t:T:uVy:z"; static struct option const long_options[] = { @@ -423,31 +425,50 @@ static struct option const long_options[] = {"field-separator", required_argument, NULL, 't'}, {"temporary-directory", required_argument, NULL, 'T'}, {"unique", no_argument, NULL, 'u'}, + {"compare-version", no_argument, NULL, 'V'}, {"zero-terminated", no_argument, NULL, 'z'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0}, }; +#define CHECK_TABLE \ + _ct_("quiet", 'C') \ + _ct_("silent", 'C') \ + _ct_("diagnose-first", 'c') + static char const *const check_args[] = { - "quiet", "silent", "diagnose-first", NULL +#define _ct_(_s, _c) _s, + CHECK_TABLE NULL +#undef _ct_ }; static char const check_types[] = { - 'C', 'C', 'c' +#define _ct_(_s, _c) _c, + CHECK_TABLE +#undef _ct_ }; -ARGMATCH_VERIFY (check_args, check_types); + +#define SORT_TABLE \ + _st_("general-numeric", 'g') \ + _st_("month", 'M') \ + _st_("numeric", 'n') \ + _st_("random", 'R') \ + _st_("version", 'V') static char const *const sort_args[] = { - "general-numeric", "month", "numeric", "random", NULL +#define _st_(_s, _c) #_s, + SORT_TABLE NULL +#undef _st_ }; static char const sort_types[] = { - 'g', 'M', 'n', 'R' +#define _st_(_s, _c) _c, + SORT_TABLE +#undef _st_ }; -ARGMATCH_VERIFY (sort_args, sort_types); /* The set of signals that are caught. */ static sigset_t caught_signals; @@ -1717,6 +1738,32 @@ compare_random (char *restrict texta, size_t lena, return diff; } +/* Compare the keys TEXTA (of length LENA) and TEXTB (of length LENB) + using strverscmp. */ + +static int +compare_version (char *restrict texta, size_t lena, + char *restrict textb, size_t lenb) +{ + int diff; + + /* + * It is necessary to save the character after the end of the field. + * "strverscmp" works with NUL terminated strings. Our blocks of + * text are not necessarily terminated with a NUL byte. + */ + char sv_a = texta[lena]; + char sv_b = textb[lenb]; + + texta[lena] = textb[lenb] = '\0'; + diff = strverscmp (texta, textb); + + texta[lena] = sv_a; + textb[lenb] = sv_b; + + return diff; +} + /* Compare two lines A and B trying every key in sequence until there are no more keys or a difference is found. */ @@ -1756,6 +1803,10 @@ keycompare (const struct line *a, const struct line *b) (texta, textb)); *lima = savea, *limb = saveb; } + + else if (key->version) + diff = compare_version (texta, lena, textb, lenb); + else if (key->month) diff = getmonth (texta, lena) - getmonth (textb, lenb); /* Sorting like this may become slow, so in a simple locale the user @@ -2602,10 +2653,11 @@ check_ordering_compatibility (void) for (key = keylist; key; key = key->next) if ((1 < (key->random + key->numeric + key->general_numeric + key->month - + !!key->ignore)) + + key->version + !!key->ignore)) || (key->random && key->translate)) { - char opts[7]; + /* The following is too big, but guaranteed to be "big enough". */ + char opts[sizeof short_options]; char *p = opts; if (key->ignore == nondictionary) *p++ = 'd'; @@ -2619,6 +2671,8 @@ check_ordering_compatibility (void) *p++ = 'M'; if (key->numeric) *p++ = 'n'; + if (key->version) + *p++ = 'V'; if (key->random) *p++ = 'R'; *p = '\0'; @@ -2720,6 +2774,9 @@ set_ordering (const char *s, struct keyfield *key, enum blanktype blanktype) case 'r': key->reverse = true; break; + case 'V': + key->version = true; + break; default: return (char *) s; } @@ -2845,7 +2902,7 @@ main (int argc, char **argv) gkey.sword = gkey.eword = SIZE_MAX; gkey.ignore = NULL; gkey.translate = NULL; - gkey.numeric = gkey.general_numeric = gkey.random = false; + gkey.numeric = gkey.general_numeric = gkey.random = gkey.version = false; gkey.month = gkey.reverse = false; gkey.skipsblanks = gkey.skipeblanks = false; @@ -3102,11 +3159,16 @@ main (int argc, char **argv) /* Inheritance of global options to individual keys. */ for (key = keylist; key; key = key->next) { - if (! (key->ignore || key->translate - || (key->skipsblanks | key->reverse - | key->skipeblanks | key->month | key->numeric - | key->general_numeric - | key->random))) + if (! (key->ignore + || key->translate + || (key->skipsblanks + | key->reverse + | key->skipeblanks + | key->month + | key->numeric + | key->version + | key->general_numeric + | key->random))) { key->ignore = gkey.ignore; key->translate = gkey.translate; @@ -3117,15 +3179,21 @@ main (int argc, char **argv) key->general_numeric = gkey.general_numeric; key->random = gkey.random; key->reverse = gkey.reverse; + key->version = gkey.version; } need_random |= key->random; } - if (!keylist && (gkey.ignore || gkey.translate - || (gkey.skipsblanks | gkey.skipeblanks | gkey.month - | gkey.numeric | gkey.general_numeric - | gkey.random))) + if (!keylist && (gkey.ignore + || gkey.translate + || (gkey.skipsblanks + | gkey.skipeblanks + | gkey.month + | gkey.numeric + | gkey.general_numeric + | gkey.random + | gkey.version))) { insertkey (&gkey); need_random |= gkey.random;
_______________________________________________ Bug-coreutils mailing list Bug-coreutils@gnu.org http://lists.gnu.org/mailman/listinfo/bug-coreutils