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

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


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.


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
    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"


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:

    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%_*}")"
    ext="$(echo "${bn##*.}")"
    #basepath before the padded number
    bp="$(echo "${1%_*}")"
    function buildpath {
        echo "$bp"_"$1"."$ext"
    for i in {$seqnumstart..$seqnumend}
       unpad="$(echo $i | sed 's/^0*//')"
       seqnumnew="$(($unpad + $shift))"
       seqnumnewpad="$(printf %05d $seqnumnew)"
       op="$(buildpath "$i")"
       np="$(buildpath "$seqnumnewpad")"
       mv "$op" "$np"

  • 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.