No 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 têm a expansão ${var:n:m} de substring e, se você precisar recursivamente, ative globstar e use **/IMG_*.jpg .
Fazer isso com find também precisa do shell:
$ 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 {} \+
O primeiro argumento para Bash depois do script vai para a variável $0 , que geralmente contém o nome do shell. O restante vai para os parâmetros posicionais $1 , $2 , etc. e for f ou for f in "$@" faz um loop sobre eles.
A construção aqui é bastante comum (pelo menos nas respostas unix.SE).
A parte principal do loop aqui é a mesma do primeiro exemplo.
O comando find em sua pergunta find .. -exec touch -t"$(...)" {} \; não funciona, pois a substituição do comando está entre aspas duplas, portanto, ele é expandido antes que find seja executado. O {} é canalizado literalmente para awk e acontece que o script awk imprime apenas um ponto.
Se você colocar a substituição do comando em cotações simples , ela será passada para touch as-is ( find só substituiria {} ). touch obteria o argumento -t$(echo ...) , cifrões, parênteses e tudo. find não executa o comando através do shell por si só, precisamos explicitamente fazê-lo, como acima.
No exemplo de aspas simples:
$ find -name x -exec echo 'hello $(foo {})' \;
hello $(foo ./x)