0

I have a lot of files where filename contains file creation date (IMG_RRRRMMDD_hhmmss.jpg):

IMG_20171015_133516.jpg
IMG_20171015_133827.jpg
IMG_20171015_142634.jpg
IMG_20171015_142834.jpg
IMG_20171015_142857.jpg

but actual file creation date is different. I need to set it back to date from a filename.

I know how to change the date of all files to current date:

find -type f -exec touch {} \;

and how to parse the file name to get the date in the proper format for touch -t

echo IMG_20171015_133516.jpg | awk -F_ '{print $2 substr($3,0,4) "." substr($3,5,2)}'
201710151335.16

But I have no idea how to combine these commands together to change all files.

find -type f -exec touch -t"$(echo '{}' | awk -F_ '{print $2 substr($3,0,4) "." substr($3,5,2)}')" {} \;

returns

touch: invalid date format ‘.’

5

In the shell:

for f in ./IMG_*.jpg ; do 
    t=${f##*/} 
    touch "$f" -t "${t:4:8}${t:13:4}.${t:17:2}"
done

Bash, ksh, zsh etc have the ${var:n:m} substring expansion, and if you need to do it recursively, enable globstar and use **/IMG_*.jpg instead.

Doing it with find needs the shell, too:

$ find -name "IMG_*.jpg" -exec bash -c '
  for f; do t=${f##*/}; touch "$f" -t "${t:4:8}${t:13:4}.${t:17:2}" ; done
  ' sh {} \+

The first argument to Bash after the script goes to the $0 variable, which usually contains the shell name. The rest go to the positional parameters $1, $2, etc. and for f or for f in "$@" loops over them. The construct is here is rather common (at least in unix.SE answers). The main part of the loop here is the same as in the first example.


The find command in your question find .. -exec touch -t"$(...)" {} \; doesn't work since the command substitution is in double quotes, so it gets expanded before find runs. The {} is piped literally to awk, and it just so happens that the awk script only prints a dot.

If you did put the command substitution in single quotes, it would be passed to touch as-is (find would only substitute {}). touch would get the argument -t$(echo ...), dollar signs, parenthesis and all. find doesn't run the command through the shell by itself, we need to explicitly do it, as above.

In single quotes example:

$ find -name x -exec echo 'hello $(foo {})' \;
hello $(foo ./x)
  • Works great, but I still don't know what's wrong with my approach. Why "$()" returns . instead formatted date? – mx0 Dec 8 '17 at 15:50
  • @mx0, ah good point, perhaps that should have been elaborated. edited. – ilkkachu Dec 8 '17 at 16:00

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.