如何在目录树中找到最旧的文件

Mar*_*nas 83 linux unix shell

我正在寻找一个 shell 单行程序来查找目录树中最旧的文件。

Mar*_*nas 84

这有效(更新以纳入 Daniel Andersson 的建议):

find -type f -printf '%T+ %p\n' | sort | head -n 1
Run Code Online (Sandbox Code Playgroud)

  • 少打字:`find -type f -printf '%T+ %p\n' | 排序 | 头-1` (8认同)

slh*_*hck 17

这个更便携一点,因为它不依赖于 GNUfind扩展-printf,所以它也适用于 BSD/OS X:

find . -type f -print0 | xargs -0 ls -ltr | head -n 1
Run Code Online (Sandbox Code Playgroud)

这里唯一的缺点是它的大小在某种程度上受到限制ARG_MAX(这对于大多数较新的内核来说应该是无关紧要的)。因此,如果getconf ARG_MAX返回的字符数超过了(在我的系统上为 262,144),它不会为您提供正确的结果。它也不符合 POSIX,因为-print0并且xargs -0不是。

此处概述了此问题的更多解决方案:如何在目录中找到最新(最新、最早、最旧)的文件?– 格雷格的维基

  • 如果文件名太多以至于 `xargs` 需要多次调用 `ls`,这就会中断。在这种情况下,这些多次调用的排序输出最终会在它们应该合并时连接起来。 (4认同)
  • 我认为这比发布一个假设文件名从不包含空格的脚本更糟糕。很多时候,这些会起作用,因为文件名没有空格。当他们失败时,你会得到一个错误。但这在实际情况下不太可能奏效,而且失败不会被发现。在任何足够大的目录树上,您不能只使用 `ls` 它并观察最旧的文件,您的解决方案可能_将_超出命令行长度限制,导致多次调用 `ls`。你会得到错误的答案,但你永远不会知道。 (2认同)

Den*_*nis 12

以下命令保证可以处理任何类型的奇怪文件名:

find -type f -printf "%T+ %p\0" | sort -z | grep -zom 1 ".*" | cat

find -type f -printf "%T@ %T+ %p\0" | \
    sort -nz | grep -zom 1 ".*" | sed 's/[^ ]* //'

stat -c "%y %n" "$(find -type f -printf "%T@ %p\0" | \
    sort -nz | grep -zom 1 ".*" | sed 's/[^ ]* //')"
Run Code Online (Sandbox Code Playgroud)

使用空字节 ( \0) 而不是换行符 ( \n) 可确保在文件名之一包含换行符的情况下, find 的输出仍然可以理解。

-z开关使 sort 和 grep 都仅将空字节解释为行尾字符。由于 head 没有这样的开关,我们grep -m 1改为使用(仅出现一次)。

命令按执行时间排序(在我的机器上测量)。

  • 第一个命令将是最慢的,因为它必须首先将每个文件的 mtime 转换为人类可读的格式,然后对这些字符串进行排序。管道到 cat 避免了输出着色。

  • 第二个命令稍微快一点。虽然它仍然执行日期转换,但数字排序 ( sort -n) 自 Unix 纪元以来经过的秒数要快一些。sed 删除自 Unix 纪元以来的秒数。

  • 最后一个命令根本不进行转换,应该比前两个命令快得多。find 命令本身不会显示最旧文件的 mtime,因此需要 stat。

相关手册页:findgrepsedsortstat


DrB*_*eco 6

尽管这里接受的答案和其他人可以完成这项工作,但如果您有一个非常大的树,所有这些人都会对整堆文件进行排序。

如果我们可以列出它们并跟踪最旧的,而根本不需要排序,那就更好了。

这就是为什么我想出了这个替代解决方案:

ls -lRU $PWD/* | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { gsub(/-/,"",$6); if (substr($1,0,1)=="/") { pat=substr($1,0,length($0)-1)"/"; }; if( $6 != "") {if ( $6 < oldd ) { oldd=$6; oldf=pat$8; }; print $6, pat$8; count++;}} END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
Run Code Online (Sandbox Code Playgroud)

我希望它可能有任何帮助,即使问题有点老。


编辑 1:此更改允许解析带有空格的文件和目录。它的速度足以在根目录中发出它/并找到有史以来最旧的文件。

ls -lRU --time-style=long-iso "$PWD"/* | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { gsub(/-/,"",$6); if (substr($0,0,1)=="/") { pat=substr($0,0,length($0)-1)"/"; $6="" }; if( $6 ~ /^[0-9]+$/) {if ( $6 < oldd ) { oldd=$6; oldf=$8; for(i=9; i<=NF; i++) oldf=oldf $i; oldf=pat oldf; }; count++;}} END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
Run Code Online (Sandbox Code Playgroud)

命令解释:

  • ls -lRU --time-style=long-iso "$PWD"/* 列出所有文件(*),长格式(l),递归(R),不排序(U)要快,管道到awk
  • awk 然后通过将计数器归零(此问题的可选)并将最旧的日期设置为今天开始,格式为 YearMonthDay。
  • 首先是主循环
    • 抓取第6个字段,日期,格式为Year-Month-Day,改成YearMonthDay(如果你的ls没有这样输出,可能需要微调一下)。
    • 使用递归,所有目录都会有标题行,格式为 /directory/here:。将此行抓取到 pat 变量中。(将最后一个“:”替换为“/”)。并将 $6 设置为空以避免将标题行用作有效的文件行。
    • 如果字段 $6 有一个有效数字,则它是一个日期。将其与旧日期 oldd 进行比较。
    • 年纪大了吗?然后保存旧日期 oldd 和旧文件名 oldf 的新值。顺便说一句,oldf 不仅是第 8 场,而是从第 8 场到最后。这就是为什么要从第 8 个连接到 NF(结束)的循环。
    • 以一计预付款
    • 通过打印结果结束

运行它:

~$time ls -lRU "$PWD"/* | awk 等

最旧日期:19691231

文件:/home/.../.../backupold/.../EXAMPLES/how-to-program.txt

比较总数:111438

真正的 0m1.135s

用户 0m0.872s

系统 0m0.760s


编辑2:相同的概念,用更好的解决方案find存取时间(使用%T与第一printf修改时间%C状态变化,而不是)。

find . -wholename "*" -type f -printf "%AY%Am%Ad %h/%f\n" | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { if ($1 < oldd) { oldd=$1; oldf=$2; for(i=3; i<=NF; i++) oldf=oldf " " $i; }; count++; } END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
Run Code Online (Sandbox Code Playgroud)

编辑 3:下面的命令使用修改时间,并在发现越来越旧的文件时打印增量进度,这在您有一些不正确的时间戳(如 1970-01-01)时很有用:

find . -wholename "*" -type f -printf "%TY%Tm%Td %h/%f\n" | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { if ($1 < oldd) { oldd=$1; oldf=$2; for(i=3; i<=NF; i++) oldf=oldf " " $i; print oldd " " oldf; }; count++; } END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
Run Code Online (Sandbox Code Playgroud)