The ls-lisp
solution is portable and straightforward, but unfortunately lacks some important (to me) flags and is considerably slower.
So instead, one can also modify ls
directly. Because I wanted the same feature in ls
in the terminal anyway, I wrote a patch for the ls
in GNU's coreutils. That's certainly a lot less portable, but it was a much smaller and more desirable change than extending ls-lisp
with the missing flags. (Still, ls-lisp
can be used as a fallback as well.)
This patch for coreutils (8.25) sorts symlinked directories like directories when --group-directories-first
is provided:
diff --git a/src/ls.c b/src/ls.c
index 3572060..8dd6395 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -2969,10 +2969,10 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
|| is_colored (C_STICKY_OTHER_WRITABLE)))
/* When dereferencing symlinks, the inode and type must come from
stat, but readdir provides the inode and type of lstat. */
- || ((print_inode || format_needs_type)
+ || ((print_inode || format_needs_type || directories_first)
&& (type == symbolic_link || type == unknown)
&& (dereference == DEREF_ALWAYS
- || color_symlink_as_referent || check_symlink_color))
+ || color_symlink_as_referent || check_symlink_color || directories_first))
/* Command line dereferences are already taken care of by the above
assertion that the inode number is not yet known. */
|| (print_inode && inode == NOT_AN_INODE_NUMBER)
@@ -3113,7 +3113,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
}
if (S_ISLNK (f->stat.st_mode)
- && (format == long_format || check_symlink_color))
+ && (format == long_format || check_symlink_color || directories_first))
{
struct stat linkstats;
@@ -3123,7 +3123,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
/* Avoid following symbolic links when possible, ie, when
they won't be traced and when no indicator is needed. */
if (linkname
- && (file_type <= indicator_style || check_symlink_color)
+ && (file_type <= indicator_style || check_symlink_color || directories_first)
&& stat (linkname, &linkstats) == 0)
{
f->linkok = true;
@@ -3131,7 +3131,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
/* Symbolic links to directories that are mentioned on the
command line are automatically traced if not being
listed as files. */
- if (!command_line_arg || format == long_format
+ if (!command_line_arg || format == long_format || directories_first
|| !S_ISDIR (linkstats.st_mode))
{
/* Get the linked-to file's mode for the filetype indicator
@@ -3251,6 +3251,15 @@ is_directory (const struct fileinfo *f)
return f->filetype == directory || f->filetype == arg_directory;
}
+/* Return true if F refers to a directory or a symlink pointing at a directory. */
+static bool
+is_directory_or_symlink_to_directory (const struct fileinfo *f)
+{
+ return f->filetype == directory
+ || f->filetype == arg_directory
+ || (f->filetype == symbolic_link && S_ISDIR(f->linkmode));
+}
+
/* Put the name of the file that FILENAME is a symbolic link to
into the LINKNAME field of 'f'. COMMAND_LINE_ARG indicates whether
FILENAME is a command-line argument. */
@@ -3396,8 +3405,8 @@ typedef int (*qsortFunc)(V a, V b);
#define DIRFIRST_CHECK(a, b) \
do \
{ \
- bool a_is_dir = is_directory ((struct fileinfo const *) a); \
- bool b_is_dir = is_directory ((struct fileinfo const *) b); \
+ bool a_is_dir = is_directory_or_symlink_to_directory ((struct fileinfo const *) a); \
+ bool b_is_dir = is_directory_or_symlink_to_directory ((struct fileinfo const *) b); \
if (a_is_dir && !b_is_dir) \
return -1; /* a goes before b */ \
if (!a_is_dir && b_is_dir) \
ls
versions do not support the--group-directories-first
option.