1

Using Nushell I want ls to list directories before files. Additionally, I want symbolic links to be sorted correctly: If it is a link to a directory, it should be included in the list of directories. And if it is a link to a file, it should be in the list of files.

I developed the following script:

let is_dir = {|file|
    $file.type == 'dir' or ($file.target | to text | str ends-with '/')
};

let dirs = ls --all --long | filter $is_dir | sort-by name --ignore-case;

let files = ls --all --long | filter {|file| not (do $is_dir $file)} | sort-by name --ignore-case;

$dirs ++ $files | select name

But that seems to be too much code for such an easy request.

  1. Can the code be simplified somehow?
    1.1 Is there a better way to find out whether the symlink points to a directory? Using $file.target | to text | str ends-with '/') was the only way I saw, but I'm not proud of that solution.
    1.2 Also, filter {|file| not (do $is_dir $file)} is not the best code I ever wrote. How can I do that better?
  2. Is there a better approach to achieve the same?

This question is about Nushell, not other shells. :-)

1 Answers1

1

Note that there are a few potential problems in your code above. I say "potential", because if it's working for you, perhaps your use-case just doesn't require them.

  • The target for a symlink to a directory doesn't necessarily end in /. It simply depends on whether or not the trailing slash was used when creating the symlink. ls -l / to see that the default Linux (at least on Ubuntu) symlink targets don't include trailing slashes.

  • Nushell ls (like a normal /usr/bin/ls or even eza) doesn't resolve the "final" target of a symlink-to-a-symlink (or further nested). I can't think of an "easy" built-in Nushell way (other then a recursive technique) to resolve the final target, but you always have the option of dropping in realpath on non-Windows platforms.

  • As far as I can tell, your implementation only works if run in the current directory. Attempting to use it on another path with ls <path> probably won't work, as $file.target doesn't return a fully-qualified path.

Regardless, here's a version that handles all of the above. It's fairly compact, and definitely fits the Nushell structured-data paradigm. It works by inserting a new column into the ls table with the type of the target (or actual file/dir) and sorts the results based on that.

ls -l
| insert sort-type {|f|
    match $f.type {
      # For symlinks, use the type of the target
      symlink => ( $f.name | path expand | ls -D $in | get type.0 )
      # Otherwise just use the type:
      _ => $f.type
    }
  }
| sort-by sort-type name
| reject sort-type

Note that there's a new sorting feature to allow closures in sort-by, which adds an additional implementation possibility, but I'm not sure that will be any easier than the option above.

NotTheDr01ds
  • 28,025