2

I have a few thousand subdirectories in a directory, each containing one config.ini file and one JPEG image. The ini file contains (including but not limited to) a section that encodes the time, when the image was taken.

[Acquisition]
Name=coating_filtered_001
Comment=Image acquisition
Year=2017
Month=3
Day=21
Hour=13
Minute=2
Second=34
Milliseconds=567

The image files always have the same exact name, for the sake of this question image.jpg.

I would like to copy all image files to some other (single) directory, and rename them to something like yyyy-mm-ddThh:mm:ss:NNN.jpg or similar, i.e. the filename consisting of the timestamp from the ini file.

Can this be achieved on the command line?

1

It can be achieved on the command line, but a script that would run on the command line would be an easier solution (I think).

Basic steps:

  • Get a list of directories to iterate over:
    find ${directory} -mindepth 1 -type d

  • Check each directory for the presence of config.ini, and image.jpg.
    if [ -f ${subdir}/config.ini -a -f ${subdir}/image.jpg ]; then ...

  • Check the config.ini for all the right parts of the timestamp.
    various grep ^Year= ${subdir}/config.ini or ^Month, etc...
  • Make a copy of the image.jpg file, using the timestamp.
    cp ${subdir}/image.jpg ${copydir}/${timestamp}.jpg

I think it's easier, and potentially safer to put these sequences into a script, where you can more easily put in readable output, error handling, etc.

Here's an example script to do those steps:

#!/bin/bash

imagepath="/path/to/images"
copydir="/path/to/copies"

# step 1: find all the directories
for dir in $(find ${imagepath} -mindepth 1 -type d); do
    echo "Procesing directory $dir:"
    ci=${dir}/config.ini
    jp=${dir}/image.jpg

    # step 2: check for config.ini and image.jpg
    if [ -f ${ci} -a -f ${jp} ]; then
        # step 3: get the parts of the timestamp
        year=$(grep ^Year= ${ci}   | cut -d= -f2)
        month=$(grep ^Month= ${ci} | cut -d= -f2)
        day=$(grep ^Day= ${ci}     | cut -d= -f2)
        hour=$(grep ^Hour= ${ci}   | cut -d= -f2)
        min=$(grep ^Minute= ${ci}  | cut -d= -f2)
        sec=$(grep ^Second= ${ci}  | cut -d= -f2)
        ms=$(grep ^Milliseconds= ${ci} | cut -d= -f2)

        # if any timestamp part is empty, don't copy the file
        # instead, write a note, and we can check it manually
        if [[ -z ${year} || -z ${month} || -z ${day} || -z ${hour} || -z ${min} || -z ${sec} || -z ${ms} ]]; then
            echo "Date variables not as expected in ${ci}!"
        else
            # step 4: copy file
            # if we got here, all the files are there, and the config.ini
            # had all the timestamp parts.
            tsfile="${year}-${month}-${day}T${hour}:${min}:${sec}:${ms}.jpg"
            target="${copydir}/${tsfile}"
            echo -n "Archiving ${jp} to ${target}: "
            st=$(cp ${jp} ${target} 2>&1)
            # capture the status and alert if there's an error
            if (( $? == 0 )); then
                echo "[ ok ]"
            else
                echo "[ err ]"
            fi
            [ ! -z $st ] && echo $st
        fi
    else
        # other side of step2... some file is missing... 
        # manual check recommended, no action taken
        echo "No config.ini or image.jpeg in ${dir}!"
    fi
    echo "---------------------"
done

It's always good to be somewhat conservative with scripts like this, so you don't accidentally delete files. This script only does 1 copy action, so that's pretty conservative, and it shouldn't harm your source files. But you may want to change specific actions or output messages to better suit your needs.

  • 1
    This did work as intended. I realized, my ini-files had windows line-endings, so I had to pipe them through dos2unix before using your script. Otherwise cut would include the \r character in the file names. – Finn Poppinga Mar 23 '17 at 9:53
  • in the pipline between grep and cut, you can add a | tr -d '\r' |, to save you the extra step. – Tim Kennedy Mar 23 '17 at 19:25
0
top="$(pwd -P)" \
find . -type d -exec sh -c '
   shift "$1"
   for iDir
   do
      cd "$iDir" && \
      if [ -f "image.jpg" ] && [ -s "config.ini" ]; then
         eval "$(sed -e "/^[[]Acquisition]/,/^Milliseconds/!d
                  /^Year=/b; /^Month=/b; /^Day=/b; /^Hour=/b; /^Minute=/b
                  /^Second=/b; /^Milliseconds=/b; d" config.ini)"
         new=$(printf "%04d-%02d-%02dT%02d:%02d:%02d:%03d\n" \
                  "$Year" "$Month" "$Day" "$Hour" "$Minute" "$Second" "$Milliseconds")
         echo cp -p "image.jpg" "$new"
         cp -p "image.jpg" "$new"
      else
        #echo >&2 "$iDir/image.jpg &/or config.ini file(s) missing or empty."
        :
      fi
      cd "$top"
   done
' 2 1 {} +

#meth-2
find . -type f -name config.ini -exec perl -F= -lane '
    push @A, $F[1] if /^\[Acquisition]/ .. /^Milliseconds/ and
                     /^(?:Year|Month|Day|Hour|Minute|Second|Milliseconds)=/;
    next if ! eof;
    my(@a, $fmt) = qw/-  -  T  :  :  :/;
    (my $d = $ARGV) =~ s|/[^/]+$||;
    print( STDERR "No image.jpg in dir: $d"),next if ! -f $d . "/image.jpg";
    $fmt .= "${_}$a[$a++]" for map { "%0${_}s" } qw/4 2 2 2 2 2 3/;
    print for map { "$d/$_" } "image.jpg", sprintf "$fmt.jpg", @A;
    ($a,@A)=(0);
' {} + | xargs -n 2 echo mv

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.