Search code examples
zshglobconditional-operator

How to test for existance of globbed file before moving it in zsh?


I use to photograph with raw-files and develop to jpg with raw therapee later. For the slideshow I also mix the photos with the ones from the iPhone of my wife and the ones of my Pixel. To have them in the right order I prepend the filename with YYYYMMDD-HH-MM-SS_ from the creation date. Because they are developed later, the jpg-files have a different creation date than the NEF-files. Therefore I wrote a short zsh-script to get the name of a jpg-file without date-time (OZ8_xxxx.JPG),look up the corresponding YYYYMMDD-HH-MM-SS_OZ8xxxx.NEF and change the date-time-part of the jpg to the one of the NEF-file. This worked - partly. I run into an error if the NEF-file was deleted or moved. So I tried this and some slightly different qualifiers like (#qN): if [[ -n *${nummer}.NEF(NY1) ]]; then This improved the script - partly.

For unknown reason, sometimes the script moves the jpg to a file named ".JPG" instead YYYYMMDD-HH-MM-SS_OS8_xxxx.JPG. If this happens more than one time, one or more jpg-files will get lost. As far as I understand, if [[ -n pattern(NY1) ]] should avoid this. Same happens with the .pp3-files. So I most probably got something wrong with the [[ -n check. I also wonder why it doesn't seem to work with [[ -f.

And I hope that some of the zsh-magicians around will come up with an even better solution.

`#!zsh
# Transfer the creation date of the NEF-Datei to the JPG-Datei
# Example NEF: 20241117-08-15-33_OZ8_3367.NEF
# Example JPG: 20241117-16-18-03_OZ8_3367.JPG
for j_f in *.JPG; do
       # only look for jpgs
       # remove extension
    j_g=${j_f%.*};
    echo "Z9: " $j_f;
    # Remove date-time: YYYYMMDD-hh-mm-ss
    #j_date=${j_f%%_*};
    #date=${date%_*}
       #echo $j_date;
       # Extrakt the uniq diskriminator of the files: OZ8_xxxx
    nummer=${j_g#*_};
    #echo $nummer;
    #echo "Z17: $nummer.NEF";
    #check for theexistance of a corresponing NEF-file (Datum-Zeit-OZ8_xxxx.NEF
    if [[ -n *${nummer}.NEF(NY1) ]]; then
        #echo Z19: *${nummer}.NEF;
        # 
        # extract original Creation date of the NEF-file
        p_date=(*${nummer}.NEF(NY1))
        #echo Z22: $p_date;
        n_date=${p_date%.*};
        #echo Z23: ${n_date}.JPG;
        
        if [[ ! -f  ${n_date}.JPG ]]; then
            #mv $j_f ${n_date}.JPG;

            echo move to ${n_date}.JPG 
        fi
    fi
done
# und gleich auch für die pp3-files
for pp3_f in *.pp3; do
    p_g=${pp3_f%%.*}; # entferne .NEF.pp3
    nummer=${p_g#*_}; # und Datum-Zeit bleibt OZ8_xxxx
    if [[ -n *${nummer}.NEF(NY1) ]]; then
        p_file=(*${nummer}.NEF(NY1));
# change the date-time part of the filename of the jpg-file to the date-time-part of the NEF-file
        echo move $pp3_f to $p_file.pp3
    fi
done

Solution

  • It looks like the wild card is not being expanded in -n *${nummer}.NEF(NY1), and therefore the test isn't working as expected. From the docs:

    Filename generation is not performed on any form of argument to conditions. However, it can be forced in any case where normal shell expansion is valid and when the option EXTENDED_GLOB is in effect by using an explicit glob qualifier of the form (#q) at the end of the string. A normal glob qualifier expression may appear between the q and the closing parenthesis; if none appears the expression has no effect beyond causing filename generation. The results of filename generation are joined together to form a single word, as with the results of other forms of expansion.
    This special use of filename generation is only available with the [[ syntax.

    The same issue probably occurred with -f. The test should work with setopt extendedglob and -n *${nummer}.NEF(#qNY1).


    This script uses some zsh-isms to try and rename the files. It adds a check on the filenames that match *.JPG to ensure they meet some of the other criteria, and then uses backreferences assigned during that check to determine the id.

    It doesn't perform the expansion in a conditional, since the matched name is used again. Instead the glob results are stored in an array.

    #!/usr/bin/env zsh
    setopt extendedglob
    
    local f match mbegin mend
    local dtTime='<20000101->-<0-23>-<0-59>-<0-59>'
    for f in *.JPG; do
    
      # check that name matches dateTime_*_num.JPG,
      # get id via backreference (#b), parens
      if [[ $f != (#b)${~dtTime}_(*_<->).JPG ]]; then
        print -r "[$f] - does not match pattern; skipping."
        continue
      fi
    
      # $match[1] is the id, set in conditional clause above
      local -a nefList=( ${~dtTime}_${match[1]}.NEF(N) )
      if (($#nefList < 1)); then
        print -r "[$f] - no matching NEF files; skipping."
        continue
      fi
      if (($#nefList > 1)); then
        print -r "[$f] - multiple matching NEF files; skipping."
        print -rl '    match: '${^nefList}
        continue
      fi
    
      # remove .NEF, add .JPG
      local neuJpegName=${nefList[1]:r}.JPG
      if [[ -f $neuJpegName ]]; then
        print -r "[$f] - destination [$neuJpegName] exists; skipping."
        continue
      fi
    
      print -r "[$f] - mv to [$neuJpegName]"
      # uncomment after testing:
      # mv -n "$f" "$neuJpegName"
    done
    

    More about backreferences here.


    Edited to add: this command for renaming the files is based on zmv. It will check for the same errors as the script above, such as naming collisions and missing or extra .NEF files.

    autoload zmv
    zmv -vi "${dt::=<20000101->(-<->)(#c3)}(_*_<->).JPG" '$(
      (){ ((#2)) || <<< $1:r.$f:e } $~dt$2.NEF
    )'
    

    The -i (interactive) option is included here for testing, and can be removed. The -n (no-op / dry run) option is also available for validating patterns.