Per evitare le condizioni di gara :
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
Inoltre, hai il file aperto per la scrittura su fd 3.
Con bash-4.4+
, puoi renderlo una funzione come:
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
}
Da utilizzare ad esempio come:
create 3 somefile .ext || exit
printf 'File: "%s"\n' "$REPLY"
echo something >&3
exec 3>&- # close the file
Il max
value può essere utilizzato per proteggersi da loop infiniti quando i file non possono essere creati per motivi diversi da noclobber
.
Nota che noclobber
si applica solo al >
operatore, non >>
né <>
.
Condizioni di competizione rimanenti
In realtà, noclobber
non rimuove la race condition in tutti i casi. Previene solo il clobbering normale file (non altri tipi di file, quindi cmd > /dev/null
per esempio non fallisce) e ha una race condition stessa nella maggior parte delle shell.
La shell prima esegue un stat(2)
sul file per verificare se si tratta di un file normale o meno (fifo, directory, dispositivo...). Solo se il file non esiste (ancora) o è un file normale 3> "$file"
usa il flag O_EXCL per garantire di non danneggiare il file.
Quindi, se esiste un fifo o un file di dispositivo con quel nome, verrà utilizzato (a condizione che possa essere aperto in sola scrittura) e un file normale potrebbe essere cancellato se viene creato in sostituzione di un fifo/dispositivo/directory. .. tra quel stat(2)
e open(2)
senza O_EXCL!
Cambiare il
{ command exec 3> "$file"; } 2> /dev/null
a
[ ! -e "$file" ] && { command exec 3> "$file"; } 2> /dev/null
Eviterebbe di usare un file non regolare già esistente, ma non affronta la race condition.
Ora, questa è davvero una preoccupazione solo di fronte a un avversario malintenzionato che vorrebbe farti sovrascrivere un file arbitrario sul file system. Rimuove la condizione di competizione nel caso normale di due istanze dello stesso script in esecuzione contemporaneamente. Quindi, in questo, è meglio degli approcci che controllano solo l'esistenza del file in anticipo con [ -e "$file" ]
.
Per una versione funzionante senza race condition, puoi usare zsh
shell invece di bash
che ha un'interfaccia grezza per open()
come sysopen
integrato nel zsh/system
modulo:
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
Più facile:
touch file`ls file* | wc -l`.ext
Otterrai:
$ ls file*
file0.ext file1.ext file2.ext file3.ext file4.ext file5.ext file6.ext
Il seguente script può aiutarti. Non dovresti eseguire più copie dello script contemporaneamente per evitare race condition.
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