假设我有以下时间戳,如目录树:
root
|__ parent1
| |__ 2021
| | |__ 01
| | | |__ 22
| | | | |__ 12H
| | | | | |__ file1
| | | | | |__ file2
| | | | |__ 13H
| | | | | |__ file1
| | | | | |__ file2
| | | |__ 23
| | | | |__ 12H
| | | | | |__ file1
| | | | | |__ file2
| | | | |__ 13H
| | | | | |__ file1
| | | | | |__ file2
|__ parent2
| |__ etc
我想要的是递归地浏览此文件夹结构,以便对于每个文件夹parent1
、parent2
等,将显示找到的最新时间戳以及所包含文件的计数。例如,类似:
PARENT | LAST_TIMESTAMP | COUNT |
--------------------------------------------
parent1 | 2021-01-23T13:00:00 | 2 |
parent2 | 2022-01-01T00:00:00 | 5 | (dummy example)
... ... ...
我看过其他答案,但所有答案都只考虑所有文件夹中文件的修改日期,而在这种情况下,它只与文件夹的名称有关。
答案1
使用find
和perl
一行:
它使用制表符来分隔时间戳和文件名,并使用 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允许模式匹配零个名称,同时启用隐藏名称的匹配。o
m
N
D
nullglob
dotglob
bash
N
D
下面的脚本使用了这个:
#!/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 |
在bash
shell 中,您可以在某个目标目录下的目录结构中找到最近修改的常规文件,$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。
与上面相同的脚本,但为bash
shell 编写:
#!/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 特定的)。