将目录时间戳设置为最近修改的文件(递归地)

将目录时间戳设置为最近修改的文件(递归地)

如何更改目录以及该目录中所有子文件夹的时间戳以反映所包含文件的修改时间?

例如,使用以下目录结构:

[Jan 9]  root
├── [Jan 3]  file1
├── [Jan 7]  file2
├── [Jan 6]  sub1
│   ├── [Jan 2]  file3
│   └── [Jan 1]  file4
└── [Jan  4] sub2
    └── [Jan 8]  file5

这是生成该内容的单行代码:

mkdir -p root/sub1 root/sub2 && touch -d '2018-01-08' root/sub2/file5 && touch -d '2018-01-04' root/sub2/ && touch -d '2018-01-01' root/sub1/file4 && touch -d '2018-01-02' root/sub1/file3 && touch -d '2018-01-06' root/sub1/ && touch -d '2018-01-07' root/file2 && touch -d '2018-01-03' root/file1 && touch -d '2018-01-09' root/

它可以列出tree -D

我想将三个目录的时间戳更改为:

[Jan 8]  root
├── [Jan 3]  file1
├── [Jan 7]  file2
├── [Jan 2]  sub1
│   ├── [Jan 2]  file3
│   └── [Jan 1]  file4
└── [Jan 8]  sub2
    └── [Jan 8]  file5

笔记:

  • 目录上的当前时间戳将被完全忽略,新的时间戳仅根据内容设置。
  • 时间戳会冒泡到父目录的多个级别。

我这样做的原因是为了使用 rsync 复制的目录。该目录已签入 git,并且可以从签出存储库的任何位置进行 rsync。为了确保 rsync 在各个地方都是一致且幂等的,我需要确保所有内容的时间戳和权限都处于已知状态。我已经有一个脚本,可以根据文件提交到 git 的时间来设置文件的时间戳。我还有一个脚本,可将所有文件和目录的权限设置为已知状态。我唯一遇到的问题是从文件到父目录的冒泡时间戳。

我想要一行或简短的脚本,我可以从命令行运行它来根据目录内容的时间戳设置目录时间戳。

答案1

除非我忽略了一些事情(还没有对此进行广泛的测试):

find . -depth -type d -execdir \
    sh -c 'touch "$PWD/$0" -r "$PWD/$0/$( ls -t "$PWD/$0" | head -n 1 )"' \
    {} \;

它能做什么:

  • 查找 的所有子目录。
  • 获取他们的全名
  • with ls -t,以相反的日期时间顺序列出其内容并获取第一个
  • 用于touch使用其最新对象作为时间戳来触摸目录

为了正确的日期时间“冒泡”,该-depth选项确保处理子目录当前子目录(“深度优先”)。一旦处理了子目录并更新了它们的日期时间,就会处理当前目录(并且可能继承子目录之一的时间戳)。

答案2

如果您可以使用 Zsh,这似乎可以满足您的要求:

$ mkdir -p x/y; touch x/y/a x/b; sleep .1; touch x/y/a; sleep .1; touch x/b
$ find -depth -type d -execdir zsh -c 'touch "$1" -r "$1"/*(om[1])' zsh {} \;

使用 GNU find 测试输出./x/b是最新的,其时间戳被复制到./x.

$ find -printf "%TT %p\n"  | sort -n
15:16:24.0182222830 ./x/y
15:16:24.0182222830 ./x/y/a
15:16:24.1222150510 .
15:16:24.1222150510 ./x
15:16:24.1222150510 ./x/b

一般来说,如果文件名带有空格/换行符等,则通过时间戳查找最新文件有点棘手。 (看Bash常见问题解答 099.) Zsh 确实让它变得相当容易,不过,上面的技巧来自是否可以在命令行参数中引用最近修改的文件?

-depth告诉find在其父目录之前处理子目录,以便冒泡真正起作用。

这实际上并没有完全忽略目录的时间戳,因为当时间戳冒泡时必须对它们进行计数。包含文件的目录将从文件中获取其时间戳,但是空的目录将保留其原始时间戳,并且该时间戳将冒泡。我不确定是否值得解决这个问题。

答案3

在 Linux 上,这对我来说效果很好:

while IFS=$'\t' read -r -a REPLY; do
  echo touch -c -h -d@"${REPLY[0]}" "${REPLY[1]}"
  touch -c -h -d@"${REPLY[0]}" "${REPLY[1]}"
done < <(du "${1:-.}" --time-style=+%s --time | cut -f 2-)

相关内容