Pour éviter les conditions de course :
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
Et en plus, vous avez le fichier ouvert en écriture sur fd 3.
Avec bash-4.4+
, vous pouvez en faire une fonction comme :
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
}
A utiliser par exemple comme :
create 3 somefile .ext || exit
printf 'File: "%s"\n' "$REPLY"
echo something >&3
exec 3>&- # close the file
Le max
la valeur peut être utilisée pour se prémunir contre les boucles infinies lorsque les fichiers ne peuvent pas être créés pour une autre raison que noclobber
.
Notez que noclobber
ne s'applique qu'au >
opérateur, pas >>
ni <>
.
Condition de concurrence restante
En fait, noclobber
ne supprime pas la condition de concurrence dans tous les cas. Cela empêche seulement le clobbering régulier fichiers (pas d'autres types de fichiers, de sorte que cmd > /dev/null
par exemple n'échoue pas) et a lui-même une condition de concurrence dans la plupart des shells.
Le shell fait d'abord un stat(2)
sur le fichier pour vérifier s'il s'agit d'un fichier normal ou non (fifo, répertoire, périphérique...). Ce n'est que si le fichier n'existe pas (encore) ou s'il s'agit d'un fichier normal que 3> "$file"
utilisez le drapeau O_EXCL pour garantir de ne pas encombrer le fichier.
Donc, s'il existe un fifo ou un fichier de périphérique portant ce nom, il sera utilisé (à condition qu'il puisse être ouvert en écriture seule), et un fichier normal peut être encombré s'il est créé en remplacement d'un fifo/device/directory. .. entre ces stat(2)
et open(2)
sans O_EXCL !
Changer le
{ command exec 3> "$file"; } 2> /dev/null
à
[ ! -e "$file" ] && { command exec 3> "$file"; } 2> /dev/null
Éviterait d'utiliser un fichier non régulier déjà existant, mais ne résoudrait pas la condition de concurrence.
Maintenant, ce n'est vraiment un problème que face à un adversaire malveillant qui voudrait vous faire écraser un fichier arbitraire sur le système de fichiers. Il supprime la condition de concurrence dans le cas normal de deux instances du même script s'exécutant en même temps. Donc, en cela, c'est mieux que les approches qui vérifient uniquement l'existence du fichier au préalable avec [ -e "$file" ]
.
Pour une version de travail sans condition de concurrence, vous pouvez utiliser le zsh
shell au lieu de bash
qui a une interface brute vers open()
comme le sysopen
intégré dans le zsh/system
modules :
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
Plus simple :
touch file`ls file* | wc -l`.ext
Vous obtiendrez :
$ ls file*
file0.ext file1.ext file2.ext file3.ext file4.ext file5.ext file6.ext
Le script suivant peut vous aider. Vous ne devez pas exécuter plusieurs copies du script en même temps pour éviter les conditions de concurrence.
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