Auto increment filename

GNU mv has a --backup option which may be useful. The following interaction shows how the files are getting renamed when the target exists:

$ touch a b c o
$ mv --backup=numbered --verbose a o
`a' -> `o' (backup: `o.~1~')
$ mv --backup=numbered --verbose b o
`b' -> `o' (backup: `o.~2~')
$ mv --backup=numbered --verbose c o
`c' -> `o' (backup: `o.~3~')

In this example, the original file o has been renamed to o.~1~, a to o.~2~, b to o.~3~, and c to o. So this doesn't rename in the same way that the code you posted does, but this may be acceptable, depending on your exact needs.


You might do it like:

set -C; num=0                                  ### set -o noclobber; init $num
[ -e "$1" ] &&                                 ### this needs to be true
until 2>&3 >"./DUPES/$((num+=1))-$1" &&        ### try to open() num+=1
      mv --  "$1"  "./DUPES/$num-$1"           ### if opened, mv over it
do :; done 3>/dev/null                         ### do nothing

You would at once assure that multiple instances cannot secure the same name for any given file, and increment your variable.

The /dev/null < stderr just drops the shell's complaint about a file existing when it tries to do the output truncate/redirect and finds an existing target. While noclobber is enabled it won't overwrite another file - it will only open() a new one unless you use >|. And so you don't need its complaint because the whole point is to increment over existing files until a non-existing name is found.

Regarding the performance aspect - it would be better if you didn't start at zero. Or, if you tried to make up the difference. I guess the above might be improved somewhat like:

set -C; num=0                                  ### set -o noclobber; init $num
until 2>&3 >"./DUPES/$((num+=1))-$1"  &&       ### try to open() num+=1
      mv --  "$1"  "./DUPES/$num-$1"           ### if opened, mv over it
do    [ -e  "./DUPES/$((num*2))-$1" ] &&       ### halve fail distance
      num=$((num*2))                           ### up to a point of course
done  3>/dev/null                              ### done

...but up to 1000 you probably don't have to worry about it terribly. I've got to 65k over random names in a couple seconds.

By the way - you might think you could just:

>"./DUPES/$((num+=1))-$1" mv -- "$1" "./DUPES/$num-$1"

...but it doesn't work in a bash shell.

num=0; echo >"/tmp/$((num+=1))" >&2 "$num"; echo "$num" /tmp/[01]

0
1 /tmp/1

For whatever reason bash does the assignment in some other context for redirections - and so the expansions happen in a strange order. So you need a separate simple command to expand the correct $num value as I get here with &&. Otherwise, though:

num=0; echo "$((num+=1))" "$num"

1 1