How to rename a zero-padded file sequence efficiently in ZSH?

I have a picture sequence named with zero-padded numbers like so:

/path/to/file_07469.jpx
/path/to/file_07470.jpx
/path/to/file_07471.jpx
/path/to/file_07472.jpx
/path/to/file_07473.jpx
/path/to/file_07474.jpx
/path/to/file_07475.jpx
/path/to/file_07476.jpx
/path/to/file_07477.jpx
/path/to/file_07478.jpx
/path/to/file_07479.jpx
/path/to/file_07480.jpx
/path/to/file_07481.jpx
/path/to/file_07482.jpx

This is just an extract. It is thousands of files. I’d like to rename all files from a certain number on, adding / subtracting X. I’d love to use find with a regex.

#!/bin/zsh

shift=-1000
seqnumstart="$(echo "$1" | grep -Eo "\d+")"
bn="$(basename $1)"
bbn="$(echo "${bn%_*}")"
ext="$(echo "${bn##*.}")"

find "$(dirname $1)" -name "$bbn*$ext" -print0 | while read -d $'\0' file
do
    seqnum="$(echo "$file" | grep -Eo "\d+")"
    seqnum="$(echo "${seqnum#"${seqnum%%[!0]*}"}")"

    if [[ "$seqnum" -ge "$seqnumstart" ]]; then
        seqnumnew=$(($seqnum + $shift))
        seqnumnew=$(printf %05d $seqnumnew)
        filenew="$(echo $file | sed -E 's [0-9]+ '$seqnumnew' g')"
        mv "$file" "$filenew"
    fi

done

How can I improve my code? It is very slow. Im on a Mac (zsh).

2 answers

  • answered 2021-06-10 11:20 florit

    This is much faster:

    #!/bin/zsh
    
    shift=1000
    
    seqnumstart="$(echo "$1" | grep -Eo "\d+")"
    lastfile="$(find "$(dirname $1)" -name "*.jpx" | sort | tail -1)"
    seqnumend="$(echo "$lastfile" | grep -Eo "\d+")"
    
    bn="$(basename $1)"
    bbn="$(echo "${bn%_*}")"
    
    #extension
    ext="$(echo "${bn##*.}")"
    
    #basepath before the padded number
    bp="$(echo "${1%_*}")"
    
    function buildpath {
        echo "$bp"_"$1"."$ext"
    }
    
    for i in {$seqnumstart..$seqnumend}
    do
       unpad="$(echo $i | sed 's/^0*//')"
       seqnumnew="$(($unpad + $shift))"
       seqnumnewpad="$(printf %05d $seqnumnew)"
    
       op="$(buildpath "$i")"
       np="$(buildpath "$seqnumnewpad")"
    
       mv "$op" "$np"
    done
    

  • answered 2021-06-10 15:42 Gairfowl

    zmv is a utility in zsh that can do a lot of filename manipulation and looping for you. Try this:

    zmv -n 'p/file_(<7000-7999>).jpx' 'p/file_$(printf "%05d" $(($1 - 1000))).jpx'
    

    Some of the pieces:

    • zmv: an autoload function; use autoload -Uz zmv to make it available (this is usually added to .zshrc).
    • -n: no-op. With this option, zmv will just print what would have happened, giving you an idea if the command is correct. Remove this to actually mv the files.
    • (...): grouping operator for zmv. This identifies sections in the name that you want to change; this section is referenced in the 'to' argument as $1.
    • <7000-7999>: glob operator for a range. Note that leading zeroes are not always required.
    • $(printf "%05d" ...): zero-padding.
    • $((...)): arithmetic.
    • $1: reference to the parenthetical value in the 'from' argument'. This is where zmv's magic happens - this is substituted for each matching filename.

    As you likely know, you'll need to do the renaming in groups or in a specific order to avoid trying to change a name to a name that already exists. zmv will usually halt when it encounters collisions like that.