2

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 '%p\t%TY-%Tm-%Td %TH:%TM\t%s\n'

# Option 2
Some Command | tr '\n' '\0' \
| xargs -0 -I{} find {} -printf '%p\t%TY-%Tm-%Td %TH:%TM\t%s\n'

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' '\0' \
| find -files0-from - -printf '%p\t%TY-%Tm-%Td %TH:%TM\t%s\n'
8
  • 1
    What metadata are you interested in, specifically? It may be available from the stat command (or the stat builtin of some shells, or perl) Nov 10, 2023 at 23:09
  • 1
    With find, I have used -printf '%p\t%TY-%Tm-%Td %TH:%TM\t%s\n', i.e., tab-separated path, date/time, and size. I get many rows of this, which I can send to a file. It reads nicely into a data frame in many languages. I have used stat, but I want the nice 1-row/file output to feed the data frame ingestors. Nov 10, 2023 at 23:30
  • 2
    So the GNU coreutils implementation of stat will get you close, it just lacks (afaik) the fine-grained control over the datetime format e.g. stat --printf '%n\t%y\t%s\n' Nov 10, 2023 at 23:44
  • 1
    If you have GNU find, then it can read a list of ASCII NUL-separated filenames from stdin or a file using -files0-from, eliminating the need for xargs
    – muru
    Nov 11, 2023 at 0:59
  • 1
    I think you need at least 4.9 for that option. Which version of find do you have?
    – muru
    Nov 11, 2023 at 7:02

1 Answer 1

6

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 '%n\t%y\t%s\n' 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 '%s\t%s\t%s\n' $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\0' "$@" |
    find -files0-from - -prune -printf '%p\t%TY-%Tm-%Td %TH:%TM\t%s\n'
}

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)s\t%(mtime:time=%Y-%m-%d %H:%M)s\t%(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.

You must log in to answer this question.

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