1

Previously I have ran dos2unix on every *.php file on my server recursively with this command:

find . -name '*.php' -type f -exec dos2unix --keepdate {} +

All files processed by dos2unix had its last modified datetime intact, but the parent directory containing such files still have their last modified datetime updated to the time I ran the command.

How do I, with an one liner, touch the directories recursively, setting the last modified datetime of the directories as the same datetime as the last modified file inside the directory?

From this:

dir_a/               2013-05-13 14:05
   abc.php           2012-09-01 12:34
   def.php           2012-09-15 23:45
   dir_b/            2013-05-13 14:05
       uvw.php       2012-10-01 01:23
       xyz.php       2012-10-08 09:10

To this:

dir_a/               2012-09-15 23:45
   abc.php           2012-09-01 12:34
   def.php           2012-09-15 23:45
   dir_b/            2012-10-08 09:10
       uvw.php       2012-10-01 01:23
       xyz.php       2012-10-08 09:10

The server is running on CentOS 5.6.

Phil
  • 135
  • 8

2 Answers2

2

Maybe something like this:

find . -type d -print0 | while read -r -d '' dir; do file="$(find "$dir" -maxdepth 1 -type f  -printf '%T+ %p\n' | sort -r | head -1 | cut -d' ' -f2-)";if [ -n "$file" ]; then touch "$dir" -mr "$file"; fi; done

Explanation:

To pass modification date from one file to another we have to run:

touch file1 -mr file2

First we have to find subdirectories:

find . -type d -print0 | while read -r -d '' dir; do echo "$dir"; done

In this case -exec option would get too complex so I use while-read approach. You need to make sure to pipe the output from find with NUL as record separator, and tell read to split on that (-d '').

To find files with last modification date I can use:

find ./  -maxdepth 1 -type f  -printf '%T+ %p\n' | sort -r | head -1 | cut -d' ' -f2-

Combining it:

find . -mindepth 1  -type d -print0 | while read -r -d '' dir; do file="$(find "$dir" -maxdepth 1 -type f  -printf '%T+ %p\n' | sort -r | head -1 | cut -d' ' -f2-)";echo "$dir <- $file"; done

Finally, adding touch:

find . -mindepth 1  -type d -print0 | while read -r -d '' dir; do file="$(find "$dir" -maxdepth 1 -type f  -printf '%T+ %p\n' | sort -r | head -1 | cut -d' ' -f2-)";touch "$dir" -mr "$file"; done

I use -mindepth option here because I launched this command from directory containing dir_a which does not have any file to read date from. To eliminate this problem I can use if-else to ensure i do not try to read time from non existing file:

find . -type d -print0 | while read -r -d '' dir; do file="$(find "$dir" -maxdepth 1 -type f  -printf '%T+ %p\n' | sort -r | head -1 | cut -d' ' -f2-)";if [ -n "$file" ]; then touch "$dir" -mr "$file"; fi; done
slhck
  • 235,242
Nykakin
  • 331
1

This should do it:

find -mindepth 1 -type d -print0 | while IFS= read -r -d '' dir; do 
 time=$(
  find "$dir" -mindepth 1 -maxdepth 1 -type f -exec stat -c %Y {} \; | 
  sort -nk2 | tail -n 1
 ); touch -d @$time "$dir"; 
 done

EXPLANATION:

  • find -mindepth 1 -type d | while IFS= read -r -d '' dir; do : iterate through all directories except the current one (-mindepth 1). Using a while loop with IFS= and the -r flag is to make sure the command works with directory names that contain spaces or new lines (\n) or other strange characters. The -d '' sets the delimiter to the null character so it can work with find's -print0 option.

  • find $dir -mindepth 1 -maxdepth 1 -type f -exec stat --printf="%Y\n" {} \; : Find all files of each directory $dir and run stat which prints their modification date in seconds since epoch.

  • sort -nk2 | tail -n 1 : this just sorts the dates and keeps the newest. The last two parts combined give the modification time of the newest file which is saved in the variable $time.

  • touch -d @$time $dir : here we use the touch command to set the modification time of each directory. The @ is there to tell touch that we a re giving time in seconds since epoch, this is a standard GNU coreutils feature.

terdon
  • 54,564