J'ai trouvé des questions similaires mais pas dans Linux/Bash
Je veux que mon script crée un fichier avec un nom donné (via une entrée utilisateur) mais ajoute un numéro à la fin si le nom de fichier existe déjà.
Exemple:
$ create somefile
Created "somefile.ext"
$ create somefile
Created "somefile-2.ext"
Le script suivant peut vous aider. Vous ne devez pas exécuter plusieurs copies du script en même temps pour éviter toute condition de concurrence.
name=somefile
if [[ -e $name.ext ]] ; then
i=0
while [[ -e $name-$i.ext ]] ; do
let i++
done
name=$name-$i
fi
touch "$name".ext
Plus facile:
touch file`ls file* | wc -l`.ext
Tu auras:
$ ls file*
file0.ext file1.ext file2.ext file3.ext file4.ext file5.ext file6.ext
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 le disque 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
La valeur max
peut être utilisée pour se protéger des boucles infinies lorsque les fichiers ne peuvent pas être créés pour une raison autre que noclobber
.
Notez que noclobber
s'applique uniquement à l'opérateur >
et non pas à >>
ni <>
.
En fait, noclobber
ne supprime pas la condition de concurrence critique dans tous les cas. Cela empêche seulement les fichiers réguliers de clobber (pas d'autres types de fichiers, de sorte que cmd > /dev/null
n'échoue pas par exemple) et a une condition de concurrence dans la plupart des shells.
Le shell effectue d’abord une stat(2)
sur le fichier pour vérifier s’il s’agit d’un fichier normal ou non (fifo, répertoire, périphérique, etc.). Ce n'est que si le fichier n'existe pas (encore) ou s'il s'agit d'un fichier normal que 3> "$file"
utilise l'indicateur O_EXCL pour garantir que le fichier n'est pas endommagé.
Donc, s’il existe un fichier fifo ou périphérique portant ce nom, il sera utilisé (à condition qu’il puisse être ouvert en écriture seule), et un fichier normal risque d’être endommagé s’il est créé en remplacement d’un répertoire fifo/device /. .. entre cette stat(2)
et open(2)
sans O_EXCL!
Maintenant, ce n’est vraiment inquiétant 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 critique dans le cas normal où deux instances du même script s'exécutent au même moment. Donc, dans ce cas, il vaut mieux que les approches qui vérifient l’existence préalable du fichier avec [ -e "$file" ]
.
Pour une version opérationnelle ne contenant aucune condition de concurrence, vous pouvez utiliser le shell zsh
à la place de bash
, qui possède une interface brute vers open()
sous la forme de la variable sysopen
du module zsh/system
:
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
Essayez quelque chose comme ça
name=somefile
path=$(dirname "$name")
filename=$(basename "$name")
extension="${filename##*.}"
filename="${filename%.*}"
if [[ -e $path/$filename.$extension ]] ; then
i=2
while [[ -e $path/$filename-$i.$extension ]] ; do
let i++
done
filename=$filename-$i
fi
target=$path/$filename.$extension
Essayez quelque chose comme ceci (non testé, mais vous avez l’idée):
filename=$1
# If file doesn't exist, create it
if [[ ! -f $filename ]]; then
touch $filename
echo "Created \"$filename\""
exit 0
fi
# If file already exists, find a similar filename that is not yet taken
digit=1
while true; do
temp_name=$filename-$digit
if [[ ! -f $temp_name ]]; then
touch $temp_name
echo "Created \"$temp_name\""
exit 0
fi
digit=$(($digit + 1))
done
Selon ce que vous faites, remplacez les appels à touch
par le code nécessaire à la création des fichiers avec lesquels vous travaillez.
Utilisez touch
ou ce que vous voulez au lieu de echo
:
echo file$((`ls file* | sed -n 's/file\([0-9]*\)/\1/p' | sort -rh | head -n 1`+1))
Parties de l'expression expliquées:
ls file*
sed -n 's/file\([0-9]*\)/\1/p'
sort -rh
head -n 1
C’est une méthode bien meilleure que j’ai utilisée pour créer progressivement des répertoires.
Il pourrait également être ajusté pour le nom de fichier.
LAST_SOLUTION=$(echo $(ls -d SOLUTION_[[:digit:]][[:digit:]][[:digit:]][[:digit:]] 2> /dev/null) | awk '{ print $(NF) }')
if [ -n "$LAST_SOLUTION" ] ; then
mkdir SOLUTION_$(printf "%04d\n" $(expr ${LAST_SOLUTION: -4} + 1))
else
mkdir SOLUTION_0001
fi
Un simple reconditionnement de réponse de choroba en tant que fonction généralisée:
autoincr() {
f="$1"
ext=""
# Extract the file extension (if any), with preceeding '.'
[[ "$f" == *.* ]] && ext=".${f##*.}"
if [[ -e "$f" ]] ; then
i=1
f="${f%.*}";
while [[ -e "${f}_${i}${ext}" ]]; do
let i++
done
f="${f}_${i}${ext}"
fi
echo "$f"
}
touch "$(autoincr "somefile.ext")"