Para evitar las condiciones de carrera :
name=some-file
n=
set -o noclobber
until
file=$name${n:+-$n}.ext
{ command exec 3> "$file"; } 2> /dev/null
do
((n++))
done
printf 'File is "%s"\n' "$file"
echo some text in it >&3
Y además, tienes el archivo abierto para escribir en fd 3.
Con bash-4.4+
, puede convertirlo en una función como:
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
}
Para ser utilizado por ejemplo como:
create 3 somefile .ext || exit
printf 'File: "%s"\n' "$REPLY"
echo something >&3
exec 3>&- # close the file
El max
El valor se puede usar para protegerse contra bucles infinitos cuando los archivos no se pueden crear por otra razón que no sea noclobber
.
Tenga en cuenta que noclobber
solo se aplica al >
operador, no >>
ni <>
.
Condición de carrera restante
En realidad, noclobber
no elimina la condición de carrera en todos los casos. Solo previene palizas regular archivos (no otros tipos de archivos, por lo que cmd > /dev/null
por ejemplo, no falla) y tiene una condición de carrera en la mayoría de los shells.
El shell primero hace un stat(2)
en el archivo para verificar si es un archivo normal o no (fifo, directorio, dispositivo...). Solo si el archivo no existe (todavía) o es un archivo normal 3> "$file"
use el indicador O_EXCL para garantizar que no se destruya el archivo.
Por lo tanto, si hay un archivo fifo o de dispositivo con ese nombre, se usará (siempre que se pueda abrir en modo de solo escritura), y un archivo normal puede verse afectado si se crea como reemplazo de un fifo/dispositivo/directorio. .. entre ese stat(2)
y open(2)
sin O_EXCL!
Cambiando el
{ command exec 3> "$file"; } 2> /dev/null
a
[ ! -e "$file" ] && { command exec 3> "$file"; } 2> /dev/null
Evitaría usar un archivo no regular ya existente, pero no abordaría la condición de carrera.
Ahora, eso solo es realmente una preocupación frente a un adversario malicioso que querría hacerle sobrescribir un archivo arbitrario en el sistema de archivos. Elimina la condición de carrera en el caso normal de dos instancias del mismo script ejecutándose al mismo tiempo. Entonces, en eso, es mejor que los enfoques que solo verifican la existencia del archivo de antemano con [ -e "$file" ]
.
Para una versión que funcione sin ninguna condición de carrera, puede usar el zsh
shell en lugar de bash
que tiene una interfaz sin formato para open()
como el sysopen
integrado en el zsh/system
módulo:
zmodload zsh/system
name=some-file
n=
until
file=$name${n:+-$n}.ext
sysopen -w -o excl -u 3 -- "$file" 2> /dev/null
do
((n++))
done
printf 'File is "%s"\n' "$file"
echo some text in it >&3
Más fácil:
touch file`ls file* | wc -l`.ext
Obtendrás:
$ ls file*
file0.ext file1.ext file2.ext file3.ext file4.ext file5.ext file6.ext
El siguiente script puede ayudarte. No debe ejecutar varias copias del script al mismo tiempo para evitar la condición de carrera.
name=somefile
if [[ -e $name.ext || -L $name.ext ]] ; then
i=0
while [[ -e $name-$i.ext || -L $name-$i.ext ]] ; do
let i++
done
name=$name-$i
fi
touch -- "$name".ext