15

I was wondering if anyone knows how to change the timestamps of folders recursively based on the latest timestamp found of the files in that folder.

So for example:

jon@UbuntuPanther:/media/media/MP3s/Foo Fighters/(1997-05-20) The Colour and The Shape$ ls -alF
total 55220
drwxr-xr-x  2 jon jon    4096 2010-08-30 12:34 ./
drwxr-xr-x 11 jon jon    4096 2010-08-30 12:34 ../
-rw-r--r--  1 jon jon 1694044 2010-04-18 00:51 Foo Fighters - Doll.mp3
-rw-r--r--  1 jon jon 3151170 2010-04-18 00:51 Foo Fighters - Enough Space.mp3
-rw-r--r--  1 jon jon 5004289 2010-04-18 00:52 Foo Fighters - Everlong.mp3
-rw-r--r--  1 jon jon 5803125 2010-04-18 00:51 Foo Fighters - February Stars.mp3
-rw-r--r--  1 jon jon 4994903 2010-04-18 00:51 Foo Fighters - Hey, Johnny Park!.mp3
-rw-r--r--  1 jon jon 4649556 2010-04-18 00:52 Foo Fighters - Monkey Wrench.mp3
-rw-r--r--  1 jon jon 5216923 2010-04-18 00:51 Foo Fighters - My Hero.mp3
-rw-r--r--  1 jon jon 4294291 2010-04-18 00:52 Foo Fighters - My Poor Brain.mp3
-rw-r--r--  1 jon jon 6778011 2010-04-18 00:52 Foo Fighters - New Way Home.mp3
-rw-r--r--  1 jon jon 2956287 2010-04-18 00:51 Foo Fighters - See You.mp3
-rw-r--r--  1 jon jon 2730072 2010-04-18 00:51 Foo Fighters - Up in Arms.mp3
-rw-r--r--  1 jon jon 6086821 2010-04-18 00:51 Foo Fighters - Walking After You.mp3
-rw-r--r--  1 jon jon 3033660 2010-04-18 00:52 Foo Fighters - Wind Up.mp3

The folder "(1997-05-20) The Colour and The Shape" would have its timestamp set to 2010-04-18 00:52.

20

You can use touch -r to use another file's timestamp instead of the current time (or touch --reference=FILE)

Here are two solutions. In each solution, the first command changes the modification time of the directory to that of the newest file immediately under it, and the second command looks at the whole directory tree recursively. Change to the directory (cd '.../(1997-05-20) The Colour and The Shape') before running any of the commands.

In zsh (remove the D to ignore dot files):

touch -r *(Dom[1]) .
touch -r **/*(Dom[1]) .

On Linux (or more generally with GNU find):

touch -r "$(find -mindepth 1 -maxdepth 1 -printf '%T+=%p\n' |
            sort |tail -n 1 | cut -d= -f2-)" .
touch -r "$(find -mindepth 1 -printf '%T+=%p\n' |
            sort |tail -n 1 | cut -d= -f2-)" .

However note that those ones assume no newline characters in file names.

  • The commands work (I used the Linux ones) but it's not working recursively. I ran it in my root folder (/media/media/MP3s) and no such luck with the rest of the artist directories in there. Thank you for your help so far. – MonkeyWrench32 Sep 3 '10 at 1:01
  • @MonkeyWrench32: I answered your question as I understood it. Do you want to apply the command to every directory underneath /media/media/MP3s? Then in zsh: for d in /media/media/MP3s/**/*(/); do touch -r $d/*(Dom[1]) $d; done. Without zsh (but really, use zsh, it's just simpler): put the command in a script and run find /media/media/MP3s -type d -execdir /path/to/the/script \;. – Gilles 'SO- stop being evil' Sep 3 '10 at 7:10
  • Perfection! You are a zsh master! :D – MonkeyWrench32 Sep 3 '10 at 15:30
  • If you use for d in ... how can you adapt the following, so it will also work with folders, that contain spaces? (that is still missing in my solution) – rubo77 Aug 16 '13 at 11:36
  • @rubo77 I don't understand what you're talking about. All the solutions I posted work on file names containing spaces (some of them fail on file names containing newlines). Note that some of my solutions require zsh, which doesn't require double quotes around variable substitutions. – Gilles 'SO- stop being evil' Aug 16 '13 at 12:16
3

That's not "recursively", it's just changing all the timestamps in a folder. If that's what you mean, there's two steps.

stat -c '%Y' filename will output the timestamp of filename, and stat -c '%Y %n' * will output the timestamp and filename of every file in the folder, so this will find the filename of the most recently modified file in the current folder:

mostrecent="`stat -c '%Y %n' * | sort -n | tail -n1 | cut -d ' ' -f '2-'`"

On second thought, there's a way easier way to get the highest timestamp in the folder:

mostrecent="`ls -t | head -n1`"

Then you want to change all the files in the folder to have the same timestamp as that file. touch -r foo bar will change bar to have the same modified timestamp as foo, so this will change all the files in the folder to have the same modified timestamp as your most recently modified file:

touch -r "$mostrecent" *

So, the one-liner is:

touch -r "`ls -t | head -n1`" *
  • Parsing the output of ls is a bad idea, though I admit it's a tempting case. It's still risky if ls mangles file names containing non-ASCII characters. If you want one-liners, use zsh:-) – Gilles 'SO- stop being evil' Sep 2 '10 at 21:42
  • @Gilles It is, but I've yet to run into a problem doing it; I suppose because my filenames don't contain non-ASCII characters. Your answer is better though – Michael Mrozek Sep 2 '10 at 21:45
2

I put the work together and now:

This would be a script that changes all directories inside /tmp/test/ to the timestamp of the newest file inside each directory:

#!/bin/bash
if [ -z "$1" ] ; then
  echo 'ERROR: Parameter missing. specify the folder!'
  exit
fi
#MODE=tail # change to newest file
MODE=head # change to oldest file
for d in "$1"/*/; do
  echo "running on $d"
  find "$d" -type d -execdir \
    echo touch --reference="$(find "$d" -mindepth 1 -maxdepth 1 -printf '%T+=%p\n' \
                              | sort | "$MODE" -n 1 | cut -d= -f2-)" "$d" \;
    # remove echo to really run it
