递归遍历目录并检索最后一个时间戳文件

递归遍历目录并检索最后一个时间戳文件

假设我有以下时间戳,如目录树:

root
  |__ parent1
  |      |__ 2021
  |      |     |__ 01
  |      |     |    |__ 22
  |      |     |    |    |__ 12H
  |      |     |    |    |    |__ file1
  |      |     |    |    |    |__ file2
  |      |     |    |    |__ 13H
  |      |     |    |    |    |__ file1
  |      |     |    |    |    |__ file2
  |      |     |    |__ 23
  |      |     |    |    |__ 12H
  |      |     |    |    |    |__ file1
  |      |     |    |    |    |__ file2
  |      |     |    |    |__ 13H
  |      |     |    |    |    |__ file1
  |      |     |    |    |    |__ file2
  |__ parent2
  |      |__ etc
                         

我想要的是递归地浏览此文件夹结构,以便对于每个文件夹parent1parent2等,将显示找到的最新时间戳以及所包含文件的计数。例如,类似:

 PARENT  |     LAST_TIMESTAMP    |  COUNT  |
--------------------------------------------
parent1  |  2021-01-23T13:00:00  |    2    |
parent2  |  2022-01-01T00:00:00  |    5    | (dummy example)
  ...             ...                ...

我看过其他答案,但所有答案都只考虑所有文件夹中文件的修改日期,而在这种情况下,它只与文件夹的名称有关。

答案1

使用findperl一行:

它使用制表符来分隔时间戳和文件名,并使用 NUL 来分隔每个记录 - 因此适用于任何文件名,包括包含换行符的文件名。

find .. -type f -printf '%T@\t%p\0' | 
    perl -MDate::Format -0ne '
      ($t,$f) = split /\t/,$_,2;
      (undef,$p) = split "/", $f;

      $T{$p} = $t if ($t > $T{$p});
      $count{$p}++;

      END {
        my $fmt = "%-20s | %-19s | %5s |\n";
        printf "$fmt", "PARENT", "LAST_TIMESTAMP", "COUNT";
        print "-" x 52, "\n";

        foreach (sort keys %T) {
          printf $fmt, $_, time2str("%Y-%m-%dT%H:%M:%S",$T{$_}), $count{$_}
        }
      }'

它产生如下输出:

PARENT               | LAST_TIMESTAMP      | COUNT | 
---------------------|---------------------|-------|
foo                  | 2021-07-16T22:54:22 |     4 | 
bar                  | 2021-06-29T12:25:06 |    13 | 
baz                  | 2021-07-14T14:31:43 |     5 | 
quux                 | 2021-07-16T19:46:21 |     7 | 

或者,如果您使用 perl 的文件::查找模块,您不需要将find的输出通过管道传输到其中:

#!/usr/bin/perl

use strict;
use Date::Format;
use File::Find;

my %T;     # hash containing newest timestamp for each top-level dir
my %count; # count of files in each top-level dir

find(\&wanted, @ARGV);

my $fmt  = "| %-20s | %-19s | %5s |\n";
my $hfmt = "|-%-20s-|-%-19s-|-%5s-|\n";

#print "-" x 54, "\n";

printf "$fmt", "PARENT", "LAST_TIMESTAMP", "COUNT";
printf $hfmt, "-" x 20, "-" x 19, "-" x 5;

foreach (sort keys %T) {
  printf $fmt, $_, time2str("%Y-%m-%dT%H:%M:%S", $T{$_}), $count{$_}
}

#print "-" x 54, "\n";

sub wanted {
  return unless -f $File::Find::name;

  # uncomment only one of the following statements:

  # get the mod time of the file itself
  my $t = (stat($File::Find::name))[9];
  # get the mod time of the directory it's in
  #my $t = (stat($File::Find::dir))[9];

  my $p = $File::Find::dir;
  $p =~ s:^\.*/::;

  $T{$p} = $t if ($t > $T{$p});
  $count{$p}++;
};

将其另存为,例如find-latest.pl,使用 make 可执行文件chmod +x find-latest.pl,并在运行时为其提供一个或多个目录作为参数:

$ ./find-latest.pl ../
| PARENT               | LAST_TIMESTAMP      | COUNT |
|----------------------|---------------------|-------|
| foo                  | 2021-07-16T22:54:22 |     4 |
| bar                  | 2021-06-29T12:25:06 |    13 |
| baz                  | 2021-07-14T14:31:43 |     5 |
| quux                 | 2021-07-16T19:46:21 |     7 |

这需要perl日期格式 模块。在 Debian 上,您可以使用apt-get install libtimedate-perl.它也应该打包用于其他发行版,否则使用cpan.

或者,您可以使用strftime()POSIX 模块中的函数,该模块是一个核心模块,包含在 perl 中。

File::Find也是核心 Perl 模块,包含在 Perl 中。

答案2

假设目录层次结构格式如图:

cd root &&\
find . -type d ! -name . -path '*/*/*/*/*/*' |
sort -rt/ |
perl -sF/ -lane '$,=" | ";
  print qw(PARENT LAST_TIMESTAMP KOUNT) if $.==1;
  my $fc = -1+ split /\n/, qx(ls -l $_);
  my $parent = $F[1];
  !$seen{$parent}++ && do{
    my($dt, $tm) = ("@F[2..4]", $F[5]);
    my $timestamp = sprintf "%sT%s%s", $dt, $tm =~ s/H$//r,  (":00" x 2); 
    print $parent, $timestamp, $fc;
  };
' -- -\"=- -|column -t|
sed -e '1!b;h;s/./-/gp;x;G'

输出:-

----------------------------------------
PARENT  |  LAST_TIMESTAMP       |  KOUNT
----------------------------------------
pA      |  2021-03-16T23:00:00  |  6

答案3

使用zsh,您可以获得目录中的常规文件列表$topdir,按通配模式中的修改时间戳排序。

$topdir/**/*(.NDom)

通配限定符(.NDom)确保常规文件 ( ) 的路径名结果列表按修改时间戳 ( ).排序 ( )。限定符中的 和 the 的作用有点像 shell 选项中的和,但对于这个单一模式, ie允许模式匹配零个名称,同时启用隐藏名称的匹配。omNDnullglobdotglobbashND

下面的脚本使用了这个:

#!/bin/zsh

zmodload -F zsh/stat b:zstat

printf '| %-20s | %-20s | %5s |\n' PARENT LAST_TIMESTAMP COUNT
printf '| %-20s | %-20s | %5s |\n' '' '' '' | tr ' ' '-'

for topdir do
        files=( $topdir/**/*(.NDom) )
        if (( ${#files} > 0 )); then
                timestamp=$( zstat -F '%Y-%m-%dT%H:%M:%S' +mtime $files[1] )
        else
                timestamp=N/A
        fi

        printf '| %-20s | %-20s | %5s |\n' $topdir $timestamp ${#files}
done

zsh脚本将在其命令行上采用一组目录路径,如下所示:

$ ./script parent*/

...哪里parent*/将匹配顶级目录名称。

它打印一个简单的标题,然后继续迭代给定的目录路径。

对于每个路径,它获取常规文件的路径名列表(包括隐藏名称),使用通配模式按最后修改的时间戳排序$topdir/**/*(.NDom)

如果此列表非空,则使用zstat(内置的可加载 shell)提取最近修改的文件的时间戳,或者N/A如果没有文件,则将其设置为字符串。

当前目录、时间戳和文件计数以表格形式打印。

运行示例:

$ ./script ~me/{Documents,Work,admin}/
| PARENT               | LAST_TIMESTAMP       | COUNT |
|----------------------|----------------------|-------|
| /home/me/Documents/  | 2021-06-18T13:27:39  |   816 |
| /home/me/Work/       | 2021-06-22T10:57:49  |  2582 |
| /home/me/admin/      | 2021-07-14T11:13:30  |   191 |

这里使用的表格格式恰好是有效的标记,并且示例中的表格将被标记为

家长 LAST_TIMESTAMP 数数
/主页/我/文档/ 2021-06-18T13:27:39 816
/家/我/工作/ 2021-06-22T10:57:49 2582
/家/我/管理员/ 2021-07-14T11:13:30 191

bashshell 中,您可以在某个目标目录下的目录结构中找到最近修改的常规文件,$topdir如下所示:

shopt -s nullglob dotglob globstar

unset newest
for name in "$topdir"/**/*; do
        if [ -f "$name" ] && [ ! -h "$name" ]; then
                if [[ "$name" -nt "$newest" ]]; then
                        newest=$name
                fi
        fi
done

这使用-nt中的测试bash来跟踪 中最近修改的文件$newest。如果当前文件是常规文件而不是符号链接,-f则 和 否定测试-h将为 true。

与上面相同的脚本,但为bashshell 编写:

#!/bin/bash

shopt -s nullglob dotglob globstar

printf '| %-20s | %-20s | %5s |\n' PARENT LAST_TIMESTAMP COUNT
printf '| %-20s | %-20s | %5s |\n' '' '' '' | tr ' ' '-'

for topdir do
        unset newest
        count=0
        for name in "$topdir"/**/*; do
                # Test whether "$name" is a regular file
                # and not a symbolic link.
                if [ -f "$name" ] && [ ! -h "$name" ]; then
                        count=$(( count + 1 ))
                        if [[ "$name" -nt "$newest" ]]; then
                                newest=$name
                        fi
                fi
        done

        if [ -n "$newest" ]; then
                printf -v timestamp '%(%Y-%m-%dT%H:%M:%S)T' "$(stat -c %Y "$newest")"
        else
                timestamp=N/A
        fi

        printf '| %-20s | %-20s | %5s |\n' "$topdir" "$timestamp" "$count"
done

在 OpenBSD 上,我会使用

timestamp=$( stat -f %Sm -t '%Y-%m-%dT%H:%M:%S' "$newest" )

代替

printf -v timestamp '%(%Y-%m-%dT%H:%M:%S)T' "$(stat -c %Y "$newest")"

在此脚本中(后者是 Linux 特定的)。

相关内容