Подтвердить что ты не робот

Как увеличить номер версии в оболочке script?

Следующее простое управление версиями script предназначено для поиска последнего номера версии данного файла, его увеличения, выполнения заданной команды с вновь созданным файлом (например, редактора), а после этого сохраните его до стабильного. Поскольку он прост, он ничего не проверяет, так как script будет изменен по мере необходимости. Например, если результат не будет стабильным, пользователь может опустить последний аргумент.

Однако одна из основных проблем текущей функции заключается в том, как реализовать следующее: если последний раздел после точки имеет две цифры, в том числе до 99; если только 1, затем inc до 9, затем перейдите к предыдущему разделу. Версии могут иметь любое положительное целое число секций.

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

Оставшаяся проблема заключается в том, что она не дожидается, когда редактор файлов с вкладками будет закрывать файл; цель состоит в том, чтобы обнаружить, когда вкладка закрыта. Кроме того, не могли бы вы объяснить, как лучше всего убедиться, что мои имена переменных не перезаписывают существующие?

Вы также можете предложить другие улучшения.

#!/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.

Обновление: Следующее работает на моем компьютере, я обновлю его, если будут найдены улучшения или решения нерешенных проблем:

#!/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.

Я ценю разнообразие предлагаемых решений, поскольку они помогают получить перспективу и провести сравнение.

4b9b3361

Ответ 1

$ 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

UPDATE:

#!/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

Ответ 2

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

результат:

1.2.3.45

Строка версии разделяется и сохраняется в массиве. Цикл переходит от последней к первой части версии. Последняя часть будет увеличиваться и, возможно, сокращаться до ее оригинальная длина. Перенос переносится в следующую часть.

Ответ 3

Вот несколько более гибких опций. Оба принимают второй аргумент, чтобы указать, какая позиция должна увеличиваться.

1. Простая функция

Для более предсказуемого ввода.

# 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. Надежная функция

Для использования со скриптами или более настраиваемостью для применения к различным системам управления версиями. Он может использовать еще несколько опций, но теперь он работает для моих проектов, используя последовательности версий "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

Очевидно, что это слишком просто, чтобы увеличить строку версии. Но я написал это, потому что мне нужны были проекты разных типов, и потому что, если скорость не является проблемой, я предпочитаю повторное использование над настройкой одного и того же кода на десятках сценариев. Я предполагаю, что только моя объектно-ориентированная сторона протекает в мои скрипты.

Ответ 4

Вот еще более короткая версия, которая также поддерживает постфикс (nice для -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

Описание

Я использовал regex для захвата 3 частей. Материал перед последней позицией, число для увеличения и материал после.

  • ((\d+\.)*) - материал от 1.1.1.1.1.
  • (\d+) - последняя цифра
  • (.*) - материал после последней цифры

Затем я использую параметр e, чтобы разрешить выражения в заменяемой части. Примечание с опцией e\1 становится переменной $1, и вам нужно объединить переменные с оператором точки.

  • $1 - группа захвата 1.1.1.1.1.
  • ($3+1) - приращение последней цифры. примечание: $2 используется в подгруппе $1 для получения повторения 1.
  • $4 - материал после последней цифры

Ответ 5

Определение номера версии для программного проекта основано на его относительном изменении/функциональности/стадии разработки/пересмотре. Последовательные приращения к версии и нумерации версий - это идеальный процесс, который должен выполнять человек. Однако, чтобы не догадываться о вашей мотивации для написания этого script, вот мое предложение.

Включите некоторую логику в script, которая будет выполнять именно то, что вы описываете в своем требовании

"... если последний раздел после точки имеет две цифры, inc до 99, если только 1, тогда inc до 9..."

Предполагая, что третья позиция - номер этапа разработки $dNum, а четвертая (последняя) позиция - номер версии $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

Возможно, функция позволит наиболее краткий способ обработки всех позиций (majNum.minNum.dNum.rNum).

Вам нужно будет разделить компоненты имени проекта и номера версии вашего имени файла в script, а затем построить номер версии со всеми его позициями и, наконец, восстановить имя файла с чем-то вроде

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

Надеюсь, что это поможет и проверит эту дискуссию, а также эту запись в википедию если вы хотите узнать больше об условных обозначениях версий.

Ответ 6

Устали от bash? Почему бы не попробовать 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

Не совсем в соответствии с требованием, конечно.

Ответ 7

Используя только bash, wc и 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