done

you can add some testing files in /tmp like this:

mkdir /tmp/test
cd /tmp/test
mkdir d1
mkdir d2
touch d1/text1.txt
sleep 1
touch d1/movie1.mov
touch d2/movie2.mov
sleep 1
touch d2/text2.txt
touch notthis.file
2

just use

find . -exec touch {} \;
0

I use this script to recursively set folder timestamps to latest content. (It's very similar to Gilles' answer):

#! /bin/bash

# Change mtime of directories to that of latest file (or dir) under it, recursively
# Empty leaf directories, or dirs with only symlinks get the $default_timestamp

default_timestamp='1980-01-01 00:00:00'

dir="$1"

[ -d "$dir" ] || { echo "Usage: $0 directory" >&2; exit 1; }

find "$dir" -depth -type d | while read d; do
    latest=$(find "$d" -mindepth 1 -maxdepth 1 \( -type f -o -type d \) -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)
    if [ -n "$latest" ]; then
        touch -c -m -r "$latest" "$d" \
            || echo "ERROR setting mtime of '$d' using ref='$f'" >&2
    else
        touch -c -m -d "$default_timestamp" "$d" \
            || echo "ERROR setting mtime of '$d' to default '$default_timestamp'" >&2
    fi
done
0

I've taken @Gilles zsh command and enhanced it to work in subfolders, but it seems that zsh is terribly inefficient for the **/*(FDod) part.

# Don't do this
for d in **/*(FDod); do touch -r "$d"/*(Dom[1]) "$d"; done

The quotes allow directory entries containing spaces and tabs to work correctly. The FD makes it find non-empty directories, including those starting with ., the od makes it search in the depth-first manner, so that the parent folder timestamps get updated properly.

When testing, I noticed that the performance and memory footprint for **/*(FDod) was insane (over 1.4GB for just 650k folders), and it read the whole lot before starting to touch folders. After replacing it with find/read it became much faster, didn't burn memory, and started almost straight away.

#! /usr/bin/env zsh
# Do this instead
find "$@" -depth -type d ! -empty -print0 |while IFS= read -r -d ''; do
    touch -r "$REPLY"/*(Dom[1]) "$REPLY"
done

If you aren't running it in a script, replace "$@" with the folders you want to run it from.

-1

I didn't really like the obscurity of the shell commands and quickly did it in python.

It recursively sets all directory mtimes to the newest non-excluded file/dir mtime inside.

import os
import sys

EXCLUDE_FILES = ['.DS_Store']
EXCLUDE_DIRS = ['.git', '.hg']

def inheritMTime(path):
    for root, dirs, files in os.walk(path, topdown=False, followlinks=False):
        currentMaxMtime = None
        for subpath in files:
            if subpath in EXCLUDE_FILES:
                print "ignore file", os.path.join(root, subpath)
                continue
            currentMaxMtime = max(currentMaxMtime, os.lstat(os.path.join(root, subpath)).st_mtime)
        for subpath in dirs:
            if subpath in EXCLUDE_DIRS:
                print "ignore dir", os.path.join(root, subpath)
                continue
            currentMaxMtime = max(currentMaxMtime, os.lstat(os.path.join(root, subpath)).st_mtime)
        if currentMaxMtime is not None:
            os.utime(root, (currentMaxMtime, currentMaxMtime))

if __name__ == "__main__":
    for dir in os.listdir(sys.argv[1]):
        if os.path.isdir(dir):
            current = os.path.join(sys.argv[1], dir)
            inheritMTime(current)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.