Create new file but add number if filename already exists in bash Create new file but add number if filename already exists in bash bash bash

Create new file but add number if filename already exists in bash


The following script can help you. You should not be running several copies of the script at the same time to avoid race condition.

name=somefileif [[ -e $name.ext || -L $name.ext ]] ; then    i=0    while [[ -e $name-$i.ext || -L $name-$i.ext ]] ; do        let i++    done    name=$name-$ifitouch -- "$name".ext


Easier:

touch file`ls file* | wc -l`.ext

You'll get:

$ ls file*file0.ext  file1.ext  file2.ext  file3.ext  file4.ext  file5.ext  file6.ext


To avoid the race conditions:

name=some-filen=set -o noclobberuntil  file=$name${n:+-$n}.ext  { command exec 3> "$file"; } 2> /dev/nulldo  ((n++))doneprintf 'File is "%s"\n' "$file"echo some text in it >&3

And in addition, you have the file open for writing on fd 3.

With bash-4.4+, you can make it a function like:

create() { # fd base [suffix [max]]]  local fd="$1" base="$2" suffix="${3-}" max="${4-}"  local n= file  local - # ash-style local scoping of options in 4.4+  set -o noclobber  REPLY=  until    file=$base${n:+-$n}$suffix    eval 'command exec '"$fd"'> "$file"' 2> /dev/null  do    ((n++))    ((max > 0 && n > max)) && return 1  done  REPLY=$file}

To be used for instance as:

create 3 somefile .ext || exitprintf 'File: "%s"\n' "$REPLY"echo something >&3exec 3>&- # close the file

The max value can be used to guard against infinite loops when the files can't be created for other reason than noclobber.

Note that noclobber only applies to the > operator, not >> nor <>.

Remaining race condition

Actually, noclobber does not remove the race condition in all cases. It only prevents clobbering regular files (not other types of files, so that cmd > /dev/null for instance doesn't fail) and has a race condition itself in most shells.

The shell first does a stat(2) on the file to check if it's a regular file or not (fifo, directory, device...). Only if the file doesn't exist (yet) or is a regular file does 3> "$file" use the O_EXCL flag to guarantee not clobbering the file.

So if there's a fifo or device file by that name, it will be used (provided it can be open in write-only), and a regular file may be clobbered if it gets created as a replacement for a fifo/device/directory... in between that stat(2) and open(2) without O_EXCL!

Changing the

  { command exec 3> "$file"; } 2> /dev/null

to

  [ ! -e "$file" ] && { command exec 3> "$file"; } 2> /dev/null

Would avoid using an already existing non-regular file, but not address the race condition.

Now, that's only really a concern in the face of a malicious adversary that would want to make you overwrite an arbitrary file on the file system. It does remove the race condition in the normal case of two instances of the same script running at the same time. So, in that, it's better than approaches that only check for file existence beforehand with [ -e "$file" ].

For a working version without race condition at all, you could use the zsh shell instead of bash which has a raw interface to open() as the sysopen builtin in the zsh/system module:

zmodload zsh/systemname=some-filen=until  file=$name${n:+-$n}.ext  sysopen -w -o excl -u 3 -- "$file" 2> /dev/nulldo  ((n++))doneprintf 'File is "%s"\n' "$file"echo some text in it >&3