Search code examples
bashshellfindpipexargs

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?


Solution

  • 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 {} +