web-dev-qa-db-fra.com

Comment incrémenter le numéro de version dans un script Shell?

Le script de contrôle de version simple suivant est destiné à trouver le dernier numéro de version d'un fichier donné, à l'incrémenter, à exécuter une commande donnée avec le fichier nouvellement créé (par exemple, l'éditeur), puis à le sauvegarder dans stable. Puisqu'il est simple, il ne vérifie rien puisque le script serait modifié au besoin. Par exemple, si le résultat n'est pas stable, l'utilisateur peut omettre le dernier argument.

Cependant, une préoccupation majeure de la fonctionnalité actuelle est de savoir comment implémenter les éléments suivants: si la dernière section après le point a deux chiffres, inc jusqu'à 99; si seulement 1, augmenter jusqu'à 9, puis passer à la section précédente. Les versions peuvent avoir n'importe quel nombre entier positif de sections.

1.2.3.44 -> 1.2.3.45
1.2.3.9 -> 1.2.4.0
1.2.3 -> 1.2.4
9 -> 10

Le problème restant est qu'il n'attend pas qu'un éditeur de vin à onglets ferme le fichier; l'objectif est de détecter la fermeture de l'onglet. Pourriez-vous également expliquer la meilleure façon de vous assurer que mes noms de variables n'écrasent pas les noms existants?

Vous pouvez également proposer d'autres améliorations.

#!/bin/bash
#Tested on bash 4.1.5
#All arguments in order: "folder with file" "file pattern" cmd [stable name]
folder="$1"
file_pattern="$2"
cmd="$3"
stable="$4"

cd "$folder"
last_version=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    sed -nr 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p' | \
    sort -Vu | \
    tail -n 1)
last_version_file=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    grep $last_version | \
    tail -n 1) #tail -n 1 is only needed to get 1 line if there are backup files with the same version number
new_version=$(echo $last_version | \
    gawk -F"." '{$NF+=1}{print $0RT}' OFS="." ORS="") #increments last section indefinitely
new_version_file=$(echo "$last_version_file" | \
    sed -r "s/$last_version/$new_version/")
cp "$last_version_file" "$new_version_file"
"$cmd" "$new_version_file" & \
    wait #works with gedit but not with wine tabbed editor
[[ "$stable" ]] && \
    cp "$new_version_file" "$stable" #True if the length of string is non-zero.

Mise à jour: les éléments suivants fonctionnent sur mon PC, je le mettrai à jour si des améliorations ou des solutions aux problèmes non résolus sont trouvées:

#!/bin/bash
inc()
{
shopt -s extglob
    num=${last_version//./}
    let num++

    re=${last_version//./)(}
    re=${re//[0-9]/.}')'
    re=${re#*)}

    count=${last_version//[0-9]/}
    count=$(wc -c<<<$count)
    out=''
    for ((i=count-1;i>0;i--)) ; do
        out='.\\'$i$out
    done

    sed -r s/$re$/$out/ <<<$num
}

folder="$1"
file_pattern="$2"
cmd="$3"
stable="$4"

cd "$folder"
last_version=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    sed -nr 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p' | \
    sort -Vu | \
    tail -n 1) #--almost-all do not list implied . and ..
last_version_file=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    grep $last_version | \
    tail -n 1) #tail -n 1 is only needed to get 1 line if there are backup files with the same version number
new_version=$(inc)
new_version_file=$(echo "$last_version_file" | \
    sed -r "s/$last_version/$new_version/")
cp "$last_version_file" "$new_version_file"
"$cmd" "$new_version_file" && \
    wait #works with gedit but not tabbed wine editor
[[ "$stable" ]] && \
    cp "$new_version_file" "$stable" #True if the length of string is non-zero.

J'apprécie la variété des solutions qui ont été proposées, car elles aident à acquérir une perspective et à faire une comparaison.

31
nnn
$ echo 1.2.3.4 | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}'
1.2.3.5

1.2.3.9  => 1.2.4.0
1.2.3.44 => 1.2.3.45
1.2.3.99 => 1.2.4.00
1.2.3.999=> 1.2.4.000
1.2.9    => 1.3.0
999      => 1000

METTRE À JOUR:

#!/usr/bin/gawk -f

BEGIN{
    v[1] = "1.2.3.4"
    v[2] = "1.2.3.44"
    v[3] = "1.2.3.99"
    v[4] = "1.2.3"
    v[5] = "9"
    v[6] = "9.9.9.9"
    v[7] = "99.99.99.99"
    v[8] = "99.0.99.99"
    v[9] = ""

    for(i in v)
        printf("#%d: %s => %s\n", i, v[i], inc(v[i])) | "sort | column -t"
}

function inc(s,    a, len1, len2, len3, head, tail)
{
    split(s, a, ".")

    len1 = length(a)
    if(len1==0)
        return -1
    else if(len1==1)
        return s+1

    len2 = length(a[len1])
    len3 = length(a[len1]+1)

    head = join(a, 1, len1-1)
    tail = sprintf("%0*d", len2, (a[len1]+1)%(10^len2))

    if(len2==len3)
        return head "." tail
    else
        return inc(head) "." tail
}

function join(a, x, y,    s)
{
    for(i=x; i<y; i++)
        s = s a[i] "."
    return s a[y]
}

$ chmod +x inc.awk
$ ./inc.awk
#1:  1.2.3.4      =>  1.2.3.5
#2:  1.2.3.44     =>  1.2.3.45
#3:  1.2.3.99     =>  1.2.4.00
#4:  1.2.3        =>  1.2.4
#5:  9            =>  10
#6:  9.9.9.9      =>  10.0.0.0
#7:  99.99.99.99  =>  100.00.00.00
#8:  99.0.99.99   =>  99.1.00.00
#9:  =>           -1
46
kev

Voici quelques options plus flexibles. Les deux acceptent un deuxième argument pour indiquer la position à incrémenter.

1. Fonction simple

Pour une entrée plus prévisible.

# Usage: increment_version <version> [<position>]
increment_version() {
 local v=$1
 if [ -z $2 ]; then 
    local rgx='^((?:[0-9]+\.)*)([0-9]+)($)'
 else 
    local rgx='^((?:[0-9]+\.){'$(($2-1))'})([0-9]+)(\.|$)'
    for (( p=`grep -o "\."<<<".$v"|wc -l`; p<$2; p++)); do 
       v+=.0; done; fi
 val=`echo -e "$v" | Perl -pe 's/^.*'$rgx'.*$/$2/'`
 echo "$v" | Perl -pe s/$rgx.*$'/${1}'`printf %0${#val}s $(($val+1))`/
}

# EXAMPLE   ------------->   # RESULT
increment_version 1          # 2
increment_version 1.0.0      # 1.0.1
increment_version 1 2        # 1.1
increment_version 1.1.1 2    # 1.2
increment_version 00.00.001  # 00.00.002

2. Fonction robuste

Pour une utilisation avec des scripts, ou plus de personnalisation à appliquer à divers systèmes de gestion des versions. Il pourrait utiliser quelques options supplémentaires, mais en l'état, il fonctionne pour mes projets en utilisant les séquences de version "major.minor [.maintenance [.build]]".

# Accepts a version string and prints it incremented by one.
# Usage: increment_version <version> [<position>] [<leftmost>]
increment_version() {
   local usage=" USAGE: $FUNCNAME [-l] [-t] <version> [<position>] [<leftmost>]
           -l : remove leading zeros
           -t : drop trailing zeros
    <version> : The version string.
   <position> : Optional. The position (starting with one) of the number 
                within <version> to increment.  If the position does not 
                exist, it will be created.  Defaults to last position.
   <leftmost> : The leftmost position that can be incremented.  If does not
                exist, position will be created.  This right-padding will
                occur even to right of <position>, unless passed the -t flag."

   # Get flags.
   local flag_remove_leading_zeros=0
   local flag_drop_trailing_zeros=0
   while [ "${1:0:1}" == "-" ]; do
      if [ "$1" == "--" ]; then shift; break
      Elif [ "$1" == "-l" ]; then flag_remove_leading_zeros=1
      Elif [ "$1" == "-t" ]; then flag_drop_trailing_zeros=1
      else echo -e "Invalid flag: ${1}\n$usage"; return 1; fi
      shift; done

   # Get arguments.
   if [ ${#@} -lt 1 ]; then echo "$usage"; return 1; fi
   local v="${1}"             # version string
   local targetPos=${2-last}  # target position
   local minPos=${3-${2-0}}   # minimum position

   # Split version string into array using its periods. 
   local IFSbak; IFSbak=IFS; IFS='.' # IFS restored at end of func to                     
   read -ra v <<< "$v"               #  avoid breaking other scripts.

   # Determine target position.
   if [ "${targetPos}" == "last" ]; then 
      if [ "${minPos}" == "last" ]; then minPos=0; fi
      targetPos=$((${#v[@]}>${minPos}?${#v[@]}:$minPos)); fi
   if [[ ! ${targetPos} -gt 0 ]]; then
      echo -e "Invalid position: '$targetPos'\n$usage"; return 1; fi
   (( targetPos--  )) || true # offset to match array index

   # Make sure minPosition exists.
   while [ ${#v[@]} -lt ${minPos} ]; do v+=("0"); done;

   # Increment target position.
   v[$targetPos]=`printf %0${#v[$targetPos]}d $((10#${v[$targetPos]}+1))`;

   # Remove leading zeros, if -l flag passed.
   if [ $flag_remove_leading_zeros == 1 ]; then
      for (( pos=0; $pos<${#v[@]}; pos++ )); do
         v[$pos]=$((${v[$pos]}*1)); done; fi

   # If targetPosition was not at end of array, reset following positions to
   #   zero (or remove them if -t flag was passed).
   if [[ ${flag_drop_trailing_zeros} -eq "1" ]]; then
        for (( p=$((${#v[@]}-1)); $p>$targetPos; p-- )); do unset v[$p]; done
   else for (( p=$((${#v[@]}-1)); $p>$targetPos; p-- )); do v[$p]=0; done; fi

   echo "${v[*]}"
   IFS=IFSbak
   return 0
}

# EXAMPLE   ------------->   # RESULT
increment_version 1          # 2
increment_version 1 2        # 1.1
increment_version 1 3        # 1.0.1
increment_version 1.0.0      # 1.0.1
increment_version 1.2.3.9    # 1.2.3.10
increment_version 00.00.001  # 00.00.002
increment_version -l 00.001  # 0.2
increment_version 1.1.1.1 2   # 1.2.0.0
increment_version -t 1.1.1 2  # 1.2
increment_version v1.1.3      # v1.1.4
increment_version 1.2.9 2 4     # 1.3.0.0
increment_version -t 1.2.9 2 4  # 1.3
increment_version 1.2.9 last 4  # 1.2.9.1

Évidemment, c'est excessif juste pour incrémenter une chaîne de version. Mais j'ai écrit cela parce que j'avais besoin de différents types de projets, et parce que si la vitesse n'est pas un problème, je préfère la réutilisabilité à l'ajustement du même code sur des dizaines de scripts. Je suppose que c'est juste mon côté orienté objet qui s'infiltre dans mes scripts.

23
Stephen M. Harris

Voici une version encore plus courte qui prend également en charge un suffixe (Nice pour -SNAPSHOT)

$ cat versions
1.2.3.44
1.2.3.9
1.2.3
9
42.2-includes-postfix

$ Perl -pe 's/^((\d+\.)*)(\d+)(.*)$/$1.($3+1).$4/e' < versions
1.2.3.45
1.2.3.10
1.2.4
10
42.3-includes-postfix

Explication

J'ai utilisé regex pour capturer 3 parties. Le truc avant la dernière position, le nombre à incrémenter et le truc après.

  • ((\d+\.)*) - des trucs du 1.1.1.1.1.
  • (\d+) - le dernier chiffre
  • (.*) - le truc après le dernier chiffre

J'utilise ensuite l'option e pour autoriser les expressions dans la pièce de remplacement. Remarque avec l'option e\1 devient une variable $ 1 et vous devez concaténer des variables avec l'opérateur point.

  • $1 - le groupe de capture de 1.1.1.1.1.
  • ($3+1) - incrémente le dernier chiffre. note $ 2 est utilisé dans le sous-groupe de $ 1 pour obtenir le 1 répété.
  • $4 - le contenu après le dernier chiffre
21
Pyrolistical

Pure Bash:

increment_version ()
{
  declare -a part=( ${1//\./ } )
  declare    new
  declare -i carry=1

  for (( CNTR=${#part[@]}-1; CNTR>=0; CNTR-=1 )); do
    len=${#part[CNTR]}
    new=$((part[CNTR]+carry))
    [ ${#new} -gt $len ] && carry=1 || carry=0
    [ $CNTR -gt 0 ] && part[CNTR]=${new: -len} || part[CNTR]=${new}
  done
  new="${part[*]}"
  echo -e "${new// /.}"
} 

version='1.2.3.44'

increment_version $version

résultat:

1.2.3.45

La chaîne de version est divisée et stockée dans le tableau partie. La boucle va de la dernière à la première partie de la version. La dernière partie sera incrémentée et éventuellement réduite à sa longueur d'origine. Un report est effectué sur la partie suivante.

11
Fritz G. Mehner

Fatigué de bash? Pourquoi ne pas essayer Perl?

$ cat versions
1.2.3.44
1.2.3.9
1.2.3
9

$ cat versions | Perl -ne 'chomp; print join(".", splice(@{[split/\./,$_]}, 0, -1), map {++$_} pop @{[split/\./,$_]}), "\n";'
1.2.3.45
1.2.3.10
1.2.4
10

Pas tout à fait conforme à l'exigence, bien sûr.

1
Sorpigal

La détermination d'un numéro de version pour un projet logiciel est basée sur sa modification/fonctionnalité/étape de développement/révision relative. Les augmentations consécutives de la numérotation des versions et des révisions sont idéalement un processus qui devrait être effectué par un humain. Cependant, pour ne pas deviner votre motivation pour écrire ce script, voici ma suggestion.

Inclure une logique dans votre script qui fera exactement ce que vous décrivez dans votre exigence

"... si la dernière section après le point comporte deux chiffres, augmentez jusqu'à 99; si seulement 1, augmentez jusqu'à 9 ..."

En supposant que la troisième position est le numéro de stade de développement $dNum et la quatrième (dernière) position est le numéro de révision $rNum:

if  [ $(expr length $rNum) = "2" ] ; then 
    if [ $rNum -lt 99 ]; then 
        rNum=$(($rNum + 1))
    else rNum=0
         dNum=$(($dNum + 1)) #some additional logic for $dNum > 9 also needed
    fi
Elif [ $(expr length $dNum) = "1" ] ; then
    ...
    ...
fi

Peut-être qu'une fonction permettra la manière la plus succincte de gérer toutes les positions (majNum.minNum.dNum.rNum).

Vous devrez séparer le nom du projet et les composants du numéro de version de votre nom de fichier dans votre script, puis construire le numéro de version avec toutes ses positions, et enfin reconstruire le nom de fichier avec quelque chose comme

new_version="$majNum.minNum.$dNum.$rNum"
new_version_file="$old_file.$new_version"

J'espère que cela aide et vérifiez cette SO ainsi que cette entrée wikipedia si vous voulez en savoir plus sur les conventions de version.

1
venzen

Une autre option consiste à utiliser Python. Je pense que de cette façon, c'est un peu plus lisible que d'utiliser Bash ou Perl.

function increase_version() {
    python - "$1" <<EOF
import sys
version = sys.argv[1]
base, _, minor = version.rpartition('.')
print(base + '.' + str(int(minor) + 1))
EOF
}
0
kokosing

En utilisant simplement bash, wc et sed:

#! /bin/bash
for v in 1.2.3.44 1.2.3.9 1.2.3 9 1.4.29.9 9.99.9 ; do
    echo -n $v '-> '

    num=${v//./}
    let num++

    re=${v//./)(}
    re=${re//[0-9]/.}')'
    re=${re#*)}

    count=${v//[0-9]/}
    count=$(wc -c<<<$count)
    out=''
    for ((i=count-1;i>0;i--)) ; do
        out='.\'$i$out
    done

    sed -r s/$re$/$out/ <<<$num
done
0
choroba