git: e2662256cdbc - main - ls(1): add a -v flag to sort naturally

From: Piotr Pawel Stefaniak <pstef_at_FreeBSD.org>
Date: Sun, 30 Oct 2022 23:12:56 UTC
The branch main has been updated by pstef:

URL: https://cgit.FreeBSD.org/src/commit/?id=e2662256cdbc6ca9be49a66837f9725081cfa9d3

commit e2662256cdbc6ca9be49a66837f9725081cfa9d3
Author:     Aymeric Wibo <obiwac@gmail.com>
AuthorDate: 2022-10-23 07:46:27 +0000
Commit:     Piotr Pawel Stefaniak <pstef@FreeBSD.org>
CommitDate: 2022-10-30 23:00:42 +0000

    ls(1): add a -v flag to sort naturally
    
    Add a -v flag for ls which sorts entries following a natural ordering
    using strverscmp(3) (e.g. "bloem1 bloem9 bloem10" as opposed to
    "bloem1 bloem10 bloem9").
    
    Update the manual page and add a test case.
    
    Reviewed by:    pauamma, bcr
    Tested by:      pstef
    Differential Revision:  https://reviews.freebsd.org/D36407
---
 bin/ls/cmp.c             | 14 ++++++++++++++
 bin/ls/extern.h          |  2 ++
 bin/ls/ls.1              | 22 +++++++++++++++++++---
 bin/ls/ls.c              | 22 +++++++++++++++-------
 bin/ls/tests/ls_tests.sh | 15 +++++++++++++++
 bin/ls/util.c            |  4 ++--
 6 files changed, 67 insertions(+), 12 deletions(-)

diff --git a/bin/ls/cmp.c b/bin/ls/cmp.c
index 13ba3ff47a99..4f2c8bcfc846 100644
--- a/bin/ls/cmp.c
+++ b/bin/ls/cmp.c
@@ -64,6 +64,20 @@ revnamecmp(const FTSENT *a, const FTSENT *b)
 	return (strcoll(b->fts_name, a->fts_name));
 }
 
