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
This commit is contained in:
Aymeric Wibo 2022-10-23 09:46:27 +02:00 committed by Piotr Pawel Stefaniak
parent 595746df6f
commit e2662256cd
6 changed files with 67 additions and 12 deletions

View file

@ -64,6 +64,20 @@ revnamecmp(const FTSENT *a, const FTSENT *b)
return (strcoll(b->fts_name, a->fts_name)); 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 int
modcmp(const FTSENT *a, const FTSENT *b) modcmp(const FTSENT *a, const FTSENT *b)
{ {

View file

@ -42,6 +42,8 @@ int modcmp(const FTSENT *, const FTSENT *);
int revmodcmp(const FTSENT *, const FTSENT *); int revmodcmp(const FTSENT *, const FTSENT *);
int namecmp(const FTSENT *, const FTSENT *); int namecmp(const FTSENT *, const FTSENT *);
int revnamecmp(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 statcmp(const FTSENT *, const FTSENT *);
int revstatcmp(const FTSENT *, const FTSENT *); int revstatcmp(const FTSENT *, const FTSENT *);
int sizecmp(const FTSENT *, const FTSENT *); int sizecmp(const FTSENT *, const FTSENT *);

View file

@ -32,7 +32,7 @@
.\" @(#)ls.1 8.7 (Berkeley) 7/29/94 .\" @(#)ls.1 8.7 (Berkeley) 7/29/94
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd August 31, 2020 .Dd October 31, 2022
.Dt LS 1 .Dt LS 1
.Os .Os
.Sh NAME .Sh NAME
@ -40,7 +40,7 @@
.Nd list directory contents .Nd list directory contents
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl ABCFGHILPRSTUWZabcdfghiklmnopqrstuwxy1\&, .Op Fl ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1\&,
.Op Fl -color Ns = Ns Ar when .Op Fl -color Ns = Ns Ar when
.Op Fl D Ar format .Op Fl D Ar format
.Op Ar .Op Ar
@ -399,6 +399,15 @@ of the file for sorting
.Pq Fl t .Pq Fl t
or printing or printing
.Pq Fl l . .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 .It Fl w
Force raw printing of non-printable characters. Force raw printing of non-printable characters.
This is the default This is the default
@ -883,8 +892,10 @@ specification.
.Xr sort 1 , .Xr sort 1 ,
.Xr xterm 1 Pq Pa ports/x11/xterm , .Xr xterm 1 Pq Pa ports/x11/xterm ,
.Xr localeconv 3 , .Xr localeconv 3 ,
.Xr strcoll 3 ,
.Xr strftime 3 , .Xr strftime 3 ,
.Xr strmode 3 , .Xr strmode 3 ,
.Xr strverscmp 3 ,
.Xr termcap 5 , .Xr termcap 5 ,
.Xr maclabel 7 , .Xr maclabel 7 ,
.Xr sticky 7 , .Xr sticky 7 ,
@ -902,7 +913,7 @@ utility conforms to
and and
.St -p1003.1-2008 . .St -p1003.1-2008 .
The options 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 and
.Fl , .Fl ,
are non-standard extensions. are non-standard extensions.
@ -918,6 +929,11 @@ An
.Nm .Nm
command appeared in command appeared in
.At v1 . .At v1 .
.Pp
The
.Fl v
option was added in
.Fx 14.0 .
.Sh BUGS .Sh BUGS
To maintain backward compatibility, the relationships between the many To maintain backward compatibility, the relationships between the many
options are quite complex. options are quite complex.

View file

@ -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 */ int f_octal_escape; /* like f_octal but use C escapes if possible */
static int f_recursive; /* ls subdirectories also */ static int f_recursive; /* ls subdirectories also */
static int f_reversesort; /* reverse whatever sort is used */ 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_samesort; /* sort time and name in same direction */
int f_sectime; /* print full time information */ int f_sectime; /* print full time information */
static int f_singlecol; /* use single column output */ static int f_singlecol; /* use single column output */
@ -275,7 +276,7 @@ main(int argc, char *argv[])
colorflag = COLORFLAG_AUTO; colorflag = COLORFLAG_AUTO;
#endif #endif
while ((ch = getopt_long(argc, argv, while ((ch = getopt_long(argc, argv,
"+1ABCD:FGHILPRSTUWXZabcdfghiklmnopqrstuwxy,", long_opts, "+1ABCD:FGHILPRSTUWXZabcdfghiklmnopqrstuvwxy,", long_opts,
NULL)) != -1) { NULL)) != -1) {
switch (ch) { switch (ch) {
/* /*
@ -439,6 +440,9 @@ main(int argc, char *argv[])
case 's': case 's':
f_size = 1; f_size = 1;
break; break;
case 'v':
f_verssort = 1;
break;
case 'w': case 'w':
f_nonprint = 0; f_nonprint = 0;
f_octal = 0; f_octal = 0;
@ -566,10 +570,12 @@ main(int argc, char *argv[])
} }
/* Select a sort function. */ /* Select a sort function. */
if (f_reversesort) { if (f_reversesort) {
if (!f_timesort && !f_sizesort) if (f_sizesort)
sortfcn = revnamecmp;
else if (f_sizesort)
sortfcn = revsizecmp; sortfcn = revsizecmp;
else if (f_verssort)
sortfcn = revverscmp;
else if (!f_timesort)
sortfcn = revnamecmp;
else if (f_accesstime) else if (f_accesstime)
sortfcn = revacccmp; sortfcn = revacccmp;
else if (f_birthtime) else if (f_birthtime)
@ -579,10 +585,12 @@ main(int argc, char *argv[])
else /* Use modification time. */ else /* Use modification time. */
sortfcn = revmodcmp; sortfcn = revmodcmp;
} else { } else {
if (!f_timesort && !f_sizesort) if (f_sizesort)
sortfcn = namecmp;
else if (f_sizesort)
sortfcn = sizecmp; sortfcn = sizecmp;
else if (f_verssort)
sortfcn = verscmp;
else if (!f_timesort)
sortfcn = namecmp;
else if (f_accesstime) else if (f_accesstime)
sortfcn = acccmp; sortfcn = acccmp;
else if (f_birthtime) else if (f_birthtime)

View file

@ -846,6 +846,20 @@ u_flag_body()
atf_check -e empty -o match:'a\.file.*b\.file' -s exit:0 ls -Cu 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 atf_test_case x_flag
x_flag_head() x_flag_head()
{ {
@ -960,6 +974,7 @@ atf_init_test_cases()
atf_add_test_case s_flag atf_add_test_case s_flag
atf_add_test_case t_flag atf_add_test_case t_flag
atf_add_test_case u_flag atf_add_test_case u_flag
atf_add_test_case v_flag
atf_add_test_case x_flag atf_add_test_case x_flag
atf_add_test_case y_flag atf_add_test_case y_flag
atf_add_test_case 1_flag atf_add_test_case 1_flag

View file

@ -227,9 +227,9 @@ usage(void)
{ {
(void)fprintf(stderr, (void)fprintf(stderr,
#ifdef COLORLS #ifdef COLORLS
"usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuwxy1,] [--color=when] [-D format]" "usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [--color=when] [-D format]"
#else #else
"usage: ls [-ABCFHILPRSTUWZabcdfghiklmnopqrstuwxy1,] [-D format]" "usage: ls [-ABCFHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [-D format]"
#endif #endif
" [file ...]\n"); " [file ...]\n");
exit(1); exit(1);