Generate new name for moved file to prevent overwriting

busyboxfilenamesfilesrename

How can I generate a new name for a file if there's an existing file With that same name? In a Desktop environment a new name is generated my appending a number to the end of the file name, but how can this be accomplished from the command line?

I'm using the Android operating system with Busybox.

Best Answer

Assuming you have a POSIX shell, you can do this:

mv() {
        eval "DEST=\${$#}" #The destination is the last positional parameter
        if [ -e "$DEST" ] && ! [ -d "$DEST" ];then
                PREFIX=${DEST%.*}
                COUNT=1
                EXT=${DEST##*.}
                args= i=1
                while [ $i -lt $# ]; do args="$args \"\${$i}\"" i=$((i+1)); done
                DEST="$NAME-"$(printf "%03d" $COUNT)".$EXT"
                while [ -e "$DEST" ];do
                    COUNT=$((COUNT+1))
                    DEST="$NAME-"$(printf "%03d" $COUNT)".$EXT"
                done
                eval "command mv $args \"\$DEST\""
        else
                command mv "$@"
        fi
}

How to use this

This is a function so save it in your ~/.bashrc and call it as you would the normal mv.

What this does

  • Stores the path to the original mv executable in the MV variable
  • Gets the last argument it was called with into the variable DEST
  • If DEST exists and is not a directory, this function assumes that your rename is trying to clobber a file
  • It then extracts the final name's prefix (anything before the final ., which marks the extension), the extension (anything after the final .), the count (if any; anything in the prefix after the final -).
  • The extracted count is set to zero if no count was found otherwise, it's set to the count found in the previous step.
  • The current count is incremented
  • The function then calls itself with all the original arguments (switches + file names) minus the last one and adds the new file name instead of the last argument in the original call. The new name is the old name but with a 3 digit counter (with zero-stuffing) added before the extension.
  • The function is recursive because if you were saying mv a.txt b.txt it will first try mv a.txt b-001.txt. This next mv call must also be the function itself because if b-001.txt also exists, we want to keep incrementing the counter until we find a new file name that doesn't exist.
  • If the final argument doesn't exist or is a directory, the original mv executable is called with your original arguments.

Caveats

  • The number of times you can repeatedly try to clobber an existing file depends on the length of the counter (999 times in this case). You may choose a number of digits that encompasses the inode limit on your filesystem to ensure that this will work for as long as you're able to create files.
  • If you try to clobber a file whose name is similar to foo-001.txt, it will be moved to foo-001-001.txt.

Notes

  • To change the naming pattern, change the 3 in the printf statement to whatever you like.
  • This code has been tested
  • This is very simplistic and I'm sure there will be edge cases where it fails miserably. I'm happy to try and fix them for you if you find any. In the meantime, don't try this on a production machine.
Related Question