【问题标题】:How to recursively find and list the latest modified files in a directory with subdirectories and times如何递归查找并列出具有子目录和时间的目录中的最新修改文件
【发布时间】:2011-07-30 19:03:09
【问题描述】:
  • 操作系统:Linux

  • 文件系统类型:ext3

  • 首选解决方案:Bash(脚本/单行)、Ruby 或 Python

我有几个目录,其中有几个子目录和文件。我需要列出所有这些目录,这些目录的构建方式使得每个一级目录都列在其中最新创建/修改的文件的日期和时间旁边。

为了澄清,如果我触摸文件或修改其内容向下几个子目录级别,则该时间戳应显示在第一级目录名称旁边。假设我有一个结构如下的目录:

./alfa/beta/gamma/example.txt

我修改了文件example.txt的内容,我需要将那个时间以人类可读的形式显示在一级目录alfa旁边,而不是纪元。我已经使用 find、xargssort 等尝试了一些事情,但是我无法解决当我创建/修改文件几个级别时“alfa”的文件系统时间戳不会改变的问题下来。

【问题讨论】:

标签: linux recursion time filesystems


【解决方案1】:

试试这个:

#!/bin/bash
find $1 -type f -exec stat --format '%Y :%y %n' "{}" \; | sort -nr | cut -d: -f2- | head

使用它应该开始递归扫描的目录的路径执行它(它支持带空格的文件名)。

如果有很多文件,它可能需要一段时间才能返回任何内容。如果我们改用xargs,可以提高性能:

#!/bin/bash
find $1 -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head

这有点快。

【讨论】:

  • 您的“快速方法”也应该能够使用 print0 来支持文件名中的空格甚至换行。这是我使用的:find $1 -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head 这对我来说仍然很快。
  • 我正在查看的某些目录不允许我stat 他们,所以我做了以下更改(对“快速”),所以我不必看到错误我的最终输出。 find ${1} -type f | xargs stat --format '%Y :%y %n' 2>/dev/null | sort -nr | cut -d: -f2-
  • 在 Mac OS X 上它不是 GNU 的 stat 所以命令失败。您必须 brew install coreutils 并使用 gstat 而不是 stat
  • 您不需要运行stat,因为find PATH -type f -printf "%T@ %p\n"| sort -nr 可以完成这项工作。这样也快一点。
  • 在 Mac OS X 上,无需安装 gstat 或其他任何东西,您可以这样做:find PATH -type f -exec stat -f "%m %N" "{}" \; | sort -nr | head
【解决方案2】:

要查找文件状态上次在 N 分钟前更改的所有文件:

find -cmin -N

例如:

find -cmin -5

【讨论】:

  • 它比其他更复杂的解决方案更快
  • 真的很好,你也可以使用 'find -ctime -50' 例如最近 50 天的变化。
  • 要排除杂乱,请使用sudo find -cmin -1 2>&1 |grep -v /proc/
  • 好主意。考虑先在目录上执行stat .,以了解您应该查看的修改日期
  • 我必须添加文件夹,查找查找的位置,例如find . -cmin -10 (MacBookPro macOS 10.12.6)。添加这个只是为了准确起见,我首先认为上面的命令可能是 find 的卷范围版本。
【解决方案3】:

GNU find(参见man find)有一个-printf 参数,用于在Epoch mtime 和相对路径名中显示文件。

redhat> find . -type f -printf '%T@ %P\n' | sort -n | awk '{print $2}'

【讨论】:

  • 谢谢!这是唯一一个足够快的答案,可以在合理的时间内搜索我非常广泛的目录结构。我通过tail 传递输出以防止在输出中打印数千行。
  • 另一条评论:awk '{print $2}' 部分似乎会在文件名带有空格时引起问题。这是一个使用sed 代替的解决方案,它还打印除了路径之外的时间:find . -type f -printf '%T@ %Tc %P\n' | sort -n | tail | sed -r 's/^.{22}//'
  • 我觉得应该是 sort -rn
  • -printf 变体比每次调用“stat”进程要快得多——它减少了我的备份工作时间。感谢您让我意识到这一点。我避免了 awk/sed 的事情,因为我只关心树中的最后一次更新 - 所以 X=$(find /path -type f -printf '%T %p\n' | grep -v something-I- don-tcare-about | sort -nr | head -n 1) 和 echo ${X#*" "} 对我来说效果很好(给我的东西到第一个空格)
  • 如果文件名跨多行,所有将不起作用。使用touch "lala<Enter>b" 创建这样的文件。我认为 unix 实用程序设计在文件名方面存在很大缺陷。
【解决方案4】:

我将Daniel Böhmer's awesome answer 缩短为这一行:

stat --printf="%y %n\n" $(ls -tr $(find * -type f))

如果文件名中有空格,可以这样修改:

OFS="$IFS";IFS=$'\n';stat --printf="%y %n\n" $(ls -tr $(find . -type f));IFS="$OFS";

【讨论】:

  • 这个怎么样:IFS=$'\n'; stat --printf="%y %n\n" $(ls -tr $(find . -type f))
  • 如果您有大量文件,这将不起作用。使用 xargs 的答案解决了这个限制。
  • @carlverbiest 确实大量文件会破坏 slashdottir 的解决方案。即使是基于 xargs 的解决方案也会很慢。 user2570243's solution 最适合大型文件系统。
  • IFS=$'\n' 在处理文件名时无论如何都不安全:换行符是 UNIX 上文件名中的有效字符。只有 NUL 字符保证不会出现在路径中。
【解决方案5】:

试试这个:

#!/bin/bash
stat --format %y $(ls -t $(find alfa/ -type f) | head -n 1)

它使用find收集目录中的所有文件,ls按修改日期排序,head选择第一个文件,最后stat以漂亮的格式显示时间。

目前,名称中包含空格或其他特殊字符的文件是不安全的。如果还不能满足您的需求,请写下推荐。

【讨论】:

  • halo:我喜欢你的回答,它运行良好并打印出正确的文件。但是,我没有帮助我,因为在我的情况下有太多的子级别。所以我得到 ls 的“参数列表太长”......在这种情况下 xargs 也无济于事。我会试试别的。
  • 在这种情况下,它有点复杂,需要一些真正的程序。我会破解一些 Perl。
  • 我使用 PHP 解决了这个问题。遍历文件系统树并存储最近修改文件的时间的递归函数。
【解决方案6】:

此命令适用于 Mac OS X:

find "$1" -type f -print0 | xargs -0 gstat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head

在 Linux 上,正如原发帖人所要求的,使用 stat 而不是 gstat

这个答案当然是user37078 的出色解决方案,从评论提升为完整答案。我混合了 CharlesB 的见解,在 Mac OS X 上使用 gstat。顺便说一下,我从 MacPorts 而不是 Homebrew 获得了 coreutils

这就是我如何将它打包成一个简单的命令~/bin/ls-recent.sh 以供重复使用:

#!/bin/bash
# ls-recent: list files in a directory tree, most recently modified first
#
# Usage: ls-recent path [-10 | more]
#
# Where "path" is a path to target directory, "-10" is any argument to pass
# to "head" to limit the number of entries, and "more" is a special argument
# in place of "-10" which calls the pager "more" instead of "head".
if [ "more" = "$2" ]; then
   H=more; N=''
else
   H=head; N=$2
fi

find "$1" -type f -print0 |xargs -0 gstat --format '%Y :%y %n' \
    |sort -nr |cut -d: -f2- |$H $N

【讨论】:

  • 在 OS X 优胜美地;我收到错误:查找:ftsopen:没有这样的文件或目录
  • 有趣。你输入了什么命令(带参数)?该目录中文件的名称是什么?如果您创建了自己的 ~/bin/ls-recent.sh 版本,您是否仔细检查了脚本是否存在差异?
  • 对于那些不想在 Mac OS X 上安装任何东西的人:find . -exec stat -f '%m%t%Sm %N' {} + | sort -n | cut -f2-
  • @Jake:我认为您的评论应该提升为完整答案。这就是 Mac 用户正在寻找的东西。谢谢!
【解决方案7】:

忽略隐藏文件 - 带有漂亮且快速的时间戳

以下是如何在带有子目录的目录中查找和列出最新修改的文​​件。隐藏文件被故意忽略。而文件名中的空格处理得很好——不是你应该使用它们!时间格式可以自定义。

$ find . -type f -not -path '*/\.*' -printf '%TY.%Tm.%Td %THh%TM %Ta %p\n' |sort -nr |head -n 10

2017.01.25 18h23 Wed ./indenting/Shifting blocks visually.mht
2016.12.11 12h33 Sun ./tabs/Converting tabs to spaces.mht
2016.12.02 01h46 Fri ./advocacy/2016.Vim or Emacs - Which text editor do you prefer?.mht
2016.11.09 17h05 Wed ./Word count - Vim Tips Wiki.mht

More find galore可以通过以下链接找到。

【讨论】:

    【解决方案8】:

    这就是我正在使用的(非常有效):

    function find_last () { find "${1:-.}" -type f -printf '%TY-%Tm-%Td %TH:%TM %P\n' 2>/dev/null | sort | tail -n "${2:-10}"; }
    

    优点:

    • 它只产生 3 个进程

    用法:

    find_last [dir [number]]
    

    地点:

    • dir - 要搜索的目录[当前目录]
    • number - 要显示的最新文件数 [10]

    find_last /etc 4 的输出如下所示:

    2019-07-09 12:12 cups/printers.conf
    2019-07-09 14:20 salt/minion.d/_schedule.conf
    2019-07-09 14:31 network/interfaces
    2019-07-09 14:41 environment
    

    【讨论】:

      【解决方案9】:

      这篇文章中的 Perl 和 Python 解决方案都帮助我在 Mac OS X 上解决了这个问题:

      How to list files sorted by modification date recursively (no stat command available!)

      引自帖子:

      Perl:

      find . -type f -print |
      perl -l -ne '
          $_{$_} = -M;  # store file age (mtime - now)
          END {
              $,="\n";
              print sort {$_{$b} <=> $_{$a}} keys %_;  # print by decreasing age
          }'
      

      Python:

      find . -type f -print |
      python -c 'import os, sys; times = {}
      for f in sys.stdin.readlines(): f = f[0:-1]; times[f] = os.stat(f).st_mtime
      for f in sorted(times.iterkeys(), key=lambda f:times[f]): print f'
      

      【讨论】:

        【解决方案10】:

        这是一个适用于可能包含空格、换行符和全局字符的文件名的版本:

        find . -type f -printf "%T@ %p\0" | sort -zk1nr
        
        • find ... -printf 打印文件修改时间 (Epoch value),后跟一个空格和 \0 终止的文件名。
        • sort -zk1nr 读取 NUL 终止的数据并按数字倒序排列

        由于问题被标记为 Linux,我假设 GNU Core Utilities 可用。

        您可以通过以下方式对上述内容进行管道传输:

        xargs -0 printf "%s\n"
        

        打印修改时间和按修改时间排序的文件名(最近的在前),由换行符终止。

        【讨论】:

          【解决方案11】:

          我显示的是最新的访问时间,您可以轻松地修改它以进行最新的修改时间。

          有两种方法可以做到这一点:


          1. 如果您想避免全局排序,如果您有数千万个文件,这可能会很昂贵,那么您可以这样做(将自己定位在您希望开始搜索的目录的根目录中):

             Linux> touch -d @0 /tmp/a;
             Linux> find . -type f -exec tcsh -f -c test `stat --printf="%X" {}` -gt  `stat --printf="%X" /tmp/a`  ; -exec tcsh -f -c touch -a -r {} /tmp/a ; -print
            

            上述方法打印访问时间逐渐更新的文件名,并且它打印的最后一个文件是具有最新访问时间的文件。您显然可以使用“tail -1”获取最新的访问时间。

          2. 您可以递归打印子目录中所有文件的名称和访问时间,然后根据访问时间和尾部最大条目进行排序:

             Linux> \find . -type f -exec stat --printf="%X  %n\n" {} \; | \sort -n | tail -1
            

          你有它...

          【讨论】:

            【解决方案12】:

            我的 .profile 中有这个别名,我经常使用:

            $ alias | grep xlogs
            xlogs='sudo find . \( -name "*.log" -o -name "*.trc" \) -mtime -1 | sudo xargs ls -ltr --color | less -R'
            

            所以它会做你正在寻找的事情(除了它不会遍历更改日期/时间多个级别) - 查找最新文件(在这种情况下为 *.log 和 *.trc 文件);它也只查找最后一天修改的文件,然后按时间排序并通过less管道输出:

            sudo find . \( -name "*.log" -o -name "*.trc" \) -mtime -1 | sudo xargs ls -ltr --color | less -R
            

            PS.:请注意,我在某些服务器上没有 root,但始终有 sudo,因此您可能不需要该部分。

            【讨论】:

            • 这个“正是你要找的”怎么样? OP 很好地解释了他想要什么,而这完全忽略了它。
            • 感谢您指出这一点。你是对的 - 这种方法不会进入多个级别来更改日期/时间,它只显示其中目录文件的日期/时间。编辑了我的答案。
            【解决方案13】:

            这实际上应该执行 OP 指定的操作:

            Bash 中的单行代码:

            $ for first_level in `find . -maxdepth 1 -type d`; do find $first_level -printf "%TY-%Tm-%Td %TH:%TM:%TS $first_level\n" | sort -n | tail -n1 ; done
            

            给出如下输出:

            2020-09-12 10:50:43.9881728000 .
            2020-08-23 14:47:55.3828912000 ./.cache
            2018-10-18 10:48:57.5483235000 ./.config
            2019-09-20 16:46:38.0803415000 ./.emacs.d
            2020-08-23 14:48:19.6171696000 ./.local
            2020-08-23 14:24:17.9773605000 ./.nano
            

            这会列出每个第一级目录以及这些文件夹中最新文件的人类可读时间戳,即使它位于子文件夹中,如中所要求的

            “我需要列出所有这些目录,这些目录在 一种使每个一级目录都列在日期旁边的方法 以及其中最新创建/修改的文件的时间。”

            【讨论】:

              【解决方案14】:

              @anubhava's answer 很棒,但不幸的是,它不适用于 BSD 工具 - 即它不适用于 find that comes installed by default on macOS,因为 BSD find 没有 -printf 运算符。

              所以这是一个适用于 macOS + BSD 的变体(在我的 Catalina Mac 上测试过),它结合了 BSD findxargsstat

              $ find . -type f -print0 \
                    | xargs -0 -n1 -I{} stat -f '%Fm %N' "{}" \
                    | sort -rn 
              

              当我在这里时,这是我喜欢使用的 BSD 命令序列,它将时间戳放入 ISO-8601 format

              $ find . -type f -print0 \
                  | xargs -0 -n1 -I{} \
                     stat  -f '%Sm %N' -t '%Y-%m-%d %H:%M:%S' "{}" \
                  | sort -rn
              

              (请注意,与@anubhava 不同,我的两个答案都将文件名从find 传递给xargs 作为单个参数而不是\0 终止列表,这会改变最后通过管道输出的内容)

              这是 GNU 版本(即@anubhava 的答案,但采用 iso-8601 格式):

              $ gfind . -type f -printf "%T+ %p\0" | sort -zk1nr
              

              相关q:find lacks the option -printf, now what?

              【讨论】:

              • 我需要获取最近修改的文件名,因此我添加了| head -1 | cut -d' ' -f2 以仅获取最新条目的文件名,但您的第一个命令序列让我走上了正确的道路。跨度>
              【解决方案15】:

              Quick Bash 功能:

              # findLatestModifiedFiles(directory, [max=10, [format="%Td %Tb %TY, %TT"]])
              function findLatestModifiedFiles() {
                  local d="${1:-.}"
                  local m="${2:-10}"
                  local f="${3:-%Td %Tb %TY, %TT}"
              
                  find "$d" -type f -printf "%T@ :$f %p\n" | sort -nr | cut -d: -f2- | head -n"$m"
              }
              

              在目录中查找最新修改的文​​件:

              findLatestModifiedFiles "/home/jason/" 1
              

              您还可以指定自己的日期/时间格式作为第三个参数。

              【讨论】:

                【解决方案16】:

                下面会返回一个时间戳字符串和具有最新时间戳的文件名:

                find $Directory -type f -printf "%TY-%Tm-%Td-%TH-%TM-%TS %p\n" | sed -r 's/([[:digit:]]{2})\.([[:digit:]]{2,})/\1-\2/' |     sort --field-separator='-' -nrk1 -nrk2 -nrk3 -nrk4 -nrk5 -nrk6 -nrk7 | head -n 1
                

                产生以下形式的输出: &lt;yy-mm-dd-hh-mm-ss.nanosec&gt; &lt;filename&gt;

                【讨论】:

                  【解决方案17】:

                  对于那些遇到过的人

                  stat: unrecognized option: format
                  

                  当执行来自Heppo's answer (find $1 -type f -exec stat --format '%Y :%y %n' "{}" \; | sort -nr | cut -d: -f2- | head) 的行时

                  请尝试使用-c 键替换--format,最终调用将是:

                  find $1 -type f -exec stat -c '%Y :%y %n' "{}" \; | sort -nr | cut -d: -f2- | head
                  

                  这在一些 Docker 容器中对我有用,其中 stat 无法使用 --format 选项。

                  【讨论】:

                  • stat 命令未正确标准化,因此它在不同平台上接受不同的选项。 --format(又名-c)是Linux 使用的(或任何带有GNU Coreutils 的东西);例如MacOS 你需要-f 并且支持的格式标志是不同的。我猜 -c 但不是 --format 可能是 Alpine(更新:已确认)或 Busybox。
                  • 在 Linux(或通常是 GNU 用户空间)系统上,find -printf 可以完成大部分 stat 可以完成的工作,而无需调用外部工具。
                  【解决方案18】:

                  对于普通的ls 输出,使用它。没有参数列表,所以不能太长:

                  find . | while read FILE;do ls -d -l "$FILE";done
                  

                  并用cut 修饰了日期、时间和名称:

                  find . | while read FILE;do ls -d -l "$FILE";done | cut --complement -d ' ' -f 1-5
                  

                  编辑:刚刚注意到当前最佳答案按修改日期排序。这与这里的第二个示例一样简单,因为修改日期是每行的第一个 - 在末尾添加一个排序:

                  find . | while read FILE;do ls -d -l "$FILE";done | cut --complement -d ' ' -f 1-5 | sort
                  

                  【讨论】:

                    【解决方案19】:

                    这也可以通过 Bash 中的递归函数来完成。

                    让 F 是一个函数,它显示文件的时间,该文件必须是按字典顺序排序的 yyyy-mm-dd 等,(取决于操作系统?)

                    F(){ stat --format %y "$1";}                # Linux
                    F(){ ls -E "$1"|awk '{print$6" "$7}';}      # SunOS: maybe this could be done easier
                    

                    R,遍历目录的递归函数:

                    R(){ local f;for f in "$1"/*;do [ -d "$f" ]&&R $f||F "$f";done;}
                    

                    最后

                    for f in *;do [ -d "$f" ]&&echo `R "$f"|sort|tail -1`" $f";done
                    

                    【讨论】:

                      【解决方案20】:

                      在 mac 上我用这个

                      find . -type f -exec stat -f "%m %N" "{}" \; | sort -nr | perl -n -e '@a = split / /;print `ls -l $a[1]`' | vim -
                      

                      如果你想过滤一些文件,你可以使用 grep 和正则表达式,即

                      find . -type f -exec stat -f "%m %N" "{}" \; | sort -nr | grep -v -E \.class$ | perl -n -e '@a = split / /;print `ls -l $a[1]`' | vim -
                      

                      【讨论】:

                        【解决方案21】:

                        你可以试试find的printf ACTION

                        %Ak 文件的最后访问时间 k 指定的格式, 这是@' or a directive for the C strftime' 功能。列出了 k 的可能值 以下; 其中一些可能并非全部可用 系统,由于 系统之间“strftime”的差异。

                        详情请查看@anubhava's answer

                        【讨论】:

                        • Re"command":没有更正式的名字吗? 动作谓词?
                        【解决方案22】:

                        Bash 有一个单行脚本解决方案,用于在多个目录中递归查找最新修改的文​​件。请在您的目标目录中找到以下命令。

                         ls -ltr $(find /path/dir1 /path/dir2 -type f)
                        

                        对于今天,grep 今天的日期或时间,如下面的命令所述

                         (ls -ltr $(find /path/dir1 /path/dir2 -type f)) |grep -i 'Oct 24'
                        

                        【讨论】:

                          猜你喜欢
                          • 2017-07-27
                          • 2011-06-01
                          • 2015-09-06
                          • 2016-09-26
                          • 1970-01-01
                          • 2021-01-05
                          • 1970-01-01
                          • 2010-10-04
                          • 2010-10-24
                          相关资源
                          最近更新 更多