+int
+verscmp(const FTSENT *a, const FTSENT *b)
+{
+
+	return (strverscmp(a->fts_name, b->fts_name));
+}
+
+int
+revverscmp(const FTSENT *a, const FTSENT *b)
+{
+
+	return (strverscmp(b->fts_name, a->fts_name));
+}
+
 int
 modcmp(const FTSENT *a, const FTSENT *b)
 {
diff --git a/bin/ls/extern.h b/bin/ls/extern.h
index 247c2c4a1d5b..0e577a885474 100644
--- a/bin/ls/extern.h
+++ b/bin/ls/extern.h
@@ -42,6 +42,8 @@ int	 modcmp(const FTSENT *, const FTSENT *);
 int	 revmodcmp(const FTSENT *, const FTSENT *);
 int	 namecmp(const FTSENT *, const FTSENT *);
 int	 revnamecmp(const FTSENT *, const FTSENT *);
+int	 verscmp(const FTSENT *, const FTSENT *);
+int	 revverscmp(const FTSENT *, const FTSENT *);
 int	 statcmp(const FTSENT *, const FTSENT *);
 int	 revstatcmp(const FTSENT *, const FTSENT *);
 int	 sizecmp(const FTSENT *, const FTSENT *);
diff --git a/bin/ls/ls.1 b/bin/ls/ls.1
index 450985fcfb4f..fb1b4f6ba606 100644
--- a/bin/ls/ls.1
+++ b/bin/ls/ls.1
@@ -32,7 +32,7 @@
 .\"     @(#)ls.1	8.7 (Berkeley) 7/29/94
 .\" $FreeBSD$
 .\"
-.Dd August 31, 2020
+.Dd October 31, 2022
 .Dt LS 1
 .Os
 .Sh NAME
@@ -40,7 +40,7 @@
 .Nd list directory contents
 .Sh SYNOPSIS
 .Nm
-.Op Fl ABCFGHILPRSTUWZabcdfghiklmnopqrstuwxy1\&,
+.Op Fl ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1\&,
 .Op Fl -color Ns = Ns Ar when
 .Op Fl D Ar format
 .Op Ar
@@ -399,6 +399,15 @@ of the file for sorting
 .Pq Fl t
 or printing
 .Pq Fl l .
+.It Fl v
+Sort following a natural ordering, using
+.Xr strverscmp 3
+instead of
+.Xr strcoll 3
+as the comparison function.
+E.g., files lexicographically ordered
+"bloem1", "bloem10", and "bloem9" would instead be ordered
+"bloem1", "bloem9", and "bloem10", as one would perhaps expect.
 .It Fl w
 Force raw printing of non-printable characters.
 This is the default
@@ -883,8 +892,10 @@ specification.
 .Xr sort 1 ,
 .Xr xterm 1 Pq Pa ports/x11/xterm ,
 .Xr localeconv 3 ,
+.Xr strcoll 3 ,
 .Xr strftime 3 ,
 .Xr strmode 3 ,
+.Xr strverscmp 3 ,
 .Xr termcap 5 ,
 .Xr maclabel 7 ,
 .Xr sticky 7 ,
@@ -902,7 +913,7 @@ utility conforms to
 and
 .St -p1003.1-2008 .
 The options
-.Fl B , D , G , I , T , U , W , Z , b , h , w , y
+.Fl B , D , G , I , T , U , W , Z , b , h , v , w , y
 and
 .Fl ,
 are non-standard extensions.
@@ -918,6 +929,11 @@ An
 .Nm
 command appeared in
 .At v1 .
+.Pp
+The
+.Fl v
+option was added in
+.Fx 14.0 .
 .Sh BUGS
 To maintain backward compatibility, the relationships between the many
 options are quite complex.
diff --git a/bin/ls/ls.c b/bin/ls/ls.c
index 8a30dd326b4e..bd7bd283c317 100644
--- a/bin/ls/ls.c
+++ b/bin/ls/ls.c
@@ -136,6 +136,7 @@ static int f_numericonly;	/* don't convert uid/gid to name */
        int f_octal_escape;	/* like f_octal but use C escapes if possible */
 static int f_recursive;		/* ls subdirectories also */
 static int f_reversesort;	/* reverse whatever sort is used */
+static int f_verssort;		/* sort names using strverscmp(3) rather than strcoll(3) */
        int f_samesort;		/* sort time and name in same direction */
        int f_sectime;		/* print full time information */
 static int f_singlecol;		/* use single column output */
@@ -275,7 +276,7 @@ main(int argc, char *argv[])
 		colorflag = COLORFLAG_AUTO;
 #endif
 	while ((ch = getopt_long(argc, argv,
-	    "+1ABCD:FGHILPRSTUWXZabcdfghiklmnopqrstuwxy,", long_opts,
+	    "+1ABCD:FGHILPRSTUWXZabcdfghiklmnopqrstuvwxy,", long_opts,
 	    NULL)) != -1) {
 		switch (ch) {
 		/*
@@ -439,6 +440,9 @@ main(int argc, char *argv[])
 		case 's':
 			f_size = 1;
 			break;
+		case 'v':
+			f_verssort = 1;
+			break;
 		case 'w':
 			f_nonprint = 0;
 			f_octal = 0;
@@ -566,10 +570,12 @@ main(int argc, char *argv[])
 	}
 	/* Select a sort function. */
 	if (f_reversesort) {
-		if (!f_timesort && !f_sizesort)
-			sortfcn = revnamecmp;
-		else if (f_sizesort)
+		if (f_sizesort)
 			sortfcn = revsizecmp;
+		else if (f_verssort)
+			sortfcn = revverscmp;
+		else if (!f_timesort)
+			sortfcn = revnamecmp;
 		else if (f_accesstime)
 			sortfcn = revacccmp;
 		else if (f_birthtime)
@@ -579,10 +585,12 @@ main(int argc, char *argv[])
 		else		/* Use modification time. */
 			sortfcn = revmodcmp;
 	} else {
-		if (!f_timesort && !f_sizesort)
-			sortfcn = namecmp;
-		else if (f_sizesort)
+		if (f_sizesort)
 			sortfcn = sizecmp;
+		else if (f_verssort)
+			sortfcn = verscmp;
+		else if (!f_timesort)
+			sortfcn = namecmp;
 		else if (f_accesstime)
 			sortfcn = acccmp;
 		else if (f_birthtime)
diff --git a/bin/ls/tests/ls_tests.sh b/bin/ls/tests/ls_tests.sh
index 14f7895e699c..117156b5cb0e 100755
--- a/bin/ls/tests/ls_tests.sh
+++ b/bin/ls/tests/ls_tests.sh
@@ -846,6 +846,20 @@ u_flag_body()
 	atf_check -e empty -o match:'a\.file.*b\.file' -s exit:0 ls -Cu
 }
 
+atf_test_case v_flag
+v_flag_head()
+{
+	atf_set "descr" "Verify that the output from ls -v sorts based on strverscmp(3)"
+}
+
+v_flag_body()
+{
+	create_test_dir
+
+	atf_check -e empty -o empty -s exit:0 touch 000 00 01 010 09 0 1 9 10
+	atf_check -e empty -o match:"000.00.01.010.09.0.1.9.10" -s exit:0 sh -c 'ls -Cv'
+}
+
 atf_test_case x_flag
 x_flag_head()
 {
@@ -960,6 +974,7 @@ atf_init_test_cases()
 	atf_add_test_case s_flag
 	atf_add_test_case t_flag
 	atf_add_test_case u_flag
+	atf_add_test_case v_flag
 	atf_add_test_case x_flag
 	atf_add_test_case y_flag
 	atf_add_test_case 1_flag
diff --git a/bin/ls/util.c b/bin/ls/util.c
index 44012de58bba..0b1cba848239 100644
--- a/bin/ls/util.c
+++ b/bin/ls/util.c
@@ -227,9 +227,9 @@ usage(void)
 {
 	(void)fprintf(stderr,
 #ifdef COLORLS
-	"usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuwxy1,] [--color=when] [-D format]"
+	"usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [--color=when] [-D format]"
 #else
-	"usage: ls [-ABCFHILPRSTUWZabcdfghiklmnopqrstuwxy1,] [-D format]"
+	"usage: ls [-ABCFHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [-D format]"
 #endif
 		      " [file ...]\n");
 	exit(1);