khustochka khustochka - 13 days ago 6
Bash Question

find, xargs: execute chain of commands for each file

I am sorry if the question title is not informative enough. Please feel free to suggest a better variant.

I want to perform the following task:
In a directory I have a number of files that are photos in JPEG format. I want to extract from EXIF the dates when those photos were taken, create a new directory for each date, and move a file to the relevant directory.

(EXIF date and time have the format

YYYY:MM:DD hh:mm:ss
, and I want the directory names to be formatted as
YYYY-MM-DD
, that's why I use sed)

I kind of know how to perform each of those tasks separately, but failed to put them together. I spent some time investigating how to execute commands using
find
with
-exec
or
xargs
but still failed to understand how to properly chain everything.

Finally I was able to fulfil my task using two commands:

find . -name '*.jpg' -exec sh -c "identify -format %[exif:DateTimeOriginal] {}
| sed 's/ [0-9:]*//; s/:/-/g' | xargs mkdir -p" \;

find . -name '*.jpg' -exec sh -c "identify -format %[exif:DateTimeOriginal] {}
| sed 's/ [0-9:]*//; s/:/-/g; s/$/\//' | xargs mv {}" \;


But I do not like the duplication, and I do not like
-exec sh -c
. Is there the right way to do this in one line and without using
-exec sh -c
?

Answer

Rather than focusing on one-liners, a better solution would be to put the logic into a script which makes it easy to execute and test. Put this in a file called movetodate.sh:

#!/usr/bin/env bash

# This script takes one or more image file paths

set -e
set -o pipefail

for path in "$@"; do
    date=$(identify -format %[exif:DateTimeOriginal] | sed 's/ [0-9:]*//; s/:/-/g')
    dest=$(dirname "$path")/$date
    mkdir -p "$dest"
    mv "$path" "$dest"
done

Then, to invoke it:

find . -name '*.jpg' -exec ./movetodate.sh {} +
Comments