web-dev-qa-db-fra.com

Créer un nouveau fichier mais ajouter un numéro si le nom de fichier existe déjà dans bash

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"
19
heltonbiker

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
30
choroba

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
7

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 <>.

Condition de course restante

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
6
Stephane Chazelas

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
4
Gyuha Shin

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.

2
bta

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:

  • liste les fichiers par motif: ls file*
  • ne prendre qu'un numéro dans chaque ligne: sed -n 's/file\([0-9]*\)/\1/p'
  • appliquer le tri humain inverse: sort -rh
  • ne prendre que la première ligne (valeur maximale): head -n 1
  • combine tout en pipe et incrément (expression complète ci-dessus)
2
Serg Stetsuk

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
1
Oliver

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")"
0
SamWN