"ls" counterpart to "find" operator "-printf"?

The find command has a handy -printf operator that prints
user-specified metadata for each found file/folder. Is there such an
option for the ls command?

As an alternative, I can feed the list of filenames of interest to
find instead of ls, but it seems like using a sledgehammer on a
fly. I already have the files of interest, and I’m not really
"finding" anything.

Additionally, feeding the paths to find would be tricky because I
can’t just append the desired file paths to the end of a find
command. The find command requires that the paths come before the
operators (or "predicates"). For this reason, I can’t easily leverage
"xargs".

Thanks to steeldriver for his answer. I think that his use of stat would be a solution if I was starting from scratch. Unfortunately, I have to compare the output with similar information that has already been generated from other systems, specifically using find‘s printf.

Here is some code idioms using find that I found to work:

# Option 1
Some Command 
| xargs -n 1 -I{} find {} -printf '%pt%TY-%Tm-%Td %TH:%TMt%sn'

# Option 2
Some Command | tr 'n' '' 
| xargs -0 -I{} find {} -printf '%pt%TY-%Tm-%Td %TH:%TMt%sn'

Based on muru’s comment, here is a code idiom that I haven’t
gotten working because because my Gnu find from
Cygwin is older than version 4.9 and doesn’t recognize the predicate -files0-from:

Some Command | tr 'n' '' 
| find -files0-from - -printf '%pt%TY-%Tm-%Td %TH:%TMt%sn'
Asked By: user2153235

||

Not with the GNU implementation of ls¹ as far as I know, however it’s possible with some implementations of stat.

For example stat from GNU Coreutils, stat --printf '%nt%yt%sn' will give you the fields and delimiters that you want although it doesn’t (afaik) provide fine-grained control of the datetime format².

If zsh is an option however, you could use the zsh/stat Module, perhaps defining your own shell function with the exact output you want:

zmodload -F zsh/stat b:zstat

mystat () {
  local f s ret=0
  for f do
    zstat -n -LF '%Y-%m-%d %H:%M' -H s -- $f &&
      printf '%st%st%sn' $s[name] $s[mtime] $s[size] ||
      ret=$?
  done
  return $ret
}

Alternatively you may be able to do what you want with a simple perl script using its lstat function, for example

mystat() {
  perl -MPOSIX -E '
    foreach $f (@ARGV) { 
      if (@s = lstat($f)) {
        say join "t", $f, strftime("%Y-%m-%d %H:%M", localtime($s[9])), $s[7];
      } else {
        warn "$f: $!n";
        $ret = 1;
      }
    }
    exit $ret;
  ' -- "$@"
}

In any case, that -printf is specific to the GNU implementation of find (though existed decades before a GNU stat command was added). With GNU find 4.9 or newer, you can pass an arbitrary list of paths reliably with the -files0-from predicate:

mystat() {
  [ "$#" -eq 0 ] ||
    printf '%s' "$@" |
    find -files0-from - -prune -printf '%pt%TY-%Tm-%Td %TH:%TMt%sn'
}

In any case, note that TAB and NL are as valid characters as any in a file path, so that output cannot be parsed reliably (the timestamp format which can be simplified to %F %R is also ambiguous as it’s missing the timezone information).

All those do a lstat() on the files, so that for those of type symlink they report the information about the symlink itself rather than the file they eventually resolve to. To do a stat() instead (and report the information after symlink resolution), add the -L option to GNU stat or GNU find, remove it from zsh stat, remove the -P option to ast-open’s ls, and replace lstat with stat in the perl script.


¹ the ast-open implementation does though with ls -PdZ '%(name)st%(mtime:time=%Y-%m-%d %H:%M)st%(size)s' -- "$@" (same syntax as for the standard pax command) but AFAIK, that one is no longer being maintained.

² GNU stat also fails on the file called - as it interprets it as meaning to perform a fstat() on stdin; zsh’s stat (which predates GNU’s by a few years) doesn’t have the issue and has a -f FD to perform a fstat() on arbitrary file descriptors.

Answered By: steeldriver
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.