1

I have a directory containing a lot of symbolic links and some huge subdirectories. I want to get a detailed listing, sorted by time, of just the files in the parent directory, but with targets replacing links if the files are symbolic links.

The command readlink -f * | sort | uniq deals with the symbolic links. So you would think that

ls -alt `readlink -f * | sort | uniq`

would simply sort by time the output of the readlink command. But instead it returns the contents, not just of the parent directory, but also all of the subdirectories as well. How do I get the desired output, i.e., a listing, sorted by time, of only the files in the parent directory, with the targets replacing the links? Thanks for any suggestions.

3
  • 1
    Doesn't the -L flag do what you want? That'll deref symlinks. Jul 28, 2020 at 1:38
  • 1
    if anything readlink returns is a directory, then your ls command will list the content of that directory. If you want to list the directory itself, you'll want to include the -d option to ls. Jul 28, 2020 at 1:41
  • Thanks to both, -L flag does indeed work, To get rid of duplicates wrote a script using readlink as above, then culled the directories out of the list that was returned, and applied ls -alt to the culled list.
    – Leo Simon
    Jul 29, 2020 at 3:08

2 Answers 2

1

With zsh and GNU ls:

files=(*(NDoN:P))
(($#files)) && ls -ltd -- ${(u)files}

Or with an anonymous function for brevity:

(){ (($#)) && ls -ltd -- ${(u)@}; } *(NDoN:P)

where N enables nullglob, D dotglob to include hidden files, oN skips the sorting as ls will sort by mtime and :P does the equivalent of GNU readlink -f. The (u) parameter expansion flag removes duplicates in the array.

Or if there are so many files in the current directory that the above fails with a too many arguments errors, you can get zsh to do the sorting and pass the list to ls -U (to disable its own sorting, requires GNU ls or compatible) via zargs which like xargs would split the list into smaller chunks if needed.

autoload zargs
files=(*(ND-om:P))
zargs -r --eof= -- ${(u)files} '' ls -lUd --

Where -om sorts by modification time after symlink resolution.

(beware zsh's :P currently chokes on symlinks that have infinite loops in their resolution).

With bash and GNU tools, you could do something approaching with:

shopt -s nullglob dotglob
files=(*)
((${#files[@]})) &&
  printf '%s\0' "${files[@]}" | # prints as NUL-delimited records
  xargs -r0 readlink -zf -- |   # get absolute canonical path
  LC_ALL=C sort -zu |           # removes duplicates
  xargs -r0 stat --printf '%.9Y/%n\0' -- | # get mtime with max precision
  sort -rnz |                   # sort numerically in reverse
  cut -zd/ -f2- |               # remove timestamp added earlier
  xargs -r0 ls -lUd --          # pass to as many ls invocations as needed

With small enough lists of files, it can be simplified to:

shopt -s nullglob dotglob
files=(*)
((${#files[@]})) &&
  readlink -zf -- "${files[@]}" | # get absolute canonical path
  LC_ALL=C sort -zu |             # removes duplicates
  xargs -r0 ls -ltd --            # pass to ls

See how we're always passing the list of files between tools as NUL-delimited records. NUL is the only character (and byte value) that cannot occur in a file path, so the only safe method.

In readlink -f * | sort, you're passing the list of files from readlink to sort as newline-delimited records, so would break with filenames that contain newlines. Since you forgot the -- before *, it would also break on filenames starting with -.

It's a lot worse in ls -alt `cmd`, where this time the output of cmd is split on space, tab and newline (assuming the default value of $IFS) and filename generation is performed on the resulting words, so it would choke on even more characters. Without -d, ls would also list the contents of the directories it is passed as arguments instead of just the directories themselves.

4
  • Thanks for the great answer @Stephane_Chazelas. I should use zsh but don't, so I can't use your suggestion, but I'm sure somebody will
    – Leo Simon
    Jul 29, 2020 at 3:10
  • @LeoSimon If you read the answer til the end, you'll notice I also gave a solution for the bash shell Jul 29, 2020 at 7:13
  • Ugh, I"m sorry that was really dumb of me. That's indeed a much more comprehensive solution to the problem than I'd implemented
    – Leo Simon
    Jul 30, 2020 at 15:11
  • 3 years later, is this still the best apporach?
    – GrabbenD
    Nov 28, 2023 at 21:19
-1

Consider this approach:

ls | xargs realpath
ls $PWD | xargs realpath

See here why it works without -1: https://unix.stackexchange.com/a/410557/537347

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .