7

I have a bunch of directories at the same level and would like to sort them according to the last modified date of the content (recursive) inside them. However, in nautilus, it looks like the directories' "last modified date" are only updated if new files are created inside.

Is there anyway to show the recursive "last modified date" of these directories?


Edit: I only needed to know the date to the nearest minute. So I've adopted Stéphane Chazelas's solution with minor modifications to reduce the clutter:

find . -mindepth 2 -type f -printf '%TF %TH:%TM/%P\0' |
LC_ALL=C sort -zt/ -k2,2 -k1,1r |
LC_ALL=C sort -t/ -zmsuk2,2 |
LC_ALL=C sort -z |
cut -zd/ -f1,2 | tr '\0/' '\n\t'
2
  • ITYM %TM. %Tm would be the month. See also %TR (same as %TH:%TM). Also note that if you don't use %Tz, you get an ambiguous output for some times of the year in timezones that do dst. You can also combine the two trs to tr '\0/' '\n\t' Oct 22, 2020 at 20:21
  • by the way, although this does not answer your question, literally the only well-known program I know that can do this is ncdu. Run with -e, then type m then M to see the results. (Sadly, it's export mode does not place these "derived" timestamps in the file; I just checked, hoping it would! So you can only see those timestamps within the ncdu program)
    – user339730
    Oct 26, 2020 at 4:56

2 Answers 2

9

The last modification time of a directory (think like phone directory, not folder) is the time it was last modified, like when an entry was removed, added or edited in that directory.

To find out the newest regular file recursively in it, you would need to read the contents of that directory and every directory within and for each file, check the file's modification time. That's a costly thing to do, I wouldn't expect any file manager application to do it.

You could however script it.

With the GNU implementations of find and sort (and any Bourne-like shell), you could do:

TZ=UTC0 find . -mindepth 2 -type f -printf '%TFZ%TT/%P\0' |
  LC_ALL=C sort -zt/ -k2,2 -k1,1r |
  LC_ALL=C sort -t/ -zmsuk2,2 |
  LC_ALL=C sort -z |
  tr '\0' '\n'

Which would give something like:

2020-02-08Z19:17:22.3588966190/Scripts/.distfiles
2020-02-09Z09:25:37.5336986350/StartupFiles/zshrc
2020-07-26Z20:33:17.7263164070/Misc/vcs_info-examples
2020-07-26Z20:33:17.7463157170/Util/ztst-syntax.vim
2020-08-22Z18:06:17.9773156630/Functions/VCS_Info
2020-08-30Z11:11:00.5701005930/autom4te.cache/requests
2020-08-30Z11:11:31.5245491550/Config/defs.mk
2020-08-30Z11:11:31.6085449480/Etc/Makefile
2020-08-30Z11:12:10.9305773600/INSTALL.d/share/zsh/5.8.0.2-dev/help
2020-10-22Z05:17:15.3808945480/Completion/Base/Utility
2020-10-22Z05:17:15.3928938520/Doc/Zsh/zle.yo
2020-10-22Z05:17:15.3968936190/Src/zsh.h
2020-10-22Z05:17:15.3968936190/Test/D02glob.ztst
2020-10-22Z05:17:15.4168924590/.git/logs/refs/heads/master

That is, giving the newest regular file in each directory with its timestamp. Directories without regular files in them are not shown.

To only see the list of directories, insert a cut -zd/ -f2 | before the tr command.

For a prettier output like in the zsh approach, you could replace the tr command with:

LC_ALL=C gawk -v RS='\0' -F / '{
  dir = $2; mtime = $1
  sub("[^/]*/[^/]*/", "")
  printf "%-20s %s (%s)\n", dir, mtime, $0}'

While we're at using gawk, we could also tell find to print the timestamp as a fractional Unix epoch time and gawk reformat it in local time:

find . -mindepth 2 -type f -printf '%T@/%P\0' |
  LC_ALL=C sort -zt/ -k2,2 -k1,1rn |
  LC_ALL=C sort -t/ -zmsuk2,2 |
  LC_ALL=C sort -zn |
  LC_ALL=C gawk -v RS='\0' -F / '{
    dir = $2; split($1, mtime, ".")
    sub("[^/]*/", "")
    printf "%-20s %s (%s)\n", dir, strftime("%FT%T." mtime[2] "%z", mtime[1]), $0}'

Which would give an output like:

cross-build          2019-12-02T13:48:33.0505299150+0000 (cross-build/x86-beos.cache)
m4                   2019-12-02T13:48:33.4615093990+0000 (m4/xsize.m4)
autom4te.cache       2019-12-02T13:50:48.8897482560+0000 (autom4te.cache/requests)
CWRU                 2020-08-09T17:17:21.4712835520+0100 (CWRU/CWRU.chlog)
include              2020-08-09T17:17:21.5872807740+0100 (include/posixtime.h)
tests                2020-08-09T17:17:21.8392747400+0100 (tests/type.right)
.git                 2020-08-09T17:17:21.8472745490+0100 (.git/index)
doc                  2020-08-09T17:35:35.1638603570+0100 (doc/Makefile)
po                   2020-08-09T17:35:35.3758514290+0100 (po/Makefile)
support              2020-08-09T17:35:36.7037954930+0100 (support/man2html)
lib                  2020-08-09T17:35:42.3755564970+0100 (lib/readline/libhistory.a)
builtins             2020-08-09T17:35:42.5035511020+0100 (builtins/libbuiltins.a)
examples             2020-08-09T17:35:47.1513551370+0100 (examples/loadables/cut)
INSTALL.d            2020-08-09T17:35:47.3993446790+0100 (INSTALL.d/lib/bash/cut)
4
  • Thanks! I wish the output could be neater but this does the job. Need to change UTC to display the local time though
    – matohak
    Oct 22, 2020 at 12:57
  • @matohak, you could remove the TZ=UTC0 to get local time and change the timestamp format to %TFT%TT%Tz (to get something like 2020-10-22T08:07:38.4965782590-0500), but then there's a small risk of the ordering be wrong if you're in a timezone with daylight saving switches and there are files modified in the hour around the switch. Oct 22, 2020 at 13:07
  • 1
    @matohak, see edit to improve formatting and display times in local time while still guaranteeing a correct order. Oct 22, 2020 at 13:37
  • @Stéphane Chazelas thanks for this, could you please tell how to change the sorting from newest to oldest
    – Tito
    Jul 13, 2021 at 15:59
5

In zsh, you could define a by_age_of_newest_file sort order function like:

zmodload zsh/stat
typeset -A newest_mtime newest_file
by_age_of_newest_file() {
  local dir=${1-$REPLY}
  local newest=($dir/**/*(ND.om[1]))

  if (($#newest)); then
    stat -gLA REPLY -F %FZ%T.%N +mtime -- $newest
    newest_mtime[$dir]=$REPLY newest_file[$dir]=$newest
  else
    REPLY= newest_mtime[$dir]= newest_file[$dir]=
  fi
}

Which you could use as:

print -rC1 -- *(ND/o+by_age_of_newest_file)

to print the list from oldest to newest (dirs with no file listed first). Replace o+ with O+ to reverse the order.

Or to print the directories alongside the newest file and its timestamp:

data=()
for dir (*(ND/o+by_age_of_newest_file))
  data+=("$dir" "$newest_mtime[$dir]" "($newest_file[$dir])")
print -raC3 -- "$data[@]"

Which gives something like:

Scripts                        2020-02-08Z19:17:22.358896619  (Scripts/.distfiles)
StartupFiles                   2020-02-09Z09:25:37.533698635  (StartupFiles/zshrc)
Misc                           2020-07-26Z20:33:17.726316407  (Misc/vcs_info-examples)
Util                           2020-07-26Z20:33:17.746315717  (Util/ztst-syntax.vim)
Functions                      2020-08-22Z18:06:17.977315663  (Functions/VCS_Info/Backends/VCS_INFO_get_data_hg)
autom4te.cache                 2020-08-30Z11:11:00.570100593  (autom4te.cache/requests)
Config                         2020-08-30Z11:11:31.524549155  (Config/defs.mk)
Etc                            2020-08-30Z11:11:31.608544948  (Etc/Makefile)
INSTALL.d                      2020-08-30Z11:12:10.870580360  (INSTALL.d/share/zsh/5.8.0.2-dev/help/zstyle)
Completion                     2020-10-22Z05:17:15.380894548  (Completion/Base/Utility/_store_cache)
Doc                            2020-10-22Z05:17:15.392893852  (Doc/Zsh/zle.yo)
Src                            2020-10-22Z05:17:15.396893619  (Src/zsh.h)
Test                           2020-10-22Z05:17:15.396893619  (Test/D02glob.ztst)
.git                           2020-10-22Z05:17:15.416892459  (.git/logs/refs/heads/master)

(note the timestamps are in Zulu/UTC time)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .